0%

给时间时间,让过去过去。
This trait provides a temporary($relationship) method on your model for you to access relationships without adding them to the list of loaded relationships.

If the relationship is already loaded, it is returned directly. If the relationship is not loaded, it will be loaded, removed from the list of loaded relationships, and then returned.

The function is wrapped in Spatie’s once package to add memoization. This allows the same temporary relationship to be called multiple times without multiple DB queries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

namespace App\Models\Traits;

trait TemporaryRelationships
{
/**
* Returns the value of the given relationship from the current model.
*
* If the relation is already loaded, it is returned directly.
*
* If the relation is not loaded, it is loaded, then removed
* from the list of loaded relations, and then returned.
*
* The function is memoized so accessing the same temporary
* relation within a request will only make one query.
*/
public function temporary(string $relation)
{
return once(function () use ($relation) {
if ($this->relationLoaded($relation)) {
return $this->{$relation};
}

$relationValue = $this->getRelationValue($relation);
$this->unsetRelation($relation);

return $relationValue;
});
}
}

Use it within a model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

namespace App\Models;

use App\Models\Traits\TemporaryRelations;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
use TemporaryRelations;

public function someMethod()
{
return $this->temporary('author');
}

public function author()
{
return $this->belongsTo(User::class);
}
}

https://owenconti.com/posts/temporary-relationship-trait-for-laravel

佛是过来人,人是未来佛,我也曾如你般天真。

Laravel provides a simple way to declare event listeners out of the box via the EventServiceProvider class.

Here’s a quick example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
LoginEvent::class => [
HandleLoginListener::class
],
];
}

With the above, anytime the LoginEvent is fired, the handle() method of the HandleLoginListener class will be called. Pretty simple, right?

But what if you have dozens or even hundreds of events that you want to all go through the same handler?

One option would be to list them out individually in the EventServiceProvider class.

Yikes! What if you forget to add the listener when you add a new event?

Another option would be to listen to an interface, and have your events implement the interface.

Here’s an example interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// App\Events\NotifiableEvent.php

<?php

namespace App\Events;

interface NotifiableEvent
{
/**
* Returns the display name of the event.
*
* @return string
*/
public function getEventName(): string;

/**
* Returns the description of the event.
*
* @return string
*/
public function getEventDescription(): string;
}

And here’s an example event, that implements the interface:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// App\Events\CreatedApplicationMember.php

<?php

namespace App\Events;

class CreatedApplicationMember implements NotifiableEvent
{
public function getEventName(): string
{
return 'Created Application Member';
}

public function getEventDescription(): string
{
return 'Fired whenever a new Application Member is added to your Application.';
}

// constructor and stuff goes here...

Then in EventServiceProvider, you can listen for the interface instead of the specific event classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* @var array
*/
protected $listen = [
NotifiableEvent::class => [
SendEventNotification::class
],
];
}

Now anytime an event that implements the NotifiableEvent interface is dispatched, the SendEventNotification listener will be called.

https://owenconti.com/posts/how-to-handle-multiple-events-with-a-single-listener-in-laravel

哪里会有人喜欢孤独,不过是不喜欢失望

If you’re serving any sort of content site with Laravel, you’ve probably looked into setting up caching for your page responses. For this site owenconti.com, I’m letting Cloudflare handle the caching for me. I do this by setting up a Page Rule to “cache everything”:

The above page rule will cache everything that the Laravel app returns. However, Cloudflare does not cache responses that modify cookies. By default, Laravel’s web middleware is setup to handle sessions for you.

Removing Session Middleware

Since this site is purely for anonymous visitors, I’ll never need to use sessions or cookies of any sort. Because of that, I am able to remove all of the session and cookie middlewares from the web middleware group:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// app\Http\Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
protected $middleware = [
// ...
];

/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class, //
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],

'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

After removing the session and cookie middlewares, Cloudflare will start to properly cache HTML responses from the Laravel application.

You can validate this by checking the response headers of the HTML response:

1
2
3
cache-control: public, max-age=3600, s-maxage=86400
cf-cache-status: HIT
cf-ray: 6803f1964956e472-SEA

https://owenconti.com/posts/caching-laravel-html-with-cloudflare

哪有什么坚强,不过都是硬撑罢了。

编译安装php7.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
./configure --prefix=/usr/local/php \
--with-config-file-path=/usr/local/php/etc \
--with-config-file-scan-dir=/usr/local/php/etc/php.d \
--with-fpm-user=www \
--with-fpm-group=www \
--enable-fpm \
--enable-opcache \
--disable-fileinfo \
--enable-mysqlnd \
--with-mysqli=mysqlnd \
--with-pdo-mysql=mysqlnd \
--with-iconv-dir=/usr/local/libiconv \
--with-freetype \
--with-jpeg \
--with-zlib \
--enable-xml \
--disable-rpath \
--enable-bcmath \
--enable-shmop \
--enable-exif \
--enable-sysvsem \
--enable-inline-optimization \
--with-curl=/usr/local/curl \
--enable-mbregex \
--enable-mbstring \
--with-password-argon2 \
--with-sodium=/usr/local \
--enable-gd \
--with-openssl=/usr/local/openssl \
--with-mhash \
--enable-pcntl \
--enable-sockets \
--with-xmlrpc \
--enable-ftp \
--enable-intl \
--with-xsl \
--with-gettext \
--with-zip=/usr/local \
--enable-soap \
--disable-debug \
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/:

今天就出现了一个问题,我的gd库中没有webp支持,看看错误提示

1
Call to undefined function imagecreatefromwebp()

重新编译GD库

重新编译gd,但是又由于我采用的是静态编译,所以必须要重新编译php,然后编译gd,最后在php.ini文件中添加扩展so文件。

重新编译php

不要添加 --enable-gd 参数即可

编译GD

1
yum install libwebp-devel libwebp autoconf automake gcc libjpeg-devel libpng-devel libXpm-devel freetype-devel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wget https://www.php.net/distributions/php-7.4.28.tar.bz2
tar xf php-7.4.28.tar.bz2
cd php-7.4.28
cd ext/gd/
phpize

## Run the actual configure

./configure --with-php-config=/usr/local/php/bin/php-config --with-gd --enable-gd --with-jpeg=/usr/include --with-webp=/usr/include/webp

make && make install

## Copy new gd.so file in place, backup old one
cp /usr/lib64/php/modules/gd.so ~/gd.so.bak
cp /var/www/php-7.4.28/ext/gd/modules/gd.so /usr/lib64/php/modules/gd.so

# Right after, otherwise server goes down.
sudo service httpd restart

# Check if WebP support is enabled:
php -r "print_r( gd_info() );"

编辑php.ini

1
2
3
echo extension=gd.so >> /usr/local/php/lib/php.ini

extension = gd.so
1
systemctl reload php-fpm

其他

尚未尝试

1
--enable-gd --with-webp --with-jpeg  --with-freetype --enable-gd-jis-conv  --with-iconv-dir=/usr/local/lib

可一想到终将是你的路人, 便觉得,沦为整个世界的路人。

PHP 库包含了哪些扩展

1
php -m

查看动态链接库位置

1
php -i | grep extension_dir

执行PHP代码

1
php -r "print_r( gd_info() );"

写文件

1
echo extension=gd.so >> /usr/local/php/lib/php.ini

编译方式

默认情况下,大多数的扩展都是静态编译的,即它们将成为生成的二进制文件的一部分。默认只有 opcache 扩展共享,即它将在 modules/ 目录生成一个 opcache.so 共享对象 。你可以通过 --enable-NAME=shared 或者 --with-NAME=shared 将其他扩展编译成共享对象(但不是所有的扩展支持这个)。

修复编译问题

如果运行 make 时遇到奇怪的错误或生成的二进制文件损坏(例如,在运行第一次测试之前, make test 就崩溃了),你应该尝试运行 make clean。它会删除所有已编译的对象,强制下一次 make 调用运行完整构建。

有时候,你必须在更改 ./configure 选项之后运行 make clean。 如果只是启用额外的扩展,则增量构建应是安全的,但是改变其他的选项可能需要完全重建。

共享扩展

PHP 扩展既能构建成静态库也可以构建成动态库(.so)。大多数静态库是与 PHP 捆绑在一起编译的,动态库可以显式地传递参数 --enable-EXTNAME=shared--with-EXTNAME=shared./configure

静态扩展默认是可用的,动态库需要增加 extension 或者 zend_extensionini 配置。俩者可以是绝对路径,也可以是相对路径。

从 PECL 安装扩展

1
~/myphp> bin/pecl install apcu

该命令将下载、编译并安装 APCu 扩展。结果会是 apcu.so 文件在扩展目录下,可以通过传递 extension=apcu.so 配置选项来加载此文件。

使用 phpize 构建扩展

进入扩展源码目录执行phpize

1
2
3
4
5
6
7
8
/tmp/apcu-4.0.2> ~/myphp/bin/phpize
Configuring for:
PHP Api Version: 20121113
Zend Module Api No: 20121113
Zend Extension Api No: 220121113

/tmp/apcu-4.0.2> ./configure --with-php-config=$HOME/myphp/bin/php-config
/tmp/apcu-4.0.2> make -jN && make install

当你构建扩展时,你应该总是指定 --with-php-config 选项(除非你只有一个全局的 PHP 安装),否则 ./configure 无法确定要构建的 PHP 版本和标志。指定 php-config 脚本也确保了 make install 将移动生成的 .so 文件(可以在 modules/ 目录找到)到正确的扩展目录。

删除已编译对象的 make clean 也是可用的,并且允许你增量构建失败时强制重新构建扩展。 另外 phpize 提供了一个清理选项 phpize --clean。该命令将删除所有 phpize 导入的文件和通过 /configure 脚本生成的文件。

https://learnku.com/docs/php-internals/php7/building_extensions/6849
https://digitalist-tech.se/blogg/webp-enabling-php-gd-library

人为什么害怕独处呢?

因为人在独处的时候,总是会听到心里深处的声音,这个心灵深处的声音会不断逼问你,你这一生到底想干嘛?

Pseudorandom numbers

The math/rand package provided by the Go Standard Library gives us pseudo-random number generators (PRNG), also called deterministic random bit generators.

As with all pseudo number generators, any number generated through math/rand is not really random by default, as being deterministic it will always print the same value each time.

As an example, try running this code which introduces rand.Intn(n), which returns a random number between 0 and n - 1.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"fmt"
"math/rand"
)

func main() {
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
}

You’ll always see the same sequence every time you run the program. The random number changes inside the program, but every time you run it, you’ll get the same output:

1
2
3
4
5
11
27
17
29
1

This is because by default the seed is always the same, the number 1. To actually get a random number, you need to provide a unique seed for your program. You really want to not forget seeding, and instead properly seed our pseudonumber generator. How?

Use rand.Seed() before calling any math/rand method, passing an int64 value. You just need to seed once in your program, not every time you need a random number. The most used seed is the current time, converted to int64 by UnixNano with rand.Seed(time.Now().UnixNano()):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
"math/rand"
"time"
)

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
fmt.Println(rand.Intn(30))
}

Remember that due to its sandboxing, the Go Playground always begins with the same time, so this code won’t work as expected. Try it on a real environment, and the numbers that previosly didn’t change, they will now print differently each time you run the program.

Some common examples are listed below for ease of reuse:

Generate a random integer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
"math/rand"
"time"
)

// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomInt(1, 11)) //get an int in the 1...10 range
}

Generate a random string

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package main

import (
"fmt"
"math/rand"
"time"
)

// Returns an int >= min, < max
func randomInt(min, max int) int {
return min + rand.Intn(max-min)
}

// Generate a random string of A-Z chars with len = l
func randomString(len int) string {
bytes := make([]byte, len)
for i := 0; i < len; i++ {
bytes[i] = byte(randomInt(65, 90))
}
return string(bytes)
}

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomString(10)) // print 10 chars
}

Will return 10 chars in uppercase format. Change

1
bytes[i] = byte(randomInt(65, 90))

to

1
bytes[i] = byte(randomInt(97, 122))

for just lowercase.

If you instead want to have a pool of specific chars to pick from, use

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"math/rand"
"time"
)

var pool = "_:$%&/()"

// Generate a random string of A-Z chars with len = l
func randomString(l int) string {
bytes := make([]byte, l)
for i := 0; i < l; i++ {
bytes[i] = pool[rand.Intn(len(pool))]
}
return string(bytes)
}

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomString(1000))
}

Change len(pool) to utf8.RuneCountInString(pool) if you use non-ascii strings, as len() counts the bytes, but not all chars take just one byte in Unicode - see https://stackoverflow.com/questions/12668681/how-to-get-the-number-of-characters-in-a-string.

Generate a random array of integers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
"math/rand"
"time"
)

func randomArray(len int) []int {
a := make([]int, len)
for i := 0; i <= len-1; i++ {
a[i] = rand.Intn(len)
}
return a
}

func main() {
rand.Seed(time.Now().UnixNano())
fmt.Println(randomArray(10))
}

Crypto-level random numbers

Go also provides a Cryptographically secure pseudorandom number generator (CSPRNG) in the standard library package crypto.rand

So you might question, why should I even use the pseudo-number random generator library provided by math/rand instead? Well, it depends on the use case. math/rand is much faster for applications that don’t need crypto-level or security-related random data generation. crypto.rand is suited for secure and crypto-ready usage, but it’s slower.

What it should be used for? For example, generating passwords, CSRF tokens, session keys, or anything remotely related to security.

It does not rely on the current time, like we did in the previous examples in math/rand, but instead it uses the operating system CSPRNG APIs: the CryptGenRandom API on Windows, and /dev/urandom/ on all the others (Linux, OSX, *nix)

You get 256 random bytes directly with

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"crypto/rand"
"fmt"
)

func main() {
key := [256]byte{}
_, err := rand.Read(key[:])
if err != nil {
panic(err)
}
fmt.Println(key)
}

I’m taking a code sample from Matt Silverlock: you can make it more general and create a random bytes generation function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// GenerateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// Note that err == nil only if we read len(b) bytes.
if err != nil {
return nil, err
}

return b, nil
}

and using this, a random string generation function,

1
2
3
4
5
6
7
8
9
// GenerateRandomString returns a URL-safe, base64 encoded
// securely generated random string.
// It will return an error if the system's secure random
// number generator fails to function correctly, in which
// case the caller should not continue.
func GenerateRandomString(s int) (string, error) {
b, err := GenerateRandomBytes(s)
return base64.URLEncoding.EncodeToString(b), err
}

https://flaviocopes.com/go-random/

没有时间学习的人,是有了时间也不会学习的人!

HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>CodePen - 🌜/🌞 Mode</title>
<link rel="stylesheet" href="./style.css">

</head>
<body>
<!-- partial:index.partial.html -->
<div class="container">
<div class="header">
<img class="sun" src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDUxMiA1MTIiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDUxMiA1MTI7IiB4bWw6c3BhY2U9InByZXNlcnZlIiB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiPgo8cGF0aCBzdHlsZT0iZmlsbDojRkZBNjAwOyIgZD0iTTUwNy44MzQsMzAxLjYwOGwtNTQuNzY5LTQ4LjMxMmw1Mi44MzItNTAuMzk5YzEuOTQxLTEuODUyLDIuNzQtNC41OTEsMi4wOTktNy4xODkgIGMtMC42NDItMi41OTctMi42MjktNC42NTUtNS4yMTEtNS40MDFsLTcwLjMxNi0yMC4yOTJsMjUuOTg4LTY4LjA1NmMwLjk1NS0yLjUsMC40OTUtNS4zMTYtMS4yMDQtNy4zODkgIGMtMS43LTIuMDcyLTQuMzgzLTMuMDg1LTcuMDM4LTIuNjU3bC03Mi4yNzQsMTEuNjUybC01Ljg3OC03Mi41NjFjLTAuMjE2LTIuNjY2LTEuODQ2LTUuMDE1LTQuMjc1LTYuMTYxICBjLTIuNDI5LTEuMTQ2LTUuMjkxLTAuOTE3LTcuNTA0LDAuNjAxbC02MC4yNjYsNDEuMzQyTDI2My40MDksMy43NDJDMjYyLjA2NCwxLjQyNiwyNTkuNTc5LDAsMjU2Ljg4OSwwICBjLTIuNjksMC01LjE3NCwxLjQyNi02LjUxOSwzLjc0MkwyMDguMzQ3LDc2LjExbC03Mi42OS00MS45NTNjLTIuMzI3LTEuMzQzLTUuMTk3LTEuMzQ5LTcuNTI4LTAuMDE4ICBjLTIuMzMzLDEuMzMxLTMuNzczLDMuNzk5LTMuNzgsNi40NzNsLTAuMTc2LDcyLjc5NWwtNzIuOTY1LTYuMDE0Yy0yLjY3NS0wLjIyLTUuMjc1LDAuOTk3LTYuODA3LDMuMTk0ICBjLTEuNTMzLDIuMTk3LTEuNzcxLDUuMDQxLTAuNjIyLDcuNDU5bDMxLjI0Miw2NS44MzVMNi41MDgsMjA5LjU2MmMtMi41MTYsMC45NDMtNC4zMzUsMy4xNS00Ljc3Myw1Ljc4OSAgYy0wLjQzOCwyLjYzOCwwLjU3NSw1LjMwOCwyLjY1Niw3LjAwM2w1Ni42MTksNDYuMTUybC01MC44MTMsNTIuNDFjLTEuODY2LDEuOTI1LTIuNTU5LDQuNjk0LTEuODE2LDcuMjY0ICBjMC43NDQsMi41NywyLjgxLDQuNTUsNS40MTksNS4xOTRsNzEuMDU1LDE3LjU1MUw2MS41Niw0MTkuOTM2Yy0wLjg1NiwyLjUzNS0wLjI4Niw1LjMzMSwxLjQ5NCw3LjMzNiAgYzEuNzgxLDIuMDA1LDQuNTAyLDIuOTEzLDcuMTM3LDIuMzgybDcxLjc2LTE0LjQ0M2w4LjcyMSw3Mi4yNzhjMC4zMTksMi42NTUsMi4wNDEsNC45MzgsNC41MTMsNS45OSAgYzIuNDc0LDEuMDUyLDUuMzIzLDAuNzEyLDcuNDc1LTAuODkxbDU4LjU5Ni00My42NDdsMzkuMDU1LDU5LjU2NmMxLjM4MiwyLjE3OSwzLjc4OSwzLjQ5Miw2LjM2NywzLjQ5MiAgYzAuMDk4LDAsMC4xOTYtMC4wMDIsMC4yOTUtMC4wMDZjMi42ODctMC4xMDQsNS4xMTUtMS42MjYsNi4zNjgtMy45OTJsMzQuMTA3LTYyLjQwNWw2MS44MzksMzguOTc0ICBjMi4yNzMsMS40MzIsNS4xMzcsMS41NSw3LjUyMiwwLjMxYzIuMzgyLTEuMjQsMy45MTktMy42NSw0LjAzLTYuMzIybDMuMDMxLTcyLjczNGw3Mi42NzQsOC44NGMyLjY2NCwwLjMyNCw1LjMxLTAuNzkxLDYuOTI4LTIuOTI3ICBjMS42MTgtMi4xMzcsMS45NjYtNC45NjksMC45MTQtNy40M2wtMjguNjM2LTY2Ljk5N2w2OS40NjUtMjMuMDAzYzIuNTUyLTAuODQ1LDQuNDU2LTIuOTc5LDQuOTk3LTUuNTk5ICBDNTEwLjc1MiwzMDYuMDksNTA5Ljg0NSwzMDMuMzgzLDUwNy44MzQsMzAxLjYwOHoiLz4KPGVsbGlwc2Ugc3R5bGU9ImZpbGw6I0ZGREIyRDsiIGN4PSIyNTQuMzUiIGN5PSIyNTQuNjkxIiByeD0iMTU1LjA2OSIgcnk9IjE1NC45NDkiLz4KPHBhdGggc3R5bGU9ImZpbGw6I0ZGQ0EwMDsiIGQ9Ik0yNTQuMzU0LDk5Ljc0M2MtMy44ODQsMC03LjczMiwwLjE0Ny0xMS41NDMsMC40MjhjODAuMjUsNS45MDEsMTQzLjUyNSw3Mi44MjUsMTQzLjUyNSwxNTQuNTIxICBjMCw4MS42OTUtNjMuMjc1LDE0OC42MTktMTQzLjUyNSwxNTQuNTIxYzMuODExLDAuMjgsNy42NiwwLjQyOCwxMS41NDMsMC40MjhjODUuNjQxLDAsMTU1LjA2OC02OS4zNzMsMTU1LjA2OC0xNTQuOTQ4ICBDNDA5LjQyMiwxNjkuMTE2LDMzOS45OTUsOTkuNzQzLDI1NC4zNTQsOTkuNzQzeiIvPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K" />
<img class="moon hide" src="data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDQ5OS43MTIgNDk5LjcxMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNDk5LjcxMiA0OTkuNzEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjUxMnB4IiBoZWlnaHQ9IjUxMnB4Ij4KPHBhdGggc3R5bGU9ImZpbGw6I0ZGRDkzQjsiIGQ9Ik0xNDYuODgsMzc1LjUyOGMxMjYuMjcyLDAsMjI4LjYyNC0xMDIuMzY4LDIyOC42MjQtMjI4LjY0YzAtNTUuOTUyLTIwLjE2LTEwNy4xMzYtNTMuNTItMTQ2Ljg4ICBDNDI1LjA1NiwzMy4wOTYsNDk5LjY5NiwxMjkuNjQsNDk5LjY5NiwyNDMuNzA0YzAsMTQxLjM5Mi0xMTQuNjA4LDI1Ni0yNTYsMjU2Yy0xMTQuMDY0LDAtMjEwLjYwOC03NC42NC0yNDMuNjk2LTE3Ny43MTIgIEMzOS43NDQsMzU1LjM2OCw5MC45NDQsMzc1LjUyOCwxNDYuODgsMzc1LjUyOHoiLz4KPHBhdGggc3R5bGU9ImZpbGw6I0Y0QzUzNDsiIGQ9Ik00MDEuOTIsNDIuNzc2YzM0LjI0LDQzLjUwNCw1NC44MTYsOTguMjcyLDU0LjgxNiwxNTcuOTUyYzAsMTQxLjM5Mi0xMTQuNjA4LDI1Ni0yNTYsMjU2ICBjLTU5LjY4LDAtMTE0LjQ0OC0yMC41NzYtMTU3Ljk1Mi01NC44MTZjNDYuODQ4LDU5LjQ3MiwxMTkuMzQ0LDk3Ljc5MiwyMDAuOTI4LDk3Ljc5MmMxNDEuMzkyLDAsMjU2LTExNC42MDgsMjU2LTI1NiAgQzQ5OS43MTIsMTYyLjEyLDQ2MS4zOTIsODkuNjQsNDAxLjkyLDQyLjc3NnoiLz4KPGc+Cgk8cG9seWdvbiBzdHlsZT0iZmlsbDojRkZEODNCOyIgcG9pbnRzPSIxMjguMTI4LDk5Ljk0NCAxNTQuNDk2LDE1My40IDIxMy40NzIsMTYxLjk2IDE3MC44LDIwMy41NiAxODAuODY0LDI2Mi4yOTYgICAgMTI4LjEyOCwyMzQuNTY4IDc1LjM3NiwyNjIuMjk2IDg1LjQ0LDIwMy41NiA0Mi43NjgsMTYxLjk2IDEwMS43NDQsMTUzLjQgICIvPgoJPHBvbHlnb24gc3R5bGU9ImZpbGw6I0ZGRDgzQjsiIHBvaW50cz0iMjc2Ljg2NCw4Mi44NCAyOTAuNTI4LDExMC41NTIgMzIxLjEwNCwxMTQuOTg0IDI5OC45NzYsMTM2LjU1MiAzMDQuMjA4LDE2Ni45ODQgICAgMjc2Ljg2NCwxNTIuNjE2IDI0OS41MiwxNjYuOTg0IDI1NC43NTIsMTM2LjU1MiAyMzIuNjI0LDExNC45ODQgMjYzLjIsMTEwLjU1MiAgIi8+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPGc+CjwvZz4KPC9zdmc+Cg==" />
</div>
<div class="content">
<h1>Hello</h1>
<p>Click the sun/moon</p>
</div>
</div>
<!-- partial -->
<script src="./script.js"></script>

</body>
</html>

CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
:root {
--primary-color: #1B1E24;
--secondary-color: #1B1E24;
--font-color: #1B1E24;
--bg-color: #fff;
--heading-color: #1B1E24;
}

[data-theme=dark] {
--primary-color: #fff;
--secondary-color: #fff;
--font-color: #fff;
--bg-color: #1B1E24;
--heading-color: #fff;
}

body {
background-color: var(--bg-color);
}

.container {
position: relative;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: 97vh;
}
.container .content h1 {
color: var(--primary-color);
font-family: "Dosis", sans-serif;
font-size: 5rem;
}
.container .content p {
color: var(--primary-color);
font-family: "Dosis", sans-serif;
font-size: 2rem;
}

.header {
position: absolute;
top: 10px;
right: 20px;
}
.header .moon, .header .sun {
width: 34px;
cursor: pointer;
transition: 0.5s all ease-in-out;
}
.header .sun:hover {
-webkit-animation: sun_rotate 1s infinite;
animation: sun_rotate 1s infinite;
}
.header .moon:hover {
transform: scale(1.1);
}
.header .hide {
display: none;
}

@-webkit-keyframes sun_rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

@keyframes sun_rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const lightMode = document.querySelector('.sun');
const darkMode = document.querySelector('.moon');

const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null;

lightMode.onclick = function(){
document.documentElement.setAttribute('data-theme', 'dark');
lightMode.style = "display: none";
darkMode.style = "display: block";
localStorage.setItem('theme', 'dark');
}

darkMode.onclick = function(){
document.documentElement.setAttribute('data-theme', 'light');
lightMode.style = "display: block";
darkMode.style = "display: none";
localStorage.setItem('theme', 'light');

}

if (currentTheme) {
document.documentElement.setAttribute('data-theme', currentTheme);

if (currentTheme === 'dark') {
lightMode.style = "display: none";
darkMode.style = "display: block";
}
}

你说我会遇到更好的人,其实是你想拥有更好的人

我们有时候需要在一台电脑上管理两个或者两个以上的 GitHub 账号,例如要在电脑上同时使用个人和公司的 Github 账号,那么如何操作呢?

创建新的 SSH Key

假设个人电脑上已经有了一个正使用的 GitHub 账号 user_a ,现在添加第二个 GitHub 账号 user_b 的配置。
打开终端生成一个新的 SSH Key:

1
ssh-keygen -t rsa -C "user_b@gmail.com"

注意,新生成的密钥不要覆盖原来的,在 ssh-keygent 运行中让新生成的 key 命名为 user_b。
在我电脑里面,它被保存在 ~/.ssh/user_b_id_rsa
把新生成的 SSH key 添加到 ssh-agent:

1
2
$ eval "$(ssh-agent -s)"
$ ssh-add ~/.ssh/user_b_id_rsa

在第二个 GitHub 账户里面添加 新生成 user_b_id_rsa.pub

登录第二个 Github 账号,把 user_b_id_rsa.pub 添加到账号里面。

在~/.ssh 目录下新建 config 文件

1
$ touch ~/.ssh/config
1
2
3
4
5
6
7
8
9
Host user_a-github.com
HostName github.com
User git
IdentityFile ~/.ssh/user_a_id_rsa

Host user_b-github.com
HostName github.com
User git
IdentityFile ~/.ssh/user_b_id_rsa

测试连接

1
2
$ ssh -T git@user_a-github.com
$ ssh -T git@user_b-github.com

若成功,都会接收到类似这样的信息:

1
2
Hi user_a! You've successfully authenticated, but GitHub does not provide shell access.
Hi user_b! You've successfully authenticated, but GitHub does not provide shell access.

测试用把仓库提交到第二个 Github 账号

新建一个文件夹,初始化为仓库,创建第一个 commit

1
2
3
4
5
6
7
$ mkdir Test
$ cd Test
$ git init
$ git commit -am "first commit"
$ git remote add origin git@user_b-github.com:user_b/testing.git
$ git branch -M main
$ git push -u origin main

注意:为本地仓库添加远程地址时,要替换为 config 文件里面的 Host 。
例如此处: user_b-github.com

1
git remote add origin git@user_b-github.com:user_b/testing.git

https://learnku.com/articles/59358

What is Panic?

The idiomatic way of handling abnormal conditions in a Go program is using errors. Errors are sufficient for most of the abnormal conditions arising in the program.

But there are some situations where the program cannot continue execution after an abnormal condition. In this case, we use panic to prematurely terminate the program. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates. This concept will be more clear when we write an example program.

It is possible to regain control of a panicking program using recover which we will discuss later in this tutorial.

panic and recover can be considered similar to try-catch-finally idiom in other languages such as Java except that they are rarely used in Go.

When Should Panic Be Used?

One important factor is that you should avoid panic and recover and use errors where ever possible. Only in cases where the program just cannot continue execution should panic and recover mechanism be used.

There are two valid use cases for panic.

  • An unrecoverable error where the program cannot simply continue its execution.
    One example is a web server that fails to bind to the required port. In this case, it’s reasonable to panic as there is nothing else to do if the port binding itself fails.

  • A programmer error.
    Let’s say we have a method that accepts a pointer as a parameter and someone calls this method using a nil argument. In this case, we can panic as it’s a programmer error to call a method with nil argument which was expecting a valid pointer.

Panic Example

The signature of the built-in panic function is provided below,

1
func panic(interface{})  

The argument passed to the panic function will be printed when the program terminates. The use of this will be clear when we write a sample program. So let’s do that right away.

We will start with a contrived example which shows how panic works.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

func fullName(firstName *string, lastName *string) {
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

The above is a simple program to print the full name of a person. The fullName function in line no. 7 prints the full name of a person. This function checks whether the firstName and lastName pointers are nil in line nos. 8 and 11 respectively. If it is nil the function calls panic with a corresponding message. This message will be printed when the program terminates.

Running this program will print the following output,

1
2
3
4
5
6
7
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0xc00006af58, 0x0)
/tmp/sandbox210590465/prog.go:12 +0x193
main.main()
/tmp/sandbox210590465/prog.go:20 +0x4d

Let’s analyze this output to understand how panic works and how the stack trace is printed when the program panics.

In line no. 19 we assign Elon to firstName. We call fullName function with lastName as nil in line no. 20. Hence the condition in line no. 11 will be satisfied and the program will panic. When panic is encountered, the program execution terminates, the argument passed to the panic function is printed followed by the stack trace. Since the program terminates following the panic function call in line no. 12, the code in line nos. 13, 14, and 15 will not be executed.

This program first prints the message passed to the panic function,

1
panic: runtime error: last name cannot be nil  

and then prints the stack trace.

The program panicked in line no. 12 of fullName function and hence,

1
2
3
goroutine 1 [running]:  
main.fullName(0xc00006af58, 0x0)
/tmp/sandbox210590465/prog.go:12 +0x193

will be printed first. Then the next item in the stack will be printed. In our case, line no. 20 where the fullName is called is the next item in the stack trace. Hence it is printed next.

1
2
main.main()  
/tmp/sandbox210590465/prog.go:20 +0x4d

Now we have reached the top level function which caused the panic and there are no more levels above, hence there is nothing more to print.

One More Example

Panics can also be caused by errors that happen during the runtime such as trying to access an index that is not present in a slice.

Let’s write a contrived example which creates a panic due to out of bounds slice access.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
)

func slicePanic() {
n := []int{5, 7, 4}
fmt.Println(n[4])
fmt.Println("normally returned from a")
}
func main() {
slicePanic()
fmt.Println("normally returned from main")
}

In the program above, in line no. 9 we are trying to access n[4] which is an invalid index in the slice. This program will panic with the following output,

1
2
3
4
5
6
7
panic: runtime error: index out of range [4] with length 3

goroutine 1 [running]:
main.slicePanic()
/tmp/sandbox942516049/prog.go:9 +0x1d
main.main()
/tmp/sandbox942516049/prog.go:13 +0x22

Defer Calls During a Panic

Let’s recollect what panic does. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.

In the example above, we did not defer any function calls. If a deferred function call is present, it is executed and then the control returns to its caller.

Let’s modify the example above a little and use a defer statement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"fmt"
)

func fullName(firstName *string, lastName *string) {
defer fmt.Println("deferred call in fullName")
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

The only changes made are the addition of the deferred function calls in line nos. 8 and 20.

This program prints,

1
2
3
4
5
6
7
8
9
deferred call in fullName  
deferred call in main
panic: runtime error: last name cannot be nil

goroutine 1 [running]:
main.fullName(0xc00006af28, 0x0)
/tmp/sandbox451943841/prog.go:13 +0x23f
main.main()
/tmp/sandbox451943841/prog.go:22 +0xc6

When the program panics in line no. 13, any deferred function calls are first executed and then the control returns to the caller whose deferred calls are executed and so on until the top level caller is reached.

In our case, defer statement in line no. 8 of fullName function is executed first. This prints the following message.

1
deferred call in fullName  

And then the control returns to the main function whose deferred calls are executed and hence this prints,

1
deferred call in main  

Now the control has reached the top level function and hence the program prints the panic message followed by the stack trace and then terminates.

Recovering from a Panic

recover is a builtin function that is used to regain control of a panicking program.

The signature of recover function is provided below,

1
func recover() interface{}  

Recover is useful only when called inside deferred functions. Executing a call to recover inside a deferred function stops the panicking sequence by restoring normal execution and retrieves the error message passed to the panic function call. If recover is called outside the deferred function, it will not stop a panicking sequence.

Let’s modify our program and use recover to restore normal execution after a panic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package main

import (
"fmt"
)

func recoverFullName() {
if r := recover(); r!= nil {
fmt.Println("recovered from ", r)
}
}

func fullName(firstName *string, lastName *string) {
defer recoverFullName()
if firstName == nil {
panic("runtime error: first name cannot be nil")
}
if lastName == nil {
panic("runtime error: last name cannot be nil")
}
fmt.Printf("%s %s\n", *firstName, *lastName)
fmt.Println("returned normally from fullName")
}

func main() {
defer fmt.Println("deferred call in main")
firstName := "Elon"
fullName(&firstName, nil)
fmt.Println("returned normally from main")
}

The recoverFullName() function in line no. 7 calls recover() which returns the value passed to panic function call. Here we are just printing the value returned by recover in line no. 9. recoverFullName() is being deferred in line no. 14 of the fullName function.

When fullName panics, the deferred function recoverName() will be called which uses recover() to stop the panicking sequence.

This program will print,

1
2
3
recovered from  runtime error: last name cannot be nil  
returned normally from main
deferred call in main

When the program panics in line no. 19, the deferred recoverFullName function is called which in turn calls recover() to regain control of the panicking sequence. The call to recover() in line no. 8 returns the argument passed to panic() and hence it prints,

1
recovered from  runtime error: last name cannot be nil  

After execution of recover(), the panicking stops and the control returns to the caller, in this case, the main function. The program continues to execute normally from line 29 in main since the panic has been recovered 😃. It prints returned normally from main followed by deferred call in main

Let’s look at one more example where we recover from a panic caused by accessing an invalid index of a slice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
)

func recoverInvalidAccess() {
if r := recover(); r != nil {
fmt.Println("Recovered", r)
}
}

func invalidSliceAccess() {
defer recoverInvalidAccess()
n := []int{5, 7, 4}
fmt.Println(n[4])
fmt.Println("normally returned from a")
}

func main() {
invalidSliceAccess()
fmt.Println("normally returned from main")
}

Running the above program will output,

1
2
Recovered runtime error: index out of range [4] with length 3  
normally returned from main

From the output, you can understand that we have recovered from the panic.

https://golangbot.com/panic-and-recover/

一辈子都要和别人去比较,是人生悲剧的源头
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
function rich_text_filter($content)
{
$allowTags = array('<br>', '<b>', '<p>', '<i>', '<u>', '<div>', '<strong>', '<img>'); // 允许通过的标签
$allowAttributes = array('src', 'width', 'style'); // 允许通过的html属性
$allowCss = array("width", "text-align", "font-weight"); // 允许通过的css属性

// 删除多余标签 保留特定标签
$content = strip_tags($content, implode($allowTags));

// 删除多余数属性 和多余css
$content = preg_replace("/<\s*/", '<', $content);

$content = preg_replace_callback("/([a-zA-Z0-9\-]+)=['\"]([^'\"]*)['\"]\s*/i", function ($matches) use ($allowAttributes, $allowCss) {
if (!isset($matches[1]) || !in_array(trim($matches[1]), $allowAttributes)) {
return ' ';
}

if (trim($matches[1]) === 'style' && isset($matches[2]) && !empty($matches[2])) {
$styles = $matches[2] . (preg_match("/;$/", $matches[2]) ? '' : ';');
$styles = preg_replace_callback("/([^:;]+):([^:;]+);/", function ($items) use($allowCss) {
if (isset($items[1]) && in_array(trim($items[1]), $allowCss)) {
return $items[0];
}
return '';
}, $styles);

return sprintf('style="%s" ', $styles);
}

return sprintf(' %s ', $matches[0]);
}, $content);

return $content;
}