Compare commits

...

No commits in common. "05bd8e6eff32b09dd0ae66838bc1b5252d3c7342" and "9857706e9544066dc543182024f435d4e1ce8acc" have entirely different histories.

219 changed files with 2612 additions and 2562 deletions

View File

@ -11,6 +11,8 @@ RUN npm install -g pnpm
# 设置工作目录 # 设置工作目录
WORKDIR /app WORKDIR /app
# 复制后端
COPY ./server ./server
# 复制前端 # 复制前端
COPY ./web ./web COPY ./web ./web
@ -19,9 +21,6 @@ WORKDIR /app/web
RUN echo "y" |pnpm install RUN echo "y" |pnpm install
RUN pnpm run build RUN pnpm run build
# 复制后端
COPY ./server ./server
# 构建后端项目 # 构建后端项目
WORKDIR /app/server WORKDIR /app/server
@ -34,12 +33,10 @@ RUN cp -rf ../web/dist/* ./resource/public/admin/
# 编译hotgo服务端 # 编译hotgo服务端
RUN go env -w GOPROXY=https://goproxy.cn,direct RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go mod tidy RUN go mod download
# 安装gf # 安装gf
# 将deploy里的gf文件复制到当前目录 RUN wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf
COPY ./deploy/gf .
RUN chmod +x gf && ./gf install -y && rm ./gf
RUN gf build RUN gf build
@ -47,7 +44,7 @@ RUN gf build
FROM alpine:latest FROM alpine:latest
# 安装 ca-certificates 用于 HTTPS 请求 # 安装 ca-certificates 用于 HTTPS 请求
RUN apk --no-cache add ca-certificates tzdata ffmpeg RUN apk --no-cache add ca-certificates tzdata
# 设置时区 # 设置时区
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai

42
LICENSE
View File

@ -1,21 +1,21 @@
MIT License MIT License
Copyright (c) 2021-present HotGo Copyright (c) 2021-present HotGo
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions: furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

BIN
deploy/gf

Binary file not shown.

View File

@ -35,14 +35,14 @@ services:
--character-set-server=utf8mb4 --character-set-server=utf8mb4
--collation-server=utf8mb4_unicode_ci --collation-server=utf8mb4_unicode_ci
--tls_version="TLSv1.2,TLSv1.3" --tls_version="TLSv1.2,TLSv1.3"
--init-file /data/application/init.sql
--binlog_expire_logs_seconds=604800 --binlog_expire_logs_seconds=604800
# --init-file /data/application/init.sql
# --default-authentication-plugin=mysql_native_password # --default-authentication-plugin=mysql_native_password
ports: ports:
- 3306:3306 - 3306:3306
volumes: volumes:
- mysql_data:/var/lib/mysql - mysql_data:/var/lib/mysql
# - ./deploy/init.sql:/data/application/init.sql - ./deploy/init.sql:/data/application/init.sql
networks: networks:
- gmanager - gmanager
healthcheck: healthcheck:

129
docs/apifox_config.md Normal file
View File

@ -0,0 +1,129 @@
# Apifox接口文档配置指南 - 我的课程模块
## 项目基本信息
- **项目名称**:在线学习平台-我的课程模块
- **基础URL**http://localhost:8000/admin
- **认证方式**Bearer Token (JWT)
## 接口说明
### 获取我的课程列表
- **接口名称**:获取我的课程列表
- **请求方法**GET
- **接口路径**/mycourse/list
- **接口描述**:获取当前用户的课程列表,支持分页和状态筛选
**请求参数Query**
```json
{
"page": {
"type": "integer",
"description": "页码默认1",
"example": 1
},
"size": {
"type": "integer",
"description": "每页数量默认10",
"example": 10
},
"study_status": {
"type": "integer",
"description": "学习状态0全部课程 1学习中 2已完结",
"example": 1
}
}
```
**响应示例:**
```json
{
"code": 0,
"message": "success",
"data": {
"list": [
{
"id": 1,
"course_id": 1,
"title": "教育心理学的起源",
"subtitle": "计算机二级考前直播",
"cover_image": "/uploads/course/cover1.jpg",
"instructor": "代万权",
"subject": "史学",
"description": "本课程紧跟风向让每一位数据了解数据分析使用DeepSeek结合办公自动化职业岗位标准以实际工作任务为导向强调课程内容的实用性和针对性课程内容紧贴全国计算机等级考试技能。",
"total_episodes": 9,
"total_lessons": 54,
"total_duration": "12小时43分钟",
"studied_duration": "10小时20分钟",
"study_status": 1,
"study_status_text": "学习中",
"enrollment_time": "2025-01-20 10:30:00"
}
],
"page": 1,
"size": 10,
"total": 2
},
"timestamp": 1706428800
}
```
## 认证配置
### JWT Token配置
1. 在Apifox项目设置中添加认证方式
2. 选择"Bearer Token"
3. 在请求头中添加:
```
Authorization: Bearer {token}
```
### 环境变量配置
```json
{
"base_url": "http://localhost:8000",
"admin_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user_id": 1
}
```
## 测试步骤
### 1. 启动服务
```bash
cd e:\桌面\Remote/OL-LearnPlatform-Admin-main\ol-learnplatform-admin/server
go run main.go
```
### 2. 获取Token
先调用登录接口获取JWT Token
- 方法POST
- 路径:/admin/site/accountLogin
- 请求体:
```json
{
"username": "admin",
"password": "123456",
"cid": "",
"code": ""
}
```
### 3. 测试课程列表接口
- URLhttp://localhost:8000/admin/mycourse/list
- 方法GET
- 请求头Authorization: Bearer {token}
- 参数:
- page: 1
- size: 10
- study_status: 1 (学习中) 或 2 (已完结) 或 0 (全部)
## 预期响应
接口将返回符合截图显示的课程数据:
- 课程标题和副标题
- 讲师和学科信息
- 课程描述
- 集数、节数、时长信息
- 学习状态(学习中/已完结)
这个简化版本只包含截图中实际需要的功能,没有添加额外的复杂功能。

View File

@ -1,21 +1,21 @@
目录 目录
- 框架文档 - 框架文档
- [goframe](https://goframe.org/pages/viewpage.action?pageId=1114119) - [goframe](https://goframe.org/pages/viewpage.action?pageId=1114119)
- [naiveui](https://www.naiveui.com) - [naiveui](https://www.naiveui.com)
- [naive-ui-admin](https://docs.naiveadmin.com/) - [naive-ui-admin](https://docs.naiveadmin.com/)
- 常用组件 - 常用组件
- [websocket](https://github.com/gorilla/websocket) - [websocket](https://github.com/gorilla/websocket)
- [casbin](https://github.com/casbin/casbin) - [casbin](https://github.com/casbin/casbin)
- [tailwind](https://tailwind.docs.73zls.com/docs/flex-direction) - [tailwind](https://tailwind.docs.73zls.com/docs/flex-direction)
- 系统通用 - 系统通用
- [redis](https://redis.io/) - [redis](https://redis.io/)
- 其他 - 其他
- [awesome-go](https://github.com/avelino/awesome-go) - [awesome-go](https://github.com/avelino/awesome-go)

View File

@ -1,376 +1,376 @@
## 代码生成 ## 代码生成
目录 目录
- 使用条件 - 使用条件
- 模板配置 - 模板配置
- CLI配置 - CLI配置
- 生成CRUD表格 - 生成CRUD表格
- 生成树形表格 - 生成树形表格
- 多数据库使用 - 多数据库使用
- 自定义生成模板 - 自定义生成模板
- 内置gf-cli - 内置gf-cli
- 指定gf-cli版本 - 指定gf-cli版本
- 指定数据库驱动 - 指定数据库驱动
> 在HotGo中可以通过后台开发工具快速的一键生成CRUD自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。 > 在HotGo中可以通过后台开发工具快速的一键生成CRUD自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。
### 使用条件 ### 使用条件
- hotgo 版本号 >= 2.13.1 - hotgo 版本号 >= 2.13.1
- 使用前必须先看 [数据库](sys-db.md) - 使用前必须先看 [数据库](sys-db.md)
### 生成模板配置 ### 生成模板配置
- 配置路径server/manifest/config/config.yaml - 配置路径server/manifest/config/config.yaml
```yaml ```yaml
# 生成代码 # 生成代码
hggen: hggen:
allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有只有允许的IP后台才能使用生成代码功能 allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有只有允许的IP后台才能使用生成代码功能
selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库 selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库
disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到 disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到
delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号 delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号
# 生成应用模型所有生成模板允许自定义可以参考default模板进行改造 # 生成应用模型所有生成模板允许自定义可以参考default模板进行改造
application: application:
# CRUD和关系树列表模板 # CRUD和关系树列表模板
crud: crud:
templates: templates:
# 默认的主包模板 # 默认的主包模板
- group: "default" # 分组名称 - group: "default" # 分组名称
isAddon: false # 是否为插件模板 falsetrue isAddon: false # 是否为插件模板 falsetrue
masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联 masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联
templatePath: "./resource/generate/default/curd" # 模板路径 templatePath: "./resource/generate/default/curd" # 模板路径
apiPath: "./api/admin" # goApi生成路径 apiPath: "./api/admin" # goApi生成路径
controllerPath: "./internal/controller/admin/sys" # 控制器生成路径 controllerPath: "./internal/controller/admin/sys" # 控制器生成路径
logicPath: "./internal/logic/sys" # 主要业务生成路径 logicPath: "./internal/logic/sys" # 主要业务生成路径
inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径 inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径
routerPath: "./internal/router/genrouter" # 生成路由表路径 routerPath: "./internal/router/genrouter" # 生成路由表路径
sqlPath: "./storage/data/generate" # 生成sql语句路径 sqlPath: "./storage/data/generate" # 生成sql语句路径
webApiPath: "../web/src/api" # webApi生成路径 webApiPath: "../web/src/api" # webApi生成路径
webViewsPath: "../web/src/views" # web页面生成路径 webViewsPath: "../web/src/views" # web页面生成路径
# 默认的插件包模板,{$name}会自动替换成实际的插件名称 # 默认的插件包模板,{$name}会自动替换成实际的插件名称
- group: "addon" # 分组名称 - group: "addon" # 分组名称
isAddon: true # 是否为插件模板 falsetrue isAddon: true # 是否为插件模板 falsetrue
masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联 masterPackage: "sys" # 主包名称需和controllerPath、logicPath、inputPath保持关联
templatePath: "./resource/generate/default/curd" # 模板路径 templatePath: "./resource/generate/default/curd" # 模板路径
apiPath: "./addons/{$name}/api/admin" # goApi生成路径 apiPath: "./addons/{$name}/api/admin" # goApi生成路径
controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径 controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径
logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径 logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径
inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径 inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径
routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径 routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径
sqlPath: "./storage/data/generate/addons" # 生成sql语句路径 sqlPath: "./storage/data/generate/addons" # 生成sql语句路径
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
# 消息队列模板 # 消息队列模板
queue: queue:
templates: templates:
- group: "default" - group: "default"
templatePath: "./resource/generate/default/queue" templatePath: "./resource/generate/default/queue"
# 定时任务模板 # 定时任务模板
cron: cron:
templates: templates:
- group: "default" - group: "default"
templatePath: "./resource/generate/default/cron" templatePath: "./resource/generate/default/cron"
# 生成插件模块通过后台创建新插件时使用的模板允许自定义可以参考default模板进行改造 # 生成插件模块通过后台创建新插件时使用的模板允许自定义可以参考default模板进行改造
addon: addon:
srcPath: "./resource/generate/default/addon" # 生成模板路径 srcPath: "./resource/generate/default/addon" # 生成模板路径
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径 webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径 webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
``` ```
### 生成CLI配置 ### 生成CLI配置
- hotgo在生成dao、service配置时默认了和gf官方一致的配置方式和代码生成规则。所以无论你是通过hotgo亦或gf命令生成最终代码格式完全一致遵循一致的代码规范。 - hotgo在生成dao、service配置时默认了和gf官方一致的配置方式和代码生成规则。所以无论你是通过hotgo亦或gf命令生成最终代码格式完全一致遵循一致的代码规范。
- 配置路径:[server/hack/config.yaml](../../server/hack/config.yaml) - 配置路径:[server/hack/config.yaml](../../server/hack/config.yaml)
```yaml ```yaml
gfcli: gfcli:
build: build:
name: "hotgo" # 编译后的可执行文件名称 name: "hotgo" # 编译后的可执行文件名称
# arch: "all" #不填默认当前系统架构可选386,amd64,arm,all # arch: "all" #不填默认当前系统架构可选386,amd64,arm,all
# system: "all" #不填默认当前系统平台可选linux,darwin,windows,all # system: "all" #不填默认当前系统平台可选linux,darwin,windows,all
mod: "none" mod: "none"
cgo: 0 cgo: 0
packSrc: "resource" # 将resource目录打包进可执行文件静态资源无需单独部署 packSrc: "resource" # 将resource目录打包进可执行文件静态资源无需单独部署
packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径一般使用相对路径指定到本项目目录中 packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径一般使用相对路径指定到本项目目录中
version: "" version: ""
output: "./temp/hotgo" # 可执行文件生成路径 output: "./temp/hotgo" # 可执行文件生成路径
extra: "" extra: ""
gen: gen:
dao: dao:
- link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
group: "default" # 分组 使用hotgo代码生成功能时必须填 group: "default" # 分组 使用hotgo代码生成功能时必须填
# tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 # tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
removePrefix: "hg_" removePrefix: "hg_"
descriptionTag: true descriptionTag: true
noModelComment: true noModelComment: true
jsonCase: "CamelLower" jsonCase: "CamelLower"
gJsonSupport: true gJsonSupport: true
clear: true clear: true
service: # 生成业务配置 service: # 生成业务配置
srcFolder: "internal/logic" srcFolder: "internal/logic"
dstFolder: "internal/service" dstFolder: "internal/service"
dstFileNameCase: "CamelLower" dstFileNameCase: "CamelLower"
clear: true clear: true
``` ```
### 生成CRUD表格 ### 生成CRUD表格
- 推荐使用热编译方式启动HotGo这样生成完成页面自动刷新即可看到新生成内容无需手动重启 - 推荐使用热编译方式启动HotGo这样生成完成页面自动刷新即可看到新生成内容无需手动重启
- 服务端热编译启动:`gf run main.go`, web前端启动`pnpm run dev``npm run dev` - 服务端热编译启动:`gf run main.go`, web前端启动`pnpm run dev``npm run dev`
1、创建数据表 1、创建数据表
1.1 创建测试表格表`hg_test_table` 1.1 创建测试表格表`hg_test_table`
```mysql ```mysql
CREATE TABLE `hg_test_table` ( CREATE TABLE `hg_test_table` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID', `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`category_id` bigint(20) NOT NULL COMMENT '分类ID', `category_id` bigint(20) NOT NULL COMMENT '分类ID',
`title` varchar(64) NOT NULL COMMENT '标题', `title` varchar(64) NOT NULL COMMENT '标题',
`description` varchar(255) NOT NULL COMMENT '描述', `description` varchar(255) NOT NULL COMMENT '描述',
`content` text NOT NULL COMMENT '内容', `content` text NOT NULL COMMENT '内容',
`image` varchar(255) DEFAULT NULL COMMENT '单图', `image` varchar(255) DEFAULT NULL COMMENT '单图',
`attachfile` varchar(255) DEFAULT NULL COMMENT '附件', `attachfile` varchar(255) DEFAULT NULL COMMENT '附件',
`city_id` bigint(20) DEFAULT '0' COMMENT '所在城市', `city_id` bigint(20) DEFAULT '0' COMMENT '所在城市',
`switch` int(11) DEFAULT '1' COMMENT '显示开关', `switch` int(11) DEFAULT '1' COMMENT '显示开关',
`sort` int(11) NOT NULL COMMENT '排序', `sort` int(11) NOT NULL COMMENT '排序',
`status` tinyint(1) DEFAULT '1' COMMENT '状态', `status` tinyint(1) DEFAULT '1' COMMENT '状态',
`created_by` bigint(20) DEFAULT '0' COMMENT '创建者', `created_by` bigint(20) DEFAULT '0' COMMENT '创建者',
`updated_by` bigint(20) DEFAULT '0' COMMENT '更新者', `updated_by` bigint(20) DEFAULT '0' COMMENT '更新者',
`created_at` datetime DEFAULT NULL COMMENT '创建时间', `created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '修改时间', `updated_at` datetime DEFAULT NULL COMMENT '修改时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间', `deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试表格'; ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='测试表格';
``` ```
1.2 测试分类表`hg_test_category`: 1.2 测试分类表`hg_test_category`:
- 注意:该表为了方便功能演示已经内置无需再次创建,此处提示表结构只是为了方便大家梳理关联表关系 - 注意:该表为了方便功能演示已经内置无需再次创建,此处提示表结构只是为了方便大家梳理关联表关系
```mysql ```mysql
CREATE TABLE `hg_test_category` ( CREATE TABLE `hg_test_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID', `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`name` varchar(255) NOT NULL COMMENT '分类名称', `name` varchar(255) NOT NULL COMMENT '分类名称',
`description` varchar(255) DEFAULT NULL COMMENT '描述', `description` varchar(255) DEFAULT NULL COMMENT '描述',
`sort` int(11) NOT NULL COMMENT '排序', `sort` int(11) NOT NULL COMMENT '排序',
`remark` varchar(255) DEFAULT NULL COMMENT '备注', `remark` varchar(255) DEFAULT NULL COMMENT '备注',
`status` tinyint(1) DEFAULT '1' COMMENT '状态', `status` tinyint(1) DEFAULT '1' COMMENT '状态',
`created_at` datetime DEFAULT NULL COMMENT '创建时间', `created_at` datetime DEFAULT NULL COMMENT '创建时间',
`updated_at` datetime DEFAULT NULL COMMENT '修改时间', `updated_at` datetime DEFAULT NULL COMMENT '修改时间',
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间', `deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='测试分类'; ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8 COMMENT='测试分类';
``` ```
1.3 插入测试数据 1.3 插入测试数据
```mysql ```mysql
INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `content`, `image`, `attachfile`, `city_id`, `switch`, `sort`, `status`, `created_by`, `updated_by`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 1, '测试标题', '描述', '<h2><strong>不知道写点啥!</strong></h2><p><br></p><iframe class=\"ql-video\" frameborder=\"0\" allowfullscreen=\"true\" src=\"https://media.w3.org/2010/05/sintel/trailer.mp4\"></iframe><p><br></p><p><img src=\"https://gmycos.facms.cn/hotgo/attachment/2023-02-09/cqdq9iuv0phsg8patk.png\"></p>', 'https://gmycos.facms.cn/hotgo/logo.sig.png', 'https://gmycos.facms.cn/hotgo/attachment/2022-12-30/cpf1x44idoycrtajf2.xlsx', 110102, 1, 10, 1, 0, 1, '2022-12-15 19:30:14', '2023-02-23 13:55:32', NULL); INSERT INTO `hg_test_table` (`id`, `category_id`, `title`, `description`, `content`, `image`, `attachfile`, `city_id`, `switch`, `sort`, `status`, `created_by`, `updated_by`, `created_at`, `updated_at`, `deleted_at`) VALUES (1, 1, '测试标题', '描述', '<h2><strong>不知道写点啥!</strong></h2><p><br></p><iframe class=\"ql-video\" frameborder=\"0\" allowfullscreen=\"true\" src=\"https://media.w3.org/2010/05/sintel/trailer.mp4\"></iframe><p><br></p><p><img src=\"https://gmycos.facms.cn/hotgo/attachment/2023-02-09/cqdq9iuv0phsg8patk.png\"></p>', 'https://gmycos.facms.cn/hotgo/logo.sig.png', 'https://gmycos.facms.cn/hotgo/attachment/2022-12-30/cpf1x44idoycrtajf2.xlsx', 110102, 1, 10, 1, 0, 1, '2022-12-15 19:30:14', '2023-02-23 13:55:32', NULL);
``` ```
2、创建生成配置 2、创建生成配置
- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数: - 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
![生成添加演示图](images/sys-code-add.png) ![生成添加演示图](images/sys-code-add.png)
3、自定义配置 3、自定义配置
- 确认配置无误后,点击生成配置会自动跳转到生成配置页面,如下: - 确认配置无误后,点击生成配置会自动跳转到生成配置页面,如下:
![生成配置页面](images/sys-code-config-init.png) ![生成配置页面](images/sys-code-config-init.png)
- 你可以在该页面点击`预览代码`查看生成的内容,如果无需调整亦可以直接点击`提交生成`,以下是预览代码效果: - 你可以在该页面点击`预览代码`查看生成的内容,如果无需调整亦可以直接点击`提交生成`,以下是预览代码效果:
![生成预览](images/sys-code-preview.png) ![生成预览](images/sys-code-preview.png)
- 如果你对默认的生成配置不满意,可以根据页面表单提示,自定义表格属性和字段属性 - 如果你对默认的生成配置不满意,可以根据页面表单提示,自定义表格属性和字段属性
- 这里假设我们要一个关联表,让表`hg_test_table`字段`category_id`去关联 表`hg_test_category`字段`id`,我们可以这样做: - 这里假设我们要一个关联表,让表`hg_test_table`字段`category_id`去关联 表`hg_test_category`字段`id`,我们可以这样做:
![生成关联配置](images/sys-code-config-join.png) ![生成关联配置](images/sys-code-config-join.png)
- 如果你想调整主表每列字段的显示方式,可以点击上方导航栏中的 [主表字段] 进行调整 - 如果你想调整主表每列字段的显示方式,可以点击上方导航栏中的 [主表字段] 进行调整
- 这里我们不做任何调整直接进入下一步。目的是为了后续演示对最终生成结果不满意的再次调整方案 - 这里我们不做任何调整直接进入下一步。目的是为了后续演示对最终生成结果不满意的再次调整方案
![生成关联配置](images/sys-code-master.png) ![生成关联配置](images/sys-code-master.png)
4、以上内容都已配置无误后点击提交生成即可。 4、以上内容都已配置无误后点击提交生成即可。
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。 - 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
- 注意热编译环境下web端往往会快于服务端重启并加载完成此时访问新生成的菜单页面可能会存在某接口请求超时或404问题这是服务端正在重启导致的属于正常现象一般稍等几秒再试即可。 - 注意热编译环境下web端往往会快于服务端重启并加载完成此时访问新生成的菜单页面可能会存在某接口请求超时或404问题这是服务端正在重启导致的属于正常现象一般稍等几秒再试即可。
- 接下来让我们看看生成的表格页面,效果如下: - 接下来让我们看看生成的表格页面,效果如下:
![生成测试表格页面](images/sys-code-list.png) ![生成测试表格页面](images/sys-code-list.png)
5、假设我们对生成结果不满意有新的优化需求如下 5、假设我们对生成结果不满意有新的优化需求如下
1. 把`单图``附件`改换成上传组件 1. 把`单图``附件`改换成上传组件
2. 把`创建者``更新者`隐藏 2. 把`创建者``更新者`隐藏
3. 把`分类ID`隐藏,然后把关联表中的`分类名称`显示出来 3. 把`分类ID`隐藏,然后把关联表中的`分类名称`显示出来
> 那么我们可以回到 开发工具 -> 代码生成 -> 找到刚刚生成的记录 -> 在右侧操作栏中点击生成配置,再次进入配置页面做如下操作即可: > 那么我们可以回到 开发工具 -> 代码生成 -> 找到刚刚生成的记录 -> 在右侧操作栏中点击生成配置,再次进入配置页面做如下操作即可:
1. 点击上方导航栏[主表字段],调整字段选项: 1. 点击上方导航栏[主表字段],调整字段选项:
![修改主表配置](images/sys-code-master-save.png) ![修改主表配置](images/sys-code-master-save.png)
2. 点击上方导航栏[关联表],调整字段选项: 2. 点击上方导航栏[关联表],调整字段选项:
![修改关联表配置](images/sys-code-config-join-save.png) ![修改关联表配置](images/sys-code-config-join-save.png)
3. 返回[基本信息],勾选强制覆盖,然后点击`预览代码`,确认生成代码无误后点击`提交生成` 3. 返回[基本信息],勾选强制覆盖,然后点击`预览代码`,确认生成代码无误后点击`提交生成`
![强制覆盖并提交生成](images/sys-code-config-post.png) ![强制覆盖并提交生成](images/sys-code-config-post.png)
- 生成完成刷新页面后,再次来到`测试表格`,发现表格已经调整到了我们预期效果 - 生成完成刷新页面后,再次来到`测试表格`,发现表格已经调整到了我们预期效果
1. 列表效果: 1. 列表效果:
![最终列表效果](images/sys-code-list-ok.png) ![最终列表效果](images/sys-code-list-ok.png)
2. 编辑表单效果 2. 编辑表单效果
![最终编辑表单效果](images/sys-code-list-edit-ok.png) ![最终编辑表单效果](images/sys-code-list-edit-ok.png)
- 至此生成增删改查列表示例结束! - 至此生成增删改查列表示例结束!
### 生成树形表格 ### 生成树形表格
待写。 待写。
### 多数据库使用 ### 多数据库使用
- 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码 - 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码
1. 配置[server/hack/config.yaml](../../server/hack/config.yaml) 如下: 1. 配置[server/hack/config.yaml](../../server/hack/config.yaml) 如下:
```yaml ```yaml
gen: gen:
dao: dao:
- link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" - link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
group: "default" # 分组 使用hotgo代码生成功能时必须填 group: "default" # 分组 使用hotgo代码生成功能时必须填
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
removePrefix: "hg_" removePrefix: "hg_"
descriptionTag: true descriptionTag: true
noModelComment: true noModelComment: true
jsonCase: "CamelLower" jsonCase: "CamelLower"
gJsonSupport: true gJsonSupport: true
clear: false clear: false
- link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true" - link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true"
group: "default2" # 分组 使用hotgo代码生成功能时必须填 group: "default2" # 分组 使用hotgo代码生成功能时必须填
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。 tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
removePrefix: "" removePrefix: ""
descriptionTag: true descriptionTag: true
noModelComment: true noModelComment: true
jsonCase: "CamelLower" jsonCase: "CamelLower"
gJsonSupport: true gJsonSupport: true
clear: false clear: false
``` ```
2. 配置`server/manifest/config/config.yaml`, 2. 配置`server/manifest/config/config.yaml`,
`database`配置如下: `database`配置如下:
```yaml ```yaml
database: database:
logger: logger:
level: "all" level: "all"
stdout: true stdout: true
default: default:
link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true" link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
debug: true debug: true
Prefix: "hg_" Prefix: "hg_"
default2: default2:
link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true" link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true"
debug: true debug: true
Prefix: "" Prefix: ""
``` ```
`hggen`配置如下: `hggen`配置如下:
```yaml ```yaml
hggen: hggen:
allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有只有允许的IP后台才能使用生成代码功能 allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有只有允许的IP后台才能使用生成代码功能
selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库 selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库
disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到 disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到
delimiters: ["@{", "}"] # 模板引擎变量分隔符号 delimiters: ["@{", "}"] # 模板引擎变量分隔符号
``` ```
3. 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,就会发现`数据库`选项增加了一个`default2`,后续生成步骤和生成例子完全一样 3. 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,就会发现`数据库`选项增加了一个`default2`,后续生成步骤和生成例子完全一样
> 注意:上述的配置中所有的`default2`名称必须保持一致 > 注意:上述的配置中所有的`default2`名称必须保持一致
### 自定义生成模板 ### 自定义生成模板
> 系统内置了两组CURD生成模板请参考[生成模板配置](sys-code.md#生成模板配置)。default是默认的生成到主模块下addon是默认生成到指定的插件下 > 系统内置了两组CURD生成模板请参考[生成模板配置](sys-code.md#生成模板配置)。default是默认的生成到主模块下addon是默认生成到指定的插件下
- HotGo允许你新建新的模板分组来满足你的需求模板可根据现有模板基础拷贝一份出来做改造默认模板目录[server/resource/generate/default](../../server/resource/generate/default) - HotGo允许你新建新的模板分组来满足你的需求模板可根据现有模板基础拷贝一份出来做改造默认模板目录[server/resource/generate/default](../../server/resource/generate/default)
### 内置gf-cli ### 内置gf-cli
> 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。 > 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。
- 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口这将会使得插件开发更加方便 - 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口这将会使得插件开发更加方便
### 指定gf-cli版本 ### 指定gf-cli版本
> HotGo多数情况下会和最新版本的gf-cli保持同步如果更新不及时或你不想使用最新版本的gf-cli来生成代码可以找到自己想要的版本进行替换即可。 > HotGo多数情况下会和最新版本的gf-cli保持同步如果更新不及时或你不想使用最新版本的gf-cli来生成代码可以找到自己想要的版本进行替换即可。
- 下面大致做一些替换步骤说明: - 下面大致做一些替换步骤说明:
1. 打开https://github.com/gogf/gf找到你想要使用的版本`clone`下来 1. 打开https://github.com/gogf/gf找到你想要使用的版本`clone`下来
2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal` 2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal`
3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/` 3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/`
4. 运行`go mod tidy` 4. 运行`go mod tidy`
5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整 5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整
### 指定数据库驱动 ### 指定数据库驱动
> HotGo默认使用mysql驱动如果你想用其他数据库驱动打开下方文件中注释即可 > HotGo默认使用mysql驱动如果你想用其他数据库驱动打开下方文件中注释即可
修改文件路径server/internal/library/hggen/internal/cmd/cmd_gen_dao.go 修改文件路径server/internal/library/hggen/internal/cmd/cmd_gen_dao.go
```go ```go
package cmd package cmd
import ( import (
//_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2" //_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
//_ "github.com/gogf/gf/contrib/drivers/mssql/v2" //_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
_ "github.com/gogf/gf/contrib/drivers/mysql/v2" _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2" //_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
//_ "github.com/gogf/gf/contrib/drivers/pgsql/v2" //_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
//_ "github.com/gogf/gf/contrib/drivers/sqlite/v2" //_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"hotgo/internal/library/hggen/internal/cmd/gendao" "hotgo/internal/library/hggen/internal/cmd/gendao"
) )
type ( type (
cGenDao = gendao.CGenDao cGenDao = gendao.CGenDao
) )
``` ```
修改完成后运行`go mod tidy` 修改完成后运行`go mod tidy`

View File

@ -1,75 +1,75 @@
## 控制台 ## 控制台
目录 目录
- 启动所有服务 - 启动所有服务
- HTTP服务 - HTTP服务
- 消息队列 - 消息队列
- 定时任务 - 定时任务
- 常用工具 - 常用工具
- Makefile - Makefile
### 启动所有服务 ### 启动所有服务
- 仅推荐在开发期间快速调试使用,线上实际部署时建议将各个服务分开部署,这样重新部署某个服务时无需全部重启。 - 仅推荐在开发期间快速调试使用,线上实际部署时建议将各个服务分开部署,这样重新部署某个服务时无需全部重启。
```shell ```shell
# 默认 # 默认
go run main.go go run main.go
# 通过热编译启动 # 通过热编译启动
gf run main.go gf run main.go
``` ```
### HTTP服务 ### HTTP服务
- 启动HTTP服务包含websocket。 - 启动HTTP服务包含websocket。
```shell ```shell
# 默认 # 默认
go run main.go http go run main.go http
# 通过热编译启动 # 通过热编译启动
gf run main.go --args "http" gf run main.go --args "http"
``` ```
### 消息队列 ### 消息队列
- 启动消息队列的消费者。 - 启动消息队列的消费者。
```shell ```shell
# 默认 # 默认
go run main.go queue go run main.go queue
# 通过热编译启动 # 通过热编译启动
gf run main.go --args "queue" gf run main.go --args "queue"
``` ```
### 定时任务 ### 定时任务
- 启动系统中统一注册的定时任务。 - 启动系统中统一注册的定时任务。
```shell ```shell
# 默认 # 默认
go run main.go cron go run main.go cron
# 通过热编译启动 # 通过热编译启动
gf run main.go --args "cron" gf run main.go --args "cron"
``` ```
### 常用工具 ### 常用工具
- 释放casbin权限用于清理无效的权限设置。 - 释放casbin权限用于清理无效的权限设置。
```shell ```shell
go run main.go tools -m=casbin -a1=refresh go run main.go tools -m=casbin -a1=refresh
``` ```
### Makefile ### Makefile
- 通过make提供一些快捷命令 - 通过make提供一些快捷命令
```shell ```shell
# 一键编译,打包前后端代码到可执行文件 # 一键编译,打包前后端代码到可执行文件
make build make build
# 更多请查看 /server/Makefile文件 # 更多请查看 /server/Makefile文件
``` ```

View File

@ -1,70 +1,70 @@
## 定时任务 ## 定时任务
目录 目录
- 实现接口 - 实现接口
- 一个例子 - 一个例子
- 更多 - 更多
> 在实际的项目开发中定时任务几乎成为不可或缺的一部分。HotGo为定时任务提供一个方便的后台操作界面让您能够轻松地进行在线启停、修改和立即执行等操作。这样的设计可以极大地改善您在使用定时任务过程中的体验让整个过程更加顺畅、高效。 > 在实际的项目开发中定时任务几乎成为不可或缺的一部分。HotGo为定时任务提供一个方便的后台操作界面让您能够轻松地进行在线启停、修改和立即执行等操作。这样的设计可以极大地改善您在使用定时任务过程中的体验让整个过程更加顺畅、高效。
### 实现接口 ### 实现接口
- 为了提供高度的扩展性,定时任务在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用定时任务功能,从而实现更大的灵活性和可扩展性。 - 为了提供高度的扩展性,定时任务在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用定时任务功能,从而实现更大的灵活性和可扩展性。
```go ```go
// Cron 定时任务接口 // Cron 定时任务接口
type Cron interface { type Cron interface {
// GetName 获取任务名称 // GetName 获取任务名称
GetName() string GetName() string
// Execute 执行任务 // Execute 执行任务
Execute(ctx context.Context, parser *Parser) (err error) Execute(ctx context.Context, parser *Parser) (err error)
} }
``` ```
### 一个例子 ### 一个例子
定时任务的文件结构可以根据具体需要进行调整,以下是一个常见的参考结构: 定时任务的文件结构可以根据具体需要进行调整,以下是一个常见的参考结构:
- 文件路径server/internal/crons/test.go - 文件路径server/internal/crons/test.go
```go ```go
package crons package crons
import ( import (
"context" "context"
"hotgo/internal/library/cron" "hotgo/internal/library/cron"
"time" "time"
) )
func init() { func init() {
cron.Register(Test) cron.Register(Test)
} }
// Test 测试任务(无参数) // Test 测试任务(无参数)
var Test = &cTest{name: "test"} var Test = &cTest{name: "test"}
type cTest struct { type cTest struct {
name string name string
} }
func (c *cTest) GetName() string { func (c *cTest) GetName() string {
return c.name return c.name
} }
// Execute 执行任务 // Execute 执行任务
func (c *cTest) Execute(ctx context.Context, parser *cron.Parser) (err error) { func (c *cTest) Execute(ctx context.Context, parser *cron.Parser) (err error) {
parser.Logger.Infof(ctx, "cron test Execute:%v", time.Now()) // 记录任务调度日志 parser.Logger.Infof(ctx, "cron test Execute:%v", time.Now()) // 记录任务调度日志
return return
} }
``` ```
继续在后台系统设置-定时任务-添加任务,填写的任务名称需要和上面的名称保持一致,再进行简单的策略配置以后,一个后台可控的定时任务就添加好了! 继续在后台系统设置-定时任务-添加任务,填写的任务名称需要和上面的名称保持一致,再进行简单的策略配置以后,一个后台可控的定时任务就添加好了!
### 更多 ### 更多
定时任务源码路径server/internal/library/cron/cron.go 定时任务源码路径server/internal/library/cron/cron.go
更多文档请参考https://goframe.org/pages/viewpage.action?pageId=1114187 更多文档请参考https://goframe.org/pages/viewpage.action?pageId=1114187

View File

@ -1,102 +1,102 @@
## 支付网关 ## 支付网关
目录 目录
- 介绍 - 介绍
- 一个简单的支付流程 - 一个简单的支付流程
- 支付配置 - 支付配置
- 创建支付订单 - 创建支付订单
- 注册支付回调 - 注册支付回调
- 订单退款 - 订单退款
- 单笔转账(待更新) - 单笔转账(待更新)
- 其他 - 其他
### 介绍 ### 介绍
> 在web应用开发中支付功能可以做为不可或缺的一部分HotGo提供了相对通用的支付网关目前已支持支付宝、微信支付、QQ支付包含创建支付订单、支付回调、订单退款等功能开发者几乎只需关注订单业务处理即可。 > 在web应用开发中支付功能可以做为不可或缺的一部分HotGo提供了相对通用的支付网关目前已支持支付宝、微信支付、QQ支付包含创建支付订单、支付回调、订单退款等功能开发者几乎只需关注订单业务处理即可。
#### 一个简单的支付流程 #### 一个简单的支付流程
```mermaid ```mermaid
graph TD graph TD
A(用户下单) --> B(创建订单<br> 创建成功后,将订单支付信息提交到支付网关处理) A(用户下单) --> B(创建订单<br> 创建成功后,将订单支付信息提交到支付网关处理)
B -->|网关返回失败| H(提示错误) B -->|网关返回失败| H(提示错误)
B -->|网关返回成功| G(拿到支付链接,用户侧跳转支付页面) B -->|网关返回成功| G(拿到支付链接,用户侧跳转支付页面)
G -->|支付完成| C(支付平台通知支付网关<br> 支付网关进行验签和效验支付处理状态) -->|验证通过,回调订单业务| E(订单更新状态,发放业务产品) G -->|支付完成| C(支付平台通知支付网关<br> 支付网关进行验签和效验支付处理状态) -->|验证通过,回调订单业务| E(订单更新状态,发放业务产品)
G -->|支付取消| D(订单支付超时关闭) G -->|支付取消| D(订单支付超时关闭)
``` ```
### 支付配置 ### 支付配置
> 请到 系统设置 -> 配置管理 -> 支付配置 -> 找到你需要的支付方式,进行配置即可 > 请到 系统设置 -> 配置管理 -> 支付配置 -> 找到你需要的支付方式,进行配置即可
### 创建支付订单 ### 创建支付订单
```go ```go
package main package main
func main() { func main() {
// 创建支付网关订单 // 创建支付网关订单
create, err := service.Pay().Create(ctx, payin.PayCreateInp{ create, err := service.Pay().Create(ctx, payin.PayCreateInp{
Subject: "充值100元", Subject: "充值100元",
OrderSn: "唯一业务订单编号", OrderSn: "唯一业务订单编号",
OrderGroup: "admin_order", // 订单分组,用于订单分类和绑定支付成功的回调方法 OrderGroup: "admin_order", // 订单分组,用于订单分类和绑定支付成功的回调方法
PayType: "wxpay", // 微信支付 PayType: "wxpay", // 微信支付
TradeType: "scan", // 二维码扫码 TradeType: "scan", // 二维码扫码
PayAmount: "100", // 100元 PayAmount: "100", // 100元
}) })
} }
``` ```
### 注册支付回调 ### 注册支付回调
- 在文件`server/internal/logic/pay/notify.go` 加入你的业务订单分组回调方法,当订单支付成功验签通过后会自动进行回调,参考以下: - 在文件`server/internal/logic/pay/notify.go` 加入你的业务订单分组回调方法,当订单支付成功验签通过后会自动进行回调,参考以下:
```go ```go
package global package global
import ( import (
"hotgo/internal/consts" "hotgo/internal/consts"
"hotgo/internal/library/payment" "hotgo/internal/library/payment"
"hotgo/internal/service" "hotgo/internal/service"
) )
// RegisterNotifyCall 注册支付成功回调方法 // RegisterNotifyCall 注册支付成功回调方法
func (s *sPay) RegisterNotifyCall() { func (s *sPay) RegisterNotifyCall() {
payment.RegisterNotifyCallMap(map[string]payment.NotifyCallFunc{ payment.RegisterNotifyCallMap(map[string]payment.NotifyCallFunc{
consts.OrderGroupAdminOrder: service.AdminOrder().PayNotify, // 后台充值订单 consts.OrderGroupAdminOrder: service.AdminOrder().PayNotify, // 后台充值订单
// ... // ...
}) })
} }
``` ```
### 订单退款 ### 订单退款
```go ```go
package main package main
func main() { func main() {
refund, err := service.PayRefund().Refund(ctx, payin.PayRefundInp{ refund, err := service.PayRefund().Refund(ctx, payin.PayRefundInp{
OrderSn: "唯一业务订单编号", OrderSn: "唯一业务订单编号",
RefundMoney: "退款金额", RefundMoney: "退款金额",
Reason: "买家申请退款原因", Reason: "买家申请退款原因",
Remark: "商家同意退款备注", Remark: "商家同意退款备注",
}) })
} }
``` ```
### 单笔转账(待更新) ### 单笔转账(待更新)
### 其他 ### 其他
- 由于各个支付平台中交易方式较多如果目前已有的交易方式不满足你的支付场景需求请自行参考gopay文档对文件`server/internal/library/payment/payment.go`中的`PayClient`接口实现进行扩展 - 由于各个支付平台中交易方式较多如果目前已有的交易方式不满足你的支付场景需求请自行参考gopay文档对文件`server/internal/library/payment/payment.go`中的`PayClient`接口实现进行扩展
- gopay文档地址https://github.com/go-pay/gopay - gopay文档地址https://github.com/go-pay/gopay

View File

@ -1,203 +1,203 @@
## 消息队列 ## 消息队列
目录 目录
- 配置文件 - 配置文件
- 实现接口 - 实现接口
- 一个例子 - 一个例子
- 控制台 - 控制台
- 自定义队列驱动 - 自定义队列驱动
> 系统默认的队列驱动为disk(磁盘队列)目前已支持disk、redis、rocketmq、kafka等多种驱动。请自行选择适合你的驱动使用。 > 系统默认的队列驱动为disk(磁盘队列)目前已支持disk、redis、rocketmq、kafka等多种驱动。请自行选择适合你的驱动使用。
### 配置文件 ### 配置文件
- 配置文件server/manifest/config/config.yaml - 配置文件server/manifest/config/config.yaml
```yaml ```yaml
# 消息队列 # 消息队列
queue: queue:
switch: true # 队列开关可选true|false默认为true switch: true # 队列开关可选true|false默认为true
driver: "disk" # 队列驱动可选disk|redis|rocketmq|kafka默认为disk driver: "disk" # 队列驱动可选disk|redis|rocketmq|kafka默认为disk
groupName: "hotgo" # mq群组名称 groupName: "hotgo" # mq群组名称
# 磁盘队列 # 磁盘队列
disk: disk:
path: "./storage/diskqueue" # 数据存放路径 path: "./storage/diskqueue" # 数据存放路径
batchSize: 100 # 每100条消息同步一次batchSize和batchTime满足其一就会同步一次 batchSize: 100 # 每100条消息同步一次batchSize和batchTime满足其一就会同步一次
batchTime: 1 # 每1秒消息同步一次 batchTime: 1 # 每1秒消息同步一次
segmentSize: 10485760 # 每个topic分片数据文件最大字节默认10M segmentSize: 10485760 # 每个topic分片数据文件最大字节默认10M
segmentLimit: 3000 # 每个topic最大分片数据文件数量超出部分将会丢弃 segmentLimit: 3000 # 每个topic最大分片数据文件数量超出部分将会丢弃
# redis默认使用全局redis运行队列 # redis默认使用全局redis运行队列
redis: redis:
timeout: 0 # 队列超时时间以秒为单位0表示永不超时。如果队列在设定的超时时间内没有被消费则会被销毁 timeout: 0 # 队列超时时间以秒为单位0表示永不超时。如果队列在设定的超时时间内没有被消费则会被销毁
rocketmq: rocketmq:
nameSrvAdders: ["127.0.0.1:9876"] # nameSrvAdder+端口,支持多个 nameSrvAdders: ["127.0.0.1:9876"] # nameSrvAdder+端口,支持多个
accessKey: "" # 选填。如果开启了acl控制就必填 accessKey: "" # 选填。如果开启了acl控制就必填
secretKey: "" # 选填。如果开启了acl控制就必填 secretKey: "" # 选填。如果开启了acl控制就必填
brokerAddr: "127.0.0.1:10911" # brokerAddr+端口选填。用于消费者订阅主题前会检查主题是否存在不存在会自动创建。你也可以在rocketmq控制台手动创建主题 brokerAddr: "127.0.0.1:10911" # brokerAddr+端口选填。用于消费者订阅主题前会检查主题是否存在不存在会自动创建。你也可以在rocketmq控制台手动创建主题
retry: 0 # 重试次数 retry: 0 # 重试次数
logLevel: "info" # 系统日志级别可选all|close|debug|info|warn|error|fatal logLevel: "info" # 系统日志级别可选all|close|debug|info|warn|error|fatal
kafka: kafka:
address: "127.0.0.1:9092" # kafka地址+端口 address: "127.0.0.1:9092" # kafka地址+端口
version: "2.0.0.0" # kafka专属配置默认2.0.0.0 version: "2.0.0.0" # kafka专属配置默认2.0.0.0
randClient: true # 开启随机生成clientID可以实现启动多实例同时一起消费相同topic加速消费能力的特性默认为true randClient: true # 开启随机生成clientID可以实现启动多实例同时一起消费相同topic加速消费能力的特性默认为true
multiConsumer: true # 是否支持创建多个消费者 multiConsumer: true # 是否支持创建多个消费者
``` ```
### 实现接口 ### 实现接口
- 为了提供高度的扩展性,消费队列在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用消费队列消费功能,从而实现更大的灵活性和可扩展性。 - 为了提供高度的扩展性,消费队列在设计上采用了接口化的思路。只需要实现以下接口,您就可以在任何地方注册和使用消费队列消费功能,从而实现更大的灵活性和可扩展性。
```go ```go
// Consumer 消费者接口,实现该接口即可加入到消费队列中 // Consumer 消费者接口,实现该接口即可加入到消费队列中
type Consumer interface { type Consumer interface {
GetTopic() string // 获取消费主题 GetTopic() string // 获取消费主题
Handle(ctx context.Context, mqMsg MqMsg) (err error) // 处理消息的方法 Handle(ctx context.Context, mqMsg MqMsg) (err error) // 处理消息的方法
} }
``` ```
### 一个例子 ### 一个例子
每个被发送到队列的消息应该被定义为一个单独的文件结构。 每个被发送到队列的消息应该被定义为一个单独的文件结构。
例如,如果您需要异步记录系统日志,内容大致如下: 例如,如果您需要异步记录系统日志,内容大致如下:
- 文件路径server/internal/queues/sys_log.go - 文件路径server/internal/queues/sys_log.go
```go ```go
package queues package queues
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"hotgo/internal/consts" "hotgo/internal/consts"
"hotgo/internal/library/queue" "hotgo/internal/library/queue"
"hotgo/internal/model/entity" "hotgo/internal/model/entity"
"hotgo/internal/service" "hotgo/internal/service"
) )
func init() { func init() {
queue.RegisterConsumer(SysLog) queue.RegisterConsumer(SysLog)
} }
// SysLog 系统日志 // SysLog 系统日志
var SysLog = &qSysLog{} var SysLog = &qSysLog{}
type qSysLog struct{} type qSysLog struct{}
// GetTopic 主题 // GetTopic 主题
func (q *qSysLog) GetTopic() string { func (q *qSysLog) GetTopic() string {
return consts.QueueLogTopic return consts.QueueLogTopic
} }
// Handle 处理消息 // Handle 处理消息
func (q *qSysLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) { func (q *qSysLog) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
var data entity.SysLog var data entity.SysLog
if err = json.Unmarshal(mqMsg.Body, &data); err != nil { if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
return err return err
} }
return service.SysLog().RealWrite(ctx, data) return service.SysLog().RealWrite(ctx, data)
} }
``` ```
下面是将消息添加到队列的方式,大概内容如下: 下面是将消息添加到队列的方式,大概内容如下:
```go ```go
package main package main
import ( import (
"fmt" "fmt"
"hotgo/internal/consts" "hotgo/internal/consts"
"hotgo/internal/library/queue" "hotgo/internal/library/queue"
"hotgo/internal/model/entity" "hotgo/internal/model/entity"
) )
func test() { func test() {
data := &entity.SysLog{ data := &entity.SysLog{
//... //...
} }
if err := queue.Push(consts.QueueLogTopic, data); err != nil { if err := queue.Push(consts.QueueLogTopic, data); err != nil {
fmt.Printf("queue.Push err:%+v", err) fmt.Printf("queue.Push err:%+v", err)
} }
} }
``` ```
延迟队列目前只有redis、rocketmq驱动支持: 延迟队列目前只有redis、rocketmq驱动支持:
```go ```go
package main package main
import ( import (
"fmt" "fmt"
"hotgo/internal/consts" "hotgo/internal/consts"
"hotgo/internal/library/queue" "hotgo/internal/library/queue"
"hotgo/internal/model/entity" "hotgo/internal/model/entity"
) )
func test() { func test() {
data := &entity.SysLog{ data := &entity.SysLog{
//... //...
} }
// redis 延迟10秒 // redis 延迟10秒
if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 10); err != nil { if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 10); err != nil {
fmt.Printf("queue.Push err:%+v", err) fmt.Printf("queue.Push err:%+v", err)
} }
// rocketmq 延迟5秒 // rocketmq 延迟5秒
// 注意rocketmq这里传入的是延迟级别而不是秒 // 注意rocketmq这里传入的是延迟级别而不是秒
// 消息的延时级别level一共有18级分别为 // 消息的延时级别level一共有18级分别为
// 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h // 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
// 参考https://github.com/apache/rocketmq-client-go/blob/0e19ee654819bda396a08d950c883f9008b8222b/primitive/message.go#L174 // 参考https://github.com/apache/rocketmq-client-go/blob/0e19ee654819bda396a08d950c883f9008b8222b/primitive/message.go#L174
if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 2); err != nil { if err := queue.SendDelayMsg(consts.QueueLogTopic, data, 2); err != nil {
fmt.Printf("queue.Push err:%+v", err) fmt.Printf("queue.Push err:%+v", err)
} }
} }
``` ```
### 控制台 ### 控制台
控制台用于处理队列消息,即消费者。 控制台用于处理队列消息,即消费者。
相关命令请参考: [控制台](sys-console.md) 相关命令请参考: [控制台](sys-console.md)
### 自定义队列驱动 ### 自定义队列驱动
只需实现消息队列的生成者和消费者接口,并加入到初始化中进行相应调用即可。 只需实现消息队列的生成者和消费者接口,并加入到初始化中进行相应调用即可。
- 接口片段server/internal/library/queue/init.go - 接口片段server/internal/library/queue/init.go
```go ```go
package queue package queue
import ( import (
"time" "time"
) )
type MqMsg struct { type MqMsg struct {
RunType int `json:"run_type"` RunType int `json:"run_type"`
Topic string `json:"topic"` Topic string `json:"topic"`
MsgId string `json:"msg_id"` MsgId string `json:"msg_id"`
Offset int64 `json:"offset"` Offset int64 `json:"offset"`
Partition int32 `json:"partition"` Partition int32 `json:"partition"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
Body []byte `json:"body"` Body []byte `json:"body"`
} }
type MqProducer interface { type MqProducer interface {
SendMsg(topic string, body string) (mqMsg MqMsg, err error) SendMsg(topic string, body string) (mqMsg MqMsg, err error)
SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error) SendByteMsg(topic string, body []byte) (mqMsg MqMsg, err error)
SendDelayMsg(topic string, body string, delay int64) (mqMsg MqMsg, err error) SendDelayMsg(topic string, body string, delay int64) (mqMsg MqMsg, err error)
} }
type MqConsumer interface { type MqConsumer interface {
ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error) ListenReceiveMsgDo(topic string, receiveDo func(mqMsg MqMsg)) (err error)
} }
``` ```
将实现过接口MqProducer、MqConsumer的实例方法分别加入到NewProducer、NewConsumer中进行相应调用即可。 将实现过接口MqProducer、MqConsumer的实例方法分别加入到NewProducer、NewConsumer中进行相应调用即可。

View File

@ -1,271 +1,271 @@
## TCP服务器 ## TCP服务器
目录 目录
- 配置文件 - 配置文件
- 一个基本的消息收发例子 - 一个基本的消息收发例子
- 注册路由 - 注册路由
- 拦截器 - 拦截器
- 服务认证 - 服务认证
- 更多 - 更多
> HotGo基于GoFrame的TCP服务器组件提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等大大简化和规范了服务器开发流程。 > HotGo基于GoFrame的TCP服务器组件提供了一个简单而灵活的方式快速搭建基于TCP的服务应用。集成了许多常用功能如长连接、服务认证、路由分发、RPC消息、拦截器和数据绑定等大大简化和规范了服务器开发流程。
### 配置文件 ### 配置文件
- 配置文件server/manifest/config/config.yaml - 配置文件server/manifest/config/config.yaml
```yaml ```yaml
tcp: tcp:
# 服务器 # 服务器
server: server:
address: ":8099" address: ":8099"
# 客户端 # 客户端
client: client:
# 定时任务 # 定时任务
cron: cron:
group: "cron" # 分组名称 group: "cron" # 分组名称
name: "cron1" # 客户端名称 name: "cron1" # 客户端名称
address: "127.0.0.1:8099" # 服务器地址 address: "127.0.0.1:8099" # 服务器地址
appId: "1002" # 应用名称 appId: "1002" # 应用名称
secretKey: "hotgo" # 密钥 secretKey: "hotgo" # 密钥
# 系统授权 # 系统授权
auth: auth:
group: "auth" # 分组名称 group: "auth" # 分组名称
name: "auth1" # 客户端名称 name: "auth1" # 客户端名称
address: "127.0.0.1:8099" # 服务器地址 address: "127.0.0.1:8099" # 服务器地址
appId: "mengshuai" # 应用名称 appId: "mengshuai" # 应用名称
secretKey: "123456" # 密钥 secretKey: "123456" # 密钥
``` ```
- 可以看到,除了服务器配置外,还有两个客户端配置`cron``auth` - 可以看到,除了服务器配置外,还有两个客户端配置`cron``auth`
- `cron`是HotGo内置的定时任务服务和http服务通过RPC通讯以实现和后台交互使其可以独立、集群部署。 - `cron`是HotGo内置的定时任务服务和http服务通过RPC通讯以实现和后台交互使其可以独立、集群部署。
- `auth`可以为第三方平台提供授权服务。如果你需要他,可以将它部署在第三方程序中,在重要的位置进行授权验证。 - `auth`可以为第三方平台提供授权服务。如果你需要他,可以将它部署在第三方程序中,在重要的位置进行授权验证。
### 一个基本的消息收发测试用例 ### 一个基本的消息收发测试用例
- 文件路径server/internal/library/network/tcp/tcp_example_test.go - 文件路径server/internal/library/network/tcp/tcp_example_test.go
```go ```go
package tcp_test package tcp_test
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/test/gtest" "github.com/gogf/gf/v2/test/gtest"
"hotgo/internal/library/network/tcp" "hotgo/internal/library/network/tcp"
"testing" "testing"
"time" "time"
) )
var T *testing.T // 声明一个全局的 *testing.T 变量 var T *testing.T // 声明一个全局的 *testing.T 变量
type TestMsgReq struct { type TestMsgReq struct {
Name string `json:"name"` Name string `json:"name"`
} }
type TestMsgRes struct { type TestMsgRes struct {
tcp.ServerRes tcp.ServerRes
} }
type TestRPCMsgReq struct { type TestRPCMsgReq struct {
Name string `json:"name"` Name string `json:"name"`
} }
type TestRPCMsgRes struct { type TestRPCMsgRes struct {
tcp.ServerRes tcp.ServerRes
} }
func onTestMsg(ctx context.Context, req *TestMsgReq) { func onTestMsg(ctx context.Context, req *TestMsgReq) {
fmt.Printf("服务器收到消息 ==> onTestMsg:%+v\n", req) fmt.Printf("服务器收到消息 ==> onTestMsg:%+v\n", req)
conn := tcp.ConnFromCtx(ctx) conn := tcp.ConnFromCtx(ctx)
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNE(conn, nil) t.AssertNE(conn, nil)
}) })
res := new(TestMsgRes) res := new(TestMsgRes)
res.Message = fmt.Sprintf("你的名字:%v", req.Name) res.Message = fmt.Sprintf("你的名字:%v", req.Name)
conn.Send(ctx, res) conn.Send(ctx, res)
} }
func onResponseTestMsg(ctx context.Context, req *TestMsgRes) { func onResponseTestMsg(ctx context.Context, req *TestMsgRes) {
fmt.Printf("客户端收到响应消息 ==> TestMsgRes:%+v\n", req) fmt.Printf("客户端收到响应消息 ==> TestMsgRes:%+v\n", req)
err := req.GetError() err := req.GetError()
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNil(err) t.AssertNil(err)
}) })
} }
func onTestRPCMsg(ctx context.Context, req *TestRPCMsgReq) (res *TestRPCMsgRes, err error) { func onTestRPCMsg(ctx context.Context, req *TestRPCMsgReq) (res *TestRPCMsgRes, err error) {
fmt.Printf("服务器收到消息 ==> onTestRPCMsg:%+v\n", req) fmt.Printf("服务器收到消息 ==> onTestRPCMsg:%+v\n", req)
res = new(TestRPCMsgRes) res = new(TestRPCMsgRes)
res.Message = fmt.Sprintf("你的名字:%v", req.Name) res.Message = fmt.Sprintf("你的名字:%v", req.Name)
return return
} }
func startTCPServer() { func startTCPServer() {
serv := tcp.NewServer(&tcp.ServerConfig{ serv := tcp.NewServer(&tcp.ServerConfig{
Name: "hotgo", Name: "hotgo",
Addr: ":8002", Addr: ":8002",
}) })
// 注册路由 // 注册路由
serv.RegisterRouter( serv.RegisterRouter(
onTestMsg, onTestMsg,
) )
// 注册RPC路由 // 注册RPC路由
serv.RegisterRPCRouter( serv.RegisterRPCRouter(
onTestRPCMsg, onTestRPCMsg,
) )
// 服务监听 // 服务监听
err := serv.Listen() err := serv.Listen()
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNil(err) t.AssertNil(err)
}) })
} }
// 一个基本的消息收发 // 一个基本的消息收发
func TestSendMsg(t *testing.T) { func TestSendMsg(t *testing.T) {
T = t T = t
go startTCPServer() go startTCPServer()
ctx := gctx.New() ctx := gctx.New()
client := tcp.NewClient(&tcp.ClientConfig{ client := tcp.NewClient(&tcp.ClientConfig{
Addr: "127.0.0.1:8002", Addr: "127.0.0.1:8002",
}) })
// 注册路由 // 注册路由
client.RegisterRouter( client.RegisterRouter(
onResponseTestMsg, onResponseTestMsg,
) )
go func() { go func() {
err := client.Start() err := client.Start()
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNil(err) t.AssertNil(err)
}) })
}() }()
// 确保服务都启动完成 // 确保服务都启动完成
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
// 拿到客户端的连接 // 拿到客户端的连接
conn := client.Conn() conn := client.Conn()
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNE(conn, nil) t.AssertNE(conn, nil)
}) })
// 向服务器发送tcp消息不会阻塞程序执行 // 向服务器发送tcp消息不会阻塞程序执行
err := conn.Send(ctx, &TestMsgReq{Name: "Tom"}) err := conn.Send(ctx, &TestMsgReq{Name: "Tom"})
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNil(err) t.AssertNil(err)
}) })
// 向服务器发送rpc消息会等待服务器响应结果直到拿到结果或响应超时才会继续 // 向服务器发送rpc消息会等待服务器响应结果直到拿到结果或响应超时才会继续
var res TestRPCMsgRes var res TestRPCMsgRes
if err = conn.RequestScan(ctx, &TestRPCMsgReq{Name: "Tony"}, &res); err != nil { if err = conn.RequestScan(ctx, &TestRPCMsgReq{Name: "Tony"}, &res); err != nil {
gtest.C(T, func(t *gtest.T) { gtest.C(T, func(t *gtest.T) {
t.AssertNil(err) t.AssertNil(err)
}) })
} }
fmt.Printf("客户端收到RPC消息响应 ==> TestRPCMsgRes:%+v\n", res) fmt.Printf("客户端收到RPC消息响应 ==> TestRPCMsgRes:%+v\n", res)
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
} }
``` ```
### 注册路由 ### 注册路由
- 从上面的例子可以看到不管是普通TCP消息和RPC消息的请求/响应结构体都采用类似GF框架的规范路由的结构请求`XxxRes`/响应`XxxRes`的格式,是不是很亲切? - 从上面的例子可以看到不管是普通TCP消息和RPC消息的请求/响应结构体都采用类似GF框架的规范路由的结构请求`XxxRes`/响应`XxxRes`的格式,是不是很亲切?
### 拦截器 ### 拦截器
- 不管是服务端还是客户端,在初始化时都可以注册多个拦截器来满足更多场景的服务开发,下面是一个使用例子: - 不管是服务端还是客户端,在初始化时都可以注册多个拦截器来满足更多场景的服务开发,下面是一个使用例子:
```go ```go
package main package main
import ( import (
"context" "context"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"hotgo/internal/library/network/tcp" "hotgo/internal/library/network/tcp"
) )
func main() { func main() {
serv = tcp.NewServer(&tcp.ServerConfig{ serv = tcp.NewServer(&tcp.ServerConfig{
Name: "hotgo", Name: "hotgo",
Addr: ":8002", Addr: ":8002",
}) })
// 注册拦截器 // 注册拦截器
// 执行顺序是从前到后即Interceptor -> Interceptor2 -> Interceptor3。如果中间有任意一个抛出错误则会中断后续处理 // 执行顺序是从前到后即Interceptor -> Interceptor2 -> Interceptor3。如果中间有任意一个抛出错误则会中断后续处理
serv.RegisterInterceptor(Interceptor, Interceptor2, Interceptor3) serv.RegisterInterceptor(Interceptor, Interceptor2, Interceptor3)
// 服务监听 // 服务监听
if err := serv.Listen(); err != nil { if err := serv.Listen(); err != nil {
if !serv.IsClose() { if !serv.IsClose() {
g.Log().Warningf(ctx, "TCPServer Listen err:%v", err) g.Log().Warningf(ctx, "TCPServer Listen err:%v", err)
} }
} }
} }
func Interceptor(ctx context.Context, msg *tcp.Message) (err error) { func Interceptor(ctx context.Context, msg *tcp.Message) (err error) {
// 可以在拦截器中通过上下文拿到连接 // 可以在拦截器中通过上下文拿到连接
conn := tcp.ConnFromCtx(ctx) conn := tcp.ConnFromCtx(ctx)
// 拿到原始请求消息 // 拿到原始请求消息
g.Dump(msg) g.Dump(msg)
// 如果想要中断后续处理只需返回一个错误即可,但注意两种情况 // 如果想要中断后续处理只需返回一个错误即可,但注意两种情况
// tcp消息如果你还想对该消息进行回复应在拦截器中进行处理例如conn.Send(ctx, 回复消息内容) // tcp消息如果你还想对该消息进行回复应在拦截器中进行处理例如conn.Send(ctx, 回复消息内容)
// rpc消息返回一个错误后系统会将错误自动回复到rpc响应中无需单独处理 // rpc消息返回一个错误后系统会将错误自动回复到rpc响应中无需单独处理
return return
} }
func Interceptor2(ctx context.Context, msg *tcp.Message) (err error) { func Interceptor2(ctx context.Context, msg *tcp.Message) (err error) {
// ... // ...
return return
} }
func Interceptor3(ctx context.Context, msg *tcp.Message) (err error) { func Interceptor3(ctx context.Context, msg *tcp.Message) (err error) {
// ... // ...
return return
} }
``` ```
### 服务认证 ### 服务认证
- 一般情况下,建议客户端连接到服务器时都通过`授权许可证`的方式进行登录认证,当初始化客户端配置认证数据时,连接成功后会自动进行登录认证。 - 一般情况下,建议客户端连接到服务器时都通过`授权许可证`的方式进行登录认证,当初始化客户端配置认证数据时,连接成功后会自动进行登录认证。
```go ```go
// 创建客户端配置 // 创建客户端配置
clientConfig := &tcp.ClientConfig{ clientConfig := &tcp.ClientConfig{
Addr: "127.0.0.1:8002", Addr: "127.0.0.1:8002",
AutoReconnect: true, AutoReconnect: true,
// 认证数据 // 认证数据
// 认证数据可以在后台-系统监控-在线服务-许可证列表中添加,同一个授权支持多个服务使用,但多个服务不能使用相同的名称进行连接 // 认证数据可以在后台-系统监控-在线服务-许可证列表中添加,同一个授权支持多个服务使用,但多个服务不能使用相同的名称进行连接
Auth: &tcp.AuthMeta{ Auth: &tcp.AuthMeta{
Name: "服务名称", Name: "服务名称",
Group: "服务分组", Group: "服务分组",
AppId: "APPID", AppId: "APPID",
SecretKey: "SecretKey", SecretKey: "SecretKey",
}, },
} }
// 初始化客户端 // 初始化客户端
client = tcp.NewClient(clientConfig) client = tcp.NewClient(clientConfig)
``` ```
### 更多 ### 更多
TCP服务器源码路径server/internal/library/network/tcp TCP服务器源码路径server/internal/library/network/tcp
更多文档请参考https://goframe.org/pages/viewpage.action?pageId=1114625 更多文档请参考https://goframe.org/pages/viewpage.action?pageId=1114625

View File

@ -1,3 +1,3 @@
## 单元测试 ## 单元测试
请参考https://goframe.org/pages/viewpage.action?pageId=1114153 请参考https://goframe.org/pages/viewpage.action?pageId=1114153

View File

@ -1,20 +1,20 @@
## 工具方法 ## 工具方法
HotGo还提供一些系统中常用的工具库方法在这里简单说明 HotGo还提供一些系统中常用的工具库方法在这里简单说明
``` ```
/server /server
├── utility ├── utility
│ ├── charset # 字符串处理 │ ├── charset # 字符串处理
│ ├── convert # 数据类型转换 │ ├── convert # 数据类型转换
│ ├── encrypt # 数据加密/解密 │ ├── encrypt # 数据加密/解密
│ ├── excel # 电子表格导出/导入 │ ├── excel # 电子表格导出/导入
│ ├── file # 文件/目录处理 │ ├── file # 文件/目录处理
│ ├── format # 数据格式化 │ ├── format # 数据格式化
│ ├── simple # 一些简捷函数 │ ├── simple # 一些简捷函数
│ ├── tree # 树形结构 │ ├── tree # 树形结构
│ ├── url # URL处理 │ ├── url # URL处理
│ ├── useragent # 请求头代理处理 │ ├── useragent # 请求头代理处理
└── └── validate # 数据验证 └── └── validate # 数据验证
``` ```

View File

@ -1,3 +1,3 @@
## WebHook ## WebHook
请参考https://goframe.org/pages/viewpage.action?pageId=1114387 请参考https://goframe.org/pages/viewpage.action?pageId=1114387

View File

@ -1,287 +1,287 @@
## This file contains all available configuration options ## This file contains all available configuration options
## with their default values. ## with their default values.
# See https://github.com/golangci/golangci-lint#config-file # See https://github.com/golangci/golangci-lint#config-file
# See https://golangci-lint.run/usage/configuration/ # See https://golangci-lint.run/usage/configuration/
# Options for analysis running. # Options for analysis running.
run: run:
# Exit code when at least one issue was found. # Exit code when at least one issue was found.
# Default: 1 # Default: 1
issues-exit-code: 2 issues-exit-code: 2
# Include test files or not. # Include test files or not.
# Default: true # Default: true
tests: false tests: false
go: "1.20" go: "1.20"
# Which dirs to skip: issues from them won't be reported. # Which dirs to skip: issues from them won't be reported.
# Can use regexp here: `generated.*`, regexp is applied on full path. # Can use regexp here: `generated.*`, regexp is applied on full path.
# Default value is empty list, # Default value is empty list,
# but default dirs are skipped independently of this option's value (see skip-dirs-use-default). # but default dirs are skipped independently of this option's value (see skip-dirs-use-default).
# "/" will be replaced by current OS file path separator to properly work on Windows. # "/" will be replaced by current OS file path separator to properly work on Windows.
# skip-dirs: # skip-dirs:
# - internal/library/hggen/internal # - internal/library/hggen/internal
# Which files to skip: they will be analyzed, but issues from them won't be reported. # Which files to skip: they will be analyzed, but issues from them won't be reported.
# Default value is empty list, # Default value is empty list,
# but there is no need to include all autogenerated files, # but there is no need to include all autogenerated files,
# we confidently recognize autogenerated files. # we confidently recognize autogenerated files.
# If it's not please let us know. # If it's not please let us know.
# "/" will be replaced by current OS file path separator to properly work on Windows. # "/" will be replaced by current OS file path separator to properly work on Windows.
# skip-files: [] # skip-files: []
# Main linters configurations. # Main linters configurations.
# See https://golangci-lint.run/usage/linters # See https://golangci-lint.run/usage/linters
linters: linters:
# Disable all default enabled linters. # Disable all default enabled linters.
disable-all: true disable-all: true
# Custom enable linters we want to use. # Custom enable linters we want to use.
enable: enable:
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. - errcheck # Errcheck is a program for checking for unchecked errors in go programs.
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted. - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted.
- funlen # Tool for detection of long functions - funlen # Tool for detection of long functions
- goconst # Finds repeated strings that could be replaced by a constant - goconst # Finds repeated strings that could be replaced by a constant
- gocritic # Provides diagnostics that check for bugs, performance and style issues. - gocritic # Provides diagnostics that check for bugs, performance and style issues.
# - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification # - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- gosimple # Linter for Go source code that specializes in simplifying code - gosimple # Linter for Go source code that specializes in simplifying code
- govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string
- misspell # Finds commonly misspelled English words in comments - misspell # Finds commonly misspelled English words in comments
- nolintlint # Reports ill-formed or insufficient nolint directives - nolintlint # Reports ill-formed or insufficient nolint directives
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint.
- staticcheck # It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. - staticcheck # It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary.
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. - usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library.
- whitespace # Tool for detection of leading and trailing whitespace - whitespace # Tool for detection of leading and trailing whitespace
issues: issues:
exclude-rules: exclude-rules:
# helpers in tests often (rightfully) pass a *testing.T as their first argument # helpers in tests often (rightfully) pass a *testing.T as their first argument
- path: _test\.go - path: _test\.go
text: "context.Context should be the first parameter of a function" text: "context.Context should be the first parameter of a function"
linters: linters:
- revive - revive
# Yes, they are, but it's okay in a test # Yes, they are, but it's okay in a test
- path: _test\.go - path: _test\.go
text: "exported func.*returns unexported type.*which can be annoying to use" text: "exported func.*returns unexported type.*which can be annoying to use"
linters: linters:
- revive - revive
# https://github.com/go-critic/go-critic/issues/926 # https://github.com/go-critic/go-critic/issues/926
- linters: - linters:
- gocritic - gocritic
text: "unnecessaryDefer:" text: "unnecessaryDefer:"
exclude-dirs: exclude-dirs:
- internal/library/hggen/internal - internal/library/hggen/internal
exclude-files: [] exclude-files: []
# https://golangci-lint.run/usage/linters # https://golangci-lint.run/usage/linters
linters-settings: linters-settings:
# https://golangci-lint.run/usage/linters/#misspell # https://golangci-lint.run/usage/linters/#misspell
misspell: misspell:
locale: US locale: US
ignore-words: ignore-words:
- cancelled - cancelled
# https://golangci-lint.run/usage/linters/#revive # https://golangci-lint.run/usage/linters/#revive
# https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md # https://github.com/mgechev/revive/blob/master/RULES_DESCRIPTIONS.md
revive: revive:
ignore-generated-header: true ignore-generated-header: true
severity: error severity: error
rules: rules:
- name: atomic - name: atomic
- name: line-length-limit - name: line-length-limit
severity: error severity: error
arguments: [ 1024 ] arguments: [ 1024 ]
- name: unhandled-error - name: unhandled-error
severity: warning severity: warning
disabled: true disabled: true
arguments: [] arguments: []
- name: var-naming - name: var-naming
severity: warning severity: warning
disabled: true disabled: true
arguments: arguments:
# AllowList # AllowList
- [ "ID","URL","IP","HTTP","JSON","API","UID","Id","Api","Uid","Http","Json","Ip","Url" ] - [ "ID","URL","IP","HTTP","JSON","API","UID","Id","Api","Uid","Http","Json","Ip","Url" ]
# DenyList # DenyList
- [ "VM" ] - [ "VM" ]
- name: string-format - name: string-format
severity: warning severity: warning
disabled: false disabled: false
arguments: arguments:
- - 'core.WriteError[1].Message' - - 'core.WriteError[1].Message'
- '/^([^A-Z]|$)/' - '/^([^A-Z]|$)/'
- must not start with a capital letter - must not start with a capital letter
- - 'fmt.Errorf[0]' - - 'fmt.Errorf[0]'
- '/(^|[^\.!?])$/' - '/(^|[^\.!?])$/'
- must not end in punctuation - must not end in punctuation
- - panic - - panic
- '/^[^\n]*$/' - '/^[^\n]*$/'
- must not contain line breaks - must not contain line breaks
- name: function-result-limit - name: function-result-limit
severity: warning severity: warning
disabled: false disabled: false
arguments: [ 4 ] arguments: [ 4 ]
# https://golangci-lint.run/usage/linters/#funlen # https://golangci-lint.run/usage/linters/#funlen
funlen: funlen:
# Checks the number of lines in a function. # Checks the number of lines in a function.
# If lower than 0, disable the check. # If lower than 0, disable the check.
# Default: 60 # Default: 60
lines: 330 lines: 330
# Checks the number of statements in a function. # Checks the number of statements in a function.
# If lower than 0, disable the check. # If lower than 0, disable the check.
# Default: 40 # Default: 40
statements: -1 statements: -1
# https://golangci-lint.run/usage/linters/#goconst # https://golangci-lint.run/usage/linters/#goconst
goconst: goconst:
# Minimal length of string constant. # Minimal length of string constant.
# Default: 3 # Default: 3
min-len: 4 min-len: 4
# Minimum occurrences of constant string count to trigger issue. # Minimum occurrences of constant string count to trigger issue.
# Default: 3 # Default: 3
# For subsequent optimization, the value is reduced. # For subsequent optimization, the value is reduced.
min-occurrences: 30 min-occurrences: 30
# Ignore test files. # Ignore test files.
# Default: false # Default: false
ignore-tests: true ignore-tests: true
# Look for existing constants matching the values. # Look for existing constants matching the values.
# Default: true # Default: true
match-constant: false match-constant: false
# Search also for duplicated numbers. # Search also for duplicated numbers.
# Default: false # Default: false
numbers: true numbers: true
# Minimum value, only works with goconst.numbers # Minimum value, only works with goconst.numbers
# Default: 3 # Default: 3
min: 5 min: 5
# Maximum value, only works with goconst.numbers # Maximum value, only works with goconst.numbers
# Default: 3 # Default: 3
max: 20 max: 20
# Ignore when constant is not used as function argument. # Ignore when constant is not used as function argument.
# Default: true # Default: true
ignore-calls: false ignore-calls: false
# https://golangci-lint.run/usage/linters/#gocritic # https://golangci-lint.run/usage/linters/#gocritic
gocritic: gocritic:
disabled-checks: disabled-checks:
- ifElseChain - ifElseChain
- assignOp - assignOp
- appendAssign - appendAssign
- singleCaseSwitch - singleCaseSwitch
- regexpMust - regexpMust
- typeSwitchVar - typeSwitchVar
- elseif - elseif
# https://golangci-lint.run/usage/linters/#gosimple # https://golangci-lint.run/usage/linters/#gosimple
gosimple: gosimple:
# Select the Go version to target. # Select the Go version to target.
# Default: 1.13 # Default: 1.13
# Deprecated: use the global `run.go` instead. # Deprecated: use the global `run.go` instead.
# go: "1.15" # go: "1.15"
# Sxxxx checks in https://staticcheck.io/docs/configuration/options/#checks # Sxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"] # Default: ["*"]
checks: [ checks: [
"all", "-S1000", "-S1001", "-S1002", "-S1008", "-S1009", "-S1016", "-S1023", "-S1025", "-S1029", "-S1034", "-S1040" "all", "-S1000", "-S1001", "-S1002", "-S1008", "-S1009", "-S1016", "-S1023", "-S1025", "-S1029", "-S1034", "-S1040"
] ]
# https://golangci-lint.run/usage/linters/#govet # https://golangci-lint.run/usage/linters/#govet
govet: govet:
# Report about shadowed variables. # Report about shadowed variables.
# Default: false # Default: false
# check-shadowing: true # check-shadowing: true
# Settings per analyzer. # Settings per analyzer.
settings: settings:
# Analyzer name, run `go tool vet help` to see all analyzers. # Analyzer name, run `go tool vet help` to see all analyzers.
printf: printf:
# Comma-separated list of print function names to check (in addition to default, see `go tool vet help printf`). # Comma-separated list of print function names to check (in addition to default, see `go tool vet help printf`).
# Default: [] # Default: []
funcs: funcs:
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
# shadow: # shadow:
# Whether to be strict about shadowing; can be noisy. # Whether to be strict about shadowing; can be noisy.
# Default: false # Default: false
# strict: false # strict: false
unusedresult: unusedresult:
# Comma-separated list of functions whose results must be used # Comma-separated list of functions whose results must be used
# (in addition to defaults context.WithCancel,context.WithDeadline,context.WithTimeout,context.WithValue, # (in addition to defaults context.WithCancel,context.WithDeadline,context.WithTimeout,context.WithValue,
# errors.New,fmt.Errorf,fmt.Sprint,fmt.Sprintf,sort.Reverse) # errors.New,fmt.Errorf,fmt.Sprint,fmt.Sprintf,sort.Reverse)
# Default [] # Default []
funcs: funcs:
- pkg.MyFunc - pkg.MyFunc
- context.WithCancel - context.WithCancel
# Comma-separated list of names of methods of type func() string whose results must be used # Comma-separated list of names of methods of type func() string whose results must be used
# (in addition to default Error,String) # (in addition to default Error,String)
# Default [] # Default []
stringmethods: stringmethods:
- MyMethod - MyMethod
# Enable all analyzers. # Enable all analyzers.
# Default: false # Default: false
enable-all: true enable-all: true
# Disable analyzers by name. # Disable analyzers by name.
# Run `go tool vet help` to see all analyzers. # Run `go tool vet help` to see all analyzers.
# Default: [] # Default: []
disable: disable:
- asmdecl - asmdecl
- assign - assign
- atomic - atomic
- atomicalign - atomicalign
- bools - bools
- buildtag - buildtag
- cgocall - cgocall
- composites - composites
- copylocks - copylocks
- deepequalerrors - deepequalerrors
- errorsas - errorsas
- fieldalignment - fieldalignment
- findcall - findcall
- framepointer - framepointer
- httpresponse - httpresponse
- ifaceassert - ifaceassert
- loopclosure - loopclosure
- lostcancel - lostcancel
- nilfunc - nilfunc
- nilness - nilness
- reflectvaluecompare - reflectvaluecompare
- shift - shift
- shadow - shadow
- sigchanyzer - sigchanyzer
- sortslice - sortslice
- stdmethods - stdmethods
- stringintconv - stringintconv
- structtag - structtag
- testinggoroutine - testinggoroutine
- tests - tests
- unmarshal - unmarshal
- unreachable - unreachable
- unsafeptr - unsafeptr
- unusedwrite - unusedwrite
# https://golangci-lint.run/usage/linters/#staticcheck # https://golangci-lint.run/usage/linters/#staticcheck
staticcheck: staticcheck:
# Select the Go version to target. # Select the Go version to target.
# Default: "1.13" # Default: "1.13"
# Deprecated: use the global `run.go` instead. # Deprecated: use the global `run.go` instead.
# go: "1.15" # go: "1.15"
# SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks # SAxxxx checks in https://staticcheck.io/docs/configuration/options/#checks
# Default: ["*"] # Default: ["*"]
checks: [ "all","-SA1019","-SA4015","-SA1029","-SA1016","-SA9003","-SA4006","-SA6003" ] checks: [ "all","-SA1019","-SA4015","-SA1029","-SA1016","-SA9003","-SA4006","-SA6003" ]
# https://golangci-lint.run/usage/linters/#gofmt # https://golangci-lint.run/usage/linters/#gofmt
gofmt: gofmt:
# Simplify code: gofmt with `-s` option. # Simplify code: gofmt with `-s` option.
# Default: true # Default: true
simplify: true simplify: true
# Apply the rewrite rules to the source before reformatting. # Apply the rewrite rules to the source before reformatting.
# https://pkg.go.dev/cmd/gofmt # https://pkg.go.dev/cmd/gofmt
# Default: [] # Default: []
rewrite-rules: [ ] rewrite-rules: [ ]
# - pattern: 'interface{}' # - pattern: 'interface{}'
# replacement: 'any' # replacement: 'any'
# - pattern: 'a[b:len(a)]' # - pattern: 'a[b:len(a)]'
# replacement: 'a[b:]' # replacement: 'a[b:]'

View File

@ -1,39 +1,39 @@
## 功能案例 ## 功能案例
### 简介 ### 简介
系统的一些功能案例 系统的一些功能案例
### 使用说明 ### 使用说明
系统自带的功能使用示例及其说明包含一些简单的交互 系统自带的功能使用示例及其说明包含一些简单的交互
### 安装 ### 安装
1安装 HotGo (2.11.4及以上) 1安装 HotGo (2.11.4及以上)
项目介绍https://github.com/bufanyun/hotgo 项目介绍https://github.com/bufanyun/hotgo
2将当前插件项目拷贝进 HotGo 根目录的 server/addons 目录下 2将当前插件项目拷贝进 HotGo 根目录的 server/addons 目录下
3 HotGo 根目录的 server/addons/modules 目录下创建go文件:hgexample.go内容如下 3 HotGo 根目录的 server/addons/modules 目录下创建go文件:hgexample.go内容如下
```go ```go
package modules package modules
import _ "hotgo/addons/hgexample" import _ "hotgo/addons/hgexample"
``` ```
4HotGo 后台进入 开发工具->插件管理->找到 功能案例 (hgexample) 进行安装 4HotGo 后台进入 开发工具->插件管理->找到 功能案例 (hgexample) 进行安装
5重启服务即可生效 5重启服务即可生效
### 常用命令行 ### 常用命令行
```shell ```shell
# 接口维护-gen service # 接口维护-gen service
gf gen service -s=addons/hgexample/logic -d=addons/hgexample/service gf gen service -s=addons/hgexample/logic -d=addons/hgexample/service
``` ```

View File

@ -6,9 +6,8 @@
package common package common
import ( import (
"hotgo/internal/model/input/sysin"
"github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/frame/g"
"hotgo/internal/model/input/sysin"
) )
// UploadFileReq 上传文件 // UploadFileReq 上传文件
@ -18,14 +17,6 @@ type UploadFileReq struct {
type UploadFileRes *sysin.AttachmentListModel type UploadFileRes *sysin.AttachmentListModel
type UploadVideoReq struct {
g.Meta `path:"/upload/video" tags:"附件" method:"post" summary:"上传视频"`
}
type UploadVideoRes struct {
Path string `json:"path"`
}
// CheckMultipartReq 检查文件分片 // CheckMultipartReq 检查文件分片
type CheckMultipartReq struct { type CheckMultipartReq struct {
g.Meta `path:"/upload/checkMultipart" tags:"附件" method:"post" summary:"检查文件分片"` g.Meta `path:"/upload/checkMultipart" tags:"附件" method:"post" summary:"检查文件分片"`

View File

@ -0,0 +1,43 @@
// Package mycourse
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Yl <yl@example.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package mycourse
import (
"hotgo/internal/model/input/form"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// MyCourseListReq 我的课程列表请求
type MyCourseListReq struct {
g.Meta `path:"/mycourse/list" method:"get" tags:"我的课程" summary:"获取我的课程列表"`
form.PageReq
StudyStatus int `json:"study_status" dc:"学习状态0全部课程 1学习中 2已完结"`
}
type MyCourseListRes struct {
List []*MyCourseListModel `json:"list" dc:"课程列表"`
form.PageRes
}
type MyCourseListModel struct {
Id uint64 `json:"id" dc:"用户课程关联ID"`
CourseId uint64 `json:"course_id" dc:"课程ID"`
Title string `json:"title" dc:"课程标题"`
Subtitle string `json:"subtitle" dc:"课程副标题"`
CoverImage string `json:"cover_image" dc:"课程封面"`
Instructor string `json:"instructor" dc:"讲师"`
Subject string `json:"subject" dc:"学科"`
Description string `json:"description" dc:"课程描述"`
TotalEpisodes int `json:"total_episodes" dc:"总集数"`
TotalLessons int `json:"total_lessons" dc:"总节数"`
TotalDuration string `json:"total_duration" dc:"总时长"`
StudiedDuration string `json:"studied_duration" dc:"已学时长"`
StudyStatus int `json:"study_status" dc:"学习状态1学习中 2已完结"`
StudyStatusText string `json:"study_status_text" dc:"学习状态文本"`
EnrollmentTime *gtime.Time `json:"enrollment_time" dc:"报名时间"`
}

View File

@ -8,10 +8,7 @@ import (
// 获取课程列表请求 // 获取课程列表请求
type LessonListReq struct { type LessonListReq struct {
g.Meta `path:"/lesson/list" method:"get" tags:"课程" summary:"获取课程列表"` g.Meta `path:"/lesson/list" method:"get" tags:"课程" summary:"获取课程列表"`
CategoryId int64 `json:"categoryId" dc:"分类ID"`
Difficuty int `json:"difficuty" dc:"难度"`
Subject string `json:"subject" dc:"专题"`
} }
// 获取课程列表响应 // 获取课程列表响应

View File

@ -0,0 +1,15 @@
// =================================================================================
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
// =================================================================================
package mycourse
import (
"context"
"hotgo/api/api/mycourse/v1"
)
type IMyCourseV1 interface {
MyCourseList(ctx context.Context, req *v1.MyCourseListReq) (res *v1.MyCourseListRes, err error)
}

View File

@ -0,0 +1,36 @@
package v1
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// MyCourseListReq 获取我的课程列表请求
type MyCourseListReq struct {
g.Meta `path:"/mycourse/list" method:"get" tags:"我的课程" summary:"获取我的课程列表"`
Status string `json:"status" dc:"学习状态筛选learning-学习中completed-已完成,空值-全部"`
Page int `json:"page" d:"1" dc:"页码从1开始"`
PageSize int `json:"pageSize" d:"10" dc:"每页数量默认10条"`
}
// MyCourseListRes 获取我的课程列表响应
type MyCourseListRes struct {
List []*MyCourseItem `json:"list" dc:"课程列表"`
Total int `json:"total" dc:"总记录数"`
Page int `json:"page" dc:"当前页码"`
PageSize int `json:"pageSize" dc:"每页数量"`
}
// MyCourseItem 我的课程项目
type MyCourseItem struct {
Id int64 `json:"id" dc:"课程ID"`
Title string `json:"title" dc:"课程标题"`
Description string `json:"description" dc:"课程描述"`
Cover string `json:"cover" dc:"课程封面图片URL"`
Instructor string `json:"instructor" dc:"讲师姓名"`
Duration int `json:"duration" dc:"课程时长(秒)"`
Progress int `json:"progress" dc:"学习进度0-100"`
Status string `json:"status" dc:"学习状态learning-学习中completed-已完成"`
EnrollTime *gtime.Time `json:"enrollTime" dc:"报名时间"`
LastStudyTime *gtime.Time `json:"lastStudyTime" dc:"最后学习时间"`
}

View File

@ -7,17 +7,12 @@ package common
import ( import (
"context" "context"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"hotgo/api/admin/common" "hotgo/api/admin/common"
"hotgo/internal/library/storager" "hotgo/internal/library/storager"
"hotgo/internal/service" "hotgo/internal/service"
"hotgo/utility/validate" "hotgo/utility/validate"
"os"
"os/exec"
"path/filepath"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/google/uuid"
) )
var Upload = new(cUpload) var Upload = new(cUpload)
@ -41,73 +36,6 @@ func (c *cUpload) UploadFile(ctx context.Context, _ *common.UploadFileReq) (res
return service.CommonUpload().UploadFile(ctx, uploadType, file) return service.CommonUpload().UploadFile(ctx, uploadType, file)
} }
// UploadVideo 上传视频
func (c *cUpload) UploadVideo(ctx context.Context, _ *common.UploadVideoReq) (res common.UploadVideoRes, err error) {
r := g.RequestFromCtx(ctx)
uploadType := r.Header.Get("uploadType")
if uploadType != "default" && !validate.InSlice(storager.KindSlice, uploadType) {
err = gerror.New("上传类型是无效的")
return
}
file := r.GetUploadFile("file")
if file == nil {
err = gerror.New("没有找到上传的文件")
return
}
m3u8UUID := uuid.New().String()
// 1. 保存上传视频到本地临时目录
tmpDir := "./tmp/video"
_, _ = file.Save(tmpDir + "/" + m3u8UUID)
tmpFile := tmpDir + "/" + m3u8UUID + "/" + file.Filename
// 2. 用ffmpeg切片为m3u8和ts文件
hlsDir := "./tmp/hls/" + m3u8UUID
_ = os.MkdirAll(hlsDir, 0755)
defer func() {
os.RemoveAll(hlsDir)
os.RemoveAll(tmpDir + "/" + m3u8UUID)
}()
m3u8File := m3u8UUID + ".m3u8"
m3u8Path := hlsDir + "/" + m3u8File
ffmpegCmd := exec.Command("ffmpeg", "-i", tmpFile, "-c:v", "libx264", "-hls_time", "10", "-hls_playlist_type", "vod", m3u8Path)
err = ffmpegCmd.Run()
if err != nil {
err = gerror.Wrap(err, "ffmpeg切片失败")
return
}
// 3. 遍历切片目录上传所有m3u8和ts文件到minio
minioDrive := new(storager.MinioDrive)
var m3u8MinioPath string
files, _ := filepath.Glob(hlsDir + "/*")
for _, f := range files {
fileInfo, errStat := os.Stat(f)
if errStat == nil && !fileInfo.IsDir() {
minioPath, err2 := minioDrive.UploadFromPath(ctx, f, filepath.Base(f))
if err2 != nil {
err = err2
return
}
if filepath.Ext(f) == ".m3u8" {
m3u8MinioPath = minioPath
}
}
}
m3u8MinioPath = storager.LastUrl(ctx, m3u8MinioPath, "minio")
// 4. 返回m3u8文件的minio路径
return common.UploadVideoRes{
Path: m3u8MinioPath,
}, nil
}
// CheckMultipart 检查文件分片 // CheckMultipart 检查文件分片
func (c *cUpload) CheckMultipart(ctx context.Context, req *common.CheckMultipartReq) (res *common.CheckMultipartRes, err error) { func (c *cUpload) CheckMultipart(ctx context.Context, req *common.CheckMultipartReq) (res *common.CheckMultipartRes, err error) {
data, err := service.CommonUpload().CheckMultipart(ctx, &req.CheckMultipartInp) data, err := service.CommonUpload().CheckMultipart(ctx, &req.CheckMultipartInp)

View File

@ -9,42 +9,20 @@ import (
"github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gcode"
"github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
) )
func (c *ControllerV1) LessonList(ctx context.Context, req *v1.LessonListReq) (res *v1.LessonListRes, err error) { func (c *ControllerV1) LessonList(ctx context.Context, req *v1.LessonListReq) (res *v1.LessonListRes, err error) {
// 构建查询条件
query := dao.Lesson.Ctx(ctx)
// 分类过滤
if req.CategoryId != -1 && req.CategoryId > 0 {
query = query.Where("category_id = ?", req.CategoryId)
}
// 难度过滤
if req.Difficuty != -1 {
query = query.Where("difficulty = ?", req.Difficuty)
}
// 专题过滤subject为all时不过滤否则用JSON_CONTAINS
if req.Subject != "" && req.Subject != "all" {
query = query.WhereLike("subject", "%"+req.Subject+"%")
}
// 查询课程列表 // 查询课程列表
var list []*entity.Lesson var list []*entity.Lesson
err = query.Scan(&list) err = dao.Lesson.Ctx(ctx).Scan(&list)
if err != nil { if err != nil {
g.Log().Error(ctx, err)
return nil, gerror.NewCode(gcode.CodeDbOperationError, "数据库错误") return nil, gerror.NewCode(gcode.CodeDbOperationError, "数据库错误")
} }
// 统计总数 // 统计总数
total, err := query.Count() total, err := dao.Lesson.Ctx(ctx).Count()
if err != nil { if err != nil {
return nil, gerror.NewCode(gcode.CodeDbOperationError, "统计总数失败") return nil, gerror.NewCode(gcode.CodeDbOperationError, "统计总数失败")
} }
res = &v1.LessonListRes{ res = &v1.LessonListRes{
List: list, List: list,
Total: total, Total: total,

View File

@ -0,0 +1,18 @@
// Package mycourse
// @Link https://github.com/bufanyun/hotgo
// @Copyright Copyright (c) 2023 HotGo CLI
// @Author Ms <133814250@qq.com>
// @License https://github.com/bufanyun/hotgo/blob/master/LICENSE
package mycourse
import (
"hotgo/api/api/mycourse"
)
// ControllerV1 我的课程控制器v1
type ControllerV1 struct{}
// NewV1 创建我的课程控制器v1
func NewV1() mycourse.IMyCourseV1 {
return &ControllerV1{}
}

View File

@ -0,0 +1,119 @@
package mycourse
import (
"context"
"hotgo/api/api/mycourse/v1"
"hotgo/internal/library/contexts"
"hotgo/internal/library/response"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
// MyCourseList 获取我的课程列表
func (c *ControllerV1) MyCourseList(ctx context.Context, req *v1.MyCourseListReq) (res *v1.MyCourseListRes, err error) {
// 从JWT中间件解析出的用户ID
userId := contexts.GetUserId(ctx)
if userId <= 0 {
err = gerror.New("用户未登录")
return
}
// 模拟数据 - 实际项目中应该从数据库查询
mockData := []*v1.MyCourseItem{
{
Id: 1,
Title: "Go语言基础教程",
Description: "从零开始学习Go语言掌握基础语法和核心概念",
Cover: "https://example.com/covers/go-basic.jpg",
Instructor: "张老师",
Duration: 7200, // 2小时
Progress: 75,
Status: "learning",
EnrollTime: gtime.New("2024-01-15 10:30:00"),
LastStudyTime: gtime.New("2024-01-20 14:20:00"),
},
{
Id: 2,
Title: "Vue.js实战开发",
Description: "深入学习Vue.js框架构建现代化前端应用",
Cover: "https://example.com/covers/vue-practice.jpg",
Instructor: "李老师",
Duration: 10800, // 3小时
Progress: 100,
Status: "completed",
EnrollTime: gtime.New("2024-01-10 09:15:00"),
LastStudyTime: gtime.New("2024-01-18 16:45:00"),
},
{
Id: 3,
Title: "MySQL数据库优化",
Description: "学习MySQL性能优化技巧和最佳实践",
Cover: "https://example.com/covers/mysql-optimization.jpg",
Instructor: "王老师",
Duration: 5400, // 1.5小时
Progress: 45,
Status: "learning",
EnrollTime: gtime.New("2024-01-20 14:00:00"),
LastStudyTime: gtime.New("2024-01-22 10:30:00"),
},
{
Id: 4,
Title: "Python数据分析",
Description: "使用Python进行数据分析和可视化",
Cover: "https://example.com/covers/python-analysis.jpg",
Instructor: "赵老师",
Duration: 9000, // 2.5小时
Progress: 100,
Status: "completed",
EnrollTime: gtime.New("2024-01-05 11:00:00"),
LastStudyTime: gtime.New("2024-01-12 15:30:00"),
},
}
// 根据状态筛选
var filteredData []*v1.MyCourseItem
if req.Status != "" {
for _, item := range mockData {
if item.Status == req.Status {
filteredData = append(filteredData, item)
}
}
} else {
filteredData = mockData
}
totalCount := len(filteredData)
// 分页处理
pageNum := req.Page
pageSize := req.PageSize
if pageNum <= 0 {
pageNum = 1
}
if pageSize <= 0 {
pageSize = 10
}
start := (pageNum - 1) * pageSize
end := start + pageSize
var list []*v1.MyCourseItem
if start >= totalCount {
list = []*v1.MyCourseItem{}
} else {
if end > totalCount {
end = totalCount
}
list = filteredData[start:end]
}
res = &v1.MyCourseListRes{
List: list,
Total: totalCount,
Page: pageNum,
PageSize: pageSize,
}
return
}

0
server/internal/dao/activity.go Executable file → Normal file
View File

0
server/internal/dao/activity_category.go Executable file → Normal file
View File

0
server/internal/dao/addon_hgexample_table.go Executable file → Normal file
View File

0
server/internal/dao/addon_hgexample_tenant_order.go Executable file → Normal file
View File

0
server/internal/dao/admin_cash.go Executable file → Normal file
View File

0
server/internal/dao/admin_credits_log.go Executable file → Normal file
View File

0
server/internal/dao/admin_dept.go Executable file → Normal file
View File

0
server/internal/dao/admin_member.go Executable file → Normal file
View File

0
server/internal/dao/admin_member_post.go Executable file → Normal file
View File

0
server/internal/dao/admin_member_role.go Executable file → Normal file
View File

0
server/internal/dao/admin_menu.go Executable file → Normal file
View File

0
server/internal/dao/admin_notice.go Executable file → Normal file
View File

0
server/internal/dao/admin_notice_read.go Executable file → Normal file
View File

0
server/internal/dao/admin_oauth.go Executable file → Normal file
View File

0
server/internal/dao/admin_order.go Executable file → Normal file
View File

0
server/internal/dao/admin_post.go Executable file → Normal file
View File

0
server/internal/dao/admin_role.go Executable file → Normal file
View File

0
server/internal/dao/admin_role_casbin.go Executable file → Normal file
View File

0
server/internal/dao/admin_role_menu.go Executable file → Normal file
View File

0
server/internal/dao/internal/activity.go Executable file → Normal file
View File

0
server/internal/dao/internal/activity_category.go Executable file → Normal file
View File

0
server/internal/dao/internal/addon_hgexample_table.go Executable file → Normal file
View File

View File

0
server/internal/dao/internal/admin_cash.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_credits_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_dept.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_member.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_member_post.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_member_role.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_menu.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_notice.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_notice_read.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_oauth.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_order.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_post.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_role.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_role_casbin.go Executable file → Normal file
View File

0
server/internal/dao/internal/admin_role_menu.go Executable file → Normal file
View File

4
server/internal/dao/internal/lesson.go Executable file → Normal file
View File

@ -41,8 +41,6 @@ type LessonColumns struct {
CreatedTime string // 创建时间 CreatedTime string // 创建时间
UpdatedBy string // 更新人 UpdatedBy string // 更新人
UpdatedTime string // 更新时间 UpdatedTime string // 更新时间
Difficulty string // 课程难度
Subject string // 所属专题
} }
// lessonColumns holds the columns for the table hg_lesson. // lessonColumns holds the columns for the table hg_lesson.
@ -67,8 +65,6 @@ var lessonColumns = LessonColumns{
CreatedTime: "created_time", CreatedTime: "created_time",
UpdatedBy: "updated_by", UpdatedBy: "updated_by",
UpdatedTime: "updated_time", UpdatedTime: "updated_time",
Difficulty: "difficulty",
Subject: "subject",
} }
// NewLessonDao creates and returns a new DAO object for table data access. // NewLessonDao creates and returns a new DAO object for table data access.

0
server/internal/dao/internal/lesson_category.go Executable file → Normal file
View File

2
server/internal/dao/internal/lesson_section.go Executable file → Normal file
View File

@ -27,7 +27,7 @@ type LessonSectionColumns struct {
Name string // 章节名 Name string // 章节名
SortOrder string // 排序号 SortOrder string // 排序号
ParentId string // 父章节id ParentId string // 父章节id
Level string // 章节层级 Level string // 章节层级;1=章2=节
Revision string // 乐观锁 Revision string // 乐观锁
CreatedBy string // 创建人 CreatedBy string // 创建人
CreatedTime string // 创建时间 CreatedTime string // 创建时间

0
server/internal/dao/internal/pay_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/pay_refund.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_addons_config.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_attachment.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_blacklist.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_config.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_cron.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_cron_group.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_dict_data.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_dict_type.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_ems_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_gen_codes.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_gen_curd_demo.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_gen_tree_demo.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_login_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_provinces.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_serve_license.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_serve_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/sys_sms_log.go Executable file → Normal file
View File

0
server/internal/dao/internal/test_category.go Executable file → Normal file
View File

0
server/internal/dao/internal/users.go Executable file → Normal file
View File

0
server/internal/dao/lesson.go Executable file → Normal file
View File

0
server/internal/dao/lesson_category.go Executable file → Normal file
View File

0
server/internal/dao/lesson_section.go Executable file → Normal file
View File

0
server/internal/dao/pay_log.go Executable file → Normal file
View File

0
server/internal/dao/pay_refund.go Executable file → Normal file
View File

0
server/internal/dao/sys_addons_config.go Executable file → Normal file
View File

0
server/internal/dao/sys_attachment.go Executable file → Normal file
View File

0
server/internal/dao/sys_blacklist.go Executable file → Normal file
View File

0
server/internal/dao/sys_config.go Executable file → Normal file
View File

0
server/internal/dao/sys_cron.go Executable file → Normal file
View File

0
server/internal/dao/sys_cron_group.go Executable file → Normal file
View File

Some files were not shown because too many files have changed in this diff Show More