0%

历史是一堆灰烬,但灰烬深处有余温。

文件目录

1
2
3
4
5
6
7
- static
- js
- 1.js
- templates
- foo.html
- index.html
- main.go

代码

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
package main

import (
"embed"
"github.com/gin-gonic/gin"
"html/template"
"net/http"
)

//go:embed static templates

var f embed.FS

func main(){

router := gin.Default()
templ := template.Must(template.New("").ParseFS(f, "templates/*.html"))
router.SetHTMLTemplate(templ)

// example: /public/static/js/1.js
router.StaticFS("/public", http.FS(f))


router.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{
"title": "Embed Demo",
})
})

router.GET("/foo", func(c *gin.Context) {
c.HTML(http.StatusOK, "foo.html", gin.H{
"title": "Foo Bar",
})
})

_ = router.Run(":9080")
}


1
2
3
4
5
6
7
8
9
10
11
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" src="/public/static/js/1.js"></script>
</head>
<body>
<h1>htn</h1>
</body>
</html>

这里荒芜寸草不生 后来你来这里走了一遭 奇迹般万物生长 这里是我的心

What is a method?

A method is a function or a piece of code that is associated with an object.

Let’s say, you have a struct called “Car” and you create a method of the struct named “Drive”, then you declare an object from the struct and name it as “tesla”. So you can call the “Drive” method of your “tesla” to do whatever you programmed the method to do by using the properties or fields of your “tesla” to go the calculation.

What I just explained to you is only an example to make you get the concept of method. So now I will show you how to create your own methods and how to use them.

Usage of Method

Basically, all methods are associated with objects, means that you need to have at least one object in your code to declare any methods.

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

import "fmt"

type Car struct {
color string
distance int
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println(tesla)
}

After you got your struct, you will be able to create your method

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
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println("init distance:", tesla.distance)
tesla.Drive(10)
fmt.Println("1st drive distance:", tesla.distance)
}

/*
>>> OUTPUT <<<
init distance: 0
1st drive distance: 10
*/

A method is very similar to a function but the difference is that the method is associated with an object and can you the properties inside the object to calculate, just like the “Drive” method that uses “distance” property of “Car” to calculate new “distance”.

In case you need to expose some fields of your struct to other packages without directly exporting them, using methods as “Getter” is your best choice.

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
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

func main() {
tesla := &Car{
distance: 0,
}
tesla.Drive(10)
fmt.Println("1st drive distance (without exporting):", tesla.GetDistance())
}

/*
>>> OUTPUT <<<
1st drive distance (without exporting): 10
*/

Setter is a way to set the value of unexported fields and getter can be used to setup default value of any field you want like the example below.

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
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

// Getter
func (c *Car) GetDistance() int {
return c.distance
}

// Setter
func (c *Car) SetColor(color string) {
c.color = color
}

// Getter with default value
func (c *Car) GetColor() string {
if c.color == "" {
c.color = "white"
}
return c.color
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println("get default color:", tesla.GetColor())
tesla.SetColor("silver")
fmt.Println("get new color:", tesla.GetColor())
}

/*
>>> OUTPUT <<<
get default color: white
get new color: silver
*/

Now you have already learnt how methods work and how to work with methods, but there is another feature in Golang that can enhance the power of structs and their methods. The feature is “Interface”.

What is an interface?

An interface is a set of method signature that an object can implement.

To make it more understandable, let’s say you have another struct named “Plane” and its methods: “Fly” and “GetDistance”. And you want to use it with “Car” struct by using the same logic to get their distance. An interface will help you for this kind of task.

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
package main

import "fmt"

// Car struct
type Car struct {
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

// Plane struct
type Plane struct {
distance int
}

func (p *Plane) Fly(dist int) {
p.distance += dist
}

func (p *Plane) GetDistance() int {
return p.distance
}

// Use interface as argument
func GetVehicleDistance(i interface{}) int {
switch i.(type) {
case *Car:
c := i.(*Car) // Type assertion
return c.GetDistance()
case *Plane:
p := i.(*Plane) // Type assertion
return p.GetDistance()
}
return 0
}

func main() {
tesla := &Car{}
airbus := &Plane{}
tesla.Drive(10)
airbus.Fly(10000)
fmt.Println("tesla distance:", GetVehicleDistance(tesla))
fmt.Println("airbus distance:", GetVehicleDistance(airbus))
}

/*
>>> OUTPUT <<<
tesla distance: 10
airbus distance: 10000
*/

From the example above, you can use the same function with different parameter types by using interface as argument. And before you use the method from the interface, you need to do “type assertion” which is very important step for using interfaces.

Anyway the code can be improved and shorten by declaring an interface type with a set of method signature. For our case, it is “GetDistance” method. You will see how short it is.

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
package main

import "fmt"

// Car struct
type Car struct {
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

// Plane struct
type Plane struct {
distance int
}

func (p *Plane) Fly(dist int) {
p.distance += dist
}

func (p *Plane) GetDistance() int {
return p.distance
}

// Declare interface type
type Vehicle interface {
GetDistance() int
}

// Use interface as argument
func GetVehicleDistance(i interface{}) int {
v := i.(Vehicle)
return v.GetDistance()
}

func main() {
tesla := &Car{}
airbus := &Plane{}
tesla.Drive(10)
airbus.Fly(10000)
fmt.Println("tesla distance:", GetVehicleDistance(tesla))
fmt.Println("airbus distance:", GetVehicleDistance(airbus))
}

/*
>>> OUTPUT <<<
tesla distance: 10
airbus distance: 10000
*/

However, don’t forget to validate your interface before using it because somehow it can be nil.

1
2
3
4
5
6
7
func GetVehicleDistance(i interface{}) int {
if i == nil { // validate interface before using
return 0
}
v := i.(Vehicle)
return v.GetDistance()
}

I hope you would get some point about implementing interface with multiple types, it will allow you to create only one logic for executing their method and make you life easier 😂.

一定要爱着点什么,恰似草木对光阴的钟情。

HH

  • env()は コントローラー, モデル, etc.. 内で直接使わない。
  • config/*.phpenv()の値を入れてconfig()から参照する。

1
2
3
4
5
6
7
<?php
// config/my-app.php

return [
// configに.envの内容を入れる。
'my-env' => env('MY_ENV'),
];
1
2
3
4
5
6
7
8
<?php
// コントローラー内など

// config() を使用。
$my_env = config('my-app.my-env');

// これはダメなパターン。
$my_env = env('MY_ENV');

何故 env() を使ってはいけないのか

本番環境でconfig:cacheコマンドを実行した際、.envファイルを読み込まないから。

.envファイルはIlluminate\Foundation\Bootstrap\LoadEnvironmentVariablesクラスのbootstrap()で読み込まれるのですが、読み込む前にconfigのキャッシュの有無を確認し、キャッシュがあった場合は.envファイルを読み込まない仕様になっています。

当該部分のLaravelのコードを引用。

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

public function bootstrap(Application $app)
{
if ($app->configurationIsCached()) {
return;
}
$this->checkForSpecificEnvironmentFile($app);
try {
(new Dotenv($app->environmentPath(), $app->environmentFile()))->load();
} catch (InvalidPathException $e) {
//
} catch (InvalidFileException $e) {
die('The environment file is invalid: '.$e->getMessage());
}
}

// 省略

大体の場合の本番環境では、高速化の為に設定を一纏めにするconfig:cacheコマンドを実行すると思うのですが、前述の通りキャッシュがあると.envが読み込まれないので、env()を直接叩いてると開発時やテスト時には動くけど本番環境で死ぬ。といった事になります。(実際なった)

実際に試す

適当なコントローラーを用意してddを使って値をダンプするだけ。

1
2
3
4
5
6
7
8
9
10
11
<?php
class HogeController extends Controller
{
public function getHoge()
{
dd(
env('MY_ENV'),
config('my-app.my-env')
);
}
}

まずはキャッシュ無しでアクセス。

1
2
3
4
"Foo"

"Foo"

テスト環境や開発環境と同じ様にキャッシュを作成していないのでどちらも同じ値が取得できます。

次にキャッシュさせてアクセス。artisanconfig:cacheを実行してbootstrap/cache/config.phpを作成します。

1
php artisan config:cache

で、先ほどと同じ様にアクセスしてみる。

1
2
3
null

"Foo"

.envファイルが読み出されていないのでenv()で取得した所で値が入ってる訳もなくnullを返します。

と言う訳で最初に書いた通り、env()は直接使わずにconfig()に.envの値を入れて使いましょう。

https://hiroto-k.hatenablog.com/entry/2018/03/28/213000

看人好处 记人长处 帮人难处

线程安全问题

假设我要要计算一个用户的积分,为了能够尽快算出用户所有的积分数,我们开启了协程

main.go

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
package main

import (
"fmt"
"sync"
"test/user"
)

func main() {

newuser:= user.User{
Point: 1,
}

wg := sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Add()
}
}()

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Minus()
}
}()

wg.Wait()

point := newuser.Get()

fmt.Println(point)
}

user/user.go

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

type User struct {
Point int64
}

func (user *User) Add() {
user.Point ++
}

func (user *User) Minus() {
user.Point --
}

func (user *User) Get() int64 {
return user.Point
}

结果计算的用户积分每次都不一样,正常情况下应该是 1

加锁处理

user/user.go

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
package user

import "sync"

type User struct {
Point int64
lock sync.Mutex
}

func (user *User) Add() {
user.lock.Lock()
defer user.lock.Unlock()
user.Point ++
}

func (user *User) Minus() {
user.lock.Lock()
defer user.lock.Unlock()
user.Point --
}

func (user *User) Get() int64 {
return user.Point
}

使用channel模拟锁

nain.go

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
package main

import (
"fmt"
"sync"
"test/user"
)

func main() {

newuser:= user.User{
Point: 1,
Lock: user.NewMutex(),
}

wg := sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Add()
}
}()

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Minus()
}
}()

wg.Wait()

point := newuser.Get()

fmt.Println(point)
}

user/user.go

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
package user

type Mutex struct {
ch chan struct{}
}

func NewMutex() *Mutex {
return &Mutex{ch: make(chan struct{}, 1)}
}

func (this *Mutex) Lock() {
this.ch <- struct{}{}
}

func (this *Mutex) UnLock() {
select {
case <-this.ch:
default:
panic("unlock error")
}
}


type User struct {
Point int64
Lock *Mutex
}

func (user *User) Add() {
user.Lock.Lock()
defer user.Lock.UnLock()
user.Point ++
}

func (user *User) Minus() {
user.Lock.Lock()
defer user.Lock.UnLock()
user.Point --
}

func (user *User) Get() int64 {
return user.Point
}

山脚人多,我们山顶见

线程安全问题

假设我要要计算一个用户的积分,为了能够尽快算出用户所有的积分数,我们开启了协程

main.go

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
package main

import (
"fmt"
"sync"
"test/user"
)

func main() {

newuser:= user.User{
Point: 1,
}

wg := sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Add()
}
}()

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Minus()
}
}()

wg.Wait()

point := newuser.Get()

fmt.Println(point)
}

user/user.go

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

type User struct {
Point int64
}

func (user *User) Add() {
user.Point ++
}

func (user *User) Minus() {
user.Point --
}

func (user *User) Get() int64 {
return user.Point
}

结果计算的用户积分每次都不一样,正常情况下应该是 1

加锁处理

user/user.go

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
package user

import "sync"

type User struct {
Point int64
lock sync.Mutex
}

func (user *User) Add() {
user.lock.Lock()
defer user.lock.Unlock()
user.Point ++
}

func (user *User) Minus() {
user.lock.Lock()
defer user.lock.Unlock()
user.Point --
}

func (user *User) Get() int64 {
return user.Point
}

channel处理

man.go

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
package main

import (
"fmt"
"sync"
"test/user"
)

func main() {

newuser:= user.NewUser()

wg := sync.WaitGroup{}
wg.Add(2)

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Add()
}
}()

go func() {
defer wg.Done()

for i:=0; i<10000; i++ {
newuser.Minus()
}
}()

wg.Wait()

point := newuser.Get()

fmt.Println(point)
}

user/user.go

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
package user

type opera func()


type User struct {
Point int64
ch chan opera
}


func NewUser() *User {
ch := make(chan opera)
user := &User{
Point: 1,
ch: ch,
}

go user.Watch()

return user
}


func (user *User) Watch() {
for fn := range user.ch {
fn()
}
}


func (user *User) add() {
user.Point ++
}

func (user *User) Add() {
user.ch <- user.add
}


func (user *User) minus() {
user.Point --
}

func (user *User) Minus() {
user.ch <- user.minus
}


func (user *User) Get() int64 {
return user.Point
}

人活一生,值得爱的东西很多,不要因为一个不满意,就灰心

DEMO1

1
2
3
4
for _,v := range Uins{
// Query the corresponding user information for each UIN
go getUserInfo(v)
}

DEMO2

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
func main(a){
// Create an empty structure channel with size 512
channel := make(chan struct{},512)
/ / create a sync. WaitGroup
var wg sync.WaitGroup
// Wg is the length of Uins
wg.Add(len(Uins))
for_,v:= range Uins{
channel <- struct{} {}// Pass WG as a parameter to the coroutine
go getUserInfo(v,channel,&wg)
}
// block until wg is 0
wg.Wait()
return
}


func getUserInfo(v int,channel chan struct{},wg *sync.WaitGroup){
defer func(a) {
// Wargaming is reduced by 1 before return
wg.Done()
<-channel
}()
/**
业务逻辑
**/
}

DEMO3

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
package main

import (
"fmt"
"sync"
)

func main() {
count := 10
sum := 100
wg := sync.WaitGroup{}

c := make(chan struct{}, count)
defer close(c)

for i:=0; i<sum;i++{
wg.Add(1)
go func(j int) {
defer wg.Done()
c <- struct{}{}
fmt.Println(j)
}(i)
<- c
}
wg.Wait()
}

DEMO4

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
package main

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

func main() {

maxnum := make(chan struct{}, 5)
setPool(maxnum)

wg := sync.WaitGroup{}
wg.Add(5)

go func() {
for {
wg.Wait()
fmt.Println("=======发放5个任务=========")
setPool(maxnum)
wg.Add(5)
}
}()


for {
<-maxnum
go func() {
defer wg.Done()
myJob()
}()
}

}

func setPool(ch chan struct{}) {
for i:=0; i<5; i++ {
ch <- struct{}{}
}
}

func myJob() {
time.Sleep(time.Second * 3)
fmt.Println(rand.Intn(100))
}

Home is behind, world ahead.

Laravel comes up with an excellent Eloquent ORM. There are lot of cool things in Eloquent, one of which is chunk method.

Usually when you need to process large data, lets say you want to update users table and assign a coupon code based on 3rd party APIs.

What you can do is :

1
2
3
4
5
6
User::get()->each(function($user){

$coupon = API::getCouponCode($user->email);
$user->coupon = $coupon;
$user->save();
});

Doing this is good but if you have thousands of users, loading them all up at once to do the coupon saving process. That’s going to take consume a hige memory at a time and maybe the server will be exusted because so much data is stored in memory for processing at a time.

The chunk method helps these kind of implementations. Chunking records means taking a batch of records by a limit, process those, take the next batch processing those and so on… The idea is you are taking a subset of data to process at a time instead of entire data loaded into memory.

The chunk method will retrieve a “chunk” of Eloquent models, feeding them to a given Closure for processing. Using the chunk method will conserve memory when working with large result sets.

Lets do the same but with chunk this time :

1
2
3
4
5
6
7
8
$recodsPerBatch = 50;

User::chunk($recodsPerBatch, function($user){

$coupon = API::getCouponCode($user->email);
$user->coupon = $coupon;
$user->save();
});

Now, as you can probably guess this will take 50 user records at a time to process, once those are completed it will take next 50 untill all records are procesed by chunk closure.

  • The main problem Limit with Chunk :

Let’s apply limit to the above example :

1
2
3
4
5
6
7
8
$recodsPerBatch = 50;

User::limit(100)->chunk($recodsPerBatch, function($user){

$coupon = API::getCouponCode($user->email);
$user->coupon = $coupon;
$user->save();
});

If you would think laravel will chunk 50 records in 2 batches are we are limiting the total records to 100, oops not really.

Laravel will do series of queries like below to chunk the records :

1
2
3
4
select * from `users` limit 50 offset 0
select * from `users` limit 50 offset 50
select * from `users` limit 50 offset 100
...

The chunk method ignores any previous eloquent method which applies limit. It can be limit and offset OR take and skip…

This becomes a problem for which lot of people had raised an issue on laravel’s github repo. But Laravel’s core dev team mentioned this is the expected behaviour and rightfully so.. Chunk itself is using limit to convert entire collection in batches.

  • And…. Here it is.. The Solution you were waiting for :

Laravel has a chunk variation called chunkById. Let’s use the first example and implement chunkById now :

1
2
3
4
5
6
7
8
$recodsPerBatch = 50;

User::chunkById($recodsPerBatch, function($user){

$coupon = API::getCouponCode($user->email);
$user->coupon = $coupon;
$user->save();
});

The main and only fundamental difference between chunk and chunkById is how it structures the query.

1
2
3
4
5
select * from `users` where `id` > 0 order by `id` asc limit 50
select * from `users` where `id` > 0 and `id` > 50 order by `id` asc limit 50
select * from `users` where `id` > 0 and `id` > 50 and `id` > 100 order by `id` asc limit 50
select * from `users` where `id` > 0 and `id` > 50 and `id` > 100 and `id` > 150 order by `id` asc limit 50
...

If you observer in the queries done by chunkById :

  • It’s adding an order by clause to the id column (By the way you can specify the column as 3rd argument to chunkById method if itis not id)
  • It adds where id > x each time it processes the next batch
  • There is no offset used and id column is used conceptually as an offset.

This gives you an advantage that you can add your offset in the query using id column as a limit just like chunkById is doing internallu.

Let’s limit the chunk in our example with 100 records to chunk :

1
2
3
4
5
6
7
8
9
10
11
12
$recodsPerBatch = 50;

$limit = 100;

$maxId = User::orderBy('id', 'asc')->offset($limit)->limit(1)->select('id')->first()->id;

User::where('id', '<', $maxId)->chunkById($recodsPerBatch, function($user){

$coupon = API::getCouponCode($user->email);
$user->coupon = $coupon;
$user->save();
});

What we did was, even if we can not use limit directly, we got an id which falls just above the limit we want. Used that to add a where clause of where('id', '<', $maxId).

This will then chunk the 100 results, with 2 batches of 50 records in each batch. Cool, isn’t it!

https://techsemicolon.github.io/blog/2019/02/12/laravel-limiting-chunk-collection/

不要到处宣扬自己的内心,

这世上不止你一个人有故事。

while read line无法读取最后一行

1
2
3
4
5
6
7

while read line
do
data=`...line...`
echo "${data}" >> $2.txt

done < $1

原因

因为我的目标文件是在windows下创建然后传到服务器上的,这样在利用while read line读取文件时,如果文件最后一行之后没有换行符\n,则read读取最后一行时遇到文件结束符EOF时循环即终止。上面代码中,虽然此时$line内存有最后一行的内容,但程序已经没有机会再处理此行内容,因此导致了最后一行无法读取。

解决

方案一

修改while循环,增加 [[ -n ${line} ]],这样当文件没有到最后一行时不会测试-n $line,当遇到文件结束(最后一行)时,仍然可以通过测试$line是否有内容来进行继续处理。

1
2
3
4
5
6
#!bin/bash
while read line || [[ -n ${line} ]]
do
data=`...line...`
echo "${data}" >> $2.txt
done < $1

方案二

通过分析原因可知,本质原因是因为文件格式不是unix导致的,可以直接通过设置文件格式来处理,该方式则脚本代码不需改动。

1
2
3
4
5
6
7
8
9
10

# 在服务器上vim编辑目标文件
vim target_file

# 然后执行如下指令,用于查看当前文件是dos格式还是unix格式
:set ff?

# 强制切换为unix格式,然后保存即可
:set ff=unix

那些看似不起波澜的日复一日,会突然在某一天让人看到坚持的意义。

error

对于Go语言(golang)的错误设计,相信很多人已经体验过了,它是通过返回值的方式,来强迫调用者对错误进行处理,要么你忽略,要么你处理(处理也可以是继续返回给调用者),对于golang这种设计方式,我们会在代码中写大量的if判断,以便做出决定。

1
2
3
4
5
6
7
8
func main() {
conent,err:=ioutil.ReadFile("filepath")
if err !=nil{
//错误处理
}else {
fmt.Println(string(conent))
}
}

error 接口

error其实一个接口,内置的,我们看下它的定义

1
2
3
4
5
// The error built-in interface type is the conventional interface for
// representing an error condition, with the nil value representing no error.
type error interface {
Error() string
}

它只有一个方法 Error,只要实现了这个方法,就是实现了error。现在我们自己定义一个错误试试。

1
2
3
4
5
6
type fileError struct {
}

func (fe *fileError) Error() string {
return "文件错误"
}

自定义 error

自定义了一个fileError类型,实现了error接口。现在测试下看看效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
conent, err := openFile()
if err != nil {
fmt.Println(err)
} else {
fmt.Println(string(conent))
}
}

//只是模拟一个错误
func openFile() ([]byte, error) {
return nil, &fileError{}
}

我们运行模拟的代码,可以看到文件错误的通知。

在实际的使用过程中,我们可能遇到很多错误,他们的区别是错误信息不一样,一种做法是每种错误都类似上面一样定义一个错误类型,但是这样太麻烦了。我们发现Error返回的其实是个字符串,我们可以修改下,让这个字符串可以设置就可以了。

1
2
3
4
5
6
7
type fileError struct {
s string
}

func (fe *fileError) Error() string {
return fe.s
}

恩,这样改造后,我们就可以在声明fileError的时候,设置好要提示的错误文字,就可以满足我们不同的需要了。

1
2
3
4
//只是模拟一个错误
func openFile() ([]byte, error) {
return nil, &fileError{"文件错误,自定义"}
}

恩,可以了,已经达到了我们的目的。现在我们可以把它变的更通用一些,比如修改fileError的名字,再创建一个辅助函数,便于我们创建不同的错误类型。

1
2
3
4
5
6
7
8
9
10
11
func New(text string) error {
return &errorString{text}
}

type errorString struct {
s string
}

func (e *errorString) Error() string {
return e.s
}

panic

error 返回的是一般性的错误,但是 panic 函数返回的是让程序崩溃的错误。

也就是当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起 painc 异常,在一般情况下,我们不应通过调用 panic 函数来报告普通的错误,而应该只把它作为报告致命错误的一种方式。当某些不应该发生的场景发生时,我们就应该调用 panic。

一般而言,当 panic 异常发生时,程序会中断运行。随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息。

当然,如果直接调用内置的 panic 函数也会引发 panic 异常;panic 函数接受任何值作为参数。

直接调用 panic 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("throw error")
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB()
TestC()
}

我们在实际的开发过程中并不会直接调用 panic ( ) 函数,但是当我们编程的程序遇到致命错误时,系统会自动调用该函数来终止整个程序的运行,也就是系统内置了 panic 函数。

数组下标越界的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
var a [10] int
a[x] = 222 // x 值为11时候,数组越界
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(11) // TestB() 发生异常 中断程序
TestC()
}

recover

运行时 panic 异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。

Go 语言为我们提供了专用于 “拦截” 运行时 panic 的内建函数 ——recover。它可以是当前的程序从运行时 panic 的状态中恢复并重新获得流程控制权。

注意:recover 只有在 defer 调用的函数中有效。

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 TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
// 设置recover
defer func() {
recover()
}() // 自调用改匿名函数
var a [10] int
a[x] = 111 // 当x为20 的时候,导致数组越界,产生pani,导致程序崩溃
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(11) // TestB() 发生异常 中断程序
TestC()
}

通过以上程序,我们发现虽然 TestB () 函数会导致整个应用程序崩溃,但是由于在改函数中调用了 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
package main

import "fmt"

func TestA() {
fmt.Println("func TestA()")
}
func TestB(x int) {
// 设置recover
defer func() {
if err := recover(); err != nil { // 产生了panic 异常
fmt.Println(err)
}
}() // 自调用改匿名函数
var a [10] int
a[x] = 111 // 当x为20 的时候,导致数组越界,产生panic,导致程序崩溃
}
func TestC() {
fmt.Println("func TestC()")
}

func main() {
TestA()
TestB(1111) // TestB() 不让他越界
TestC()
}

https://xuhy.top/posts/develop/go/go-lesson/go-lesson07/
https://learnku.com/docs/qianxi-golang/2020/section-2-panic-function/6444#ed4f2f
https://www.flysnow.org/2019/01/01/golang-error-handle-suggestion.html

无聊是非常有必要的,一个人在空白时间所做的事,决定了这个人和他人根本的不同。

Go编译的程序非常适合部署,如果没有通过CGO引用其它的库的话,我们一般编译出来的可执行二进制文件都是单个的文件,非常适合复制和部署。在实际使用中,除了二进制文件,可能还需要一些配置文件,或者静态文件,比如html模板、静态的图片、CSS、javascript等文件,如何这些文件也能打进到二进制文件中,那就太美妙,我们只需复制、按照单个的可执行文件即可。

嵌入

嵌入为字符串

比如当前文件下有个hello.txt的文件,文件内容为hello,world!。通过go:embed指令,在编译后下面程序中的s变量的值就变为了hello,world!。

1
2
3
4
5
6
7
8
9
10
package main
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var s string
func main() {
fmt.Println(s)
}

嵌入为byte slice

你还可以把单个文件的内容嵌入为slice of byte,也就是一个字节数组。

1
2
3
4
5
6
7
8
9
10
package main
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var b []byte
func main() {
fmt.Println(b)
}

嵌入为fs.FS

甚至你可以嵌入为一个文件系统,这在嵌入多个文件的时候非常有用。
比如嵌入一个文件:

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
}

嵌入本地的另外一个文件hello2.txt, 支持同一个变量上多个go:embed指令(嵌入为string或者byte slice是不能有多个go:embed指令的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
//go:embed hello2.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("hello2.txt")
fmt.Println(string(data))
}

当前重复的go:embed指令嵌入为embed.FS是支持的,相当于一个:

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
//go:embed hello.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
}

还可以嵌入子文件夹下的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"embed"
"fmt"
)
//go:embed p/hello.txt
//go:embed p/hello2.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("p/hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("p/hello2.txt")
fmt.Println(string(data))
}

同一个文件嵌入为多个变量

比如下面的例子,s和s2变量都嵌入hello.txt的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var s string
//go:embed hello.txt
var s2 string
func main() {
fmt.Println(s)
fmt.Println(s2)
}

exported/unexported的变量都支持

Go可以将文件可以嵌入为exported的变量,也可以嵌入为unexported的变量。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
_ "embed"
"fmt"
)
//go:embed hello.txt
var s string
//go:embed hello2.txt
var S string
func main() {
fmt.Println(s)
fmt.Println(S)
}

package级别的变量和局部变量都支持

前面的例子都是package一级的的变量,即使是函数内的局部变量,也都支持嵌入:

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
_ "embed"
"fmt"
)
func main() {
//go:embed hello.txt
var s string
//go:embed hello.txt
var s2 string
fmt.Println(s, s2)
}

局部变量s的值在编译时就已经嵌入了,而且虽然s和s2嵌入同一个文件,但是它们的值在编译的时候会使用初始化字段中的不同的值

只读

嵌入的内容是只读的。也就是在编译期嵌入文件的内容是什么,那么在运行时的内容也就是什么。

FS文件系统值提供了打开和读取的方法,并没有write的方法,也就是说FS实例是线程安全的,多个goroutine可以并发使用。

go:embed指令

go:embed指令支持嵌入多个文件

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt hello2.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("hello2.txt")
fmt.Println(string(data))
}

当然你也可以像前面的例子一样写成多行go:embed:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import (
"embed"
"fmt"
)
//go:embed hello.txt
//go:embed hello2.txt
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("hello2.txt")
fmt.Println(string(data))
}

支持文件夹

文件夹分隔符采用正斜杠/,即使是windows系统也采用这个模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"embed"
"fmt"
)
//go:embed p
var f embed.FS
func main() {
data, _ := f.ReadFile("p/hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("p/hello2.txt")
fmt.Println(string(data))
}

使用的是相对路径

相对路径的根路径是go源文件所在的文件夹。

支持使用双引号”或者反引号的方式应用到嵌入的文件名或者文件夹名或者模式名上,这对名称中带空格或者特殊字符的文件文件夹有用。

1
2
3
4
5
6
7
8
9
10
11
package main
import (
"embed"
"fmt"
)
//go:embed "he llo.txt" `hello-2.txt`
var f embed.FS
func main() {
data, _ := f.ReadFile("he llo.txt")
fmt.Println(string(data))
}

匹配模式

go:embed指令中可以只写文件夹名,此文件夹中除了._开头的文件和文件夹都会被嵌入,并且子文件夹也会被递归的嵌入,形成一个此文件夹的文件系统。

如果想嵌入._开头的文件和文件夹, 比如p文件夹下的.hello.txt文件,那么就需要使用*,比如go:embed p/*

不具有递归性,所以子文件夹下的.和_不会被嵌入,除非你在专门使用子文件夹的进行嵌入

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"embed"
"fmt"
)
//go:embed p/*
var f embed.FS
func main() {
data, _ := f.ReadFile("p/.hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile("p/q/.hi.txt") // 没有嵌入 p/q/.hi.txt
fmt.Println(string(data))
}

嵌入和嵌入模式不支持绝对路径、不支持路径中包含.和..,如果想嵌入go源文件所在的路径,使用*:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"embed"
"fmt"
)
//go:embed *
var f embed.FS
func main() {
data, _ := f.ReadFile("hello.txt")
fmt.Println(string(data))
data, _ = f.ReadFile(".hello.txt")
fmt.Println(string(data))
}

文件系统

embed.FS实现了 io/fs.FS接口,它可以打开一个文件,返回fs.File:

1
2
3
4
5
6
7
8
9
10
11
12
package main
import (
"embed"
"fmt"
)
//go:embed *
var f embed.FS
func main() {
helloFile, _ := f.Open("hello.txt")
stat, _ := helloFile.Stat()
fmt.Println(stat.Name(), stat.Size())
}

它还提供了ReadFileh和ReadDir功能,遍历一个文件下的文件和文件夹信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import (
"embed"
"fmt"
)
//go:embed *
var f embed.FS
func main() {
dirEntries, _ := f.ReadDir("p")
for _, de := range dirEntries {
fmt.Println(de.Name(), de.IsDir())
}
}

因为它实现了io/fs.FS接口,所以可以返回它的子文件夹作为新的文件系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"embed"
"fmt"
"io/fs"
"io/ioutil"
)
//go:embed *
var f embed.FS
func main() {
ps, _ := fs.Sub(f, "p")
hi, _ := ps.Open("q/hi.txt")
data, _ := ioutil.ReadAll(hi)
fmt.Println(string(data))
}

应用

net/http

先前,我们提供一个静态文件的服务时,使用:

1
http.Handle("/", http.FileServer(http.Dir("/tmp")))

现在,io/fs.FS文件系统也可以转换成http.FileServer的参数了:

1
2
3
4
5
type FileSystem
func FS(fsys fs.FS) FileSystem
type Handler
func FileServer(root FileSystem) Handler

所以,嵌入文件可以使用下面的方式:

1
http.Handle("/", http.FileServer(http.FS(fsys)))

text/template和html/template.

同样的,template也可以从嵌入的文件系统中解析模板:

1
2
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)
func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

https://colobu.com/2021/01/17/go-embed-tutorial/