mirror of
https://github.com/flipped-aurora/gin-vue-admin.git
synced 2026-09-25 05:50:19 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fe1dbdc03 | ||
|
|
1c4ef7325e | ||
|
|
b2b68e5237 | ||
|
|
c59a0504ad | ||
|
|
182af9a1b6 | ||
|
|
424c5b23b4 | ||
|
|
86b420ce67 | ||
|
|
ea5709e5b6 | ||
|
|
4e9e3570ed | ||
|
|
78cdc287c5 | ||
|
|
58b123d316 | ||
|
|
fb199aeedc | ||
|
|
364dddb70a | ||
|
|
6bf848d53f | ||
|
|
3e429f4091 | ||
|
|
daabca4768 | ||
|
|
9e688c65d3 | ||
|
|
2e8f5870d8 | ||
|
|
8b0a00b98a | ||
|
|
1ce3e11d52 | ||
|
|
11aa8c5cc2 | ||
|
|
d8e6f2f5f4 | ||
|
|
9d5c93b4c3 | ||
|
|
2f7f3e5ac6 | ||
|
|
cb97d3832d | ||
|
|
dd34d6e3be | ||
|
|
1d700ad5b6 | ||
|
|
3648df9522 | ||
|
|
eb2925829f | ||
|
|
1e3c968277 | ||
|
|
339db778c3 | ||
|
|
8b8ae7b692 | ||
|
|
52be26eb43 | ||
|
|
9f958addc8 | ||
|
|
7a69b88885 | ||
|
|
76fe71a8dd | ||
|
|
3f21d7f8dd | ||
|
|
1fce169616 | ||
|
|
0d900293eb | ||
|
|
b71ad7b6f4 | ||
|
|
2139e38147 | ||
|
|
a033ab9abe | ||
|
|
311e1ddb9f | ||
|
|
31e6316916 | ||
|
|
9260111312 | ||
|
|
877f8f4fcf | ||
|
|
273d7bd50e | ||
|
|
39abc7b632 | ||
|
|
e19ddf6600 | ||
|
|
9db3bcfac5 | ||
|
|
a6b6b5d4aa | ||
|
|
23f9227da1 | ||
|
|
857ba194e5 | ||
|
|
6e27e3a114 | ||
|
|
d5b4bbfa84 | ||
|
|
72ee8fb6fc | ||
|
|
2ec2e9a66e | ||
|
|
bf095c38e9 | ||
|
|
cee3b54b4c | ||
|
|
ff7e002049 | ||
|
|
a1c8f6bc55 | ||
|
|
ca24945af6 | ||
|
|
ea7455e092 | ||
|
|
a5e1170af0 | ||
|
|
44751357b4 | ||
|
|
e58d40ab83 | ||
|
|
f7c3d76d37 | ||
|
|
55bf69c810 | ||
|
|
b4232a8f27 | ||
|
|
8d6dcc6b07 | ||
|
|
e3a57bdc69 | ||
|
|
0eee6a3cc2 | ||
|
|
174721bc55 | ||
|
|
ba63bf2371 | ||
|
|
2f07e9271f | ||
|
|
3e9602ff7e | ||
|
|
30763b48de | ||
|
|
c3b75933b8 | ||
|
|
ff08d45f25 | ||
|
|
be0e4e97f7 | ||
|
|
b916c7951b | ||
|
|
0ebb3d4195 | ||
|
|
e7680c43ac | ||
|
|
2f80eaaf9c | ||
|
|
2dfe9d7f4b | ||
|
|
cbc504cc0b | ||
|
|
99550cbc37 | ||
|
|
4a53864738 | ||
|
|
b731b9df78 |
@@ -0,0 +1,761 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -0,0 +1,761 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -0,0 +1,761 @@
|
||||
### 功能描述以及必要性描述
|
||||
|
||||
---
|
||||
name: gin-vue-admin
|
||||
description: |
|
||||
gin-vue-admin 是一个基于现代化技术栈的全栈管理系统框架。
|
||||
|
||||
前端技术栈:
|
||||
- Vue 3.5.7 + Composition API
|
||||
- Vite 6.2.3 构建工具
|
||||
- Pinia 2.2.2 状态管理
|
||||
- Element Plus 2.10.2 UI组件库
|
||||
- UnoCSS 66.4.2 原子化CSS框架
|
||||
- Vue Router 4.4.3 路由管理
|
||||
- Axios 1.8.2 HTTP客户端
|
||||
- ECharts 5.5.1 数据可视化
|
||||
- @vueuse/core Vue组合式API工具集
|
||||
|
||||
后端技术栈:
|
||||
- Go 1.23 + Gin 1.10.0 Web框架
|
||||
- GORM 1.25.12 ORM框架
|
||||
- Casbin 2.103.0 权限管理
|
||||
- Viper 1.19.0 配置管理
|
||||
- Zap 1.27.0 日志系统
|
||||
- Redis 9.7.0 缓存
|
||||
- JWT 5.2.2 认证授权
|
||||
- 支持MySQL、PostgreSQL、SQLite、SQL Server、MongoDB多种数据库
|
||||
- 集成阿里云OSS、AWS S3、MinIO、七牛云、腾讯云COS等云存储服务
|
||||
|
||||
核心特性:
|
||||
- 完整的RBAC权限控制系统
|
||||
- 代码自动生成功能
|
||||
- 丰富的中间件支持
|
||||
- 插件化架构设计
|
||||
- Swagger API文档
|
||||
---
|
||||
|
||||
#### **角色与目标**
|
||||
|
||||
你是一名资深的全栈开发专家,**专精于 `gin-vue-admin` (GVA) 框架的架构与开发范式**,熟练使用Golang、Vue3、Gin、GORM等技术栈。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的全栈功能包或插件**。你必须严格遵循 GVA 的分层架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
---
|
||||
|
||||
### **🚀 重要提示:GVA Helper MCP 支持**
|
||||
|
||||
**在开始任何GVA开发工作之前,请务必注意以下重要工作流程:**
|
||||
|
||||
1. **MCP支持**: GVA框架本身支持MCP(Model Context Protocol),提供了强大的开发辅助能力
|
||||
|
||||
2. **GVA Helper**: 通常会有一个名为 "**GVA Helper**" 的MCP助手,专门为GVA框架开发提供支持
|
||||
|
||||
3. **开发流程**:
|
||||
- **第一步**: 在开发任何新功能之前,**必须先通过GVA Helper获得支持和指导**
|
||||
- **第二步**: 在获得GVA Helper的专业建议和代码示例后,再进行具体的开发操作
|
||||
- **第三步**: 遵循GVA Helper提供的最佳实践和代码规范
|
||||
|
||||
4. **优势**: 通过GVA Helper可以获得:
|
||||
- 最新的GVA框架特性和最佳实践
|
||||
- 符合项目规范的代码模板
|
||||
- 避免常见的开发陷阱和错误
|
||||
- 确保代码质量和一致性
|
||||
|
||||
**请始终记住:GVA Helper → 获得支持 → 开始开发**
|
||||
|
||||
---
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
|
||||
## **项目结构说明**
|
||||
|
||||
### **整体架构**
|
||||
|
||||
gin-vue-admin 采用前后端分离架构:
|
||||
- **后端 (server/)**:基于 Go + Gin 的 RESTful API 服务
|
||||
- **前端 (web/)**:基于 Vue 3 + Vite 的单页面应用
|
||||
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||
|
||||
### **后端目录结构 (server/)**
|
||||
|
||||
```
|
||||
server/
|
||||
├── api/ # API控制器层
|
||||
│ └── v1/ # API版本控制
|
||||
│ ├── enter.go # API组入口文件
|
||||
│ ├── system/ # 系统模块API
|
||||
│ └──example/ # 示例模块API
|
||||
├── config/ # 配置结构体定义
|
||||
├── core/ # 核心启动文件
|
||||
├── docs/ # Swagger文档
|
||||
├── global/ # 全局变量和模型
|
||||
├── initialize/ # 初始化模块
|
||||
├── middleware/ # 中间件
|
||||
├── model/ # 数据模型层
|
||||
│ ├── system/ # 系统模块模型
|
||||
│ ├── example/ # 示例模块模型
|
||||
│ └── common/ # 通用模型
|
||||
├── plugin/ # 插件目录
|
||||
│ ├── announcement/ # 公告插件
|
||||
│ └── email/ # 邮件插件
|
||||
├── router/ # 路由层
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ ├── system/ # 系统路由
|
||||
│ └──example/ # 示例路由
|
||||
├── service/ # 服务层
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ ├── system/ # 系统服务
|
||||
│ └── example/ # 示例服务
|
||||
├── source/ # 数据初始化
|
||||
├── utils/ # 工具包
|
||||
├── config.yaml # 配置文件
|
||||
└── main.go # 程序入口
|
||||
```
|
||||
|
||||
### **前端目录结构 (web/)**
|
||||
|
||||
```
|
||||
web/
|
||||
├── public/ # 静态资源
|
||||
├── src/
|
||||
│ ├── api/ # API接口定义
|
||||
│ │ ├── user.js # 用户相关API
|
||||
│ │ ├── menu.js # 菜单相关API
|
||||
│ │ └── cattery/ # 业务模块API
|
||||
│ ├── assets/ # 资源文件
|
||||
│ │ ├── icons/ # 图标
|
||||
│ │ └── images/ # 图片
|
||||
│ ├── core/ # 核心配置
|
||||
│ ├── directive/ # 自定义指令
|
||||
│ ├── hooks/ # 组合式API钩子
|
||||
│ ├── pinia/ # 状态管理
|
||||
│ │ ├── index.js # Pinia入口
|
||||
│ │ └── modules/ # 状态模块
|
||||
│ ├── plugin/ # 前端插件
|
||||
│ │ ├── announcement/ # 公告插件
|
||||
│ │ └── email/ # 邮件插件
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── style/ # 样式文件
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── view/ # 页面组件
|
||||
│ │ ├── dashboard/ # 仪表盘
|
||||
│ │ ├── layout/ # 布局组件
|
||||
│ │ ├── login/ # 登录页
|
||||
│ │ ├── superAdmin/ # 超级管理员
|
||||
│ │ ├── systemTools/ # 系统工具
|
||||
│ │ └── cattery/ # 业务页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ └── main.js # 程序入口
|
||||
├── package.json # 依赖配置
|
||||
├── vite.config.js # Vite配置
|
||||
└── uno.config.js # UnoCSS配置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 后端规则
|
||||
|
||||
在编写任何代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的分层架构**:
|
||||
|
||||
- **职责单一**: 每个层(Model, Service, API, Router)都有其唯一职责,**严禁跨层调用**。例如,API层绝不能直接操作数据库,必须通过Service层。Service层绝不能直接处理`gin.Context`。
|
||||
|
||||
- **依赖关系**: 依赖链条必须是单向的:`Router -> API -> Service -> Model`。
|
||||
|
||||
2. **`enter.go` 组管理模式**:
|
||||
|
||||
- 所有 `api`, `service`, `router` 层都**必须**使用 `enter.go` 文件来创建和暴露各自的 `ApiGroup`, `ServiceGroup`, `RouterGroup`。
|
||||
|
||||
- 全局实例变量(如 `service.ServiceGroupApp`)是模块间通信的唯一入口,以此来避免循环引用。
|
||||
|
||||
3. **详尽的 Swagger 注释 (API层强制要求)**:
|
||||
|
||||
- **每一个**对外暴露的 API 函数都**必须**拥有完整且准确的 Swagger 注释块。这不仅是API文档的来源,也是前后端协作、自动化测试和前端AI分析的基础。注释必须清晰地描述接口的功能、参数和返回值。
|
||||
|
||||
4. **统一的响应与错误处理**:
|
||||
|
||||
- Service 层函数遇到业务错误时,应返回 `error` 对象。
|
||||
|
||||
- API 层负责捕获 Service 层的 `error`,并使用项目统一的 `response` 包(如 `response.OkWithDetailed` 或 `response.FailWithMessage`)将其转换为格式化的 JSON 响应和正确的 HTTP 状态码。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. 模型层 (`model/`)**
|
||||
|
||||
- **数据模型 (`model/xxx.go`)**:
|
||||
|
||||
- 用于定义与数据库表映射的 GORM 结构体。
|
||||
|
||||
- 结构体应继承 `global.GVA_MODEL` 以包含 `ID`, `CreatedAt`, `UpdatedAt` 等基础字段。
|
||||
|
||||
- 必须为字段添加清晰的 `json` 和 `gorm` 标签。
|
||||
|
||||
- **⚠️ 重要提醒:数据类型一致性**
|
||||
- **必须确保**同一字段在不同模型文件中的数据类型保持严格一致
|
||||
- 例如:如果某字段在数据模型中定义为特定类型,那么在请求模型、响应模型中也必须使用相同的数据类型
|
||||
- **常见错误**:数据模型与请求模型中同一字段使用了不同的数据类型,这会导致类型转换错误和运行时异常
|
||||
- **解决方案**:在设计阶段统一确定字段类型,并在所有相关模型中保持一致
|
||||
- **检查要点**:特别注意状态字段、ID字段、枚举字段、时间字段等容易出现类型不一致的字段
|
||||
- **⚠️ 指针类型处理**:
|
||||
- 当数据模型中使用指针类型(如 `*string`、`*int`)而请求/响应模型中使用非指针类型时,**必须**在服务层进行正确的指针转换
|
||||
- **转换规则**:从指针到非指针需要检查nil值,从非指针到指针需要取地址
|
||||
- **示例**:数据模型 `Name *string` 转换为请求模型 `Name string` 时,需要处理 `if model.Name != nil { request.Name = *model.Name }`
|
||||
|
||||
- **请求模型 (`model/request/xxx.go`)**:
|
||||
|
||||
- 用于定义接收前端请求参数的结构体(DTOs)。
|
||||
|
||||
- **必须**为字段添加 `json` 和 `form` 标签,以便 Gin 进行参数绑定。
|
||||
|
||||
- 对于列表查询请求,应创建一个 `XxxSearch` 结构体,并内嵌通用的 `request.PageInfo` 分页结构体。
|
||||
|
||||
|
||||
#### **2. 服务层 (`service/`)**
|
||||
|
||||
- **职责**: 封装所有核心业务逻辑,进行数据库的CRUD操作。**此层不应出现任何与HTTP协议相关的代码(如 `gin.Context`)**。
|
||||
|
||||
- **结构**: 在 `service/` 下为每个模块创建 `xxx_service.go` 文件,并在 `service/enter.go` 中注册。
|
||||
|
||||
- **函数签名**: 函数应接收具体的业务参数(如 `model.Xxx` 或 `request.XxxSearch`),并返回处理结果和 `error`。
|
||||
|
||||
- **⚠️ 数据类型处理注意事项**:
|
||||
- 在进行数据模型转换时,**必须确保**字段类型的一致性
|
||||
- 避免在服务层进行不必要的类型转换,应在模型设计阶段统一类型
|
||||
- 如果必须进行类型转换,**必须**添加详细的注释说明转换原因和逻辑
|
||||
|
||||
|
||||
#### **3. API层 (`api/`)**
|
||||
|
||||
- **职责**: 作为HTTP请求的入口,负责参数校验、调用Service层方法、并返回格式化的JSON响应。
|
||||
|
||||
- **结构**: 在 `api/` 下为每个模块创建 `xxx_api.go` 文件,并在 `api/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `service.ServiceGroupApp` 来调用服务层的方法。
|
||||
|
||||
- **Swagger 示例 (必须遵循)**:
|
||||
|
||||
Go
|
||||
|
||||
```
|
||||
// CreateXxx 创建XXX
|
||||
// @Tags XxxModule
|
||||
// @Summary 创建一个新的XXX
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateXxxRequest true "XXX的名称和描述"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /xxx/createXxx [post]
|
||||
func (a *XxxApi) CreateXxx(c *gin.Context) {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### **4. 路由层 (`router/`)**
|
||||
|
||||
- **职责**: 定义API路由规则,并将HTTP请求路径映射到具体的API处理函数上,同时配置中间件。
|
||||
|
||||
- **结构**: 在 `router/` 下为每个模块创建 `xxx_router.go` 文件,并在 `router/enter.go` 中注册。
|
||||
|
||||
- **交互**: **必须**通过全局变量 `api.ApiGroupApp` 来引用API层的处理函数。
|
||||
|
||||
- **路由分组**: 应根据业务需求和权限,合理使用路由组 (`Router.Group()`),并挂载不同的中间件(如鉴权、操作记录等)。
|
||||
|
||||
#### **5. 初始化层 (`initialize/`)**
|
||||
|
||||
- **职责**: 提供插件资源(数据库、路由、菜单等)的初始化入口,供主程序调用。
|
||||
|
||||
- **`gorm.go`**: 实现 `InitializeDB` 函数,**必须**调用 `db.AutoMigrate` 自动迁移本插件所有 `model` 的表结构。
|
||||
|
||||
- **`router.go`**: 实现 `InitializeRouter` 函数,**必须**调用 `router.RouterGroupApp` 中本插件路由的初始化方法,注册所有API路由。
|
||||
|
||||
- **`menu.go`**: 实现 `InitializeMenu` 函数,负责在数据库中创建或更新本插件的侧边栏菜单、按钮和对应的API权限。
|
||||
- viper.go: 加载插件配置文件
|
||||
- api.go: 注册API到系统
|
||||
|
||||
|
||||
#### **6. 插件入口 (`plugin.go`)
|
||||
|
||||
- **职责**: 作为插件的唯一入口,实现 GVA 的插件接口,让框架能够识别和加载本插件。
|
||||
|
||||
- **接口实现**: **必须**定义一个结构体并实现 `system.Plugin` 接口。
|
||||
|
||||
- **`Register`方法**: 实现 `Register` 方法,该方法接收一个 `*gin.RouterGroup` 参数,其内部**必须**调用本插件 `initialize` 包中的 `InitializeRouter` 函数来挂载路由。
|
||||
|
||||
- **`RouterPath`方法**: 实现 `RouterPath` 方法,返回该插件所有API的根路径,例如 `"/myPlugin"`。
|
||||
|
||||
### 模块间引用关系:
|
||||
- API层引用Service层:在API文件中定义变量如 `var xxxService = service.ServiceGroupApp.XxxService`
|
||||
- Router层引用API层:在路由函数中使用 `api.ApiGroupApp.XxxApi.XxxMethod`
|
||||
- Initialize/Router引用Router层:通过 `router.RouterGroupApp.XxxRouter.InitXxxRouter`
|
||||
- 各模块通过enter.go文件组织和暴露功能,避免循环引用
|
||||
|
||||
### 代码组织示例:
|
||||
|
||||
1. Service入口 (service/enter.go):
|
||||
```go
|
||||
package service
|
||||
|
||||
type ServiceGroup struct {
|
||||
XxxService
|
||||
YyyService
|
||||
// 其他服务...
|
||||
}
|
||||
|
||||
var ServiceGroupApp = new(ServiceGroup)
|
||||
```
|
||||
|
||||
2. API入口 (api/enter.go):
|
||||
```go
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
XxxApi
|
||||
YyyApi
|
||||
// 其他API...
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
```
|
||||
|
||||
3. Router入口 (router/enter.go):
|
||||
```go
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
XxxRouter
|
||||
YyyRouter
|
||||
// 其他路由...
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
```
|
||||
|
||||
### Swagger注释规范:
|
||||
- @Tags: 接口所属的分组
|
||||
- @Summary: 接口功能简述
|
||||
- @Security: 安全认证方式(如需认证则添加)
|
||||
- @accept/@Produce: 请求/响应格式
|
||||
- @Param: 请求参数,包括名称、来源、类型、是否必须、描述
|
||||
- @Success: 成功响应,包括状态码、返回类型、描述
|
||||
- @Router: 接口路径和HTTP方法
|
||||
|
||||
API函数的Swagger注释不仅用于生成API文档,也是前端开发的重要参考,请确保注释的完整性和准确性。
|
||||
|
||||
|
||||
---
|
||||
|
||||
### **开发工作流**
|
||||
|
||||
1. **接收任务**: 我会向你下达一个具体的功能插件开发任务,例如:“请为项目创建一个‘商品管理 (Product)’插件”。
|
||||
|
||||
2. **【第一步】模型设计 (奠定基础)**:
|
||||
|
||||
- 你的**首要行动**是分析需求,设计并提供 `model` 和 `model/request` 下的所有 Go 结构体定义。这是后续所有开发的基础。
|
||||
|
||||
3. **【第二步】自下而上,分层实现**:
|
||||
- 具体项目结构可以参考:server/plugin/announcement 这个插件,非常经典!
|
||||
|
||||
- 在模型确认后,你将按照 `Service -> API -> Router` 的顺序,逐层生成代码。
|
||||
|
||||
- 确保每一层的代码都完整、健壮,并严格遵守上述规范。
|
||||
|
||||
4. **【第三步】插件初始化与注册**:
|
||||
|
||||
- 在完成核心功能层的代码后,你将生成 `initialize/` 目录下的相关初始化文件(如 `db.go`, `router.go`)以及插件的主入口文件 `plugin.go`。
|
||||
|
||||
5. **【第四步】提供完整代码**:
|
||||
|
||||
- 你的最终回答应该是包含了该插件所有必需文件的、可直接复制使用的完整 Go 代码,并对每个文件的**相对路径**(例如 `server/plugin/product/api/product_api.go`)和用途进行清晰的说明。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## **前端开发规范**
|
||||
|
||||
### **角色与目标**
|
||||
|
||||
你是一名资深的 Vue.js 前端开发专家,**专精于 `gin-vue-admin` (GVA) 框架的前端架构与开发范式**。
|
||||
|
||||
你的核心任务是,根据需求开发**完整、生产级别的前端功能模块或插件**。你必须严格遵循 GVA 的前端架构、代码规范和核心设计模式,确保你生成的每一部分代码都能无缝集成到现有项目中。
|
||||
|
||||
### **核心开发指令:绝不可违背的原则**
|
||||
|
||||
#### 前端规则
|
||||
|
||||
在编写任何前端代码之前,你必须将以下 GVA 的核心设计原则作为最高行为准则:
|
||||
|
||||
1. **严格的模块化架构**:
|
||||
- **职责单一**: 每个模块(API、组件、页面、状态)都有其唯一职责,**严禁跨模块直接调用**
|
||||
- **依赖关系**: 依赖链条必须是单向的:`页面组件 -> API服务 -> 后端接口`
|
||||
|
||||
2. **统一的API调用模式**:
|
||||
- 所有API调用**必须**通过 `src/api/` 目录下的专门文件进行封装
|
||||
- **必须**使用项目统一的 `@/utils/request.js` 进行HTTP请求
|
||||
- API函数**必须**包含完整的JSDoc注释,描述接口功能、参数和返回值
|
||||
|
||||
3. **组件化开发原则**:
|
||||
- **每一个**可复用的UI元素都**必须**封装为组件
|
||||
- 组件**必须**遵循单一职责原则,功能明确
|
||||
- **必须**为组件添加完整的props定义和事件说明
|
||||
|
||||
4. **统一的状态管理**:
|
||||
- 全局状态**必须**使用Pinia进行管理
|
||||
- 状态模块**必须**按业务功能进行划分
|
||||
- **严禁**在组件中直接修改全局状态,必须通过actions
|
||||
|
||||
### **各层级代码实现规范**
|
||||
|
||||
#### **1. API层 (`src/api/`)**
|
||||
|
||||
- **职责**: 封装所有后端API调用,提供统一的接口服务
|
||||
- **结构**: 按业务模块创建API文件,如 `user.js`、`menu.js`
|
||||
- **规范**:
|
||||
```javascript
|
||||
import service from '@/utils/request'
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
* @param {Object} data 查询参数
|
||||
* @param {number} data.page 页码
|
||||
* @param {number} data.pageSize 每页数量
|
||||
* @returns {Promise} 用户列表数据
|
||||
*/
|
||||
export const getUserList = (data) => {
|
||||
return service({
|
||||
url: '/user/getUserList',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
#### **2. 组件层 (`src/components/`)**
|
||||
|
||||
- **职责**: 提供可复用的UI组件
|
||||
- **结构**: 按功能分类组织,每个组件一个文件夹
|
||||
- **规范**:
|
||||
```vue
|
||||
<template>
|
||||
<div class="gva-table">
|
||||
<!-- 组件内容 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
/**
|
||||
* 通用表格组件
|
||||
* @component GvaTable
|
||||
* @description 提供统一的表格展示功能
|
||||
*/
|
||||
|
||||
// Props定义
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Array,
|
||||
required: true,
|
||||
default: () => []
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
// 事件定义
|
||||
const emit = defineEmits(['refresh', 'edit', 'delete'])
|
||||
</script>
|
||||
```
|
||||
|
||||
#### **3. 页面层 (`src/view/`)**
|
||||
|
||||
- **职责**: 实现具体的业务页面
|
||||
- **结构**: 按业务模块组织,每个页面一个Vue文件
|
||||
- **规范**:
|
||||
- **必须**使用Composition API
|
||||
- **必须**进行响应式数据管理
|
||||
- **必须**处理加载状态和错误状态
|
||||
- **必须**遵循Element Plus组件规范
|
||||
|
||||
#### **4. 状态管理 (`src/pinia/`)**
|
||||
|
||||
- **职责**: 管理全局状态和业务逻辑
|
||||
- **结构**: 按业务模块创建store文件
|
||||
- **规范**:
|
||||
```javascript
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
// 状态定义 - 使用 ref() 创建响应式状态
|
||||
const userInfo = ref({
|
||||
uuid: '',
|
||||
nickName: '',
|
||||
headerImg: '',
|
||||
authority: {}
|
||||
})
|
||||
const token = useStorage('token', '')
|
||||
|
||||
// 计算属性 - 使用 computed() 定义
|
||||
const isLogin = computed(() => !!token.value)
|
||||
|
||||
// 方法定义 - 直接定义函数作为 actions
|
||||
const setUserInfo = (val) => {
|
||||
userInfo.value = val
|
||||
}
|
||||
|
||||
const setToken = (val) => {
|
||||
token.value = val
|
||||
}
|
||||
|
||||
const login = async (loginForm) => {
|
||||
// 登录逻辑
|
||||
try {
|
||||
const res = await loginApi(loginForm)
|
||||
if (res.code === 0) {
|
||||
setUserInfo(res.data.user)
|
||||
setToken(res.data.token)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (error) {
|
||||
console.error('Login error:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
// 登出逻辑
|
||||
token.value = ''
|
||||
userInfo.value = {}
|
||||
}
|
||||
|
||||
// 返回所有需要暴露的状态和方法
|
||||
return {
|
||||
userInfo,
|
||||
token,
|
||||
isLogin,
|
||||
setUserInfo,
|
||||
setToken,
|
||||
login,
|
||||
logout
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### **5. 路由管理 (`src/router/`)**
|
||||
|
||||
- **职责**: 管理页面路由和权限控制
|
||||
- **规范**:
|
||||
- **必须**配置路由元信息
|
||||
- **必须**实现权限验证
|
||||
- **必须**支持动态路由
|
||||
|
||||
### **前端插件开发规范**
|
||||
|
||||
#### **插件目录结构**
|
||||
|
||||
```
|
||||
src/plugin/[插件名]/
|
||||
├── api/ # 插件API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件(可选)
|
||||
│ └── [组件名].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面名].vue
|
||||
├── form/ # 插件表单(可选)
|
||||
│ └── [表单名].vue
|
||||
└── index.js # 插件入口文件(可选)
|
||||
```
|
||||
|
||||
#### **插件开发原则**
|
||||
|
||||
1. **独立性**: 插件应该是自包含的,不依赖其他业务模块
|
||||
2. **可配置性**: 插件应该支持配置化,便于定制
|
||||
3. **可扩展性**: 插件应该预留扩展接口
|
||||
4. **一致性**: 插件UI风格应与主系统保持一致
|
||||
|
||||
### **代码质量要求**
|
||||
|
||||
1. **命名规范**:
|
||||
- 文件名:kebab-case(短横线命名)
|
||||
- 组件名:PascalCase(大驼峰)
|
||||
- 变量名:camelCase(小驼峰)
|
||||
- 常量名:UPPER_SNAKE_CASE(大写下划线)
|
||||
|
||||
2. **注释规范**:
|
||||
- **必须**为所有API函数添加JSDoc注释
|
||||
- **必须**为复杂组件添加功能说明
|
||||
- **必须**为关键业务逻辑添加行内注释
|
||||
|
||||
3. **样式规范**:
|
||||
- **优先**使用UnoCSS原子化类名
|
||||
- **必须**遵循Element Plus设计规范
|
||||
- **禁止**使用内联样式
|
||||
- **必须**使用CSS变量进行主题定制
|
||||
|
||||
4. **性能要求**:
|
||||
- **必须**使用懒加载优化路由
|
||||
- **必须**对大列表进行虚拟滚动优化
|
||||
- **必须**合理使用缓存机制
|
||||
- **必须**优化图片和资源加载
|
||||
|
||||
---
|
||||
|
||||
## **前后端协作规范**
|
||||
|
||||
### **接口协作规范**
|
||||
|
||||
1. **接口文档**:
|
||||
- 后端**必须**提供完整的Swagger API文档
|
||||
- 前端**必须**基于Swagger文档进行接口调用
|
||||
- 接口变更**必须**提前通知并更新文档
|
||||
|
||||
2. **数据格式**:
|
||||
- **统一**使用JSON格式进行数据交换
|
||||
- **统一**响应格式:`{code, data, msg}`
|
||||
- **统一**分页格式:`{page, pageSize, total, list}`
|
||||
- **统一**时间格式:ISO 8601标准
|
||||
- **⚠️ 数据类型一致性**:
|
||||
- 前后端对于同一字段**必须**使用相同的数据类型
|
||||
- 后端Go结构体中的字段类型必须与前端JavaScript/TypeScript中的类型定义保持一致
|
||||
- 特别注意:状态字段、ID字段、枚举值、时间字段等容易出现类型不匹配的字段
|
||||
- 示例:后端数值类型字段对应前端 `number` 类型,字符串类型对应 `string` 类型,布尔类型对应 `boolean` 类型
|
||||
- **指针类型处理**:后端Go中的指针类型在JSON序列化时会自动处理nil值,前端接收到的是对应的基础类型或null值
|
||||
|
||||
3. **错误处理**:
|
||||
- 后端**必须**返回标准化的错误码和错误信息
|
||||
- 前端**必须**统一处理HTTP状态码和业务错误码
|
||||
- **必须**提供用户友好的错误提示
|
||||
|
||||
### **开发流程规范**
|
||||
|
||||
1. **需求分析阶段**:
|
||||
- 确定功能需求和接口设计
|
||||
- 定义数据模型和业务流程
|
||||
- 制定前后端开发计划
|
||||
|
||||
2. **开发阶段**:
|
||||
- 后端优先开发API接口
|
||||
- 前端基于Mock数据进行并行开发
|
||||
- 定期进行接口联调测试
|
||||
|
||||
3. **测试阶段**:
|
||||
- 单元测试:前后端各自负责
|
||||
- 集成测试:前后端协作完成
|
||||
- 用户验收测试:产品团队主导
|
||||
|
||||
### **版本管理规范**
|
||||
|
||||
1. **分支策略**:
|
||||
- `main`:生产环境分支
|
||||
- `develop`:开发环境分支
|
||||
- `feature/*`:功能开发分支
|
||||
- `hotfix/*`:紧急修复分支
|
||||
|
||||
2. **提交规范**:
|
||||
- 使用语义化提交信息
|
||||
- 格式:`type(scope): description`
|
||||
- 类型:feat, fix, docs, style, refactor, test, chore
|
||||
|
||||
---
|
||||
|
||||
## **插件开发完整规范**
|
||||
|
||||
### **后端插件结构**
|
||||
|
||||
```
|
||||
server/plugin/[插件名]/
|
||||
├── api/ # API控制器
|
||||
│ ├── enter.go # API组入口
|
||||
│ └── [模块].go # 具体API实现
|
||||
├── config/ # 插件配置
|
||||
│ └── config.go
|
||||
├── initialize/ # 初始化模块
|
||||
│ ├── api.go # API注册
|
||||
│ ├── gorm.go # 数据库初始化
|
||||
│ ├── menu.go # 菜单初始化
|
||||
│ ├── router.go # 路由初始化
|
||||
│ └── viper.go # 配置初始化
|
||||
├── model/ # 数据模型
|
||||
│ ├── [模型].go # 数据库模型
|
||||
│ └── request/ # 请求模型
|
||||
├── router/ # 路由定义
|
||||
│ ├── enter.go # 路由组入口
|
||||
│ └── [模块].go # 具体路由
|
||||
├── service/ # 业务服务
|
||||
│ ├── enter.go # 服务组入口
|
||||
│ └── [模块].go # 具体服务
|
||||
└── plugin.go # 插件入口
|
||||
```
|
||||
|
||||
### **前端插件结构**
|
||||
|
||||
```
|
||||
web/src/plugin/[插件名]/
|
||||
├── api/ # API接口
|
||||
│ └── [模块].js
|
||||
├── components/ # 插件组件
|
||||
│ └── [组件].vue
|
||||
├── view/ # 插件页面
|
||||
│ └── [页面].vue
|
||||
├── form/ # 表单组件
|
||||
│ └── [表单].vue
|
||||
└── config.js # 插件配置
|
||||
```
|
||||
|
||||
### **插件开发工作流**
|
||||
|
||||
1. **【第一步】需求分析**:
|
||||
- 明确插件功能和业务需求
|
||||
- 设计数据模型和接口规范
|
||||
- 规划前端页面和交互流程
|
||||
|
||||
2. **【第二步】后端开发**:
|
||||
- 创建数据模型和请求模型
|
||||
- 实现服务层业务逻辑
|
||||
- 开发API控制器和路由
|
||||
- 编写初始化和配置代码
|
||||
|
||||
3. **【第三步】前端开发**:
|
||||
- 创建API接口封装
|
||||
- 开发页面组件和表单
|
||||
- 实现业务逻辑和状态管理
|
||||
- 集成到主系统菜单
|
||||
|
||||
4. **【第四步】测试集成**:
|
||||
- 单元测试和集成测试
|
||||
- 前后端联调测试
|
||||
- 用户体验测试
|
||||
- 性能和安全测试
|
||||
|
||||
### **插件质量标准**
|
||||
|
||||
1. **功能完整性**: 插件功能完整,满足业务需求
|
||||
2. **代码质量**: 代码规范,注释完整,易于维护
|
||||
3. **数据类型一致性**: 前后端数据模型字段类型保持严格一致,避免类型转换错误
|
||||
4. **性能表现**: 响应速度快,资源占用合理
|
||||
5. **用户体验**: 界面友好,操作流畅,错误处理完善
|
||||
6. **兼容性**: 与主系统兼容,不影响其他功能
|
||||
7. **安全性**: 数据安全,权限控制,防止安全漏洞
|
||||
|
||||
---
|
||||
|
||||
### **建议和方案**
|
||||
|
||||
基于以上规范,建议AI在开发gin-vue-admin项目时:
|
||||
|
||||
1. **严格遵循分层架构**:确保前后端代码都按照规定的层次结构组织
|
||||
2. **保持代码一致性**:使用统一的命名规范、注释格式和代码风格
|
||||
3. **注重文档完整性**:确保API文档、代码注释和使用说明的完整性
|
||||
4. **优化用户体验**:关注页面加载速度、交互流畅性和错误处理
|
||||
5. **考虑扩展性**:设计时预留扩展接口,便于后续功能增强
|
||||
6. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||
@@ -9,6 +9,7 @@
|
||||
<img src="https://img.shields.io/badge/vue-3.3.4-brightgreen"/>
|
||||
<img src="https://img.shields.io/badge/element--plus-2.3.8-green"/>
|
||||
<img src="https://img.shields.io/badge/gorm-1.25.2-red"/>
|
||||
<img src="https://gitcode.com/flipped-aurora/gin-vue-admin/star/badge.svg"/>
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
@@ -21,7 +22,8 @@
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="200">
|
||||
<td width="250">
|
||||
<p>⭐️ <a href="https://www.bilibili.com/video/BV1B3htzqEf1/?spm_id_from=333.1387.homepage.video_card.click" target="__blank"> 高度适配AI编辑器的MCP </a></p>
|
||||
<p>📄 创建基础模板</p>
|
||||
<p>🤖 AI生成结构</p>
|
||||
<p>⏰ 生成代码</p>
|
||||
@@ -377,7 +379,7 @@ fmt.Println(decodeBytes, err)
|
||||
|
||||
如果你觉得这个项目对你有帮助,你可以请作者喝饮料 :tropical_drink: [点我](https://www.gin-vue-admin.com/coffee/index.html)
|
||||
|
||||
## 10. 商用注意事项
|
||||
## 10. 注意事项
|
||||
|
||||
请严格遵守Apache 2.0协议并保留作品声明,商业用途请务必[获取授权](https://www.gin-vue-admin.com/empower/)
|
||||
未授权商用将依法追究法律责任
|
||||
请严格遵守Apache 2.0协议并保留作品声明,去除版权信息请务必[获取授权](https://www.gin-vue-admin.com/empower/)
|
||||
未授权去除版权信息将依法追究法律责任
|
||||
|
||||
@@ -22,6 +22,7 @@ type ApiGroup struct {
|
||||
AutoCodeHistoryApi
|
||||
AutoCodeTemplateApi
|
||||
SysParamsApi
|
||||
SysVersionApi
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -44,4 +45,5 @@ var (
|
||||
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
|
||||
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
|
||||
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
|
||||
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -116,10 +117,17 @@ func (s *DictionaryApi) FindSysDictionary(c *gin.Context) {
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query request.SysDictionarySearch true "字典 name 或者 type"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionary列表,返回包括列表,总数,页码,每页数量"
|
||||
// @Router /sysDictionary/getSysDictionaryList [get]
|
||||
func (s *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
|
||||
list, err := dictionaryService.GetSysDictionaryInfoList()
|
||||
var dictionary request.SysDictionarySearch
|
||||
err := c.ShouldBindQuery(&dictionary)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, err := dictionaryService.GetSysDictionaryInfoList(c, dictionary)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败", c)
|
||||
|
||||
@@ -143,7 +143,7 @@ func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {
|
||||
err = menuService.AddBaseMenu(menu)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("添加失败!", zap.Error(err))
|
||||
response.FailWithMessage("添加失败", c)
|
||||
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("添加成功", c)
|
||||
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
func (b *BaseApi) Login(c *gin.Context) {
|
||||
var l systemReq.Login
|
||||
err := c.ShouldBindJSON(&l)
|
||||
key := c.ClientIP()
|
||||
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
@@ -39,6 +37,7 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
key := c.ClientIP()
|
||||
// 判断验证码是否开启
|
||||
openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数
|
||||
openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
|
||||
@@ -48,30 +47,30 @@ func (b *BaseApi) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)
|
||||
|
||||
if !oc || (l.CaptchaId != "" && l.Captcha != "" && store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
if oc && (l.Captcha == "" || l.CaptchaId == "" || !store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
return
|
||||
}
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("验证码错误", c)
|
||||
|
||||
u := &system.SysUser{Username: l.Username, Password: l.Password}
|
||||
user, err := userService.Login(u)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("登陆失败! 用户名不存在或者密码错误!", zap.Error(err))
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
if user.Enable != 1 {
|
||||
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||
// 验证码次数+1
|
||||
global.BlackCache.Increment(key, 1)
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
b.TokenNext(c, *user)
|
||||
}
|
||||
|
||||
// TokenNext 登录以后签发jwt
|
||||
@@ -184,7 +183,7 @@ func (b *BaseApi) ChangePassword(c *gin.Context) {
|
||||
}
|
||||
uid := utils.GetUserID(c)
|
||||
u := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}
|
||||
_, err = userService.ChangePassword(u, req.NewPassword)
|
||||
err = userService.ChangePassword(u, req.NewPassword)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||
response.FailWithMessage("修改失败,原密码与当前账户不符", c)
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
systemReq "github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
systemRes "github.com/flipped-aurora/gin-vue-admin/server/model/system/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type SysVersionApi struct{}
|
||||
|
||||
// buildMenuTree 构建菜单树结构
|
||||
func buildMenuTree(menus []system.SysBaseMenu) []system.SysBaseMenu {
|
||||
// 创建菜单映射
|
||||
menuMap := make(map[uint]*system.SysBaseMenu)
|
||||
for i := range menus {
|
||||
menuMap[menus[i].ID] = &menus[i]
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
var rootMenus []system.SysBaseMenu
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId == 0 {
|
||||
// 根菜单
|
||||
menuData := convertMenuToStruct(menu, menuMap)
|
||||
rootMenus = append(rootMenus, menuData)
|
||||
}
|
||||
}
|
||||
|
||||
// 按sort排序根菜单
|
||||
sort.Slice(rootMenus, func(i, j int) bool {
|
||||
return rootMenus[i].Sort < rootMenus[j].Sort
|
||||
})
|
||||
|
||||
return rootMenus
|
||||
}
|
||||
|
||||
// convertMenuToStruct 将菜单转换为结构体并递归处理子菜单
|
||||
func convertMenuToStruct(menu system.SysBaseMenu, menuMap map[uint]*system.SysBaseMenu) system.SysBaseMenu {
|
||||
result := system.SysBaseMenu{
|
||||
Path: menu.Path,
|
||||
Name: menu.Name,
|
||||
Hidden: menu.Hidden,
|
||||
Component: menu.Component,
|
||||
Sort: menu.Sort,
|
||||
Meta: menu.Meta,
|
||||
}
|
||||
|
||||
// 清理并复制参数数据
|
||||
if len(menu.Parameters) > 0 {
|
||||
cleanParameters := make([]system.SysBaseMenuParameter, 0, len(menu.Parameters))
|
||||
for _, param := range menu.Parameters {
|
||||
cleanParam := system.SysBaseMenuParameter{
|
||||
Type: param.Type,
|
||||
Key: param.Key,
|
||||
Value: param.Value,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
|
||||
}
|
||||
cleanParameters = append(cleanParameters, cleanParam)
|
||||
}
|
||||
result.Parameters = cleanParameters
|
||||
}
|
||||
|
||||
// 清理并复制菜单按钮数据
|
||||
if len(menu.MenuBtn) > 0 {
|
||||
cleanMenuBtns := make([]system.SysBaseMenuBtn, 0, len(menu.MenuBtn))
|
||||
for _, btn := range menu.MenuBtn {
|
||||
cleanBtn := system.SysBaseMenuBtn{
|
||||
Name: btn.Name,
|
||||
Desc: btn.Desc,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysBaseMenuID
|
||||
}
|
||||
cleanMenuBtns = append(cleanMenuBtns, cleanBtn)
|
||||
}
|
||||
result.MenuBtn = cleanMenuBtns
|
||||
}
|
||||
|
||||
// 查找并处理子菜单
|
||||
var children []system.SysBaseMenu
|
||||
for _, childMenu := range menuMap {
|
||||
if childMenu.ParentId == menu.ID {
|
||||
childData := convertMenuToStruct(*childMenu, menuMap)
|
||||
children = append(children, childData)
|
||||
}
|
||||
}
|
||||
|
||||
// 按sort排序子菜单
|
||||
if len(children) > 0 {
|
||||
sort.Slice(children, func(i, j int) bool {
|
||||
return children[i].Sort < children[j].Sort
|
||||
})
|
||||
result.Children = children
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// DeleteSysVersion 删除版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 删除版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body system.SysVersion true "删除版本管理"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /sysVersion/deleteSysVersion [delete]
|
||||
func (sysVersionApi *SysVersionApi) DeleteSysVersion(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
err := sysVersionService.DeleteSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysVersionByIds 批量删除版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 批量删除版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||
// @Router /sysVersion/deleteSysVersionByIds [delete]
|
||||
func (sysVersionApi *SysVersionApi) DeleteSysVersionByIds(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
IDs := c.QueryArray("IDs[]")
|
||||
err := sysVersionService.DeleteSysVersionByIds(ctx, IDs)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithMessage("批量删除成功", c)
|
||||
}
|
||||
|
||||
// FindSysVersion 用id查询版本管理
|
||||
// @Tags SysVersion
|
||||
// @Summary 用id查询版本管理
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query uint true "用id查询版本管理"
|
||||
// @Success 200 {object} response.Response{data=system.SysVersion,msg=string} "查询成功"
|
||||
// @Router /sysVersion/findSysVersion [get]
|
||||
func (sysVersionApi *SysVersionApi) FindSysVersion(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
resysVersion, err := sysVersionService.GetSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithData(resysVersion, c)
|
||||
}
|
||||
|
||||
// GetSysVersionList 分页获取版本管理列表
|
||||
// @Tags SysVersion
|
||||
// @Summary 分页获取版本管理列表
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data query systemReq.SysVersionSearch true "分页获取版本管理列表"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /sysVersion/getSysVersionList [get]
|
||||
func (sysVersionApi *SysVersionApi) GetSysVersionList(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var pageInfo systemReq.SysVersionSearch
|
||||
err := c.ShouldBindQuery(&pageInfo)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
list, total, err := sysVersionService.GetSysVersionInfoList(ctx, pageInfo)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: pageInfo.Page,
|
||||
PageSize: pageInfo.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysVersionPublic 不需要鉴权的版本管理接口
|
||||
// @Tags SysVersion
|
||||
// @Summary 不需要鉴权的版本管理接口
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "获取成功"
|
||||
// @Router /sysVersion/getSysVersionPublic [get]
|
||||
func (sysVersionApi *SysVersionApi) GetSysVersionPublic(c *gin.Context) {
|
||||
// 创建业务用Context
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 此接口不需要鉴权
|
||||
// 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑
|
||||
sysVersionService.GetSysVersionPublic(ctx)
|
||||
response.OkWithDetailed(gin.H{
|
||||
"info": "不需要鉴权的版本管理接口信息",
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// ExportVersion 创建发版数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 创建发版数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.ExportVersionRequest true "创建发版数据"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /sysVersion/exportVersion [post]
|
||||
func (sysVersionApi *SysVersionApi) ExportVersion(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
var req systemReq.ExportVersionRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取选中的菜单数据
|
||||
var menuData []system.SysBaseMenu
|
||||
if len(req.MenuIds) > 0 {
|
||||
menuData, err = sysVersionService.GetMenusByIds(ctx, req.MenuIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取菜单数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取菜单数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的API数据
|
||||
var apiData []system.SysApi
|
||||
if len(req.ApiIds) > 0 {
|
||||
apiData, err = sysVersionService.GetApisByIds(ctx, req.ApiIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取API数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取API数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 获取选中的字典数据
|
||||
var dictData []system.SysDictionary
|
||||
if len(req.DictIds) > 0 {
|
||||
dictData, err = sysVersionService.GetDictionariesByIds(ctx, req.DictIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取字典数据失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取字典数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 处理菜单数据,构建递归的children结构
|
||||
processedMenus := buildMenuTree(menuData)
|
||||
|
||||
// 处理API数据,清除ID和时间戳字段
|
||||
processedApis := make([]system.SysApi, 0, len(apiData))
|
||||
for _, api := range apiData {
|
||||
cleanApi := system.SysApi{
|
||||
Path: api.Path,
|
||||
Description: api.Description,
|
||||
ApiGroup: api.ApiGroup,
|
||||
Method: api.Method,
|
||||
}
|
||||
processedApis = append(processedApis, cleanApi)
|
||||
}
|
||||
|
||||
// 处理字典数据,清除ID和时间戳字段,包含字典详情
|
||||
processedDicts := make([]system.SysDictionary, 0, len(dictData))
|
||||
for _, dict := range dictData {
|
||||
cleanDict := system.SysDictionary{
|
||||
Name: dict.Name,
|
||||
Type: dict.Type,
|
||||
Status: dict.Status,
|
||||
Desc: dict.Desc,
|
||||
}
|
||||
|
||||
// 处理字典详情数据,清除ID和时间戳字段
|
||||
cleanDetails := make([]system.SysDictionaryDetail, 0, len(dict.SysDictionaryDetails))
|
||||
for _, detail := range dict.SysDictionaryDetails {
|
||||
cleanDetail := system.SysDictionaryDetail{
|
||||
Label: detail.Label,
|
||||
Value: detail.Value,
|
||||
Extend: detail.Extend,
|
||||
Status: detail.Status,
|
||||
Sort: detail.Sort,
|
||||
// 不复制 ID, CreatedAt, UpdatedAt, SysDictionaryID
|
||||
}
|
||||
cleanDetails = append(cleanDetails, cleanDetail)
|
||||
}
|
||||
cleanDict.SysDictionaryDetails = cleanDetails
|
||||
|
||||
processedDicts = append(processedDicts, cleanDict)
|
||||
}
|
||||
|
||||
// 构建导出数据
|
||||
exportData := systemRes.ExportVersionResponse{
|
||||
Version: systemReq.VersionInfo{
|
||||
Name: req.VersionName,
|
||||
Code: req.VersionCode,
|
||||
Description: req.Description,
|
||||
ExportTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Menus: processedMenus,
|
||||
Apis: processedApis,
|
||||
Dictionaries: processedDicts,
|
||||
}
|
||||
|
||||
// 转换为JSON
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("JSON序列化失败!", zap.Error(err))
|
||||
response.FailWithMessage("JSON序列化失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 保存版本记录
|
||||
version := system.SysVersion{
|
||||
VersionName: utils.Pointer(req.VersionName),
|
||||
VersionCode: utils.Pointer(req.VersionCode),
|
||||
Description: utils.Pointer(req.Description),
|
||||
VersionData: utils.Pointer(string(jsonData)),
|
||||
}
|
||||
|
||||
err = sysVersionService.CreateSysVersion(ctx, &version)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("保存版本记录失败!", zap.Error(err))
|
||||
response.FailWithMessage("保存版本记录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建发版成功", c)
|
||||
}
|
||||
|
||||
// DownloadVersionJson 下载版本JSON数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 下载版本JSON数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param ID query string true "版本ID"
|
||||
// @Success 200 {object} response.Response{data=object,msg=string} "下载成功"
|
||||
// @Router /sysVersion/downloadVersionJson [get]
|
||||
func (sysVersionApi *SysVersionApi) DownloadVersionJson(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
ID := c.Query("ID")
|
||||
if ID == "" {
|
||||
response.FailWithMessage("版本ID不能为空", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取版本记录
|
||||
version, err := sysVersionService.GetSysVersion(ctx, ID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取版本记录失败!", zap.Error(err))
|
||||
response.FailWithMessage("获取版本记录失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建JSON数据
|
||||
var jsonData []byte
|
||||
if version.VersionData != nil && *version.VersionData != "" {
|
||||
jsonData = []byte(*version.VersionData)
|
||||
} else {
|
||||
// 如果没有存储的JSON数据,构建一个基本的结构
|
||||
basicData := systemRes.ExportVersionResponse{
|
||||
Version: systemReq.VersionInfo{
|
||||
Name: *version.VersionName,
|
||||
Code: *version.VersionCode,
|
||||
Description: *version.Description,
|
||||
ExportTime: version.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||
},
|
||||
Menus: []system.SysBaseMenu{},
|
||||
Apis: []system.SysApi{},
|
||||
}
|
||||
jsonData, _ = json.MarshalIndent(basicData, "", " ")
|
||||
}
|
||||
|
||||
// 设置下载响应头
|
||||
filename := fmt.Sprintf("version_%s_%s.json", *version.VersionCode, time.Now().Format("20060102150405"))
|
||||
c.Header("Content-Type", "application/json")
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
c.Header("Content-Length", strconv.Itoa(len(jsonData)))
|
||||
|
||||
c.Data(http.StatusOK, "application/json", jsonData)
|
||||
}
|
||||
|
||||
// ImportVersion 导入版本数据
|
||||
// @Tags SysVersion
|
||||
// @Summary 导入版本数据
|
||||
// @Security ApiKeyAuth
|
||||
// @Accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body systemReq.ImportVersionRequest true "版本JSON数据"
|
||||
// @Success 200 {object} response.Response{msg=string} "导入成功"
|
||||
// @Router /sysVersion/importVersion [post]
|
||||
func (sysVersionApi *SysVersionApi) ImportVersion(c *gin.Context) {
|
||||
ctx := c.Request.Context()
|
||||
|
||||
// 获取JSON数据
|
||||
var importData systemReq.ImportVersionRequest
|
||||
err := c.ShouldBindJSON(&importData)
|
||||
if err != nil {
|
||||
response.FailWithMessage("解析JSON数据失败:"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证数据格式
|
||||
if importData.VersionInfo.Name == "" || importData.VersionInfo.Code == "" {
|
||||
response.FailWithMessage("版本信息格式错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 导入菜单数据
|
||||
if len(importData.ExportMenu) > 0 {
|
||||
if err := sysVersionService.ImportMenus(ctx, importData.ExportMenu); err != nil {
|
||||
global.GVA_LOG.Error("导入菜单失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入菜单失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导入API数据
|
||||
if len(importData.ExportApi) > 0 {
|
||||
if err := sysVersionService.ImportApis(importData.ExportApi); err != nil {
|
||||
global.GVA_LOG.Error("导入API失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入API失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 导入字典数据
|
||||
if len(importData.ExportDictionary) > 0 {
|
||||
if err := sysVersionService.ImportDictionaries(importData.ExportDictionary); err != nil {
|
||||
global.GVA_LOG.Error("导入字典失败!", zap.Error(err))
|
||||
response.FailWithMessage("导入字典失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 创建导入记录
|
||||
jsonData, _ := json.Marshal(importData)
|
||||
version := system.SysVersion{
|
||||
VersionName: utils.Pointer(importData.VersionInfo.Name),
|
||||
VersionCode: utils.Pointer(fmt.Sprintf("%s_imported_%s", importData.VersionInfo.Code, time.Now().Format("20060102150405"))),
|
||||
Description: utils.Pointer(fmt.Sprintf("导入版本: %s", importData.VersionInfo.Description)),
|
||||
VersionData: utils.Pointer(string(jsonData)),
|
||||
}
|
||||
|
||||
err = sysVersionService.CreateSysVersion(ctx, &version)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("保存导入记录失败!", zap.Error(err))
|
||||
// 这里不返回错误,因为数据已经导入成功
|
||||
}
|
||||
|
||||
response.OkWithMessage("导入成功", c)
|
||||
}
|
||||
+218
-155
@@ -2,107 +2,103 @@
|
||||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
# zap logger configuration
|
||||
zap:
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
db: 0
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 177.7.0.14:6379
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# redis-list configuration
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 177.7.0.14:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "177.7.0.14:7000"
|
||||
- "177.7.0.15:7001"
|
||||
- "177.7.0.13:7002"
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: public # Change to "develop" to skip authentication for development mode
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
@@ -114,107 +110,174 @@ db-list:
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
transfer-restart: true
|
||||
# root 自动适配项目根目录
|
||||
# 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
root: ""
|
||||
server: /server
|
||||
server-plug: /plugin/%s
|
||||
server-api: /api/v1/%s
|
||||
server-initialize: /initialize
|
||||
server-model: /model/%s
|
||||
server-request: /model/%s/request/
|
||||
server-router: /router/%s
|
||||
server-service: /service/%s
|
||||
web: /web/src
|
||||
web-api: /api
|
||||
web-form: /view
|
||||
web-table: /view
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: server
|
||||
module: 'github.com/flipped-aurora/gin-vue-admin/server'
|
||||
ai-path: "" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# aws s3 configuration (minio compatible)
|
||||
aws-s3:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# cloudflare r2 configuration
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
path: uploads
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
|
||||
# excel configuration
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
dir: ./resource/excel/
|
||||
|
||||
# timer task db clear table
|
||||
Timer:
|
||||
start: true
|
||||
spec: "@daily" # 定时任务详细配置参考 https://pkg.go.dev/github.com/robfig/cron/v3
|
||||
detail:
|
||||
- tableName: sys_operation_records
|
||||
compareField: created_at
|
||||
interval: 2160h
|
||||
- tableName: jwt_blacklists
|
||||
compareField: created_at
|
||||
interval: 168h
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
+215
-250
@@ -1,210 +1,3 @@
|
||||
# github.com/flipped-aurora/gin-vue-admin/server Global Configuration
|
||||
|
||||
# jwt configuration
|
||||
jwt:
|
||||
signing-key: qmPlus
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
# zap logger configuration
|
||||
zap:
|
||||
level: info
|
||||
format: console
|
||||
prefix: "[github.com/flipped-aurora/gin-vue-admin/server]"
|
||||
director: log
|
||||
show-line: true
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
# redis configuration
|
||||
redis:
|
||||
#是否使用redis集群模式
|
||||
useCluster: false
|
||||
#使用集群模式addr和db默认无效
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
|
||||
# redis-list configuration
|
||||
redis-list:
|
||||
- name: cache # 数据库的名称,注意: name 需要在 redis-list 中唯一
|
||||
useCluster: false # 是否使用redis集群模式
|
||||
addr: 127.0.0.1:6379 # 使用集群模式addr和db默认无效
|
||||
password: ""
|
||||
db: 0
|
||||
clusterAddrs:
|
||||
- "172.21.0.3:7000"
|
||||
- "172.21.0.4:7001"
|
||||
- "172.21.0.2:7002"
|
||||
|
||||
# mongo configuration
|
||||
mongo:
|
||||
coll: ''
|
||||
options: ''
|
||||
database: ''
|
||||
username: ''
|
||||
password: ''
|
||||
auth-source: ''
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ''
|
||||
port: ''
|
||||
|
||||
# email configuration
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
port: 465
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
is-ssl: true
|
||||
secret: xxx
|
||||
nickname: test
|
||||
|
||||
# system configuration
|
||||
system:
|
||||
env: local # 修改为public可以关闭路由日志输出
|
||||
addr: 8888
|
||||
db-type: mysql
|
||||
oss-type: local # 控制oss选择走本地还是 七牛等其他仓 自行增加其他oss仓可以在 server/utils/upload/upload.go 中 NewOss函数配置
|
||||
use-redis: false # 使用redis
|
||||
use-mongo: false # 使用mongo
|
||||
use-multipoint: false
|
||||
# IP限制次数 一个小时15000次
|
||||
iplimit-count: 15000
|
||||
# IP限制一个小时
|
||||
iplimit-time: 3600
|
||||
# 路由全局前缀
|
||||
router-prefix: ""
|
||||
# 严格角色模式 打开后权限将会存在上下级关系
|
||||
use-strict-auth: false
|
||||
|
||||
# captcha configuration
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0 # 0代表一直开启,大于0代表限制次数
|
||||
open-captcha-timeout: 3600 # open-captcha大于0时才生效
|
||||
|
||||
# mysql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
mysql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# pgsql connect configuration
|
||||
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://gin-vue-admin.com/docs/first_master)
|
||||
pgsql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
oracle:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
mssql:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
sqlite:
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
db-list:
|
||||
- disable: true # 是否禁用
|
||||
type: "" # 数据库的类型,目前支持mysql、pgsql、mssql、oracle
|
||||
alias-name: "" # 数据库的名称,注意: alias-name 需要在db-list中唯一
|
||||
path: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
log-mode: ""
|
||||
log-zap: false
|
||||
|
||||
# local configuration
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
|
||||
# autocode configuration
|
||||
autocode:
|
||||
web: web/src
|
||||
root: "" # root 自动适配项目根目录, 请不要手动配置,他会在项目加载的时候识别出根路径
|
||||
server: server
|
||||
module: 'github.com/flipped-aurora/gin-vue-admin/server'
|
||||
ai-path: "" # AI服务路径
|
||||
|
||||
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
use-https: false
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-cdn-domains: false
|
||||
|
||||
# minio oss configuration
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: "http://host:9000/yourBucketName"
|
||||
|
||||
# aliyun oss configuration
|
||||
aliyun-oss:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
@@ -212,29 +5,28 @@ aliyun-oss:
|
||||
bucket-name: yourBucketName
|
||||
bucket-url: yourBucketUrl
|
||||
base-path: yourBasePath
|
||||
|
||||
# tencent cos configuration
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# aws s3 configuration (minio compatible)
|
||||
autocode:
|
||||
web: web/src
|
||||
root: /Users/panghu/Documents/gva/gin-vue-admin
|
||||
server: server
|
||||
module: github.com/flipped-aurora/gin-vue-admin/server
|
||||
ai-path: ""
|
||||
aws-s3:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
endpoint: ""
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
|
||||
# cloudflare r2 configuration
|
||||
s3-force-path-style: false
|
||||
disable-ssl: false
|
||||
captcha:
|
||||
key-long: 6
|
||||
img-width: 240
|
||||
img-height: 80
|
||||
open-captcha: 0
|
||||
open-captcha-timeout: 3600
|
||||
cloudflare-r2:
|
||||
bucket: xxxx0bucket
|
||||
base-url: https://gin.vue.admin.com
|
||||
@@ -242,42 +34,215 @@ cloudflare-r2:
|
||||
account-id: xxx_account_id
|
||||
access-key-id: xxx_key_id
|
||||
secret-access-key: xxx_secret_key
|
||||
|
||||
# huawei obs configuration
|
||||
cors:
|
||||
mode: strict-whitelist
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-methods: POST, GET
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true
|
||||
- allow-origin: example2.com
|
||||
allow-methods: GET, POST
|
||||
allow-headers: content-type
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true
|
||||
db-list:
|
||||
- type: ""
|
||||
alias-name: ""
|
||||
prefix: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
path: ""
|
||||
engine: ""
|
||||
log-mode: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
disable: true
|
||||
disk-list:
|
||||
- mount-point: /
|
||||
email:
|
||||
to: xxx@qq.com
|
||||
from: xxx@163.com
|
||||
host: smtp.163.com
|
||||
secret: xxx
|
||||
nickname: test
|
||||
port: 465
|
||||
is-ssl: true
|
||||
is-loginauth: false
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
hua-wei-obs:
|
||||
path: you-path
|
||||
bucket: you-bucket
|
||||
endpoint: you-endpoint
|
||||
access-key: you-access-key
|
||||
secret-key: you-secret-key
|
||||
|
||||
# excel configuration
|
||||
excel:
|
||||
dir: ./resource/excel/
|
||||
|
||||
# disk usage configuration
|
||||
disk-list:
|
||||
- mount-point: "/"
|
||||
|
||||
# 跨域配置
|
||||
# 需要配合 server/initialize/router.go -> `Router.Use(middleware.CorsByRules())` 使用
|
||||
cors:
|
||||
mode: strict-whitelist # 放行模式: allow-all, 放行全部; whitelist, 白名单模式, 来自白名单内域名的请求添加 cors 头; strict-whitelist 严格白名单模式, 白名单外的请求一律拒绝
|
||||
whitelist:
|
||||
- allow-origin: example1.com
|
||||
allow-headers: Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id
|
||||
allow-methods: POST, GET
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
|
||||
allow-credentials: true # 布尔值
|
||||
- allow-origin: example2.com
|
||||
allow-headers: content-type
|
||||
allow-methods: GET, POST
|
||||
expose-headers: Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type
|
||||
allow-credentials: true # 布尔值
|
||||
jwt:
|
||||
signing-key: 151a53c2-4dfa-42f2-aba3-0c3ad96535d9
|
||||
expires-time: 7d
|
||||
buffer-time: 1d
|
||||
issuer: qmPlus
|
||||
local:
|
||||
path: uploads/file
|
||||
store-path: uploads/file
|
||||
mcp:
|
||||
name: GVA_MCP
|
||||
version: v1.0.0
|
||||
sse_path: /sse
|
||||
message_path: /message
|
||||
url_prefix: ''
|
||||
url_prefix: ""
|
||||
minio:
|
||||
endpoint: yourEndpoint
|
||||
access-key-id: yourAccessKeyId
|
||||
access-key-secret: yourAccessKeySecret
|
||||
bucket-name: yourBucketName
|
||||
use-ssl: false
|
||||
base-path: ""
|
||||
bucket-url: http://host:9000/yourBucketName
|
||||
mongo:
|
||||
coll: ""
|
||||
options: ""
|
||||
database: ""
|
||||
username: ""
|
||||
password: ""
|
||||
auth-source: ""
|
||||
min-pool-size: 0
|
||||
max-pool-size: 100
|
||||
socket-timeout-ms: 0
|
||||
connect-timeout-ms: 0
|
||||
is-zap: false
|
||||
hosts:
|
||||
- host: ""
|
||||
port: ""
|
||||
mssql:
|
||||
prefix: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
path: ""
|
||||
engine: ""
|
||||
log-mode: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
mysql:
|
||||
prefix: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
path: ""
|
||||
engine: ""
|
||||
log-mode: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
oracle:
|
||||
prefix: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
path: ""
|
||||
engine: ""
|
||||
log-mode: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
pgsql:
|
||||
prefix: ""
|
||||
port: "5432"
|
||||
config: sslmode=disable TimeZone=Asia/Shanghai
|
||||
db-name: gva
|
||||
username: jutze
|
||||
password: jutze123
|
||||
path: 127.0.0.1
|
||||
engine: ""
|
||||
log-mode: error
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
qiniu:
|
||||
zone: ZoneHuaDong
|
||||
bucket: ""
|
||||
img-path: ""
|
||||
access-key: ""
|
||||
secret-key: ""
|
||||
use-https: false
|
||||
use-cdn-domains: false
|
||||
redis:
|
||||
name: ""
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- 172.21.0.3:7000
|
||||
- 172.21.0.4:7001
|
||||
- 172.21.0.2:7002
|
||||
redis-list:
|
||||
- name: cache
|
||||
addr: 127.0.0.1:6379
|
||||
password: ""
|
||||
db: 0
|
||||
useCluster: false
|
||||
clusterAddrs:
|
||||
- 172.21.0.3:7000
|
||||
- 172.21.0.4:7001
|
||||
- 172.21.0.2:7002
|
||||
sqlite:
|
||||
prefix: ""
|
||||
port: ""
|
||||
config: ""
|
||||
db-name: ""
|
||||
username: ""
|
||||
password: ""
|
||||
path: ""
|
||||
engine: ""
|
||||
log-mode: ""
|
||||
max-idle-conns: 10
|
||||
max-open-conns: 100
|
||||
singular: false
|
||||
log-zap: false
|
||||
system:
|
||||
db-type: pgsql
|
||||
oss-type: local
|
||||
router-prefix: ""
|
||||
addr: 8888
|
||||
iplimit-count: 15000
|
||||
iplimit-time: 3600
|
||||
use-multipoint: false
|
||||
use-redis: false
|
||||
use-mongo: false
|
||||
use-strict-auth: false
|
||||
tencent-cos:
|
||||
bucket: xxxxx-10005608
|
||||
region: ap-shanghai
|
||||
secret-id: your-secret-id
|
||||
secret-key: your-secret-key
|
||||
base-url: https://gin.vue.admin
|
||||
path-prefix: github.com/flipped-aurora/gin-vue-admin/server
|
||||
zap:
|
||||
level: info
|
||||
prefix: '[github.com/flipped-aurora/gin-vue-admin/server]'
|
||||
format: console
|
||||
director: log
|
||||
encode-level: LowercaseColorLevelEncoder
|
||||
stacktrace-key: stacktrace
|
||||
show-line: true
|
||||
log-in-console: true
|
||||
retention-day: -1
|
||||
|
||||
@@ -4,6 +4,6 @@ type Captcha struct {
|
||||
KeyLong int `mapstructure:"key-long" json:"key-long" yaml:"key-long"` // 验证码长度
|
||||
ImgWidth int `mapstructure:"img-width" json:"img-width" yaml:"img-width"` // 验证码宽度
|
||||
ImgHeight int `mapstructure:"img-height" json:"img-height" yaml:"img-height"` // 验证码高度
|
||||
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码此数,如3代表错误三次后出现验证码
|
||||
OpenCaptcha int `mapstructure:"open-captcha" json:"open-captcha" yaml:"open-captcha"` // 防爆破验证码开启此数,0代表每次登录都需要验证码,其他数字代表错误密码次数,如3代表错误三次后出现验证码
|
||||
OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间,单位:s(秒)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gorm.io/gorm/logger"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
type DsnProvider interface {
|
||||
@@ -31,13 +32,13 @@ type GeneralDB struct {
|
||||
|
||||
func (c GeneralDB) LogLevel() logger.LogLevel {
|
||||
switch strings.ToLower(c.LogMode) {
|
||||
case "silent", "Silent":
|
||||
case "silent":
|
||||
return logger.Silent
|
||||
case "error", "Error":
|
||||
case "error":
|
||||
return logger.Error
|
||||
case "warn", "Warn":
|
||||
case "warn":
|
||||
return logger.Warn
|
||||
case "info", "Info":
|
||||
case "info":
|
||||
return logger.Info
|
||||
default:
|
||||
return logger.Info
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package config
|
||||
|
||||
type Email struct {
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"is-ssl" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Oracle struct {
|
||||
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||
}
|
||||
|
||||
func (m *Oracle) Dsn() string {
|
||||
return "oracle://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "/" + m.Dbname + "?" + m.Config
|
||||
dsn := fmt.Sprintf("oracle://%s:%s@%s/%s?%s", url.PathEscape(m.Username), url.PathEscape(m.Password),
|
||||
net.JoinHostPort(m.Path, m.Port), url.PathEscape(m.Dbname), m.Config)
|
||||
return dsn
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func RunServer() {
|
||||
|
||||
fmt.Printf(`
|
||||
欢迎使用 gin-vue-admin
|
||||
当前版本:v2.8.2
|
||||
当前版本:%s
|
||||
加群方式:微信号:shouzi_1994 QQ群:470239250
|
||||
项目地址:https://github.com/flipped-aurora/gin-vue-admin
|
||||
插件市场:https://plugin.gin-vue-admin.com
|
||||
@@ -48,6 +48,7 @@ func RunServer() {
|
||||
** 版权所有方:flipped-aurora开源团队 **
|
||||
** 版权持有公司:北京翻转极光科技有限责任公司 **
|
||||
** 剔除授权标识需购买商用授权:https://gin-vue-admin.com/empower/index.html **
|
||||
`, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||
** 感谢您对Gin-Vue-Admin的支持与关注 合法授权使用更有利于项目的长久发展**
|
||||
`, global.Version, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||
initServer(address, Router, 10*time.Minute, 10*time.Minute)
|
||||
}
|
||||
|
||||
+5
-2
@@ -2,7 +2,10 @@
|
||||
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
@@ -9296,7 +9299,7 @@ const docTemplate = `{
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "v2.8.2",
|
||||
Version: global.Version,
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package global
|
||||
|
||||
// Version 版本信息
|
||||
// 目前只有Version正式使用 其余为预留
|
||||
const (
|
||||
// Version 当前版本号
|
||||
Version = "v2.8.5"
|
||||
// AppName 应用名称
|
||||
AppName = "Gin-Vue-Admin"
|
||||
// Description 应用描述
|
||||
Description = "使用gin+vue进行极速开发的全栈开发基础平台"
|
||||
)
|
||||
+17
-6
@@ -5,11 +5,11 @@ go 1.23
|
||||
toolchain go1.23.9
|
||||
|
||||
require (
|
||||
github.com/ThinkInAIXYZ/go-mcp v0.2.2
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/aws/aws-sdk-go v1.55.6
|
||||
github.com/casbin/casbin/v2 v2.103.0
|
||||
github.com/casbin/gorm-adapter/v3 v3.32.0
|
||||
github.com/dzwvip/gorm-oracle v0.1.2
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
@@ -20,6 +20,7 @@ require (
|
||||
github.com/gookit/color v1.5.4
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/mark3labs/mcp-go v0.31.0
|
||||
github.com/mholt/archives v0.1.1
|
||||
github.com/minio/minio-go/v7 v7.0.84
|
||||
github.com/mojocn/base64Captcha v1.3.8
|
||||
@@ -72,8 +73,11 @@ require (
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/doquangtan/socketio/v4 v4.1.6 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/emirpasic/gods v1.12.0 // indirect
|
||||
github.com/fasthttp/websocket v1.5.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gammazero/toposort v0.1.1 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
@@ -87,12 +91,15 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.24.0 // indirect
|
||||
github.com/gofiber/fiber/v2 v2.52.9 // indirect
|
||||
github.com/gofiber/websocket/v2 v2.2.1 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang-sql/sqlexp v0.1.0 // indirect
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
@@ -113,8 +120,9 @@ require (
|
||||
github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect
|
||||
github.com/magiconair/properties v1.8.9 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mark3labs/mcp-go v0.26.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/microsoft/go-mssqldb v1.8.0 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minlz v1.0.0 // indirect
|
||||
@@ -126,7 +134,6 @@ require (
|
||||
github.com/mozillazg/go-httpheader v0.4.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.1.0 // indirect
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect
|
||||
github.com/otiai10/mint v1.6.3 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
@@ -135,10 +142,13 @@ require (
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.7.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sijms/go-ora/v2 v2.7.17 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.5 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.12.0 // indirect
|
||||
@@ -146,14 +156,15 @@ require (
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/thoas/go-funk v0.7.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.14 // indirect
|
||||
github.com/tklauser/numcpus v0.9.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
|
||||
+38
-12
@@ -48,8 +48,6 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/STARRY-S/zip v0.2.1 h1:pWBd4tuSGm3wtpoqRZZ2EAwOmcHK6XFf7bU9qcJXyFg=
|
||||
github.com/STARRY-S/zip v0.2.1/go.mod h1:xNvshLODWtC4EJ702g7cTYn13G53o1+X9BWnPFpcWV4=
|
||||
github.com/ThinkInAIXYZ/go-mcp v0.2.2 h1:zlm4Xo8pxGzmfTvN16hM0YP75Q7QZ713g3mZSbO3JAs=
|
||||
github.com/ThinkInAIXYZ/go-mcp v0.2.2/go.mod h1:KnUWUymko7rmOgzvIjxwX0uB9oiJeLF/Q3W9cRt8fVg=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4=
|
||||
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
@@ -104,13 +102,21 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/doquangtan/socketio/v4 v4.1.6 h1:dpcO8IsQxNrvCJ7kNADfXMAmfomO9kTidKExboxtwpM=
|
||||
github.com/doquangtan/socketio/v4 v4.1.6/go.mod h1:p43iXxVgwzOfdFg+TsC0bYXUHycwh6oYKwe+hkuDJu4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/dzwvip/gorm-oracle v0.1.2 h1:811aFDY7oDfKWHc0Z0lHdXzzr89EmKBSwc/jLJ8GU5g=
|
||||
github.com/dzwvip/gorm-oracle v0.1.2/go.mod h1:TbF7idnO9UgGpJ0qJpDZby1/wGquzP5GYof88ScBITE=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek=
|
||||
github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
@@ -164,6 +170,10 @@ github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpv
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
|
||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
|
||||
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w=
|
||||
github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU=
|
||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
||||
github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E=
|
||||
github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0=
|
||||
@@ -228,6 +238,8 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -262,6 +274,7 @@ github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJk
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
@@ -312,10 +325,15 @@ github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mark3labs/mcp-go v0.26.0 h1:xz/Kv1cHLYovF8txv6btBM39/88q3YOjnxqhi51jB0w=
|
||||
github.com/mark3labs/mcp-go v0.26.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
|
||||
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
@@ -353,8 +371,6 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nwaples/rardecode/v2 v2.1.0 h1:JQl9ZoBPDy+nIZGb1mx8+anfHp/LV3NE2MjMiv0ct/U=
|
||||
github.com/nwaples/rardecode/v2 v2.1.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1 h1:jOJ5Pg2w1oeB6PeDurIYf6k9PQ+aTITr/6lP/L/zp6c=
|
||||
github.com/orcaman/concurrent-map/v2 v2.0.1/go.mod h1:9Eq3TG2oBe5FirmYWQfYO5iH1q0Jv47PLaNK++uCdOM=
|
||||
github.com/otiai10/copy v1.14.1 h1:5/7E6qsUMBaH5AnQ0sSLzzTg1oTECmcCmT6lvF45Na8=
|
||||
github.com/otiai10/copy v1.14.1/go.mod h1:oQwrEDDOci3IM8dJF0d8+jnbfPDllW6vUjNc3DoZm9I=
|
||||
github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs=
|
||||
@@ -392,6 +408,8 @@ github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
@@ -406,12 +424,16 @@ github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsF
|
||||
github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
|
||||
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sijms/go-ora/v2 v2.7.17 h1:M/pYIqjaMUeBxyzOWp2oj4ntF6fHSBloJWGNH9vbmsU=
|
||||
github.com/sijms/go-ora/v2 v2.7.17/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk=
|
||||
github.com/songzhibin97/gkit v1.2.13 h1:paY0XJkdRuy9/8k9nTnbdrzo8pC22jIIFldUkOQv5nU=
|
||||
github.com/songzhibin97/gkit v1.2.13/go.mod h1:38CreNR27eTGaG1UMGihrXqI4xc3nGfYxLVKKVx6Ngg=
|
||||
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
|
||||
@@ -455,12 +477,8 @@ github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.60/go.mod h1:8+hG+mQMuRP/OIS9d83syAvXvrMj9HhkND6Q1fLghw0=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/thoas/go-funk v0.7.0 h1:GmirKrs6j6zJbhJIficOsz2aAI7700KsU/5YrdHRM1Y=
|
||||
github.com/thoas/go-funk v0.7.0/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
|
||||
github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo=
|
||||
@@ -474,6 +492,12 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU=
|
||||
github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
@@ -640,6 +664,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -791,6 +816,7 @@ gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||
gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY=
|
||||
gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE=
|
||||
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
|
||||
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
|
||||
@@ -53,7 +53,7 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error
|
||||
sysModel.Condition{},
|
||||
sysModel.JoinTemplate{},
|
||||
sysModel.SysParams{},
|
||||
|
||||
sysModel.SysVersion{},
|
||||
adapter.CasbinRule{},
|
||||
|
||||
example.ExaFile{},
|
||||
|
||||
@@ -56,6 +56,7 @@ func RegisterTables() {
|
||||
system.Condition{},
|
||||
system.JoinTemplate{},
|
||||
system.SysParams{},
|
||||
system.SysVersion{},
|
||||
|
||||
example.ExaFile{},
|
||||
example.ExaCustomer{},
|
||||
|
||||
@@ -28,7 +28,9 @@ func GormMssql() *gorm.DB {
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
return nil
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
@@ -48,7 +50,9 @@ func GormMssqlByConfig(m config.Mssql) *gorm.DB {
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(sqlserver.New(mssqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE=InnoDB")
|
||||
|
||||
@@ -34,8 +34,9 @@ func initMysqlDatabase(m config.Mysql) *gorm.DB {
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
SkipInitializeWithVersion: false, // 根据版本自动配置
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(mysql.New(mysqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
db.InstanceSet("gorm:table_options", "ENGINE="+m.Engine)
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
//"github.com/dzwvip/oracle"
|
||||
oracle "github.com/dzwvip/gorm-oracle"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/initialize/internal"
|
||||
|
||||
//_ "github.com/godror/godror"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormOracle 初始化oracle数据库
|
||||
// 如果需要Oracle库 放开import里的注释 把下方 mysql.Config 改为 oracle.Config ; mysql.New 改为 oracle.New
|
||||
func GormOracle() *gorm.DB {
|
||||
m := global.GVA_CONFIG.Oracle
|
||||
return initOracleDatabase(m)
|
||||
@@ -28,13 +24,9 @@ func initOracleDatabase(m config.Oracle) *gorm.DB {
|
||||
if m.Dbname == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
oracleConfig := mysql.Config{
|
||||
DSN: m.Dsn(), // DSN data source name
|
||||
DefaultStringSize: 191, // string 类型字段的默认长度
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(mysql.New(oracleConfig), internal.Gorm.Config(m.Prefix, m.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := m.GeneralDB
|
||||
if db, err := gorm.Open(oracle.Open(m.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -30,7 +30,9 @@ func initPgSqlDatabase(p config.Pgsql) *gorm.DB {
|
||||
DSN: p.Dsn(), // DSN data source name
|
||||
PreferSimpleProtocol: false,
|
||||
}
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(p.Prefix, p.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := p.GeneralDB
|
||||
if db, err := gorm.Open(postgres.New(pgsqlConfig), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -25,7 +25,9 @@ func initSqliteDatabase(s config.Sqlite) *gorm.DB {
|
||||
return nil
|
||||
}
|
||||
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(s.Prefix, s.Singular)); err != nil {
|
||||
// 数据库配置
|
||||
general := s.GeneralDB
|
||||
if db, err := gorm.Open(sqlite.Open(s.Dsn()), internal.Gorm.Config(general)); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
sqlDB, _ := db.DB()
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/config"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
"gorm.io/gorm/schema"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Gorm = new(_gorm)
|
||||
@@ -15,22 +15,7 @@ type _gorm struct{}
|
||||
|
||||
// Config gorm 自定义配置
|
||||
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||
func (g *_gorm) Config(prefix string, singular bool) *gorm.Config {
|
||||
var general config.GeneralDB
|
||||
switch global.GVA_CONFIG.System.DbType {
|
||||
case "mysql":
|
||||
general = global.GVA_CONFIG.Mysql.GeneralDB
|
||||
case "pgsql":
|
||||
general = global.GVA_CONFIG.Pgsql.GeneralDB
|
||||
case "oracle":
|
||||
general = global.GVA_CONFIG.Oracle.GeneralDB
|
||||
case "sqlite":
|
||||
general = global.GVA_CONFIG.Sqlite.GeneralDB
|
||||
case "mssql":
|
||||
general = global.GVA_CONFIG.Mssql.GeneralDB
|
||||
default:
|
||||
general = global.GVA_CONFIG.Mysql.GeneralDB
|
||||
}
|
||||
func (g *_gorm) Config(general config.GeneralDB) *gorm.Config {
|
||||
return &gorm.Config{
|
||||
Logger: logger.New(NewWriter(general), logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond,
|
||||
@@ -38,8 +23,8 @@ func (g *_gorm) Config(prefix string, singular bool) *gorm.Config {
|
||||
Colorful: true,
|
||||
}),
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: prefix,
|
||||
SingularTable: singular,
|
||||
TablePrefix: general.Prefix,
|
||||
SingularTable: general.Singular,
|
||||
},
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/email"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin"
|
||||
@@ -29,6 +30,7 @@ func bizPluginV1(group ...*gin.RouterGroup) {
|
||||
global.GVA_CONFIG.Email.Nickname,
|
||||
global.GVA_CONFIG.Email.Port,
|
||||
global.GVA_CONFIG.Email.IsSSL,
|
||||
global.GVA_CONFIG.Email.IsLoginAuth,
|
||||
))
|
||||
holder(public, private)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package initialize
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/announcement"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -13,4 +14,5 @@ func PluginInitV2(group *gin.Engine, plugins ...plugin.Plugin) {
|
||||
}
|
||||
func bizPluginV2(engine *gin.Engine) {
|
||||
PluginInitV2(engine, announcement.Plugin)
|
||||
PluginInitV2(engine, notice.Plugin)
|
||||
}
|
||||
|
||||
@@ -93,6 +93,7 @@ func Routers() *gin.Engine {
|
||||
systemRouter.InitUserRouter(PrivateGroup) // 注册用户路由
|
||||
systemRouter.InitMenuRouter(PrivateGroup) // 注册menu路由
|
||||
systemRouter.InitSystemRouter(PrivateGroup) // system相关路由
|
||||
systemRouter.InitSysVersionRouter(PrivateGroup) // 发版相关路由
|
||||
systemRouter.InitCasbinRouter(PrivateGroup) // 权限相关路由
|
||||
systemRouter.InitAutoCodeRouter(PrivateGroup, PublicGroup) // 创建自动化代码
|
||||
systemRouter.InitAuthorityRouter(PrivateGroup) // 注册角色路由
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ import (
|
||||
// @Tag.Description 用户
|
||||
|
||||
// @title Gin-Vue-Admin Swagger API接口文档
|
||||
// @version v2.8.2
|
||||
// @version v2.8.5
|
||||
// @description 使用gin+vue进行极速开发的全栈开发基础平台
|
||||
// @securityDefinitions.apikey ApiKeyAuth
|
||||
// @in header
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
RegisterTool(&ApiCreator{})
|
||||
}
|
||||
|
||||
// ApiCreateRequest API创建请求结构
|
||||
type ApiCreateRequest struct {
|
||||
Path string `json:"path"` // API路径
|
||||
Description string `json:"description"` // API中文描述
|
||||
ApiGroup string `json:"apiGroup"` // API组
|
||||
Method string `json:"method"` // HTTP方法
|
||||
}
|
||||
|
||||
// ApiCreateResponse API创建响应结构
|
||||
type ApiCreateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
ApiID uint `json:"apiId"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// ApiCreator API创建工具
|
||||
type ApiCreator struct{}
|
||||
|
||||
// New 创建API创建工具
|
||||
func (a *ApiCreator) New() mcp.Tool {
|
||||
return mcp.NewTool("create_api",
|
||||
mcp.WithDescription(`创建后端API记录,用于AI编辑器自动添加API接口时自动创建对应的API权限记录。
|
||||
|
||||
**重要限制:**
|
||||
- 当使用gva_auto_generate工具且needCreatedModules=true时,模块创建会自动生成API权限,不应调用此工具
|
||||
- 仅在以下情况使用:1) 单独创建API(不涉及模块创建);2) AI编辑器自动添加API;3) router下的文件产生路径变化时`),
|
||||
mcp.WithString("path",
|
||||
mcp.Required(),
|
||||
mcp.Description("API路径,如:/user/create"),
|
||||
),
|
||||
mcp.WithString("description",
|
||||
mcp.Required(),
|
||||
mcp.Description("API中文描述,如:创建用户"),
|
||||
),
|
||||
mcp.WithString("apiGroup",
|
||||
mcp.Required(),
|
||||
mcp.Description("API组名称,用于分类管理,如:用户管理"),
|
||||
),
|
||||
mcp.WithString("method",
|
||||
mcp.Description("HTTP方法"),
|
||||
mcp.DefaultString("POST"),
|
||||
),
|
||||
mcp.WithString("apis",
|
||||
mcp.Description("批量创建API的JSON字符串,格式:[{\"path\":\"/user/create\",\"description\":\"创建用户\",\"apiGroup\":\"用户管理\",\"method\":\"POST\"}]"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理API创建请求
|
||||
func (a *ApiCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := request.GetArguments()
|
||||
|
||||
var apis []ApiCreateRequest
|
||||
|
||||
// 检查是否是批量创建
|
||||
if apisStr, ok := args["apis"].(string); ok && apisStr != "" {
|
||||
if err := json.Unmarshal([]byte(apisStr), &apis); err != nil {
|
||||
return nil, fmt.Errorf("apis 参数格式错误: %v", err)
|
||||
}
|
||||
} else {
|
||||
// 单个API创建
|
||||
path, ok := args["path"].(string)
|
||||
if !ok || path == "" {
|
||||
return nil, errors.New("path 参数是必需的")
|
||||
}
|
||||
|
||||
description, ok := args["description"].(string)
|
||||
if !ok || description == "" {
|
||||
return nil, errors.New("description 参数是必需的")
|
||||
}
|
||||
|
||||
apiGroup, ok := args["apiGroup"].(string)
|
||||
if !ok || apiGroup == "" {
|
||||
return nil, errors.New("apiGroup 参数是必需的")
|
||||
}
|
||||
|
||||
method := "POST"
|
||||
if val, ok := args["method"].(string); ok && val != "" {
|
||||
method = val
|
||||
}
|
||||
|
||||
apis = append(apis, ApiCreateRequest{
|
||||
Path: path,
|
||||
Description: description,
|
||||
ApiGroup: apiGroup,
|
||||
Method: method,
|
||||
})
|
||||
}
|
||||
|
||||
if len(apis) == 0 {
|
||||
return nil, errors.New("没有要创建的API")
|
||||
}
|
||||
|
||||
// 创建API记录
|
||||
apiService := service.ServiceGroupApp.SystemServiceGroup.ApiService
|
||||
var responses []ApiCreateResponse
|
||||
successCount := 0
|
||||
|
||||
for _, apiReq := range apis {
|
||||
api := system.SysApi{
|
||||
Path: apiReq.Path,
|
||||
Description: apiReq.Description,
|
||||
ApiGroup: apiReq.ApiGroup,
|
||||
Method: apiReq.Method,
|
||||
}
|
||||
|
||||
err := apiService.CreateApi(api)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Warn("创建API失败",
|
||||
zap.String("path", apiReq.Path),
|
||||
zap.String("method", apiReq.Method),
|
||||
zap.Error(err))
|
||||
|
||||
responses = append(responses, ApiCreateResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("创建API失败: %v", err),
|
||||
Path: apiReq.Path,
|
||||
Method: apiReq.Method,
|
||||
})
|
||||
} else {
|
||||
// 获取创建的API ID
|
||||
var createdApi system.SysApi
|
||||
err = global.GVA_DB.Where("path = ? AND method = ?", apiReq.Path, apiReq.Method).First(&createdApi).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Warn("获取创建的API ID失败", zap.Error(err))
|
||||
}
|
||||
|
||||
responses = append(responses, ApiCreateResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("成功创建API %s %s", apiReq.Method, apiReq.Path),
|
||||
ApiID: createdApi.ID,
|
||||
Path: apiReq.Path,
|
||||
Method: apiReq.Method,
|
||||
})
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
// 构建总体响应
|
||||
var resultMessage string
|
||||
if len(apis) == 1 {
|
||||
resultMessage = responses[0].Message
|
||||
} else {
|
||||
resultMessage = fmt.Sprintf("批量创建API完成,成功 %d 个,失败 %d 个", successCount, len(apis)-successCount)
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"success": successCount > 0,
|
||||
"message": resultMessage,
|
||||
"totalCount": len(apis),
|
||||
"successCount": successCount,
|
||||
"failedCount": len(apis) - successCount,
|
||||
"details": responses,
|
||||
}
|
||||
|
||||
resultJSON, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||||
}
|
||||
|
||||
// 添加权限分配提醒
|
||||
permissionReminder := "\n\n⚠️ 重要提醒:\n" +
|
||||
"API创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的API权限," +
|
||||
"以确保用户能够正常访问新接口。\n" +
|
||||
"具体步骤:\n" +
|
||||
"1. 进入角色管理页面\n" +
|
||||
"2. 选择需要授权的角色\n" +
|
||||
"3. 在API权限中勾选新创建的API接口\n" +
|
||||
"4. 保存权限配置"
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("API创建结果:\n\n%s%s", string(resultJSON), permissionReminder),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
// 注册工具将在enter.go中统一处理
|
||||
RegisterTool(&ApiLister{})
|
||||
}
|
||||
|
||||
// ApiInfo API信息结构
|
||||
type ApiInfo struct {
|
||||
ID uint `json:"id,omitempty"` // 数据库ID(仅数据库API有)
|
||||
Path string `json:"path"` // API路径
|
||||
Description string `json:"description,omitempty"` // API描述
|
||||
ApiGroup string `json:"apiGroup,omitempty"` // API组
|
||||
Method string `json:"method"` // HTTP方法
|
||||
Source string `json:"source"` // 来源:database 或 gin
|
||||
}
|
||||
|
||||
// ApiListResponse API列表响应结构
|
||||
type ApiListResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
DatabaseApis []ApiInfo `json:"databaseApis"` // 数据库中的API
|
||||
GinApis []ApiInfo `json:"ginApis"` // gin框架中的API
|
||||
TotalCount int `json:"totalCount"` // 总数量
|
||||
}
|
||||
|
||||
// ApiLister API列表工具
|
||||
type ApiLister struct{}
|
||||
|
||||
// New 创建API列表工具
|
||||
func (a *ApiLister) New() mcp.Tool {
|
||||
return mcp.NewTool("list_all_apis",
|
||||
mcp.WithDescription(`获取系统中所有的API接口,分为两组:
|
||||
|
||||
**功能说明:**
|
||||
- 返回数据库中已注册的API列表
|
||||
- 返回gin框架中实际注册的路由API列表
|
||||
- 帮助前端判断是使用现有API还是需要创建新的API,如果api在前端未使用且需要前端调用的时候,请到api文件夹下对应模块的js中添加方法并暴露给当前业务调用
|
||||
|
||||
**返回数据结构:**
|
||||
- databaseApis: 数据库中的API记录(包含ID、描述、分组等完整信息)
|
||||
- ginApis: gin路由中的API(仅包含路径和方法),需要AI根据路径自行揣摩路径的业务含义,例如:/api/user/:id 表示根据用户ID获取用户信息`),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理API列表请求
|
||||
func (a *ApiLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
|
||||
// 获取数据库中的API
|
||||
databaseApis, err := a.getDatabaseApis()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取数据库API失败", zap.Error(err))
|
||||
errorResponse := ApiListResponse{
|
||||
Success: false,
|
||||
Message: "获取数据库API失败: " + err.Error(),
|
||||
}
|
||||
resultJSON, _ := json.Marshal(errorResponse)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 获取gin路由中的API
|
||||
ginApis, err := a.getGinApis()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取gin路由API失败", zap.Error(err))
|
||||
errorResponse := ApiListResponse{
|
||||
Success: false,
|
||||
Message: "获取gin路由API失败: " + err.Error(),
|
||||
}
|
||||
resultJSON, _ := json.Marshal(errorResponse)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := ApiListResponse{
|
||||
Success: true,
|
||||
Message: "获取API列表成功",
|
||||
DatabaseApis: databaseApis,
|
||||
GinApis: ginApis,
|
||||
TotalCount: len(databaseApis) + len(ginApis),
|
||||
}
|
||||
|
||||
global.GVA_LOG.Info("API列表获取成功",
|
||||
zap.Int("数据库API数量", len(databaseApis)),
|
||||
zap.Int("gin路由API数量", len(ginApis)),
|
||||
zap.Int("总数量", response.TotalCount))
|
||||
|
||||
resultJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(resultJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getDatabaseApis 获取数据库中的所有API
|
||||
func (a *ApiLister) getDatabaseApis() ([]ApiInfo, error) {
|
||||
var apis []system.SysApi
|
||||
err := global.GVA_DB.Model(&system.SysApi{}).Order("api_group ASC, path ASC").Find(&apis).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为ApiInfo格式
|
||||
var result []ApiInfo
|
||||
for _, api := range apis {
|
||||
result = append(result, ApiInfo{
|
||||
ID: api.ID,
|
||||
Path: api.Path,
|
||||
Description: api.Description,
|
||||
ApiGroup: api.ApiGroup,
|
||||
Method: api.Method,
|
||||
Source: "database",
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getGinApis 获取gin路由中的所有API(包含被忽略的API)
|
||||
func (a *ApiLister) getGinApis() ([]ApiInfo, error) {
|
||||
// 从gin路由信息中获取所有API
|
||||
var result []ApiInfo
|
||||
for _, route := range global.GVA_ROUTERS {
|
||||
result = append(result, ApiInfo{
|
||||
Path: route.Path,
|
||||
Method: route.Method,
|
||||
Source: "gin",
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&CurrentTime{})
|
||||
}
|
||||
|
||||
type CurrentTime struct {
|
||||
}
|
||||
|
||||
// 获取当前系统时间
|
||||
func (t *CurrentTime) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 获取当前系统时间
|
||||
|
||||
timezone, ok := request.Params.Arguments["timezone"].(string)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("参数错误:timezone 必须是字符串类型")
|
||||
}
|
||||
// 根据timezone参数加载对应时区
|
||||
loc, err := loadTimeZone(timezone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取当前时间并转换为指定时区
|
||||
currentTime := time.Now().In(loc).Format("2006-01-02 15:04:05")
|
||||
//返回
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("%s 时区的当前时间是:%s", timezone, currentTime),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *CurrentTime) New() mcp.Tool {
|
||||
return mcp.NewTool("currentTime",
|
||||
mcp.WithDescription("获取当前系统时间"),
|
||||
mcp.WithString("timezone",
|
||||
mcp.Required(),
|
||||
mcp.Description("时区"),
|
||||
mcp.Enum("UTC", "CST", "PST", "EST", "GMT", "CET", "JST", "MST", "IST", "AST", "HST"),
|
||||
))
|
||||
}
|
||||
|
||||
// 将简写时区转换为IANA标准时区
|
||||
func loadTimeZone(timezone string) (*time.Location, error) {
|
||||
// 时区映射表
|
||||
timezoneMap := map[string]string{
|
||||
"UTC": "UTC",
|
||||
"CST": "Asia/Shanghai", // 中国标准时间
|
||||
"PST": "America/Los_Angeles",
|
||||
"EST": "America/New_York",
|
||||
"GMT": "GMT",
|
||||
"CET": "Europe/Paris",
|
||||
"JST": "Asia/Tokyo",
|
||||
"MST": "America/Denver",
|
||||
"IST": "Asia/Kolkata",
|
||||
"AST": "Asia/Riyadh", // 阿拉伯标准时间
|
||||
"HST": "Pacific/Honolulu",
|
||||
}
|
||||
|
||||
// 获取标准时区名称
|
||||
tzName, exists := timezoneMap[timezone]
|
||||
if !exists {
|
||||
return nil, errors.New("不支持的时区: " + timezone)
|
||||
}
|
||||
|
||||
// 加载时区
|
||||
return time.LoadLocation(tzName)
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&DictionaryOptionsGenerator{})
|
||||
}
|
||||
|
||||
// DictionaryOptionsGenerator 字典选项生成器
|
||||
type DictionaryOptionsGenerator struct{}
|
||||
|
||||
// DictionaryGenerateRequest 字典生成请求
|
||||
type DictionaryGenerateRequest struct {
|
||||
DictType string `json:"dictType"` // 字典类型
|
||||
FieldDesc string `json:"fieldDesc"` // 字段描述
|
||||
Options []DictionaryOption `json:"options"` // AI生成的字典选项
|
||||
DictName string `json:"dictName"` // 字典名称(可选)
|
||||
Description string `json:"description"` // 字典描述(可选)
|
||||
}
|
||||
|
||||
// DictionaryGenerateResponse 字典生成响应
|
||||
type DictionaryGenerateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
DictType string `json:"dictType"`
|
||||
OptionsCount int `json:"optionsCount"`
|
||||
}
|
||||
|
||||
// New 返回工具注册信息
|
||||
func (d *DictionaryOptionsGenerator) New() mcp.Tool {
|
||||
return mcp.NewTool("generate_dictionary_options",
|
||||
mcp.WithDescription("智能生成字典选项并自动创建字典和字典详情"),
|
||||
mcp.WithString("dictType",
|
||||
mcp.Required(),
|
||||
mcp.Description("字典类型,用于标识字典的唯一性"),
|
||||
),
|
||||
mcp.WithString("fieldDesc",
|
||||
mcp.Required(),
|
||||
mcp.Description("字段描述,用于AI理解字段含义"),
|
||||
),
|
||||
mcp.WithString("options",
|
||||
mcp.Required(),
|
||||
mcp.Description("字典选项JSON字符串,格式:[{\"label\":\"显示名\",\"value\":\"值\",\"sort\":1}]"),
|
||||
),
|
||||
mcp.WithString("dictName",
|
||||
mcp.Description("字典名称,如果不提供将自动生成"),
|
||||
),
|
||||
mcp.WithString("description",
|
||||
mcp.Description("字典描述"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Name 返回工具名称
|
||||
func (d *DictionaryOptionsGenerator) Name() string {
|
||||
return "generate_dictionary_options"
|
||||
}
|
||||
|
||||
// Description 返回工具描述
|
||||
func (d *DictionaryOptionsGenerator) Description() string {
|
||||
return `字典选项生成工具 - 让AI生成并创建字典选项
|
||||
|
||||
此工具允许AI根据字典类型和字段描述生成合适的字典选项,并自动创建字典和字典详情。
|
||||
|
||||
参数说明:
|
||||
- dictType: 字典类型(必填)
|
||||
- fieldDesc: 字段描述(必填)
|
||||
- options: AI生成的字典选项数组(必填)
|
||||
- label: 选项标签
|
||||
- value: 选项值
|
||||
- sort: 排序号
|
||||
- dictName: 字典名称(可选,默认根据fieldDesc生成)
|
||||
- description: 字典描述(可选)
|
||||
|
||||
使用场景:
|
||||
1. 在创建模块时,如果字段需要字典类型,AI可以根据字段描述智能生成合适的选项
|
||||
2. 支持各种业务场景的字典选项生成,如状态、类型、等级等
|
||||
3. 自动创建字典和字典详情,无需手动配置
|
||||
|
||||
示例调用:
|
||||
{
|
||||
"dictType": "user_status",
|
||||
"fieldDesc": "用户状态",
|
||||
"options": [
|
||||
{"label": "正常", "value": "1", "sort": 1},
|
||||
{"label": "禁用", "value": "0", "sort": 2}
|
||||
],
|
||||
"dictName": "用户状态字典",
|
||||
"description": "用于管理用户账户状态的字典"
|
||||
}`
|
||||
}
|
||||
|
||||
// InputSchema 返回输入参数的JSON Schema
|
||||
func (d *DictionaryOptionsGenerator) InputSchema() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"dictType": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "字典类型,用于标识字典的唯一性",
|
||||
},
|
||||
"fieldDesc": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "字段描述,用于生成字典名称和理解字典用途",
|
||||
},
|
||||
"options": map[string]interface{}{
|
||||
"type": "array",
|
||||
"description": "AI生成的字典选项数组",
|
||||
"items": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"label": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "选项标签,显示给用户的文本",
|
||||
},
|
||||
"value": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "选项值,存储在数据库中的值",
|
||||
},
|
||||
"sort": map[string]interface{}{
|
||||
"type": "integer",
|
||||
"description": "排序号,用于控制选项显示顺序",
|
||||
},
|
||||
},
|
||||
"required": []string{"label", "value", "sort"},
|
||||
},
|
||||
},
|
||||
"dictName": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "字典名称,可选,默认根据fieldDesc生成",
|
||||
},
|
||||
"description": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "字典描述,可选",
|
||||
},
|
||||
},
|
||||
"required": []string{"dictType", "fieldDesc", "options"},
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 处理工具调用
|
||||
func (d *DictionaryOptionsGenerator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 解析请求参数
|
||||
args := request.GetArguments()
|
||||
|
||||
dictType, ok := args["dictType"].(string)
|
||||
if !ok || dictType == "" {
|
||||
return nil, errors.New("dictType 参数是必需的")
|
||||
}
|
||||
|
||||
fieldDesc, ok := args["fieldDesc"].(string)
|
||||
if !ok || fieldDesc == "" {
|
||||
return nil, errors.New("fieldDesc 参数是必需的")
|
||||
}
|
||||
|
||||
optionsStr, ok := args["options"].(string)
|
||||
if !ok || optionsStr == "" {
|
||||
return nil, errors.New("options 参数是必需的")
|
||||
}
|
||||
|
||||
// 解析options JSON字符串
|
||||
var options []DictionaryOption
|
||||
if err := json.Unmarshal([]byte(optionsStr), &options); err != nil {
|
||||
return nil, fmt.Errorf("options 参数格式错误: %v", err)
|
||||
}
|
||||
|
||||
if len(options) == 0 {
|
||||
return nil, errors.New("options 不能为空")
|
||||
}
|
||||
|
||||
// 可选参数
|
||||
dictName, _ := args["dictName"].(string)
|
||||
description, _ := args["description"].(string)
|
||||
|
||||
// 构建请求对象
|
||||
req := &DictionaryGenerateRequest{
|
||||
DictType: dictType,
|
||||
FieldDesc: fieldDesc,
|
||||
Options: options,
|
||||
DictName: dictName,
|
||||
Description: description,
|
||||
}
|
||||
|
||||
// 创建字典
|
||||
response, err := d.createDictionaryWithOptions(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
resultJSON, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("字典选项生成结果:\n\n%s", string(resultJSON)),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// createDictionaryWithOptions 创建字典和字典选项
|
||||
func (d *DictionaryOptionsGenerator) createDictionaryWithOptions(ctx context.Context, req *DictionaryGenerateRequest) (*DictionaryGenerateResponse, error) {
|
||||
// 检查字典是否已存在
|
||||
exists, err := d.checkDictionaryExists(req.DictType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查字典是否存在失败: %v", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
return &DictionaryGenerateResponse{
|
||||
Success: false,
|
||||
Message: fmt.Sprintf("字典 %s 已存在,跳过创建", req.DictType),
|
||||
DictType: req.DictType,
|
||||
OptionsCount: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 生成字典名称
|
||||
dictName := req.DictName
|
||||
if dictName == "" {
|
||||
dictName = d.generateDictionaryName(req.DictType, req.FieldDesc)
|
||||
}
|
||||
|
||||
// 创建字典
|
||||
dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
|
||||
dictionary := system.SysDictionary{
|
||||
Name: dictName,
|
||||
Type: req.DictType,
|
||||
Status: &[]bool{true}[0], // 默认启用
|
||||
Desc: req.Description,
|
||||
}
|
||||
|
||||
err = dictionaryService.CreateSysDictionary(dictionary)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建字典失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取刚创建的字典ID
|
||||
var createdDict system.SysDictionary
|
||||
err = global.GVA_DB.Where("type = ?", req.DictType).First(&createdDict).Error
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取创建的字典失败: %v", err)
|
||||
}
|
||||
|
||||
// 创建字典详情项
|
||||
dictionaryDetailService := service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
|
||||
successCount := 0
|
||||
|
||||
for _, option := range req.Options {
|
||||
dictionaryDetail := system.SysDictionaryDetail{
|
||||
Label: option.Label,
|
||||
Value: option.Value,
|
||||
Status: &[]bool{true}[0], // 默认启用
|
||||
Sort: option.Sort,
|
||||
SysDictionaryID: int(createdDict.ID),
|
||||
}
|
||||
|
||||
err = dictionaryDetailService.CreateSysDictionaryDetail(dictionaryDetail)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Warn("创建字典详情项失败", zap.Error(err))
|
||||
} else {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
|
||||
return &DictionaryGenerateResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("成功创建字典 %s,包含 %d 个选项", req.DictType, successCount),
|
||||
DictType: req.DictType,
|
||||
OptionsCount: successCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// checkDictionaryExists 检查字典是否存在
|
||||
func (d *DictionaryOptionsGenerator) checkDictionaryExists(dictType string) (bool, error) {
|
||||
var dictionary system.SysDictionary
|
||||
err := global.GVA_DB.Where("type = ?", dictType).First(&dictionary).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, nil // 字典不存在
|
||||
}
|
||||
return false, err // 其他错误
|
||||
}
|
||||
return true, nil // 字典存在
|
||||
}
|
||||
|
||||
// generateDictionaryName 生成字典名称
|
||||
func (d *DictionaryOptionsGenerator) generateDictionaryName(dictType, fieldDesc string) string {
|
||||
if fieldDesc != "" {
|
||||
return fmt.Sprintf("%s字典", fieldDesc)
|
||||
}
|
||||
return fmt.Sprintf("%s字典", dictType)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
RegisterTool(&DictionaryQuery{})
|
||||
}
|
||||
|
||||
// DictionaryInfo 字典信息结构
|
||||
type DictionaryInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"` // 字典名(中)
|
||||
Type string `json:"type"` // 字典名(英)
|
||||
Status *bool `json:"status"` // 状态
|
||||
Desc string `json:"desc"` // 描述
|
||||
Details []DictionaryDetailInfo `json:"details"` // 字典详情
|
||||
}
|
||||
|
||||
// DictionaryDetailInfo 字典详情信息结构
|
||||
type DictionaryDetailInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Label string `json:"label"` // 展示值
|
||||
Value string `json:"value"` // 字典值
|
||||
Extend string `json:"extend"` // 扩展值
|
||||
Status *bool `json:"status"` // 启用状态
|
||||
Sort int `json:"sort"` // 排序标记
|
||||
}
|
||||
|
||||
// DictionaryQueryResponse 字典查询响应结构
|
||||
type DictionaryQueryResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Total int `json:"total"`
|
||||
Dictionaries []DictionaryInfo `json:"dictionaries"`
|
||||
}
|
||||
|
||||
// DictionaryQuery 字典查询工具
|
||||
type DictionaryQuery struct{}
|
||||
|
||||
// New 创建字典查询工具
|
||||
func (d *DictionaryQuery) New() mcp.Tool {
|
||||
return mcp.NewTool("query_dictionaries",
|
||||
mcp.WithDescription("查询系统中所有的字典和字典属性,用于AI生成逻辑时了解可用的字典选项"),
|
||||
mcp.WithString("dictType",
|
||||
mcp.Description("可选:指定字典类型进行精确查询,如果不提供则返回所有字典"),
|
||||
),
|
||||
mcp.WithBoolean("includeDisabled",
|
||||
mcp.Description("是否包含已禁用的字典和字典项,默认为false(只返回启用的)"),
|
||||
),
|
||||
mcp.WithBoolean("detailsOnly",
|
||||
mcp.Description("是否只返回字典详情信息(不包含字典基本信息),默认为false"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理字典查询请求
|
||||
func (d *DictionaryQuery) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := request.GetArguments()
|
||||
|
||||
// 获取参数
|
||||
dictType := ""
|
||||
if val, ok := args["dictType"].(string); ok {
|
||||
dictType = val
|
||||
}
|
||||
|
||||
includeDisabled := false
|
||||
if val, ok := args["includeDisabled"].(bool); ok {
|
||||
includeDisabled = val
|
||||
}
|
||||
|
||||
detailsOnly := false
|
||||
if val, ok := args["detailsOnly"].(bool); ok {
|
||||
detailsOnly = val
|
||||
}
|
||||
|
||||
// 获取字典服务
|
||||
dictionaryService := service.ServiceGroupApp.SystemServiceGroup.DictionaryService
|
||||
|
||||
var dictionaries []DictionaryInfo
|
||||
var err error
|
||||
|
||||
if dictType != "" {
|
||||
// 查询指定类型的字典
|
||||
var status *bool
|
||||
if !includeDisabled {
|
||||
status = &[]bool{true}[0]
|
||||
}
|
||||
|
||||
sysDictionary, err := dictionaryService.GetSysDictionary(dictType, 0, status)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询字典失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "查询字典失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 转换为响应格式
|
||||
dictInfo := DictionaryInfo{
|
||||
ID: sysDictionary.ID,
|
||||
Name: sysDictionary.Name,
|
||||
Type: sysDictionary.Type,
|
||||
Status: sysDictionary.Status,
|
||||
Desc: sysDictionary.Desc,
|
||||
}
|
||||
|
||||
// 获取字典详情
|
||||
for _, detail := range sysDictionary.SysDictionaryDetails {
|
||||
if includeDisabled || (detail.Status != nil && *detail.Status) {
|
||||
dictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{
|
||||
ID: detail.ID,
|
||||
Label: detail.Label,
|
||||
Value: detail.Value,
|
||||
Extend: detail.Extend,
|
||||
Status: detail.Status,
|
||||
Sort: detail.Sort,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
dictionaries = append(dictionaries, dictInfo)
|
||||
} else {
|
||||
// 查询所有字典
|
||||
var sysDictionaries []system.SysDictionary
|
||||
db := global.GVA_DB.Model(&system.SysDictionary{})
|
||||
|
||||
if !includeDisabled {
|
||||
db = db.Where("status = ?", true)
|
||||
}
|
||||
|
||||
err = db.Preload("SysDictionaryDetails", func(db *gorm.DB) *gorm.DB {
|
||||
if includeDisabled {
|
||||
return db.Order("sort")
|
||||
} else {
|
||||
return db.Where("status = ?", true).Order("sort")
|
||||
}
|
||||
}).Find(&sysDictionaries).Error
|
||||
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("查询字典列表失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "查询字典列表失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 转换为响应格式
|
||||
for _, dict := range sysDictionaries {
|
||||
dictInfo := DictionaryInfo{
|
||||
ID: dict.ID,
|
||||
Name: dict.Name,
|
||||
Type: dict.Type,
|
||||
Status: dict.Status,
|
||||
Desc: dict.Desc,
|
||||
}
|
||||
|
||||
// 获取字典详情
|
||||
for _, detail := range dict.SysDictionaryDetails {
|
||||
if includeDisabled || (detail.Status != nil && *detail.Status) {
|
||||
dictInfo.Details = append(dictInfo.Details, DictionaryDetailInfo{
|
||||
ID: detail.ID,
|
||||
Label: detail.Label,
|
||||
Value: detail.Value,
|
||||
Extend: detail.Extend,
|
||||
Status: detail.Status,
|
||||
Sort: detail.Sort,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
dictionaries = append(dictionaries, dictInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果只需要详情信息,则提取所有详情
|
||||
if detailsOnly {
|
||||
var allDetails []DictionaryDetailInfo
|
||||
for _, dict := range dictionaries {
|
||||
allDetails = append(allDetails, dict.Details...)
|
||||
}
|
||||
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "查询字典详情成功",
|
||||
"total": len(allDetails),
|
||||
"details": allDetails,
|
||||
}
|
||||
|
||||
responseJSON, _ := json.Marshal(response)
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(string(responseJSON)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := DictionaryQueryResponse{
|
||||
Success: true,
|
||||
Message: "查询字典成功",
|
||||
Total: len(dictionaries),
|
||||
Dictionaries: dictionaries,
|
||||
}
|
||||
|
||||
responseJSON, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化响应失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(fmt.Sprintf(`{"success": false, "message": "序列化响应失败: %v", "total": 0, "dictionaries": []}`, err.Error())),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(string(responseJSON)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,529 @@
|
||||
# ExecutionPlan 结构体格式说明
|
||||
|
||||
## 概述
|
||||
ExecutionPlan 是用于自动化模块创建的执行计划结构体,包含了创建包和模块所需的所有信息。
|
||||
|
||||
## 完整结构体定义
|
||||
|
||||
```go
|
||||
type ExecutionPlan struct {
|
||||
PackageName string `json:"packageName"` // 包名,如:"user", "order", "product"
|
||||
PackageType string `json:"packageType"` // "plugin" 或 "package"
|
||||
NeedCreatedPackage bool `json:"needCreatedPackage"` // 是否需要创建包
|
||||
NeedCreatedModules bool `json:"needCreatedModules"` // 是否需要创建模块
|
||||
PackageInfo *request.SysAutoCodePackageCreate `json:"packageInfo,omitempty"` // 包信息(当NeedCreatedPackage=true时必需)
|
||||
ModulesInfo []*request.AutoCode `json:"modulesInfo,omitempty"` // 模块信息数组(当NeedCreatedModules=true时必需,支持批量创建)
|
||||
Paths map[string]string `json:"paths,omitempty"` // 路径信息
|
||||
}
|
||||
```
|
||||
|
||||
## 子结构体详细说明
|
||||
|
||||
### 1. SysAutoCodePackageCreate 结构体
|
||||
|
||||
```go
|
||||
type SysAutoCodePackageCreate struct {
|
||||
Desc string `json:"desc"` // 描述,如:"用户管理模块"
|
||||
Label string `json:"label"` // 展示名,如:"用户管理"
|
||||
Template string `json:"template"` // 模板类型:"plugin" 或 "package"
|
||||
PackageName string `json:"packageName"` // 包名,如:"user"
|
||||
Module string `json:"-"` // 模块名(自动填充,无需设置)
|
||||
}
|
||||
```
|
||||
|
||||
### 2. AutoCode 结构体(核心字段)
|
||||
|
||||
```go
|
||||
type AutoCode struct {
|
||||
Package string `json:"package"` // 包名
|
||||
TableName string `json:"tableName"` // 数据库表名
|
||||
BusinessDB string `json:"businessDB"` // 业务数据库名
|
||||
StructName string `json:"structName"` // 结构体名称
|
||||
PackageName string `json:"packageName"` // 文件名称
|
||||
Description string `json:"description"` // 结构体中文名称
|
||||
Abbreviation string `json:"abbreviation"` // 结构体简称
|
||||
HumpPackageName string `json:"humpPackageName"` // 驼峰命名的包名
|
||||
GvaModel bool `json:"gvaModel"` // 是否使用GVA默认Model
|
||||
AutoMigrate bool `json:"autoMigrate"` // 是否自动迁移表结构
|
||||
AutoCreateResource bool `json:"autoCreateResource"` // 是否自动创建资源标识
|
||||
AutoCreateApiToSql bool `json:"autoCreateApiToSql"` // 是否自动创建API
|
||||
AutoCreateMenuToSql bool `json:"autoCreateMenuToSql"` // 是否自动创建菜单
|
||||
AutoCreateBtnAuth bool `json:"autoCreateBtnAuth"` // 是否自动创建按钮权限
|
||||
OnlyTemplate bool `json:"onlyTemplate"` // 是否只生成模板
|
||||
IsTree bool `json:"isTree"` // 是否树形结构
|
||||
TreeJson string `json:"treeJson"` // 树形结构JSON字段
|
||||
IsAdd bool `json:"isAdd"` // 是否新增
|
||||
Fields []*AutoCodeField `json:"fields"` // 字段列表
|
||||
GenerateWeb bool `json:"generateWeb"` // 是否生成前端代码
|
||||
GenerateServer bool `json:"generateServer"` // 是否生成后端代码
|
||||
Module string `json:"-"` // 模块(自动填充)
|
||||
DictTypes []string `json:"-"` // 字典类型(自动填充)
|
||||
}
|
||||
```
|
||||
|
||||
### 3. AutoCodeField 结构体(字段定义)
|
||||
|
||||
```go
|
||||
type AutoCodeField struct {
|
||||
FieldName string `json:"fieldName"` // 字段名
|
||||
FieldDesc string `json:"fieldDesc"` // 字段中文描述
|
||||
FieldType string `json:"fieldType"` // 字段类型:string, int, bool, time.Time等
|
||||
FieldJson string `json:"fieldJson"` // JSON标签名
|
||||
DataTypeLong string `json:"dataTypeLong"` // 数据库字段长度
|
||||
Comment string `json:"comment"` // 数据库字段注释
|
||||
ColumnName string `json:"columnName"` // 数据库列名
|
||||
FieldSearchType string `json:"fieldSearchType"` // 搜索类型:EQ, LIKE, BETWEEN等
|
||||
FieldSearchHide bool `json:"fieldSearchHide"` // 是否隐藏查询条件
|
||||
DictType string `json:"dictType"` // 字典类型
|
||||
Form bool `json:"form"` // 是否在表单中显示
|
||||
Table bool `json:"table"` // 是否在表格中显示
|
||||
Desc bool `json:"desc"` // 是否在详情中显示
|
||||
Excel bool `json:"excel"` // 是否支持导入导出
|
||||
Require bool `json:"require"` // 是否必填
|
||||
DefaultValue string `json:"defaultValue"` // 默认值
|
||||
ErrorText string `json:"errorText"` // 校验失败提示
|
||||
Clearable bool `json:"clearable"` // 是否可清空
|
||||
Sort bool `json:"sort"` // 是否支持排序
|
||||
PrimaryKey bool `json:"primaryKey"` // 是否主键
|
||||
DataSource *DataSource `json:"dataSource"` // 数据源配置(用于关联其他表)
|
||||
CheckDataSource bool `json:"checkDataSource"` // 是否检查数据源
|
||||
FieldIndexType string `json:"fieldIndexType"` // 索引类型
|
||||
}
|
||||
```
|
||||
|
||||
### 4. DataSource 结构体(关联表配置)
|
||||
|
||||
```go
|
||||
type DataSource struct {
|
||||
DBName string `json:"dbName"` // 关联的数据库名称
|
||||
Table string `json:"table"` // 关联的表名
|
||||
Label string `json:"label"` // 用于显示的字段名(如name、title等)
|
||||
Value string `json:"value"` // 用于存储的值字段名(通常是id)
|
||||
Association int `json:"association"` // 关联关系:1=一对一,2=一对多
|
||||
HasDeletedAt bool `json:"hasDeletedAt"` // 关联表是否有软删除字段
|
||||
}
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例1:创建新包和批量创建多个模块
|
||||
|
||||
```json
|
||||
{
|
||||
"packageName": "user",
|
||||
"packageType": "package",
|
||||
"needCreatedPackage": true,
|
||||
"needCreatedModules": true,
|
||||
"packageInfo": {
|
||||
"desc": "用户管理模块",
|
||||
"label": "用户管理",
|
||||
"template": "package",
|
||||
"packageName": "user"
|
||||
},
|
||||
"modulesInfo": [
|
||||
{
|
||||
"package": "user",
|
||||
"tableName": "sys_users",
|
||||
"businessDB": "",
|
||||
"structName": "User",
|
||||
"packageName": "user",
|
||||
"description": "用户",
|
||||
"abbreviation": "user",
|
||||
"humpPackageName": "user",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"treeJson": "",
|
||||
"isAdd": true,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "Username",
|
||||
"fieldDesc": "用户名",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "username",
|
||||
"dataTypeLong": "50",
|
||||
"comment": "用户名",
|
||||
"columnName": "username",
|
||||
"fieldSearchType": "LIKE",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请输入用户名",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": {
|
||||
"dbName": "gva",
|
||||
"table": "sys_users",
|
||||
"label": "username",
|
||||
"value": "id",
|
||||
"association": 2,
|
||||
"hasDeletedAt": true
|
||||
},
|
||||
"checkDataSource": true,
|
||||
"fieldIndexType": ""
|
||||
},
|
||||
{
|
||||
"fieldName": "Email",
|
||||
"fieldDesc": "邮箱",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "email",
|
||||
"dataTypeLong": "100",
|
||||
"comment": "邮箱地址",
|
||||
"columnName": "email",
|
||||
"fieldSearchType": "EQ",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请输入邮箱",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": null,
|
||||
"checkDataSource": false,
|
||||
"fieldIndexType": "index"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"package": "user",
|
||||
"tableName": "user_profiles",
|
||||
"businessDB": "",
|
||||
"structName": "UserProfile",
|
||||
"packageName": "user",
|
||||
"description": "用户档案",
|
||||
"abbreviation": "userProfile",
|
||||
"humpPackageName": "user",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"treeJson": "",
|
||||
"isAdd": true,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "UserID",
|
||||
"fieldDesc": "用户ID",
|
||||
"fieldType": "int",
|
||||
"fieldJson": "userId",
|
||||
"dataTypeLong": "",
|
||||
"comment": "关联用户ID",
|
||||
"columnName": "user_id",
|
||||
"fieldSearchType": "EQ",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请选择用户",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": null,
|
||||
"checkDataSource": false,
|
||||
"fieldIndexType": "index"
|
||||
},
|
||||
{
|
||||
"fieldName": "Avatar",
|
||||
"fieldDesc": "头像",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "avatar",
|
||||
"dataTypeLong": "255",
|
||||
"comment": "用户头像URL",
|
||||
"columnName": "avatar",
|
||||
"fieldSearchType": "",
|
||||
"fieldSearchHide": true,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": false,
|
||||
"require": false,
|
||||
"defaultValue": "",
|
||||
"errorText": "",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": null,
|
||||
"checkDataSource": false,
|
||||
"fieldIndexType": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 示例2:仅在现有包中批量创建多个模块
|
||||
|
||||
```json
|
||||
{
|
||||
"packageName": "system",
|
||||
"packageType": "package",
|
||||
"needCreatedPackage": false,
|
||||
"needCreatedModules": true,
|
||||
"packageInfo": null,
|
||||
"modulesInfo": [
|
||||
{
|
||||
"package": "system",
|
||||
"tableName": "sys_roles",
|
||||
"businessDB": "",
|
||||
"structName": "Role",
|
||||
"packageName": "system",
|
||||
"description": "角色",
|
||||
"abbreviation": "role",
|
||||
"humpPackageName": "system",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "RoleName",
|
||||
"fieldDesc": "角色名称",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "roleName",
|
||||
"dataTypeLong": "50",
|
||||
"comment": "角色名称",
|
||||
"columnName": "role_name",
|
||||
"fieldSearchType": "LIKE",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"package": "system",
|
||||
"tableName": "sys_permissions",
|
||||
"businessDB": "",
|
||||
"structName": "Permission",
|
||||
"packageName": "system",
|
||||
"description": "权限",
|
||||
"abbreviation": "permission",
|
||||
"humpPackageName": "system",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "PermissionName",
|
||||
"fieldDesc": "权限名称",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "permissionName",
|
||||
"dataTypeLong": "100",
|
||||
"comment": "权限名称",
|
||||
"columnName": "permission_name",
|
||||
"fieldSearchType": "LIKE",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true
|
||||
},
|
||||
{
|
||||
"fieldName": "PermissionCode",
|
||||
"fieldDesc": "权限代码",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "permissionCode",
|
||||
"dataTypeLong": "50",
|
||||
"comment": "权限代码",
|
||||
"columnName": "permission_code",
|
||||
"fieldSearchType": "=",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 示例3:模块关联关系配置详解
|
||||
|
||||
以下示例展示了如何配置不同类型的关联关系:
|
||||
|
||||
```json
|
||||
{
|
||||
"packageName": "order",
|
||||
"packageType": "package",
|
||||
"needCreatedPackage": true,
|
||||
"needCreatedModules": true,
|
||||
"packageInfo": {
|
||||
"desc": "订单管理模块",
|
||||
"label": "订单管理",
|
||||
"template": "package",
|
||||
"packageName": "order"
|
||||
},
|
||||
"modulesInfo": [
|
||||
{
|
||||
"package": "order",
|
||||
"tableName": "orders",
|
||||
"structName": "Order",
|
||||
"packageName": "order",
|
||||
"description": "订单",
|
||||
"abbreviation": "order",
|
||||
"humpPackageName": "order",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "UserID",
|
||||
"fieldDesc": "下单用户",
|
||||
"fieldType": "uint",
|
||||
"fieldJson": "userId",
|
||||
"columnName": "user_id",
|
||||
"fieldSearchType": "EQ",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true,
|
||||
"dataSource": {
|
||||
"dbName": "gva",
|
||||
"table": "sys_users",
|
||||
"label": "username",
|
||||
"value": "id",
|
||||
"association": 2,
|
||||
"hasDeletedAt": true
|
||||
},
|
||||
"checkDataSource": true
|
||||
},
|
||||
{
|
||||
"fieldName": "ProductID",
|
||||
"fieldDesc": "商品",
|
||||
"fieldType": "uint",
|
||||
"fieldJson": "productId",
|
||||
"columnName": "product_id",
|
||||
"fieldSearchType": "EQ",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true,
|
||||
"dataSource": {
|
||||
"dbName": "gva",
|
||||
"table": "products",
|
||||
"label": "name",
|
||||
"value": "id",
|
||||
"association": 2,
|
||||
"hasDeletedAt": false
|
||||
},
|
||||
"checkDataSource": true
|
||||
},
|
||||
{
|
||||
"fieldName": "Status",
|
||||
"fieldDesc": "订单状态",
|
||||
"fieldType": "int",
|
||||
"fieldJson": "status",
|
||||
"columnName": "status",
|
||||
"fieldSearchType": "EQ",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"require": true,
|
||||
"dictType": "order_status"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## DataSource 配置说明
|
||||
|
||||
### 关联关系类型
|
||||
- **association: 1** - 一对一关联(如用户与用户档案)
|
||||
- **association: 2** - 一对多关联(如用户与订单)
|
||||
|
||||
### 配置要点
|
||||
1. **dbName**: 通常为 "gva"(默认数据库)
|
||||
2. **table**: 关联表的实际表名
|
||||
3. **label**: 用于前端显示的字段(如用户名、商品名称)
|
||||
4. **value**: 用于存储关联ID的字段(通常是 "id")
|
||||
5. **hasDeletedAt**: 关联表是否支持软删除
|
||||
6. **checkDataSource**: 建议设为true,会验证关联表是否存在
|
||||
|
||||
### 常见关联场景
|
||||
- 用户关联:`{"table": "sys_users", "label": "username", "value": "id"}`
|
||||
- 角色关联:`{"table": "sys_authorities", "label": "authorityName", "value": "authorityId"}`
|
||||
- 部门关联:`{"table": "sys_departments", "label": "name", "value": "id"}`
|
||||
- 分类关联:`{"table": "categories", "label": "name", "value": "id"}`
|
||||
|
||||
## 重要注意事项
|
||||
|
||||
1. **PackageType**: 只能是 "plugin" 或 "package"
|
||||
2. **NeedCreatedPackage**: 当为true时,PackageInfo必须提供
|
||||
3. **NeedCreatedModules**: 当为true时,ModulesInfo必须提供
|
||||
4. **字段类型**: FieldType支持的类型包括:
|
||||
- string(字符串)
|
||||
- richtext(富文本)
|
||||
- int(整型)
|
||||
- bool(布尔值)
|
||||
- float64(浮点型)
|
||||
- time.Time(时间)
|
||||
- enum(枚举)
|
||||
- picture(单图片,字符串)
|
||||
- pictures(多图片,json字符串)
|
||||
- video(视频,字符串)
|
||||
- file(文件,json字符串)
|
||||
- json(JSON)
|
||||
- array(数组)
|
||||
5. **搜索类型**: FieldSearchType支持:EQ, NE, GT, GE, LT, LE, LIKE, BETWEEN等
|
||||
6. **索引类型**: FieldIndexType支持:index, unique等
|
||||
7. **GvaModel**: 设置为true时会自动包含ID、CreatedAt、UpdatedAt、DeletedAt字段
|
||||
8. **关联配置**: 使用dataSource时,确保关联表已存在,建议开启checkDataSource验证
|
||||
|
||||
## 常见错误避免
|
||||
|
||||
1. 确保PackageName和ModuleName符合Go语言命名规范
|
||||
2. 字段名使用大写开头的驼峰命名
|
||||
3. JSON标签使用小写开头的驼峰命名
|
||||
4. 数据库列名使用下划线分隔的小写命名
|
||||
5. 必填字段不要遗漏
|
||||
6. 字段类型要与实际需求匹配
|
||||
@@ -0,0 +1,205 @@
|
||||
# GAG工具使用示例 - 带用户确认流程
|
||||
|
||||
## 新的工作流程
|
||||
|
||||
现在GAG工具支持三步工作流程:
|
||||
1. `analyze` - 分析现有模块信息
|
||||
2. `confirm` - 请求用户确认创建计划
|
||||
3. `execute` - 执行创建操作(需要用户确认)
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 第一步:分析
|
||||
```json
|
||||
{
|
||||
"action": "analyze",
|
||||
"requirement": "创建一个图书管理功能"
|
||||
}
|
||||
```
|
||||
|
||||
### 第二步:确认(支持批量创建多个模块)
|
||||
```json
|
||||
{
|
||||
"action": "confirm",
|
||||
"executionPlan": {
|
||||
"packageName": "library",
|
||||
"packageType": "package",
|
||||
"needCreatedPackage": true,
|
||||
"needCreatedModules": true,
|
||||
"packageInfo": {
|
||||
"desc": "图书管理包",
|
||||
"label": "图书管理",
|
||||
"template": "package",
|
||||
"packageName": "library"
|
||||
},
|
||||
"modulesInfo": [
|
||||
{
|
||||
"package": "library",
|
||||
"tableName": "library_books",
|
||||
"businessDB": "",
|
||||
"structName": "Book",
|
||||
"packageName": "library",
|
||||
"description": "图书信息",
|
||||
"abbreviation": "book",
|
||||
"humpPackageName": "Library",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"treeJson": "",
|
||||
"isAdd": false,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "title",
|
||||
"fieldDesc": "书名",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "title",
|
||||
"dataTypeLong": "255",
|
||||
"comment": "书名",
|
||||
"columnName": "title",
|
||||
"fieldSearchType": "LIKE",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请输入书名",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": {},
|
||||
"checkDataSource": false,
|
||||
"fieldIndexType": ""
|
||||
},
|
||||
{
|
||||
"fieldName": "AuthorID",
|
||||
"fieldDesc": "作者",
|
||||
"fieldType": "uint",
|
||||
"fieldJson": "authorId",
|
||||
"dataTypeLong": "",
|
||||
"comment": "作者ID",
|
||||
"columnName": "author_id",
|
||||
"fieldSearchType": "EQ",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请选择作者",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": {
|
||||
"dbName": "gva",
|
||||
"table": "library_authors",
|
||||
"label": "name",
|
||||
"value": "id",
|
||||
"association": 2,
|
||||
"hasDeletedAt": true
|
||||
},
|
||||
"checkDataSource": true,
|
||||
"fieldIndexType": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"package": "library",
|
||||
"tableName": "library_authors",
|
||||
"businessDB": "",
|
||||
"structName": "Author",
|
||||
"packageName": "library",
|
||||
"description": "作者信息",
|
||||
"abbreviation": "author",
|
||||
"humpPackageName": "Library",
|
||||
"gvaModel": true,
|
||||
"autoMigrate": true,
|
||||
"autoCreateResource": true,
|
||||
"autoCreateApiToSql": true,
|
||||
"autoCreateMenuToSql": true,
|
||||
"autoCreateBtnAuth": true,
|
||||
"onlyTemplate": false,
|
||||
"isTree": false,
|
||||
"treeJson": "",
|
||||
"isAdd": false,
|
||||
"generateWeb": true,
|
||||
"generateServer": true,
|
||||
"fields": [
|
||||
{
|
||||
"fieldName": "name",
|
||||
"fieldDesc": "作者姓名",
|
||||
"fieldType": "string",
|
||||
"fieldJson": "name",
|
||||
"dataTypeLong": "100",
|
||||
"comment": "作者姓名",
|
||||
"columnName": "name",
|
||||
"fieldSearchType": "LIKE",
|
||||
"fieldSearchHide": false,
|
||||
"dictType": "",
|
||||
"form": true,
|
||||
"table": true,
|
||||
"desc": true,
|
||||
"excel": true,
|
||||
"require": true,
|
||||
"defaultValue": "",
|
||||
"errorText": "请输入作者姓名",
|
||||
"clearable": true,
|
||||
"sort": false,
|
||||
"primaryKey": false,
|
||||
"dataSource": {},
|
||||
"checkDataSource": false,
|
||||
"fieldIndexType": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 第三步:执行(需要确认参数)
|
||||
```json
|
||||
{
|
||||
"action": "execute",
|
||||
"executionPlan": {
|
||||
// ... 同上面的executionPlan
|
||||
},
|
||||
"packageConfirm": "yes", // 确认创建包
|
||||
"modulesConfirm": "yes" // 确认创建模块
|
||||
}
|
||||
```
|
||||
|
||||
## 确认参数说明
|
||||
|
||||
- `packageConfirm`: 当`needCreatedPackage`为true时必需
|
||||
- "yes": 确认创建包
|
||||
- "no": 取消创建包(停止后续处理)
|
||||
|
||||
- `modulesConfirm`: 当`needCreatedModules`为true时必需
|
||||
- "yes": 确认创建模块
|
||||
- "no": 取消创建模块(停止后续处理)
|
||||
|
||||
## 取消操作的行为
|
||||
|
||||
1. 如果用户在`packageConfirm`中选择"no",系统将停止所有后续处理
|
||||
2. 如果用户在`modulesConfirm`中选择"no",系统将停止模块创建
|
||||
3. 任何取消操作都会返回相应的取消消息,不会执行任何创建操作
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 必须先调用`confirm`来获取确认信息
|
||||
2. 在`execute`时必须提供相应的确认参数
|
||||
3. 确认参数的值必须是"yes"或"no"
|
||||
4. 如果不需要创建包或模块,则不需要提供对应的确认参数
|
||||
5. 字段类型支持:string(字符串),richtext(富文本),int(整型),bool(布尔值),float64(浮点型),time.Time(时间),enum(枚举),picture(单图片,字符串),pictures(多图片,json字符串),video(视频,字符串),file(文件,json字符串),json(JSON),array(数组)
|
||||
@@ -1,79 +0,0 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&GetNickname{})
|
||||
}
|
||||
|
||||
type GetNickname struct{}
|
||||
|
||||
// 根据用户username获取nickname
|
||||
func (t *GetNickname) New() mcp.Tool {
|
||||
return mcp.NewTool("getNickname",
|
||||
mcp.WithDescription("根据用户username获取nickname"),
|
||||
mcp.WithString("username",
|
||||
mcp.Required(),
|
||||
mcp.Description("用户的username"),
|
||||
))
|
||||
}
|
||||
|
||||
// Handle 处理获取昵称的请求
|
||||
func (t *GetNickname) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 1. 参数验证
|
||||
username, ok := request.Params.Arguments["username"].(string)
|
||||
if !ok {
|
||||
return nil, errors.New("参数错误:username 必须是字符串类型")
|
||||
}
|
||||
|
||||
if username == "" {
|
||||
return nil, errors.New("参数错误:username 不能为空")
|
||||
}
|
||||
|
||||
// 2. 记录操作日志
|
||||
global.GVA_LOG.Info("getNickname 工具被调用")
|
||||
|
||||
// 3. 优化查询,只选择需要的字段
|
||||
var user struct {
|
||||
NickName string
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Model(&system.SysUser{}).
|
||||
Select("nick_name").
|
||||
Where("username = ?", username).
|
||||
First(&user).Error
|
||||
|
||||
// 4. 优化错误处理
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("用户 %s 不存在", username),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
global.GVA_LOG.Error("数据库查询错误")
|
||||
return nil, errors.New("系统错误,请稍后再试")
|
||||
}
|
||||
|
||||
// 构造回复信息
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("用户 %s 的昵称是 %s", username, user.NickName),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,287 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/service"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
RegisterTool(&MenuCreator{})
|
||||
}
|
||||
|
||||
// MenuCreateRequest 菜单创建请求结构
|
||||
type MenuCreateRequest struct {
|
||||
ParentId uint `json:"parentId"` // 父菜单ID,0表示根菜单
|
||||
Path string `json:"path"` // 路由path
|
||||
Name string `json:"name"` // 路由name
|
||||
Hidden bool `json:"hidden"` // 是否在列表隐藏
|
||||
Component string `json:"component"` // 对应前端文件路径
|
||||
Sort int `json:"sort"` // 排序标记
|
||||
Title string `json:"title"` // 菜单名
|
||||
Icon string `json:"icon"` // 菜单图标
|
||||
KeepAlive bool `json:"keepAlive"` // 是否缓存
|
||||
DefaultMenu bool `json:"defaultMenu"` // 是否是基础路由
|
||||
CloseTab bool `json:"closeTab"` // 自动关闭tab
|
||||
ActiveName string `json:"activeName"` // 高亮菜单
|
||||
Parameters []MenuParameterRequest `json:"parameters"` // 路由参数
|
||||
MenuBtn []MenuButtonRequest `json:"menuBtn"` // 菜单按钮
|
||||
}
|
||||
|
||||
// MenuParameterRequest 菜单参数请求结构
|
||||
type MenuParameterRequest struct {
|
||||
Type string `json:"type"` // 参数类型:params或query
|
||||
Key string `json:"key"` // 参数key
|
||||
Value string `json:"value"` // 参数值
|
||||
}
|
||||
|
||||
// MenuButtonRequest 菜单按钮请求结构
|
||||
type MenuButtonRequest struct {
|
||||
Name string `json:"name"` // 按钮名称
|
||||
Desc string `json:"desc"` // 按钮描述
|
||||
}
|
||||
|
||||
// MenuCreateResponse 菜单创建响应结构
|
||||
type MenuCreateResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
MenuID uint `json:"menuId"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// MenuCreator 菜单创建工具
|
||||
type MenuCreator struct{}
|
||||
|
||||
// New 创建菜单创建工具
|
||||
func (m *MenuCreator) New() mcp.Tool {
|
||||
return mcp.NewTool("create_menu",
|
||||
mcp.WithDescription(`创建前端菜单记录,用于AI编辑器自动添加前端页面时自动创建对应的菜单项。
|
||||
|
||||
**重要限制:**
|
||||
- 当使用gva_auto_generate工具且needCreatedModules=true时,模块创建会自动生成菜单项,不应调用此工具
|
||||
- 仅在以下情况使用:1) 单独创建菜单(不涉及模块创建);2) AI编辑器自动添加前端页面时`),
|
||||
mcp.WithNumber("parentId",
|
||||
mcp.Description("父菜单ID,0表示根菜单"),
|
||||
mcp.DefaultNumber(0),
|
||||
),
|
||||
mcp.WithString("path",
|
||||
mcp.Required(),
|
||||
mcp.Description("路由path,如:userList"),
|
||||
),
|
||||
mcp.WithString("name",
|
||||
mcp.Required(),
|
||||
mcp.Description("路由name,用于Vue Router,如:userList"),
|
||||
),
|
||||
mcp.WithBoolean("hidden",
|
||||
mcp.Description("是否在菜单列表中隐藏"),
|
||||
),
|
||||
mcp.WithString("component",
|
||||
mcp.Required(),
|
||||
mcp.Description("对应的前端Vue组件路径,如:view/user/list.vue"),
|
||||
),
|
||||
mcp.WithNumber("sort",
|
||||
mcp.Description("菜单排序号,数字越小越靠前"),
|
||||
mcp.DefaultNumber(1),
|
||||
),
|
||||
mcp.WithString("title",
|
||||
mcp.Required(),
|
||||
mcp.Description("菜单显示标题"),
|
||||
),
|
||||
mcp.WithString("icon",
|
||||
mcp.Description("菜单图标名称"),
|
||||
mcp.DefaultString("menu"),
|
||||
),
|
||||
mcp.WithBoolean("keepAlive",
|
||||
mcp.Description("是否缓存页面"),
|
||||
),
|
||||
mcp.WithBoolean("defaultMenu",
|
||||
mcp.Description("是否是基础路由"),
|
||||
),
|
||||
mcp.WithBoolean("closeTab",
|
||||
mcp.Description("是否自动关闭tab"),
|
||||
),
|
||||
mcp.WithString("activeName",
|
||||
mcp.Description("高亮菜单名称"),
|
||||
),
|
||||
mcp.WithString("parameters",
|
||||
mcp.Description("路由参数JSON字符串,格式:[{\"type\":\"params\",\"key\":\"id\",\"value\":\"1\"}]"),
|
||||
),
|
||||
mcp.WithString("menuBtn",
|
||||
mcp.Description("菜单按钮JSON字符串,格式:[{\"name\":\"add\",\"desc\":\"新增\"}]"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理菜单创建请求
|
||||
func (m *MenuCreator) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 解析请求参数
|
||||
args := request.GetArguments()
|
||||
|
||||
// 必需参数
|
||||
path, ok := args["path"].(string)
|
||||
if !ok || path == "" {
|
||||
return nil, errors.New("path 参数是必需的")
|
||||
}
|
||||
|
||||
name, ok := args["name"].(string)
|
||||
if !ok || name == "" {
|
||||
return nil, errors.New("name 参数是必需的")
|
||||
}
|
||||
|
||||
component, ok := args["component"].(string)
|
||||
if !ok || component == "" {
|
||||
return nil, errors.New("component 参数是必需的")
|
||||
}
|
||||
|
||||
title, ok := args["title"].(string)
|
||||
if !ok || title == "" {
|
||||
return nil, errors.New("title 参数是必需的")
|
||||
}
|
||||
|
||||
// 可选参数
|
||||
parentId := uint(0)
|
||||
if val, ok := args["parentId"].(float64); ok {
|
||||
parentId = uint(val)
|
||||
}
|
||||
|
||||
hidden := false
|
||||
if val, ok := args["hidden"].(bool); ok {
|
||||
hidden = val
|
||||
}
|
||||
|
||||
sort := 1
|
||||
if val, ok := args["sort"].(float64); ok {
|
||||
sort = int(val)
|
||||
}
|
||||
|
||||
icon := "menu"
|
||||
if val, ok := args["icon"].(string); ok && val != "" {
|
||||
icon = val
|
||||
}
|
||||
|
||||
keepAlive := false
|
||||
if val, ok := args["keepAlive"].(bool); ok {
|
||||
keepAlive = val
|
||||
}
|
||||
|
||||
defaultMenu := false
|
||||
if val, ok := args["defaultMenu"].(bool); ok {
|
||||
defaultMenu = val
|
||||
}
|
||||
|
||||
closeTab := false
|
||||
if val, ok := args["closeTab"].(bool); ok {
|
||||
closeTab = val
|
||||
}
|
||||
|
||||
activeName := ""
|
||||
if val, ok := args["activeName"].(string); ok {
|
||||
activeName = val
|
||||
}
|
||||
|
||||
// 解析参数和按钮
|
||||
var parameters []system.SysBaseMenuParameter
|
||||
if parametersStr, ok := args["parameters"].(string); ok && parametersStr != "" {
|
||||
var paramReqs []MenuParameterRequest
|
||||
if err := json.Unmarshal([]byte(parametersStr), ¶mReqs); err != nil {
|
||||
return nil, fmt.Errorf("parameters 参数格式错误: %v", err)
|
||||
}
|
||||
for _, param := range paramReqs {
|
||||
parameters = append(parameters, system.SysBaseMenuParameter{
|
||||
Type: param.Type,
|
||||
Key: param.Key,
|
||||
Value: param.Value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var menuBtn []system.SysBaseMenuBtn
|
||||
if menuBtnStr, ok := args["menuBtn"].(string); ok && menuBtnStr != "" {
|
||||
var btnReqs []MenuButtonRequest
|
||||
if err := json.Unmarshal([]byte(menuBtnStr), &btnReqs); err != nil {
|
||||
return nil, fmt.Errorf("menuBtn 参数格式错误: %v", err)
|
||||
}
|
||||
for _, btn := range btnReqs {
|
||||
menuBtn = append(menuBtn, system.SysBaseMenuBtn{
|
||||
Name: btn.Name,
|
||||
Desc: btn.Desc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 构建菜单对象
|
||||
menu := system.SysBaseMenu{
|
||||
ParentId: parentId,
|
||||
Path: path,
|
||||
Name: name,
|
||||
Hidden: hidden,
|
||||
Component: component,
|
||||
Sort: sort,
|
||||
Meta: system.Meta{
|
||||
Title: title,
|
||||
Icon: icon,
|
||||
KeepAlive: keepAlive,
|
||||
DefaultMenu: defaultMenu,
|
||||
CloseTab: closeTab,
|
||||
ActiveName: activeName,
|
||||
},
|
||||
Parameters: parameters,
|
||||
MenuBtn: menuBtn,
|
||||
}
|
||||
|
||||
// 创建菜单
|
||||
menuService := service.ServiceGroupApp.SystemServiceGroup.MenuService
|
||||
err := menuService.AddBaseMenu(menu)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建菜单失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取创建的菜单ID
|
||||
var createdMenu system.SysBaseMenu
|
||||
err = global.GVA_DB.Where("name = ? AND path = ?", name, path).First(&createdMenu).Error
|
||||
if err != nil {
|
||||
global.GVA_LOG.Warn("获取创建的菜单ID失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &MenuCreateResponse{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("成功创建菜单 %s", title),
|
||||
MenuID: createdMenu.ID,
|
||||
Name: name,
|
||||
Path: path,
|
||||
}
|
||||
|
||||
resultJSON, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化结果失败: %v", err)
|
||||
}
|
||||
|
||||
// 添加权限分配提醒
|
||||
permissionReminder := "\n\n⚠️ 重要提醒:\n" +
|
||||
"菜单创建完成后,请前往【系统管理】->【角色管理】中为相关角色分配新创建的菜单权限," +
|
||||
"以确保用户能够正常访问新菜单。\n" +
|
||||
"具体步骤:\n" +
|
||||
"1. 进入角色管理页面\n" +
|
||||
"2. 选择需要授权的角色\n" +
|
||||
"3. 在菜单权限中勾选新创建的菜单项\n" +
|
||||
"4. 保存权限配置"
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("菜单创建结果:\n\n%s%s", string(resultJSON), permissionReminder),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 注册工具
|
||||
func init() {
|
||||
// 注册工具将在enter.go中统一处理
|
||||
RegisterTool(&MenuLister{})
|
||||
}
|
||||
|
||||
// MenuListResponse 菜单列表响应结构
|
||||
type MenuListResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Menus []system.SysBaseMenu `json:"menus"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// MenuLister 菜单列表工具
|
||||
type MenuLister struct{}
|
||||
|
||||
// New 创建菜单列表工具
|
||||
func (m *MenuLister) New() mcp.Tool {
|
||||
return mcp.NewTool("list_all_menus",
|
||||
mcp.WithDescription(`获取系统中所有菜单信息,包括菜单树结构、路由信息、组件路径等,用于前端编写vue-router时正确跳转
|
||||
|
||||
**功能说明:**
|
||||
- 返回完整的菜单树形结构
|
||||
- 包含路由配置信息(path、name、component)
|
||||
- 包含菜单元数据(title、icon、keepAlive等)
|
||||
- 包含菜单参数和按钮配置
|
||||
- 支持父子菜单关系展示
|
||||
|
||||
**使用场景:**
|
||||
- 前端路由配置:获取所有菜单信息用于配置vue-router
|
||||
- 菜单权限管理:了解系统中所有可用的菜单项
|
||||
- 导航组件开发:构建动态导航菜单
|
||||
- 系统架构分析:了解系统的菜单结构和页面组织`),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理菜单列表请求
|
||||
func (m *MenuLister) Handle(_ context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
// 获取所有基础菜单
|
||||
allMenus, err := m.getAllMenus()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取菜单列表失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("获取菜单列表失败: %v", err),
|
||||
},
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 构建返回结果
|
||||
response := MenuListResponse{
|
||||
Success: true,
|
||||
Message: "获取菜单列表成功",
|
||||
Menus: allMenus,
|
||||
TotalCount: len(allMenus),
|
||||
Description: "系统中所有菜单信息的标准列表,包含路由配置和组件信息",
|
||||
}
|
||||
|
||||
// 序列化响应
|
||||
responseJSON, err := json.MarshalIndent(response, "", " ")
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("序列化菜单响应失败", zap.Error(err))
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: fmt.Sprintf("序列化响应失败: %v", err),
|
||||
},
|
||||
},
|
||||
IsError: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.TextContent{
|
||||
Type: "text",
|
||||
Text: string(responseJSON),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getAllMenus 获取所有基础菜单
|
||||
func (m *MenuLister) getAllMenus() ([]system.SysBaseMenu, error) {
|
||||
var menus []system.SysBaseMenu
|
||||
err := global.GVA_DB.Order("sort").Preload("Parameters").Preload("MenuBtn").Find(&menus).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return menus, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package mcpTool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterTool(&RequirementAnalyzer{})
|
||||
}
|
||||
|
||||
type RequirementAnalyzer struct{}
|
||||
|
||||
// RequirementAnalysisRequest 需求分析请求
|
||||
type RequirementAnalysisRequest struct {
|
||||
UserRequirement string `json:"userRequirement"`
|
||||
}
|
||||
|
||||
|
||||
|
||||
// RequirementAnalysisResponse 需求分析响应
|
||||
type RequirementAnalysisResponse struct {
|
||||
AIPrompt string `json:"aiPrompt"` // 给AI的提示词
|
||||
}
|
||||
|
||||
// New 返回工具注册信息
|
||||
func (t *RequirementAnalyzer) New() mcp.Tool {
|
||||
return mcp.NewTool("requirement_analyzer",
|
||||
mcp.WithDescription(`**🚀 需求分析工具 - 首选入口工具(最高优先级)**
|
||||
|
||||
**⭐ 重要提示:这是所有MCP工具的首选入口,请优先使用!**
|
||||
|
||||
**🎯 核心职责:**
|
||||
将用户的自然语言需求转换为AI可理解的结构化提示词
|
||||
|
||||
**📋 工作流程:**
|
||||
1. 接收用户自然语言需求描述
|
||||
2. 生成专业的AI提示词,要求AI将需求梳理为清晰的逻辑步骤:
|
||||
- **1. 第一步功能描述**
|
||||
- **2. 第二步功能描述**
|
||||
- **3. 第三步功能描述**
|
||||
- **...**
|
||||
3. 指导后续使用 gva_auto_generate 工具进行代码生成
|
||||
|
||||
**✅ 适用场景:**
|
||||
- 用户有新的业务需求需要开发
|
||||
- 需要创建新的功能模块
|
||||
- 想要快速搭建业务系统
|
||||
- 需求描述比较模糊,需要AI帮助梳理
|
||||
|
||||
**❌ 不负责的事情:**
|
||||
- 不生成具体的包名和模块名(交给 gva_auto_generate)
|
||||
- 不进行代码生成(交给 gva_auto_generate)
|
||||
- 不创建数据库表结构(交给 gva_auto_generate)
|
||||
|
||||
**🔄 推荐工作流:**
|
||||
requirement_analyzer → gva_auto_generate → 其他辅助工具
|
||||
|
||||
`),
|
||||
mcp.WithString("userRequirement",
|
||||
mcp.Required(),
|
||||
mcp.Description("用户的需求描述,支持自然语言,如:'我要做一个猫舍管理系统,用来录入猫的信息,并且记录每只猫每天的活动信息'"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// Handle 处理工具调用
|
||||
func (t *RequirementAnalyzer) Handle(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
userRequirement, ok := request.GetArguments()["userRequirement"].(string)
|
||||
if !ok || userRequirement == "" {
|
||||
return nil, errors.New("参数错误:userRequirement 必须是非空字符串")
|
||||
}
|
||||
|
||||
// 分析用户需求
|
||||
analysisResponse, err := t.analyzeRequirement(userRequirement)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("需求分析失败: %v", err)
|
||||
}
|
||||
|
||||
// 序列化响应
|
||||
responseData, err := json.Marshal(analysisResponse)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化响应失败: %v", err)
|
||||
}
|
||||
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
mcp.NewTextContent(string(responseData)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// analyzeRequirement 分析用户需求 - 专注于AI需求传递
|
||||
func (t *RequirementAnalyzer) analyzeRequirement(userRequirement string) (*RequirementAnalysisResponse, error) {
|
||||
// 生成AI提示词 - 这是唯一功能
|
||||
aiPrompt := t.generateAIPrompt(userRequirement)
|
||||
|
||||
return &RequirementAnalysisResponse{
|
||||
AIPrompt: aiPrompt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateAIPrompt 生成AI提示词 - 要求AI梳理逻辑为1xxx2xxx格式
|
||||
func (t *RequirementAnalyzer) generateAIPrompt(userRequirement string) string {
|
||||
prompt := fmt.Sprintf(`# 🤖 AI需求逻辑梳理任务
|
||||
|
||||
## 📝 用户原始需求
|
||||
%s
|
||||
|
||||
## 🎯 AI任务要求
|
||||
请将上述用户需求梳理成清晰的逻辑步骤,格式要求:
|
||||
|
||||
**1. 第一步功能描述**
|
||||
**2. 第二步功能描述**
|
||||
**3. 第三步功能描述**
|
||||
**...**
|
||||
|
||||
## 📋 梳理要求
|
||||
- 将需求拆解为具体的功能步骤
|
||||
- 每个步骤用数字编号(1、2、3...)
|
||||
- 步骤描述要清晰、具体、可执行
|
||||
- 按照业务逻辑顺序排列
|
||||
- 考虑数据流和用户操作流程
|
||||
|
||||
## 🔄 后续流程
|
||||
梳理完成后,请使用 gva_auto_generate 工具进行代码生成:
|
||||
- gva_auto_generate 会根据梳理的逻辑步骤自动生成包名、模块名
|
||||
- gva_auto_generate 会设计数据表结构和API接口
|
||||
- gva_auto_generate 会生成完整的前后端代码
|
||||
|
||||
|
||||
现在请开始梳理用户需求:"%s"`, userRequirement, userRequirement)
|
||||
|
||||
return prompt
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CasbinHandler 拦截器
|
||||
|
||||
@@ -17,7 +17,7 @@ func JWTAuth() gin.HandlerFunc {
|
||||
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||||
token := utils.GetToken(c)
|
||||
if token == "" {
|
||||
response.NoAuth("未登录或非法访问", c)
|
||||
response.NoAuth("未登录或非法访问,请登录", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func JWTAuth() gin.HandlerFunc {
|
||||
claims, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
if errors.Is(err, utils.TokenExpired) {
|
||||
response.NoAuth("授权已过期", c)
|
||||
response.NoAuth("登录已过期,请重新登录", c)
|
||||
utils.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeoutMiddleware 创建超时中间件
|
||||
// 入参 timeout 设置超时时间(例如:time.Second * 5)
|
||||
// 使用示例 xxx.Get("path",middleware.TimeoutMiddleware(30*time.Second),HandleFunc)
|
||||
func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), timeout)
|
||||
defer cancel()
|
||||
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
||||
// 使用 buffered channel 避免 goroutine 泄漏
|
||||
done := make(chan struct{}, 1)
|
||||
panicChan := make(chan interface{}, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
select {
|
||||
case panicChan <- p:
|
||||
default:
|
||||
}
|
||||
}
|
||||
select {
|
||||
case done <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}()
|
||||
|
||||
select {
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
case <-done:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
// 确保服务器超时设置足够长
|
||||
c.Header("Connection", "close")
|
||||
c.AbortWithStatusJSON(http.StatusGatewayTimeout, gin.H{
|
||||
"code": 504,
|
||||
"msg": "请求超时",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ const (
|
||||
)
|
||||
|
||||
func Result(code int, data interface{}, msg string, c *gin.Context) {
|
||||
// 开始时间
|
||||
c.JSON(http.StatusOK, Response{
|
||||
code,
|
||||
data,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package request
|
||||
|
||||
type SysDictionarySearch struct {
|
||||
Name string `json:"name" form:"name" gorm:"column:name;comment:字典名(中)"` // 字典名(中)
|
||||
}
|
||||
@@ -56,7 +56,6 @@ type ChangeUserInfo struct {
|
||||
AuthorityIds []uint `json:"authorityIds" gorm:"-"` // 角色ID
|
||||
Email string `json:"email" gorm:"comment:用户邮箱"` // 用户邮箱
|
||||
HeaderImg string `json:"headerImg" gorm:"default:https://qmplusimg.henrongyi.top/gva_header.jpg;comment:用户头像"` // 用户头像
|
||||
SideMode string `json:"sideMode" gorm:"comment:用户侧边主题"` // 用户侧边主题
|
||||
Enable int `json:"enable" gorm:"comment:冻结用户"` //冻结用户
|
||||
Authorities []system.SysAuthority `json:"-" gorm:"many2many:sys_user_authority;"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SysVersionSearch struct {
|
||||
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
|
||||
VersionName *string `json:"versionName" form:"versionName"`
|
||||
VersionCode *string `json:"versionCode" form:"versionCode"`
|
||||
request.PageInfo
|
||||
}
|
||||
|
||||
// ExportVersionRequest 导出版本请求结构体
|
||||
type ExportVersionRequest struct {
|
||||
VersionName string `json:"versionName" binding:"required"` // 版本名称
|
||||
VersionCode string `json:"versionCode" binding:"required"` // 版本号
|
||||
Description string `json:"description"` // 版本描述
|
||||
MenuIds []uint `json:"menuIds"` // 选中的菜单ID列表
|
||||
ApiIds []uint `json:"apiIds"` // 选中的API ID列表
|
||||
DictIds []uint `json:"dictIds"` // 选中的字典ID列表
|
||||
}
|
||||
|
||||
// ImportVersionRequest 导入版本请求结构体
|
||||
type ImportVersionRequest struct {
|
||||
VersionInfo VersionInfo `json:"version" binding:"required"` // 版本信息
|
||||
ExportMenu []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
ExportApi []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
ExportDictionary []system.SysDictionary `json:"dictionaries"` // 字典数据,直接复用SysDictionary
|
||||
}
|
||||
|
||||
// VersionInfo 版本信息结构体
|
||||
type VersionInfo struct {
|
||||
Name string `json:"name" binding:"required"` // 版本名称
|
||||
Code string `json:"code" binding:"required"` // 版本号
|
||||
Description string `json:"description"` // 版本描述
|
||||
ExportTime string `json:"exportTime"` // 导出时间
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
)
|
||||
|
||||
// ExportVersionResponse 导出版本响应结构体
|
||||
type ExportVersionResponse struct {
|
||||
Version request.VersionInfo `json:"version"` // 版本信息
|
||||
Menus []system.SysBaseMenu `json:"menus"` // 菜单数据,直接复用SysBaseMenu
|
||||
Apis []system.SysApi `json:"apis"` // API数据,直接复用SysApi
|
||||
Dictionaries []system.SysDictionary `json:"dictionaries"` // 字典数据,直接复用SysDictionary
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 自动生成模板SysVersion
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
)
|
||||
|
||||
// 版本管理 结构体 SysVersion
|
||||
type SysVersion struct {
|
||||
global.GVA_MODEL
|
||||
VersionName *string `json:"versionName" form:"versionName" gorm:"comment:版本名称;column:version_name;size:255;" binding:"required"` //版本名称
|
||||
VersionCode *string `json:"versionCode" form:"versionCode" gorm:"comment:版本号;column:version_code;size:100;" binding:"required"` //版本号
|
||||
Description *string `json:"description" form:"description" gorm:"comment:版本描述;column:description;size:500;"` //版本描述
|
||||
VersionData *string `json:"versionData" form:"versionData" gorm:"comment:版本数据JSON;column:version_data;type:text;"` //版本数据
|
||||
}
|
||||
|
||||
// TableName 版本管理 SysVersion自定义表名 sys_versions
|
||||
func (SysVersion) TableName() string {
|
||||
return "sys_versions"
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
global.GVA_CONFIG.Email.Nickname,
|
||||
global.GVA_CONFIG.Email.Port,
|
||||
global.GVA_CONFIG.Email.IsSSL,
|
||||
global.GVA_CONFIG.Email.IsLoginAuth,
|
||||
))
|
||||
|
||||
同样也可以再传入时写死
|
||||
@@ -26,6 +27,7 @@
|
||||
"登录密钥",
|
||||
465,
|
||||
true,
|
||||
true,
|
||||
))
|
||||
|
||||
### 2. 配置说明
|
||||
@@ -34,13 +36,14 @@
|
||||
//其中 Form 和 Secret 通常来说就是用户名和密码
|
||||
|
||||
type Email struct {
|
||||
To string // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件
|
||||
From string // 发件人 你自己要发邮件的邮箱
|
||||
Host string // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string // 昵称 发件人昵称 自定义即可 可以不填
|
||||
Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool // 是否SSL 是否开启SSL
|
||||
To string // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用 此处配置主要用于发送错误监控邮件
|
||||
From string // 发件人 你自己要发邮件的邮箱
|
||||
Host string // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string // 昵称 发件人昵称 自定义即可 可以不填
|
||||
Port int // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool // 是否SSL 是否开启SSL
|
||||
IsLoginAuth bool // 是否LoginAuth 是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)
|
||||
}
|
||||
#### 2-2 入参结构说明
|
||||
//其中 Form 和 Secret 通常来说就是用户名和密码
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package config
|
||||
|
||||
type Email struct {
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"isSSL" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
To string `mapstructure:"to" json:"to" yaml:"to"` // 收件人:多个以英文逗号分隔 例:a@qq.com b@qq.com 正式开发中请把此项目作为参数使用
|
||||
From string `mapstructure:"from" json:"from" yaml:"from"` // 发件人 你自己要发邮件的邮箱
|
||||
Host string `mapstructure:"host" json:"host" yaml:"host"` // 服务器地址 例如 smtp.qq.com 请前往QQ或者你要发邮件的邮箱查看其smtp协议
|
||||
Secret string `mapstructure:"secret" json:"secret" yaml:"secret"` // 密钥 用于登录的密钥 最好不要用邮箱密码 去邮箱smtp申请一个用于登录的密钥
|
||||
Nickname string `mapstructure:"nickname" json:"nickname" yaml:"nickname"` // 昵称 发件人昵称 通常为自己的邮箱
|
||||
Port int `mapstructure:"port" json:"port" yaml:"port"` // 端口 请前往QQ或者你要发邮件的邮箱查看其smtp协议 大多为 465
|
||||
IsSSL bool `mapstructure:"is-ssl" json:"isSSL" yaml:"is-ssl"` // 是否SSL 是否开启SSL
|
||||
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
type emailPlugin struct{}
|
||||
|
||||
func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool) *emailPlugin {
|
||||
func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bool, IsLoginAuth bool) *emailPlugin {
|
||||
global.GlobalConfig.To = To
|
||||
global.GlobalConfig.From = From
|
||||
global.GlobalConfig.Host = Host
|
||||
@@ -16,6 +16,7 @@ func CreateEmailPlug(To, From, Host, Secret, Nickname string, Port int, IsSSL bo
|
||||
global.GlobalConfig.Nickname = Nickname
|
||||
global.GlobalConfig.Port = Port
|
||||
global.GlobalConfig.IsSSL = IsSSL
|
||||
global.GlobalConfig.IsLoginAuth = IsLoginAuth
|
||||
return &emailPlugin{}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,14 @@ func send(to []string, subject string, body string) error {
|
||||
host := global.GlobalConfig.Host
|
||||
port := global.GlobalConfig.Port
|
||||
isSSL := global.GlobalConfig.IsSSL
|
||||
isLoginAuth := global.GlobalConfig.IsLoginAuth
|
||||
|
||||
auth := smtp.PlainAuth("", from, secret, host)
|
||||
var auth smtp.Auth
|
||||
if isLoginAuth {
|
||||
auth = LoginAuth(from, secret)
|
||||
} else {
|
||||
auth = smtp.PlainAuth("", from, secret, host)
|
||||
}
|
||||
e := email.NewEmail()
|
||||
if nickname != "" {
|
||||
e.From = fmt.Sprintf("%s <%s>", nickname, from)
|
||||
@@ -80,3 +86,37 @@ func send(to []string, subject string, body string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// LoginAuth 用于IBM、微软邮箱服务器的LOGIN认证方式
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte{}, nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(a.username), nil
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
// 邮箱服务器可能发送的其他提示信息
|
||||
prompt := strings.ToLower(string(fromServer))
|
||||
if strings.Contains(prompt, "username") || strings.Contains(prompt, "user") {
|
||||
return []byte(a.username), nil
|
||||
}
|
||||
if strings.Contains(prompt, "password") || strings.Contains(prompt, "pass") {
|
||||
return []byte(a.password), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package api
|
||||
|
||||
type ApiGroup struct {
|
||||
NotificationApi
|
||||
OnlineUserApi
|
||||
}
|
||||
|
||||
var ApiGroupApp = new(ApiGroup)
|
||||
@@ -0,0 +1,333 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/request"
|
||||
noticeService "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/service"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NotificationApi struct{}
|
||||
|
||||
var notificationService = noticeService.ServiceGroupApp.NotificationService
|
||||
|
||||
// CreateNotification 创建通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 创建一个新的通知
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.CreateNotificationRequest true "通知的标题、内容、类型等信息"
|
||||
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||
// @Router /notice/createNotification [post]
|
||||
func (a *NotificationApi) CreateNotification(c *gin.Context) {
|
||||
var req request.CreateNotificationRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
notification, err := notificationService.CreateNotification(req, userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("创建通知失败!" + err.Error())
|
||||
response.FailWithMessage("创建通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(notification, "创建成功", c)
|
||||
}
|
||||
|
||||
// UpdateNotification 更新通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 更新通知信息
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.UpdateNotificationRequest true "通知的更新信息"
|
||||
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||
// @Router /notice/updateNotification [put]
|
||||
func (a *NotificationApi) UpdateNotification(c *gin.Context) {
|
||||
var req request.UpdateNotificationRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
err = notificationService.UpdateNotification(req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新通知失败!" + err.Error())
|
||||
response.FailWithMessage("更新通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// DeleteNotification 删除通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 删除通知
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "通知ID"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /notice/deleteNotification/{id} [delete]
|
||||
func (a *NotificationApi) DeleteNotification(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的通知ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
err = notificationService.DeleteNotification(uint(id))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除通知失败!" + err.Error())
|
||||
response.FailWithMessage("删除通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// GetNotificationList 获取通知列表
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取通知列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.NotificationSearch true "分页和搜索条件"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /notice/getNotificationList [post]
|
||||
func (a *NotificationApi) GetNotificationList(c *gin.Context) {
|
||||
var req request.NotificationSearch
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
list, total, err := notificationService.GetNotificationList(req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取通知列表失败!" + err.Error())
|
||||
response.FailWithMessage("获取通知列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetNotificationById 根据ID获取通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 根据ID获取通知详情
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "通知ID"
|
||||
// @Success 200 {object} response.Response{data=model.Notification,msg=string} "获取成功"
|
||||
// @Router /notice/getNotification/{id} [get]
|
||||
func (a *NotificationApi) GetNotificationById(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的通知ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
notification, err := notificationService.GetNotificationById(uint(id))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取通知失败!" + err.Error())
|
||||
response.FailWithMessage("获取通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(notification, "获取成功", c)
|
||||
}
|
||||
|
||||
// PublishNotification 发布通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 发布通知
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param id path int true "通知ID"
|
||||
// @Success 200 {object} response.Response{msg=string} "发布成功"
|
||||
// @Router /notice/publishNotification/{id} [post]
|
||||
func (a *NotificationApi) PublishNotification(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的通知ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
err = notificationService.PublishNotification(uint(id))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("发布通知失败!" + err.Error())
|
||||
response.FailWithMessage("发布通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("发布成功", c)
|
||||
}
|
||||
|
||||
// SendNotification 发送通知
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 发送通知给指定用户或角色
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.SendNotificationRequest true "发送通知的请求参数"
|
||||
// @Success 200 {object} response.Response{data=response.SendNotificationResponse,msg=string} "发送成功"
|
||||
// @Router /notice/sendNotification [post]
|
||||
func (a *NotificationApi) SendNotification(c *gin.Context) {
|
||||
var req request.SendNotificationRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
result, err := notificationService.SendNotification(req, userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("发送通知失败!" + err.Error())
|
||||
response.FailWithMessage("发送通知失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(result, "发送成功", c)
|
||||
}
|
||||
|
||||
// GetUserNotifications 获取用户通知列表
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取当前用户的通知列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.UserNotificationSearch true "分页和搜索条件"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /notice/getUserNotifications [post]
|
||||
func (a *NotificationApi) GetUserNotifications(c *gin.Context) {
|
||||
var req request.UserNotificationSearch
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
list, total, err := notificationService.GetUserNotifications(userId, req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取用户通知列表失败!" + err.Error())
|
||||
response.FailWithMessage("获取用户通知列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// MarkNotificationAsRead 标记通知为已读
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 标记通知为已读
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.MarkReadRequest true "标记已读的请求参数"
|
||||
// @Success 200 {object} response.Response{msg=string} "标记成功"
|
||||
// @Router /notice/markAsRead [post]
|
||||
func (a *NotificationApi) MarkNotificationAsRead(c *gin.Context) {
|
||||
var req request.MarkReadRequest
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
err = notificationService.MarkNotificationsAsRead(userId, req.NotificationIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("标记通知已读失败!" + err.Error())
|
||||
response.FailWithMessage("标记通知已读失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("标记成功", c)
|
||||
}
|
||||
|
||||
// GetNotificationStats 获取通知统计信息
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取通知统计信息
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=response.NotificationStatsResponse,msg=string} "获取成功"
|
||||
// @Router /notice/getNotificationStats [get]
|
||||
func (a *NotificationApi) GetNotificationStats(c *gin.Context) {
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
stats, err := notificationService.GetNotificationStats(userId)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取通知统计失败!" + err.Error())
|
||||
response.FailWithMessage("获取通知统计失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(stats, "获取成功", c)
|
||||
}
|
||||
|
||||
// DeleteUserNotification 删除用户通知记录
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 删除用户通知记录,如果通知只针对单个用户则删除通知本身
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param notificationId path int true "通知ID"
|
||||
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||
// @Router /notice/deleteUserNotification/{notificationId} [delete]
|
||||
func (a *NotificationApi) DeleteUserNotification(c *gin.Context) {
|
||||
notificationIdStr := c.Param("notificationId")
|
||||
notificationId, err := strconv.ParseUint(notificationIdStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("通知ID格式错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
userId := utils.GetUserID(c)
|
||||
|
||||
err = notificationService.DeleteUserNotification(userId, uint(notificationId))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("删除用户通知失败!" + err.Error())
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/response"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/request"
|
||||
noticeResponse "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/response"
|
||||
noticeService "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OnlineUserApi struct{}
|
||||
|
||||
var onlineUserService = noticeService.ServiceGroupApp.OnlineUserService
|
||||
|
||||
// GetOnlineUsers 获取在线用户列表
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取在线用户列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=[]response.OnlineUserResponse,msg=string} "获取成功"
|
||||
// @Router /notice/onlineUser/getOnlineUsers [get]
|
||||
func (a *OnlineUserApi) GetOnlineUsers(c *gin.Context) {
|
||||
onlineUsers, err := onlineUserService.GetOnlineUsers()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取在线用户列表失败!" + err.Error())
|
||||
response.FailWithMessage("获取在线用户列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(onlineUsers, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetOnlineUsersWithPagination 获取在线用户列表(分页)
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取在线用户列表(支持分页和搜索)
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param data body request.OnlineUserSearch true "分页参数"
|
||||
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||
// @Router /notice/onlineUser/getOnlineUsers [post]
|
||||
func (a *OnlineUserApi) GetOnlineUsersWithPagination(c *gin.Context) {
|
||||
var req request.OnlineUserSearch
|
||||
err := c.ShouldBindJSON(&req)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
onlineUsers, total, err := onlineUserService.GetOnlineUsersWithPagination(req)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取在线用户列表失败!" + err.Error())
|
||||
response.FailWithMessage("获取在线用户列表失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: onlineUsers,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetOnlineUsersByRole 根据角色获取在线用户
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 根据角色获取在线用户列表
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param roleId path int true "角色ID"
|
||||
// @Success 200 {object} response.Response{data=[]response.OnlineUserResponse,msg=string} "获取成功"
|
||||
// @Router /notice/getOnlineUsersByRole/{roleId} [get]
|
||||
func (a *OnlineUserApi) GetOnlineUsersByRole(c *gin.Context) {
|
||||
roleIdStr := c.Param("roleId")
|
||||
roleId, err := strconv.ParseUint(roleIdStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的角色ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 根据角色获取用户ID列表
|
||||
userIds, err := onlineUserService.GetUsersByRoleIds([]uint{uint(roleId)})
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取角色用户失败!" + err.Error())
|
||||
response.FailWithMessage("获取角色用户失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有在线用户
|
||||
allOnlineUsers, err := onlineUserService.GetOnlineUsers()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取在线用户失败!" + err.Error())
|
||||
response.FailWithMessage("获取在线用户失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤出指定角色的在线用户
|
||||
var onlineUsers []noticeResponse.OnlineUserResponse
|
||||
userIdMap := make(map[uint]bool)
|
||||
for _, id := range userIds {
|
||||
userIdMap[id] = true
|
||||
}
|
||||
for _, user := range allOnlineUsers {
|
||||
if userIdMap[user.UserId] {
|
||||
onlineUsers = append(onlineUsers, user)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("根据角色获取在线用户失败!" + err.Error())
|
||||
response.FailWithMessage("根据角色获取在线用户失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(onlineUsers, "获取成功", c)
|
||||
}
|
||||
|
||||
// CheckUserOnline 检查用户是否在线
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 检查指定用户是否在线
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param userId path int true "用户ID"
|
||||
// @Success 200 {object} response.Response{data=bool,msg=string} "检查成功"
|
||||
// @Router /notice/checkUserOnline/{userId} [get]
|
||||
func (a *OnlineUserApi) CheckUserOnline(c *gin.Context) {
|
||||
userIdStr := c.Param("userId")
|
||||
userId, err := strconv.ParseUint(userIdStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的用户ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
isOnline := onlineUserService.IsUserOnline(uint(userId))
|
||||
response.OkWithDetailed(isOnline, "检查成功", c)
|
||||
}
|
||||
|
||||
// GetOnlineUserCount 获取在线用户数量
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取当前在线用户数量
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=int64,msg=string} "获取成功"
|
||||
// @Router /notice/getOnlineUserCount [get]
|
||||
func (a *OnlineUserApi) GetOnlineUserCount(c *gin.Context) {
|
||||
count, err := onlineUserService.GetOnlineUserCount()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取在线用户数量失败!" + err.Error())
|
||||
response.FailWithMessage("获取在线用户数量失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(count, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetOnlineUserStats 获取在线用户统计数据
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 获取在线用户统计数据(总在线数、今日登录、峰值在线、平均在线)
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{data=response.OnlineUserStatsResponse,msg=string} "获取成功"
|
||||
// @Router /notice/onlineUser/getOnlineUserStats [get]
|
||||
func (a *OnlineUserApi) GetOnlineUserStats(c *gin.Context) {
|
||||
stats, err := onlineUserService.GetOnlineUserStats()
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取在线用户统计数据失败!" + err.Error())
|
||||
response.FailWithMessage("获取在线用户统计数据失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(stats, "获取成功", c)
|
||||
}
|
||||
|
||||
// RemoveOnlineUser 移除在线用户
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 移除指定的在线用户
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Param userId path int true "用户ID"
|
||||
// @Success 200 {object} response.Response{msg=string} "移除成功"
|
||||
// @Router /notice/removeOnlineUser/{userId} [delete]
|
||||
func (a *OnlineUserApi) RemoveOnlineUser(c *gin.Context) {
|
||||
userIdStr := c.Param("userId")
|
||||
userId, err := strconv.ParseUint(userIdStr, 10, 32)
|
||||
if err != nil {
|
||||
response.FailWithMessage("无效的用户ID", c)
|
||||
return
|
||||
}
|
||||
|
||||
//// 检查权限:只有管理员或用户本人可以移除
|
||||
//currentUserId := utils.GetUserID(c)
|
||||
//if currentUserId != uint(userId) {
|
||||
// // 这里可以添加管理员权限检查
|
||||
// // 暂时允许所有用户操作,实际项目中应该添加权限验证
|
||||
//}
|
||||
|
||||
err = onlineUserService.RemoveOnlineUser(uint(userId))
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("移除在线用户失败!" + err.Error())
|
||||
response.FailWithMessage("移除在线用户失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("移除成功", c)
|
||||
}
|
||||
|
||||
// CleanOfflineUsers 清理离线用户
|
||||
// @Tags NoticeCenter
|
||||
// @Summary 清理长时间未活跃的离线用户
|
||||
// @Security ApiKeyAuth
|
||||
// @accept application/json
|
||||
// @Produce application/json
|
||||
// @Success 200 {object} response.Response{msg=string} "清理成功"
|
||||
// @Router /notice/cleanOfflineUsers [post]
|
||||
func (a *OnlineUserApi) CleanOfflineUsers(c *gin.Context) {
|
||||
// 清理30分钟未活跃的用户
|
||||
err := onlineUserService.CleanOfflineUsers(30 * time.Minute)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("清理离线用户失败!" + err.Error())
|
||||
response.FailWithMessage("清理离线用户失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("清理成功", c)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package config
|
||||
|
||||
type Config struct {
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"gorm.io/gen"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
//go:generate go mod tidy
|
||||
//go:generate go mod download
|
||||
//go:generate go run gen.go
|
||||
func main() {
|
||||
g := gen.NewGenerator(gen.Config{
|
||||
OutPath: filepath.Join("..", "..", "..", "notice", "blender", "model", "dao"),
|
||||
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
|
||||
})
|
||||
g.ApplyBasic()
|
||||
g.Execute()
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
|
||||
)
|
||||
|
||||
func Api(ctx context.Context) {
|
||||
entities := []model.SysApi{
|
||||
// 通知管理相关API
|
||||
{
|
||||
Path: "/notice/notification/createNotification",
|
||||
Description: "创建通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/updateNotification",
|
||||
Description: "更新通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "PUT",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/deleteNotification",
|
||||
Description: "删除通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/publishNotification/:id",
|
||||
Description: "发布通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/sendNotification",
|
||||
Description: "发送通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/getNotificationList",
|
||||
Description: "获取通知列表",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/getNotificationById/:id",
|
||||
Description: "根据ID获取通知",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/getUserNotifications",
|
||||
Description: "获取用户通知列表",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/markAsRead",
|
||||
Description: "标记通知已读",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/notification/getNotificationStats",
|
||||
Description: "获取通知统计",
|
||||
ApiGroup: "通知管理",
|
||||
Method: "GET",
|
||||
},
|
||||
// 在线用户管理相关API
|
||||
{
|
||||
Path: "/notice/onlineUser/removeOnlineUser/:userId",
|
||||
Description: "移除在线用户",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "DELETE",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/cleanOfflineUsers",
|
||||
Description: "清理离线用户",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/getOnlineUsers",
|
||||
Description: "获取在线用户列表",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/getOnlineUsers",
|
||||
Description: "获取在线用户列表(分页)",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "POST",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/getOnlineUsersByRole/:roleId",
|
||||
Description: "根据角色获取在线用户",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/checkUserOnline/:userId",
|
||||
Description: "检查用户是否在线",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/getOnlineUserCount",
|
||||
Description: "获取在线用户数量",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/notice/onlineUser/getOnlineUserStats",
|
||||
Description: "获取在线用户统计数据",
|
||||
ApiGroup: "在线用户管理",
|
||||
Method: "GET",
|
||||
},
|
||||
// Socket.IO相关API
|
||||
{
|
||||
Path: "/socket.io/*any",
|
||||
Description: "Socket.IO连接",
|
||||
ApiGroup: "实时通信",
|
||||
Method: "GET",
|
||||
},
|
||||
{
|
||||
Path: "/socket.io/*any",
|
||||
Description: "Socket.IO连接",
|
||||
ApiGroup: "实时通信",
|
||||
Method: "POST",
|
||||
},
|
||||
}
|
||||
utils.RegisterApis(entities...)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Gorm(ctx context.Context) {
|
||||
err := global.GVA_DB.WithContext(ctx).AutoMigrate(
|
||||
&model.Notification{},
|
||||
&model.UserRead{},
|
||||
&model.OnlineUser{},
|
||||
)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "注册表失败!")
|
||||
zap.L().Error(fmt.Sprintf("%+v", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
model "github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/plugin-tool/utils"
|
||||
)
|
||||
|
||||
func Menu(ctx context.Context) {
|
||||
entities := []model.SysBaseMenu{
|
||||
{
|
||||
MenuLevel: 0,
|
||||
ParentId: 0,
|
||||
Path: "notice",
|
||||
Name: "notice",
|
||||
Hidden: false,
|
||||
Component: "view/routerHolder.vue",
|
||||
Sort: 5,
|
||||
Meta: model.Meta{
|
||||
Title: "通知中心",
|
||||
Icon: "notification",
|
||||
},
|
||||
},
|
||||
{
|
||||
MenuLevel: 0,
|
||||
ParentId: 0, // 这里需要在运行时动态设置为父菜单ID
|
||||
Path: "notificationManage",
|
||||
Name: "notificationManage",
|
||||
Hidden: false,
|
||||
Component: "view/notice/notification/index.vue",
|
||||
Sort: 1,
|
||||
Meta: model.Meta{
|
||||
Title: "通知管理",
|
||||
Icon: "message",
|
||||
},
|
||||
},
|
||||
{
|
||||
MenuLevel: 0,
|
||||
ParentId: 0, // 这里需要在运行时动态设置为父菜单ID
|
||||
Path: "onlineUserManage",
|
||||
Name: "onlineUserManage",
|
||||
Hidden: false,
|
||||
Component: "view/notice/onlineUser/index.vue",
|
||||
Sort: 2,
|
||||
Meta: model.Meta{
|
||||
Title: "在线用户",
|
||||
Icon: "user",
|
||||
},
|
||||
},
|
||||
{
|
||||
MenuLevel: 0,
|
||||
ParentId: 0, // 这里需要在运行时动态设置为父菜单ID
|
||||
Path: "userCenter",
|
||||
Name: "userCenter",
|
||||
Hidden: false,
|
||||
Component: "view/notice/userCenter/index.vue",
|
||||
Sort: 3,
|
||||
Meta: model.Meta{
|
||||
Title: "我的通知",
|
||||
Icon: "bell",
|
||||
},
|
||||
},
|
||||
}
|
||||
utils.RegisterMenus(entities...)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/router"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Router(engine *gin.Engine) {
|
||||
public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
|
||||
private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("")
|
||||
private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
|
||||
|
||||
// 初始化通知中心路由
|
||||
noticeGroup := private.Group("notice")
|
||||
{
|
||||
// 初始化通知管理路由
|
||||
router.RouterGroupApp.NotificationRouter.InitNotificationRouter(noticeGroup)
|
||||
|
||||
// 初始化在线用户管理路由
|
||||
router.RouterGroupApp.OnlineUserRouter.InitOnlineUserRouter(noticeGroup)
|
||||
|
||||
// 初始化Socket.IO路由
|
||||
router.RouterGroupApp.SocketRouter.InitSocketRouter(public)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/plugin"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Viper() {
|
||||
err := global.GVA_VP.UnmarshalKey("notice", &plugin.Config)
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "初始化配置文件失败!")
|
||||
zap.L().Error(fmt.Sprintf("%+v", err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Notification 通知信息模型
|
||||
type Notification struct {
|
||||
global.GVA_MODEL
|
||||
Title string `json:"title" gorm:"column:title;type:varchar(255);not null;comment:通知标题"`
|
||||
Content string `json:"content" gorm:"column:content;type:text;not null;comment:通知内容"`
|
||||
Type string `json:"type" gorm:"column:type;type:varchar(50);not null;default:system;comment:通知类型:system-系统通知,personal-个人通知,role-角色通知"`
|
||||
Priority string `json:"priority" gorm:"column:priority;type:varchar(20);not null;default:normal;comment:通知优先级:low-低,normal-普通,high-高,urgent-紧急"`
|
||||
SenderId uint `json:"senderId" gorm:"column:sender_id;not null;comment:发送者用户ID"`
|
||||
TargetType string `json:"targetType" gorm:"column:target_type;type:varchar(20);not null;default:all;comment:目标类型:all-全部用户,user-指定用户,role-指定角色"`
|
||||
TargetIds string `json:"targetIds" gorm:"column:target_ids;type:text;comment:目标用户或角色ID列表,JSON格式"`
|
||||
Status string `json:"status" gorm:"column:status;type:varchar(20);not null;default:draft;comment:通知状态:draft-草稿,published-已发布,expired-已过期"`
|
||||
PublishTime *time.Time `json:"publishTime" gorm:"column:publish_time;comment:通知发布时间"`
|
||||
ExpireTime *time.Time `json:"expireTime" gorm:"column:expire_time;comment:通知过期时间"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (Notification) TableName() string {
|
||||
return "notice_notifications"
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OnlineUser 在线用户模型
|
||||
type OnlineUser struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `json:"userId" gorm:"column:user_id;not null;uniqueIndex;comment:用户ID"`
|
||||
SocketId string `json:"socketId" gorm:"column:socket_id;type:varchar(255);not null;comment:Socket连接ID"`
|
||||
LastActiveTime time.Time `json:"lastActiveTime" gorm:"column:last_active_time;not null;comment:最后活跃时间"`
|
||||
Status string `json:"status" gorm:"column:status;type:varchar(20);not null;default:online;comment:在线状态:online-在线,offline-离线"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (OnlineUser) TableName() string {
|
||||
return "notice_online_users"
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NotificationSearch 通知搜索请求
|
||||
type NotificationSearch struct {
|
||||
request.PageInfo
|
||||
Title string `json:"title" form:"title"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Priority string `json:"priority" form:"priority"`
|
||||
Status string `json:"status" form:"status"`
|
||||
SenderId uint `json:"senderId" form:"senderId"`
|
||||
TargetType string `json:"targetType" form:"targetType"`
|
||||
StartTime *time.Time `json:"startTime" form:"startTime"`
|
||||
EndTime *time.Time `json:"endTime" form:"endTime"`
|
||||
}
|
||||
|
||||
// CreateNotificationRequest 创建通知请求
|
||||
type CreateNotificationRequest struct {
|
||||
Title string `json:"title" binding:"required" form:"title"`
|
||||
Content string `json:"content" binding:"required" form:"content"`
|
||||
Type string `json:"type" binding:"required" form:"type"`
|
||||
Priority string `json:"priority" form:"priority"`
|
||||
TargetType string `json:"targetType" binding:"required" form:"targetType"`
|
||||
TargetIds []uint `json:"targetIds" form:"targetIds"`
|
||||
PublishTime *time.Time `json:"publishTime" form:"publishTime"`
|
||||
ExpireTime *time.Time `json:"expireTime" form:"expireTime"`
|
||||
}
|
||||
|
||||
// UpdateNotificationRequest 更新通知请求
|
||||
type UpdateNotificationRequest struct {
|
||||
ID uint `json:"id" binding:"required"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Priority string `json:"priority" binding:"required"`
|
||||
TargetType string `json:"targetType" binding:"required"`
|
||||
TargetIds []uint `json:"targetIds"`
|
||||
Status string `json:"status" binding:"required"`
|
||||
PublishTime *time.Time `json:"publishTime"`
|
||||
ExpireTime *time.Time `json:"expireTime"`
|
||||
}
|
||||
|
||||
// SendNotificationRequest 发送通知请求
|
||||
type SendNotificationRequest struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Content string `json:"content" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Priority string `json:"priority" binding:"required"`
|
||||
TargetType string `json:"targetType" binding:"required"`
|
||||
TargetIds []uint `json:"targetIds"`
|
||||
RoleIds []uint `json:"roleIds"`
|
||||
}
|
||||
|
||||
// MarkReadRequest 标记已读请求
|
||||
type MarkReadRequest struct {
|
||||
NotificationIds []uint `json:"notificationIds" binding:"required"`
|
||||
}
|
||||
|
||||
// UserNotificationSearch 用户通知搜索请求
|
||||
type UserNotificationSearch struct {
|
||||
request.PageInfo
|
||||
IsRead *bool `json:"isRead" form:"isRead"`
|
||||
Type string `json:"type" form:"type"`
|
||||
Priority string `json:"priority" form:"priority"`
|
||||
StartTime *time.Time `json:"startTime" form:"startTime"`
|
||||
EndTime *time.Time `json:"endTime" form:"endTime"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package request
|
||||
|
||||
import "github.com/flipped-aurora/gin-vue-admin/server/model/common/request"
|
||||
|
||||
// OnlineUserSearch 在线用户搜索请求
|
||||
type OnlineUserSearch struct {
|
||||
request.PageInfo
|
||||
Username string `json:"username" form:"username"` // 用户名搜索
|
||||
NickName string `json:"nickName" form:"nickName"` // 昵称搜索
|
||||
RoleId uint `json:"roleId" form:"roleId"` // 角色ID过滤
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// NotificationResponse 通知响应
|
||||
type NotificationResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
Priority string `json:"priority"`
|
||||
SenderId uint `json:"senderId"`
|
||||
SenderName string `json:"senderName"`
|
||||
TargetType string `json:"targetType"`
|
||||
TargetIds string `json:"targetIds"`
|
||||
Status string `json:"status"`
|
||||
PublishTime *time.Time `json:"publishTime"`
|
||||
ExpireTime *time.Time `json:"expireTime"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// UserNotificationResponse 用户通知响应
|
||||
type UserNotificationResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
Priority string `json:"priority"`
|
||||
SenderId uint `json:"senderId"`
|
||||
SenderName string `json:"senderName"`
|
||||
IsRead bool `json:"isRead"`
|
||||
ReadTime *time.Time `json:"readTime"`
|
||||
PublishTime *time.Time `json:"publishTime"`
|
||||
ExpireTime *time.Time `json:"expireTime"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// NotificationStatsResponse 通知统计响应
|
||||
type NotificationStatsResponse struct {
|
||||
TotalCount int64 `json:"totalCount"`
|
||||
UnreadCount int64 `json:"unreadCount"`
|
||||
ReadCount int64 `json:"readCount"`
|
||||
}
|
||||
|
||||
// OnlineUserResponse 在线用户响应
|
||||
type OnlineUserResponse struct {
|
||||
UserId uint `json:"userId"` // 用户ID
|
||||
Username string `json:"username"` // 用户名
|
||||
NickName string `json:"nickName"` // 昵称
|
||||
SocketId string `json:"socketId"` // Socket连接ID
|
||||
LastActiveTime time.Time `json:"lastActiveTime"` // 最后活跃时间
|
||||
Status string `json:"status"` // 状态
|
||||
OnlineDuration int64 `json:"onlineDuration"` // 在线时长(分钟)
|
||||
}
|
||||
|
||||
// OnlineUserStatsResponse 在线用户统计响应结构体
|
||||
type OnlineUserStatsResponse struct {
|
||||
TotalOnline int64 `json:"totalOnline"` // 当前在线总数
|
||||
TodayLogin int64 `json:"todayLogin"` // 今日登录人数
|
||||
PeakOnline int64 `json:"peakOnline"` // 峰值在线人数
|
||||
AverageOnline int64 `json:"averageOnline"` // 平均在线人数
|
||||
}
|
||||
|
||||
// SendNotificationResponse 发送通知响应
|
||||
type SendNotificationResponse struct {
|
||||
NotificationId uint `json:"notificationId"`
|
||||
SuccessCount int `json:"successCount"`
|
||||
FailCount int `json:"failCount"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserRead 用户通知读取状态模型
|
||||
type UserRead struct {
|
||||
global.GVA_MODEL
|
||||
UserId uint `json:"userId" gorm:"column:user_id;not null;index;comment:用户ID"`
|
||||
NotificationId uint `json:"notificationId" gorm:"column:notification_id;not null;index;comment:通知ID"`
|
||||
IsRead bool `json:"isRead" gorm:"column:is_read;not null;default:false;comment:是否已读"`
|
||||
ReadTime *time.Time `json:"readTime" gorm:"column:read_time;comment:通知读取时间"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (UserRead) TableName() string {
|
||||
return "notice_user_reads"
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package notice
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/initialize"
|
||||
interfaces "github.com/flipped-aurora/gin-vue-admin/server/utils/plugin/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
var _ interfaces.Plugin = (*plugin)(nil)
|
||||
|
||||
var Plugin = new(plugin)
|
||||
|
||||
type plugin struct{}
|
||||
|
||||
// Register 注册插件
|
||||
func (p *plugin) Register(group *gin.Engine) {
|
||||
ctx := context.Background()
|
||||
// 初始化数据库
|
||||
initialize.Gorm(ctx)
|
||||
// 初始化路由
|
||||
initialize.Router(group)
|
||||
// 初始化菜单
|
||||
initialize.Menu(ctx)
|
||||
// 初始化 api
|
||||
initialize.Api(ctx)
|
||||
}
|
||||
|
||||
// RouterPath 返回插件路由路径
|
||||
func (p *plugin) RouterPath() string {
|
||||
return "notice"
|
||||
}
|
||||
|
||||
// Name 返回插件名称
|
||||
func (p *plugin) Name() string {
|
||||
return "notice"
|
||||
}
|
||||
|
||||
// Description 返回插件描述
|
||||
func (p *plugin) Description() string {
|
||||
return "通知中心插件,提供实时通知、在线用户管理等功能"
|
||||
}
|
||||
|
||||
// Version 返回插件版本
|
||||
func (p *plugin) Version() string {
|
||||
return "v1.0.0"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package plugin
|
||||
|
||||
import "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/config"
|
||||
|
||||
var Config config.Config
|
||||
@@ -0,0 +1,9 @@
|
||||
package router
|
||||
|
||||
type RouterGroup struct {
|
||||
NotificationRouter
|
||||
OnlineUserRouter
|
||||
SocketRouter
|
||||
}
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
@@ -0,0 +1,41 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
|
||||
noticeApi "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NotificationRouter struct{}
|
||||
|
||||
// InitNotificationRouter 初始化通知路由
|
||||
func (r *NotificationRouter) InitNotificationRouter(Router *gin.RouterGroup) {
|
||||
notificationRouter := Router.Group("notification")
|
||||
notificationRouterWithoutRecord := Router.Group("notification")
|
||||
|
||||
// 需要记录操作日志的路由
|
||||
notificationRouter.Use(middleware.OperationRecord())
|
||||
{
|
||||
// 管理员接口
|
||||
notificationRouter.POST("createNotification", noticeApi.ApiGroupApp.NotificationApi.CreateNotification) // 创建通知
|
||||
notificationRouter.PUT("updateNotification", noticeApi.ApiGroupApp.NotificationApi.UpdateNotification) // 更新通知
|
||||
notificationRouter.DELETE("deleteNotification", noticeApi.ApiGroupApp.NotificationApi.DeleteNotification) // 删除通知
|
||||
notificationRouter.POST("publishNotification/:id", noticeApi.ApiGroupApp.NotificationApi.PublishNotification) // 发布通知
|
||||
notificationRouter.POST("sendNotification", noticeApi.ApiGroupApp.NotificationApi.SendNotification) // 发送通知
|
||||
}
|
||||
|
||||
// 不需要记录操作日志的路由
|
||||
notificationRouterWithoutRecord.Use(middleware.JWTAuth())
|
||||
{
|
||||
// 查询接口
|
||||
notificationRouterWithoutRecord.POST("getNotificationList", noticeApi.ApiGroupApp.NotificationApi.GetNotificationList) // 获取通知列表
|
||||
notificationRouterWithoutRecord.GET("getNotificationById/:id", noticeApi.ApiGroupApp.NotificationApi.GetNotificationById) // 根据ID获取通知
|
||||
|
||||
// 用户接口
|
||||
notificationRouterWithoutRecord.POST("getUserNotifications", noticeApi.ApiGroupApp.NotificationApi.GetUserNotifications) // 获取用户通知列表
|
||||
notificationRouterWithoutRecord.POST("markAsRead", noticeApi.ApiGroupApp.NotificationApi.MarkNotificationAsRead) // 标记已读
|
||||
notificationRouterWithoutRecord.GET("getNotificationStats", noticeApi.ApiGroupApp.NotificationApi.GetNotificationStats) // 获取通知统计
|
||||
notificationRouterWithoutRecord.DELETE("deleteUserNotification/:notificationId", noticeApi.ApiGroupApp.NotificationApi.DeleteUserNotification) // 删除用户通知
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
|
||||
noticeApi "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OnlineUserRouter struct{}
|
||||
|
||||
// InitOnlineUserRouter 初始化在线用户路由
|
||||
func (r *OnlineUserRouter) InitOnlineUserRouter(Router *gin.RouterGroup) {
|
||||
onlineUserRouter := Router.Group("onlineUser")
|
||||
onlineUserRouterWithoutRecord := Router.Group("onlineUser")
|
||||
|
||||
// 需要记录操作日志的路由
|
||||
onlineUserRouter.Use(middleware.OperationRecord())
|
||||
{
|
||||
// 管理员接口
|
||||
onlineUserRouter.DELETE("removeOnlineUser/:userId", noticeApi.ApiGroupApp.OnlineUserApi.RemoveOnlineUser) // 移除在线用户
|
||||
onlineUserRouter.POST("cleanOfflineUsers", noticeApi.ApiGroupApp.OnlineUserApi.CleanOfflineUsers) // 清理离线用户
|
||||
}
|
||||
|
||||
// 不需要记录操作日志的路由
|
||||
onlineUserRouterWithoutRecord.Use(middleware.JWTAuth())
|
||||
{
|
||||
// 查询接口
|
||||
onlineUserRouterWithoutRecord.GET("getOnlineUsers", noticeApi.ApiGroupApp.OnlineUserApi.GetOnlineUsers) // 获取在线用户列表
|
||||
onlineUserRouterWithoutRecord.POST("getOnlineUsers", noticeApi.ApiGroupApp.OnlineUserApi.GetOnlineUsersWithPagination) // 获取在线用户列表(分页)
|
||||
onlineUserRouterWithoutRecord.GET("getOnlineUsersByRole/:roleId", noticeApi.ApiGroupApp.OnlineUserApi.GetOnlineUsersByRole) // 根据角色获取在线用户
|
||||
onlineUserRouterWithoutRecord.GET("checkUserOnline/:userId", noticeApi.ApiGroupApp.OnlineUserApi.CheckUserOnline) // 检查用户是否在线
|
||||
onlineUserRouterWithoutRecord.GET("getOnlineUserCount", noticeApi.ApiGroupApp.OnlineUserApi.GetOnlineUserCount) // 获取在线用户数量
|
||||
onlineUserRouterWithoutRecord.GET("getOnlineUserStats", noticeApi.ApiGroupApp.OnlineUserApi.GetOnlineUserStats) // 获取在线用户统计数据
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/utils"
|
||||
"go.uber.org/zap"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/doquangtan/socketio/v4"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
noticeService "github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SocketRouter struct{}
|
||||
|
||||
var (
|
||||
onlineUserService = noticeService.ServiceGroupApp.OnlineUserService
|
||||
socketService = noticeService.ServiceGroupApp.SocketService
|
||||
)
|
||||
|
||||
// InitSocketRouter 初始化Socket.IO路由
|
||||
func (r *SocketRouter) InitSocketRouter(Router *gin.RouterGroup) {
|
||||
// 创建Socket.IO服务器
|
||||
io := socketio.New()
|
||||
var claims *request.CustomClaims
|
||||
// 认证中间件
|
||||
io.OnAuthentication(func(params map[string]string) bool {
|
||||
// 从参数获取token
|
||||
token, ok := params["token"]
|
||||
if !ok || token == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
j := utils.NewJWT()
|
||||
// parseToken 解析token包含的信息
|
||||
claimsP, err := j.ParseToken(token)
|
||||
if err != nil {
|
||||
zap.L().Error("JWT解析失败: " + err.Error())
|
||||
return false
|
||||
}
|
||||
claims = claimsP
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// 连接事件
|
||||
io.OnConnection(func(socket *socketio.Socket) {
|
||||
if claims == nil {
|
||||
global.GVA_LOG.Error("用户认证失败,无法建立连接")
|
||||
socket.Disconnect()
|
||||
return
|
||||
}
|
||||
userInfo := claims
|
||||
userID := userInfo.BaseClaims.ID
|
||||
socketID := socket.Id
|
||||
|
||||
// 加入用户房间
|
||||
userRoom := "user_" + strconv.Itoa(int(userID))
|
||||
socket.Join(userRoom)
|
||||
// 添加到在线用户列表
|
||||
err := onlineUserService.AddOnlineUser(userID, socketID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("添加在线用户失败: " + err.Error())
|
||||
}
|
||||
// 添加到Socket连接管理
|
||||
socketService.AddConnection(userID, socketID, socket)
|
||||
|
||||
// 记录用户连接信息
|
||||
global.GVA_LOG.Info("用户连接详情: UserID=" + strconv.Itoa(int(userID)) +
|
||||
", SocketID=" + socketID)
|
||||
|
||||
// 发送用户上线通知
|
||||
socket.Emit("user_online", map[string]interface{}{
|
||||
"userId": userID,
|
||||
"socketId": socketID,
|
||||
"timestamp": time.Now(),
|
||||
"message": "连接成功",
|
||||
})
|
||||
|
||||
global.GVA_LOG.Info("用户连接成功: UserID=" + strconv.Itoa(int(userID)) + ", SocketID=" + socketID)
|
||||
|
||||
// 监听心跳事件
|
||||
socket.On("heartbeat", func(event *socketio.EventPayload) {
|
||||
// 更新用户活跃时间
|
||||
err := onlineUserService.UpdateUserActivity(userID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("更新用户活跃时间失败: " + err.Error())
|
||||
}
|
||||
|
||||
// 回复心跳
|
||||
socket.Emit("heartbeat_reply", map[string]interface{}{
|
||||
"timestamp": time.Now(),
|
||||
"status": "ok",
|
||||
})
|
||||
})
|
||||
|
||||
// 监听通知已读事件
|
||||
socket.On("mark_notification_read", func(event *socketio.EventPayload) {
|
||||
if event.Data != nil && len(event.Data) > 0 {
|
||||
if dataMap, ok := event.Data[0].(map[string]interface{}); ok {
|
||||
if notificationIds, exists := dataMap["notificationIds"]; exists {
|
||||
if ids, ok := notificationIds.([]interface{}); ok {
|
||||
var uintIds []uint
|
||||
for _, id := range ids {
|
||||
if idFloat, ok := id.(float64); ok {
|
||||
uintIds = append(uintIds, uint(idFloat))
|
||||
}
|
||||
}
|
||||
|
||||
// 标记通知为已读
|
||||
notificationService := noticeService.ServiceGroupApp.NotificationService
|
||||
err := notificationService.MarkNotificationsAsRead(userID, uintIds)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("标记通知已读失败: " + err.Error())
|
||||
socket.Emit("error", map[string]interface{}{
|
||||
"message": "标记通知已读失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 发送成功响应
|
||||
socket.Emit("notification_read_success", map[string]interface{}{
|
||||
"notificationIds": uintIds,
|
||||
"timestamp": time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 监听获取通知统计事件
|
||||
socket.On("get_notification_stats", func(event *socketio.EventPayload) {
|
||||
notificationService := noticeService.ServiceGroupApp.NotificationService
|
||||
stats, err := notificationService.GetNotificationStats(userID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("获取通知统计失败: " + err.Error())
|
||||
socket.Emit("error", map[string]interface{}{
|
||||
"message": "获取通知统计失败",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
socket.Emit("notification_stats", stats)
|
||||
})
|
||||
|
||||
// 监听断开连接事件
|
||||
socket.On("disconnect", func(event *socketio.EventPayload) {
|
||||
// 从在线用户列表移除
|
||||
err := onlineUserService.RemoveOnlineUser(userID)
|
||||
if err != nil {
|
||||
global.GVA_LOG.Error("移除在线用户失败: " + err.Error())
|
||||
}
|
||||
|
||||
// 从Socket连接管理移除
|
||||
socketService.RemoveConnectionByUserId(userID)
|
||||
|
||||
global.GVA_LOG.Info("用户断开连接: UserID=" + strconv.Itoa(int(userID)) + ", SocketID=" + socketID)
|
||||
})
|
||||
})
|
||||
|
||||
// 注册Socket.IO路由
|
||||
Router.GET("/socket.io/*any", gin.WrapH(io.HttpHandler()))
|
||||
Router.POST("/socket.io/*any", gin.WrapH(io.HttpHandler()))
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package service
|
||||
|
||||
import "github.com/doquangtan/socketio/v4"
|
||||
|
||||
type ServiceGroup struct {
|
||||
NotificationService
|
||||
OnlineUserService
|
||||
SocketService
|
||||
}
|
||||
|
||||
var ServiceGroupApp = &ServiceGroup{
|
||||
SocketService: SocketService{
|
||||
connections: make(map[string]*socketio.Socket),
|
||||
userSockets: make(map[uint]string),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,507 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
systemModel "github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/response"
|
||||
)
|
||||
|
||||
type NotificationService struct{}
|
||||
|
||||
// CreateNotification 创建通知
|
||||
func (s *NotificationService) CreateNotification(req request.CreateNotificationRequest, senderId uint) (notification model.Notification, err error) {
|
||||
// 将 []uint 类型的 TargetIds 转换为 JSON 字符串
|
||||
targetIdsJson := ""
|
||||
if len(req.TargetIds) > 0 {
|
||||
targetIdsBytes, err := json.Marshal(req.TargetIds)
|
||||
if err != nil {
|
||||
return notification, fmt.Errorf("序列化目标用户ID失败: %v", err)
|
||||
}
|
||||
targetIdsJson = string(targetIdsBytes)
|
||||
}
|
||||
|
||||
notification = model.Notification{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Type: req.Type,
|
||||
Priority: req.Priority,
|
||||
SenderId: senderId,
|
||||
TargetType: req.TargetType,
|
||||
TargetIds: targetIdsJson,
|
||||
Status: "draft",
|
||||
PublishTime: req.PublishTime,
|
||||
ExpireTime: req.ExpireTime,
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Create(¬ification).Error
|
||||
return notification, err
|
||||
}
|
||||
|
||||
// UpdateNotification 更新通知
|
||||
func (s *NotificationService) UpdateNotification(req request.UpdateNotificationRequest) error {
|
||||
var notification model.Notification
|
||||
if err := global.GVA_DB.First(¬ification, req.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 将 []uint 类型的 TargetIds 转换为 JSON 字符串
|
||||
targetIdsJson := ""
|
||||
if len(req.TargetIds) > 0 {
|
||||
targetIdsBytes, err := json.Marshal(req.TargetIds)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化目标用户ID失败: %v", err)
|
||||
}
|
||||
targetIdsJson = string(targetIdsBytes)
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
"title": req.Title,
|
||||
"content": req.Content,
|
||||
"type": req.Type,
|
||||
"priority": req.Priority,
|
||||
"target_type": req.TargetType,
|
||||
"target_ids": targetIdsJson,
|
||||
"status": req.Status,
|
||||
"publish_time": req.PublishTime,
|
||||
"expire_time": req.ExpireTime,
|
||||
}
|
||||
|
||||
return global.GVA_DB.Model(¬ification).Updates(updates).Error
|
||||
}
|
||||
|
||||
// DeleteNotification 删除通知
|
||||
func (s *NotificationService) DeleteNotification(id uint) error {
|
||||
return global.GVA_DB.Delete(&model.Notification{}, id).Error
|
||||
}
|
||||
|
||||
// GetNotificationList 获取通知列表
|
||||
func (s *NotificationService) GetNotificationList(req request.NotificationSearch) (list []response.NotificationResponse, total int64, err error) {
|
||||
limit := req.PageSize
|
||||
offset := req.PageSize * (req.Page - 1)
|
||||
db := global.GVA_DB.Model(&model.Notification{})
|
||||
|
||||
// 构建查询条件
|
||||
if req.Title != "" {
|
||||
db = db.Where("title LIKE ?", "%"+req.Title+"%")
|
||||
}
|
||||
if req.Type != "" {
|
||||
db = db.Where("type = ?", req.Type)
|
||||
}
|
||||
if req.Priority != "" {
|
||||
db = db.Where("priority = ?", req.Priority)
|
||||
}
|
||||
if req.Status != "" {
|
||||
db = db.Where("status = ?", req.Status)
|
||||
}
|
||||
if req.SenderId != 0 {
|
||||
db = db.Where("sender_id = ?", req.SenderId)
|
||||
}
|
||||
if req.TargetType != "" {
|
||||
db = db.Where("target_type = ?", req.TargetType)
|
||||
}
|
||||
if req.StartTime != nil {
|
||||
db = db.Where("created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
db = db.Where("created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var notifications []model.Notification
|
||||
err = db.Limit(limit).Offset(offset).Order("created_at DESC").Find(¬ifications).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取发送者信息
|
||||
var senderIds []uint
|
||||
for _, notification := range notifications {
|
||||
senderIds = append(senderIds, notification.SenderId)
|
||||
}
|
||||
|
||||
var users []systemModel.SysUser
|
||||
userMap := make(map[uint]string)
|
||||
if len(senderIds) > 0 {
|
||||
global.GVA_DB.Where("id IN ?", senderIds).Find(&users)
|
||||
for _, user := range users {
|
||||
userMap[user.ID] = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应数据
|
||||
for _, notification := range notifications {
|
||||
senderName := userMap[notification.SenderId]
|
||||
list = append(list, response.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
Title: notification.Title,
|
||||
Content: notification.Content,
|
||||
Type: notification.Type,
|
||||
Priority: notification.Priority,
|
||||
SenderId: notification.SenderId,
|
||||
SenderName: senderName,
|
||||
TargetType: notification.TargetType,
|
||||
TargetIds: notification.TargetIds,
|
||||
Status: notification.Status,
|
||||
PublishTime: notification.PublishTime,
|
||||
ExpireTime: notification.ExpireTime,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
// GetNotificationById 根据ID获取通知
|
||||
func (s *NotificationService) GetNotificationById(id uint) (notification model.Notification, err error) {
|
||||
err = global.GVA_DB.First(¬ification, id).Error
|
||||
return
|
||||
}
|
||||
|
||||
// PublishNotification 发布通知
|
||||
func (s *NotificationService) PublishNotification(id uint) error {
|
||||
// 获取通知详情
|
||||
var notification model.Notification
|
||||
if err := global.GVA_DB.First(¬ification, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查通知是否已经发布
|
||||
if notification.Status == "published" {
|
||||
return fmt.Errorf("通知已经发布")
|
||||
}
|
||||
|
||||
// 更新通知状态为已发布
|
||||
now := time.Now()
|
||||
if err := global.GVA_DB.Model(&model.Notification{}).Where("id = ?", id).Updates(map[string]interface{}{
|
||||
"status": "published",
|
||||
"publish_time": &now,
|
||||
}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 重新获取更新后的通知信息
|
||||
if err := global.GVA_DB.First(¬ification, id).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 根据目标类型创建用户读取记录
|
||||
var targetUserIds []uint
|
||||
var targetIds []uint
|
||||
if notification.TargetIds != "" {
|
||||
json.Unmarshal([]byte(notification.TargetIds), &targetIds)
|
||||
}
|
||||
|
||||
switch notification.TargetType {
|
||||
case "all":
|
||||
// 获取所有用户ID
|
||||
var users []systemModel.SysUser
|
||||
global.GVA_DB.Select("id").Find(&users)
|
||||
for _, user := range users {
|
||||
targetUserIds = append(targetUserIds, user.ID)
|
||||
}
|
||||
case "users":
|
||||
targetUserIds = targetIds
|
||||
case "roles":
|
||||
// 根据角色获取用户ID
|
||||
if len(targetIds) > 0 {
|
||||
var users []systemModel.SysUser
|
||||
global.GVA_DB.Joins("JOIN sys_user_authority ON sys_users.id = sys_user_authority.sys_user_id").
|
||||
Where("sys_user_authority.sys_authority_id IN ?", targetIds).
|
||||
Select("DISTINCT sys_users.id").
|
||||
Find(&users)
|
||||
for _, user := range users {
|
||||
targetUserIds = append(targetUserIds, user.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量创建用户读取记录
|
||||
var userReads []model.UserRead
|
||||
for _, userId := range targetUserIds {
|
||||
userReads = append(userReads, model.UserRead{
|
||||
UserId: userId,
|
||||
NotificationId: notification.ID,
|
||||
IsRead: false,
|
||||
})
|
||||
}
|
||||
|
||||
if len(userReads) > 0 {
|
||||
if err := global.GVA_DB.CreateInBatches(userReads, 100).Error; err != nil {
|
||||
return fmt.Errorf("创建用户读取记录失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 通过Socket实时推送通知给在线用户
|
||||
socketService := ServiceGroupApp.SocketService
|
||||
switch notification.TargetType {
|
||||
case "all":
|
||||
// 广播给所有在线用户
|
||||
socketService.BroadcastNotification(¬ification)
|
||||
default:
|
||||
// 发送给指定的在线用户
|
||||
if len(targetUserIds) > 0 {
|
||||
socketService.SendNotificationToUsers(targetUserIds, ¬ification)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendNotification 发送通知
|
||||
func (s *NotificationService) SendNotification(req request.SendNotificationRequest, senderId uint) (response.SendNotificationResponse, error) {
|
||||
var result response.SendNotificationResponse
|
||||
|
||||
// 创建通知记录
|
||||
targetIds, _ := json.Marshal(req.TargetIds)
|
||||
notification := model.Notification{
|
||||
Title: req.Title,
|
||||
Content: req.Content,
|
||||
Type: req.Type,
|
||||
Priority: req.Priority,
|
||||
SenderId: senderId,
|
||||
TargetType: req.TargetType,
|
||||
TargetIds: string(targetIds),
|
||||
Status: "published",
|
||||
PublishTime: &time.Time{},
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
notification.PublishTime = &now
|
||||
|
||||
if err := global.GVA_DB.Create(¬ification).Error; err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
result.NotificationId = notification.ID
|
||||
|
||||
// 根据目标类型创建用户读取记录
|
||||
var targetUserIds []uint
|
||||
switch req.TargetType {
|
||||
case "all":
|
||||
// 获取所有用户ID
|
||||
var users []systemModel.SysUser
|
||||
global.GVA_DB.Select("id").Find(&users)
|
||||
for _, user := range users {
|
||||
targetUserIds = append(targetUserIds, user.ID)
|
||||
}
|
||||
case "user":
|
||||
targetUserIds = req.TargetIds
|
||||
case "role":
|
||||
// 根据角色获取用户ID
|
||||
if len(req.RoleIds) > 0 {
|
||||
var users []systemModel.SysUser
|
||||
global.GVA_DB.Joins("JOIN sys_user_authority ON sys_users.id = sys_user_authority.sys_user_id").
|
||||
Where("sys_user_authority.sys_authority_id IN ?", req.RoleIds).
|
||||
Select("DISTINCT sys_users.id").
|
||||
Find(&users)
|
||||
for _, user := range users {
|
||||
targetUserIds = append(targetUserIds, user.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 批量创建用户读取记录
|
||||
var userReads []model.UserRead
|
||||
for _, userId := range targetUserIds {
|
||||
userReads = append(userReads, model.UserRead{
|
||||
UserId: userId,
|
||||
NotificationId: notification.ID,
|
||||
IsRead: false,
|
||||
})
|
||||
}
|
||||
|
||||
if len(userReads) > 0 {
|
||||
if err := global.GVA_DB.CreateInBatches(userReads, 100).Error; err != nil {
|
||||
result.FailCount = len(userReads)
|
||||
result.Message = "创建用户读取记录失败: " + err.Error()
|
||||
return result, err
|
||||
}
|
||||
result.SuccessCount = len(userReads)
|
||||
}
|
||||
|
||||
result.Message = fmt.Sprintf("通知发送成功,共发送给 %d 个用户", result.SuccessCount)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetUserNotifications 获取用户通知列表
|
||||
func (s *NotificationService) GetUserNotifications(userId uint, req request.UserNotificationSearch) (list []response.UserNotificationResponse, total int64, err error) {
|
||||
limit := req.PageSize
|
||||
offset := req.PageSize * (req.Page - 1)
|
||||
|
||||
db := global.GVA_DB.Table("notice_user_reads ur").
|
||||
Select("n.id, n.title, n.content, n.type, n.priority, n.sender_id, n.publish_time, n.expire_time, n.created_at, ur.is_read, ur.read_time").
|
||||
Joins("JOIN notice_notifications n ON ur.notification_id = n.id").
|
||||
Where("ur.user_id = ? AND n.status = 'published'", userId)
|
||||
|
||||
// 构建查询条件
|
||||
if req.IsRead != nil {
|
||||
db = db.Where("ur.is_read = ?", *req.IsRead)
|
||||
}
|
||||
if req.Type != "" {
|
||||
db = db.Where("n.type = ?", req.Type)
|
||||
}
|
||||
if req.Priority != "" {
|
||||
db = db.Where("n.priority = ?", req.Priority)
|
||||
}
|
||||
if req.StartTime != nil {
|
||||
db = db.Where("n.created_at >= ?", req.StartTime)
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
db = db.Where("n.created_at <= ?", req.EndTime)
|
||||
}
|
||||
|
||||
err = db.Count(&total).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var results []struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Type string `json:"type"`
|
||||
Priority string `json:"priority"`
|
||||
SenderId uint `json:"sender_id"`
|
||||
PublishTime *time.Time `json:"publish_time"`
|
||||
ExpireTime *time.Time `json:"expire_time"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
IsRead bool `json:"is_read"`
|
||||
ReadTime *time.Time `json:"read_time"`
|
||||
}
|
||||
|
||||
err = db.Limit(limit).Offset(offset).Order("n.created_at DESC").Scan(&results).Error
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取发送者信息
|
||||
var senderIds []uint
|
||||
for _, result := range results {
|
||||
senderIds = append(senderIds, result.SenderId)
|
||||
}
|
||||
|
||||
var users []systemModel.SysUser
|
||||
userMap := make(map[uint]string)
|
||||
if len(senderIds) > 0 {
|
||||
global.GVA_DB.Where("id IN ?", senderIds).Find(&users)
|
||||
for _, user := range users {
|
||||
userMap[user.ID] = user.Username
|
||||
}
|
||||
}
|
||||
|
||||
// 构建响应数据
|
||||
for _, result := range results {
|
||||
senderName := userMap[result.SenderId]
|
||||
list = append(list, response.UserNotificationResponse{
|
||||
ID: result.ID,
|
||||
Title: result.Title,
|
||||
Content: result.Content,
|
||||
Type: result.Type,
|
||||
Priority: result.Priority,
|
||||
SenderId: result.SenderId,
|
||||
SenderName: senderName,
|
||||
IsRead: result.IsRead,
|
||||
ReadTime: result.ReadTime,
|
||||
PublishTime: result.PublishTime,
|
||||
ExpireTime: result.ExpireTime,
|
||||
CreatedAt: result.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return list, total, err
|
||||
}
|
||||
|
||||
// MarkNotificationsAsRead 标记通知为已读
|
||||
func (s *NotificationService) MarkNotificationsAsRead(userId uint, notificationIds []uint) error {
|
||||
now := time.Now()
|
||||
return global.GVA_DB.Model(&model.UserRead{}).
|
||||
Where("user_id = ? AND notification_id IN ?", userId, notificationIds).
|
||||
Updates(map[string]interface{}{
|
||||
"is_read": true,
|
||||
"read_time": &now,
|
||||
}).Error
|
||||
}
|
||||
|
||||
// GetNotificationStats 获取用户通知统计
|
||||
func (s *NotificationService) GetNotificationStats(userId uint) (response.NotificationStatsResponse, error) {
|
||||
var stats response.NotificationStatsResponse
|
||||
|
||||
// 总通知数
|
||||
err := global.GVA_DB.Table("notice_user_reads ur").
|
||||
Joins("JOIN notice_notifications n ON ur.notification_id = n.id").
|
||||
Where("ur.user_id = ? AND n.status = 'published'", userId).
|
||||
Count(&stats.TotalCount).Error
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
|
||||
// 未读通知数
|
||||
err = global.GVA_DB.Table("notice_user_reads ur").
|
||||
Joins("JOIN notice_notifications n ON ur.notification_id = n.id").
|
||||
Where("ur.user_id = ? AND ur.is_read = false AND n.status = 'published'", userId).
|
||||
Count(&stats.UnreadCount).Error
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
|
||||
stats.ReadCount = stats.TotalCount - stats.UnreadCount
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// DeleteUserNotification 删除用户通知记录
|
||||
// 如果通知只针对单个用户,则删除通知本身;否则只删除用户读取记录
|
||||
func (s *NotificationService) DeleteUserNotification(userId uint, notificationId uint) error {
|
||||
// 首先检查通知是否存在
|
||||
var notification model.Notification
|
||||
err := global.GVA_DB.First(¬ification, notificationId).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("通知不存在")
|
||||
}
|
||||
|
||||
// 检查通知的接收者数量
|
||||
var receiverCount int64
|
||||
err = global.GVA_DB.Model(&model.UserRead{}).
|
||||
Where("notification_id = ?", notificationId).
|
||||
Count(&receiverCount).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果只有一个接收者,删除通知本身
|
||||
if receiverCount == 1 {
|
||||
// 开启事务
|
||||
tx := global.GVA_DB.Begin()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
// 删除用户读取记录
|
||||
if err := tx.Where("notification_id = ? AND user_id = ?", notificationId, userId).Delete(&model.UserRead{}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除通知本身
|
||||
if err := tx.Delete(¬ification).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
} else {
|
||||
// 如果有多个接收者,只删除当前用户的读取记录
|
||||
return global.GVA_DB.Where("notification_id = ? AND user_id = ?", notificationId, userId).Delete(&model.UserRead{}).Error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/request"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/response"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type OnlineUserService struct{}
|
||||
|
||||
// AddOnlineUser 添加在线用户
|
||||
func (s *OnlineUserService) AddOnlineUser(userId uint, socketId string) error {
|
||||
// 使用事务确保操作的原子性
|
||||
return global.GVA_DB.Transaction(func(tx *gorm.DB) error {
|
||||
// 先硬删除该用户的所有在线记录(包括软删除的记录)
|
||||
if err := tx.Unscoped().Where("user_id = ?", userId).Delete(&model.OnlineUser{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建新的在线记录
|
||||
onlineUser := model.OnlineUser{
|
||||
UserId: userId,
|
||||
SocketId: socketId,
|
||||
LastActiveTime: time.Now(),
|
||||
Status: "online",
|
||||
}
|
||||
|
||||
return tx.Create(&onlineUser).Error
|
||||
})
|
||||
}
|
||||
|
||||
// RemoveOnlineUser 移除在线用户
|
||||
func (s *OnlineUserService) RemoveOnlineUser(userId uint) error {
|
||||
// 使用硬删除确保用户立即从在线列表中移除
|
||||
err := global.GVA_DB.Unscoped().Where("user_id = ?", userId).Delete(&model.OnlineUser{}).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "移除在线用户失败")
|
||||
}
|
||||
// 广播给所有在线用户
|
||||
return ServiceGroupApp.SocketService.SendToUser(userId, "forceLogout", "您已被管理员强制下线,请重新登录")
|
||||
}
|
||||
|
||||
// RemoveOnlineUserBySocketId 根据SocketId移除在线用户
|
||||
func (s *OnlineUserService) RemoveOnlineUserBySocketId(socketId string) error {
|
||||
// 使用硬删除确保用户立即从在线列表中移除
|
||||
return global.GVA_DB.Unscoped().Where("socket_id = ?", socketId).Delete(&model.OnlineUser{}).Error
|
||||
}
|
||||
|
||||
// UpdateUserActivity 更新用户活跃时间
|
||||
func (s *OnlineUserService) UpdateUserActivity(userId uint) error {
|
||||
return global.GVA_DB.Model(&model.OnlineUser{}).
|
||||
Where("user_id = ?", userId).
|
||||
Update("last_active_time", time.Now()).Error
|
||||
}
|
||||
|
||||
// UpdateUserActivityBySocketId 根据SocketId更新用户活跃时间
|
||||
func (s *OnlineUserService) UpdateUserActivityBySocketId(socketId string) error {
|
||||
return global.GVA_DB.Model(&model.OnlineUser{}).
|
||||
Where("socket_id = ?", socketId).
|
||||
Update("last_active_time", time.Now()).Error
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户列表
|
||||
func (s *OnlineUserService) GetOnlineUsers() ([]response.OnlineUserResponse, error) {
|
||||
var results []struct {
|
||||
UserId uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nick_name"`
|
||||
SocketId string `json:"socket_id"`
|
||||
LastActiveTime time.Time `json:"last_active_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
err := global.GVA_DB.Table("notice_online_users ou").
|
||||
Select("ou.user_id, u.username, u.nick_name, ou.socket_id, ou.last_active_time, ou.status").
|
||||
Joins("JOIN sys_users u ON ou.user_id = u.id").
|
||||
Where("ou.status = 'online'").
|
||||
Order("ou.last_active_time DESC").
|
||||
Scan(&results).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var onlineUsers []response.OnlineUserResponse
|
||||
for _, result := range results {
|
||||
onlineUsers = append(onlineUsers, response.OnlineUserResponse{
|
||||
UserId: result.UserId,
|
||||
Username: result.Username,
|
||||
NickName: result.NickName,
|
||||
SocketId: result.SocketId,
|
||||
LastActiveTime: result.LastActiveTime,
|
||||
Status: result.Status,
|
||||
})
|
||||
}
|
||||
|
||||
return onlineUsers, nil
|
||||
}
|
||||
|
||||
// GetOnlineUsersWithPagination 获取在线用户列表(分页)
|
||||
func (s *OnlineUserService) GetOnlineUsersWithPagination(req request.OnlineUserSearch) ([]response.OnlineUserResponse, int64, error) {
|
||||
limit := req.PageSize
|
||||
offset := req.PageSize * (req.Page - 1)
|
||||
|
||||
// 构建查询条件
|
||||
db := global.GVA_DB.Table("notice_online_users ou").
|
||||
Select("ou.user_id, u.username, u.nick_name, ou.socket_id, ou.last_active_time, ou.status").
|
||||
Joins("JOIN sys_users u ON ou.user_id = u.id").
|
||||
Where("ou.status = 'online'")
|
||||
|
||||
// 添加搜索条件
|
||||
if req.Username != "" {
|
||||
db = db.Where("u.username LIKE ?", "%"+req.Username+"%")
|
||||
}
|
||||
if req.NickName != "" {
|
||||
db = db.Where("u.nick_name LIKE ?", "%"+req.NickName+"%")
|
||||
}
|
||||
if req.RoleId != 0 {
|
||||
// 通过角色过滤用户
|
||||
db = db.Where("EXISTS (SELECT 1 FROM sys_user_authority sua WHERE sua.sys_user_id = u.id AND sua.sys_authority_authority_id = ?)", req.RoleId)
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
var total int64
|
||||
err := db.Count(&total).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取分页数据
|
||||
var results []struct {
|
||||
UserId uint `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nick_name"`
|
||||
SocketId string `json:"socket_id"`
|
||||
LastActiveTime time.Time `json:"last_active_time"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
err = db.Order("ou.last_active_time DESC").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Scan(&results).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
var onlineUsers []response.OnlineUserResponse
|
||||
for _, result := range results {
|
||||
// 计算在线时长
|
||||
onlineDuration := time.Since(result.LastActiveTime)
|
||||
onlineUsers = append(onlineUsers, response.OnlineUserResponse{
|
||||
UserId: result.UserId,
|
||||
Username: result.Username,
|
||||
NickName: result.NickName,
|
||||
SocketId: result.SocketId,
|
||||
LastActiveTime: result.LastActiveTime,
|
||||
Status: result.Status,
|
||||
OnlineDuration: int64(onlineDuration.Minutes()), // 在线时长(分钟)
|
||||
})
|
||||
}
|
||||
|
||||
return onlineUsers, total, nil
|
||||
}
|
||||
|
||||
// GetOnlineUserByUserId 根据用户ID获取在线用户信息
|
||||
func (s *OnlineUserService) GetOnlineUserByUserId(userId uint) (model.OnlineUser, error) {
|
||||
var onlineUser model.OnlineUser
|
||||
err := global.GVA_DB.Where("user_id = ? AND status = 'online'", userId).First(&onlineUser).Error
|
||||
return onlineUser, err
|
||||
}
|
||||
|
||||
// GetOnlineUserBySocketId 根据SocketId获取在线用户信息
|
||||
func (s *OnlineUserService) GetOnlineUserBySocketId(socketId string) (model.OnlineUser, error) {
|
||||
var onlineUser model.OnlineUser
|
||||
err := global.GVA_DB.Where("socket_id = ? AND status = 'online'", socketId).First(&onlineUser).Error
|
||||
return onlineUser, err
|
||||
}
|
||||
|
||||
// GetUserSocketId 获取用户的SocketId
|
||||
func (s *OnlineUserService) GetUserSocketId(userId uint) (string, error) {
|
||||
var onlineUser model.OnlineUser
|
||||
err := global.GVA_DB.Select("socket_id").Where("user_id = ? AND status = 'online'", userId).First(&onlineUser).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return onlineUser.SocketId, nil
|
||||
}
|
||||
|
||||
// GetUsersByRoleIds 根据角色ID获取在线用户列表
|
||||
func (s *OnlineUserService) GetUsersByRoleIds(roleIds []uint) ([]uint, error) {
|
||||
var userIds []uint
|
||||
|
||||
err := global.GVA_DB.Table("notice_online_users ou").
|
||||
Select("DISTINCT ou.user_id").
|
||||
Joins("JOIN sys_user_authority sua ON ou.user_id = sua.sys_user_id").
|
||||
Where("sua.sys_authority_id IN ? AND ou.status = 'online'", roleIds).
|
||||
Pluck("user_id", &userIds).Error
|
||||
|
||||
return userIds, err
|
||||
}
|
||||
|
||||
// IsUserOnline 检查用户是否在线
|
||||
func (s *OnlineUserService) IsUserOnline(userId uint) bool {
|
||||
var count int64
|
||||
global.GVA_DB.Model(&model.OnlineUser{}).Where("user_id = ? AND status = 'online'", userId).Count(&count)
|
||||
return count > 0
|
||||
}
|
||||
|
||||
// GetOnlineUserCount 获取在线用户数量
|
||||
func (s *OnlineUserService) GetOnlineUserCount() (int64, error) {
|
||||
var count int64
|
||||
err := global.GVA_DB.Model(&model.OnlineUser{}).Where("status = 'online'").Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CleanOfflineUsers 清理离线用户(超过指定时间未活跃的用户)
|
||||
func (s *OnlineUserService) CleanOfflineUsers(timeout time.Duration) error {
|
||||
cutoffTime := time.Now().Add(-timeout)
|
||||
// 使用硬删除确保用户立即从在线列表中移除
|
||||
return global.GVA_DB.Unscoped().Where("last_active_time < ?", cutoffTime).Delete(&model.OnlineUser{}).Error
|
||||
}
|
||||
|
||||
// GetOnlineUserStats 获取在线用户统计数据
|
||||
func (s *OnlineUserService) GetOnlineUserStats() (response.OnlineUserStatsResponse, error) {
|
||||
var stats response.OnlineUserStatsResponse
|
||||
|
||||
// 获取当前在线总数
|
||||
err := global.GVA_DB.Model(&model.OnlineUser{}).Where("status = 'online'").Count(&stats.TotalOnline).Error
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
|
||||
// 获取今日登录人数(基于最后活跃时间在今天的用户)
|
||||
today := time.Now().Format("2006-01-02")
|
||||
todayStart := today + " 00:00:00"
|
||||
todayEnd := today + " 23:59:59"
|
||||
|
||||
err = global.GVA_DB.Model(&model.OnlineUser{}).
|
||||
Where("last_active_time >= ? AND last_active_time <= ?", todayStart, todayEnd).
|
||||
Count(&stats.TodayLogin).Error
|
||||
if err != nil {
|
||||
return stats, err
|
||||
}
|
||||
|
||||
// 峰值在线人数(这里简化处理,可以根据实际需求存储历史峰值数据)
|
||||
// 暂时使用当前在线数作为峰值
|
||||
stats.PeakOnline = stats.TotalOnline
|
||||
|
||||
// 平均在线人数(这里简化处理,实际应该基于历史数据计算)
|
||||
// 暂时使用当前在线数的80%作为平均值
|
||||
stats.AverageOnline = int64(float64(stats.TotalOnline) * 0.8)
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
|
||||
"github.com/doquangtan/socketio/v4"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/plugin/notice/model/response"
|
||||
)
|
||||
|
||||
type SocketService struct {
|
||||
server *socketio.Socket
|
||||
connections map[string]*socketio.Socket // socketId -> socket
|
||||
userSockets map[uint]string // userId -> socketId
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// SetServer 设置Socket.IO服务器实例
|
||||
func (s *SocketService) SetServer(server *socketio.Socket) {
|
||||
s.server = server
|
||||
}
|
||||
|
||||
// AddConnection 添加连接
|
||||
func (s *SocketService) AddConnection(userId uint, socketId string, socket *socketio.Socket) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
if s.connections == nil {
|
||||
s.connections = make(map[string]*socketio.Socket)
|
||||
}
|
||||
if s.userSockets == nil {
|
||||
s.userSockets = make(map[uint]string)
|
||||
}
|
||||
|
||||
// 如果用户已有连接,先断开旧连接
|
||||
if oldSocketId, exists := s.userSockets[userId]; exists {
|
||||
if oldSocket, ok := s.connections[oldSocketId]; ok {
|
||||
oldSocket.Disconnect()
|
||||
delete(s.connections, oldSocketId)
|
||||
}
|
||||
}
|
||||
|
||||
s.connections[socketId] = socket
|
||||
s.userSockets[userId] = socketId
|
||||
|
||||
log.Printf("用户 %d 连接成功,SocketId: %s", userId, socketId)
|
||||
}
|
||||
|
||||
// RemoveConnection 移除连接
|
||||
func (s *SocketService) RemoveConnection(socketId string) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
delete(s.connections, socketId)
|
||||
|
||||
// 从用户映射中移除
|
||||
for userId, sid := range s.userSockets {
|
||||
if sid == socketId {
|
||||
delete(s.userSockets, userId)
|
||||
log.Printf("用户 %d 断开连接,SocketId: %s", userId, socketId)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveConnectionByUserId 根据用户ID移除连接
|
||||
func (s *SocketService) RemoveConnectionByUserId(userId uint) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
if socketId, exists := s.userSockets[userId]; exists {
|
||||
if socket, ok := s.connections[socketId]; ok {
|
||||
socket.Disconnect()
|
||||
delete(s.connections, socketId)
|
||||
}
|
||||
delete(s.userSockets, userId)
|
||||
log.Printf("用户 %d 连接被移除", userId)
|
||||
}
|
||||
}
|
||||
|
||||
// GetConnection 获取连接
|
||||
func (s *SocketService) GetConnection(socketId string) (*socketio.Socket, bool) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
socket, exists := s.connections[socketId]
|
||||
return socket, exists
|
||||
}
|
||||
|
||||
// GetUserConnection 根据用户ID获取连接
|
||||
func (s *SocketService) GetUserConnection(userId uint) (*socketio.Socket, bool) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
if socketId, exists := s.userSockets[userId]; exists {
|
||||
if socket, ok := s.connections[socketId]; ok {
|
||||
return socket, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetUserSocketId 获取用户的SocketId
|
||||
func (s *SocketService) GetUserSocketId(userId uint) (string, bool) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
socketId, exists := s.userSockets[userId]
|
||||
return socketId, exists
|
||||
}
|
||||
|
||||
// IsUserOnline 检查用户是否在线
|
||||
func (s *SocketService) IsUserOnline(userId uint) bool {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
_, exists := s.userSockets[userId]
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetOnlineUsers 获取在线用户列表
|
||||
func (s *SocketService) GetOnlineUsers() []uint {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
users := make([]uint, 0, len(s.userSockets))
|
||||
for userId := range s.userSockets {
|
||||
users = append(users, userId)
|
||||
}
|
||||
|
||||
return users
|
||||
}
|
||||
|
||||
// GetOnlineUserCount 获取在线用户数量
|
||||
func (s *SocketService) GetOnlineUserCount() int {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
return len(s.userSockets)
|
||||
}
|
||||
|
||||
// SendToUser 向指定用户发送消息
|
||||
func (s *SocketService) SendToUser(userId uint, event string, data interface{}) error {
|
||||
log.Printf("尝试向用户 %d 发送消息,事件: %s", userId, event)
|
||||
socket, exists := s.GetUserConnection(userId)
|
||||
if !exists {
|
||||
log.Printf("用户 %d 不在线,无法发送消息", userId)
|
||||
return nil // 用户不在线,不发送
|
||||
}
|
||||
|
||||
log.Printf("用户 %d 在线,发送消息,事件: %s", userId, event)
|
||||
socket.Emit(event, data)
|
||||
log.Printf("消息已发送给用户 %d,事件: %s", userId, event)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendToUsers 向多个用户发送消息
|
||||
func (s *SocketService) SendToUsers(userIds []uint, event string, data interface{}) {
|
||||
for _, userId := range userIds {
|
||||
if err := s.SendToUser(userId, event, data); err != nil {
|
||||
log.Printf("向用户 %d 发送消息失败: %v", userId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastToAll 向所有在线用户广播消息
|
||||
func (s *SocketService) BroadcastToAll(event string, data interface{}) {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
log.Printf("开始广播消息,事件: %s,在线用户数: %d", event, len(s.userSockets))
|
||||
log.Printf("当前在线用户列表: %v", s.userSockets)
|
||||
|
||||
for userId := range s.userSockets {
|
||||
log.Printf("向用户 %d 发送消息,事件: %s", userId, event)
|
||||
if err := s.SendToUser(userId, event, data); err != nil {
|
||||
log.Printf("向用户 %d 广播消息失败: %v", userId, err)
|
||||
} else {
|
||||
log.Printf("向用户 %d 广播消息成功", userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendNotificationToUser 向用户发送通知
|
||||
func (s *SocketService) SendNotificationToUser(userId uint, notification *model.Notification) error {
|
||||
notificationData := response.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
Title: notification.Title,
|
||||
Content: notification.Content,
|
||||
Type: notification.Type,
|
||||
Priority: notification.Priority,
|
||||
SenderId: notification.SenderId,
|
||||
TargetType: notification.TargetType,
|
||||
Status: notification.Status,
|
||||
PublishTime: notification.PublishTime,
|
||||
ExpireTime: notification.ExpireTime,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
}
|
||||
|
||||
return s.SendToUser(userId, "new_notification", notificationData)
|
||||
}
|
||||
|
||||
// SendNotificationToUsers 向多个用户发送通知
|
||||
func (s *SocketService) SendNotificationToUsers(userIds []uint, notification *model.Notification) {
|
||||
notificationData := response.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
Title: notification.Title,
|
||||
Content: notification.Content,
|
||||
Type: notification.Type,
|
||||
Priority: notification.Priority,
|
||||
SenderId: notification.SenderId,
|
||||
TargetType: notification.TargetType,
|
||||
Status: notification.Status,
|
||||
PublishTime: notification.PublishTime,
|
||||
ExpireTime: notification.ExpireTime,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
}
|
||||
|
||||
for _, userId := range userIds {
|
||||
if err := s.SendToUser(userId, "new_notification", notificationData); err != nil {
|
||||
log.Printf("向用户 %d 发送通知失败: %v", userId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BroadcastNotification 广播通知给所有在线用户
|
||||
func (s *SocketService) BroadcastNotification(notification *model.Notification) {
|
||||
notificationData := response.NotificationResponse{
|
||||
ID: notification.ID,
|
||||
Title: notification.Title,
|
||||
Content: notification.Content,
|
||||
Type: notification.Type,
|
||||
Priority: notification.Priority,
|
||||
SenderId: notification.SenderId,
|
||||
TargetType: notification.TargetType,
|
||||
Status: notification.Status,
|
||||
PublishTime: notification.PublishTime,
|
||||
ExpireTime: notification.ExpireTime,
|
||||
CreatedAt: notification.CreatedAt,
|
||||
UpdatedAt: notification.UpdatedAt,
|
||||
}
|
||||
|
||||
s.BroadcastToAll("new_notification", notificationData)
|
||||
}
|
||||
|
||||
// SendReadStatusUpdate 发送已读状态更新
|
||||
func (s *SocketService) SendReadStatusUpdate(userId uint, notificationId uint) error {
|
||||
data := map[string]interface{}{
|
||||
"notification_id": notificationId,
|
||||
"read_status": true,
|
||||
}
|
||||
|
||||
return s.SendToUser(userId, "read_status_update", data)
|
||||
}
|
||||
|
||||
// GetConnectionInfo 获取连接信息
|
||||
func (s *SocketService) GetConnectionInfo() map[string]interface{} {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
|
||||
return map[string]interface{}{
|
||||
"total_connections": len(s.connections),
|
||||
"online_users": len(s.userSockets),
|
||||
"user_sockets": s.userSockets,
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ func (t *{{.Name | title}}) Handle(ctx context.Context, request mcp.CallToolRequ
|
||||
// TODO: 实现工具逻辑
|
||||
// 参数示例:
|
||||
// {{- range .Params}}
|
||||
// {{.Name}} := request.Params["{{.Name}}"]
|
||||
// {{.Name}} := request.GetArguments()["{{.Name}}"]
|
||||
// {{- end}}
|
||||
return &mcp.CallToolResult{
|
||||
Content: []mcp.Content{
|
||||
|
||||
@@ -15,14 +15,14 @@ package request
|
||||
import (
|
||||
{{- if not .OnlyTemplate }}
|
||||
"{{.Module}}/model/common/request"
|
||||
{{ if .HasSearchTimer}}"time"{{ end }}
|
||||
{{ if or .HasSearchTimer .GvaModel }}"time"{{ end }}
|
||||
{{- end }}
|
||||
)
|
||||
|
||||
type {{.StructName}}Search struct{
|
||||
{{- if not .OnlyTemplate}}
|
||||
{{- if .GvaModel }}
|
||||
CreatedAtRange []string `json:"createdAtRange" form:"createdAtRange[]"`
|
||||
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
|
||||
{{- end }}
|
||||
{{- range .Fields}}
|
||||
{{- if ne .FieldSearchType ""}}
|
||||
|
||||
@@ -148,7 +148,7 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
|
||||
var {{.Abbreviation}}s []{{.Package}}.{{.StructName}}
|
||||
// 如果有条件搜索 下方会自动创建搜索语句
|
||||
{{- if .GvaModel }}
|
||||
if info.CreatedAtRange !=nil && len(info.CreatedAtRange) == 2 {
|
||||
if len(info.CreatedAtRange) == 2 {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
|
||||
}
|
||||
{{- end }}
|
||||
@@ -161,8 +161,8 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}InfoLis
|
||||
var OrderStr string
|
||||
orderMap := make(map[string]bool)
|
||||
{{- if .GvaModel }}
|
||||
orderMap["ID"] = true
|
||||
orderMap["CreatedAt"] = true
|
||||
orderMap["id"] = true
|
||||
orderMap["created_at"] = true
|
||||
{{- end }}
|
||||
{{- range .Fields}}
|
||||
{{- if .Sort}}
|
||||
@@ -195,7 +195,7 @@ func ({{.Abbreviation}}Service *{{.StructName}}Service)Get{{.StructName}}DataSou
|
||||
{{$key}} := make([]map[string]any, 0)
|
||||
{{ $dataDB := "" }}
|
||||
{{- if eq $value.DBName "" }}
|
||||
{{ $dataDB = $db }}
|
||||
{{ $dataDB = "global.GVA_DB" }}
|
||||
{{- else}}
|
||||
{{ $dataDB = printf "global.MustGetGlobalDBByDBName(\"%s\")" $value.DBName }}
|
||||
{{- end}}
|
||||
|
||||
@@ -110,7 +110,7 @@ getDataSourceFunc()
|
||||
|
||||
<el-date-picker
|
||||
v-model="searchInfo.createdAtRange"
|
||||
class="w-[380px]"
|
||||
class="!w-380px"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
@@ -161,7 +161,7 @@ getDataSourceFunc()
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
{{ if .GvaModel }}
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end }}width="180">
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{ if .IsTree -}} min-{{- end -}}width="180">
|
||||
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
|
||||
</el-table-column>
|
||||
{{ end }}
|
||||
@@ -232,7 +232,7 @@ getDataSourceFunc()
|
||||
{{- if .IsTree }}
|
||||
<el-descriptions-item label="父节点">
|
||||
<el-tree-select
|
||||
v-model="detailFrom.parentID"
|
||||
v-model="detailForm.parentID"
|
||||
:data="[rootNode,...tableData]"
|
||||
check-strictly
|
||||
disabled
|
||||
@@ -393,8 +393,8 @@ const searchInfo = ref({})
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
const sortMap = {
|
||||
CreatedAt:"CreatedAt",
|
||||
ID:"ID",
|
||||
CreatedAt:"created_at",
|
||||
ID:"id",
|
||||
{{- range .Fields}}
|
||||
{{- if .Table}}
|
||||
{{- if and .Sort}}
|
||||
@@ -629,7 +629,7 @@ const enterDialog = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
const detailFrom = ref({})
|
||||
const detailForm = ref({})
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
@@ -646,7 +646,7 @@ const getDetails = async (row) => {
|
||||
// 打开弹窗
|
||||
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
|
||||
if (res.code === 0) {
|
||||
detailFrom.value = res.data
|
||||
detailForm.value = res.data
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
@@ -655,7 +655,7 @@ const getDetails = async (row) => {
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
detailFrom.value = {}
|
||||
detailForm.value = {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,14 +14,14 @@ package request
|
||||
{{- if not .OnlyTemplate}}
|
||||
import (
|
||||
"{{.Module}}/model/common/request"
|
||||
{{ if .HasSearchTimer}}"time"{{ end }}
|
||||
{{ if or .HasSearchTimer .GvaModel }}"time"{{ end }}
|
||||
)
|
||||
{{- end}}
|
||||
type {{.StructName}}Search struct{
|
||||
{{- if not .OnlyTemplate}}
|
||||
|
||||
{{- if .GvaModel }}
|
||||
CreatedAtRange []string `json:"createdAtRange" form:"createdAtRange[]"`
|
||||
CreatedAtRange []time.Time `json:"createdAtRange" form:"createdAtRange[]"`
|
||||
{{- end }}
|
||||
{{- range .Fields}}
|
||||
{{- if ne .FieldSearchType ""}}
|
||||
|
||||
@@ -155,7 +155,7 @@ func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info
|
||||
var {{.Abbreviation}}s []model.{{.StructName}}
|
||||
// 如果有条件搜索 下方会自动创建搜索语句
|
||||
{{- if .GvaModel }}
|
||||
if info.CreatedAtRange !=nil && len(info.CreatedAtRange) == 2 {
|
||||
if len(info.CreatedAtRange) == 2 {
|
||||
db = db.Where("created_at BETWEEN ? AND ?", info.CreatedAtRange[0], info.CreatedAtRange[1])
|
||||
}
|
||||
{{- end }}
|
||||
@@ -168,8 +168,8 @@ func (s *{{.Abbreviation}}) Get{{.StructName}}InfoList(ctx context.Context, info
|
||||
var OrderStr string
|
||||
orderMap := make(map[string]bool)
|
||||
{{- if .GvaModel }}
|
||||
orderMap["ID"] = true
|
||||
orderMap["CreatedAt"] = true
|
||||
orderMap["id"] = true
|
||||
orderMap["created_at"] = true
|
||||
{{- end }}
|
||||
{{- range .Fields}}
|
||||
{{- if .Sort}}
|
||||
|
||||
@@ -108,7 +108,7 @@ getDataSourceFunc()
|
||||
</template>
|
||||
<el-date-picker
|
||||
v-model="searchInfo.createdAtRange"
|
||||
class="w-[380px]"
|
||||
class="!w-380px"
|
||||
type="datetimerange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始时间"
|
||||
@@ -158,7 +158,7 @@ getDataSourceFunc()
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
{{ if .GvaModel }}
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{- if .IsTree }} min-{{- end }}width="180">
|
||||
<el-table-column sortable align="left" label="日期" prop="CreatedAt" {{ if .IsTree -}} min-{{- end -}}width="180">
|
||||
<template #default="scope">{{ "{{ formatDate(scope.row.CreatedAt) }}" }}</template>
|
||||
</el-table-column>
|
||||
{{ end }}
|
||||
@@ -229,7 +229,7 @@ getDataSourceFunc()
|
||||
{{- if .IsTree }}
|
||||
<el-descriptions-item label="父节点">
|
||||
<el-tree-select
|
||||
v-model="detailFrom.parentID"
|
||||
v-model="detailForm.parentID"
|
||||
:data="[rootNode,...tableData]"
|
||||
check-strictly
|
||||
disabled
|
||||
@@ -388,8 +388,8 @@ const searchInfo = ref({})
|
||||
// 排序
|
||||
const sortChange = ({ prop, order }) => {
|
||||
const sortMap = {
|
||||
CreatedAt:"CreatedAt",
|
||||
ID:"ID",
|
||||
CreatedAt:"created_at",
|
||||
ID:"id",
|
||||
{{- range .Fields}}
|
||||
{{- if .Table}}
|
||||
{{- if and .Sort}}
|
||||
@@ -624,7 +624,7 @@ const enterDialog = async () => {
|
||||
})
|
||||
}
|
||||
|
||||
const detailFrom = ref({})
|
||||
const detailForm = ref({})
|
||||
|
||||
// 查看详情控制标记
|
||||
const detailShow = ref(false)
|
||||
@@ -641,7 +641,7 @@ const getDetails = async (row) => {
|
||||
// 打开弹窗
|
||||
const res = await find{{.StructName}}({ {{.PrimaryField.FieldJson}}: row.{{.PrimaryField.FieldJson}} })
|
||||
if (res.code === 0) {
|
||||
detailFrom.value = res.data
|
||||
detailForm.value = res.data
|
||||
openDetailShow()
|
||||
}
|
||||
}
|
||||
@@ -650,7 +650,7 @@ const getDetails = async (row) => {
|
||||
// 关闭详情弹窗
|
||||
const closeDetailShow = () => {
|
||||
detailShow.value = false
|
||||
detailFrom.value = {}
|
||||
detailForm.value = {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ type RouterGroup struct {
|
||||
AuthorityBtnRouter
|
||||
SysExportTemplateRouter
|
||||
SysParamsRouter
|
||||
SysVersionRouter
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -41,4 +42,5 @@ var (
|
||||
dictionaryDetailApi = api.ApiGroupApp.SystemApiGroup.DictionaryDetailApi
|
||||
autoCodeTemplateApi = api.ApiGroupApp.SystemApiGroup.AutoCodeTemplateApi
|
||||
exportTemplateApi = api.ApiGroupApp.SystemApiGroup.SysExportTemplateApi
|
||||
sysVersionApi = api.ApiGroupApp.SystemApiGroup.SysVersionApi
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ func (s *UserRouter) InitUserRouter(Router *gin.RouterGroup) {
|
||||
userRouter.PUT("setUserInfo", baseApi.SetUserInfo) // 设置用户信息
|
||||
userRouter.PUT("setSelfInfo", baseApi.SetSelfInfo) // 设置自身信息
|
||||
userRouter.POST("setUserAuthorities", baseApi.SetUserAuthorities) // 设置用户权限组
|
||||
userRouter.POST("resetPassword", baseApi.ResetPassword) // 设置用户权限组
|
||||
userRouter.POST("resetPassword", baseApi.ResetPassword) // 重置用户密码
|
||||
userRouter.PUT("setSelfSetting", baseApi.SetSelfSetting) // 用户界面配置
|
||||
}
|
||||
{
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/middleware"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SysVersionRouter struct{}
|
||||
|
||||
// InitSysVersionRouter 初始化 版本管理 路由信息
|
||||
func (s *SysVersionRouter) InitSysVersionRouter(Router *gin.RouterGroup) {
|
||||
sysVersionRouter := Router.Group("sysVersion").Use(middleware.OperationRecord())
|
||||
sysVersionRouterWithoutRecord := Router.Group("sysVersion")
|
||||
{
|
||||
sysVersionRouter.DELETE("deleteSysVersion", sysVersionApi.DeleteSysVersion) // 删除版本管理
|
||||
sysVersionRouter.DELETE("deleteSysVersionByIds", sysVersionApi.DeleteSysVersionByIds) // 批量删除版本管理
|
||||
sysVersionRouter.POST("exportVersion", sysVersionApi.ExportVersion) // 导出版本数据
|
||||
sysVersionRouter.POST("importVersion", sysVersionApi.ImportVersion) // 导入版本数据
|
||||
}
|
||||
{
|
||||
sysVersionRouterWithoutRecord.GET("findSysVersion", sysVersionApi.FindSysVersion) // 根据ID获取版本管理
|
||||
sysVersionRouterWithoutRecord.GET("getSysVersionList", sysVersionApi.GetSysVersionList) // 获取版本管理列表
|
||||
sysVersionRouterWithoutRecord.GET("downloadVersionJson", sysVersionApi.DownloadVersionJson) // 下载版本JSON数据
|
||||
}
|
||||
}
|
||||
@@ -114,6 +114,20 @@ func (s *autoCodePackage) Delete(ctx context.Context, info common.GetById) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteByNames
|
||||
// @author: [piexlmax](https://github.com/piexlmax)
|
||||
// @author: [SliverHorn](https://github.com/SliverHorn)
|
||||
func (s *autoCodePackage) DeleteByNames(ctx context.Context, names []string) error {
|
||||
if len(names) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := global.GVA_DB.WithContext(ctx).Where("package_name IN ?", names).Delete(&model.SysAutoCodePackage{}).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "删除失败!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// All 获取所有包
|
||||
// @author: [piexlmax](https://github.com/piexlmax)
|
||||
// @author: [SliverHorn](https://github.com/SliverHorn)
|
||||
|
||||
@@ -170,7 +170,7 @@ func (s *autoCodePlugin) PubPlug(plugName string) (zipPath string, err error) {
|
||||
// (compression is not required; you could use Tar directly)
|
||||
format := archives.CompressedArchive{
|
||||
//Compression: archives.Gz{},
|
||||
Archival: archives.Zip{},
|
||||
Archival: archives.Zip{},
|
||||
}
|
||||
|
||||
// create the archive
|
||||
@@ -208,7 +208,8 @@ func (s *autoCodePlugin) InitMenu(menuInfo request.InitMenu) (err error) {
|
||||
},
|
||||
}
|
||||
|
||||
err = global.GVA_DB.Find(&menus, "id in (?)", menuInfo.Menus).Error
|
||||
// 查询菜单及其关联的参数和按钮
|
||||
err = global.GVA_DB.Preload("Parameters").Preload("MenuBtn").Find(&menus, "id in (?)", menuInfo.Menus).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ type ServiceGroup struct {
|
||||
AuthorityBtnService
|
||||
SysExportTemplateService
|
||||
SysParamsService
|
||||
SysVersionService
|
||||
AutoCodePlugin autoCodePlugin
|
||||
AutoCodePackage autoCodePackage
|
||||
AutoCodeHistory autoCodeHistory
|
||||
|
||||
@@ -326,5 +326,8 @@ func (authorityService *AuthorityService) findChildrenAuthority(authority *syste
|
||||
func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) (parentID uint, err error) {
|
||||
var authority system.SysAuthority
|
||||
err = global.GVA_DB.Where("authority_id = ?", authorityID).First(&authority).Error
|
||||
return *authority.ParentId, err
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return *authority.ParentId, nil
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ WHERE
|
||||
lower(a.table_name) = ?
|
||||
AND lower(a.OWNER) = ?
|
||||
ORDER BY
|
||||
a.COLUMN_ID;
|
||||
a.COLUMN_ID
|
||||
`
|
||||
|
||||
err = global.GVA_DBList[businessDB].Raw(sql, tableName, dbName).Scan(&entities).Error
|
||||
|
||||
@@ -2,6 +2,8 @@ package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system/request"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/global"
|
||||
"github.com/flipped-aurora/gin-vue-admin/server/model/system"
|
||||
@@ -105,8 +107,12 @@ func (dictionaryService *DictionaryService) GetSysDictionary(Type string, Id uin
|
||||
//@param: info request.SysDictionarySearch
|
||||
//@return: err error, list interface{}, total int64
|
||||
|
||||
func (dictionaryService *DictionaryService) GetSysDictionaryInfoList() (list interface{}, err error) {
|
||||
func (dictionaryService *DictionaryService) GetSysDictionaryInfoList(c *gin.Context, req request.SysDictionarySearch) (list interface{}, err error) {
|
||||
var sysDictionarys []system.SysDictionary
|
||||
err = global.GVA_DB.Find(&sysDictionarys).Error
|
||||
query := global.GVA_DB.WithContext(c)
|
||||
if req.Name != "" {
|
||||
query = query.Where("name LIKE ? OR type LIKE ?", "%"+req.Name+"%", "%"+req.Name+"%")
|
||||
}
|
||||
err = query.Find(&sysDictionarys).Error
|
||||
return sysDictionarys, err
|
||||
}
|
||||
|
||||
@@ -128,6 +128,11 @@ func (sysExportTemplateService *SysExportTemplateService) GetSysExportTemplateIn
|
||||
// ExportExcel 导出Excel
|
||||
// Author [piexlmax](https://github.com/piexlmax)
|
||||
func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID string, values url.Values) (file *bytes.Buffer, name string, err error) {
|
||||
var params = values.Get("params")
|
||||
paramsValues, err := url.ParseQuery(params)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("解析 params 参数失败: %v", err)
|
||||
}
|
||||
var template system.SysExportTemplate
|
||||
err = global.GVA_DB.Preload("Conditions").Preload("JoinTemplate").First(&template, "template_id = ?", templateID).Error
|
||||
if err != nil {
|
||||
@@ -178,7 +183,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
|
||||
filterDeleted := false
|
||||
|
||||
filterParam := values.Get("filterDeleted")
|
||||
filterParam := paramsValues.Get("filterDeleted")
|
||||
if filterParam == "true" {
|
||||
filterDeleted = true
|
||||
}
|
||||
@@ -202,7 +207,12 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
if len(template.Conditions) > 0 {
|
||||
for _, condition := range template.Conditions {
|
||||
sql := fmt.Sprintf("%s %s ?", condition.Column, condition.Operator)
|
||||
value := values.Get(condition.From)
|
||||
value := paramsValues.Get(condition.From)
|
||||
|
||||
if condition.Operator == "IN" || condition.Operator == "NOT IN" {
|
||||
sql = fmt.Sprintf("%s %s (?)", condition.Column, condition.Operator)
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
if condition.Operator == "LIKE" {
|
||||
value = "%" + value + "%"
|
||||
@@ -212,7 +222,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
}
|
||||
}
|
||||
// 通过参数传入limit
|
||||
limit := values.Get("limit")
|
||||
limit := paramsValues.Get("limit")
|
||||
if limit != "" {
|
||||
l, e := strconv.Atoi(limit)
|
||||
if e == nil {
|
||||
@@ -225,7 +235,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
}
|
||||
|
||||
// 通过参数传入offset
|
||||
offset := values.Get("offset")
|
||||
offset := paramsValues.Get("offset")
|
||||
if offset != "" {
|
||||
o, e := strconv.Atoi(offset)
|
||||
if e == nil {
|
||||
@@ -248,7 +258,7 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
}
|
||||
|
||||
// 通过参数传入order
|
||||
order := values.Get("order")
|
||||
order := paramsValues.Get("order")
|
||||
|
||||
if order == "" && template.Order != "" {
|
||||
// 如果没有order入参,这里会使用模板的默认排序
|
||||
@@ -305,7 +315,17 @@ func (sysExportTemplateService *SysExportTemplateService) ExportExcel(templateID
|
||||
}
|
||||
for i, row := range rows {
|
||||
for j, colCell := range row {
|
||||
sErr := f.SetCellValue("Sheet1", fmt.Sprintf("%s%d", getColumnName(j+1), i+1), colCell)
|
||||
cell := fmt.Sprintf("%s%d", getColumnName(j+1), i+1)
|
||||
|
||||
var sErr error
|
||||
if v, err := strconv.ParseFloat(colCell, 64); err == nil {
|
||||
sErr = f.SetCellValue("Sheet1", cell, v)
|
||||
} else if v, err := strconv.ParseInt(colCell, 10, 64); err == nil {
|
||||
sErr = f.SetCellValue("Sheet1", cell, v)
|
||||
} else {
|
||||
sErr = f.SetCellValue("Sheet1", cell, colCell)
|
||||
}
|
||||
|
||||
if sErr != nil {
|
||||
return nil, "", sErr
|
||||
}
|
||||
@@ -429,7 +449,7 @@ func (sysExportTemplateService *SysExportTemplateService) ImportExcel(templateID
|
||||
for _, row := range values {
|
||||
var item = make(map[string]interface{})
|
||||
for ii, value := range row {
|
||||
if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
|
||||
if _, ok := titleKeyMap[excelTitle[ii]]; !ok {
|
||||
continue // excel中多余的标题,在模板信息中没有对应的字段,因此key为空,必须跳过
|
||||
}
|
||||
key := titleKeyMap[excelTitle[ii]]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user