Golang 微框架 Gin 简介

所谓框架

框架一直是敏捷开发中的利器,能让开发者很快的上手并做出应用,甚至有的时候,脱离了框架,一些开发者都不会写程序了。成长总不会一蹴而就,从写出程序获取成就感,再到精通框架,快速构造应用,当这些方面都得心应手的时候,可以尝试改造一些框架,或是自己创造一个。

曾经我以为Python世界里的框架已经够多了,后来发现相比golang简直小巫见大巫。golang提供的net/http库已经很好了,对于http的协议的实现非常好,基于此再造框架,也不会是难事,因此生态中出现了很多框架。既然构造框架的门槛变低了,那么低门槛同样也会带来质量参差不齐的框架。

考察了几个框架,通过其github的活跃度,维护的team,以及生产环境中的使用率。发现Gin还是一个可以学习的轻巧框架。

Gin

Gin是一个golang的微框架,封装比较优雅,API友好,源码注释比较明确,已经发布了1.0版本。具有快速灵活,容错方便等特点。其实对于golang而言,web框架的依赖要远比Python,Java之类的要小。自身的net/http足够简单,性能也非常不错。框架更像是一些常用函数或者工具的集合。借助框架开发,不仅可以省去很多常用的封装带来的时间,也有助于团队的编码风格和形成规范。

在 Go语言开发的 Web 框架中,有两款著名 Web 框架分别是 Martini 和 Gin,两款 Web 框架相比较的话,Gin 自己说它比 Martini 要强很多。

Gin 是 Go语言写的一个 web 框架,它具有运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件和 json。总之在 Go语言开发领域是一款值得好好研究的 Web 框架,开源网址:https://github.com/gin-gonic/gin

首先需要安装,安装比较简单,使用go get即可:

go get gopkg.in/gin-gonic/gin.v1

Hello World

使用Gin实现Hello world非常简单,创建一个router,然后使用其Run的方法:

import (
    "gopkg.in/gin-gonic/gin.v1"
    "net/http"
)

func main(){
    
    router := gin.Default()

    router.GET("/", func(c *gin.Context) {
        c.String(http.StatusOK, "Hello World")
    })
    router.Run(":8000")
}

简单几行代码,就能实现一个web服务。使用gin的Default方法创建一个路由handler。然后通过HTTP方法绑定路由规则和路由函数。不同于net/http库的路由函数,gin进行了封装,把request和response都封装到gin.Context的上下文环境。最后是启动路由的Run方法监听端口。麻雀虽小,五脏俱全。当然,除了GET方法,gin也支持POST,PUT,DELETE,OPTION等常用的restful方法。

restful路由

gin的路由来自httprouter库。因此httprouter具有的功能,gin也具有,不过gin不支持路由正则表达式:

func main(){
    router := gin.Default()
    
    router.GET("/user/:name", func(c *gin.Context) {
        name := c.Param("name")
        c.String(http.StatusOK, "Hello %s", name)
    })
}
冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。
curl http://127.0.0.1:8000/user/rsj217
Hello rsj217%                                                                 curl http://127.0.0.1:8000/user/rsj217/
404 page not found%                                                                curl http://127.0.0.1:8000/user/
404 page not found%
冒号:加上一个参数名组成路由参数。可以使用c.Params的方法读取其值。当然这个值是字串string。诸如/user/rsj217,和/user/hello都可以匹配,而/user//user/rsj217/不会被匹配。
curl http://127.0.0.1:8000/user/rsj217
Hello rsj217%                                                                 curl http://127.0.0.1:8000/user/rsj217/
404 page not found%                                                              curl http://127.0.0.1:8000/user/
404 page not found%

除了:,gin还提供了*号处理参数,*号能匹配的规则就更多。

func main(){
    router := gin.Default()
    
    router.GET("/user/:name/*action", func(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        message := name + " is " + action
        c.String(http.StatusOK, message)
    })
}

访问效果如下

curl http://127.0.0.1:8000/user/rsj217/
rsj217 is /%                                                                  curl http://127.0.0.1:8000/user/rsj217/中国
rsj217 is /中国%

query string参数与body参数

web提供的服务通常是client和server的交互。其中客户端向服务器发送请求,除了路由参数,其他的参数无非两种,查询字符串query string和报文体body参数。所谓query string,即路由用,用?以后连接的key1=value2&key2=value2的形式的参数。当然这个key-value是经过urlencode编码。

query string

对于参数的处理,经常会出现参数不存在的情况,对于是否提供默认值,gin也考虑了,并且给出了一个优雅的方案:

func main(){
    router := gin.Default()
    router.GET("/welcome", func(c *gin.Context) {
        firstname := c.DefaultQuery("firstname", "Guest")
        lastname := c.Query("lastname")

        c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
    })
  router.Run()
}

使用c.DefaultQuery方法读取参数,其中当参数不存在的时候,提供一个默认值。使用Query方法读取正常参数,当参数不存在的时候,返回空字串:

curl http://127.0.0.1:8000/welcome
Hello Guest %                                                                 curl http://127.0.0.1:8000/welcome\?firstname\=中国
Hello 中国 %                                                                  curl http://127.0.0.1:8000/welcome\?firstname\=中国\&lastname\=天朝
Hello 中国 天朝%                                                              curl http://127.0.0.1:8000/welcome\?firstname\=\&lastname\=天朝
Hello  天朝%
curl http://127.0.0.1:8000/welcome\?firstname\=%E4%B8%AD%E5%9B%BD
Hello 中国 %

之所以使用中文,是为了说明urlencode。注意,当firstname为空字串的时候,并不会使用默认的Guest值,空值也是值,DefaultQuery只作用于key不存在的时候,提供默认值。

body

http的报文体传输数据就比query string稍微复杂一点,常见的格式就有四种。例如application/jsonapplication/x-www-form-urlencoded, application/xmlmultipart/form-data。后面一个主要用于图片上传。json格式的很好理解,urlencode其实也不难,无非就是把query string的内容,放到了body体里,同样也需要urlencode。默认情况下,c.PostFROM解析的是x-www-form-urlencodedfrom-data的参数。

func main(){
    router := gin.Default()
    router.POST("/form_post", func(c *gin.Context) {
        message := c.PostForm("message")
        nick := c.DefaultPostForm("nick", "anonymous")

        c.JSON(http.StatusOK, gin.H{
            "status":  gin.H{
                "status_code": http.StatusOK,
                "status":      "ok",
            },
            "message": message,
            "nick":    nick,
        })
    })
}

与get处理query参数一样,post方法也提供了处理默认参数的情况。同理,如果参数不存在,将会得到空字串。

curl -X POST http://127.0.0.1:8000/form_post -H "Content-Type:application/x-www-form-urlencoded" -d "message=hello&nick=rsj217" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   104  100    79  100    25  48555  15365 --:--:-- --:--:-- --:--:-- 79000
{
    "message": "hello",
    "nick": "rsj217",
    "status": {
        "status": "ok",
        "status_code": 200
    }
}

发送数据给服务端,并不是post方法才行,put方法一样也可以。同时querystring和body也不是分开的,两个同时发送也可以:

func main(){
    router := gin.Default()
    
    router.PUT("/post", func(c *gin.Context) {
        id := c.Query("id")
        page := c.DefaultQuery("page", "0")
        name := c.PostForm("name")
        message := c.PostForm("message")
        fmt.Printf("id: %s; page: %s; name: %s; message: %s \n", id, page, name, message)
        c.JSON(http.StatusOK, gin.H{
            "status_code": http.StatusOK,
        })
    })
}

上面的例子,展示了同时使用查询字串和body参数发送数据给服务器。

文件上传

上传单个文件

前面介绍了基本的发送数据,其中multipart/form-data转用于文件上传。gin文件上传也很方便,和原生的net/http方法类似,不同在于gin把原生的request封装到c.Request中了。

func main(){
    router := gin.Default()
    
    router.POST("/upload", func(c *gin.Context) {
        name := c.PostForm("name")
        fmt.Println(name)
        file, header, err := c.Request.FormFile("upload")
        if err != nil {
            c.String(http.StatusBadRequest, "Bad request")
            return
        }
        filename := header.Filename

        fmt.Println(file, err, filename)

        out, err := os.Create(filename)
        if err != nil {
            log.Fatal(err)
        }
        defer out.Close()
        _, err = io.Copy(out, file)
        if err != nil {
            log.Fatal(err)
        }
        c.String(http.StatusCreated, "upload successful")
    })
    router.Run(":8000")
}

使用c.Request.FormFile解析客户端文件name属性。如果不传文件,则会抛错,因此需要处理这个错误。一种方式是直接返回。然后使用os的操作,把文件数据复制到硬盘上。

使用下面的命令可以测试上传,注意upload为c.Request.FormFile指定的参数,其值必须要是绝对路径:

curl -X POST http://127.0.0.1:8000/upload -F "upload=@/Users/ghost/Desktop/pic.jpg" -H "Content-Type: multipart/form-data"

表单上传

上面我们使用的都是curl上传,实际上,用户上传图片更多是通过表单,或者ajax和一些requests的请求完成。下面展示一下web的form表单如何上传。

我们先要写一个表单页面,因此需要引入gin如何render模板。前面我们见识了c.String和c.JSON。下面就来看看c.HTML方法。

首先需要定义一个模板的文件夹。然后调用c.HTML渲染模板,可以通过gin.H给模板传值。至此,无论是String,JSON还是HTML,以及后面的XML和YAML,都可以看到Gin封装的接口简明易用。

创建一个文件夹templates,然后再里面创建html文件upload.html:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>upload</title>
</head>
<body>
<h3>Single Upload</h3>
<form action="/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>


<h3>Multi Upload</h3>
<form action="/multi/upload", method="post" enctype="multipart/form-data">
<input type="text" value="hello gin" />
<input type="file" name="upload" />
<input type="file" name="upload" />
<input type="submit" value="upload" />
</form>

</body>
</html>

upload 很简单,没有参数。一个用于单个文件上传,一个用于多个文件上传。

    router.LoadHTMLGlob("templates/*")
    router.GET("/upload", func(c *gin.Context) {
        c.HTML(http.StatusOK, "upload.html", gin.H{})
    })

使用LoadHTMLGlob定义模板文件路径。

参数绑定

我们已经见识了x-www-form-urlencoded类型的参数处理,现在越来越多的应用习惯使用JSON来通信,也就是无论返回的response还是提交的request,其content-type类型都是application/json的格式。而对于一些旧的web表单页还是x-www-form-urlencoded的形式,这就需要我们的服务器能改hold住这多种content-type的参数了。

Python的世界里很好解决,毕竟动态语言不需要实现定义数据模型。因此可以写一个装饰器将两个格式的数据封装成一个数据模型。golang中要处理并非易事,好在有gin,他们的model bind功能非常强大。

 

type User struct {
    Username string `form:"username" json:"username" binding:"required"`
    Passwd   string `form:"passwd" json:"passwd" bdinding:"required"`
    Age      int    `form:"age" json:"age"`
}

func main(){
    router := gin.Default()
    
    router.POST("/login", func(c *gin.Context) {
        var user User
        var err error
        contentType := c.Request.Header.Get("Content-Type")

        switch contentType {
        case "application/json":
            err = c.BindJSON(&user)
        case "application/x-www-form-urlencoded":
            err = c.BindWith(&user, binding.Form)
        }

        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "user":   user.Username,
            "passwd": user.Passwd,
            "age":    user.Age,
        })

    })

}

先定义一个User模型结构体,然后针对客户端的content-type,一次使BindJSONBindWith方法。

curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&age=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    79  100    46  100    33  41181  29543 --:--:-- --:--:-- --:--:-- 46000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&passwd=123&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    78  100    45  100    33  37751  27684 --:--:-- --:--:-- --:--:-- 45000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/x-www-form-urlencoded" -d "username=rsj217&new=21" | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

可以看到,结构体中,设置了binding标签的字段(username和passwd),如果没传会抛错误。非banding的字段(age),对于客户端没有传,User结构会用零值填充。对于User结构没有的参数,会自动被忽略。

改成json的效果类似:

curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "age": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    96  100    46  100    50  32670  35511 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 21,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": "123", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    95  100    45  100    50  49559  55066 --:--:-- --:--:-- --:--:-- 50000
{
    "age": 0,
    "passwd": "123",
    "username": "rsj217"
}
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded
curl -X POST http://127.0.0.1:8000/login -H "Content-Type:application/json" -d '{"username": "rsj217", "passwd": 123, "new": 21}' | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0curl: (52) Empty reply from server
No JSON object could be decoded

当然,gin还提供了更加高级方法,c.Bind,它会更加content-type自动推断是bind表单还是json的参数。

    router.POST("/login", func(c *gin.Context) {
        var user User
        
        err := c.Bind(&user)
        if err != nil {
            fmt.Println(err)
            log.Fatal(err)
        }

        c.JSON(http.StatusOK, gin.H{
            "username":   user.Username,
            "passwd":     user.Passwd,
            "age":        user.Age,
        })

    })

多格式渲染

既然请求可以使用不同的content-type,响应也如此。通常响应会有html,text,plain,json和xml等。
gin提供了很优雅的渲染方法。到目前为止,我们已经见识了c.String, c.JSON,c.HTML,下面介绍一下c.XML。

    router.GET("/render", func(c *gin.Context) {
        contentType := c.DefaultQuery("content_type", "json")
        if contentType == "json" {
            c.JSON(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        } else if contentType == "xml" {
            c.XML(http.StatusOK, gin.H{
                "user":   "rsj217",
                "passwd": "123",
            })
        }

    })

结果如下:

curl http://127.0.0.1:8000/render\?content_type\=json
{"passwd":"123","user":"rsj217"}
curl http://127.0.0.1:8000/render\?content_type\=xml
<map><user>rsj217</user><passwd>123</passwd></map>%

重定向

gin对于重定向的请求,相当简单。调用上下文的Redirect方法:

    router.GET("/redict/google", func(c *gin.Context) {
        c.Redirect(http.StatusMovedPermanently, "https://google.com")
    })

分组路由

熟悉Flask的同学应该很了解蓝图分组。Flask提供了蓝图用于管理组织分组api。gin也提供了这样的功能,让你的代码逻辑更加模块化,同时分组也易于定义中间件的使用范围。

    v1 := router.Group("/v1")

    v1.GET("/login", func(c *gin.Context) {
        c.String(http.StatusOK, "v1 login")
    })

    v2 := router.Group("/v2")

    v2.GET("/login", func(c *gin.Context) {
        c.String(http.StatusOK, "v2 login")
    })

访问效果如下:

curl http://127.0.0.1:8000/v1/login
v1 login%                                                                                 
curl http://127.0.0.1:8000/v2/login
v2 login%

middleware中间件

golang的net/http设计的一大特点就是特别容易构建中间件。gin也提供了类似的中间件。需要注意的是中间件只对注册过的路由函数起作用。对于分组路由,嵌套使用中间件,可以限定中间件的作用范围。中间件分为全局中间件,单个路由中间件和群组中间件。

全局中间件

先定义一个中间件函数:

func MiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        fmt.Println("before middleware")
        c.Set("request", "clinet_request")
        c.Next()
        fmt.Println("before middleware")
    }
}

该函数很简单,只会给c上下文添加一个属性,并赋值。后面的路由处理器,可以根据被中间件装饰后提取其值。需要注意,虽然名为全局中间件,只要注册中间件的过程之前设置的路由,将不会受注册的中间件所影响。只有注册了中间件一下代码的路由函数规则,才会被中间件装饰。

    router.Use(MiddleWare())
    {
        router.GET("/middleware", func(c *gin.Context) {
            request := c.MustGet("request").(string)
            req, _ := c.Get("request")
            c.JSON(http.StatusOK, gin.H{
                "middile_request": request,
                "request": req,
            })
        })
    }

使用router装饰中间件,然后在/middlerware即可读取request的值,注意在router.Use(MiddleWare())代码以上的路由函数,将不会有被中间件装饰的效果。

curl  http://127.0.0.1:8000/middleware
{"middile_request":"clinet_request","request":"clinet_request"}

如果没有注册就使用MustGet方法读取c的值将会抛错,可以使用Get方法取而代之。

上面的注册装饰方式,会让所有下面所写的代码都默认使用了router的注册过的中间件。

单个路由中间件

当然,gin也提供了针对指定的路由函数进行注册。

    router.GET("/before", MiddleWare(), func(c *gin.Context) {
        request := c.MustGet("request").(string)
        c.JSON(http.StatusOK, gin.H{
            "middile_request": request,
        })
    })

把上述代码写在 router.Use(Middleware())之前,同样也能看见/before被装饰了中间件。

群组中间件

群组的中间件也类似,只要在对于的群组路由上注册中间件函数即可:

authorized := router.Group("/", MyMiddelware())
// 或者这样用:
authorized := router.Group("/")
authorized.Use(MyMiddelware())
{
    authorized.POST("/login", loginEndpoint)
}

群组可以嵌套,因为中间件也可以根据群组的嵌套规则嵌套。

中间件实践

中间件最大的作用,莫过于用于一些记录log,错误handler,还有就是对部分接口的鉴权。下面就实现一个简易的鉴权中间件。

    router.GET("/auth/signin", func(c *gin.Context) {
        cookie := &http.Cookie{
            Name:     "session_id",
            Value:    "123",
            Path:     "/",
            HttpOnly: true,
        }
        http.SetCookie(c.Writer, cookie)
        c.String(http.StatusOK, "Login successful")
    })

    router.GET("/home", AuthMiddleWare(), func(c *gin.Context) {
        c.JSON(http.StatusOK, gin.H{"data": "home"})
    })

登录函数会设置一个session_id的cookie,注意这里需要指定path为/,不然gin会自动设置cookie的path为/auth,一个特别奇怪的问题。/homne的逻辑很简单,使用中间件AuthMiddleWare注册之后,将会先执行AuthMiddleWare的逻辑,然后才到/home的逻辑。

AuthMiddleWare的代码如下:

func AuthMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        if cookie, err := c.Request.Cookie("session_id"); err == nil {
            value := cookie.Value
            fmt.Println(value)
            if value == "123" {
                c.Next()
                return
            }
        }
        c.JSON(http.StatusUnauthorized, gin.H{
            "error": "Unauthorized",
        })
        c.Abort()
        return
    }
}

从上下文的请求中读取cookie,然后校对cookie,如果有问题,则终止请求,直接返回,这里使用了c.Abort()方法。

In [7]: resp = requests.get('http://127.0.0.1:8000/home')

In [8]: resp.json()
Out[8]: {u'error': u'Unauthorized'}

In [9]: login = requests.get('http://127.0.0.1:8000/auth/signin')

In [10]: login.cookies
Out[10]: <RequestsCookieJar[Cookie(version=0, name='session_id', value='123', port=None, port_specified=False, domain='127.0.0.1', domain_specified=False, domain_initial_dot=False, path='/', path_specified=True, secure=False, expires=None, discard=True, comment=None, comment_url=None, rest={'HttpOnly': None}, rfc2109=False)]>

In [11]: resp = requests.get('http://127.0.0.1:8000/home', cookies=login.cookies)

In [12]: resp.json()
Out[12]: {u'data': u'home'}

异步协程

golang的高并发一大利器就是协程。gin里可以借助协程实现异步任务。因为涉及异步过程,请求的上下文需要copy到异步的上下文,并且这个上下文是只读的。

    router.GET("/sync", func(c *gin.Context) {
        time.Sleep(5 * time.Second)
        log.Println("Done! in path" + c.Request.URL.Path)
    })

    router.GET("/async", func(c *gin.Context) {
        cCp := c.Copy()
        go func() {
            time.Sleep(5 * time.Second)
            log.Println("Done! in path" + cCp.Request.URL.Path)
        }()
    })

在请求的时候,sleep5秒钟,同步的逻辑可以看到,服务的进程睡眠了。异步的逻辑则看到响应返回了,然后程序还在后台的协程处理。

自定义router

gin不仅可以使用框架本身的router进行Run,也可以配合使用net/http本身的功能:

func main() {
    router := gin.Default()
    http.ListenAndServe(":8080", router)
}

或者

func main() {
    router := gin.Default()

    s := &http.Server{
        Addr:           ":8000",
        Handler:        router,
        ReadTimeout:    10 * time.Second,
        WriteTimeout:   10 * time.Second,
        MaxHeaderBytes: 1 << 20,
    }
    s.ListenAndServe()
}

当然还有一个优雅的重启和结束进程的方案。后面将会探索使用supervisor管理golang的进程。

总结

Gin是一个轻巧而强大的golang web框架。涉及常见开发的功能,我们都做了简单的介绍。关于服务的启动,请求参数的处理和响应格式的渲染,以及针对上传和中间件鉴权做了例子。更好的掌握来自实践,同时gin的源码注释很详细,可以阅读源码了解更多详细的功能和魔法特性。

php8 源码编译安装

简述

PHP 团队于2020年11月26日宣布 PHP 8 正式发布!这意味着将不会有 PHP 7.5 版本。PHP8 目前正处于非常活跃的开发阶段,所以在接下来的几个月里,情况可能会发生很大的变化。我也分享一些研究PHP 8 的心得,希望PHPer大家一起共同进步。首先说一下最受关注的JIT。

JIT

由于 PHP 8 是一个新的大版本,因此升级版本,代码被破坏的可能性更高。如果项目始终保持运行 PHP 的最新版本,那么升级相对来说就会轻松很多,因为在 7. * 版本中,大多数重大更改均已弃用。除重大更改外,PHP 8 还带来了一些不错的新功能,比如说 JIT 编译器 , 联合类型 , 属性,以及更多。很多人可能对JIT有很深的误解,觉得引入JIT之后性能就能提高10倍跟V8平起平坐了,事实上不是这样的。JIT技术的水很深,动态语言的JIT尤其困难,V8的诞生几乎可以说是一个技术奇迹。以PHP社区的技术水平,我谨慎地不看好他们解决这个问题的能力,毕竟Facebook的HHVM也没有完全解决,最后是靠Hacklang补全PHP的语法功能之后才基本圆满解决的。

 

动态语言的JIT本质要解决的问题之中,生成汇编只是一小部分,对于弱类型和动态类型语言来说,优化内存布局也是重点。例如,对于JavaScript和Python来说,以前对象内部是一个HashMap,这种数据结构的访问效率比较低,导致访问对象的每个属性都很慢,在JIT之后会将它优化成类似C++的平铺式的布局,将属性的值按顺序放在特定的位置上,这就带来一些新的要求:

  1. 没有类型标注的情况下,JIT只能猜测类型而无法肯定,那么使用优化的类型布局之前需要进行额外的检测,判断是否的确为预想的类型;

  2. 属性的类型也需要进一步推测,使用时也需要检验;

  3. JavaScript、Python乃至PHP都支持在对象创建之后为它添加新的属性。之前符合推测的类型后来添加或者删除了属性,要怎么处理?

 

  除此之外,调用函数时候如何优化调用开销也是一个重点,本质上跟优化对象的内存布局是类似的,可以将传入参数看成是构建一个有多个属性的对象,每个属性的类型不同。局部变量也需要有选择性地优化到寄存器、栈和堆当中。

PHP在这里的优势是支持类型标注,缺点是所有Hacklang里面修改掉的部分:

1. 不支持泛型,尤其是array类型不支持泛型。将一个变量类型标注为array几乎没有任何帮助,PHP中的array可以是顺序表也可以是hashmap,还可以混着,value的类型也不确定,这些都对类型优化有很高要求。Hacklang就推荐废掉array改用vector等几个确定类型且支持泛型的数据结构。

  2. reference这个功能,这个功能非常容易成为内存布局优化的障碍,也会阻碍JIT生成高效代码,尤其是数组中可以存储reference这件事,JIT编译器完全无法从字面上判断某条对array元素赋值的语句是否会影响环境中的其它变量的值。这也是为什么Hacklang直接删掉了这个功能。

  3. 其他参考Hacklang的变更

之前版本(PHP7)抠解释器实现带来的性能优化也会是一个阻碍,JIT的时候这些都得放弃掉,因为内存布局不一样了,这样可能导致最初的时候许多应用JIT反而变慢。所以,PHP8如果解决不了这些问题,最大的可能是许多microbenchmark速度大幅上升,但整体应用性能持平,自娱自乐。

源码编译安装

1. 安装依赖

yum -y install autoconf freetype gd libpng libpng-devel libjpeg libxml2 libxml2-devel zlib curl curl-devel net-snmp-devel libjpeg-devel

2. oniguruma 依赖包

yum install autoconf automake libtool
wget https://github.com/kkos/oniguruma/archive/v6.9.4.tar.gz -O oniguruma-6.9.4.tar.gz
tar xf oniguruma-6.9.4.tar.gz && cd oniguruma-6.9.4
./autogen.sh && ./configure --prefix=/usr
make && make install

4.新建用户

userdel www
groupadd www
useradd -g www -M -d /data/www -s /sbin/nologin www &> /dev/null

5. 下载源码包

wget  https://www.php.net/distributions/php-8.0.7.tar.gz

6. 解压

tar -zxvf php-8.0.7.tar.gz
cd php-8.0.7

7. 编译和安装 (选择自己需要的安装)

./configure --prefix=/usr/local/php \
--with-config-file-path=/usr/local/php \
--enable-mbstring  \
--enable-ftp  \
--enable-gd   \
--enable-gd-jis-conv \
--enable-mysqlnd \
--enable-pdo   \
--enable-sockets   \
--enable-fpm   \
--enable-xml  \
--enable-soap  \
--enable-pcntl   \
--enable-cli   \
--with-openssl  \
--with-mysqli=mysqlnd   \
--with-pdo-mysql=mysqlnd   \
--enable-mysqlnd-compression-support \
--with-pear   \
--with-zlib  \
--with-iconv  \
--with-curl   \
--build=arm-linux && make && make install

7. 开始配置

新增环境变量

vim /etc/profile
##最后增加 
export PHP=/usr/local/php
export PATH=$PHP/bin:$PHP/sbin:$PATH
source /etc/profile 

8. 配置PHP

cp php.ini-production /usr/local/php/etc/php.ini
cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf
cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf
cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
chmod +x /etc/init.d/php-fpm

9. 测试

[root@localhost www]# php -v 
PHP 8.0.7 (cli) (built: Jun 25 2021 10:10:16) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.7, Copyright (c) Zend Technologies

 

编译过程错误解决

若在 configure 过程中,遇到提示 configure: error,留意看下方的错误详细信息。以下 列出了在配置过程中出现的错误情况之关键信息摘要,未列举到的其它错误情况,解决方 法如出一辙:

Error #1:

configure: error: Package requirements (libxml-2.0 >= 2.7.6) were not met:
No package ‘libxml-2.0’ found
解决方法:

yum install libxml2-devel.x86_64

Error #2:
configure: error: Package requirements (sqlite3 > 3.7.4) were not met:
No package ‘sqlite3’ found

解决方法:

yum install sqlite-devel.x86_64

安装配置匿名访问网络Tor洋葱浏览器

概述

tor工作原理

Tor由已经安装了Tor软件的电脑连接网络而成。它之所以被称为onion,是因为它的结构就跟洋葱相同,你只能看出它的外表,而想要看到核心,就必须把它层层的剥开。即每个路由器间的传输都经过点对点密钥(symmetric key)来加密,形成有层次的结构。它中间所经过的各节点,都好像洋葱的一层皮,把客户端包在里面,算是保护信息来源的一种方式。

用 Tor 创建一条私有网络路径时,用户的软件或客户端通过网络上的中继递增地建立一条由若干加密连接组成的环路(circuit)。环路一次扩展一跳(hop),环路上的中继仅仅知道它从哪一个中继接收数据以及向哪一个中继发送数据。没有一台单独的中继会知道数据包的完整路径。客户端与环路上的每一跳都协商一组独立的密钥,这样可以保证数据通过任何一跳时都无法跟踪。

tor browser浏览器

tor browser的最大特点就是匿名性。

  • 您的互联网服务提供商和任何本地的监视者都将无法查看您的连接、跟踪您的网络活动,包括您所访问网站的名称和地址。
  • 您使用的网站和服务的运营商以及任何监视它们的人都将看到连接来自 Tor 网络而不是您的互联网IP地址,并且不知道您是谁,除非您明确标识自己。

安装

1. Tor安装配置

首先我们下载安装tor browser
下载地址: https://tor-browser.en.softonic.com/

安装时根据网络环境选择直连安装、网络受限安装(内置网桥连接、获取网桥连接、代理连接)。直连安装是网络可直接访问国外受限站点的网络环境;网络受限安装是当前网络不能直接访问国外受限站点,如有通过代理可访问受限站点选择代理连接,没有代理可选择内置网桥连接或获取网桥连接。

获取Tor网桥方法:

(一)mail自助获取tor的桥接ip
用 gmail等给 [email protected] 发一封内容为 get bridges 的邮件,主题随意,或标题 get transport obfs3 正文 get transport obfs3 或 标题 get transport scramblesuit 正文 get transport scramblesuit

(二)直接随机获取网桥ip,https://bridges.torproject.org/打开网页获得网桥ip。

获得的网桥样子是:

obfs4 139.59.140.35:8491 B1137CEB247F5FF9B57DA3160E6931D0269FF010 cert=g92HymBTzn1KzyizGjJkXxT8YRJw/QC3LEfoVFWuv9but3AqLd916ZoB+fo/6xJqWB0faQ iat-mode=0
obfs4 185.178.93.141:9060 05012FE073A4B522580E6FE77F3FD92E1ABF6FD1 cert=377UalzVX10eI5raeIVmJFFvq0FpKEF12igXoKe2rMJWGJxScKzMm9dph9m4v8hz0y3hDg iat-mode=0
obfs4 192.241.152.208:444 05277333D6A14B6E706CF5A189C57C97C471189B cert=2j8mktwt8pA4LGmqBUFeDfkGbVE0U0MqYBIbt6/ePMRrlrk9UhHQeeyw8WqgGPa1xYzyaQ iat-mode=0

填加网桥:

** 请注意:只添加前面的地址和端口, 最后面的长串数字字母是数字指纹,不要添加。

Tor官方其实也更推荐obfs4 https://2019.www.torproject.org/docs/pluggable-transports

代理连接:

这种方法比较常用,原因是比较快,可以用免费的节点分享网址或者自己搭建(以后讲方法,欢迎关注)
免费的节点分享网址:http://free-proxy.cz/en

2. Tor高级选项配置方法

在 Tor Browser\Data\Tor目录中找到torrc文件,用记事本等文本编辑器打开这个文件,可以看到里面有一些参数,这个文件是tor的核心设置文件。

以下为排除的节点(StrictNodes 1为坚决执行)
ExcludeNodes {cn},{hk},{mo},{sg},{th},{pk},{by},{ru},{ir},{vn},{ph},{my},{cu}
ExcludeExitNodes {cn},{hk},{mo},{sg},{th},{pk},{by},{ru},{ir},{vn},{ph},{my},{cu}
StrictNodes 1
注:
ExcludeNodes 是指排除节点,即把括号中的国家的节点从tor链路上除去;
ExcludeExitNodes 是指“排除“出口”节点”,,即tor的出口节点要排除括号中的国家的节点。

指定出口节点:

StrictExitNodes 1
ExitNodes {us}
这里us是指限定美国的ip为出口ip,你可以改为任何国家,国家代码请参考:https://zh.wikipedia.org/zh-cn/ISO_3166-1

补充:opera浏览器自带VPN功能同样可以访问国外受限站点。将计算机区域设置为中国台湾,安装好opera并在opera设置里将首选语言选为繁体,清除缓除重启opera,再到opera设置的高级选项里的隐私安全设置里即有VPN启用选择项,启用VPN后可在浏览器地址栏看到VPN字样,点击此字样可定位VPN位置。如果当前网络环境本身就在(已启用其他代理)国外IP,就不需改变计算机区域位置及opera语言设置,直接到opera设置高级选择项就可选择是否开启VPN功能。

3.Tor 的匿名性(挺好)

Tor 的一些特性使得其匿名性略高于普通浏览器的无痕浏览模式:参考 Chrome 无痕浏览的工作原理;以及 Tor 安全设置说明:https://tb-manual.torproject.org/zh-CN/security-settings/

访问 https://ip.sb/ 或 https://whoer.net/zh 查看你的IP;既不是你的真实IP(网络运营商分配的IP),也不是你的代理IP(机场),而是 Tor 的三层叠加的最后一层 IP;

X-ui,支持多协议多用户的Xray 面板!V2-ui全面停止更新

前言

sprov 这位大神开发的这套面板程序,很是方便,可以可视化的搭建SS、V2ray、Xray、Trojan等热门的协议,并且可以实时看到 VPS 的性能状态以及流量的使用情况。

那在经过将近两年的不断更新之后,V2-ui面板,迎来了一个比较大的转折——停止更新了。

sprov 大神又用 GO 语言重新开发了一套面板 X-UI。那这套面板相比原来的 V2-ui,兼容性更强,也便于维护, GO 语言的性能更好,而且内存占用也会相对的低一些。

功能介绍

  • 系统状态监控
  • 支持多用户多协议,网页可视化操作
  • 支持的协议:vmess、vless、trojan、shadowsocks、dokodemo-door、socks、http
  • 支持配置更多传输配置
  • 流量统计,限制流量限制到期时间
  • 可自定义 xray 配置模板
  • 支持 https 访问面板(自备域名 + ssl 证书)
  • 更多高级配置项,详见该项目的 GitHub:点击访问

安装&升级

bash <(curl -Ls https://raw.githubusercontent.com/vaxilu/x-ui/master/install.sh)

根据提示安装后,在浏览器中打开 http://<服务器 IP>:<端口>即可访问面板,默认用户名和密码都是 admin。

登陆后请修改用户名和密码

更换面板端口

x-ui 打开脚本面板,更换面板端口为你想用的端口即可,记得开放防火墙

添加节点

添加节点的过程 youtube 有很多视频,不再赘述

最简单的就是添加一个 vmess+websocket,其他保持全默认即可

手动安装&升级

  1. 首先从 https://github.com/vaxilu/x-ui/releases 下载最新的压缩包,一般选择amd64架构
  2. 然后将这个压缩包上传到服务器的/root/目录下,并使用root用户登录服务器

如果你的服务器 cpu 架构不是amd64,自行将命令中的amd64替换为其他架构

cd /root/
rm x-ui/ /usr/local/x-ui/ /usr/bin/x-ui -rf
tar zxvf x-ui-linux-amd64.tar.gz
chmod +x x-ui/x-ui x-ui/bin/xray-linux-* x-ui/x-ui.sh
cp x-ui/x-ui.sh /usr/bin/x-ui
cp -f x-ui/x-ui.service /etc/systemd/system/
mv x-ui/ /usr/local/
systemctl daemon-reload
systemctl enable x-ui
systemctl restart x-ui

建议系统

  • CentOS 7+
  • Ubuntu 16+
  • Debian 8+

常见问题

从 v2-ui 迁移

首先在安装了 v2-ui 的服务器上安装最新版 x-ui,然后使用以下命令进行迁移,将迁移本机 v2-ui 的所有 inbound 账号数据至 x-ui,面板设置和用户名密码不会迁移

迁移成功后请关闭 v2-ui并且重启 x-ui,否则 v2-ui 的 inbound 会与 x-ui 的 inbound 会产生端口冲突

x-ui v2-ui

更新系统

安装相关依赖
centos 系统下

yum update -y                                                                                                
apt-get update -y && apt-get install curl -y

ubuntu 系统下

apt update -y
apt-get update -y && apt-get install curl -y

若出现 bash curl: command not found 等错误,请先安装 curl

CentOS 系统:

yum install curl -y

Debian 或 Ubuntu 系统:

apt install curl -y

错误提示:-bash curl: command not found

Ubuntu、Debian 如果出现无法安装 Curl, 需要先升级

sudo apt-get update

申请 SSL 证书

下面环境的安装方式,大家根据自己的系统选择命令安装就好了。

更新及安装组件

apt update -y          # Debian/Ubuntu 命令
apt install -y curl    #Debian/Ubuntu 命令
apt install -y socat    #Debian/Ubuntu 命令
yum update -y          #CentOS 命令
yum install -y curl    #CentOS 命令
yum install -y socat    #CentOS 命令

安装 Acme 脚本

curl https://get.acme.sh | sh

80 端口空闲的证书申请方式

自行更换代码中的域名、邮箱为你解析的域名及邮箱

~/.acme.sh/acme.sh --register-account -m [email protected]
~/.acme.sh/acme.sh  --issue -d mydomain.com   --standalone

安装证书到指定文件夹

自行更换代码中的域名为你解析的域名

~/.acme.sh/acme.sh --installcert -d mydomain.com --key-file /root/private.key --fullchain-file /root/cert.crt

FunAdmin开发框架系统 支持PHP8.0(ThinkPHP6.X + Layui +Requirejs)

介绍

FunAdmin是基于ThinkPHP6.X + Layui +Requirejs的敏捷开发的后台管理系统。

演示站点

演示地址: fundemo.funadmin.com (账号:admin,密码:123456。备注:只有查看信息的权限

主要特性

  • 基于auth的权限管理系统
    • 支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置
    • 支持管理员多角色管理
  • CURD 命令行模式助您快速开发
  • 完善的菜单管理
    • 分模块管理
    • 无限极菜单
  • 二级域名部署支持,同时域名支持绑定到插件
  • 多语言支持,服务端及客户端支持
  • 丰富的插件市场
  • 完善的上传插件组件功能
    • 本地存储
    • 阿里云OSS建议使用
    • 腾讯云COS
    • 七牛云OSS
  • 完善的前端功能组件开发
    • 自适应手机、平板、PC,自使用IE+谷歌+火狐等浏览器
    • 基于RequireJS进行JS模块管理,按需加载
    • 对form表单二次封装,只需要使用函数就可以展示表
    • 对layui table ,form表格再次封装,方便使用
  • 完善的后台操作日志
    • 记录用户的详细操作信息 操作ip 等
  • 文件附件管理
  • 后台路径自定义,防止别人找到对应的后台地址

项目介绍

FunAdmin 基于thinkphp6.X +Layui2.6.*+requirejs开发权限(RBAC)管理框架,框架中集成了权限管理、模块管理、插件管理、后台支持多主题切换、配置管理、会员管理等常用功能模块,以方便开发者快速构建自己的应用。框架专注于为中小企业提供最佳的行业基础后台框架解决方案,执行效率、扩展性、稳定性值得信赖,操作体验流畅,使用非常优化,欢迎大家使用及进行二次开发。

  • PHP支持php8.0的快速开发框架 建议使用PHP8
  • 这是一个有趣的后台管理系统,这是可以让你节约时间的系统
  • 这是一款快速、高效、便捷、灵活敏捷的应用开发框架。
  • 系统采用最新版TinkPHP6框架开发,底层安全可靠,数据查询更快,运行效率更高,网站速度更快, 后续随官网升级而升级
  • 密码动态加密,相同密码入库具有唯一性,用户信息安全牢固,告别简单md5加密
  • 自适应前端,桌面和移动端访问界面友好简洁,模块清晰
  • 兼容ie11 + firefox + Chrome +360 等浏览器
  • UI组件化,只需要写函数就可以成就后台表单
  • 内置CURD 命令行模式,帮助助您快速开发系统
  • 模块化:全新的架构和模块化的开发机制,便于灵活扩展和二次开发。
  • 强大的表单管理,只需要使用函数即可成就表单
  • layui采用最新layui2.6.X 框架
  • 适用范围:可以开发OA、ERP、BPM、CRM、WMS、TMS、MIS、BI、电商平台后台、物流管理系统、快递管理系统、教务管理系统等各类管理软件。
  • require.js 模块化开发 一个命令即可打包js,css ; node r.js -o backend-build.js
  • restful api 接口,接口使用jwt接口验证等
  • …更多功能尽请关注

环境要求:

  • PHP >= 7.4 支持php8.0
  • PDO PHP Extension
  • MBstring PHP Extension
  • CURL PHP Extension
  • 开启静态重写
  • 要求环境支持pathinfo
  • Mysql 5.7及以上
  • Apache 或 Nginx

功能特性

  • 严谨规范: 提供一套有利于团队协作的结构设计、编码、数据等规范。
  • 高效灵活: 清晰的分层设计,解耦设计更能灵活应对需求变更。
  • 严谨安全: 清晰的系统执行流程,严谨的异常检测和安全机制,详细的日志统计,为系统保驾护航。
  • 组件化: 完善的组件化设计,丰富的表单组件,让开发列表和表单更得心应手。无需前端开发,省时省力。
  • 简单上手快: 结构清晰、代码规范、在开发快速的同时还兼顾性能的极致追求。
  • 自身特色: 权限管理、组件丰富、第三方应用多、分层解耦化设计和先进的设计思想。
  • 高级进阶: 分布式、负载均衡、集群、Redis、分库分表。

插件

  • ** CMS内容管理插件(免费)
  • ** BBS社区插件
  • ** 编辑器插件
  • ** 微信管理插件
  • ** 更多请查看 插件列表

开发者信息

  • 开源协议:Apache 2.0

Centos 7安装配置NTP网络时间同步服务器

现在的系统大多是微服务化架构,且部署方式大多为分布式集群化部署,也就是说系统的各个组件分布在不同的服务器中运行,导致时间同步对于系统的正常运行来说尤为重要, 由于时间不同同步,会导致系统出现各种异常

NTP是网络时间协议(Network Time Protocol),它是用来同步网络中各个计算机的时间的协议。本文,我们采用ntpd做时间同步,ntpd不仅仅是时间同步服务器,它还可以做客户端与标准时间服务器进行同步时间,而且是平滑同步

时间时区概念理解:

GMT、UTC、CST、DST

UTC:

整个地球分为二十四时区,每个时区都有自己的本地时间,在国际无线电通信场合,为了统一起见,使用一个统一的时间,称为通用协调时(UTC:Universal Time Coordinated)。

GMT:

格林威治标准时间 (Greenwich Mean Time)指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线(UTC与GMT时间基本相同)。

CST:

中国标准时间 (China Standard Time)

GMT + 8 = UTC + 8 = CST

DST:
夏令时(Daylight Saving Time) 指在夏天太阳升起的比较早时,将时间拨快一小时,以提早日光的使用(中国不使用)。

1、查看当前服务器时区&列出时区并设置时区(如已是正确时区,请略过):

查看当前系统时间、时区

$ timedatectl 
Local time: Thu 2018-10-11 13:03:04 CST
Universal time: Thu 2018-10-11 05:03:04 UTC
RTC time: Thu 2018-10-11 01:17:11
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: no
NTP synchronized: no
RTC in local TZ: no
DST active: n/a

$ timedatectl status
Local time: Thu 2018-10-11 13:03:09 CST
Universal time: Thu 2018-10-11 05:03:09 UTC
RTC time: Thu 2018-10-11 01:17:16
Time zone: Asia/Shanghai (CST, +0800)
NTP enabled: no
NTP synchronized: no
RTC in local TZ: no
DST active: n/a

列出全世界所有的时区

# timedatectl list-timezones | grep Asia
...
Asia/Hong_Kong
Asia/Shanghai
Asia/TaipeiAsia/Urumqi
...

设置时区

# 方法1:
# 将时区设置为上海
$ timedatectl set-timezone Asia/Shanghai 
#方法2:
# 直接修改符号链接
$ rm /etc/localtime$ ln -s ../usr/share/zoneinfo/Asia/Shanghai /etc/localtime

若坚持要手动修改时间,先timedatectl set-ntp no。

# 设置日期和时间
$ timedatectl set-time '2018-10-11 09:00:00'
# 设置日期
$ timedatectl set-time '2018-10-11'
# 设置时间
$ timedatectl set-time '09:00:00'

# 方法2:使用date

$ date -s '2018-10-11 09:00:00'

同步系统时间到硬件时间

# 方法1:不建议硬件时间随系统时间变化
# 设置硬件时间随系统时间变化
$ timedatectl set-local-rtc 1
# 设置硬件时间不随系统时间变化
$ timedatectl set-local-rtc 0

# 方法2:
$ hwclock --systohc

是否启用自动同步时间

# 启用|停用自动同步时间
$ timedatectl set-ntp yes|no

# 上面的命令其实是启用、停用时间服务器,若安装了chrony服务,则等同于对该服务启停,若只安装了ntp,则是对ntp服务启停。
# 对chrony服务启停
$ systemctl start|stop chronyd
# 对ntp服务启停
$ systemctl start|stop ntpd

2、确认是否已安装ntp

【命令】rpm –qa | grep ntp

若只有ntpdate而未见ntp,则需删除原有ntpdate。如:

ntpdate-4.2.6p5-22.el7_0.x86_64

fontpackages-filesystem-1.44-8.el7.noarch

python-ntplib-0.3.2-1.el7.noarch

删除已安装ntp
【命令】yum –y remove ntpdate-4.2.6p5-22.el7.x86_64

重新安装ntp
【命令】yum –y install ntp

3、安装NTP

yum install -y ntp

说明:如果本机不支持联网,则可以找一台可以上网的服务器把安装包下载下来,然后做离线安装:

yum install --downloadonly --downloaddir=/home ntp

修改NTP配置

/etc/ntp.conf

#在配置中增加以下配置:
#允许上层时间服务器主动修改本机时间

restrict time.pool.aliyun.com nomodify notrap noquery

#允许所有主机通过本机同步时间

restrict default nomodify notrap

#外部时间服务器不可用时,以本地时间作为时间服务

server time.pool.aliyun.com iburst

server 127.127.1.0
fudge 127.127.1.0 stratum 10

修改完成后保存退出并重启ntp(systemctl restart ntpd)。

说明:
#nomodfiy:客户端不能更改服务端的时间参数,但是客户端可以通过服务端进行网络校时。
#noquery:不提供客户端的时间查询。
#notrap:不提供trap这个远程时间登录的功能

4、启动NTP服务&开机启动设置

#启动NTP服务

systemctl start ntpd

#将NTP服务设置为开机启动

systemctl enable ntpd

5、chrony的安装配置

安装chrony

$ yum -y install chrony

配置chrony

$ vim /etc/chrony.conf
server ntp1.alyun.com
server ntp2.alyun.com
server ntp3.alyun.com
#server 0.centos.pool.ntp.org iburst...

启动chrony

$ systemctl start chronyd

6、配置计划任务,使用ntpdate同步时间

# 启动并开机启动计划任务cron
$ systemctl start crond
$ systemctl enable crond

# 配置计划任务,每5分钟同步一次
$ crontab -e
*/5 * * * * /usr/sbin/ntpdate ntp1.aliyun.com

tp6 解决跨域问题

在和前端js对接接口的时候遇到跨域问题,记录一下

方法一
通过route文件配置
文件位置 : app/api/route/app.php

<?php
/**
 * Created by PhpStorm.
 * User: 雪后西溏 <[email protected]>
 * Date: 2021-02-23
 * Time: 16:06
 */

use think\facade\Route;

// 手写资源路由
Route::group(':v/:c', function () {
    Route::get('', '/:v.:c/index');
    Route::get('create', '/:v.:c/create');
    Route::get(':id/edit', '/:v.:c/edit');
    Route::get(':id', '/:v.:c/read');
    Route::post('', '/:v.:c/save');
    Route::put(':id', '/:v.:c/update');
    Route::delete(':id', '/:v.:c/delete');
})->allowCrossDomain([
    /** 设置跨域允许的header头信息,新增token字段 */
    'Access-Control-Allow-Headers'     => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, authKey, Accept, Origin, token',
    /** 允许所有请求 */
    'Access-Control-Allow-Origin'      => '*',
    'Access-Control-Allow-Credentials' => 'true',
]);

更新后写法

<?php
/**
 * Created by PhpStorm.
 * User: 雪后西溏 <[email protected]>
 * Date: 2021-06-16
 * Time: 10:14
 */

use think\facade\Route;

// 开启资源路由
Route::resource(':v/:c', ':v.:c');
// 配置跨域
Route::allowCrossDomain([
    /** 设置跨域允许的header头信息 , 增加token字段 */
    'Access-Control-Allow-Headers'     => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With, authKey, Accept, Origin, token',
    //    允许所有请求
    'Access-Control-Allow-Origin'      => '*',
]);

参见 官方文档
https://www.kancloud.cn/manual/thinkphp6_0/1037507

 

方法二

设置全局中间件

在 ./app/middleware.php 文件中启用内置中间件即可

\think\middleware\AllowCrossDomain::class
<?php
// 全局中间件定义文件
return [
    // 全局请求缓存
    // \think\middleware\CheckRequestCache::class,
    // 多语言加载
    // \think\middleware\LoadLangPack::class,
    // Session初始化
    // \think\middleware\SessionInit::class
    \think\middleware\AllowCrossDomain::class,
];

 

 

linux命令总结之date命令

命令简介:

date 根据给定格式显示日期或设置系统日期时间。print or set the system date and time

指令所在路径:/bin/date

命令语法:

date [OPTION]… [+FORMAT]

date [-u|–utc|–universal] [MMDDhhmm[[CC]YY][.ss]]

命令参数:

参数 描述
-d 显示字符串描述的时间
-f 显示DATEFILE文件中的每行时间
-r 显示文件的最后修改时间
-R 以RFC-2822兼容日期格式显示时间
-rfc-2822 以RFC-2822兼容日期格式显示时间
-s 设置时间为string
-u 显示或设定为Coordinated Universal Time时间格式
–help 显示date命令的帮助信息
–version 显示date命令的版本信息

Format参数格式

要说写这位程序的 David MacKenzie老兄,真是事无巨细啊,居然整了这么多格式参数,佩服佩服。

参数 描述
%% 显示字符%
%a 星期几的缩写(Sun..Sat)
%A 星期几的完整名称(Sunday…Saturday)
%b 月份的缩写(Jan..Dec)
%B 月份的完整名称(January..December)
%c 日期与时间。只输入date指令也会显示同样的结果
%C 世纪(年份除100后去整) [00-99]
%d 日期(以01-31来表示)。
%D 日期(含年月日)。
%e 一个月的第几天 ( 1..31)
%F 日期,同%Y-%m-%d
%g 年份(yy)
%G 年份(yyyy)
%h 同%b
%H 小时(00..23)
%I 小时(01..12)
%j 一年的第几天(001..366)
%k 小时( 0..23)
%l 小时( 1..12)
%m 月份(01..12)
%M 分钟(00..59)
%n 换行
%N 纳秒(000000000..999999999)
%p AM or PM
%P am or pm
%r 12小时制时间(hh:mm:ss [AP]M)
%R 24小时制时间(hh:mm)
%s 从00:00:00 1970-01-01 UTC开始的秒数
%S 秒(00..60)
%t 制表符
%T 24小时制时间(hh:mm:ss)
%u 一周的第几天(1..7); 1 表示星期一
%U 一年的第几周,周日为每周的第一天(00..53)
%V 一年的第几周,周一为每周的第一天 (01..53)
%w 一周的第几天 (0..6); 0 代表周日
%W 一年的第几周,周一为每周的第一天(00..53)
%x 日期(mm/dd/yy)
%X 时间(%H:%M:%S)
%y 年份(00..99)
%Y 年份 (1970…)
%z RFC-2822 风格数字格式时区(-0500)
%Z 时区(e.g., EDT), 无法确定时区则为空

 

使用示例:

1: 查看date命令帮助信息

 date --help
 man date

2:运用-d参数

[root@Gin scripts]# date
Sun Jan 29 10:46:03 CST 2017
[root@Gin scripts]# date -d now
Sun Jan 29 10:46:07 CST 2017
[root@Gin scripts]# date -d 'next monday'
Mon Jan 30 00:00:00 CST 2017
[root@Gin scripts]# date -d yesterday +%Y%m%d
20170128

3:显示文件中的时间

[root@Gin scripts]# cat >date.txt
2013-11-17 10:54:00
2013-11-22 11:11:11

[root@Gin scripts]# more date.txt
2013-11-17 10:54:00
2013-11-22 11:11:11

[root@Gin scripts]# date -f date.txt
Sun Nov 17 10:54:00 CST 2013
Fri Nov 22 11:11:11 CST 2013

4:显示文件最后修改的时间

[root@Gin scripts]# date -r date.txt
Sun Jan 29 10:49:11 CST 2017

5:按各种格式显示当前日期时间
这个命令的格式参数实在是太多了,在此没有必要每一个参数都尝试一遍,大家可以对照Format参数表,自己敲一敲命令实践一下。

[root@Gin scripts]# date +%Y
2017
[root@Gin scripts]# date +%m
01
[root@Gin scripts]# date +%D
01/29/17
[root@Gin scripts]# date '+%Y-%m-%d'
2017-01-29
[root@Gin scripts]# date +%Y-%m-%d
2017-01-29
[root@Gin scripts]# date +%m/%d/%y
01/29/17

6: 设置系统时间

[root@Gin scripts]# date -s "2016-11-11 00:00:00"
Fri Nov 11 00:00:00 CST 2016
[root@Gin scripts]# date
Fri Nov 11 00:00:05 CST 2016

7:请给出如下格式的date命令,如:11-02-26。再给出实现按周输出,如:周六为6,请分别给出命令

[root@Gin scripts]# date +%y-%m-%d
17-01-29
[root@Gin scripts]# date +%F
2017-01-29
[root@Gin scripts]# date "+%Y-%m-%d %H:%M:%S"
2017-01-29 10:57:02

生产场景中常用时间格式来打包数据:

[root@Andy andy]# tar zcvf etc-$(date +%F).tar.gz /etc
[root@Andy andy]# tar zcvf etc-`date +%F`.tar.gz /etc
解析命令方法:反引号,或$(),如上
[root@Andy andy]# date +%w  ##显示周,0-6

显示过去与未来时间:

[root@Andy andy]# date +%F
2016-11-16
[root@Andy andy]# date +%F -d "-1day"
2016-11-15
[root@Andy andy]# date +%F -d "+2day"
2016-11-18
[root@Andy andy]# date +%F -d "+24hour"
2016-11-17
[root@Andy andy]# date +%F-%H -d "+2hour"
2016-11-16-14

centos设置时区且自动同步

Linux的时间分为System Clock(系统时间)和Real Time Clock (硬件时间,简称RTC)。

系统时间:指当前Linux Kernel中的时间。

硬件时间:主板上有电池供电的时间。

查看系统时间的命令: #date

设置系统时间的命令: #date –set(月/日/年 时:分:秒)

例:#date –set “10/11/10 10:15”

查看硬件时间的命令: # hwclock

设置硬件时间的命令: # hwclock –set –date = (月/日/年 时:分:秒)

上述提到的是手动设置时间到一个时间点,可能与当前网络的时间有误差。下面介绍一下与时间服务器上的时间同步的方法

 

1.查看时间

[root@bogon ~]# date
Thu Apr 4 11:24:24 CST 2019

2.使用date -s修改时间

[root@bogon ~]# date -s "2019/4/4 11:26" 
Thu Apr  4 11:26:00 CST 2019

3.修改时区

使用新的时区文件覆盖系统默认时区
 # cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime

4.安装ntpdate

yum install ntpdate -y

5.同步网络时间

ntpdate ntp1.aliyun.com

6.保存为bios时间

同步BIOS时钟,强制将系统时间写入CMOS,使之永久生效,避免系统重启后恢复成原时间。
# clock -w

7.设置crontab同步时间

59 23 * * *    ntpdate ntp1.aliyun.com
*  *  *  *  *  command 
分 时 日 月 周 命令 
第1列表示分钟1~59 每分钟用*或者 */1表示 
第2列表示小时1~23(0表示0点) 
第3列表示日期1~31 
第4列表示月份1~12 
第5列标识号星期0~6(0表示星期天) 
第6列要运行的命令

8.查看bios时间

[root@bogon ~]# hwclock  -r
Thu 04 Apr 2019 11:35:20 AM CST  -0.365289 seconds

推荐一个国产的SSH工具FinalShell ,服务器管理,远程桌面加速软件,支持Windows,macOS,Linux

fs5.png

之前管理Linux一直用的是Xshell+xftp的搭配,不得不说,两者的结合还是很完善的,用起来也很方便,但是xshell的界面风格一直有些“反人类”,不止一个同事问过我“除了xshell外有没有其他的好用的ssh连接软件”,最令我生气的是有一次的升级把我的所有服务器的连接凭证全部弄丢了,但我一直没有想到有什么能代替的,要么比xshell更老旧,要么功能不全面。最近不知道为什么看到好多人推荐FinalShell,讲真,我之前是没有听说过的,今天试了一下不得不说真的很强大,接下来今天打算用用试试稳定性如何。
支持Windows,Mac OS X,Linux
FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求.
特色功能:

免费海外服务器远程桌面加速,ssh加速,双边tcp加速,内网穿透.

官网:http://www.hostbuf.com/

FinalShell是一体化的的服务器,网络管理软件,不仅是ssh客户端,还是功能强大的开发,运维工具,充分满足开发,运维需求.
特色功能:
免费海外服务器远程桌面加速,ssh加速,双边tcp加速,内网穿透.

Windows版下载地址:
http://www.hostbuf.com/downloads/finalshell_install.exe

Mac版,Linux版安装及教程:
http://www.hostbuf.com/t/1059.html

更新日志:
http://www.hostbuf.com/t/989.html

主要特性:

1.多平台支持Windows,Mac OS X,Linux

2.多标签,批量服务器管理.

3.支持登录Ssh和Windows远程桌面.

4.漂亮的平滑字体显示,内置100多个配色方案.

5.shell,sftp同屏显示,同步切换目录.

6.命令自动提示,智能匹配,输入更快捷,方便.

7.sftp支持,通过各种优化技术,加载更快,切换,打开目录无需等待.

8.服务器网络,性能实时监控,无需安装服务器插件.

9.内置海外服务器加速,加速远程桌面和ssh连接,操作流畅无卡顿.

10.双边加速功能,大幅度提高访问服务器速度.

11.内存,Cpu性能监控,Ping延迟丢包,Trace路由监控.

12.实时硬盘监控.

13.进程管理器.

14.快捷命令面板,可同时显示数十个命令.

15.内置文本编辑器,支持语法高亮,代码折叠,搜索,替换.

16.ssh和远程桌面均支持代理服务器.

17.打包传输,自动压缩解压.

18.免费内网穿透,无需设置路由器,无需公网ip.

界面截图:

Shell终端
fs5.png
p2.png 
p4.pngp6.png
进程管理器
task_manager.png
主机检测,Ping监控,Trace实时跟踪
ping_tracert.png

高级网络监控,监控每个进程监听的端口,以及网络连接状态.222.png
双边加速
http://www.hostbuf.com/upload/image/20170222/1487738221746093924.png
打包传输,自动压缩解压,适合传输大量文件,文件夹和文本文件.
z1.pngz2.png