From 35c6387b4b00791b2eb13a26d5b05f8c6e50b174 Mon Sep 17 00:00:00 2001 From: zjy Date: Sun, 13 Jul 2025 18:29:28 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E4=BC=98=E5=8C=96docsify=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1.调整在docsify下代码块乱掉的问题 2.新增docsify下的go/yaml/vue/json/sql代码高亮插件 3.新增docsify下的mermaid图解析插件 --- docs/guide-zh-CN/addon-flow.md | 8 +- docs/guide-zh-CN/addon-helper.md | 3 + docs/guide-zh-CN/start-installation.md | 4 +- docs/guide-zh-CN/sys-db.md | 299 +++++------ docs/guide-zh-CN/sys-exploit.md | 41 +- docs/guide-zh-CN/sys-library.md | 632 ++++++++++++----------- docs/guide-zh-CN/sys-middleware.md | 499 +++++++++--------- docs/guide-zh-CN/sys-tenant.md | 3 + docs/guide-zh-CN/sys-websocket-client.md | 3 + docs/guide-zh-CN/sys-websocket-server.md | 5 + index.html | 277 +++++----- 11 files changed, 905 insertions(+), 869 deletions(-) diff --git a/docs/guide-zh-CN/addon-flow.md b/docs/guide-zh-CN/addon-flow.md index 35650e4..209cc56 100644 --- a/docs/guide-zh-CN/addon-flow.md +++ b/docs/guide-zh-CN/addon-flow.md @@ -14,7 +14,7 @@ 1、HotGo 后台进入 开发工具->插件管理->找到创建新插件,根据引导进行创建即可。 -``` +```shell 创建成功后默认情况下会在以下目录中生成插件文件,假设新生成的插件名为:hgexample 1. /server/addons/hgexample/ # 插件模块目录 @@ -29,7 +29,7 @@ 2、创建插件完毕重启服务端后,插件管理中会出现你新创建的插件信息。操作栏有几个按钮,在此进行说明 - 安装:会自动执行 server/hgexample/main.go 文件中的Install方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。如生成后台菜单、生成插件配置表初始化数据、迁移home页面、web项目文件等。 -``` +```go // Install 安装模块 func (m *module) Install(ctx context.Context) (err error) { // ... @@ -38,7 +38,7 @@ func (m *module) Install(ctx context.Context) (err error) { ``` - 更新:会自动执行 server/hgexample/main.go 文件中的Upgrade方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。 -``` +```go // Upgrade 更新模块 func (m *module) Upgrade(ctx context.Context) (err error) { // ... @@ -47,7 +47,7 @@ func (m *module) Upgrade(ctx context.Context) (err error) { ``` - 卸载:会自动执行 server/hgexample/main.go 文件中的UnInstall方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。如会清除所有的数据表和已安装的信息等。 -``` +```go // UnInstall 卸载模块 func (m *module) UnInstall(ctx context.Context) (err error) { // ... diff --git a/docs/guide-zh-CN/addon-helper.md b/docs/guide-zh-CN/addon-helper.md index 2ae1465..57d4d56 100644 --- a/docs/guide-zh-CN/addon-helper.md +++ b/docs/guide-zh-CN/addon-helper.md @@ -9,6 +9,7 @@ #### 模块结构 - 文件路径:server/internal/library/addons/module.go + ```go // Skeleton 模块骨架 type Skeleton struct { @@ -43,6 +44,7 @@ type Module interface { #### 获取模块信息 - 在插件模块内 + ```go package main @@ -57,6 +59,7 @@ func test() { ``` - 在插件模块外 + ```go package main diff --git a/docs/guide-zh-CN/start-installation.md b/docs/guide-zh-CN/start-installation.md index ab69804..11f5936 100644 --- a/docs/guide-zh-CN/start-installation.md +++ b/docs/guide-zh-CN/start-installation.md @@ -69,7 +69,7 @@ gfcli: 三、 启动服务 1、服务端: -```shell script +```shell cd server # 设置国内代理,如果已经设置好了代理可以跳过 @@ -86,7 +86,7 @@ gfcli: ``` 2、web前端: -```shell script +```shell cd web # 首先确定你以安装node16.0以上版本并安装了包[npm、pnpm],否则可能会出现一些未知报错 diff --git a/docs/guide-zh-CN/sys-db.md b/docs/guide-zh-CN/sys-db.md index 369a132..64a70ef 100644 --- a/docs/guide-zh-CN/sys-db.md +++ b/docs/guide-zh-CN/sys-db.md @@ -1,149 +1,150 @@ -## 数据库 - -目录 - -- 字段类型 -- 特殊字段默认表单组件 -- 特殊字段默认表单验证器 -- SQL默认查询方式 -- 其他 - -### 字段类型 - -- 创建数据库表当按如下的规则进行字段命名、类型、属性设置和备注后,再生成CRUD代码时会自动生成对应的Api、控制器、业务逻辑、Web页面、[表单组件](web-form.md)等的一些默认属性 -- 当你了解这些默认技巧后,会有效提高你在实际开发中的生产效率 - -| 数据库类型 | 额外属性 | 转换Go类型 | 转换Ts类型 | 表单组件 | -|---------------------------------------------------------------|--------------|--------------|---------|-----------------------| -| int, tinyint,small_int,smallint,medium_int,mediumint,serial | / | int | number | InputNumber(数字输入框) | -| int, tinyint,small_int,smallint,medium_int,mediumint,serial | unsigned | uint | number | InputNumber(数字输入框) | -| big_int,bigint,bigserial | / | int64 | number | InputNumber(数字输入框) | -| big_int,bigint,bigserial | unsigned | uint64 | number | InputNumber(数字输入框) | -| real | / | float32 | number | InputNumber(数字输入框) | -| float,double,decimal,money,numeric,smallmoney | / | float64 | number | InputNumber(数字输入框) | -| bit(1) 、bit(true)、bit(false) | / | bool | boolean | Input(文本输入框,默认) | -| bit | / | int64-bytes | array | InputDynamic(动态KV表单) | -| bit | unsigned | uint64-bytes | array | InputDynamic (动态KV表单) | -| bool | / | bool | boolean | Input(文本输入框,默认) | -| date | / | *gtime.Time | string | Date(日期选择器) | -| datetime,timestamp,timestamptz | / | *gtime.Time | string | Time(时间选择器) | -| json | / | *gjson.Json | string | Input(文本输入框) | -| jsonb | / | *gjson.Json | string | Input(文本输入框) | -| 以下为物理类型中包含字段部分时的转换方式,默认情况 | / | / | / | / | -| text,char,character | / | string | string | Input(文本输入框) | -| float,double,numeric | / | string | string | Input(文本输入框) | -| bool | / | bool | boolean | Input(文本输入框,默认) | -| binary,blob | / | []byte | string | Input(文本输入框,默认) | -| int | / | int | number | InputNumber(数字输入框) | -| int | unsigned | int | number | InputNumber(数字输入框) | -| time | / | *gtime.Time | string | Time(时间选择器) | -| date | / | *gtime.Time | string | Date(日期选择器) | -| 没有满足以上任何条件的 | / | string | string | Input(文本输入框) | - - -### 特殊字段默认表单组件 -- 以下字段在不设置表单组件时会默认使用的表单组件 - -| 数据库字段 | 字段名称 | 表单组件 | -|--------------|----------------------|----------------------| -| status | 状态字段(任意int类型) | Select (单选下拉框) | -| created_at | 创建时间字段 | TimeRange (时间范围选择) | -| province_id | 省份ID字段(任意int类型) | CitySelector (省市区选择) | -| city_id | 城市ID字段(任意int类型) | CitySelector (省市区选择) | -| 任意字串符字段 | 长度>= 200 and <= 500 | InputTextarea (文本域) | -| 任意字串符字段 | 长度> 500 | InputEditor (富文本) | - - -### 特殊字段默认表单验证器 -- 以下字段在不设置表单组件时会默认使用的表单验证器 - -| 数据库字段/Go类型 | 字段名称 | 表单验证规则 | -|-------------------|--------|-----------------------| -| mobile | 手机号 | 不为空时必须是手机号码(国内) | -| qq | QQ | 不为空时必须是QQ号码 | -| email | 邮箱地址 | 不为空时必须是邮箱格式 | -| id_card | 身份证号码 | 不为空时必须是15或18位身份证号码 | -| bank_card | 银行卡号码 | 银行卡号码 | -| password | 密码 | 密码验证,必须包含6-18为字母和数字 | -| price | 价格 | 金额验证,最多允许输入10位整数及2位小数 | -| Go类型为uint、uint64 | 正整数 | 非零正整数验证 | - -### SQL默认查询方式 -- Go类型取决于数据库物理类型,请参考 [字段类型] 部分 - -| Go类型 | 查询方式 | -|-------------------------|--------------------------------------| -| string | LIKE | -| date,datetime | = | -| int,uint,int64,uint64 | = | -| []int,[]int64,[]uint64 | IN (...) | -| float32,float64 | = | -| []byte4 | =(默认) | -| time.Time,*gtime.Time | = | -| *gjson.Json | JSON_CONTAINS(json_doc, val[, path]) | - - - -### 其他 - -#### 默认字典选项 - -- 数据库字段为 `status`且类型为任意数字类型的会使用系统默认的状态字典 - -#### 默认属性 - -- 默认必填,当数据库字段存在非空`IS_NULLABLE`属性时,默认勾选必填验证 -- 默认唯一,当数据库字段索引存在`UNI`时,默认勾选唯一值验证 -- 默认主键,当数据库字段索引存在`PRI`时,默认为主键,不允许编辑 -- 默认排序,当数据库字段存在`sort`时,默认开启排序,添加表单自动获取最大排序增量值并填充表单 -- 默认列名,默认使用字段注释作为表格的列名。当数据库字段未设置注释时,默认使用字段名称作为列名 - -#### 自动更新/插入 - -- 自动更新,当数据库字段为`updated_at`(更新时间),`updated_by`(更新者) -- 自动插入,当数据库字段为`created_at`(创建时间),`created_by`(创建者) -- 软删除,表存在字段`deleted_at`时,使用表的Orm模型查询条件将会自动加入[ `deleted_at` IS NULL ],删除时只更新删除时间而不会真的删除数据 -- 树表:不论更新插入,都会根据表中字段`pid`(上级ID)自动维护`level`(树等级)和`tree`(关系树) - - -#### 操作人字段维护 - -- 生成列表中存在并且勾选展示字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会自动到表`hg_admin_member`中获取操作人的基本信息摘要,并渲染到列表中,效果如下: - -![](images/sys-db-by.png) - -- 生成列表中存在并且勾选查询字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会强制将查询表单改为关键词查询,从`hg_admin_member`查询操作人。效果如下: - -![](images/sys-db-by2.png) - -- 查询代码片段,参考路径:[server/internal/logic/admin/member.go](../../server/internal/logic/admin/member.go) -```go - -// 查询创建者 -if in.CreatedBy != "" { - ids, err := service.AdminMember().GetIdsByKeyword(ctx, in.CreatedBy) - if err != nil { - return nil, 0, err - } - mod = mod.WhereIn(dao.SysGenCurdDemo.Columns().CreatedBy, ids) -} - -// GetIdsByKeyword 根据关键词查找符合条件的用户ID -func (s *sAdminMember) GetIdsByKeyword(ctx context.Context, ks string) (res []int64, err error) { - ks = gstr.Trim(ks) - if len(ks) == 0 { - return - } - array, err := dao.AdminMember.Ctx(ctx).Fields("id"). - Where("`id` = ? or `real_name` = ? or `username` = ? or `mobile` = ?", ks, ks, ks, ks). - Array() - if err != nil { - err = gerror.Wrap(err, "根据关键词获取用户ID失败,请稍后重试!") - } - res = gvar.New(array).Int64s() - return -} -``` - - -> 这里只列举了较为常用的默认规则,其他更多默认规则请参考:[server/internal/library/hggen/views/column_default.go](../../server/internal/library/hggen/views/column_default.go) +## 数据库 + +目录 + +- 字段类型 +- 特殊字段默认表单组件 +- 特殊字段默认表单验证器 +- SQL默认查询方式 +- 其他 + +### 字段类型 + +- 创建数据库表当按如下的规则进行字段命名、类型、属性设置和备注后,再生成CRUD代码时会自动生成对应的Api、控制器、业务逻辑、Web页面、[表单组件](web-form.md)等的一些默认属性 +- 当你了解这些默认技巧后,会有效提高你在实际开发中的生产效率 + +| 数据库类型 | 额外属性 | 转换Go类型 | 转换Ts类型 | 表单组件 | +|---------------------------------------------------------------|--------------|--------------|---------|-----------------------| +| int, tinyint,small_int,smallint,medium_int,mediumint,serial | / | int | number | InputNumber(数字输入框) | +| int, tinyint,small_int,smallint,medium_int,mediumint,serial | unsigned | uint | number | InputNumber(数字输入框) | +| big_int,bigint,bigserial | / | int64 | number | InputNumber(数字输入框) | +| big_int,bigint,bigserial | unsigned | uint64 | number | InputNumber(数字输入框) | +| real | / | float32 | number | InputNumber(数字输入框) | +| float,double,decimal,money,numeric,smallmoney | / | float64 | number | InputNumber(数字输入框) | +| bit(1) 、bit(true)、bit(false) | / | bool | boolean | Input(文本输入框,默认) | +| bit | / | int64-bytes | array | InputDynamic(动态KV表单) | +| bit | unsigned | uint64-bytes | array | InputDynamic (动态KV表单) | +| bool | / | bool | boolean | Input(文本输入框,默认) | +| date | / | *gtime.Time | string | Date(日期选择器) | +| datetime,timestamp,timestamptz | / | *gtime.Time | string | Time(时间选择器) | +| json | / | *gjson.Json | string | Input(文本输入框) | +| jsonb | / | *gjson.Json | string | Input(文本输入框) | +| 以下为物理类型中包含字段部分时的转换方式,默认情况 | / | / | / | / | +| text,char,character | / | string | string | Input(文本输入框) | +| float,double,numeric | / | string | string | Input(文本输入框) | +| bool | / | bool | boolean | Input(文本输入框,默认) | +| binary,blob | / | []byte | string | Input(文本输入框,默认) | +| int | / | int | number | InputNumber(数字输入框) | +| int | unsigned | int | number | InputNumber(数字输入框) | +| time | / | *gtime.Time | string | Time(时间选择器) | +| date | / | *gtime.Time | string | Date(日期选择器) | +| 没有满足以上任何条件的 | / | string | string | Input(文本输入框) | + + +### 特殊字段默认表单组件 +- 以下字段在不设置表单组件时会默认使用的表单组件 + +| 数据库字段 | 字段名称 | 表单组件 | +|--------------|----------------------|----------------------| +| status | 状态字段(任意int类型) | Select (单选下拉框) | +| created_at | 创建时间字段 | TimeRange (时间范围选择) | +| province_id | 省份ID字段(任意int类型) | CitySelector (省市区选择) | +| city_id | 城市ID字段(任意int类型) | CitySelector (省市区选择) | +| 任意字串符字段 | 长度>= 200 and <= 500 | InputTextarea (文本域) | +| 任意字串符字段 | 长度> 500 | InputEditor (富文本) | + + +### 特殊字段默认表单验证器 +- 以下字段在不设置表单组件时会默认使用的表单验证器 + +| 数据库字段/Go类型 | 字段名称 | 表单验证规则 | +|-------------------|--------|-----------------------| +| mobile | 手机号 | 不为空时必须是手机号码(国内) | +| qq | QQ | 不为空时必须是QQ号码 | +| email | 邮箱地址 | 不为空时必须是邮箱格式 | +| id_card | 身份证号码 | 不为空时必须是15或18位身份证号码 | +| bank_card | 银行卡号码 | 银行卡号码 | +| password | 密码 | 密码验证,必须包含6-18为字母和数字 | +| price | 价格 | 金额验证,最多允许输入10位整数及2位小数 | +| Go类型为uint、uint64 | 正整数 | 非零正整数验证 | + +### SQL默认查询方式 +- Go类型取决于数据库物理类型,请参考 [字段类型] 部分 + +| Go类型 | 查询方式 | +|-------------------------|--------------------------------------| +| string | LIKE | +| date,datetime | = | +| int,uint,int64,uint64 | = | +| []int,[]int64,[]uint64 | IN (...) | +| float32,float64 | = | +| []byte4 | =(默认) | +| time.Time,*gtime.Time | = | +| *gjson.Json | JSON_CONTAINS(json_doc, val[, path]) | + + + +### 其他 + +#### 默认字典选项 + +- 数据库字段为 `status`且类型为任意数字类型的会使用系统默认的状态字典 + +#### 默认属性 + +- 默认必填,当数据库字段存在非空`IS_NULLABLE`属性时,默认勾选必填验证 +- 默认唯一,当数据库字段索引存在`UNI`时,默认勾选唯一值验证 +- 默认主键,当数据库字段索引存在`PRI`时,默认为主键,不允许编辑 +- 默认排序,当数据库字段存在`sort`时,默认开启排序,添加表单自动获取最大排序增量值并填充表单 +- 默认列名,默认使用字段注释作为表格的列名。当数据库字段未设置注释时,默认使用字段名称作为列名 + +#### 自动更新/插入 + +- 自动更新,当数据库字段为`updated_at`(更新时间),`updated_by`(更新者) +- 自动插入,当数据库字段为`created_at`(创建时间),`created_by`(创建者) +- 软删除,表存在字段`deleted_at`时,使用表的Orm模型查询条件将会自动加入[ `deleted_at` IS NULL ],删除时只更新删除时间而不会真的删除数据 +- 树表:不论更新插入,都会根据表中字段`pid`(上级ID)自动维护`level`(树等级)和`tree`(关系树) + + +#### 操作人字段维护 + +- 生成列表中存在并且勾选展示字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会自动到表`hg_admin_member`中获取操作人的基本信息摘要,并渲染到列表中,效果如下: + +![](images/sys-db-by.png) + +- 生成列表中存在并且勾选查询字段`created_by`(创建者)、`updated_by`(修改者)、`deleted_by`(删除者)时,会强制将查询表单改为关键词查询,从`hg_admin_member`查询操作人。效果如下: + +![](images/sys-db-by2.png) + +- 查询代码片段,参考路径:[server/internal/logic/admin/member.go](../../server/internal/logic/admin/member.go) + +```go + +// 查询创建者 +if in.CreatedBy != "" { + ids, err := service.AdminMember().GetIdsByKeyword(ctx, in.CreatedBy) + if err != nil { + return nil, 0, err + } + mod = mod.WhereIn(dao.SysGenCurdDemo.Columns().CreatedBy, ids) +} + +// GetIdsByKeyword 根据关键词查找符合条件的用户ID +func (s *sAdminMember) GetIdsByKeyword(ctx context.Context, ks string) (res []int64, err error) { + ks = gstr.Trim(ks) + if len(ks) == 0 { + return + } + array, err := dao.AdminMember.Ctx(ctx).Fields("id"). + Where("`id` = ? or `real_name` = ? or `username` = ? or `mobile` = ?", ks, ks, ks, ks). + Array() + if err != nil { + err = gerror.Wrap(err, "根据关键词获取用户ID失败,请稍后重试!") + } + res = gvar.New(array).Int64s() + return +} +``` + + +> 这里只列举了较为常用的默认规则,其他更多默认规则请参考:[server/internal/library/hggen/views/column_default.go](../../server/internal/library/hggen/views/column_default.go) diff --git a/docs/guide-zh-CN/sys-exploit.md b/docs/guide-zh-CN/sys-exploit.md index 76b1fbe..70fb37b 100644 --- a/docs/guide-zh-CN/sys-exploit.md +++ b/docs/guide-zh-CN/sys-exploit.md @@ -63,16 +63,17 @@ #### import * 单行import不建议用圆括号包裹 * 按照`官方包`,NEW LINE,`当前工程包`,NEW LINE,`第三方依赖包`顺序引入 - ```go - import ( - "context" - "string" - - "greet/user/internal/config" - - "google.golang.org/grpc" - ) - ``` + +```go +import ( + "context" + "string" + + "greet/user/internal/config" + + "google.golang.org/grpc" +) +``` #### 函数返回 * 对象避免非指针返回 @@ -84,7 +85,8 @@ #### 函数体编码 * 建议一个block结束空一行,如if、for等 - ```go + +```go func main (){ if x==1{ // do something @@ -92,15 +94,16 @@ fmt.println("xxx") } - ``` +``` * return前尽可能空一行 - ```go - func getUser(id string)(string,error){ - .... - - return "xx",nil - } - ``` + +```go +func getUser(id string)(string,error){ + .... + + return "xx",nil +} +``` ### 框架规范 diff --git a/docs/guide-zh-CN/sys-library.md b/docs/guide-zh-CN/sys-library.md index 99471c1..25255d9 100644 --- a/docs/guide-zh-CN/sys-library.md +++ b/docs/guide-zh-CN/sys-library.md @@ -1,316 +1,318 @@ -## 功能扩展库 - -目录 - -- 缓存驱动 -- 请求上下文 -- JWT -- 数据字典 -- 地理定位(待写) -- 通知(待写) - - -### 缓存驱动 - -> 系统默认的缓存驱动为file,目前已支持:memory|redis|file等多种驱动。请自行选择适合你的驱动使用。 - -- 配置文件:server/manifest/config/config.yaml - -```yaml -#缓存 -cache: - adapter: "file" # 缓存驱动方式,支持:memory|redis|file,不填默认memory - fileDir: "./storage/cache" # 文件缓存路径,adapter=file时必填 -``` - -#### 使用方式 -```go -package main - -import ( - "hotgo/internal/library/cache" - "github.com/gogf/gf/v2/os/gctx" -) - -func test() { - ctx := gctx.New() - - // 添加/修改 - cache.Instance().Set(ctx, "qwe", 123, 0) - - // 查询 - cache.Instance().Get(ctx, "qwe") - - // 删除 - cache.Instance().Remove(ctx, "qwe") - - // 更多方法请参考:https://goframe.org/pages/viewpage.action?pageId=27755640 -} - -``` - -### 请求上下文 - -- 主要用于在处理HTTP和websocket请求时通过中间件将用户、应用、插件等信息绑定到上下文中,方便在做业务处理时用到这些信息 - -```go -package admin - -import ( - "fmt" - "context" - "hotgo/internal/library/contexts" - "hotgo/internal/library/addons" -) - - -func test(ctx context.Context) { - // 获取当前请求的所有上下文变量 - var ctxModel = contexts.Get(ctx) - fmt.Printf("当前请求的所有上下文变量:%+v\n", ctxModel) - - // 获取当前请求的应用模块 - var module = contexts.GetModule(ctx) - fmt.Printf("当前请求的应用:%+v\n", module) - - // 获取当前请求的用户信息 - var member = contexts.GetUser(ctx) - fmt.Printf("当前访问用户信息:%+v\n", member) - - // 获取当前请求的插件模块 - fmt.Printf("当前是否为插件请求:%v", contexts.IsAddonRequest(ctx)) - if contexts.IsAddonRequest(ctx) { - fmt.Printf("当前插件名称:%v", contexts.GetAddonName(ctx)) - fmt.Printf("当前插件信息:%v", addons.GetModule(contexts.GetAddonName(ctx))) - } -} - -``` - -### JWT - -- 基于jwt+缓存驱动实现的用户登录令牌功能,支持自动续约,解决了jwt服务端无法退出问题和jwt令牌无法主动失效问题 - -#### 配置示例 -```yaml -# 登录令牌 -token: - secretKey: "hotgo123" # 令牌加密秘钥,考虑安全问题生产环境中请修改默认值 - expires: 604800 # 令牌有效期,单位:秒。默认7天 - autoRefresh: true # 是否开启自动刷新过期时间, false|true 默认为true - refreshInterval: 86400 # 刷新间隔,单位:秒。必须小于expires,否则无法触发。默认1天内只允许刷新一次 - maxRefreshTimes: 30 # 最大允许刷新次数,-1不限制。默认30次 - multiLogin: true # 是否允许多端登录, false|true 默认为true - -``` - -```go -package admin - -import ( - "fmt" - "context" - "hotgo/internal/library/token" - "hotgo/internal/model" -) - - -func test(ctx context.Context) { - // 登录 - user := &model.Identity{ - Id: mb.Id, - Pid: mb.Pid, - DeptId: mb.DeptId, - RoleId: ro.Id, - RoleKey: ro.Key, - Username: mb.Username, - RealName: mb.RealName, - Avatar: mb.Avatar, - Email: mb.Email, - Mobile: mb.Mobile, - App: consts.AppAdmin, - LoginAt: gtime.Now(), - } - - loginToken, expires, err := token.Login(ctx, user) - if err != nil { - return nil, err - } - - // gf请求对象 - r := *ghttp.Request - - // 获取登录用户信息 - user, err := token.ParseLoginUser(r) - if err != nil { - return - } - - // 注销登录 - err = token.Logout(r) -} - -``` - -### 数据字典 - -- hotgo增加了对枚举字典和自定义方法字典的内置支持,从而在系统中经常使用的一些特定数据上做出了增强。 - -#### 字典数据选项 -- 文件路径:server/internal/model/dict.go -```go -package model - -// Option 字典数据选项 -type Option struct { - Key interface{} `json:"key"` - Label string `json:"label" description:"字典标签"` - Value interface{} `json:"value" description:"字典键值"` - ValueType string `json:"valueType" description:"键值数据类型"` - Type string `json:"type" description:"字典类型"` - ListClass string `json:"listClass" description:"表格回显样式"` -} -``` - -#### 枚举字典 -- 适用于系统开发期间内置的枚举数据,这样即维护了枚举值,又关联了数据字典 - -##### 一个例子 -- 定义枚举值和字典数据选项,并注册字典类型 -- 文件路径:server/internal/consts/credit_log.go - -```go -package consts - -import ( - "hotgo/internal/library/dict" - "hotgo/internal/model" -) - -func init() { - dict.RegisterEnums("creditType", "资金变动类型", CreditTypeOptions) - dict.RegisterEnums("creditGroup", "资金变动分组", CreditGroupOptions) -} - -const ( - CreditTypeBalance = "balance" // 余额 - CreditTypeIntegral = "integral" // 积分 -) - -const ( - CreditGroupDecr = "decr" // 扣款 - CreditGroupIncr = "incr" // 加款 - CreditGroupOpDecr = "op_decr" // 操作扣款 - CreditGroupOpIncr = "op_incr" // 操作加款 - CreditGroupBalanceRecharge = "balance_recharge" // 余额充值 - CreditGroupBalanceRefund = "balance_refund" // 余额退款 - CreditGroupApplyCash = "apply_cash" // 申请提现 -) - -// CreditTypeOptions 变动类型 -var CreditTypeOptions = []*model.Option{ - dict.GenSuccessOption(CreditTypeBalance, "余额"), - dict.GenInfoOption(CreditTypeIntegral, "积分"), -} - -// CreditGroupOptions 变动分组 -var CreditGroupOptions = []*model.Option{ - dict.GenWarningOption(CreditGroupDecr, "扣款"), - dict.GenSuccessOption(CreditGroupIncr, "加款"), - dict.GenWarningOption(CreditGroupOpDecr, "操作扣款"), - dict.GenSuccessOption(CreditGroupOpIncr, "操作加款"), - dict.GenWarningOption(CreditGroupBalanceRefund, "余额退款"), - dict.GenSuccessOption(CreditGroupBalanceRecharge, "余额充值"), - dict.GenInfoOption(CreditGroupApplyCash, "申请提现"), -} - -``` - - -#### 自定义方法字典 -- 适用于非固定选项,如数据是从某个表/文件读取或从第三方读取,数据需要进行转换时使用 - -##### 方法字典接口 -- 文件路径:server/internal/consts/credit_log.go -```go -package dict - -// FuncDict 方法字典,实现本接口即可使用内置方法字典 -type FuncDict func(ctx context.Context) (res []*model.Option, err error) -``` - -##### 一个例子 -- 定义获取字典数据方法,并注册字典类型 -- 文件路径:server/internal/logic/admin/post.go - -```go -package admin - -import ( - "context" - "github.com/gogf/gf/v2/errors/gerror" - "github.com/gogf/gf/v2/frame/g" - "hotgo/internal/consts" - "hotgo/internal/dao" - "hotgo/internal/library/dict" - "hotgo/internal/model" - "hotgo/internal/model/entity" - "hotgo/internal/service" -) - -type sAdminPost struct{} - -func NewAdminPost() *sAdminPost { - return &sAdminPost{} -} - -func init() { - service.RegisterAdminPost(NewAdminPost()) - dict.RegisterFunc("adminPostOption", "岗位选项", service.AdminPost().Option) -} - -// Option 岗位选项 -func (s *sAdminPost) Option(ctx context.Context) (opts []*model.Option, err error) { - var list []*entity.AdminPost - if err = dao.AdminPost.Ctx(ctx).OrderAsc(dao.AdminPost.Columns().Sort).Scan(&list); err != nil { - return nil, err - } - - if len(list) == 0 { - opts = make([]*model.Option, 0) - return - } - - for _, v := range list { - opts = append(opts, dict.GenHashOption(v.Id, v.Name)) - } - return -} -``` - -#### 代码生成支持 -- 内置的枚举字典和自定义方法字典在生成代码时可以直接进行选择,生成代码格式和系统字典管理写法一致 - -![最终编辑表单效果](images/sys-library-dict.png) - - -#### 内置字典和系统字典的区分 - -##### 主要区别 -- 系统字典由表:`hg_sys_dict_type`和`hg_sys_dict_data`共同进行维护,使用时需通过后台到字典管理中进行添加 -- 内置字典是系统开发期间在代码层面事先定义和注册好的数据选项 - - -##### 数据格式区别 -- 系统字典所有ID都是大于0的int64类型 -- 内置字典ID都是小于0的int64类型。枚举字典以20000开头,如:-200001381053496;方法字典以30000开头,如:-30000892528327;开头以外数字是根据数据选项的`key`值进行哈希算法得出 - -### 地理定位 -```go -// 待写 -``` - -### 通知 -```go -// 待写 +## 功能扩展库 + +目录 + +- 缓存驱动 +- 请求上下文 +- JWT +- 数据字典 +- 地理定位(待写) +- 通知(待写) + + +### 缓存驱动 + +> 系统默认的缓存驱动为file,目前已支持:memory|redis|file等多种驱动。请自行选择适合你的驱动使用。 + +- 配置文件:server/manifest/config/config.yaml + +```yaml +#缓存 +cache: + adapter: "file" # 缓存驱动方式,支持:memory|redis|file,不填默认memory + fileDir: "./storage/cache" # 文件缓存路径,adapter=file时必填 +``` + +#### 使用方式 +```go +package main + +import ( + "hotgo/internal/library/cache" + "github.com/gogf/gf/v2/os/gctx" +) + +func test() { + ctx := gctx.New() + + // 添加/修改 + cache.Instance().Set(ctx, "qwe", 123, 0) + + // 查询 + cache.Instance().Get(ctx, "qwe") + + // 删除 + cache.Instance().Remove(ctx, "qwe") + + // 更多方法请参考:https://goframe.org/pages/viewpage.action?pageId=27755640 +} + +``` + +### 请求上下文 + +- 主要用于在处理HTTP和websocket请求时通过中间件将用户、应用、插件等信息绑定到上下文中,方便在做业务处理时用到这些信息 + +```go +package admin + +import ( + "fmt" + "context" + "hotgo/internal/library/contexts" + "hotgo/internal/library/addons" +) + + +func test(ctx context.Context) { + // 获取当前请求的所有上下文变量 + var ctxModel = contexts.Get(ctx) + fmt.Printf("当前请求的所有上下文变量:%+v\n", ctxModel) + + // 获取当前请求的应用模块 + var module = contexts.GetModule(ctx) + fmt.Printf("当前请求的应用:%+v\n", module) + + // 获取当前请求的用户信息 + var member = contexts.GetUser(ctx) + fmt.Printf("当前访问用户信息:%+v\n", member) + + // 获取当前请求的插件模块 + fmt.Printf("当前是否为插件请求:%v", contexts.IsAddonRequest(ctx)) + if contexts.IsAddonRequest(ctx) { + fmt.Printf("当前插件名称:%v", contexts.GetAddonName(ctx)) + fmt.Printf("当前插件信息:%v", addons.GetModule(contexts.GetAddonName(ctx))) + } +} + +``` + +### JWT + +- 基于jwt+缓存驱动实现的用户登录令牌功能,支持自动续约,解决了jwt服务端无法退出问题和jwt令牌无法主动失效问题 + +#### 配置示例 +```yaml +# 登录令牌 +token: + secretKey: "hotgo123" # 令牌加密秘钥,考虑安全问题生产环境中请修改默认值 + expires: 604800 # 令牌有效期,单位:秒。默认7天 + autoRefresh: true # 是否开启自动刷新过期时间, false|true 默认为true + refreshInterval: 86400 # 刷新间隔,单位:秒。必须小于expires,否则无法触发。默认1天内只允许刷新一次 + maxRefreshTimes: 30 # 最大允许刷新次数,-1不限制。默认30次 + multiLogin: true # 是否允许多端登录, false|true 默认为true + +``` + +```go +package admin + +import ( + "fmt" + "context" + "hotgo/internal/library/token" + "hotgo/internal/model" +) + + +func test(ctx context.Context) { + // 登录 + user := &model.Identity{ + Id: mb.Id, + Pid: mb.Pid, + DeptId: mb.DeptId, + RoleId: ro.Id, + RoleKey: ro.Key, + Username: mb.Username, + RealName: mb.RealName, + Avatar: mb.Avatar, + Email: mb.Email, + Mobile: mb.Mobile, + App: consts.AppAdmin, + LoginAt: gtime.Now(), + } + + loginToken, expires, err := token.Login(ctx, user) + if err != nil { + return nil, err + } + + // gf请求对象 + r := *ghttp.Request + + // 获取登录用户信息 + user, err := token.ParseLoginUser(r) + if err != nil { + return + } + + // 注销登录 + err = token.Logout(r) +} + +``` + +### 数据字典 + +- hotgo增加了对枚举字典和自定义方法字典的内置支持,从而在系统中经常使用的一些特定数据上做出了增强。 + +#### 字典数据选项 +- 文件路径:server/internal/model/dict.go + +```go +package model + +// Option 字典数据选项 +type Option struct { + Key interface{} `json:"key"` + Label string `json:"label" description:"字典标签"` + Value interface{} `json:"value" description:"字典键值"` + ValueType string `json:"valueType" description:"键值数据类型"` + Type string `json:"type" description:"字典类型"` + ListClass string `json:"listClass" description:"表格回显样式"` +} +``` + +#### 枚举字典 +- 适用于系统开发期间内置的枚举数据,这样即维护了枚举值,又关联了数据字典 + +##### 一个例子 +- 定义枚举值和字典数据选项,并注册字典类型 +- 文件路径:server/internal/consts/credit_log.go + +```go +package consts + +import ( + "hotgo/internal/library/dict" + "hotgo/internal/model" +) + +func init() { + dict.RegisterEnums("creditType", "资金变动类型", CreditTypeOptions) + dict.RegisterEnums("creditGroup", "资金变动分组", CreditGroupOptions) +} + +const ( + CreditTypeBalance = "balance" // 余额 + CreditTypeIntegral = "integral" // 积分 +) + +const ( + CreditGroupDecr = "decr" // 扣款 + CreditGroupIncr = "incr" // 加款 + CreditGroupOpDecr = "op_decr" // 操作扣款 + CreditGroupOpIncr = "op_incr" // 操作加款 + CreditGroupBalanceRecharge = "balance_recharge" // 余额充值 + CreditGroupBalanceRefund = "balance_refund" // 余额退款 + CreditGroupApplyCash = "apply_cash" // 申请提现 +) + +// CreditTypeOptions 变动类型 +var CreditTypeOptions = []*model.Option{ + dict.GenSuccessOption(CreditTypeBalance, "余额"), + dict.GenInfoOption(CreditTypeIntegral, "积分"), +} + +// CreditGroupOptions 变动分组 +var CreditGroupOptions = []*model.Option{ + dict.GenWarningOption(CreditGroupDecr, "扣款"), + dict.GenSuccessOption(CreditGroupIncr, "加款"), + dict.GenWarningOption(CreditGroupOpDecr, "操作扣款"), + dict.GenSuccessOption(CreditGroupOpIncr, "操作加款"), + dict.GenWarningOption(CreditGroupBalanceRefund, "余额退款"), + dict.GenSuccessOption(CreditGroupBalanceRecharge, "余额充值"), + dict.GenInfoOption(CreditGroupApplyCash, "申请提现"), +} + +``` + + +#### 自定义方法字典 +- 适用于非固定选项,如数据是从某个表/文件读取或从第三方读取,数据需要进行转换时使用 + +##### 方法字典接口 +- 文件路径:server/internal/consts/credit_log.go + +```go +package dict + +// FuncDict 方法字典,实现本接口即可使用内置方法字典 +type FuncDict func(ctx context.Context) (res []*model.Option, err error) +``` + +##### 一个例子 +- 定义获取字典数据方法,并注册字典类型 +- 文件路径:server/internal/logic/admin/post.go + +```go +package admin + +import ( + "context" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "hotgo/internal/consts" + "hotgo/internal/dao" + "hotgo/internal/library/dict" + "hotgo/internal/model" + "hotgo/internal/model/entity" + "hotgo/internal/service" +) + +type sAdminPost struct{} + +func NewAdminPost() *sAdminPost { + return &sAdminPost{} +} + +func init() { + service.RegisterAdminPost(NewAdminPost()) + dict.RegisterFunc("adminPostOption", "岗位选项", service.AdminPost().Option) +} + +// Option 岗位选项 +func (s *sAdminPost) Option(ctx context.Context) (opts []*model.Option, err error) { + var list []*entity.AdminPost + if err = dao.AdminPost.Ctx(ctx).OrderAsc(dao.AdminPost.Columns().Sort).Scan(&list); err != nil { + return nil, err + } + + if len(list) == 0 { + opts = make([]*model.Option, 0) + return + } + + for _, v := range list { + opts = append(opts, dict.GenHashOption(v.Id, v.Name)) + } + return +} +``` + +#### 代码生成支持 +- 内置的枚举字典和自定义方法字典在生成代码时可以直接进行选择,生成代码格式和系统字典管理写法一致 + +![最终编辑表单效果](images/sys-library-dict.png) + + +#### 内置字典和系统字典的区分 + +##### 主要区别 +- 系统字典由表:`hg_sys_dict_type`和`hg_sys_dict_data`共同进行维护,使用时需通过后台到字典管理中进行添加 +- 内置字典是系统开发期间在代码层面事先定义和注册好的数据选项 + + +##### 数据格式区别 +- 系统字典所有ID都是大于0的int64类型 +- 内置字典ID都是小于0的int64类型。枚举字典以20000开头,如:-200001381053496;方法字典以30000开头,如:-30000892528327;开头以外数字是根据数据选项的`key`值进行哈希算法得出 + +### 地理定位 +```go +// 待写 +``` + +### 通知 +```go +// 待写 ``` \ No newline at end of file diff --git a/docs/guide-zh-CN/sys-middleware.md b/docs/guide-zh-CN/sys-middleware.md index 4546cb7..70f334a 100644 --- a/docs/guide-zh-CN/sys-middleware.md +++ b/docs/guide-zh-CN/sys-middleware.md @@ -1,249 +1,250 @@ -## 中间件/拦截器 - -目录 - -- 介绍 -- 全局中间件 -- 鉴权中间件 -- 响应中间件 -- 更多 - -### 介绍 -- 在hotgo中,中间件/拦截器主要作用于web请求的上下文预设、跨域请求处理、鉴权处理、请求拦截和请求结束后统一响应处理等。 - - -### 全局中间件 -```go -package main - -import ( - "hotgo/internal/service" -) - -func main() { - - // 初始化请求上下文,一般需要第一个进行加载,后续中间件存在依赖关系 - service.Middleware().Ctx() - - // 跨域中间件,自动处理跨域问题 - service.Middleware().CORS() - - // IP黑名单中间件,如果请求IP被后台拉黑,所有请求将被拒绝 - service.Middleware().Blacklist() - - // 演示系統操作限制,当开启演示模式时,所有POST请求将被拒绝 - service.Middleware().DemoLimit() - - // 请求输入预处理,api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理 - service.Middleware().PreFilter() - - // HTTP响应预处理,在业务处理完成后,对响应结果进行格式化和错误过滤,将处理后的数据发送给请求方 - service.Middleware().ResponseHandler() - -} - -``` -### 鉴权中间件 -```go -package main - -import ( - "github.com/gogf/gf/v2/frame/g" -) - -func main() { - - // 在鉴权中间件下的路由如果没有通过权限验证,后续请求将被拒绝 - // 在hotgo中,鉴权中间件一般是配合一个业务模块下的路由组进行使用 - // 目前admin、api、home、websocket模块都已接入 - // 如果你需要创建一个新的模块也需要用到鉴权中间件,可以参考:server/internal/logic/middleware/admin_auth.go - - - // 一个简单例子 - s := g.Server() - s.Group("/api", func(group *ghttp.RouterGroup) { - group.Middleware(service.Middleware().ApiAuth) - group.Bind( - member.Member, // 管理员 - ) - }) - -} - -``` - -### 响应中间件 -- 文件路径:server/internal/logic/middleware/response.go - - -#### 常用响应类型 - -- hotgo为一些常用的响应类型做了统一格式封装,例如:`application/json`、`text/xml`、`text/html`、`text/event-stream`等,默认使用`application/json`。 -- 下面我们以`text/xml`为例简单演示几种使用方法: - -1. 当你使用规范化路由时,可直接在XxxRes结构体的`g.Meta`中声明响应类型: -```go -type HelloReq struct { - g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"` - Name string `json:"name" d:"hotgo" dc:"名字"` -} - -type HelloRes struct { - g.Meta `mime:"text/xml" type:"string"` - Tips string `json:"tips"` -} -``` - -2. 在响应前设置响应头: -```go -var ( - Hello = cHello{} -) - -type cHello struct{} - -func (c *cHello) Hello(ctx context.Context, req *user.HelloReq) (res *user.HelloRes, err error) { - r := ghttp.RequestFromCtx(ctx) - r.Response.Header().Set("Content-Type", "text/xml") - - res = &user.HelloRes{ - Tips: fmt.Sprintf("hello %v, this is the api for %v applications.", req.Name, simple.AppName(ctx)), - } - return -} -``` - -- 浏览器中访问响应内容如下: - -![./images/sys-middleware-com-response.png](./images/sys-middleware-com-response.png) - - -#### 自定义响应 -- 在实际开发中,可能需要使用自定义的响应类型,由于响应中间件是全局的,因此您需要对其进行单独处理。 -- 推荐以下几种处理方案,可做参考: -1. 使用`ghttp.ExitAll()`,需要注意的是此方法会终止后续所有的http处理 - -```go -package main - -import ( - "github.com/gogf/gf/v2/net/ghttp" -) - -func main() { - r := new(ghttp.Request) // 当前请求对象 - - // 清空响应 - r.Response.ClearBuffer() - - // 写入响应 - r.Response.Write("自定义响应内容") - - // 终止后续http处理 - r.ExitAll() -} -``` - -2. 在`server/internal/logic/middleware/response.go`中根据请求的独有特征进行单独的处理,兼容后续http处理。 - - -#### 重写响应错误提示 - -- 在实际开发中,我们可能想要隐藏一些敏感错误,返回给客户端友好的错误提示,但开发者同时又想需要看到真实的敏感错误。对此hotgo已经进行了过滤处理,下面是一个简单的例子: - -```go -package main - -import ( - "github.com/gogf/gf/v2/errors/gerror" -) - -func test() error { - err = gerror.New("这是一个sql执行错误") - err = gerror.Wrap(err, "用户创建失败,请稍后重试!~") - return err -} -``` - -- 开启debug时的客户端响应: -```json -{ - "code": -1, - "message": "用户创建失败,请稍后重试!~", - "error": [ - "1. 用户创建失败,请稍后重试!~", - " 1). hotgo/internal/logic/admin.(*sAdminMember).List", - " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526", - "2. 这是一个sql执行错误", " 1). hotgo/internal/logic/admin.(*sAdminMember).List", - " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:525", - " 2). hotgo/internal/controller/admin/admin.(*cMember).List", - " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/controller/admin/admin/member.go:157", "" - ], - "timestamp": 1684145107, - "traceID": "084022730d495f17f19e550140f3e1a8" -} -``` - -- 关闭debug时的客户端响应: -```json -{ - "code": -1, - "message": "用户创建失败,请稍后重试!~", - "timestamp": 1684145107, - "traceID": "084022730d495f17f19e550140f3e1a8" -} -``` - -- 控制台的输出日志: -```shell -2023-05-15 18:05:07.776 {084022730d495f17f19e550140f3e1a8} 200 "GET http localhost:8000 /admin/member/list?page=1&pageSize=10&roleId=-1 HTTP/1.1" 0.002, 127.0.0.1, "http://192.168.0.207:8001/login", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Co -re/1.94.197.400 QQBrowser/11.7.5287.400", -1, "", "" -Stack: -1. 用户创建失败,请稍后重试!~ - 1). hotgo/internal/logic/admin.(*sAdminMember).List - E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526 -2. 这是一个sql执行错误 - 1). hotgo/internal/logic/admin.(*sAdminMember).List - E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/response.go:24 - 13). hotgo/internal/logic/middleware.(*sMiddleware).DemoLimit - E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/init.go:90 - -``` - -- 如果你开启了访问日志,那么日志记录中会详细记录本次请求的相关信息,内容如下: -![./images/sys-middleware-error-log.png](./images/sys-middleware-error-log.png) - - -#### 重写错误码 -- hotgo默认使用了gf内置的错误码进行业务处理,通常情况下成功状态码为`0`,失败状态码为`-1` -- 查看gf内置错误码:https://goframe.org/pages/viewpage.action?pageId=30739587 -- 以下是自定义错误码的简单例子: - -```go -package main - -import ( - "github.com/gogf/gf/v2/errors/gerror" -) - -func test() error { - // 使用自定义状态码30001响应客户端 - err = gerror.NewCode(gcode.New(30001, "用户创建失败,请稍后重试!~", nil)) - return err -} -``` - - -- 客户端响应如下: -```json -{ - "code": 30001, - "message": "用户创建失败,请稍后重试!~", - "timestamp": 1684146313, - "traceID": "b4f90e16264a5f17cd3fc27141aba448" -} -``` - -### 更多 -- 更多关于中间件/拦截器的介绍请参考:https://goframe.org/pages/viewpage.action?pageId=55289881 - +## 中间件/拦截器 + +目录 + +- 介绍 +- 全局中间件 +- 鉴权中间件 +- 响应中间件 +- 更多 + +### 介绍 +- 在hotgo中,中间件/拦截器主要作用于web请求的上下文预设、跨域请求处理、鉴权处理、请求拦截和请求结束后统一响应处理等。 + + +### 全局中间件 +```go +package main + +import ( + "hotgo/internal/service" +) + +func main() { + + // 初始化请求上下文,一般需要第一个进行加载,后续中间件存在依赖关系 + service.Middleware().Ctx() + + // 跨域中间件,自动处理跨域问题 + service.Middleware().CORS() + + // IP黑名单中间件,如果请求IP被后台拉黑,所有请求将被拒绝 + service.Middleware().Blacklist() + + // 演示系統操作限制,当开启演示模式时,所有POST请求将被拒绝 + service.Middleware().DemoLimit() + + // 请求输入预处理,api使用gf规范路由并且XxxReq结构体实现了validate.Filter接口即可隐式预处理 + service.Middleware().PreFilter() + + // HTTP响应预处理,在业务处理完成后,对响应结果进行格式化和错误过滤,将处理后的数据发送给请求方 + service.Middleware().ResponseHandler() + +} + +``` +### 鉴权中间件 +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + + // 在鉴权中间件下的路由如果没有通过权限验证,后续请求将被拒绝 + // 在hotgo中,鉴权中间件一般是配合一个业务模块下的路由组进行使用 + // 目前admin、api、home、websocket模块都已接入 + // 如果你需要创建一个新的模块也需要用到鉴权中间件,可以参考:server/internal/logic/middleware/admin_auth.go + + + // 一个简单例子 + s := g.Server() + s.Group("/api", func(group *ghttp.RouterGroup) { + group.Middleware(service.Middleware().ApiAuth) + group.Bind( + member.Member, // 管理员 + ) + }) + +} + +``` + +### 响应中间件 +- 文件路径:server/internal/logic/middleware/response.go + + +#### 常用响应类型 + +- hotgo为一些常用的响应类型做了统一格式封装,例如:`application/json`、`text/xml`、`text/html`、`text/event-stream`等,默认使用`application/json`。 +- 下面我们以`text/xml`为例简单演示几种使用方法: + +1. 当你使用规范化路由时,可直接在XxxRes结构体的`g.Meta`中声明响应类型: + +```go +type HelloReq struct { + g.Meta `path:"/hello" tags:"Hello" method:"get" summary:"You first hello api"` + Name string `json:"name" d:"hotgo" dc:"名字"` +} + +type HelloRes struct { + g.Meta `mime:"text/xml" type:"string"` + Tips string `json:"tips"` +} +``` + +2. 在响应前设置响应头: + +```go +var ( + Hello = cHello{} +) + +type cHello struct{} + +func (c *cHello) Hello(ctx context.Context, req *user.HelloReq) (res *user.HelloRes, err error) { + r := ghttp.RequestFromCtx(ctx) + r.Response.Header().Set("Content-Type", "text/xml") + + res = &user.HelloRes{ + Tips: fmt.Sprintf("hello %v, this is the api for %v applications.", req.Name, simple.AppName(ctx)), + } + return +} +``` + +- 浏览器中访问响应内容如下: + +![./images/sys-middleware-com-response.png](./images/sys-middleware-com-response.png) + + +#### 自定义响应 +- 在实际开发中,可能需要使用自定义的响应类型,由于响应中间件是全局的,因此您需要对其进行单独处理。 +- 推荐以下几种处理方案,可做参考: +1. 使用`ghttp.ExitAll()`,需要注意的是此方法会终止后续所有的http处理 + +```go +package main + +import ( + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + r := new(ghttp.Request) // 当前请求对象 + + // 清空响应 + r.Response.ClearBuffer() + + // 写入响应 + r.Response.Write("自定义响应内容") + + // 终止后续http处理 + r.ExitAll() +} +``` + +2. 在`server/internal/logic/middleware/response.go`中根据请求的独有特征进行单独的处理,兼容后续http处理。 + +#### 重写响应错误提示 +- 在实际开发中,我们可能想要隐藏一些敏感错误,返回给客户端友好的错误提示,但开发者同时又想需要看到真实的敏感错误。对此hotgo已经进行了过滤处理,下面是一个简单的例子: + +```go +package main + +import ( + "github.com/gogf/gf/v2/errors/gerror" +) + +func test() error { + err = gerror.New("这是一个sql执行错误") + err = gerror.Wrap(err, "用户创建失败,请稍后重试!~") + return err +} +``` + +- 开启debug时的客户端响应: +```json +{ + "code": -1, + "message": "用户创建失败,请稍后重试!~", + "error": [ + "1. 用户创建失败,请稍后重试!~", + " 1). hotgo/internal/logic/admin.(*sAdminMember).List", + " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526", + "2. 这是一个sql执行错误", " 1). hotgo/internal/logic/admin.(*sAdminMember).List", + " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:525", + " 2). hotgo/internal/controller/admin/admin.(*cMember).List", + " E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/controller/admin/admin/member.go:157", "" + ], + "timestamp": 1684145107, + "traceID": "084022730d495f17f19e550140f3e1a8" +} +``` + +- 关闭debug时的客户端响应: +```json +{ + "code": -1, + "message": "用户创建失败,请稍后重试!~", + "timestamp": 1684145107, + "traceID": "084022730d495f17f19e550140f3e1a8" +} +``` + +- 控制台的输出日志: + +```shell +2023-05-15 18:05:07.776 {084022730d495f17f19e550140f3e1a8} 200 "GET http localhost:8000 /admin/member/list?page=1&pageSize=10&roleId=-1 HTTP/1.1" 0.002, 127.0.0.1, "http://192.168.0.207:8001/login", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36 Co +re/1.94.197.400 QQBrowser/11.7.5287.400", -1, "", "" +Stack: +1. 用户创建失败,请稍后重试!~ + 1). hotgo/internal/logic/admin.(*sAdminMember).List + E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/admin/member.go:526 +2. 这是一个sql执行错误 + 1). hotgo/internal/logic/admin.(*sAdminMember).List + E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/response.go:24 + 13). hotgo/internal/logic/middleware.(*sMiddleware).DemoLimit + E:/Users/Administrator/Desktop/gosrc/hotgo_dev/server/internal/logic/middleware/init.go:90 + +``` + +- 如果你开启了访问日志,那么日志记录中会详细记录本次请求的相关信息,内容如下: +![./images/sys-middleware-error-log.png](./images/sys-middleware-error-log.png) + + +#### 重写错误码 +- hotgo默认使用了gf内置的错误码进行业务处理,通常情况下成功状态码为`0`,失败状态码为`-1` +- 查看gf内置错误码:https://goframe.org/pages/viewpage.action?pageId=30739587 +- 以下是自定义错误码的简单例子: + +```go +package main + +import ( + "github.com/gogf/gf/v2/errors/gerror" +) + +func test() error { + // 使用自定义状态码30001响应客户端 + err = gerror.NewCode(gcode.New(30001, "用户创建失败,请稍后重试!~", nil)) + return err +} +``` + + +- 客户端响应如下: +```json +{ + "code": 30001, + "message": "用户创建失败,请稍后重试!~", + "timestamp": 1684146313, + "traceID": "b4f90e16264a5f17cd3fc27141aba448" +} +``` + +### 更多 +- 更多关于中间件/拦截器的介绍请参考:https://goframe.org/pages/viewpage.action?pageId=55289881 + diff --git a/docs/guide-zh-CN/sys-tenant.md b/docs/guide-zh-CN/sys-tenant.md index 931224e..6c97d79 100644 --- a/docs/guide-zh-CN/sys-tenant.md +++ b/docs/guide-zh-CN/sys-tenant.md @@ -44,6 +44,7 @@ SaaS系统多租户多应用设计,已成为互联网企业的重要发展建 - 在用户登录成功后,server端可通过上下文来获取用户部门类型来确定用户身份 - 文件路径:server/internal/library/contexts/context.go + ```go package contexts @@ -87,6 +88,7 @@ func IsUserDept(ctx context.Context) bool { - 在用户登录成功后,web端可通`useUserStore`来获取用户部门类型来确定用户身份 - 文件路径:web/src/store/modules/user.ts + ```vue + // Progress Plugin Configuration + progress: { + position: "top", + // color: "var(--theme-color,#42b983)", + height: "3px", + }, + }; + - - + + - - - - - - - - + + + + + + + + - - - + + + + + + + + - - - + + + + + + + + + + + + + + + - - - - - -