Compare commits
No commits in common. "9857706e9544066dc543182024f435d4e1ce8acc" and "05bd8e6eff32b09dd0ae66838bc1b5252d3c7342" have entirely different histories.
9857706e95
...
05bd8e6eff
13
Dockerfile
13
Dockerfile
@ -11,8 +11,6 @@ RUN npm install -g pnpm
|
|||||||
# 设置工作目录
|
# 设置工作目录
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# 复制后端
|
|
||||||
COPY ./server ./server
|
|
||||||
# 复制前端
|
# 复制前端
|
||||||
COPY ./web ./web
|
COPY ./web ./web
|
||||||
|
|
||||||
@ -21,6 +19,9 @@ 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
|
||||||
|
|
||||||
@ -33,10 +34,12 @@ 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 download
|
RUN go mod tidy
|
||||||
|
|
||||||
# 安装gf
|
# 安装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
|
# 将deploy里的gf文件复制到当前目录
|
||||||
|
COPY ./deploy/gf .
|
||||||
|
RUN chmod +x gf && ./gf install -y && rm ./gf
|
||||||
|
|
||||||
RUN gf build
|
RUN gf build
|
||||||
|
|
||||||
@ -44,7 +47,7 @@ RUN gf build
|
|||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
|
|
||||||
# 安装 ca-certificates 用于 HTTPS 请求
|
# 安装 ca-certificates 用于 HTTPS 请求
|
||||||
RUN apk --no-cache add ca-certificates tzdata
|
RUN apk --no-cache add ca-certificates tzdata ffmpeg
|
||||||
|
|
||||||
# 设置时区
|
# 设置时区
|
||||||
ENV TZ=Asia/Shanghai
|
ENV TZ=Asia/Shanghai
|
||||||
|
42
LICENSE
42
LICENSE
@ -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.
|
||||||
|
@ -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:
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
# 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. 测试课程列表接口
|
|
||||||
- URL:http://localhost:8000/admin/mycourse/list
|
|
||||||
- 方法:GET
|
|
||||||
- 请求头:Authorization: Bearer {token}
|
|
||||||
- 参数:
|
|
||||||
- page: 1
|
|
||||||
- size: 10
|
|
||||||
- study_status: 1 (学习中) 或 2 (已完结) 或 0 (全部)
|
|
||||||
|
|
||||||
## 预期响应
|
|
||||||
|
|
||||||
接口将返回符合截图显示的课程数据:
|
|
||||||
- 课程标题和副标题
|
|
||||||
- 讲师和学科信息
|
|
||||||
- 课程描述
|
|
||||||
- 集数、节数、时长信息
|
|
||||||
- 学习状态(学习中/已完结)
|
|
||||||
|
|
||||||
这个简化版本只包含截图中实际需要的功能,没有添加额外的复杂功能。
|
|
@ -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)
|
||||||
|
|
@ -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 # 是否为插件模板 false|true
|
isAddon: false # 是否为插件模板 false|true
|
||||||
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 # 是否为插件模板 false|true
|
isAddon: true # 是否为插件模板 false|true
|
||||||
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后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
3、自定义配置
|
3、自定义配置
|
||||||
- 确认配置无误后,点击生成配置会自动跳转到生成配置页面,如下:
|
- 确认配置无误后,点击生成配置会自动跳转到生成配置页面,如下:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 你可以在该页面点击`预览代码`查看生成的内容,如果无需调整亦可以直接点击`提交生成`,以下是预览代码效果:
|
- 你可以在该页面点击`预览代码`查看生成的内容,如果无需调整亦可以直接点击`提交生成`,以下是预览代码效果:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
- 如果你对默认的生成配置不满意,可以根据页面表单提示,自定义表格属性和字段属性
|
- 如果你对默认的生成配置不满意,可以根据页面表单提示,自定义表格属性和字段属性
|
||||||
|
|
||||||
- 这里假设我们要一个关联表,让表`hg_test_table`字段`category_id`去关联 表`hg_test_category`字段`id`,我们可以这样做:
|
- 这里假设我们要一个关联表,让表`hg_test_table`字段`category_id`去关联 表`hg_test_category`字段`id`,我们可以这样做:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
- 如果你想调整主表每列字段的显示方式,可以点击上方导航栏中的 [主表字段] 进行调整
|
- 如果你想调整主表每列字段的显示方式,可以点击上方导航栏中的 [主表字段] 进行调整
|
||||||
- 这里我们不做任何调整直接进入下一步。目的是为了后续演示对最终生成结果不满意的再次调整方案
|
- 这里我们不做任何调整直接进入下一步。目的是为了后续演示对最终生成结果不满意的再次调整方案
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
4、以上内容都已配置无误后,点击提交生成即可。
|
4、以上内容都已配置无误后,点击提交生成即可。
|
||||||
|
|
||||||
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
|
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
|
||||||
|
|
||||||
- 注意:热编译环境下,web端往往会快于服务端重启并加载完成,此时访问新生成的菜单页面可能会存在某接口请求超时或404问题,这是服务端正在重启导致的,属于正常现象,一般稍等几秒再试即可。
|
- 注意:热编译环境下,web端往往会快于服务端重启并加载完成,此时访问新生成的菜单页面可能会存在某接口请求超时或404问题,这是服务端正在重启导致的,属于正常现象,一般稍等几秒再试即可。
|
||||||
|
|
||||||
- 接下来让我们看看生成的表格页面,效果如下:
|
- 接下来让我们看看生成的表格页面,效果如下:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
5、假设我们对生成结果不满意,有新的优化需求如下:
|
5、假设我们对生成结果不满意,有新的优化需求如下:
|
||||||
1. 把`单图`、`附件`改换成上传组件
|
1. 把`单图`、`附件`改换成上传组件
|
||||||
2. 把`创建者`、`更新者`隐藏
|
2. 把`创建者`、`更新者`隐藏
|
||||||
3. 把`分类ID`隐藏,然后把关联表中的`分类名称`显示出来
|
3. 把`分类ID`隐藏,然后把关联表中的`分类名称`显示出来
|
||||||
|
|
||||||
> 那么我们可以回到 开发工具 -> 代码生成 -> 找到刚刚生成的记录 -> 在右侧操作栏中点击生成配置,再次进入配置页面做如下操作即可:
|
> 那么我们可以回到 开发工具 -> 代码生成 -> 找到刚刚生成的记录 -> 在右侧操作栏中点击生成配置,再次进入配置页面做如下操作即可:
|
||||||
|
|
||||||
1. 点击上方导航栏[主表字段],调整字段选项:
|
1. 点击上方导航栏[主表字段],调整字段选项:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
2. 点击上方导航栏[关联表],调整字段选项:
|
2. 点击上方导航栏[关联表],调整字段选项:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
3. 返回[基本信息],勾选强制覆盖,然后点击`预览代码`,确认生成代码无误后点击`提交生成`
|
3. 返回[基本信息],勾选强制覆盖,然后点击`预览代码`,确认生成代码无误后点击`提交生成`
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
- 生成完成刷新页面后,再次来到`测试表格`,发现表格已经调整到了我们预期效果
|
- 生成完成刷新页面后,再次来到`测试表格`,发现表格已经调整到了我们预期效果
|
||||||
|
|
||||||
1. 列表效果:
|
1. 列表效果:
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
2. 编辑表单效果
|
2. 编辑表单效果
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
- 至此生成增删改查列表示例结束!
|
- 至此生成增删改查列表示例结束!
|
||||||
|
|
||||||
### 生成树形表格
|
### 生成树形表格
|
||||||
|
|
||||||
待写。
|
待写。
|
||||||
|
|
||||||
### 多数据库使用
|
### 多数据库使用
|
||||||
|
|
||||||
- 假设我们要增加一个库名为`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`
|
||||||
|
@ -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文件
|
||||||
```
|
```
|
@ -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
|
@ -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
|
||||||
|
|
||||||
|
@ -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中进行相应调用即可。
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
## 单元测试
|
## 单元测试
|
||||||
|
|
||||||
请参考:https://goframe.org/pages/viewpage.action?pageId=1114153
|
请参考:https://goframe.org/pages/viewpage.action?pageId=1114153
|
@ -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 # 数据验证
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
## WebHook
|
## WebHook
|
||||||
|
|
||||||
请参考:https://goframe.org/pages/viewpage.action?pageId=1114387
|
请参考:https://goframe.org/pages/viewpage.action?pageId=1114387
|
||||||
|
@ -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:]'
|
@ -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"
|
||||||
```
|
```
|
||||||
|
|
||||||
4、HotGo 后台进入 开发工具->插件管理->找到 功能案例 (hgexample) 进行安装
|
4、HotGo 后台进入 开发工具->插件管理->找到 功能案例 (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
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -6,8 +6,9 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
|
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UploadFileReq 上传文件
|
// UploadFileReq 上传文件
|
||||||
@ -17,6 +18,14 @@ 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:"检查文件分片"`
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
// 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:"报名时间"`
|
|
||||||
}
|
|
@ -8,7 +8,10 @@ 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:"专题"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取课程列表响应
|
// 获取课程列表响应
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
// =================================================================================
|
|
||||||
// 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)
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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:"最后学习时间"`
|
|
||||||
}
|
|
@ -7,12 +7,17 @@ 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)
|
||||||
@ -36,6 +41,73 @@ 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)
|
||||||
|
@ -9,20 +9,42 @@ 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 = dao.Lesson.Ctx(ctx).Scan(&list)
|
err = query.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 := dao.Lesson.Ctx(ctx).Count()
|
total, err := query.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,
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
// 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{}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
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
Normal file → Executable file
0
server/internal/dao/activity.go
Normal file → Executable file
0
server/internal/dao/activity_category.go
Normal file → Executable file
0
server/internal/dao/activity_category.go
Normal file → Executable file
0
server/internal/dao/addon_hgexample_table.go
Normal file → Executable file
0
server/internal/dao/addon_hgexample_table.go
Normal file → Executable file
0
server/internal/dao/addon_hgexample_tenant_order.go
Normal file → Executable file
0
server/internal/dao/addon_hgexample_tenant_order.go
Normal file → Executable file
0
server/internal/dao/admin_cash.go
Normal file → Executable file
0
server/internal/dao/admin_cash.go
Normal file → Executable file
0
server/internal/dao/admin_credits_log.go
Normal file → Executable file
0
server/internal/dao/admin_credits_log.go
Normal file → Executable file
0
server/internal/dao/admin_dept.go
Normal file → Executable file
0
server/internal/dao/admin_dept.go
Normal file → Executable file
0
server/internal/dao/admin_member.go
Normal file → Executable file
0
server/internal/dao/admin_member.go
Normal file → Executable file
0
server/internal/dao/admin_member_post.go
Normal file → Executable file
0
server/internal/dao/admin_member_post.go
Normal file → Executable file
0
server/internal/dao/admin_member_role.go
Normal file → Executable file
0
server/internal/dao/admin_member_role.go
Normal file → Executable file
0
server/internal/dao/admin_menu.go
Normal file → Executable file
0
server/internal/dao/admin_menu.go
Normal file → Executable file
0
server/internal/dao/admin_notice.go
Normal file → Executable file
0
server/internal/dao/admin_notice.go
Normal file → Executable file
0
server/internal/dao/admin_notice_read.go
Normal file → Executable file
0
server/internal/dao/admin_notice_read.go
Normal file → Executable file
0
server/internal/dao/admin_oauth.go
Normal file → Executable file
0
server/internal/dao/admin_oauth.go
Normal file → Executable file
0
server/internal/dao/admin_order.go
Normal file → Executable file
0
server/internal/dao/admin_order.go
Normal file → Executable file
0
server/internal/dao/admin_post.go
Normal file → Executable file
0
server/internal/dao/admin_post.go
Normal file → Executable file
0
server/internal/dao/admin_role.go
Normal file → Executable file
0
server/internal/dao/admin_role.go
Normal file → Executable file
0
server/internal/dao/admin_role_casbin.go
Normal file → Executable file
0
server/internal/dao/admin_role_casbin.go
Normal file → Executable file
0
server/internal/dao/admin_role_menu.go
Normal file → Executable file
0
server/internal/dao/admin_role_menu.go
Normal file → Executable file
0
server/internal/dao/internal/activity.go
Normal file → Executable file
0
server/internal/dao/internal/activity.go
Normal file → Executable file
0
server/internal/dao/internal/activity_category.go
Normal file → Executable file
0
server/internal/dao/internal/activity_category.go
Normal file → Executable file
0
server/internal/dao/internal/addon_hgexample_table.go
Normal file → Executable file
0
server/internal/dao/internal/addon_hgexample_table.go
Normal file → Executable file
0
server/internal/dao/internal/addon_hgexample_tenant_order.go
Normal file → Executable file
0
server/internal/dao/internal/addon_hgexample_tenant_order.go
Normal file → Executable file
0
server/internal/dao/internal/admin_cash.go
Normal file → Executable file
0
server/internal/dao/internal/admin_cash.go
Normal file → Executable file
0
server/internal/dao/internal/admin_credits_log.go
Normal file → Executable file
0
server/internal/dao/internal/admin_credits_log.go
Normal file → Executable file
0
server/internal/dao/internal/admin_dept.go
Normal file → Executable file
0
server/internal/dao/internal/admin_dept.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member_post.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member_post.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member_role.go
Normal file → Executable file
0
server/internal/dao/internal/admin_member_role.go
Normal file → Executable file
0
server/internal/dao/internal/admin_menu.go
Normal file → Executable file
0
server/internal/dao/internal/admin_menu.go
Normal file → Executable file
0
server/internal/dao/internal/admin_notice.go
Normal file → Executable file
0
server/internal/dao/internal/admin_notice.go
Normal file → Executable file
0
server/internal/dao/internal/admin_notice_read.go
Normal file → Executable file
0
server/internal/dao/internal/admin_notice_read.go
Normal file → Executable file
0
server/internal/dao/internal/admin_oauth.go
Normal file → Executable file
0
server/internal/dao/internal/admin_oauth.go
Normal file → Executable file
0
server/internal/dao/internal/admin_order.go
Normal file → Executable file
0
server/internal/dao/internal/admin_order.go
Normal file → Executable file
0
server/internal/dao/internal/admin_post.go
Normal file → Executable file
0
server/internal/dao/internal/admin_post.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role_casbin.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role_casbin.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role_menu.go
Normal file → Executable file
0
server/internal/dao/internal/admin_role_menu.go
Normal file → Executable file
4
server/internal/dao/internal/lesson.go
Normal file → Executable file
4
server/internal/dao/internal/lesson.go
Normal file → Executable file
@ -41,6 +41,8 @@ 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.
|
||||||
@ -65,6 +67,8 @@ 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
Normal file → Executable file
0
server/internal/dao/internal/lesson_category.go
Normal file → Executable file
2
server/internal/dao/internal/lesson_section.go
Normal file → Executable file
2
server/internal/dao/internal/lesson_section.go
Normal file → Executable file
@ -27,7 +27,7 @@ type LessonSectionColumns struct {
|
|||||||
Name string // 章节名
|
Name string // 章节名
|
||||||
SortOrder string // 排序号
|
SortOrder string // 排序号
|
||||||
ParentId string // 父章节id
|
ParentId string // 父章节id
|
||||||
Level string // 章节层级;1=章,2=节
|
Level string // 章节层级
|
||||||
Revision string // 乐观锁
|
Revision string // 乐观锁
|
||||||
CreatedBy string // 创建人
|
CreatedBy string // 创建人
|
||||||
CreatedTime string // 创建时间
|
CreatedTime string // 创建时间
|
||||||
|
0
server/internal/dao/internal/pay_log.go
Normal file → Executable file
0
server/internal/dao/internal/pay_log.go
Normal file → Executable file
0
server/internal/dao/internal/pay_refund.go
Normal file → Executable file
0
server/internal/dao/internal/pay_refund.go
Normal file → Executable file
0
server/internal/dao/internal/sys_addons_config.go
Normal file → Executable file
0
server/internal/dao/internal/sys_addons_config.go
Normal file → Executable file
0
server/internal/dao/internal/sys_attachment.go
Normal file → Executable file
0
server/internal/dao/internal/sys_attachment.go
Normal file → Executable file
0
server/internal/dao/internal/sys_blacklist.go
Normal file → Executable file
0
server/internal/dao/internal/sys_blacklist.go
Normal file → Executable file
0
server/internal/dao/internal/sys_config.go
Normal file → Executable file
0
server/internal/dao/internal/sys_config.go
Normal file → Executable file
0
server/internal/dao/internal/sys_cron.go
Normal file → Executable file
0
server/internal/dao/internal/sys_cron.go
Normal file → Executable file
0
server/internal/dao/internal/sys_cron_group.go
Normal file → Executable file
0
server/internal/dao/internal/sys_cron_group.go
Normal file → Executable file
0
server/internal/dao/internal/sys_dict_data.go
Normal file → Executable file
0
server/internal/dao/internal/sys_dict_data.go
Normal file → Executable file
0
server/internal/dao/internal/sys_dict_type.go
Normal file → Executable file
0
server/internal/dao/internal/sys_dict_type.go
Normal file → Executable file
0
server/internal/dao/internal/sys_ems_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_ems_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_codes.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_codes.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_curd_demo.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_curd_demo.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_tree_demo.go
Normal file → Executable file
0
server/internal/dao/internal/sys_gen_tree_demo.go
Normal file → Executable file
0
server/internal/dao/internal/sys_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_login_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_login_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_provinces.go
Normal file → Executable file
0
server/internal/dao/internal/sys_provinces.go
Normal file → Executable file
0
server/internal/dao/internal/sys_serve_license.go
Normal file → Executable file
0
server/internal/dao/internal/sys_serve_license.go
Normal file → Executable file
0
server/internal/dao/internal/sys_serve_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_serve_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_sms_log.go
Normal file → Executable file
0
server/internal/dao/internal/sys_sms_log.go
Normal file → Executable file
0
server/internal/dao/internal/test_category.go
Normal file → Executable file
0
server/internal/dao/internal/test_category.go
Normal file → Executable file
0
server/internal/dao/internal/users.go
Normal file → Executable file
0
server/internal/dao/internal/users.go
Normal file → Executable file
0
server/internal/dao/lesson.go
Normal file → Executable file
0
server/internal/dao/lesson.go
Normal file → Executable file
0
server/internal/dao/lesson_category.go
Normal file → Executable file
0
server/internal/dao/lesson_category.go
Normal file → Executable file
0
server/internal/dao/lesson_section.go
Normal file → Executable file
0
server/internal/dao/lesson_section.go
Normal file → Executable file
0
server/internal/dao/pay_log.go
Normal file → Executable file
0
server/internal/dao/pay_log.go
Normal file → Executable file
0
server/internal/dao/pay_refund.go
Normal file → Executable file
0
server/internal/dao/pay_refund.go
Normal file → Executable file
0
server/internal/dao/sys_addons_config.go
Normal file → Executable file
0
server/internal/dao/sys_addons_config.go
Normal file → Executable file
0
server/internal/dao/sys_attachment.go
Normal file → Executable file
0
server/internal/dao/sys_attachment.go
Normal file → Executable file
0
server/internal/dao/sys_blacklist.go
Normal file → Executable file
0
server/internal/dao/sys_blacklist.go
Normal file → Executable file
0
server/internal/dao/sys_config.go
Normal file → Executable file
0
server/internal/dao/sys_config.go
Normal file → Executable file
0
server/internal/dao/sys_cron.go
Normal file → Executable file
0
server/internal/dao/sys_cron.go
Normal file → Executable file
0
server/internal/dao/sys_cron_group.go
Normal file → Executable file
0
server/internal/dao/sys_cron_group.go
Normal file → Executable file
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user