go web脚手架
 
 
 
 
 
 
Go to file
xiao.ming 8925945571 增加web代码 2021-11-30 16:19:40 +08:00
docker 配置镜像时区 2021-11-20 17:39:18 +08:00
res 修改docker部署配置 2021-11-16 14:54:50 +08:00
server 增加web代码 2021-11-30 16:19:40 +08:00
web 增加web代码 2021-11-30 16:19:40 +08:00
.gitignore 增加用户/权限/菜单功能 2021-10-29 14:39:50 +08:00
README.md 修改文件上传目录 2021-11-16 19:45:31 +08:00

README.md

目录

docker部署

  cd docker && docker-compose up -d  

修改IP地址为安装容器主机的IP地址

# nginx配置
1. docker/nginx/nginx.conf

 location /week/ {
              proxy_pass  http://服务器IP:8981/;
 }

2. server/config.yaml

# mysql connect configuration
mysql:
  path: 'mysqlIP:3306'

访问地址: http://localhost:8980即可

运行界面截图:
写周报


统计规则

goweb-gin-demo

go web脚手架, 数据库及表结构

web框架gin

中文参考文档

特性

  • 快速 基于 Radix 树的路由,小内存占用。没有反射。可预测的 API 性能。

  • 支持中间件 传入的 HTTP 请求可以由一系列中间件和最终操作来处理。 例如LoggerAuthorizationGZIP最终操作 DB。

  • Crash 处理 Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic

  • JSON 验证 Gin 可以解析并验证请求的 JSON例如检查所需值的存在。

  • 路由组 更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。

  • 错误管理 Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。

  • 内置渲染 Gin 为 JSONXML 和 HTML 渲染提供了易于使用的 API。

  • 可扩展性 新建一个中间件非常简单,去查看 示例代码 吧。

服务创建及启动

官方demo
把ping映射在参数为(c *gin.Context)的方法

package main

import "github.com/gin-gonic/gin"

func main() {
	r := gin.Default()
	r.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	r.Run() // 监听并在 0.0.0.0:8080 上启动服务
}

endless 创建服务

func main() {
	mux1 := mux.NewRouter()
	mux1.HandleFunc("/hello", handler).
		Methods("GET")

	srv := endless.NewServer("localhost:4244", mux1)
	srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1] = append(
		srv.SignalHooks[endless.PRE_SIGNAL][syscall.SIGUSR1],
		preSigUsr1)
	srv.SignalHooks[endless.POST_SIGNAL][syscall.SIGUSR1] = append(
		srv.SignalHooks[endless.POST_SIGNAL][syscall.SIGUSR1],
		postSigUsr1)

	err := srv.ListenAndServe()
	if err != nil {
		log.Println(err)
	}
	log.Println("Server on 4244 stopped")

	os.Exit(0)
}

gorm

概述

  • 全功能 ORM
  • 关联 (Has OneHas ManyBelongs ToMany To Many多态单表继承)属于、多对多、多态、单表继承)
  • CreateSaveUpdateDeleteFind 中钩子方法保存/更新/删除/查找)
  • 支持 Preload、Joins 的预加载Joins
  • 事务嵌套事务Save PointRollback To Saved Point、回滚到保存点
  • Context、预编译模式、DryRun 模式DryRun 模式
  • 批量插入FindInBatchesFind/Create with Map使用 SQL 表达式、Context Valuer 进行 CRUDBatches、使用 Map 查找/创建、使用 SQL Expr 和 Context Valuer 进行 CRUD
  • SQL 构建器Upsert数据库锁Optimizer/Index/Comment Hint命名参数子查询、Upsert、锁定、优化器/索引/注释提示、命名参数、子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 APIDatabase Resolver多数据库读写分离、Prometheus…PI数据库解析器多个数据库读/写拆分)/ Prometheus...
  • 每个特性都经过了测试的重重考验
  • 开发者友好

模型定义

type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

GORM 使用结构体名的 蛇形命名 作为表名。对于结构体 User根据约定其表名为 users
您可以实现 Tabler 接口来更改默认表名,例如:

type Tabler interface {
    TableName() string
}

// TableName 会将 User 的表名重写为 `profiles`
func (User) TableName() string {
  return "profiles"
}

您可以使用 Table 方法临时指定表名,例如:

// 根据 User 的字段创建 `deleted_users` 表
db.Table("deleted_users").AutoMigrate(&User{})

// 从另一张表查询数据
var deletedUsers []User
db.Table("deleted_users").Find(&deletedUsers)
// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete(&User{})
// DELETE FROM deleted_users WHERE name = 'jinzhu';

连接数据库

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

连接池

GORM 使用 database/sql 维护连接池

sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

CRUD

基本操作

#  创建记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数
----------------------------------------------------------------------

#  查询
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
----------------------------------------------------------------------

#  更新
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111
----------------------------------------------------------------------

# 删除
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
----------------------------------------------------------------------

创建钩子

GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法,请参考 Hooks 中关于生命周期的详细信息

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()

    if u.Role == "admin" {
        return errors.New("invalid role")
    }
    return
}

通过Swagger测试接口, 中文文档

官方地址 ,其中包含swag可执行程序和gin-swaggergo web模块, 使用手册
查看地址为: http://IP:8888/swagger/index.html

// @BasePath /api/v1

// PingExample godoc
// @Summary ping example
// @Schemes
// @Description do ping
// @Tags example
// @Accept json
// @Produce json
// @Success 200 {string} Helloworld
// @Router /example/helloworld [get]
func Helloworld(g *gin.Context)  {
	g.JSON(http.StatusOK,"helloworld")
}

生成文档swag init

docs/
├── docs.go
├── swagger.json
└── swagger.yaml

需要注意把docs文件夹导入到代码中 import "goweb-gin-demo/docs"

  • 如何获取到方法的注释呢?

验证码获取及校验

首选通过/base/captcha获取验证码,其中包含验证码图片地址:data:image/png;...

{
    "code":0,
    "data":{
        "captchaId":"hrzSvHSdGo4Emm9oG9OY",
        "picPath":"data:image/png;base64,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"
    },
    "msg":"验证码获取成功"
}

在用户登录时,请求的数据为:

{
	  "username": "admin",
	  "password": "123456",
	  "captcha": "153842",
	  "captchaId": "hrzSvHSdGo4Emm9oG9OY"
}

携带着验证码及验证码的Id, 框架通过Id去匹配输入的验证码。
登录城后后返回信息:

{
    "code":0,
    "data":{
        "user":{
            "ID":1,
            "CreatedAt":"2021-10-25T14:53:31Z",
            "UpdatedAt":"2021-10-25T14:53:31Z",
            "uuid":"8e600b7f-3297-4979-a445-d218205ef9a6",
            "userName":"admin",
            "nickName":"超级管理员",
            "sideMode":"dark",
            "headerImg":"https:///qmplusimg.henrongyi.top/gva_header.jpg",
            "baseColor":"#fff",
            "activeColor":"#1890ff",
            "authorityId":"888",
            "authority":{
                "CreatedAt":"2021-10-25T14:53:31Z",
                "UpdatedAt":"2021-10-25T14:53:31Z",
                "DeletedAt":null,
                "authorityId":"888",
                "authorityName":"普通用户",
                "parentId":"0",
                "dataAuthorityId":null,
                "children":null,
                "defaultRouter":"dashboard"
            },
            "authorities":[
                {
                    "CreatedAt":"2021-10-25T14:53:31Z",
                    "UpdatedAt":"2021-10-25T14:53:31Z",
                    "DeletedAt":null,
                    "authorityId":"888",
                    "authorityName":"普通用户",
                    "parentId":"0",
                    "dataAuthorityId":null,
                    "children":null,
                    "defaultRouter":"dashboard"
                },
                {
                    "CreatedAt":"2021-10-25T14:53:31Z",
                    "UpdatedAt":"2021-10-25T14:53:31Z",
                    "DeletedAt":null,
                    "authorityId":"8881",
                    "authorityName":"普通用户子角色",
                    "parentId":"888",
                    "dataAuthorityId":null,
                    "children":null,
                    "defaultRouter":"dashboard"
                },
                {
                    "CreatedAt":"2021-10-25T14:53:31Z",
                    "UpdatedAt":"2021-10-25T14:53:31Z",
                    "DeletedAt":null,
                    "authorityId":"9528",
                    "authorityName":"测试角色",
                    "parentId":"0",
                    "dataAuthorityId":null,
                    "children":null,
                    "defaultRouter":"dashboard"
                }
            ]
        },
        "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiOGU2MDBiN2YtMzI5Ny00OTc5LWE0NDUtZDIxODIwNWVmOWE2IiwiSUQiOjEsIlVzZXJuYW1lIjoiYWRtaW4iLCJOaWNrTmFtZSI6Iui2hee6p-euoeeQhuWRmCIsIkF1dGhvcml0eUlkIjoiODg4IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTYzNjAyMDY2MSwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjM1NDE0ODYxfQ.-E9YNI6YV3XLbC5GgXiftTBI7A5ZiIheH5dqwgwnaDA",
        "expiresAt":1636020661000
    },
    "msg":"登录成功"
}

jwt token


**用户-角色权限-菜单项|API|资源对应关系图**

用户授权

获取菜单项

api接口/api/menu/getMenu

头文件携带信息:

x-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVVUlEIjoiOGU2MDBiN2YtMzI5Ny00OTc5LWE0NDUtZDIxODIwNWVmOWE2IiwiSUQiOjEsIlVzZXJuYW1lIjoiYWRtaW4iLCJOaWNrTmFtZSI6Iui2hee6p-euoeeQhuWRmCIsIkF1dGhvcml0eUlkIjoiODg4IiwiQnVmZmVyVGltZSI6ODY0MDAsImV4cCI6MTYzNjAyMDY2MSwiaXNzIjoicW1QbHVzIiwibmJmIjoxNjM1NDE0ODYxfQ.-E9YNI6YV3XLbC5GgXiftTBI7A5ZiIheH5dqwgwnaDA
x-user-id: 1
  • 角色对应着菜单项和API接口

通过userid获取权限id(SELECT * FROM sys_user_authorityWHEREsys_user_authority.sys_user_id = 1)

+-------------+----------------------------+
| sys_user_id | sys_authority_authority_id |
+-------------+----------------------------+
|           1 | 888                        |   普通用户
|           1 | 8881                       |   普通子用户
|           1 | 9528                       |   测试用户
+-------------+----------------------------+

通过权限ID获取权限(SELECT * FROM sys_authoritiesWHEREsys_authorities.authority_id IN ('888','8881','9528'))

+---------------------+---------------------+------------+--------------+----------------+-----------+----------------+
| created_at          | updated_at          | deleted_at | authority_id | authority_name | parent_id | default_router |
+---------------------+---------------------+------------+--------------+----------------+-----------+----------------+
| 2021-10-25 14:53:31 | 2021-10-25 14:53:31 | NULL       | 888          | ????           | 0         | dashboard      |
| 2021-10-25 14:53:31 | 2021-10-25 14:53:31 | NULL       | 8881         | ???????        | 888       | dashboard      |
| 2021-10-25 14:53:31 | 2021-10-25 14:53:31 | NULL       | 9528         | ????           | 0         | dashboard      |
+---------------------+---------------------+------------+--------------+----------------+-----------+----------------+

返回数据样本

{
    "code":0,
    "data":{
        "menus":[
            {
                "ID":1,
                "CreatedAt":"2021-11-01T11:14:12Z",
                "UpdatedAt":"2021-11-01T11:14:14Z",
                "parentId":"0",
                "path":"",
                "name":"周报",
                "hidden":false,
                "component":"",
                "sort":1,
                "meta":{
                    "keepAlive":false,
                    "defaultMenu":false,
                    "title":"周报",
                    "icon":"",
                    "closeTab":false
                },
                "authoritys":null,
                "menuId":"1",
                "children":[
                    {
                        "ID":2,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"1",
                        "path":"",
                        "name":"查看周报",
                        "hidden":false,
                        "component":"",
                        "sort":1,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"查看周报",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"2",
                        "children":null,
                        "parameters":[

                        ]
                    },
                    {
                        "ID":3,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"1",
                        "path":"",
                        "name":"写周报",
                        "hidden":false,
                        "component":"",
                        "sort":2,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"写周报",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"3",
                        "children":null,
                        "parameters":[

                        ]
                    },
                    {
                        "ID":4,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"1",
                        "path":"",
                        "name":"统计导出",
                        "hidden":false,
                        "component":"",
                        "sort":3,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"统计导出",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"4",
                        "children":null,
                        "parameters":[

                        ]
                    },
                    {
                        "ID":5,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"1",
                        "path":"",
                        "name":"模板编辑",
                        "hidden":false,
                        "component":"",
                        "sort":4,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"模板编辑",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"5",
                        "children":null,
                        "parameters":[

                        ]
                    }
                ],
                "parameters":[

                ]
            },
            {
                "ID":6,
                "CreatedAt":"2021-11-01T11:16:40Z",
                "UpdatedAt":"2021-11-01T11:16:43Z",
                "parentId":"0",
                "path":"",
                "name":"设置",
                "hidden":false,
                "component":"",
                "sort":2,
                "meta":{
                    "keepAlive":false,
                    "defaultMenu":false,
                    "title":"设置",
                    "icon":"",
                    "closeTab":false
                },
                "authoritys":null,
                "menuId":"6",
                "children":[
                    {
                        "ID":7,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"6",
                        "path":"",
                        "name":"用户密码",
                        "hidden":false,
                        "component":"",
                        "sort":1,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"用户密码",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"7",
                        "children":null,
                        "parameters":[

                        ]
                    },
                    {
                        "ID":8,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"6",
                        "path":"",
                        "name":"统计规则",
                        "hidden":false,
                        "component":"",
                        "sort":2,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"统计规则",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"8",
                        "children":null,
                        "parameters":[

                        ]
                    },
                    {
                        "ID":9,
                        "CreatedAt":"2021-11-01T11:16:40Z",
                        "UpdatedAt":"2021-11-01T11:16:43Z",
                        "parentId":"6",
                        "path":"",
                        "name":"用户管理",
                        "hidden":false,
                        "component":"",
                        "sort":3,
                        "meta":{
                            "keepAlive":false,
                            "defaultMenu":false,
                            "title":"用户管理",
                            "icon":"",
                            "closeTab":false
                        },
                        "authoritys":null,
                        "menuId":"9",
                        "children":null,
                        "parameters":[

                        ]
                    }
                ],
                "parameters":[

                ]
            }
        ]
    },
    "msg":"获取成功"
}

使用casbin 控制权限

Casbin is a powerful and efficient open-source access control library. It provides support for enforcing authorization based on various access control models.

可以 在线 书写规则:

在线写casbin规则

创建一个Casbin enforcer

import "github.com/casbin/casbin/v2"

e, err := casbin.NewEnforcer("path/to/model.conf", "path/to/policy.csv")

检查权限

sub := "alice" // the user that wants to access a resource.
obj := "data1" // the resource that is going to be accessed.
act := "read" // the operation that the user performs on the resource.

ok, err := e.Enforce(sub, obj, act)

if err != nil {
    // handle err
}

if ok == true {
    // permit alice to read data1
} else {
    // deny the request, show an error
}

// You could use BatchEnforce() to enforce some requests in batches.
// This method returns a bool slice, and this slice's index corresponds to the row index of the two-dimensional array.
// e.g. results[0] is the result of {"alice", "data1", "read"}
results, err := e.BatchEnforce([][]interface{}{{"alice", "data1", "read"}, {"bob", "data2", "write"}, {"jack", "data3", "read"}})

文件上传及下载

文件上传及下载code

func main() {
	router := gin.Default()
	// 给表单限制上传大小 (默认 32 MiB)
	// router.MaxMultipartMemory = 8 << 20  // 8 MiB
	router.POST("/upload", func(c *gin.Context) {
		// 单文件
		file, _ := c.FormFile("file")
		log.Println(file.Filename)

		// 上传文件到指定的路径
		// c.SaveUploadedFile(file, dst)

		c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
	})
	router.Run(":8080")
}
// @Tags FileUploadAndDownload
// @Summary 文件下载
// @Security ApiKeyAuth
// @accept application/json
// @Produce application/json
// @Param fileName query string true "待下载的文件名"
// @Success 200 {string} string "{"success":true,"data":{},"msg":"文件下载成功"}"
// @Router /fileUploadAndDownload/download [get]
func (u *FileUploadAndDownloadApi) DownloadFile(c *gin.Context) {
	fileName := c.Query("fileName")

	//待下载的文件是存储本地的,如果存储在云盘,直接访问链接即可
	//如果没有记录也会报错record not found
	err, fileInfo := fileUploadAndDownloadService.FindFile(fileName)

	if err != nil {
		global.GLOBAL_LOG.Error("文件未找到!", zap.Any("err", err))
		c.Redirect(http.StatusFound, "/404")
		return
	}

	c.Header("Content-Type", "application/octet-stream")
	c.Header("Content-Disposition", "attachment; filename="+fileInfo.Name)
	c.Header("Content-Transfer-Encoding", "binary")

	c.File(fileInfo.Url)
	return
}

文件上传成功返回:

{
    "code": 0,
    "data": {
        "file": {
            "ID": 0,
            "CreatedAt": "0001-01-01T00:00:00Z",
            "UpdatedAt": "0001-01-01T00:00:00Z",
            "name": "demo.c",
            "url": "/Users/zero/Documents/uploads/file/fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.c",
            "tag": "c",
            "key": "fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.c"
        }
    },
    "msg": "上传成功"
}

数据库信息:

mysql> select * from file_upload_and_downloads;
+----+---------------------+---------------------+------------+--------+--------------------------------------------------------------------------------------+------+---------------------------------------------------+
| id | created_at          | updated_at          | deleted_at | name   | url                                                                                  | tag  | key                                               |
+----+---------------------+---------------------+------------+--------+--------------------------------------------------------------------------------------+------+---------------------------------------------------+
|  3 | 2021-11-02 09:04:27 | 2021-11-02 09:04:27 | NULL       | demo.c | /Users/zero/Documents/uploads/file/fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.c | c    | fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.c |
+----+---------------------+---------------------+------------+--------+--------------------------------------------------------------------------------------+------+---------------------------------------------------+

代码自动生成

gin-vue-admin提供代码自动生成功能选择数据库表结构可直接生成前端代码及后端代码很方便
比如周报的表结构如下:

// 周报
{
    "code": 0,
    "data": {
        "rewtReports": {
            "ID": 100,
            "CreatedAt": "2021-11-04T03:11:07Z",
            "UpdatedAt": "2021-11-04T11:35:11Z",
            "userName": "xiaoming",
            "sendTo": [
                {
                    "ID": 1,
                    "name": "xiao11"
                },
                {
                    "ID": 2,
                    "name": "xiao21"
                }
            ],
            "header": "周报",
            "contents": [
                {
                    "title": "本周工作",
                    "content": "<strong>本周工作</strong>这里"
                },
                {
                    "title": "下周计划",
                    "content": "<p>下周计划在这</p>这里"
                }
            ],
            "pictures": [
                {
                    "key": "fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.png",
                    "name": "demo.png"
                },
                {
                    "key": "fe01ce2a7fbac8fafaed7c982a04e229_20211102170420.png",
                    "name": "demo1.png"
                }
            ],
            "attachments": [
                {
                    "key": "fe01ce2a7fbac8fafaed7c982a04e229_20211102170427.txt",
                    "name": "demo.txt"
                },
                {
                    "key": "fe01ce2a7fbac8fafaed7c982a04e229_20211102170420.txt",
                    "name": "demo1.txt"
                }
            ]
        }
    },
    "msg": "操作成功"
}

// 评论
{
    "id":100,
    "comments":[
        {""}
    ]
}

反射reflect

官方文档

包反射实现运行时反射,允许程序操作任意类型的对象。典型的用法是取一个静态类型 interface{} 的值,通过调用 TypeOf 提取其动态类型信息,返回一个 Type。

对 ValueOf 的调用返回一个表示运行时数据的值。Zero 接受一个 Type 并返回一个表示该类型的零值的 Value。

有关 Go 中反射的介绍,请参阅 “反射定律”

动态调用有参数的函数

type T struct{}

func main() {
    name := "Do"
    t := &T{}
    a := reflect.ValueOf(1111)
    b := reflect.ValueOf("world")
    in := []reflect.Value{a, b}
    reflect.ValueOf(t).MethodByName(name).Call(in)
}

func (t *T) Do(a int, b string) {
    fmt.Println("hello" + b, a)
}

GoWeb 发布

go build编译出moduleName的可执行文件加载配置文件config.yaml即可运行。

疑问及拓展

为什么函数或方法中变量名很多都是大写字母开始的?

  • 任何需要对外暴露的名字必须以大写字母开头,不需要对外暴露的则应该以小写字母开头。
    但是gin-vue-admin代码中有很多大小连方法参数都是大写字母开始的?
# 函数中的变量Router
Router := initialize.Routers()

# 方法参数Router
func (s *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) (R gin.IRoutes) {

unsupported Scan, storing driver.Value type []uint8 into type *time.Time

需要在连接数据库时增加参数:

db, err := sqlx.Connect("mysql", "myuser:mypass@tcp(127.0.0.1:3306)/mydb?parseTime=true") 

Sets the location for time.Time values (when using parseTime=true). "Local" sets the system's location. See time.LoadLocation for details.

Error 1075: Incorrect table definition; there can be only one auto column and it must be defined as a key

日志信息

2021/10/29 11:44:55 /Users/zero/go/pkg/mod/github.com/casbin/gorm-adapter/v3@v3.4.4/adapter.go:373 Error 1075: Incorrect table definition; there can be only one auto column and it must be defined as a key [7.259ms] [rows:0] ALTER TABLE casbin_rule ADD id bigint unsigned AUTO_INCREMENT

断点调试gorm-adapter/v3@v3.4.4/adapter.go, 最后上网搜索发现版本问题。 把版本都降低了
github.com/casbin/casbin/v2 v2.11.0
github.com/casbin/gorm-adapter/v3 v3.0.2

查看http请求详情

通过查看gin.Context>>Request>>(Body io.ReadCloser)>>src>>R>>buf=>点击view就能看到http详情。



docker容器没有访问权限

nginx     | /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
nginx     | /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
mysql     | 2021-11-16 07:29:26+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.36-1debian10 started.
mysql     | 2021-11-16 07:29:26+00:00 [ERROR] [Entrypoint]: mysqld failed while attempting to check config
mysql     | 	command was: mysqld --verbose --help --log-bin-index=/tmp/tmp.2oV5LPDbk7
mysql     | 	mysqld: Can't read dir of '/etc/mysql/conf.d/' (Errcode: 13 - Permission denied)
mysql     | mysqld: [ERROR] Fatal error in defaults handling. Program aborted!

最终原因是没有关闭selinux, 直接关闭即可。 setenforce 0

vim /etc/selinux/config
// 关闭
SELINUX=disable

SELinux 主要作用就是最大限度地减小系统中服务进程可访问的资源(最小权限原则)。 设想一下,如果一个以 root 身份运行的网络服务存在 0day 漏洞,黑客就可以利用这个漏洞,以 root 的身份在您的服务器上为所欲为了。是不是很可怕? SELinux 就是来解决这个问题的。

另外跨容器访问时,也需要关闭防火墙,或者增加端口规则.