chore: seed migration objects batch 1
This commit is contained in:
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/im_manager.iml" filepath="$PROJECT_DIR$/.idea/im_manager.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,960 @@
|
|||||||
|
# HotGo 项目开发规范
|
||||||
|
|
||||||
|
**生成或修改任何代码前,必须完整阅读本文档。**
|
||||||
|
本规范是项目唯一事实来源,所有新增代码必须严格对齐现有模块风格,不得自创新的代码模式。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
| 层 | 技术 |
|
||||||
|
|----|----------------------------------------------------------------|
|
||||||
|
| 后端语言 | Go,module 名 `hotgo` |
|
||||||
|
| 后端框架 | GoFrame v2(gdb、ghttp、gcron、gerror、gctx、gerror、gjson、gvar) |
|
||||||
|
| 前端框架 | Vue 3 + Vite + TypeScript |
|
||||||
|
| UI 组件库 | **Naive UI**(优先使用,未经允许不得引入其他 UI 库) |
|
||||||
|
| 状态管理 | Pinia |
|
||||||
|
| HTTP 客户端 | 项目封装 `@/utils/http/axios`(`http.request`),禁止直接使用 axios |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── addons/ # 插件模块(每个插件独立微架构,见"插件开发"章节)
|
||||||
|
│ ├── modules/ # 隐式注册文件(每个插件一个 .go,仅含 import)
|
||||||
|
│ └── <addonName>/ # 插件目录,与主模块结构镜像
|
||||||
|
├── api/admin/<module>/ # 主模块 HTTP 契约:Req/Res + g.Meta 路由声明
|
||||||
|
├── internal/
|
||||||
|
│ ├── consts/ # 全局常量与字典选项(枚举在此定义并注册)
|
||||||
|
│ ├── global/ # 全局公用变量(谨慎添加,尽量少用)
|
||||||
|
│ ├── controller/admin/sys/ # 薄控制器,只做参数透传,不含业务逻辑
|
||||||
|
│ ├── crons/ # 主模块定时任务注册与处理
|
||||||
|
│ ├── queues/ # 主模块消息队列消费者注册与处理
|
||||||
|
│ ├── logic/sys/ # 业务实现(sSysXxx),init() 注册到 service
|
||||||
|
│ ├── library/ # 内部基础库(addons、dict、hgorm、cache、queue、cron 等)
|
||||||
|
│ ├── service/ # 接口声明(ISysXxx)+ 注册函数
|
||||||
|
│ ├── dao/ # 对外 DAO;internal/dao/internal/ 为 CLI 生成,禁止手改
|
||||||
|
│ ├── model/
|
||||||
|
│ │ ├── entity/ # 表结构体(CLI 生成,禁止手改)
|
||||||
|
│ │ ├── do/ # ORM 写入结构(CLI 生成,禁止手改)
|
||||||
|
│ │ └── input/sysin/ # 业务入参、出参、过滤结构
|
||||||
|
│ └── router/
|
||||||
|
│ ├── admin.go # 主路由,末尾调用 genrouter.Register()
|
||||||
|
│ └── genrouter/ # 每个生成模块一个文件,init() 追加到 LoginRequiredRouter
|
||||||
|
├── resource/
|
||||||
|
│ ├── generate/default/curd/ # 主模块 CRUD 代码生成模板(参考,禁止直接修改)
|
||||||
|
│ └── generate/default/addon/ # 插件脚手架代码生成模板(参考,禁止直接修改)
|
||||||
|
└── utility/ # 通用工具:convert、excel、validate 等
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 后端分层约定
|
||||||
|
|
||||||
|
### 分层职责(禁止跨层调用)
|
||||||
|
|
||||||
|
```
|
||||||
|
api(HTTP契约) → controller(参数透传) → service(接口) → logic(业务实现) → dao(数据访问)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **controller 层**:不写任何业务逻辑,只转调 service
|
||||||
|
- **logic 层**:不出现 HTTP 相关代码,所有校验通过 `Filter()` 或 `g.Validator()` 完成
|
||||||
|
- **dao 层**:`internal/dao/internal/` 由 CLI 自动生成,禁止手改
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. API 层 `api/admin/<module>/<module>.go`
|
||||||
|
|
||||||
|
- 每个接口一对 `XxxReq` / `XxxRes`,`g.Meta` 声明路由
|
||||||
|
- GET 接口用 `method:"get"`,写操作用 `method:"post"`
|
||||||
|
- 请求体嵌入 `sysin.XxxInp`,响应体嵌入对应 Model 或空 `struct{}`
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ListReq struct {
|
||||||
|
g.Meta `path:"/xxxModule/list" method:"get" tags:"XXX模块" summary:"获取XXX列表"`
|
||||||
|
sysin.XxxListInp
|
||||||
|
}
|
||||||
|
type ListRes struct {
|
||||||
|
form.PageRes
|
||||||
|
List []*sysin.XxxListModel `json:"list" dc:"数据列表"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Controller 层 `internal/controller/admin/sys/<module>.go`
|
||||||
|
|
||||||
|
- 变量:`var XxxModule = cXxxModule{}`,类型:`type cXxxModule struct{}`
|
||||||
|
- 只做参数转发和结果组装,禁止直接操作 dao 或写业务逻辑
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (c *cXxxModule) List(ctx context.Context, req *xxxmodule.ListReq) (res *xxxmodule.ListRes, err error) {
|
||||||
|
list, totalCount, err := service.SysXxxModule().List(ctx, &req.XxxListInp)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if list == nil {
|
||||||
|
list = []*sysin.XxxListModel{}
|
||||||
|
}
|
||||||
|
res = new(xxxmodule.ListRes)
|
||||||
|
res.List = list
|
||||||
|
res.PageRes.Pack(req, totalCount)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Service 层 `internal/service/sys.go`
|
||||||
|
|
||||||
|
- 在已有文件中**追加**接口声明和注册函数,禁止新建文件
|
||||||
|
- 声明 `ISysXxxModule` 接口,提供 `SysXxxModule()` 获取函数和 `RegisterSysXxxModule()` 注册函数
|
||||||
|
|
||||||
|
### 4. Logic 层 `internal/logic/sys/<module>.go`
|
||||||
|
|
||||||
|
- 结构体:`type sSysXxxModule struct{}`
|
||||||
|
- `init()` 里调用 `service.RegisterSysXxxModule(NewSysXxxModule())`
|
||||||
|
- `Model()` 方法:`return handler.Model(dao.XxxTable.Ctx(ctx), option...)`
|
||||||
|
- 列名引用必须用 `dao.XxxTable.Columns().FieldName`,禁止硬编码字符串字段名
|
||||||
|
- `Edit` 方法以 `in.Id > 0` 区分更新/新增,更新用 `XxxUpdateFields`,新增用 `XxxInsertFields`
|
||||||
|
- 字段校验在 `sysin` 的 `Filter(ctx)` 方法完成,logic 层不重复校验
|
||||||
|
|
||||||
|
### 5. Model 层 `internal/model/input/sysin/<module>.go`
|
||||||
|
|
||||||
|
| 类型 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `XxxUpdateFields` | 更新时字段白名单 |
|
||||||
|
| `XxxInsertFields` | 新增时字段白名单 |
|
||||||
|
| `XxxEditInp` | 嵌入 `entity.XxxTable`,含 `Filter()` 校验方法 |
|
||||||
|
| `XxxListInp` | 列表查询入参,嵌入 `form.PageInp` |
|
||||||
|
| `XxxListModel` | 列表返回字段 |
|
||||||
|
| `XxxViewInp` / `XxxViewModel` | 详情入参/出参 |
|
||||||
|
| `XxxDeleteInp` | 删除入参(通常含 `Ids []int64`) |
|
||||||
|
| `XxxStatusInp`、`XxxMaxSortInp` 等 | 其他操作 |
|
||||||
|
|
||||||
|
### 6. 路由注册 `internal/router/genrouter/<module>.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package genrouter
|
||||||
|
|
||||||
|
import "hotgo/internal/controller/admin/sys"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
LoginRequiredRouter = append(LoginRequiredRouter, sys.XxxModule) // XXX模块
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
新建此文件即可,`genrouter/init.go` 通过 `init()` 链自动加载,无需修改 `admin.go`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 字典与枚举规范
|
||||||
|
|
||||||
|
**字典由后端统一维护**,前端通过固定接口拉取,禁止在前端硬编码枚举值。
|
||||||
|
|
||||||
|
### 后端:定义与注册(`internal/consts/` 下对应文件)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 1. 定义常量
|
||||||
|
const (
|
||||||
|
XxxStatusEnabled = 1 // 启用
|
||||||
|
XxxStatusDisable = 2 // 禁用
|
||||||
|
)
|
||||||
|
|
||||||
|
// 2. 定义选项(选择合适的样式函数)
|
||||||
|
var XxxStatusOptions = []*model.Option{
|
||||||
|
dict.GenSuccessOption(XxxStatusEnabled, "启用"),
|
||||||
|
dict.GenWarningOption(XxxStatusDisable, "禁用"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 在 init() 中注册(同文件)
|
||||||
|
func init() {
|
||||||
|
dict.RegisterEnums("XxxStatusOptions", "XXX状态选项", XxxStatusOptions)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**样式函数选择规则:**
|
||||||
|
|
||||||
|
| 函数 | 适用场景 |
|
||||||
|
|------|---------|
|
||||||
|
| `dict.GenSuccessOption` | 正常、启用、成功 |
|
||||||
|
| `dict.GenWarningOption` | 待处理、禁用、警告 |
|
||||||
|
| `dict.GenErrorOption` | 失败、封禁、危险 |
|
||||||
|
| `dict.GenInfoOption` | 信息类、中性状态 |
|
||||||
|
| `dict.GenPrimaryOption` | 主要操作、强调 |
|
||||||
|
| `dict.GenDefaultOption` | 默认、无特殊含义 |
|
||||||
|
| `dict.GenHashOption` | 不确定数量的动态枚举 |
|
||||||
|
|
||||||
|
### 后端:Logic 层中使用字典
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 获取标签文本
|
||||||
|
label := dict.GetOptionLabel(consts.XxxStatusOptions, in.Status)
|
||||||
|
|
||||||
|
// 校验是否为合法值
|
||||||
|
if !dict.HasOptionKey(consts.XxxStatusOptions, in.Status) {
|
||||||
|
return gerror.New("状态值不合法")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 前端:加载与使用字典
|
||||||
|
|
||||||
|
**加载**(在 `model.ts` 的 `loadOptions()` 中):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export function loadOptions() {
|
||||||
|
dict.loadOptions(['XxxStatusOptions', 'sys_normal_disable']);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**在 `model.ts` 的 columns 中渲染字典标签:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { useDictStore } from '@/store/modules/dict';
|
||||||
|
const dict = useDictStore();
|
||||||
|
|
||||||
|
// columns 中
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
key: 'status',
|
||||||
|
render(row) {
|
||||||
|
return dict.getLabel('XxxStatusOptions', row.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**在搜索表单 schemas 中使用字典下拉:**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
component: 'NSelect',
|
||||||
|
label: '状态',
|
||||||
|
componentProps: {
|
||||||
|
options: computed(() => dict.getOptions('XxxStatusOptions')),
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 插件(Addon)开发规范
|
||||||
|
|
||||||
|
> 定位:独立、临时性、工具类型的功能推荐插件化开发,例如小游戏、广告管理、文章管理、小程序、微商城等。插件之间完全隔离,方便多项目复用。
|
||||||
|
|
||||||
|
### 插件目录结构
|
||||||
|
|
||||||
|
每个插件拥有完整的独立微架构,与主模块结构镜像,但 **包路径前缀为 `hotgo/addons/<addonName>/`**:
|
||||||
|
|
||||||
|
```
|
||||||
|
server/addons/<addonName>/
|
||||||
|
├── main.go # 插件入口:定义 module 结构体,实现 Module 接口,init() 注册
|
||||||
|
├── global/
|
||||||
|
│ ├── global.go # 插件级全局变量
|
||||||
|
│ └── init.go # Init(ctx, skeleton),GetSkeleton() 方法
|
||||||
|
├── api/
|
||||||
|
│ ├── admin/ # 后台接口契约(同主模块 api 写法)
|
||||||
|
│ ├── api/ # 前台 API 接口契约
|
||||||
|
│ └── home/ # 首页接口契约(如有)
|
||||||
|
├── controller/
|
||||||
|
│ ├── admin/sys/ # 后台薄控制器
|
||||||
|
│ ├── api/ # 前台控制器
|
||||||
|
│ └── home/ # 首页控制器(如有)
|
||||||
|
├── logic/
|
||||||
|
│ ├── logic.go # 匿名导入各 logic 子包触发 init()
|
||||||
|
│ └── sys/ # 业务实现(同主模块 logic 写法)
|
||||||
|
├── service/ # 插件内接口声明与注册(独立,不与主模块 service 混用)
|
||||||
|
├── model/
|
||||||
|
│ └── input/sysin/ # 插件内入参/出参(调用主模块服务时继承主模块 input 结构)
|
||||||
|
├── router/
|
||||||
|
│ ├── admin.go # 注册后台路由,使用 addons.RouterPrefix() 获取路由前缀
|
||||||
|
│ ├── api.go # 注册前台路由
|
||||||
|
│ ├── home.go # 注册首页路由(如有)
|
||||||
|
│ ├── websocket.go # 注册 WebSocket 路由(如有)
|
||||||
|
│ └── genrouter/
|
||||||
|
│ ├── init.go # 插件版 genrouter:Register() 使用插件前缀
|
||||||
|
│ └── *.go # 每个生成模块一个文件,追加到插件 LoginRequiredRouter
|
||||||
|
├── consts/ # 插件内常量与字典(同主模块 consts 写法)
|
||||||
|
├── crons/ # 插件定时任务(可选)
|
||||||
|
├── queues/ # 插件消息队列消费者(可选)
|
||||||
|
└── resource/ # 插件静态资源与模板(可选)
|
||||||
|
```
|
||||||
|
|
||||||
|
**对应前端目录:**
|
||||||
|
|
||||||
|
```
|
||||||
|
web/src/
|
||||||
|
├── api/addons/<addonName>/ # 插件前端 API 封装
|
||||||
|
└── views/addons/<addonName>/ # 插件前端页面
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件入口 `main.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package <addonName>
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/gogf/gf/v2/net/ghttp"
|
||||||
|
_ "hotgo/addons/<addonName>/crons"
|
||||||
|
"hotgo/addons/<addonName>/global"
|
||||||
|
_ "hotgo/addons/<addonName>/logic"
|
||||||
|
_ "hotgo/addons/<addonName>/queues"
|
||||||
|
"hotgo/addons/<addonName>/router"
|
||||||
|
"hotgo/internal/library/addons"
|
||||||
|
"hotgo/internal/service"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type module struct {
|
||||||
|
skeleton *addons.Skeleton
|
||||||
|
ctx context.Context
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { newModule() }
|
||||||
|
|
||||||
|
func newModule() {
|
||||||
|
m := &module{
|
||||||
|
skeleton: &addons.Skeleton{
|
||||||
|
Label: "插件显示名",
|
||||||
|
Name: "<addonName>", // 与目录名一致
|
||||||
|
Group: 1,
|
||||||
|
Brief: "简介",
|
||||||
|
Description: "详细描述",
|
||||||
|
Author: "作者",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
},
|
||||||
|
ctx: gctx.New(),
|
||||||
|
}
|
||||||
|
addons.RegisterModule(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *module) Start(option *addons.Option) (err error) {
|
||||||
|
global.Init(m.ctx, m.skeleton)
|
||||||
|
option.Server.Group("/", func(group *ghttp.RouterGroup) {
|
||||||
|
group.Middleware(service.Middleware().Addon)
|
||||||
|
router.Admin(m.ctx, group)
|
||||||
|
router.Api(m.ctx, group)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 其余方法:Stop、Ctx、GetSkeleton、Install、Upgrade、UnInstall(默认留空)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件路由前缀规则
|
||||||
|
|
||||||
|
```
|
||||||
|
后台:/admin/<addonName>/接口路径 → 对应 controller/admin/sys/
|
||||||
|
前台:/api/<addonName>/接口路径 → 对应 controller/api/
|
||||||
|
首页:/home/<addonName>/接口路径 → 对应 controller/home/
|
||||||
|
WebSocket:/socket/<addonName>/接口路径
|
||||||
|
```
|
||||||
|
|
||||||
|
路由前缀通过 `addons.RouterPrefix(ctx, consts.AppAdmin, global.GetSkeleton().Name)` 自动生成,**禁止在路由文件里硬编码路径前缀**。
|
||||||
|
|
||||||
|
### 插件 genrouter 与主模块的区别
|
||||||
|
|
||||||
|
插件 `genrouter/init.go` 的 `Register()` 函数需要传入 `ctx` 和 `group`,并使用插件路由前缀:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 插件 genrouter/init.go
|
||||||
|
func Register(ctx context.Context, group *ghttp.RouterGroup) {
|
||||||
|
prefix := addons.RouterPrefix(ctx, consts.AppAdmin, global.GetSkeleton().Name)
|
||||||
|
group.Group(prefix, func(group *ghttp.RouterGroup) {
|
||||||
|
group.Middleware(service.Middleware().AdminAuth)
|
||||||
|
if len(LoginRequiredRouter) > 0 {
|
||||||
|
group.Bind(LoginRequiredRouter...)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 插件 genrouter/<module>.go
|
||||||
|
func init() {
|
||||||
|
LoginRequiredRouter = append(LoginRequiredRouter, sys.XxxModule)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
参考实现:`server/addons/hgexample/router/genrouter/`
|
||||||
|
|
||||||
|
### 插件调用主模块服务
|
||||||
|
|
||||||
|
**在插件 input 层继承主模块 input 结构,解耦参数依赖**,避免 import cycle:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 插件 model/input/sysin/config.go
|
||||||
|
package sysin
|
||||||
|
|
||||||
|
import "hotgo/internal/model/input/sysin"
|
||||||
|
|
||||||
|
type UpdateConfigInp struct {
|
||||||
|
sysin.UpdateAddonsConfigInp
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 插件 logic 中调用主模块服务
|
||||||
|
import isc "hotgo/internal/service"
|
||||||
|
|
||||||
|
func (s *sSysConfig) UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error {
|
||||||
|
in.UpdateAddonsConfigInp.AddonName = global.GetSkeleton().Name
|
||||||
|
return isc.SysAddonsConfig().UpdateConfigByGroup(ctx, in.UpdateAddonsConfigInp)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件获取自身信息
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 插件内部
|
||||||
|
global.GetSkeleton()
|
||||||
|
|
||||||
|
// 任意位置判断当前请求是否为插件请求
|
||||||
|
contexts.IsAddonRequest(ctx)
|
||||||
|
contexts.GetAddonName(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 插件隐式注册
|
||||||
|
|
||||||
|
在 `server/addons/modules/` 下新建 `<addonName>.go`,内容仅含一行匿名导入:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package modules
|
||||||
|
|
||||||
|
import _ "hotgo/addons/<addonName>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 新增插件检查清单
|
||||||
|
|
||||||
|
**后端:**
|
||||||
|
- [ ] `addons/<addonName>/main.go` — 插件入口,实现 Module 接口
|
||||||
|
- [ ] `addons/<addonName>/global/` — global.go + init.go
|
||||||
|
- [ ] `addons/modules/<addonName>.go` — 隐式注册
|
||||||
|
- [ ] `addons/<addonName>/service/` — 插件内接口声明(独立包)
|
||||||
|
- [ ] `addons/<addonName>/logic/logic.go` — 匿名导入触发 init()
|
||||||
|
- [ ] `addons/<addonName>/logic/sys/<module>.go` — 业务实现
|
||||||
|
- [ ] `addons/<addonName>/model/input/sysin/<module>.go` — 入参/出参
|
||||||
|
- [ ] `addons/<addonName>/api/admin/<module>/<module>.go` — API 契约
|
||||||
|
- [ ] `addons/<addonName>/controller/admin/sys/<module>.go` — 薄控制器
|
||||||
|
- [ ] `addons/<addonName>/router/admin.go` — 路由注册
|
||||||
|
- [ ] `addons/<addonName>/router/genrouter/init.go` — 插件 genrouter
|
||||||
|
- [ ] `addons/<addonName>/router/genrouter/<module>.go` — 模块路由追加
|
||||||
|
|
||||||
|
**前端:**
|
||||||
|
- [ ] `web/src/api/addons/<addonName>/index.ts` — API 封装
|
||||||
|
- [ ] `web/src/views/addons/<addonName>/model.ts` — State、schemas、columns
|
||||||
|
- [ ] `web/src/views/addons/<addonName>/index.vue` — 列表页
|
||||||
|
- [ ] `web/src/views/addons/<addonName>/edit.vue` — 编辑页
|
||||||
|
|
||||||
|
参考实现:`server/addons/hgexample/`(完整插件示例)、`server/resource/generate/default/addon/`(生成模板)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 标准 CRUD 接口清单
|
||||||
|
|
||||||
|
| 接口 | method | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `/xxx/list` | GET | 分页列表 |
|
||||||
|
| `/xxx/view` | GET | 详情 |
|
||||||
|
| `/xxx/edit` | POST | 新增/编辑(id>0 为编辑) |
|
||||||
|
| `/xxx/delete` | POST | 删除(支持批量,传 ids) |
|
||||||
|
| `/xxx/status` | POST | 启用/禁用状态切换 |
|
||||||
|
| `/xxx/switch` | POST | 开关字段切换 |
|
||||||
|
| `/xxx/maxSort` | GET | 获取最大排序值 |
|
||||||
|
| `/xxx/export` | GET | 导出列表 |
|
||||||
|
|
||||||
|
按实际需求裁剪,不强制全部实现。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
web/src/
|
||||||
|
├── api/<module>/index.ts # HTTP 请求封装
|
||||||
|
├── views/<module>/
|
||||||
|
│ ├── index.vue # 列表页
|
||||||
|
│ ├── edit.vue # 新增/编辑弹窗
|
||||||
|
│ └── model.ts # State 类、rules、schemas、columns、loadOptions
|
||||||
|
├── components/ # 通用组件(BasicTable、BasicForm、BasicModal 等)
|
||||||
|
├── store/modules/dict.ts # 字典 store(loadOptions、getLabel、getOptions)
|
||||||
|
└── utils/
|
||||||
|
├── http/axios.ts # HTTP 封装(http.request / jumpExport)
|
||||||
|
└── dateUtil.ts # 日期工具(defRangeShortcuts 等)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端编码约定
|
||||||
|
|
||||||
|
### API 文件 `api/<module>/index.ts`
|
||||||
|
|
||||||
|
- 使用 `http.request`,禁止直接使用 axios
|
||||||
|
- 函数名与接口语义一致:`List`、`Edit`、`Delete`、`View`、`Status`、`Switch`、`Export`、`MaxSort`
|
||||||
|
- 导出接口用 `jumpExport('/xxx/export', params)`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { http, jumpExport } from '@/utils/http/axios';
|
||||||
|
|
||||||
|
export function List(params) {
|
||||||
|
return http.request({ url: '/xxxModule/list', method: 'get', params });
|
||||||
|
}
|
||||||
|
export function Edit(params) {
|
||||||
|
return http.request({ url: '/xxxModule/edit', method: 'POST', params });
|
||||||
|
}
|
||||||
|
export function Export(params) {
|
||||||
|
jumpExport('/xxxModule/export', params);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### model.ts 结构
|
||||||
|
|
||||||
|
- `class State`:属性与后端 entity 字段对应,提供合理默认值
|
||||||
|
- `newState()`:工厂函数,支持深拷贝(`cloneDeep`)
|
||||||
|
- `rules`:Naive UI 表单校验规则
|
||||||
|
- `schemas`:搜索栏 `FormSchema[]`(`ref` 包装)
|
||||||
|
- `columns`:表格列定义,含权限守卫的操作用 `hasPermission(['/xxx/edit'])`
|
||||||
|
- `loadOptions()`:调用 `dict.loadOptions([...])`,预加载字典数据
|
||||||
|
|
||||||
|
### 组件使用规范
|
||||||
|
|
||||||
|
**必须优先使用 Naive UI 原生组件**,能用 Naive UI 实现的禁止自行封装替代组件:
|
||||||
|
|
||||||
|
| 场景 | 必须使用的组件 |
|
||||||
|
|------|--------------|
|
||||||
|
| 表格 | 项目封装 `BasicTable`(基于 Naive UI) |
|
||||||
|
| 表单 | 项目封装 `BasicForm`(基于 Naive UI) |
|
||||||
|
| 弹窗 | `n-modal` 或项目封装 `BasicModal` |
|
||||||
|
| 按钮 | `n-button`,类型用 `primary`/`default`/`error` 等语义值 |
|
||||||
|
| 选择器 | `n-select`(字典下拉)、`n-tree-select`(树形) |
|
||||||
|
| 开关 | `n-switch`,配合 Switch 接口 |
|
||||||
|
| 输入框 | `n-input` |
|
||||||
|
| 日期选择 | `n-date-picker`,范围选择配合 `defRangeShortcuts()` |
|
||||||
|
| 图片渲染 | `renderImage(url)` |
|
||||||
|
| 文件渲染 | `renderFile(url)` |
|
||||||
|
| 成员信息 | `renderPopoverMemberSumma(summa)` |
|
||||||
|
|
||||||
|
**禁止行为:**
|
||||||
|
- 禁止引入 Element Plus、Ant Design Vue 等其他 UI 库
|
||||||
|
- 禁止使用内联 style(使用 Naive UI 的 props 或 class)
|
||||||
|
- 禁止在模板里硬编码字典值文本,必须通过 `dict.getLabel()` 渲染
|
||||||
|
|
||||||
|
### 权限控制
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { usePermission } from '@/hooks/web/usePermission';
|
||||||
|
const { hasPermission } = usePermission();
|
||||||
|
|
||||||
|
// 模板中
|
||||||
|
v-if="hasPermission(['/xxxModule/edit'])"
|
||||||
|
|
||||||
|
// columns 中开关操作
|
||||||
|
disabled: !hasPermission(['/xxxModule/switch'])
|
||||||
|
```
|
||||||
|
|
||||||
|
权限标识与后端路由路径保持一致,**路由由后端菜单数据驱动,禁止在源码里硬编码路由路径**。
|
||||||
|
|
||||||
|
### Vue 3 代码规范
|
||||||
|
|
||||||
|
- 使用 Composition API(`<script setup>`)
|
||||||
|
- 响应式数据用 `ref` / `reactive`,计算属性用 `computed`
|
||||||
|
- 字典选项传给组件时用 `computed(() => dict.getOptions('XxxOptions'))`,保持响应式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 新增 CRUD 模块检查清单
|
||||||
|
|
||||||
|
**后端(必须全部完成,顺序建议从下往上):**
|
||||||
|
|
||||||
|
- [ ] `internal/consts/<module>.go` — 常量定义、选项变量、`init()` 注册字典
|
||||||
|
- [ ] 使用 goframe CLI 生成:`entity`、`do`、`dao/internal`、`dao`(禁止手写)
|
||||||
|
- [ ] `internal/model/input/sysin/<module>.go` — 入参/出参/过滤模型
|
||||||
|
- [ ] `internal/service/sys.go` — 追加接口声明与注册函数
|
||||||
|
- [ ] `internal/logic/sys/<module>.go` — 业务实现
|
||||||
|
- [ ] `internal/controller/admin/sys/<module>.go` — 薄控制器
|
||||||
|
- [ ] `api/admin/<module>/<module>.go` — API 契约
|
||||||
|
- [ ] `internal/router/genrouter/<module>.go` — 路由注册(`LoginRequiredRouter` 追加)
|
||||||
|
|
||||||
|
**前端(必须全部完成):**
|
||||||
|
|
||||||
|
- [ ] `web/src/api/<module>/index.ts` — HTTP 封装
|
||||||
|
- [ ] `web/src/views/<module>/model.ts` — State、schemas、columns、rules、loadOptions
|
||||||
|
- [ ] `web/src/views/<module>/index.vue` — 列表页
|
||||||
|
- [ ] `web/src/views/<module>/edit.vue` — 编辑页
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考实现
|
||||||
|
|
||||||
|
**所有新模块必须严格对齐以下文件的风格,生成前请逐一阅读:**
|
||||||
|
|
||||||
|
**主模块 CRUD 参考:**
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `server/internal/consts/status.go` | 字典常量定义与注册标准写法 |
|
||||||
|
| `server/api/admin/curddemo/curddemo.go` | API 契约标准写法 |
|
||||||
|
| `server/internal/controller/admin/sys/curd_demo.go` | 薄控制器标准写法 |
|
||||||
|
| `server/internal/logic/sys/curd_demo.go` | Logic 标准写法(含关联查询、导出、字段过滤) |
|
||||||
|
| `server/internal/model/input/sysin/curd_demo.go` | 入参/出参模型标准写法 |
|
||||||
|
| `server/internal/router/genrouter/curd_demo.go` | 主模块路由注册标准写法 |
|
||||||
|
| `web/src/api/curdDemo/index.ts` | 前端 API 封装标准写法 |
|
||||||
|
| `web/src/views/curdDemo/model.ts` | 前端 model 标准写法(含字典加载) |
|
||||||
|
| `web/src/views/curdDemo/index.vue` | 列表页标准写法 |
|
||||||
|
| `web/src/views/curdDemo/edit.vue` | 编辑页标准写法 |
|
||||||
|
|
||||||
|
**插件参考(以 hgexample 为标准):**
|
||||||
|
|
||||||
|
| 文件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `server/addons/hgexample/main.go` | 插件入口标准写法 |
|
||||||
|
| `server/addons/hgexample/global/init.go` | 插件 global 标准写法 |
|
||||||
|
| `server/addons/hgexample/router/admin.go` | 插件路由注册标准写法 |
|
||||||
|
| `server/addons/hgexample/router/genrouter/init.go` | 插件 genrouter 标准写法 |
|
||||||
|
| `server/addons/hgexample/router/genrouter/tenant_order.go` | 插件模块路由追加标准写法 |
|
||||||
|
| `server/addons/hgexample/logic/sys/tenant_order.go` | 插件业务逻辑标准写法 |
|
||||||
|
| `server/addons/hgexample/logic/sys/config.go` | 插件调用主模块服务标准写法 |
|
||||||
|
|
||||||
|
代码生成模板:
|
||||||
|
- 主模块 CRUD:`server/resource/generate/default/curd/`
|
||||||
|
- 插件脚手架:`server/resource/generate/default/addon/`
|
||||||
|
|
||||||
|
以上模板可参考逻辑,**禁止直接修改模板文件**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 错误处理规范
|
||||||
|
|
||||||
|
### 基本原则
|
||||||
|
|
||||||
|
- 业务错误直接用 `gerror.New("描述清晰的中文信息")` 返回,GoFrame 框架会统一处理响应码
|
||||||
|
- 错误信息要面向用户,清晰说明**发生了什么**,而不是堆栈路径
|
||||||
|
|
||||||
|
### 错误创建方式
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/gogf/gf/v2/errors/gerror"
|
||||||
|
|
||||||
|
// 普通业务错误(最常用)
|
||||||
|
return gerror.New("用户名已存在")
|
||||||
|
|
||||||
|
// 包装底层错误,追加上下文(调用第三方/dao 层出错时使用)
|
||||||
|
if err := dao.User.Ctx(ctx).Data(data).Insert(); err != nil {
|
||||||
|
return gerror.Wrap(err, "创建用户失败")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 带格式化参数
|
||||||
|
return gerror.Newf("订单 %d 状态不合法,当前状态:%d", in.Id, order.Status)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误传递原则
|
||||||
|
|
||||||
|
- **logic 层**:遇到底层错误用 `gerror.Wrap` 追加上下文后返回;纯业务校验失败用 `gerror.New` 直接返回
|
||||||
|
- **controller 层**:直接 `return` 透传 logic 的 error,**不再包装**,不打印日志(框架统一处理)
|
||||||
|
- **禁止**在 controller 层 `fmt.Println(err)` 或 `g.Log().Error` 打印已经会被框架记录的错误
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 缓存使用规范
|
||||||
|
|
||||||
|
### 统一使用 `cache.Instance()`
|
||||||
|
|
||||||
|
**禁止直接调用 `g.Redis().Do()`**,项目统一通过 `cache.Instance()` 操作缓存,底层适配器(Memory / Redis / File)由配置决定,业务层无需关心。
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"hotgo/internal/library/cache"
|
||||||
|
"github.com/gogf/gf/v2/os/gcache"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 设置缓存(带过期时间)
|
||||||
|
err := cache.Instance().Set(ctx, "user:info:1", userInfo, 30*time.Minute)
|
||||||
|
|
||||||
|
// 获取缓存
|
||||||
|
val, err := cache.Instance().Get(ctx, "user:info:1")
|
||||||
|
if err != nil || val.IsNil() {
|
||||||
|
// 缓存未命中,查数据库
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除缓存
|
||||||
|
_, err = cache.Instance().Remove(ctx, "user:info:1")
|
||||||
|
|
||||||
|
// GetOrSet:缓存不存在时自动执行函数并写入(推荐用于只读类缓存)
|
||||||
|
val, err := cache.Instance().GetOrSet(ctx, "user:info:1", func(ctx context.Context) (interface{}, error) {
|
||||||
|
return dao.AdminMember.Ctx(ctx).Where(...).One()
|
||||||
|
}, 30*time.Minute)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 缓存 Key 命名规范
|
||||||
|
|
||||||
|
- 格式:`模块:实体:唯一标识`,例如:`user:info:1`、`config:site:basic`、`dict:options:XxxStatus`
|
||||||
|
- 同一模块的 Key 统一在 `internal/consts/cache.go`(或对应模块的 consts 文件)中定义为常量,禁止散落在 logic 里硬编码
|
||||||
|
- 插件缓存 Key 加插件名前缀:`hgexample:order:1`
|
||||||
|
|
||||||
|
### 缓存使用原则
|
||||||
|
|
||||||
|
- 更新或删除数据后,**主动清除**相关缓存,不依赖过期自动失效
|
||||||
|
- 缓存时间:频繁更新的数据(配置、状态)用较短时间(5~30 分钟);基本不变的数据(字典、枚举)可用更长时间
|
||||||
|
- 缓存穿透场景:查询结果为空时也写入短时缓存(如 1 分钟)防止击穿
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 日志规范
|
||||||
|
|
||||||
|
### 日志级别选择
|
||||||
|
|
||||||
|
| 级别 | 方法 | 适用场景 |
|
||||||
|
|------|------|---------|
|
||||||
|
| Info | `g.Log().Info` / `g.Log().Infof` | 正常业务流程的关键节点(登录成功、订单创建、支付回调等) |
|
||||||
|
| Warning | `g.Log().Warning` / `g.Log().Warningf` | **遇到错误但不影响主流程**(重试、降级、外部服务异常、非致命校验失败) |
|
||||||
|
| Panic | `g.Log().Panic` / `g.Log().Panicf` | 极其严重、必须立即终止的情况(配置缺失、核心依赖不可用) |
|
||||||
|
|
||||||
|
### 使用示例
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Info:记录正常流程关键事件
|
||||||
|
g.Log().Infof(ctx, "用户登录成功 uid:%d ip:%s", member.Id, ip)
|
||||||
|
|
||||||
|
// Info:记录外部调用结果
|
||||||
|
g.Log().Info(ctx, "支付回调处理完成", g.Map{"orderId": orderId, "status": status})
|
||||||
|
|
||||||
|
// Warning:外部接口失败但已降级处理
|
||||||
|
g.Log().Warningf(ctx, "短信发送失败,已跳过 mobile:%s err:%+v", mobile, err)
|
||||||
|
|
||||||
|
// Warning:业务异常但不中断流程
|
||||||
|
g.Log().Warning(ctx, "用户余额不足,跳过自动扣款", g.Map{"uid": uid, "balance": balance})
|
||||||
|
|
||||||
|
// Panic:启动时核心配置缺失
|
||||||
|
g.Log().Panicf(ctx, "缓存未初始化,无法启动: %+v", err)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日志上下文原则
|
||||||
|
|
||||||
|
- 日志**必须传入 `ctx`**(第一个参数),便于链路追踪
|
||||||
|
- 关键日志需携带足够的业务字段(uid、orderId、requestId 等),用 `g.Map{}` 附加结构化信息
|
||||||
|
- **禁止**用 `fmt.Println` 替代日志,禁止在 logic/controller 层裸打印调试信息
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据库建表约定
|
||||||
|
|
||||||
|
建表时遵循以下字段命名约定,代码生成器和 ORM hook 会自动识别并处理:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `id` | bigint(20) NOT NULL AUTO_INCREMENT | 主键,必须 |
|
||||||
|
| `created_at` | datetime | 创建时间,**自动写入**,禁止手动赋值 |
|
||||||
|
| `updated_at` | datetime | 更新时间,**自动维护**,禁止手动赋值 |
|
||||||
|
| `deleted_at` | datetime | 软删除时间,存在时查询自动加 `IS NULL`,删除只更新此字段 |
|
||||||
|
| `created_by` | bigint(20) | 创建者 ID,**自动写入** |
|
||||||
|
| `updated_by` | bigint(20) | 更新者 ID,**自动维护** |
|
||||||
|
| `deleted_by` | bigint(20) | 删除者 ID,**自动写入** |
|
||||||
|
| `status` | tinyint | 状态字段,默认使用系统字典 `sys_normal_disable` |
|
||||||
|
| `sort` | int | 排序字段,存在时编辑表单自动获取最大值+1填充 |
|
||||||
|
| `pid` | bigint(20) | 树表上级ID(树表必须) |
|
||||||
|
| `level` | int | 树等级(树表必须,自动维护) |
|
||||||
|
| `tree` | varchar(512) | 关系树(树表必须,自动维护) |
|
||||||
|
| `tenant_id` | bigint(20) | 租户ID(多租户场景按需加) |
|
||||||
|
|
||||||
|
**特殊字段查询方式(代码生成器默认生成):**
|
||||||
|
- `string` 类型字段 → `LIKE` 模糊查询
|
||||||
|
- `int/uint/int64` → `=` 精确查询
|
||||||
|
- `created_by`/`updated_by`/`deleted_by` → 关键词查询(ID/用户名/手机号匹配),调用 `service.AdminMember().GetIdsByKeyword()`
|
||||||
|
|
||||||
|
**数据类型 → Go 类型映射(影响生成代码的字段类型):**
|
||||||
|
- `tinyint/int/smallint` → `int`;加 `unsigned` → `uint`
|
||||||
|
- `bigint` → `int64`;加 `unsigned` → `uint64`
|
||||||
|
- `varchar/text/char` → `string`
|
||||||
|
- `datetime/timestamp` → `*gtime.Time`
|
||||||
|
- `json/jsonb` → `*gjson.Json`
|
||||||
|
- `decimal/float/double` → `float64`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 数据权限(权限过滤)
|
||||||
|
|
||||||
|
业务查询中如需过滤数据权限(仅自己/所属部门/下级等),在 ORM 链式调用中加入 handler:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "hotgo/internal/library/hgorm/handler"
|
||||||
|
|
||||||
|
// 按登录用户的数据权限范围过滤(表需含 created_by 或 member_id 字段)
|
||||||
|
dao.XxxTable.Ctx(ctx).Handler(handler.FilterAuth).Scan(&res)
|
||||||
|
|
||||||
|
// 表中无标准字段时,指定自定义字段
|
||||||
|
dao.XxxTable.Ctx(ctx).Handler(handler.FilterAuthWithField("custom_field")).Scan(&res)
|
||||||
|
|
||||||
|
// 多租户数据权限过滤(表需含 tenant_id 字段)
|
||||||
|
// 在 Model() 方法中通过 handler.Option 开启
|
||||||
|
func (s *sSysXxx) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||||
|
if len(option) == 0 {
|
||||||
|
option = append(option, &handler.Option{FilterTenant: true})
|
||||||
|
}
|
||||||
|
return handler.Model(dao.XxxTable.Ctx(ctx), option...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
多租户增改时自动维护租户关系,在 ORM 操作后加 `.Hook(hook.SaveTenant)`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "hotgo/internal/library/hgorm/hook"
|
||||||
|
|
||||||
|
// 新增时自动写入 tenant_id/merchant_id/user_id
|
||||||
|
dao.XxxTable.Ctx(ctx).Fields(sysin.XxxInsertFields{}).Hook(hook.SaveTenant).Data(in).Insert()
|
||||||
|
|
||||||
|
// 更新时自动维护
|
||||||
|
s.Model(ctx).Fields(sysin.XxxUpdateFields{}).WherePri(in.Id).Data(in).Hook(hook.SaveTenant).Update()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 定时任务(Crons)
|
||||||
|
|
||||||
|
定时任务通过实现接口 + `init()` 注册,在后台「系统设置 → 定时任务」中配置执行策略:
|
||||||
|
|
||||||
|
**主模块**:新建 `server/internal/crons/<name>.go`
|
||||||
|
**插件**:新建 `server/addons/<addonName>/crons/<name>.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package crons
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"hotgo/internal/library/cron"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cron.Register(MyTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
var MyTask = &cMyTask{name: "myTask"} // name 与后台配置的任务名称一致
|
||||||
|
|
||||||
|
type cMyTask struct{ name string }
|
||||||
|
|
||||||
|
func (c *cMyTask) GetName() string { return c.name }
|
||||||
|
|
||||||
|
func (c *cMyTask) Execute(ctx context.Context, parser *cron.Parser) (err error) {
|
||||||
|
parser.Logger.Infof(ctx, "任务执行:%v", ...)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 消息队列(Queues)
|
||||||
|
|
||||||
|
队列采用接口 + `init()` 注册,topic 统一定义在 `internal/consts/queue.go`(或对应模块 consts 文件)。
|
||||||
|
|
||||||
|
**主模块**:新建 `server/internal/queues/<name>.go`
|
||||||
|
**插件**:新建 `server/addons/<addonName>/queues/<name>.go`
|
||||||
|
|
||||||
|
```go
|
||||||
|
package queues
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"hotgo/internal/consts"
|
||||||
|
"hotgo/internal/library/queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
queue.RegisterConsumer(MyConsumer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var MyConsumer = &qMyConsumer{}
|
||||||
|
|
||||||
|
type qMyConsumer struct{}
|
||||||
|
|
||||||
|
func (q *qMyConsumer) GetTopic() string { return consts.QueueMyTopic }
|
||||||
|
|
||||||
|
func (q *qMyConsumer) Handle(ctx context.Context, mqMsg queue.MqMsg) (err error) {
|
||||||
|
var data MyData
|
||||||
|
if err = json.Unmarshal(mqMsg.Body, &data); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 处理消息...
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**发送消息:**
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 即时消息
|
||||||
|
queue.Push(consts.QueueMyTopic, data)
|
||||||
|
|
||||||
|
// 延迟消息(仅 redis/rocketmq 驱动支持)
|
||||||
|
queue.SendDelayMsg(consts.QueueMyTopic, data, 10) // redis: 延迟秒数;rocketmq: 延迟级别
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工具库使用规范
|
||||||
|
|
||||||
|
**后端(`server/utility/`)优先使用已有工具,禁止重复造轮子:**
|
||||||
|
|
||||||
|
| 包 | 用途 |
|
||||||
|
|----|------|
|
||||||
|
| `utility/convert` | 数据类型转换 |
|
||||||
|
| `utility/encrypt` | 加密/解密 |
|
||||||
|
| `utility/excel` | 电子表格导出/导入(导出接口用此包) |
|
||||||
|
| `utility/validate` | 数据验证工具 |
|
||||||
|
| `utility/tree` | 树形结构处理 |
|
||||||
|
| `utility/simple` | 简捷函数集合 |
|
||||||
|
| `utility/format` | 数据格式化 |
|
||||||
|
|
||||||
|
**前端(`web/src/utils/`)优先使用已有工具:**
|
||||||
|
|
||||||
|
| 工具 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `@/utils/http/axios`(`http.request`) | HTTP 请求,**唯一入口** |
|
||||||
|
| `@/utils/dateUtil`(`defRangeShortcuts`) | 日期范围快捷选项 |
|
||||||
|
| `@/utils`(`renderImage`、`renderFile`、`renderPopoverMemberSumma`) | 表格渲染工具函数 |
|
||||||
|
| `@/store/modules/dict`(`useDictStore`) | 字典数据加载与渲染 |
|
||||||
|
| `@/hooks/web/usePermission`(`hasPermission`) | 权限判断 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 树表(Tree CRUD)特殊约定
|
||||||
|
|
||||||
|
树表在普通 CRUD 基础上有以下差异:
|
||||||
|
|
||||||
|
- 表中必须含 `pid`、`level`、`tree` 字段(参见建表约定)
|
||||||
|
- `pid`/`level`/`tree` 字段**由系统自动维护**,禁止在编辑表单中手动设置这三个字段
|
||||||
|
- 前端列表页使用树形表格(`BasicTable` 的 `childrenKey` 或 `treeData` 模式),而非普通分页列表
|
||||||
|
- 参考实现:`server/internal/logic/sys/tree_demo.go`、`web/src/views/develop/curd/treeDemo/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 响应格式约定
|
||||||
|
|
||||||
|
所有接口统一响应格式(由框架中间件自动处理,**无需在 controller 手动包装**):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": 0, "message": "操作成功", "timestamp": 1234567890, "traceID": "xxx", "data": {} }
|
||||||
|
```
|
||||||
|
|
||||||
|
- 成功状态码:`0`;失败状态码:`-1`(GoFrame 内置,无需手动设置)
|
||||||
|
- `gerror.Wrap(err, "用户友好的提示")` → 客户端只看到最外层提示,开发者日志可见完整堆栈
|
||||||
|
- 禁止在 controller 层手动 `Write`/`WriteJSON` 等直接写响应(破坏统一格式)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见错误禁止清单
|
||||||
|
|
||||||
|
**后端:**
|
||||||
|
- 禁止在 controller 层直接调用 dao
|
||||||
|
- 禁止硬编码字段名字符串(用 `dao.Table.Columns().Field`)
|
||||||
|
- 禁止手改 `entity/`、`do/`、`dao/internal/` 目录下的文件
|
||||||
|
- 禁止在 logic 层重复做 `Filter()` 已完成的校验
|
||||||
|
- 禁止在 `internal/service/sys.go` 之外新建主模块 service 文件(同文件追加)
|
||||||
|
- 禁止插件 import 另一个插件的包(插件间完全隔离,通过主模块 service 通信)
|
||||||
|
- 禁止在插件路由文件里硬编码路由前缀(用 `addons.RouterPrefix()` 生成)
|
||||||
|
- 禁止在 `internal/library/` 下修改基础库(如需扩展,提 issue 或在 utility/ 添加)
|
||||||
|
|
||||||
|
**前端:**
|
||||||
|
- 禁止直接使用 axios,必须用 `http.request`
|
||||||
|
- 禁止硬编码字典值文本,必须用 `dict.getLabel()` 渲染
|
||||||
|
- 禁止引入 Naive UI 以外的 UI 框架
|
||||||
|
- 禁止内联 style
|
||||||
|
- 禁止在源码里硬编码菜单路由路径
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021-present HotGo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
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
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,3 +1,167 @@
|
|||||||
# im_manager
|
# HotGo-V2
|
||||||
|
<div align="center">
|
||||||
|
<img width="140px" src="https://gmycos.facms.cn/hotgo/logo.sig.png">
|
||||||
|
<p>
|
||||||
|
<h1>HotGo V2</h1>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://goframe.org/pages/viewpage.action?pageId=1114119" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/goframe-2.10.0-green" alt="goframe">
|
||||||
|
</a>
|
||||||
|
<a href="https://v3.vuejs.org/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/vue.js-vue3.4-green" alt="vue">
|
||||||
|
</a>
|
||||||
|
<a href="https://www.naiveui.com" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/naiveui-%3E2.44.1-blue" alt="naiveui">
|
||||||
|
</a>
|
||||||
|
<a href="https://www.tslang.cn/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/typescript-%3E4.0.0-blue" alt="typescript">
|
||||||
|
</a>
|
||||||
|
<a href="https://vitejs.dev/" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/vite-%3E5.4.2-yellow" alt="vite">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/bufanyun/hotgo/blob/v2.0/LICENSE" target="_blank">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-success" alt="license">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
## 平台简介
|
||||||
|
* 基于 GoFrame2 + Vue3 + NaiveUI + uniapp 的企业级全栈框架,为二次开发而生。
|
||||||
|
* 前端采用 Naive-Ui-Admin、Vue、Naive UI、uniapp。
|
||||||
|
* 企业级 AI 赋能基础开发平台,内置 AI 开发规范,适配主流 AI 开发工具,实现人机协同开发,大幅提升开发效率。
|
||||||
|
|
||||||
|
## 演示地址
|
||||||
|
- [https://hotgo.facms.cn/admin](https://hotgo.facms.cn/admin)
|
||||||
|
> 账号:admin 密码:123456
|
||||||
|
|
||||||
|
### 使用文档
|
||||||
|
|
||||||
|
[安装文档](docs/guide-zh-CN/start-installation.md) · [本地文档](docs/guide-zh-CN/README.md) · [更新历史](docs/guide-zh-CN/start-update-log.md) · [常见问题](docs/guide-zh-CN/start-issue.md)
|
||||||
|
|
||||||
|
|
||||||
|
## 特征
|
||||||
|
* 高生产率:极强的可扩展性,应用化、模块化、插件化机制敏捷开发,几分钟即可搭建一个应用开发骨架。
|
||||||
|
* 多应用入口:多入口分为 Admin (后台)、Home (前台页面)、Api (对外通用接口)、WebSocket (即时通讯接口),不同的业务,进入不同的应用入口。
|
||||||
|
* 极致的插件化: 微核架构,功能隔离,高可定制性,可以渐进式开发,亦可以多人协同开发。支持一键创建插件模板、一键安装、更新、卸载插件、可以非常方便的将插件迁移到新项目中。
|
||||||
|
* 快速生成代码:无需编写代码,只需创建表进行简单配置就能生成一个完善的 CURD、树表等常用的开发代码,其中所需表单控件也是勾选即可直接生成。
|
||||||
|
* 认证机制:采用 JWT 的用户状态认证及 casbin 的权限认证
|
||||||
|
* 路由模式:得益于 GoFrame 提供了规范化的路由注册方式,无需注解自动生成api文档
|
||||||
|
* 模块化设计,面向接口开发
|
||||||
|
|
||||||
|
|
||||||
|
## 内置功能
|
||||||
|
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
|
2. 部门管理:配置系统组织机构(公司、部门、岗位),树结构展现支持数据权限。
|
||||||
|
3. 岗位管理:配置系统用户所属担任职务。
|
||||||
|
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
|
||||||
|
5. 角色管理:角色菜单权限分配、设置角色按机构或按上下级关系进行数据范围权限划分。
|
||||||
|
6. 字典管理:对系统中经常使用的一些特定数据进行维护,支持枚举字典和自定义方法字典。
|
||||||
|
7. 配置管理:对系统动态配置常用参数。
|
||||||
|
8. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||||
|
9. 登录日志:系统登录日志记录查询包含登录异常。
|
||||||
|
10. 服务日志:服务端运行所产生的警告、异常、崩溃日志的详细数据和堆栈信息。
|
||||||
|
11. 支付网关:集成支付宝、微信支付、QQ支付等多种支付方式,只需简单配置即可使用。
|
||||||
|
12. 资金管理:支持在线充值、订单申请/原路退款、资金提现、资金/积分变动明细等通用模块。
|
||||||
|
13. 在线用户:当前系统中活跃用户状态监控。
|
||||||
|
14. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
|
||||||
|
15. 代码生成:支持自动化生成前后端代码。CURD关联表、树表、消息队列、定时任务一键生成等。
|
||||||
|
16. 插件应用:支持一键生成插件模板,每个插件之间开发隔离,拥有独立多应用入口、独立配置。完美支持多人协同开发、插件插拔不会对原系统产生影响等。
|
||||||
|
17. 服务监控:监视当前系统CPU、内存、磁盘、网络、堆栈等相关信息。
|
||||||
|
18. 附件管理:文件图片上传,大文件分片上传、断点续传,支持本地、阿里云oss、腾讯云cos、ucloud对象存储、七牛云对象存储、minio等多种上传驱动,后台一键切换配置,并集成了文件选择器。
|
||||||
|
19. TCP服务:基于gtcp的服务应用,支持长连接、断线重连、服务认证、路由分发、RPC消息、拦截器和数据绑定等。简化和规范了服务器开发流程。
|
||||||
|
20. 消息队列:同时兼容 kafka、redis、rocketmq、磁盘队列,一键配置切换到场景适用的MQ。
|
||||||
|
21. 通知公告:采用WebSocket实时推送在线用户最新通知、公告、私信消息。
|
||||||
|
22. 地区编码:整合国内通用省市区编码,运用于项目于一身,支持动态省市区选项。
|
||||||
|
23. 常用工具:集成常用的工具包和命令行工具,可以快速开发自定义命令行,多种启动入口。
|
||||||
|
|
||||||
|
|
||||||
|
> HotGo开源以来得到了大家的很多支持,本项目初衷只为互相学习交流,没有任何盈利性目的!欢迎为HotGo贡献代码或提供建议!
|
||||||
|
|
||||||
|
|
||||||
|
## 演示图
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/1.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/2.png"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/3.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/4.png"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/5.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/6.png"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/7.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/8.png"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/9.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/10.png"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/11.png"/></td>
|
||||||
|
<td><img src="./docs/guide-zh-CN/images/demo/12.png"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
## 感谢(排名不分先后)
|
||||||
|
> gf框架 [https://github.com/gogf/gf](https://github.com/gogf/gf)
|
||||||
|
>
|
||||||
|
> naive-ui [https://www.naiveui.com](https://www.naiveui.com)
|
||||||
|
>
|
||||||
|
> naive-ui-admin [https://github.com/jekip/naive-ui-admin](https://github.com/jekip/naive-ui-admin)
|
||||||
|
>
|
||||||
|
> websocket [https://github.com/gorilla/websocket](github.com/gorilla/websocket)
|
||||||
|
>
|
||||||
|
> casbin [https://github.com/casbin/casbin](https://github.com/casbin/casbin)
|
||||||
|
>
|
||||||
|
> gopay [https://github.com/go-pay/gopay](https://github.com/go-pay/gopay)
|
||||||
|
|
||||||
|
|
||||||
|
## 交流QQ群
|
||||||
|
交流群①:190966648 <a target="_blank" href="https://qm.qq.com/cgi-bin/qm/qr?k=mJafkvme3VNyiQlCFIFNRtY8Xlr7pj9U&jump_from=webapi&authKey=jL10vIESr+vO8wpxwyd6DlChzkrbHpzN9uhAsIHgAinL/Vvd+nvuRyilf2UqUlCy"><img border="0" src="https://gmycos.facms.cn/hotgo/group.png" alt="HotGo框架交流1群" title="HotGo框架交流1群"></a>
|
||||||
|
> <img src="https://gmycos.facms.cn/hotgo/hotgo1qun.png" width="400px"/>
|
||||||
|
|
||||||
|
|
||||||
|
## 商用说明
|
||||||
|
|
||||||
|
> HotGo 是开源免费的,遵循 MIT 开源协议,意味着您无需支付任何费用,也无需授权,即可将它应用到您的产品中。
|
||||||
|
|
||||||
|
* 使用本项目必须保留所有版权信息。
|
||||||
|
|
||||||
|
* 本项目包含的第三方源码和二进制文件之版权信息另行标注。
|
||||||
|
|
||||||
|
* 版权所有Copyright © 2020-2026 by Ms (https://github.com/bufanyun/hotgo)
|
||||||
|
|
||||||
|
* All rights reserved。
|
||||||
|
|
||||||
|
|
||||||
|
## 免责声明:
|
||||||
|
* HotGo为开源学习项目,一切商业行为与HotGo无关。
|
||||||
|
|
||||||
|
* 用户不得利用HotGo从事非法行为,用户应当合法合规的使用,发现用户在使用产品时有任何的非法行为,HotGo有权配合有关机关进行调查或向政府部门举报,HotGo不承担用户因非法行为造成的任何法律责任,一切法律责任由用户自行承担,如因用户使用造成第三方损害的,用户应当依法予以赔偿。
|
||||||
|
|
||||||
|
* 所有与使用HotGo相关的资源直接风险均由用户承担。
|
||||||
|
|
||||||
|
|
||||||
|
#### 如果对您有帮助,您可以点右上角 💘Star💘支持
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [感谢JetBrains提供的免费GoLand](https://jb.gg/OpenSource)
|
||||||
|
[](https://jb.gg/OpenSource)
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
[MIT © HotGo-2026](./LICENSE)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
后台管理系统
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
## 目录
|
||||||
|
|
||||||
|
#### 介绍安装
|
||||||
|
|
||||||
|
- [系统介绍](../../README.md)
|
||||||
|
- [环境搭建](start-environment.md)
|
||||||
|
- [系统安装](start-installation.md)
|
||||||
|
- [生产部署](start-deploy.md)
|
||||||
|
- [如何提问](start-questions.md)
|
||||||
|
- [常见问题](start-issue.md)
|
||||||
|
- [更新历史](start-update-log.md)
|
||||||
|
|
||||||
|
|
||||||
|
#### 系统开发
|
||||||
|
|
||||||
|
- [目录结构](sys-catalog.md)
|
||||||
|
- [开发规范](sys-exploit.md)
|
||||||
|
- [控制台](sys-console.md)
|
||||||
|
- [中间件/拦截器](sys-middleware.md)
|
||||||
|
- [WebHook](sys-webhook.md)
|
||||||
|
- [权限控制](sys-auth.md)
|
||||||
|
- [支付网关](sys-payment.md)
|
||||||
|
- [定时任务](sys-cron.md)
|
||||||
|
- [消息队列](sys-queue.md)
|
||||||
|
- [功能扩展库](sys-library.md)
|
||||||
|
- [工具方法](sys-utility.md)
|
||||||
|
- [WebSocket服务器](sys-websocket-server.md)
|
||||||
|
- [TCP服务器](sys-tcp-server.md)
|
||||||
|
- [SaaS多租户](sys-tenant.md)
|
||||||
|
- [国际化](sys-i18n.md)
|
||||||
|
- [单元测试](sys-test.md)
|
||||||
|
|
||||||
|
|
||||||
|
#### 插件模块开发
|
||||||
|
|
||||||
|
- [模块介绍及目录](addon-introduce-catalog.md)
|
||||||
|
- [模块开发流程](addon-flow.md)
|
||||||
|
- [模块辅助说明](addon-helper.md)
|
||||||
|
|
||||||
|
|
||||||
|
#### 生成代码
|
||||||
|
- [使用前提](code-start.md)
|
||||||
|
- [数据库](sys-db.md)
|
||||||
|
- [生成配置](code-config.md)
|
||||||
|
- [生成CURD](code-curd.md)
|
||||||
|
- [生成关联表CURD](code-curd-join.md)
|
||||||
|
- [生成树型CURD](code-tree.md)
|
||||||
|
- [生成业务模板](code-business.md)
|
||||||
|
- [生成模板开发](code-template-dev.md)
|
||||||
|
- [生成常见问题](code-help.md)
|
||||||
|
|
||||||
|
|
||||||
|
### 前端开发
|
||||||
|
- [表单组件](web-form.md)
|
||||||
|
- [WebSocket客户端](sys-websocket-client.md)
|
||||||
|
- [独立部署](web-deploy.md)
|
||||||
|
|
||||||
|
|
||||||
|
#### 附录
|
||||||
|
- [网址收录](append-website.md)
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
## 模块开发流程
|
||||||
|
|
||||||
|
目录
|
||||||
|
|
||||||
|
- 创建新插件
|
||||||
|
- 开发
|
||||||
|
- 调用主模块服务接口
|
||||||
|
- 访问路径
|
||||||
|
- 数据迁移
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 创建新插件
|
||||||
|
|
||||||
|
1、HotGo 后台进入 开发工具->插件管理->找到创建新插件,根据引导进行创建即可。
|
||||||
|
|
||||||
|
```shell
|
||||||
|
创建成功后默认情况下会在以下目录中生成插件文件,假设新生成的插件名为:hgexample
|
||||||
|
|
||||||
|
1. /server/addons/hgexample/ # 插件模块目录
|
||||||
|
2. /server/addons/modules/hgexample.go # 隐式注册插件文件
|
||||||
|
3. /server/resource/addons/hgexample # 静态资源和页面模板目录,属于扩展功能选项,勾选对应选项后才会生成
|
||||||
|
4. /web/src/api/addons/hgexample # webApi目录
|
||||||
|
5. /web/src/views/addons/hgexample # web页面目录
|
||||||
|
|
||||||
|
# 默认情况下没有为web页面生成菜单权限,因为在实际场景中插件不一定需要用到web页面,所以如有需要请手动到后台 权限管理 -> 菜单权限->自行添加菜单和配置权限
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
2、创建插件完毕重启服务端后,插件管理中会出现你新创建的插件信息。操作栏有几个按钮,在此进行说明
|
||||||
|
- 安装:会自动执行 server/hgexample/main.go 文件中的Install方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。如生成后台菜单、生成插件配置表初始化数据、迁移home页面、web项目文件等。
|
||||||
|
```go
|
||||||
|
// Install 安装模块
|
||||||
|
func (m *module) Install(ctx context.Context) (err error) {
|
||||||
|
// ...
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 更新:会自动执行 server/hgexample/main.go 文件中的Upgrade方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。
|
||||||
|
```go
|
||||||
|
// Upgrade 更新模块
|
||||||
|
func (m *module) Upgrade(ctx context.Context) (err error) {
|
||||||
|
// ...
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 卸载:会自动执行 server/hgexample/main.go 文件中的UnInstall方法,方法中的具体逻辑默认为空,可以根据实际情况自行配置。如会清除所有的数据表和已安装的信息等。
|
||||||
|
```go
|
||||||
|
// UnInstall 卸载模块
|
||||||
|
func (m *module) UnInstall(ctx context.Context) (err error) {
|
||||||
|
// ...
|
||||||
|
return
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 开发
|
||||||
|
|
||||||
|
完全可以根据HotGo正常的开发流程去开发对应的API、控制器、业务逻辑、插件内的应用
|
||||||
|
|
||||||
|
### 调用主模块服务接口
|
||||||
|
|
||||||
|
这里推荐的方式是在插件input层新建一个结构,继承主模块中的input结构。这样做的目的是为了服务与服务之间的输入/输出关系解耦,便于参数扩展和避免插件模块下使用`gf gen service`时出现`import cycle not allowed`。
|
||||||
|
|
||||||
|
一个简单的例子:
|
||||||
|
> 假设hgexample插件模块要通过主模块的服务接口更新插件配置
|
||||||
|
|
||||||
|
插件模块input:\server\addons\hgexample\model\input\sysin\config.go
|
||||||
|
```go
|
||||||
|
package sysin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hotgo/internal/model/input/sysin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateConfigInp 更新指定配置
|
||||||
|
type UpdateConfigInp struct {
|
||||||
|
sysin.UpdateAddonsConfigInp
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
插件模块业务逻辑:\server\addons\hgexample\logic\sys\config.go
|
||||||
|
```go
|
||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"hotgo/addons/hgexample/global"
|
||||||
|
"hotgo/addons/hgexample/model/input/sysin"
|
||||||
|
"hotgo/addons/hgexample/service"
|
||||||
|
isc "hotgo/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sSysConfig struct{}
|
||||||
|
|
||||||
|
func NewSysConfig() *sSysConfig {
|
||||||
|
return &sSysConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
service.RegisterSysConfig(NewSysConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfigByGroup 更新指定分组的配置
|
||||||
|
func (s *sSysConfig) UpdateConfigByGroup(ctx context.Context, in sysin.UpdateConfigInp) error {
|
||||||
|
in.UpdateAddonsConfigInp.AddonName = global.GetSkeleton().Name
|
||||||
|
return isc.SysAddonsConfig().UpdateConfigByGroup(ctx, in.UpdateAddonsConfigInp)
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
主模块input:\server\internal\model\input\sysin\addons_config.go
|
||||||
|
```go
|
||||||
|
package sysin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UpdateAddonsConfigInp 更新指定插件的配置
|
||||||
|
type UpdateAddonsConfigInp struct {
|
||||||
|
AddonName string `json:"addonName"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
List g.Map `json:"list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
主模块业务逻辑:\server\internal\logic\sys\addons_config.go
|
||||||
|
```go
|
||||||
|
package sys
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"hotgo/internal/model/input/sysin"
|
||||||
|
"hotgo/internal/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sSysAddonsConfig struct{}
|
||||||
|
|
||||||
|
func NewSysAddonsConfig() *sSysAddonsConfig {
|
||||||
|
return &sSysAddonsConfig{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
service.RegisterSysAddonsConfig(NewSysAddonsConfig())
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfigByGroup 更新指定分组的配置
|
||||||
|
func (s *sSysAddonsConfig) UpdateConfigByGroup(ctx context.Context, in sysin.UpdateAddonsConfigInp) error {
|
||||||
|
// ...
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 访问路径
|
||||||
|
|
||||||
|
#### 后台插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/admin/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/admin/hgexample/index/test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应控制器路径:`server/addons/hgexample/controller/admin/sys/index.go`
|
||||||
|
|
||||||
|
#### 前端API插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/api/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/api/hgexample/index/test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应控制器路径:`server/addons/hgexample/controller/api/index.go`
|
||||||
|
|
||||||
|
#### 前台页面插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/home/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/home/hgexample/index/test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应控制器路径:`server/addons/hgexample/controller/home/index.go`
|
||||||
|
- 对应模板路径:`server/addons/hgexample/resource/public/template`
|
||||||
|
|
||||||
|
#### 静态资源插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/home/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/addons/hgexample/default
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应资源路径:`server/addons/hgexample/resource/public`
|
||||||
|
|
||||||
|
|
||||||
|
#### Websocket插件访问路径
|
||||||
|
|
||||||
|
```
|
||||||
|
// IP+端口或域名/socket/插件名称/API路径
|
||||||
|
如:127.0.0.1:8000/socket/hgexample/index/test
|
||||||
|
```
|
||||||
|
|
||||||
|
- 对应控制器路径:`server/addons/hgexample/controller/socket/index.go`
|
||||||
|
|
||||||
|
|
||||||
|
### 数据迁移
|
||||||
|
|
||||||
|
- 可以将数据迁移逻辑写进server/xxx插件/main.go 文件中的Install方法中,并遵循系统规范进行数据安装
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
## 模块辅助说明
|
||||||
|
|
||||||
|
目录
|
||||||
|
|
||||||
|
- 模块结构
|
||||||
|
- 获取模块信息
|
||||||
|
- 插件路由规则
|
||||||
|
|
||||||
|
|
||||||
|
#### 模块结构
|
||||||
|
- 文件路径:server/internal/library/addons/module.go
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Skeleton 模块骨架
|
||||||
|
type Skeleton struct {
|
||||||
|
Label string `json:"label"` // 标识
|
||||||
|
Name string `json:"name"` // 名称
|
||||||
|
Group int `json:"group"` // 分组
|
||||||
|
Logo string `json:"logo"` // logo
|
||||||
|
Brief string `json:"brief"` // 简介
|
||||||
|
Description string `json:"description"` // 详细描述
|
||||||
|
Author string `json:"author"` // 作者
|
||||||
|
Version string `json:"version"` // 版本号
|
||||||
|
RootPath string `json:"rootPath"` // 根路径
|
||||||
|
View *gview.View `json:"view"` // 模板引擎
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Skeleton) GetModule() Module {
|
||||||
|
return GetModule(s.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module 插件模块
|
||||||
|
type Module interface {
|
||||||
|
Start(option *Option) (err error) // 启动模块
|
||||||
|
Stop() (err error) // 停止模块
|
||||||
|
Ctx() context.Context // 上下文
|
||||||
|
GetSkeleton() *Skeleton // 获取模块
|
||||||
|
Install(ctx context.Context) (err error) // 安装模块
|
||||||
|
Upgrade(ctx context.Context) (err error) // 更新模块
|
||||||
|
UnInstall(ctx context.Context) (err error) // 卸载模块
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 获取模块信息
|
||||||
|
|
||||||
|
- 在插件模块内
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hotgo/addons/hgexample/global"
|
||||||
|
)
|
||||||
|
|
||||||
|
func test() {
|
||||||
|
fmt.Printf("当前插件模块是:%+v", global.GetSkeleton())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 在插件模块外
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"hotgo/internal/library/addons"
|
||||||
|
"hotgo/internal/library/contexts"
|
||||||
|
)
|
||||||
|
|
||||||
|
func test(ctx context.Context) {
|
||||||
|
fmt.Printf("当前是否为插件请求:%v", contexts.IsAddonRequest(ctx))
|
||||||
|
if contexts.IsAddonRequest(ctx) {
|
||||||
|
fmt.Printf("当前插件名称:%v", contexts.GetAddonName(ctx))
|
||||||
|
fmt.Printf("当前插件信息:%v", addons.GetModule(contexts.GetAddonName(ctx)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- 更多辅助方法请参考插件功能库:server/internal/library/addons
|
||||||
|
|
||||||
|
#### 插件路由规则
|
||||||
|
- 如果你不喜欢现在的路由风格,可以自行调整。修改位置在:\server\internal\library\addons\addons.go的RouterPrefix方法。
|
||||||
|
- 注意调整后如web前端页面中如有之前的路由风格也需同步修改。
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
## 模块介绍及目录
|
||||||
|
|
||||||
|
目录
|
||||||
|
|
||||||
|
- 模块介绍
|
||||||
|
- 启动流程
|
||||||
|
- 目录结构
|
||||||
|
|
||||||
|
### 模块介绍
|
||||||
|
|
||||||
|
> 定位:开发独立、临时性、工具类型的功能时推荐使用插件化开发,例如:小游戏(大转盘/消消乐/抽奖/大屏互动/红包等)、小插件(广告管理/文章管理/友情链接等等)、小模块(报名/投票/签到)、小程序、大型插件微商城等等。
|
||||||
|
|
||||||
|
> 插件模块方便多项目复用,同时完美支持多人协同开发,每个插件模块都有独立的微架构目录结构,多插件之间完全隔离。
|
||||||
|
|
||||||
|
### 启动流程
|
||||||
|
|
||||||
|
HotGo 入口文件->隐式注入(hotgo/addons/modules)->注册所有插件->初始化已安装的插件->写入路由组->根据 HotGo 正常的开发和访问流程去开发访问插件
|
||||||
|
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
- 详细介绍请参考:[目录结构](sys-catalog.md)
|
||||||
|
```
|
||||||
|
/server
|
||||||
|
├── addons
|
||||||
|
│ ├── modules
|
||||||
|
│ ├── xxx插件
|
||||||
|
│ | ├── api
|
||||||
|
│ | ├── consts
|
||||||
|
│ | ├── controller
|
||||||
|
│ | ├── crons
|
||||||
|
│ | ├── global
|
||||||
|
│ | ├── logic
|
||||||
|
│ | ├── model
|
||||||
|
│ | ├── queues
|
||||||
|
│ | ├── resource
|
||||||
|
│ | ├── router
|
||||||
|
│ | ├── service
|
||||||
|
│ | ├── main.go
|
||||||
|
│ | └── README.md
|
||||||
|
```
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
目录
|
||||||
|
|
||||||
|
- 框架文档
|
||||||
|
- [goframe](https://goframe.org/pages/viewpage.action?pageId=1114119)
|
||||||
|
- [naiveui](https://www.naiveui.com)
|
||||||
|
- [naive-ui-admin](https://docs.naiveadmin.com/)
|
||||||
|
|
||||||
|
|
||||||
|
- 常用组件
|
||||||
|
- [websocket](https://github.com/gorilla/websocket)
|
||||||
|
- [casbin](https://github.com/casbin/casbin)
|
||||||
|
- [tailwind](https://tailwind.docs.73zls.com/docs/flex-direction)
|
||||||
|
|
||||||
|
|
||||||
|
- 系统通用
|
||||||
|
- [redis](https://redis.io/)
|
||||||
|
|
||||||
|
|
||||||
|
- 其他
|
||||||
|
- [awesome-go](https://github.com/avelino/awesome-go)
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
## 生成业务模板
|
||||||
|
|
||||||
|
根据`api`接口文件一键生成业务模板:api、controller、logic、service
|
||||||
|
|
||||||
|
待写。
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
## 生成配置
|
||||||
|
|
||||||
|
目录
|
||||||
|
|
||||||
|
- 模板配置
|
||||||
|
- CLI配置
|
||||||
|
- 多数据库使用
|
||||||
|
|
||||||
|
### 模板配置
|
||||||
|
|
||||||
|
- 配置路径:server/manifest/config/config.yaml
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 生成代码
|
||||||
|
hggen:
|
||||||
|
allowedIPs: [ "127.0.0.1", "*" ] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能
|
||||||
|
selectDbs: [ "default" ] # 可选生成表的数据库配置名称,支持多库
|
||||||
|
disableTables: [ "hg_sys_gen_codes","hg_admin_role_casbin" ] # 禁用的表,禁用以后将不会在选择表中看到
|
||||||
|
delimiters: [ "@{", "}" ] # 模板引擎变量分隔符号
|
||||||
|
# 生成应用模型,所有生成模板允许自定义,可以参考default模板进行改造
|
||||||
|
application:
|
||||||
|
# CRUD和关系树列表模板
|
||||||
|
crud:
|
||||||
|
templates:
|
||||||
|
# 默认的主包模板
|
||||||
|
- group: "default" # 分组名称
|
||||||
|
isAddon: false # 是否为插件模板 false|true
|
||||||
|
masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联
|
||||||
|
templatePath: "./resource/generate/default/curd" # 模板路径
|
||||||
|
apiPath: "./api/admin" # goApi生成路径
|
||||||
|
controllerPath: "./internal/controller/admin/sys" # 控制器生成路径
|
||||||
|
logicPath: "./internal/logic/sys" # 主要业务生成路径
|
||||||
|
inputPath: "./internal/model/input/sysin" # 表单过滤器生成路径
|
||||||
|
routerPath: "./internal/router/genrouter" # 生成路由表路径
|
||||||
|
sqlPath: "./storage/data/generate" # 生成sql语句路径
|
||||||
|
webApiPath: "../web/src/api" # webApi生成路径
|
||||||
|
webViewsPath: "../web/src/views" # web页面生成路径
|
||||||
|
|
||||||
|
# 默认的插件包模板,{$name}会自动替换成实际的插件名称
|
||||||
|
- group: "addon" # 分组名称
|
||||||
|
isAddon: true # 是否为插件模板 false|true
|
||||||
|
masterPackage: "sys" # 主包名称,需和controllerPath、logicPath、inputPath保持关联
|
||||||
|
templatePath: "./resource/generate/default/curd" # 模板路径
|
||||||
|
apiPath: "./addons/{$name}/api/admin" # goApi生成路径
|
||||||
|
controllerPath: "./addons/{$name}/controller/admin/sys" # 控制器生成路径
|
||||||
|
logicPath: "./addons/{$name}/logic/sys" # 主要业务生成路径
|
||||||
|
inputPath: "./addons/{$name}/model/input/sysin" # 表单过滤器生成路径
|
||||||
|
routerPath: "./addons/{$name}/router/genrouter" # 生成路由表路径
|
||||||
|
sqlPath: "./storage/data/generate/addons" # 生成sql语句路径
|
||||||
|
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
|
||||||
|
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
|
||||||
|
|
||||||
|
# 消息队列模板
|
||||||
|
queue:
|
||||||
|
templates:
|
||||||
|
- group: "default"
|
||||||
|
templatePath: "./resource/generate/default/queue"
|
||||||
|
|
||||||
|
# 定时任务模板
|
||||||
|
cron:
|
||||||
|
templates:
|
||||||
|
- group: "default"
|
||||||
|
templatePath: "./resource/generate/default/cron"
|
||||||
|
|
||||||
|
# 生成插件模块,通过后台创建新插件时使用的模板,允许自定义,可以参考default模板进行改造
|
||||||
|
addon:
|
||||||
|
srcPath: "./resource/generate/default/addon" # 生成模板路径
|
||||||
|
webApiPath: "../web/src/api/addons/{$name}" # webApi生成路径
|
||||||
|
webViewsPath: "../web/src/views/addons/{$name}" # web页面生成路径
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI配置
|
||||||
|
|
||||||
|
- hotgo在生成dao、service配置时,默认了和gf官方一致的配置方式和代码生成规则。所以无论你是通过hotgo亦或gf命令生成,最终代码格式完全一致,遵循一致的代码规范。
|
||||||
|
|
||||||
|
- 配置路径:[server/hack/config.yaml](../../server/hack/config.yaml)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
gfcli:
|
||||||
|
build:
|
||||||
|
name: "hotgo" # 编译后的可执行文件名称
|
||||||
|
# arch: "all" #不填默认当前系统架构,可选:386,amd64,arm,all
|
||||||
|
# system: "all" #不填默认当前系统平台,可选:linux,darwin,windows,all
|
||||||
|
mod: "none"
|
||||||
|
cgo: 0
|
||||||
|
packSrc: "resource" # 将resource目录打包进可执行文件,静态资源无需单独部署
|
||||||
|
packDst: "internal/packed/packed.go" # 打包后生成的Go文件路径,一般使用相对路径指定到本项目目录中
|
||||||
|
version: ""
|
||||||
|
output: "./temp/hotgo" # 可执行文件生成路径
|
||||||
|
extra: ""
|
||||||
|
|
||||||
|
gen:
|
||||||
|
dao:
|
||||||
|
- link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
|
||||||
|
# - link: "pgsql:postgres:hg123456@tcp(127.0.0.1:5432)/hotgo"
|
||||||
|
group: "default" # 分组 使用hotgo代码生成功能时必须填
|
||||||
|
# tables: "" # 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。
|
||||||
|
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
|
||||||
|
removePrefix: "hg_"
|
||||||
|
descriptionTag: true
|
||||||
|
noModelComment: true
|
||||||
|
jsonCase: "CamelLower"
|
||||||
|
gJsonSupport: true
|
||||||
|
clear: true
|
||||||
|
|
||||||
|
service: # 生成业务配置
|
||||||
|
srcFolder: "internal/logic"
|
||||||
|
dstFolder: "internal/service"
|
||||||
|
dstFileNameCase: "CamelLower"
|
||||||
|
clear: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 多数据库使用
|
||||||
|
|
||||||
|
- 假设我们要增加一个库名为`hotgo2`、分组为`default2`的数据库,并要为其生成代码
|
||||||
|
|
||||||
|
1. 配置[server/hack/config.yaml](../../server/hack/config.yaml) 如下:
|
||||||
|
```yaml
|
||||||
|
gen:
|
||||||
|
dao:
|
||||||
|
- link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
|
||||||
|
group: "default" # 分组 使用hotgo代码生成功能时必须填
|
||||||
|
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
|
||||||
|
removePrefix: "hg_"
|
||||||
|
descriptionTag: true
|
||||||
|
noModelComment: true
|
||||||
|
jsonCase: "CamelLower"
|
||||||
|
gJsonSupport: true
|
||||||
|
clear: false
|
||||||
|
- link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true"
|
||||||
|
group: "default2" # 分组 使用hotgo代码生成功能时必须填
|
||||||
|
tablesEx: "hg_sys_addons_install" # 指定当前数据库中需要排除代码生成的数据表。
|
||||||
|
removePrefix: ""
|
||||||
|
descriptionTag: true
|
||||||
|
noModelComment: true
|
||||||
|
jsonCase: "CamelLower"
|
||||||
|
gJsonSupport: true
|
||||||
|
clear: false
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 配置`server/manifest/config/config.yaml`,
|
||||||
|
|
||||||
|
`database`配置如下:
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
logger:
|
||||||
|
level: "all"
|
||||||
|
stdout: true
|
||||||
|
default:
|
||||||
|
link: "mysql:hotgo:hg123456.@tcp(127.0.0.1:3306)/hotgo?loc=Local&parseTime=true"
|
||||||
|
# link: "pgsql:postgres:hg123456@tcp(127.0.0.1:5432)/hotgo"
|
||||||
|
debug: true
|
||||||
|
Prefix: "hg_"
|
||||||
|
default2:
|
||||||
|
link: "mysql:hotgo2:hg123456.@tcp(127.0.0.1:3306)/hotgo2?loc=Local&parseTime=true"
|
||||||
|
debug: true
|
||||||
|
Prefix: ""
|
||||||
|
```
|
||||||
|
|
||||||
|
`hggen`配置如下:
|
||||||
|
```yaml
|
||||||
|
hggen:
|
||||||
|
allowedIPs: ["127.0.0.1", "*"] # 白名单,*代表所有,只有允许的IP后台才能使用生成代码功能
|
||||||
|
selectDbs: [ "default", "default2" ] # 可选生成表的数据库配置名称,支持多库
|
||||||
|
disableTables : ["hg_sys_gen_codes","hg_admin_role_casbin"] # 禁用的表,禁用以后将不会在选择表中看到
|
||||||
|
delimiters: ["@{", "}"] # 模板引擎变量分隔符号
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,就会发现`数据库`选项增加了一个`default2`,后续生成步骤和生成例子完全一样
|
||||||
|
|
||||||
|
> 注意:上述的配置中所有的`default2`名称必须保持一致
|
||||||
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
## 生成关联表CURD
|
||||||
|
|
||||||
|
### 热编译启动
|
||||||
|
- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 服务端
|
||||||
|
cd server
|
||||||
|
gf run main.go
|
||||||
|
|
||||||
|
# web端
|
||||||
|
cd web
|
||||||
|
pnpm run dev 或 npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
以下是一个关联表的CURD生成流程
|
||||||
|
|
||||||
|
### 创建表结构
|
||||||
|
- 以下表结构和数据为了方便功能演示已经内置无需再次创建
|
||||||
|
|
||||||
|
|
||||||
|
- 创建主表:hg_sys_gen_curd_demo
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `hg_sys_gen_curd_demo` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`category_id` bigint(20) DEFAULT '0' COMMENT '分类ID',
|
||||||
|
`title` varchar(64) NOT NULL COMMENT '标题',
|
||||||
|
`description` varchar(255) DEFAULT '' COMMENT '描述',
|
||||||
|
`content` text COMMENT '内容',
|
||||||
|
`image` varchar(255) DEFAULT NULL COMMENT '单图',
|
||||||
|
`attachfile` varchar(255) DEFAULT NULL COMMENT '附件',
|
||||||
|
`city_id` bigint(20) DEFAULT '0' COMMENT '所在城市',
|
||||||
|
`switch` int(11) DEFAULT '1' COMMENT '显示开关',
|
||||||
|
`sort` int(11) DEFAULT NULL COMMENT '排序',
|
||||||
|
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
|
||||||
|
`created_by` bigint(20) DEFAULT '0' COMMENT '创建者',
|
||||||
|
`updated_by` bigint(20) DEFAULT '0' COMMENT '更新者',
|
||||||
|
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COMMENT='系统_生成curd演示';
|
||||||
|
```
|
||||||
|
|
||||||
|
创建关联表:hg_test_category
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `hg_test_category` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
|
||||||
|
`name` varchar(255) NOT NULL COMMENT '分类名称',
|
||||||
|
`short_name` varchar(128) DEFAULT NULL COMMENT '简称',
|
||||||
|
`description` varchar(255) DEFAULT NULL COMMENT '描述',
|
||||||
|
`sort` int(11) NOT NULL COMMENT '排序',
|
||||||
|
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
||||||
|
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
|
||||||
|
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='测试分类';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表字段要求
|
||||||
|
- 使用生成时,表中必须具有以下字段和属性
|
||||||
|
|
||||||
|
| 字段名称 | 字段含义 | 字段类型 | 可为空 |
|
||||||
|
|--------|----------------------|---------------------|-----|
|
||||||
|
| id | 主键ID | bigint(20) | 否 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 创建生成配置
|
||||||
|
- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 基本设置
|
||||||
|
- 确认无误后,点击生成配置会跳转到生成配置页面,如下:
|
||||||
|
- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 主表字段设置
|
||||||
|
|
||||||
|
- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 关联表字段设置
|
||||||
|
- 在该页面你可以调整生成表格关联表字段名称、列表展示/查询项、字段排序、重置和同步字段
|
||||||
|
- 如果存在多个关联表,也可以对多个关联表字段进行设置
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 预览并生成
|
||||||
|
|
||||||
|
点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
|
||||||
|
|
||||||
|
|
||||||
|
### 生成完成
|
||||||
|
|
||||||
|
- 让我们看看生成的表格页面,效果如下:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
- [生成常见问题](code-help.md)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
## 生成CURD
|
||||||
|
|
||||||
|
### 热编译启动
|
||||||
|
- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 服务端
|
||||||
|
cd server
|
||||||
|
gf run main.go
|
||||||
|
|
||||||
|
# web端
|
||||||
|
cd web
|
||||||
|
pnpm run dev 或 npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
以下是一个基本的CURD生成流程
|
||||||
|
|
||||||
|
### 创建表结构
|
||||||
|
- 以下表结构和数据为了方便功能演示已经内置无需再次创建
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `hg_test_category` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
|
||||||
|
`name` varchar(255) NOT NULL COMMENT '分类名称',
|
||||||
|
`short_name` varchar(128) DEFAULT NULL COMMENT '简称',
|
||||||
|
`description` varchar(255) DEFAULT NULL COMMENT '描述',
|
||||||
|
`sort` int(11) NOT NULL COMMENT '排序',
|
||||||
|
`remark` varchar(255) DEFAULT NULL COMMENT '备注',
|
||||||
|
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
|
||||||
|
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='测试分类';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表字段要求
|
||||||
|
- 使用生成时,表中必须具有以下字段和属性
|
||||||
|
|
||||||
|
| 字段名称 | 字段含义 | 字段类型 | 可为空 |
|
||||||
|
|--------|----------------------|---------------------|-----|
|
||||||
|
| id | 主键ID | bigint(20) | 否 |
|
||||||
|
|
||||||
|
|
||||||
|
### 创建生成配置
|
||||||
|
- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 基本设置
|
||||||
|
- 确认无误后,点击生成配置会跳转到生成配置页面,如下:
|
||||||
|
- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 表字段设置
|
||||||
|
|
||||||
|
- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 预览并生成
|
||||||
|
|
||||||
|
点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
|
||||||
|
|
||||||
|
|
||||||
|
### 生成完成
|
||||||
|
|
||||||
|
- 让我们看看生成的表格页面,效果如下:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
- [生成常见问题](code-help.md)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
## 生成常见问题
|
||||||
|
|
||||||
|
### 生成完后页面提示:服务器错误,请稍候重试!
|
||||||
|
|
||||||
|
- 热编译环境下,web端往往会快于服务端重启并加载完成,此时接口访问会出现`服务器错误,请稍候重试!`或`404`。这是服务端正在重启导致的,一般稍等几秒就好,如果不行就手动重启下服务端。
|
||||||
|
|
||||||
|
|
||||||
|
### fetching tables failed: SHOW TABLES: Error 1045 (28000): Access denied for user * (using password: YES)
|
||||||
|
|
||||||
|
- 请去确认`server/manifest/config/config.yaml`和`server/hack/config.yaml`下的数据库配置一致并且权限正确
|
||||||
|
- 参考:[生成配置](code-config.md)
|
||||||
|
|
||||||
|
|
||||||
|
### 为什么后台找不到开发工具菜单
|
||||||
|
|
||||||
|
- 请去确认`server/manifest/config/config.yaml`中的`system.mode`不为`product`。product模式下后台【开发工具】菜单自动隐藏
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
## 使用前提
|
||||||
|
|
||||||
|
|
||||||
|
在HotGo中可以通过后台开发工具快速的一键生成CRUD,自动生成Api、控制器、业务逻辑、Web页面、表单组件、菜单权限等。
|
||||||
|
|
||||||
|
|
||||||
|
- hotgo 版本号 >= 2.13.1
|
||||||
|
- 使用前必须配置 [生成配置](code-config.md)
|
||||||
|
- 使用前必须了解 [数据库](sys-db.md)
|
||||||
|
|
||||||
|
|
||||||
|
相关目录
|
||||||
|
|
||||||
|
- [生成配置](code-config.md)
|
||||||
|
- [生成CURD](code-curd.md)
|
||||||
|
- [生成关联表CURD](code-curd-join.md)
|
||||||
|
- [生成树型CURD](code-tree.md)
|
||||||
|
- [生成业务模板](code-business.md)
|
||||||
|
- [生成模板开发](code-template-dev.md)
|
||||||
|
- [生成常见问题](code-help.md)
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
## 生成模板开发
|
||||||
|
|
||||||
|
### 自定义生成模板
|
||||||
|
|
||||||
|
- HotGo允许你新建新的模板分组来满足你的需求,模板可根据现有模板基础拷贝一份出来做改造,默认模板目录:[server/resource/generate/default](../../server/resource/generate/default)
|
||||||
|
|
||||||
|
- 系统内置了两组CURD生成模板,请参考[生成模板配置](sys-code.md#生成模板配置)。default:是默认的生成到主模块下;addon:是默认生成到指定的插件下
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 内置gf-cli
|
||||||
|
|
||||||
|
- 为了确保生成代码的依赖稳定性,在面对`gf`版本更新可能导致向下不兼容情况时,HotGo将`gf-cli`工具内置到系统中并进行在线执行调整,从而提供更可靠和一致的生成代码功能。
|
||||||
|
|
||||||
|
- 后续我们也将开放在线运行`gf gen ...`功能。在做插件开发时也会支持到在线生成插件下的service接口,这将会使得插件开发更加方便
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 指定gf-cli版本
|
||||||
|
|
||||||
|
- HotGo多数情况下会和最新版本的gf-cli保持同步,如果更新不及时或你不想使用最新版本的gf-cli来生成代码,可以找到自己想要的版本进行替换即可。
|
||||||
|
|
||||||
|
- 下面大致做一些替换步骤说明:
|
||||||
|
|
||||||
|
1. 打开`github.com/gogf/gf` 找到你想要使用的版本`clone`下来
|
||||||
|
2. 将`clone`代码中`gf/cmd/gf/internal/`目录覆盖到`server/internal/library/hggen/internal`
|
||||||
|
3. 将覆盖过来的目录文件中引入包名`github.com/gogf/gf/cmd/gf/v2/`批量改为`hotgo/internal/library/hggen/`
|
||||||
|
4. 运行`go mod tidy`
|
||||||
|
5. 运行`go run main.go`,如果没有报错,那么恭喜你已经完成了。如果有报错一般都是版本差异带来的影响,需要根据情况自行调整
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 指定数据库驱动
|
||||||
|
|
||||||
|
> HotGo默认使用mysql驱动,如果你想用其他数据库驱动打开下方文件中注释即可,目前已支持mysql、pgsql
|
||||||
|
|
||||||
|
- 修改文件路径:[server/internal/library/hggen/internal/cmd/cmd_gen_dao.go](../../server/internal/library/hggen/internal/cmd/cmd_gen_dao.go)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
//_ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
|
||||||
|
//_ "github.com/gogf/gf/contrib/drivers/mssql/v2"
|
||||||
|
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||||
|
//_ "github.com/gogf/gf/contrib/drivers/oracle/v2"
|
||||||
|
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
||||||
|
//_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||||
|
|
||||||
|
"hotgo/internal/library/hggen/internal/cmd/gendao"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
cGenDao = gendao.CGenDao
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
修改完成后运行`go mod tidy`
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
## 生成树型CURD
|
||||||
|
|
||||||
|
### 热编译启动
|
||||||
|
- 推荐使用热编译方式启动HotGo,这样生成完成页面自动刷新即可看到新生成内容,无需手动重启
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# 服务端
|
||||||
|
cd server
|
||||||
|
gf run main.go
|
||||||
|
|
||||||
|
# web端
|
||||||
|
cd web
|
||||||
|
pnpm run dev 或 npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
以下是一个基本的树形CURD生成流程
|
||||||
|
|
||||||
|
### 创建表结构
|
||||||
|
- 以下表结构和数据为了方便功能演示已经内置无需再次创建
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `hg_sys_gen_tree_demo` (
|
||||||
|
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
|
||||||
|
`pid` bigint(20) DEFAULT NULL COMMENT '上级ID',
|
||||||
|
`level` int(11) DEFAULT '1' COMMENT '关系树级别',
|
||||||
|
`tree` varchar(512) COMMENT '关系树',
|
||||||
|
`category_id` bigint(20) DEFAULT '0' COMMENT '分类ID',
|
||||||
|
`title` varchar(64) NOT NULL COMMENT '标题',
|
||||||
|
`description` varchar(255) DEFAULT '' COMMENT '描述',
|
||||||
|
`sort` int(11) DEFAULT NULL COMMENT '排序',
|
||||||
|
`status` tinyint(1) DEFAULT '1' COMMENT '状态',
|
||||||
|
`created_by` bigint(20) DEFAULT '0' COMMENT '创建者',
|
||||||
|
`updated_by` bigint(20) DEFAULT '0' COMMENT '更新者',
|
||||||
|
`created_at` datetime DEFAULT NULL COMMENT '创建时间',
|
||||||
|
`updated_at` datetime DEFAULT NULL COMMENT '修改时间',
|
||||||
|
`deleted_at` datetime DEFAULT NULL COMMENT '删除时间',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4 COMMENT='系统_生成树表演示';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表字段要求
|
||||||
|
- 使用生成时,表中必须具有以下字段和属性
|
||||||
|
|
||||||
|
| 字段名称 | 字段含义 | 字段类型 | 可为空 |
|
||||||
|
|--------|----------------------|---------------------|-----|
|
||||||
|
| id | 主键ID | bigint(20) | 否 |
|
||||||
|
| pid | 上级树ID | bigint(20) | 是 |
|
||||||
|
| level | 关系树级别 | int(11) | 否 |
|
||||||
|
| tree | 关系树 | varchar(512) | 是 |
|
||||||
|
|
||||||
|
### 创建生成配置
|
||||||
|
- 登录HotGo后台 -> 开发工具 -> 代码生成 -> 找到立即生成按钮并打开,选择和填写如下参数:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### 基本设置
|
||||||
|
- 确认无误后,点击生成配置会跳转到生成配置页面,如下:
|
||||||
|
- 你可以在该页面调整生成表格表头/表列的接口功能、菜单权限、高级设置和关联表设置
|
||||||
|
- 相比普通CURD,这里多了`树名称字段`和`树表格样式`选项
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 表字段设置
|
||||||
|
|
||||||
|
- 在该页面你可以调整生成表格字段名称、表单组件、表单编辑/验证项、列表展示/查询项、字段排序、重置和同步字段
|
||||||
|
- 树表中的pid、level、tree字段由系统维护,单独设置编辑表单功能无效!
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 预览并生成
|
||||||
|
|
||||||
|
点击`预览代码`查看生成的代码内容。如果无需继续调整直接点击`提交生成`即可,以下是预览代码效果:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
- 如果你使用的热编译,那么页面会在生成成功后立即刷新,刷新完成你即可在后台菜单栏中看到`测试表格`菜单。如果不是使用热编译启动,请手动重启服务后刷新。
|
||||||
|
|
||||||
|
|
||||||
|
### 生成完成
|
||||||
|
|
||||||
|
- 让我们看看生成的表格页面,效果如下:
|
||||||
|
|
||||||
|
- 普通树表
|
||||||
|

|
||||||
|
|
||||||
|
- 选项式树表
|
||||||
|

|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
|
||||||
|
- [生成常见问题](code-help.md)
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 197 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 125 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Reference in New Issue
Block a user