🎉 初始化项目
This commit is contained in:
773
.aone_copilot/rules/project_rules.md
Normal file
773
.aone_copilot/rules/project_rules.md
Normal file
@@ -0,0 +1,773 @@
|
|||||||
|
### 功能描述以及必要性描述
|
||||||
|
|
||||||
|
---
|
||||||
|
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` 接口。
|
||||||
|
|
||||||
|
- **插件注册**: **必须**调用 ```
|
||||||
|
func init() {
|
||||||
|
interfaces.Register(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文件组织和暴露功能,避免循环引用
|
||||||
|
|
||||||
|
### 插件默认注册功能
|
||||||
|
|
||||||
|
`plugin/register.go` 文件下用 ` _ "git.echol.cn/loser/st/server/plugin/插件"
|
||||||
|
` 的方式匿名引用用于激活插件本体的init
|
||||||
|
|
||||||
|
### 代码组织示例:
|
||||||
|
|
||||||
|
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. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||||
864
.claude/rules/project_rules.md
Normal file
864
.claude/rules/project_rules.md
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
### 功能描述以及必要性描述
|
||||||
|
|
||||||
|
---
|
||||||
|
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 的单页面应用(管理后台前端)
|
||||||
|
- **前端 (web-app/)**:基于 Vue 3 + Vite 的单页面应用(用户端前端)
|
||||||
|
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||||
|
|
||||||
|
### **重要代码组织规则**
|
||||||
|
|
||||||
|
**后端代码组织(server/)**:
|
||||||
|
- `server/api/v1/app/` - 所有用户端API接口必须放在此目录下
|
||||||
|
- `server/service/app/` - 所有用户端服务层代码必须放在此目录下
|
||||||
|
- `server/router/app/` - 所有用户端路由定义必须放在此目录下
|
||||||
|
- `server/api/v1/system/` - 管理后台API接口放在此目录下
|
||||||
|
- `server/service/system/` - 管理后台服务层代码放在此目录下
|
||||||
|
- `server/router/system/` - 管理后台路由定义放在此目录下
|
||||||
|
|
||||||
|
**前端代码组织**:
|
||||||
|
- `web/` - 管理后台前端,供管理员使用,用于系统管理和数据统计
|
||||||
|
- `web-app/` - 用户端前端,供普通用户使用,用于业务操作
|
||||||
|
|
||||||
|
**命名约定**:
|
||||||
|
- 用户端相关的所有后端代码文件必须组织在 `app` 子目录中
|
||||||
|
- 这样可以清晰地区分管理端和用户端的代码,便于维护和扩展
|
||||||
|
- 避免将用户端和管理端代码混在一起
|
||||||
|
|
||||||
|
### **后端目录结构 (server/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── api/ # API控制器层
|
||||||
|
│ └── v1/ # API版本控制
|
||||||
|
│ ├── enter.go # API组入口文件
|
||||||
|
│ ├── system/ # 系统模块API(管理后台)
|
||||||
|
│ ├── example/ # 示例模块API
|
||||||
|
│ └── app/ # 用户端API(重要:所有用户端API放这里)
|
||||||
|
│ └── enter.go # 用户端API入口
|
||||||
|
├── config/ # 配置结构体定义
|
||||||
|
├── core/ # 核心启动文件
|
||||||
|
├── docs/ # Swagger文档
|
||||||
|
├── global/ # 全局变量和模型
|
||||||
|
├── initialize/ # 初始化模块
|
||||||
|
├── middleware/ # 中间件
|
||||||
|
├── model/ # 数据模型层
|
||||||
|
│ ├── system/ # 系统模块模型
|
||||||
|
│ ├── example/ # 示例模块模型
|
||||||
|
│ └── common/ # 通用模型
|
||||||
|
├── plugin/ # 插件目录
|
||||||
|
│ ├── announcement/ # 公告插件
|
||||||
|
│ └── email/ # 邮件插件
|
||||||
|
├── router/ # 路由层
|
||||||
|
│ ├── enter.go # 路由组入口
|
||||||
|
│ ├── system/ # 系统路由(管理后台)
|
||||||
|
│ ├── example/ # 示例路由
|
||||||
|
│ └── app/ # 用户端路由(重要:所有用户端路由放这里)
|
||||||
|
│ └── enter.go # 用户端路由入口
|
||||||
|
├── service/ # 服务层
|
||||||
|
│ ├── enter.go # 服务组入口
|
||||||
|
│ ├── system/ # 系统服务(管理后台)
|
||||||
|
│ ├── example/ # 示例服务
|
||||||
|
│ └── app/ # 用户端服务(重要:所有用户端服务放这里)
|
||||||
|
│ └── enter.go # 用户端服务入口
|
||||||
|
├── source/ # 数据初始化
|
||||||
|
├── utils/ # 工具包
|
||||||
|
├── config.yaml # 配置文件
|
||||||
|
└── main.go # 程序入口
|
||||||
|
```
|
||||||
|
|
||||||
|
### **前端目录结构**
|
||||||
|
|
||||||
|
#### **管理后台前端 (web/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(管理后台)
|
||||||
|
│ │ ├── user.js # 用户相关API
|
||||||
|
│ │ ├── menu.js # 菜单相关API
|
||||||
|
│ │ └── drama/ # 业务模块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/ # 系统工具
|
||||||
|
│ │ └── drama/ # 业务页面
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **用户端前端 (web-app/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web-app/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(用户端)
|
||||||
|
│ │ └── drama/ # Drama业务API
|
||||||
|
│ ├── assets/ # 资源文件
|
||||||
|
│ ├── components/ # 组件
|
||||||
|
│ ├── views/ # 页面组件(用户端)
|
||||||
|
│ │ ├── login/ # 登录页
|
||||||
|
│ │ ├── home/ # 首页
|
||||||
|
│ │ └── drama/ # Drama业务页面
|
||||||
|
│ ├── router/ # 路由配置
|
||||||
|
│ ├── pinia/ # 状态管理
|
||||||
|
│ ├── utils/ # 工具函数
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 后端规则
|
||||||
|
|
||||||
|
**【重要】代码组织强制规则**:
|
||||||
|
|
||||||
|
1. **用户端代码组织**:
|
||||||
|
- 所有用户端相关的API代码必须放在 `server/api/v1/app/` 目录下
|
||||||
|
- 所有用户端相关的Service代码必须放在 `server/service/app/` 目录下
|
||||||
|
- 所有用户端相关的Router代码必须放在 `server/router/app/` 目录下
|
||||||
|
- 例如:Drama模块用户端API应放在 `server/api/v1/app/drama/`
|
||||||
|
|
||||||
|
2. **管理后台代码组织**:
|
||||||
|
- 管理后台相关的API代码放在 `server/api/v1/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Service代码放在 `server/service/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Router代码放在 `server/router/system/` 或其他业务模块目录
|
||||||
|
|
||||||
|
3. **严格遵守**:
|
||||||
|
- 绝不能将用户端代码和管理端代码混在一起
|
||||||
|
- 必须按照上述目录结构组织代码
|
||||||
|
- 这是项目的强制性规范,必须100%遵守
|
||||||
|
|
||||||
|
在编写任何代码之前,你必须将以下 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` 接口。
|
||||||
|
|
||||||
|
- **插件注册**: **必须**调用 ```
|
||||||
|
func init() {
|
||||||
|
interfaces.Register(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文件组织和暴露功能,避免循环引用
|
||||||
|
|
||||||
|
### 插件默认注册功能
|
||||||
|
|
||||||
|
`plugin/register.go` 文件下用 ` _ "git.echol.cn/loser/st/server/plugin/插件"
|
||||||
|
` 的方式匿名引用用于激活插件本体的init
|
||||||
|
|
||||||
|
### 代码组织示例:
|
||||||
|
|
||||||
|
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/`)**
|
||||||
|
|
||||||
|
- **职责**: 管理页面路由和权限控制
|
||||||
|
- **规范**:
|
||||||
|
- **必须**配置路由元信息
|
||||||
|
- **必须**实现权限验证
|
||||||
|
- **必须**支持动态路由
|
||||||
|
|
||||||
|
### **前端代码组织规则**
|
||||||
|
|
||||||
|
**【重要】前端代码强制规则**:
|
||||||
|
|
||||||
|
1. **用户端前端 (web-app/)**:
|
||||||
|
- 用户端所有页面放在 `web-app/src/views/` 目录下
|
||||||
|
- 用户端所有API调用放在 `web-app/src/api/` 目录下
|
||||||
|
- 用户端所有组件放在 `web-app/src/components/` 目录下
|
||||||
|
- 面向普通用户的业务功能开发
|
||||||
|
|
||||||
|
2. **管理后台前端 (web/)**:
|
||||||
|
- 管理后台页面放在 `web/src/view/` 目录下
|
||||||
|
- 管理后台API调用放在 `web/src/api/` 目录下
|
||||||
|
- 管理后台组件放在 `web/src/components/` 目录下
|
||||||
|
- 面向管理员的管理功能开发
|
||||||
|
|
||||||
|
3. **开发原则**:
|
||||||
|
- 用户端和管理端前端代码完全分离
|
||||||
|
- 不同的项目目录,不同的依赖管理
|
||||||
|
- 可以独立部署和开发
|
||||||
|
|
||||||
|
### **前端插件开发规范**
|
||||||
|
|
||||||
|
#### **插件目录结构**
|
||||||
|
|
||||||
|
```
|
||||||
|
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. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||||
864
.cursor/project_rules.md
Normal file
864
.cursor/project_rules.md
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
### 功能描述以及必要性描述
|
||||||
|
|
||||||
|
---
|
||||||
|
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 的单页面应用(管理后台前端)
|
||||||
|
- **前端 (web-app/)**:基于 Vue 3 + Vite 的单页面应用(用户端前端)
|
||||||
|
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||||
|
|
||||||
|
### **重要代码组织规则**
|
||||||
|
|
||||||
|
**后端代码组织(server/)**:
|
||||||
|
- `server/api/v1/app/` - 所有用户端API接口必须放在此目录下
|
||||||
|
- `server/service/app/` - 所有用户端服务层代码必须放在此目录下
|
||||||
|
- `server/router/app/` - 所有用户端路由定义必须放在此目录下
|
||||||
|
- `server/api/v1/system/` - 管理后台API接口放在此目录下
|
||||||
|
- `server/service/system/` - 管理后台服务层代码放在此目录下
|
||||||
|
- `server/router/system/` - 管理后台路由定义放在此目录下
|
||||||
|
|
||||||
|
**前端代码组织**:
|
||||||
|
- `web/` - 管理后台前端,供管理员使用,用于系统管理和数据统计
|
||||||
|
- `web-app/` - 用户端前端,供普通用户使用,用于业务操作
|
||||||
|
|
||||||
|
**命名约定**:
|
||||||
|
- 用户端相关的所有后端代码文件必须组织在 `app` 子目录中
|
||||||
|
- 这样可以清晰地区分管理端和用户端的代码,便于维护和扩展
|
||||||
|
- 避免将用户端和管理端代码混在一起
|
||||||
|
|
||||||
|
### **后端目录结构 (server/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── api/ # API控制器层
|
||||||
|
│ └── v1/ # API版本控制
|
||||||
|
│ ├── enter.go # API组入口文件
|
||||||
|
│ ├── system/ # 系统模块API(管理后台)
|
||||||
|
│ ├── example/ # 示例模块API
|
||||||
|
│ └── app/ # 用户端API(重要:所有用户端API放这里)
|
||||||
|
│ └── enter.go # 用户端API入口
|
||||||
|
├── config/ # 配置结构体定义
|
||||||
|
├── core/ # 核心启动文件
|
||||||
|
├── docs/ # Swagger文档
|
||||||
|
├── global/ # 全局变量和模型
|
||||||
|
├── initialize/ # 初始化模块
|
||||||
|
├── middleware/ # 中间件
|
||||||
|
├── model/ # 数据模型层
|
||||||
|
│ ├── system/ # 系统模块模型
|
||||||
|
│ ├── example/ # 示例模块模型
|
||||||
|
│ └── common/ # 通用模型
|
||||||
|
├── plugin/ # 插件目录
|
||||||
|
│ ├── announcement/ # 公告插件
|
||||||
|
│ └── email/ # 邮件插件
|
||||||
|
├── router/ # 路由层
|
||||||
|
│ ├── enter.go # 路由组入口
|
||||||
|
│ ├── system/ # 系统路由(管理后台)
|
||||||
|
│ ├── example/ # 示例路由
|
||||||
|
│ └── app/ # 用户端路由(重要:所有用户端路由放这里)
|
||||||
|
│ └── enter.go # 用户端路由入口
|
||||||
|
├── service/ # 服务层
|
||||||
|
│ ├── enter.go # 服务组入口
|
||||||
|
│ ├── system/ # 系统服务(管理后台)
|
||||||
|
│ ├── example/ # 示例服务
|
||||||
|
│ └── app/ # 用户端服务(重要:所有用户端服务放这里)
|
||||||
|
│ └── enter.go # 用户端服务入口
|
||||||
|
├── source/ # 数据初始化
|
||||||
|
├── utils/ # 工具包
|
||||||
|
├── config.yaml # 配置文件
|
||||||
|
└── main.go # 程序入口
|
||||||
|
```
|
||||||
|
|
||||||
|
### **前端目录结构**
|
||||||
|
|
||||||
|
#### **管理后台前端 (web/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(管理后台)
|
||||||
|
│ │ ├── user.js # 用户相关API
|
||||||
|
│ │ ├── menu.js # 菜单相关API
|
||||||
|
│ │ └── drama/ # 业务模块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/ # 系统工具
|
||||||
|
│ │ └── drama/ # 业务页面
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **用户端前端 (web-app/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web-app/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(用户端)
|
||||||
|
│ │ └── drama/ # Drama业务API
|
||||||
|
│ ├── assets/ # 资源文件
|
||||||
|
│ ├── components/ # 组件
|
||||||
|
│ ├── views/ # 页面组件(用户端)
|
||||||
|
│ │ ├── login/ # 登录页
|
||||||
|
│ │ ├── home/ # 首页
|
||||||
|
│ │ └── drama/ # Drama业务页面
|
||||||
|
│ ├── router/ # 路由配置
|
||||||
|
│ ├── pinia/ # 状态管理
|
||||||
|
│ ├── utils/ # 工具函数
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 后端规则
|
||||||
|
|
||||||
|
**【重要】代码组织强制规则**:
|
||||||
|
|
||||||
|
1. **用户端代码组织**:
|
||||||
|
- 所有用户端相关的API代码必须放在 `server/api/v1/app/` 目录下
|
||||||
|
- 所有用户端相关的Service代码必须放在 `server/service/app/` 目录下
|
||||||
|
- 所有用户端相关的Router代码必须放在 `server/router/app/` 目录下
|
||||||
|
- 例如:Drama模块用户端API应放在 `server/api/v1/app/drama/`
|
||||||
|
|
||||||
|
2. **管理后台代码组织**:
|
||||||
|
- 管理后台相关的API代码放在 `server/api/v1/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Service代码放在 `server/service/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Router代码放在 `server/router/system/` 或其他业务模块目录
|
||||||
|
|
||||||
|
3. **严格遵守**:
|
||||||
|
- 绝不能将用户端代码和管理端代码混在一起
|
||||||
|
- 必须按照上述目录结构组织代码
|
||||||
|
- 这是项目的强制性规范,必须100%遵守
|
||||||
|
|
||||||
|
在编写任何代码之前,你必须将以下 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` 接口。
|
||||||
|
|
||||||
|
- **插件注册**: **必须**调用 ```
|
||||||
|
func init() {
|
||||||
|
interfaces.Register(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文件组织和暴露功能,避免循环引用
|
||||||
|
|
||||||
|
### 插件默认注册功能
|
||||||
|
|
||||||
|
`plugin/register.go` 文件下用 ` _ "git.echol.cn/loser/st/server/plugin/插件"
|
||||||
|
` 的方式匿名引用用于激活插件本体的init
|
||||||
|
|
||||||
|
### 代码组织示例:
|
||||||
|
|
||||||
|
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/`)**
|
||||||
|
|
||||||
|
- **职责**: 管理页面路由和权限控制
|
||||||
|
- **规范**:
|
||||||
|
- **必须**配置路由元信息
|
||||||
|
- **必须**实现权限验证
|
||||||
|
- **必须**支持动态路由
|
||||||
|
|
||||||
|
### **前端代码组织规则**
|
||||||
|
|
||||||
|
**【重要】前端代码强制规则**:
|
||||||
|
|
||||||
|
1. **用户端前端 (web-app/)**:
|
||||||
|
- 用户端所有页面放在 `web-app/src/views/` 目录下
|
||||||
|
- 用户端所有API调用放在 `web-app/src/api/` 目录下
|
||||||
|
- 用户端所有组件放在 `web-app/src/components/` 目录下
|
||||||
|
- 面向普通用户的业务功能开发
|
||||||
|
|
||||||
|
2. **管理后台前端 (web/)**:
|
||||||
|
- 管理后台页面放在 `web/src/view/` 目录下
|
||||||
|
- 管理后台API调用放在 `web/src/api/` 目录下
|
||||||
|
- 管理后台组件放在 `web/src/components/` 目录下
|
||||||
|
- 面向管理员的管理功能开发
|
||||||
|
|
||||||
|
3. **开发原则**:
|
||||||
|
- 用户端和管理端前端代码完全分离
|
||||||
|
- 不同的项目目录,不同的依赖管理
|
||||||
|
- 可以独立部署和开发
|
||||||
|
|
||||||
|
### **前端插件开发规范**
|
||||||
|
|
||||||
|
#### **插件目录结构**
|
||||||
|
|
||||||
|
```
|
||||||
|
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. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||||
864
.cursor/rules/project_rules.md
Normal file
864
.cursor/rules/project_rules.md
Normal file
@@ -0,0 +1,864 @@
|
|||||||
|
### 功能描述以及必要性描述
|
||||||
|
|
||||||
|
---
|
||||||
|
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 的单页面应用(管理后台前端)
|
||||||
|
- **前端 (web-app/)**:基于 Vue 3 + Vite 的单页面应用(用户端前端)
|
||||||
|
- **部署 (deploy/)**:Docker、Kubernetes 等部署配置
|
||||||
|
|
||||||
|
### **重要代码组织规则**
|
||||||
|
|
||||||
|
**后端代码组织(server/)**:
|
||||||
|
- `server/api/v1/app/` - 所有用户端API接口必须放在此目录下
|
||||||
|
- `server/service/app/` - 所有用户端服务层代码必须放在此目录下
|
||||||
|
- `server/router/app/` - 所有用户端路由定义必须放在此目录下
|
||||||
|
- `server/api/v1/system/` - 管理后台API接口放在此目录下
|
||||||
|
- `server/service/system/` - 管理后台服务层代码放在此目录下
|
||||||
|
- `server/router/system/` - 管理后台路由定义放在此目录下
|
||||||
|
|
||||||
|
**前端代码组织**:
|
||||||
|
- `web/` - 管理后台前端,供管理员使用,用于系统管理和数据统计
|
||||||
|
- `web-app/` - 用户端前端,供普通用户使用,用于业务操作
|
||||||
|
|
||||||
|
**命名约定**:
|
||||||
|
- 用户端相关的所有后端代码文件必须组织在 `app` 子目录中
|
||||||
|
- 这样可以清晰地区分管理端和用户端的代码,便于维护和扩展
|
||||||
|
- 避免将用户端和管理端代码混在一起
|
||||||
|
|
||||||
|
### **后端目录结构 (server/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
server/
|
||||||
|
├── api/ # API控制器层
|
||||||
|
│ └── v1/ # API版本控制
|
||||||
|
│ ├── enter.go # API组入口文件
|
||||||
|
│ ├── system/ # 系统模块API(管理后台)
|
||||||
|
│ ├── example/ # 示例模块API
|
||||||
|
│ └── app/ # 用户端API(重要:所有用户端API放这里)
|
||||||
|
│ └── enter.go # 用户端API入口
|
||||||
|
├── config/ # 配置结构体定义
|
||||||
|
├── core/ # 核心启动文件
|
||||||
|
├── docs/ # Swagger文档
|
||||||
|
├── global/ # 全局变量和模型
|
||||||
|
├── initialize/ # 初始化模块
|
||||||
|
├── middleware/ # 中间件
|
||||||
|
├── model/ # 数据模型层
|
||||||
|
│ ├── system/ # 系统模块模型
|
||||||
|
│ ├── example/ # 示例模块模型
|
||||||
|
│ └── common/ # 通用模型
|
||||||
|
├── plugin/ # 插件目录
|
||||||
|
│ ├── announcement/ # 公告插件
|
||||||
|
│ └── email/ # 邮件插件
|
||||||
|
├── router/ # 路由层
|
||||||
|
│ ├── enter.go # 路由组入口
|
||||||
|
│ ├── system/ # 系统路由(管理后台)
|
||||||
|
│ ├── example/ # 示例路由
|
||||||
|
│ └── app/ # 用户端路由(重要:所有用户端路由放这里)
|
||||||
|
│ └── enter.go # 用户端路由入口
|
||||||
|
├── service/ # 服务层
|
||||||
|
│ ├── enter.go # 服务组入口
|
||||||
|
│ ├── system/ # 系统服务(管理后台)
|
||||||
|
│ ├── example/ # 示例服务
|
||||||
|
│ └── app/ # 用户端服务(重要:所有用户端服务放这里)
|
||||||
|
│ └── enter.go # 用户端服务入口
|
||||||
|
├── source/ # 数据初始化
|
||||||
|
├── utils/ # 工具包
|
||||||
|
├── config.yaml # 配置文件
|
||||||
|
└── main.go # 程序入口
|
||||||
|
```
|
||||||
|
|
||||||
|
### **前端目录结构**
|
||||||
|
|
||||||
|
#### **管理后台前端 (web/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(管理后台)
|
||||||
|
│ │ ├── user.js # 用户相关API
|
||||||
|
│ │ ├── menu.js # 菜单相关API
|
||||||
|
│ │ └── drama/ # 业务模块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/ # 系统工具
|
||||||
|
│ │ └── drama/ # 业务页面
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
#### **用户端前端 (web-app/)**
|
||||||
|
|
||||||
|
```
|
||||||
|
web-app/
|
||||||
|
├── public/ # 静态资源
|
||||||
|
├── src/
|
||||||
|
│ ├── api/ # API接口定义(用户端)
|
||||||
|
│ │ └── drama/ # Drama业务API
|
||||||
|
│ ├── assets/ # 资源文件
|
||||||
|
│ ├── components/ # 组件
|
||||||
|
│ ├── views/ # 页面组件(用户端)
|
||||||
|
│ │ ├── login/ # 登录页
|
||||||
|
│ │ ├── home/ # 首页
|
||||||
|
│ │ └── drama/ # Drama业务页面
|
||||||
|
│ ├── router/ # 路由配置
|
||||||
|
│ ├── pinia/ # 状态管理
|
||||||
|
│ ├── utils/ # 工具函数
|
||||||
|
│ ├── App.vue # 根组件
|
||||||
|
│ └── main.js # 程序入口
|
||||||
|
├── package.json # 依赖配置
|
||||||
|
├── vite.config.js # Vite配置
|
||||||
|
└── uno.config.js # UnoCSS配置
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 后端规则
|
||||||
|
|
||||||
|
**【重要】代码组织强制规则**:
|
||||||
|
|
||||||
|
1. **用户端代码组织**:
|
||||||
|
- 所有用户端相关的API代码必须放在 `server/api/v1/app/` 目录下
|
||||||
|
- 所有用户端相关的Service代码必须放在 `server/service/app/` 目录下
|
||||||
|
- 所有用户端相关的Router代码必须放在 `server/router/app/` 目录下
|
||||||
|
- 例如:Drama模块用户端API应放在 `server/api/v1/app/drama/`
|
||||||
|
|
||||||
|
2. **管理后台代码组织**:
|
||||||
|
- 管理后台相关的API代码放在 `server/api/v1/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Service代码放在 `server/service/system/` 或其他业务模块目录
|
||||||
|
- 管理后台相关的Router代码放在 `server/router/system/` 或其他业务模块目录
|
||||||
|
|
||||||
|
3. **严格遵守**:
|
||||||
|
- 绝不能将用户端代码和管理端代码混在一起
|
||||||
|
- 必须按照上述目录结构组织代码
|
||||||
|
- 这是项目的强制性规范,必须100%遵守
|
||||||
|
|
||||||
|
在编写任何代码之前,你必须将以下 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` 接口。
|
||||||
|
|
||||||
|
- **插件注册**: **必须**调用 ```
|
||||||
|
func init() {
|
||||||
|
interfaces.Register(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文件组织和暴露功能,避免循环引用
|
||||||
|
|
||||||
|
### 插件默认注册功能
|
||||||
|
|
||||||
|
`plugin/register.go` 文件下用 ` _ "git.echol.cn/loser/st/server/plugin/插件"
|
||||||
|
` 的方式匿名引用用于激活插件本体的init
|
||||||
|
|
||||||
|
### 代码组织示例:
|
||||||
|
|
||||||
|
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/`)**
|
||||||
|
|
||||||
|
- **职责**: 管理页面路由和权限控制
|
||||||
|
- **规范**:
|
||||||
|
- **必须**配置路由元信息
|
||||||
|
- **必须**实现权限验证
|
||||||
|
- **必须**支持动态路由
|
||||||
|
|
||||||
|
### **前端代码组织规则**
|
||||||
|
|
||||||
|
**【重要】前端代码强制规则**:
|
||||||
|
|
||||||
|
1. **用户端前端 (web-app/)**:
|
||||||
|
- 用户端所有页面放在 `web-app/src/views/` 目录下
|
||||||
|
- 用户端所有API调用放在 `web-app/src/api/` 目录下
|
||||||
|
- 用户端所有组件放在 `web-app/src/components/` 目录下
|
||||||
|
- 面向普通用户的业务功能开发
|
||||||
|
|
||||||
|
2. **管理后台前端 (web/)**:
|
||||||
|
- 管理后台页面放在 `web/src/view/` 目录下
|
||||||
|
- 管理后台API调用放在 `web/src/api/` 目录下
|
||||||
|
- 管理后台组件放在 `web/src/components/` 目录下
|
||||||
|
- 面向管理员的管理功能开发
|
||||||
|
|
||||||
|
3. **开发原则**:
|
||||||
|
- 用户端和管理端前端代码完全分离
|
||||||
|
- 不同的项目目录,不同的依赖管理
|
||||||
|
- 可以独立部署和开发
|
||||||
|
|
||||||
|
### **前端插件开发规范**
|
||||||
|
|
||||||
|
#### **插件目录结构**
|
||||||
|
|
||||||
|
```
|
||||||
|
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. **重视安全性**:实现完善的权限控制和数据验证机制
|
||||||
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*.sql linguist-language=GO
|
||||||
|
*.html linguist-language=GO
|
||||||
10
.idea/.gitignore
generated
vendored
Normal file
10
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 已忽略包含查询文件的默认文件夹
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
6
.idea/copilot.data.migration.ask2agent.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Ask2AgentMigrationStateService">
|
||||||
|
<option name="migrationStatus" value="COMPLETED" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
11
.idea/go.imports.xml
generated
Normal file
11
.idea/go.imports.xml
generated
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GoImports">
|
||||||
|
<option name="excludedPackages">
|
||||||
|
<array>
|
||||||
|
<option value="github.com/pkg/errors" />
|
||||||
|
<option value="golang.org/x/net/context" />
|
||||||
|
</array>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/st.iml" filepath="$PROJECT_DIR$/.idea/st.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
9
.idea/st.iml
generated
Normal file
9
.idea/st.iml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
75
Makefile
Normal file
75
Makefile
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
SHELL = /bin/bash
|
||||||
|
|
||||||
|
#SCRIPT_DIR = $(shell pwd)/etc/script
|
||||||
|
#请选择golang版本
|
||||||
|
BUILD_IMAGE_SERVER = golang:1.22
|
||||||
|
#请选择node版本
|
||||||
|
BUILD_IMAGE_WEB = node:20
|
||||||
|
#项目名称
|
||||||
|
PROJECT_NAME = git.echol.cn/loser/st/server
|
||||||
|
#配置文件目录
|
||||||
|
CONFIG_FILE = config.yaml
|
||||||
|
#镜像仓库命名空间
|
||||||
|
IMAGE_NAME = gva
|
||||||
|
#镜像地址
|
||||||
|
REPOSITORY = registry.cn-hangzhou.aliyuncs.com/${IMAGE_NAME}
|
||||||
|
#镜像版本
|
||||||
|
TAGS_OPT ?= latest
|
||||||
|
PLUGIN ?= email
|
||||||
|
|
||||||
|
#容器环境前后端共同打包
|
||||||
|
build: build-web build-server
|
||||||
|
docker run --name build-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-local
|
||||||
|
|
||||||
|
#容器环境打包前端
|
||||||
|
build-web:
|
||||||
|
docker run --name build-web-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_WEB} make build-web-local
|
||||||
|
|
||||||
|
#容器环境打包后端
|
||||||
|
build-server:
|
||||||
|
docker run --name build-server-local --rm -v $(shell pwd):/go/src/${PROJECT_NAME} -w /go/src/${PROJECT_NAME} ${BUILD_IMAGE_SERVER} make build-server-local
|
||||||
|
|
||||||
|
#构建web镜像
|
||||||
|
build-image-web:
|
||||||
|
@cd web/ && docker build -t ${REPOSITORY}/web:${TAGS_OPT} .
|
||||||
|
|
||||||
|
#构建server镜像
|
||||||
|
build-image-server:
|
||||||
|
@cd server/ && docker build -t ${REPOSITORY}/server:${TAGS_OPT} .
|
||||||
|
|
||||||
|
#本地环境打包前后端
|
||||||
|
build-local:
|
||||||
|
if [ -d "build" ];then rm -rf build; else echo "OK!"; fi \
|
||||||
|
&& if [ -f "/.dockerenv" ];then echo "OK!"; else make build-web-local && make build-server-local; fi \
|
||||||
|
&& mkdir build && cp -r web/dist build/ && cp server/server build/ && cp -r server/resource build/resource
|
||||||
|
|
||||||
|
#本地环境打包前端
|
||||||
|
build-web-local:
|
||||||
|
@cd web/ && if [ -d "dist" ];then rm -rf dist; else echo "OK!"; fi \
|
||||||
|
&& yarn config set registry http://mirrors.cloud.tencent.com/npm/ && yarn install && yarn build
|
||||||
|
|
||||||
|
#本地环境打包后端
|
||||||
|
build-server-local:
|
||||||
|
@cd server/ && if [ -f "server" ];then rm -rf server; else echo "OK!"; fi \
|
||||||
|
&& go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \
|
||||||
|
&& go env -w CGO_ENABLED=0 && go env && go mod tidy \
|
||||||
|
&& go build -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') -X main.Version=${TAGS_OPT}" -v
|
||||||
|
|
||||||
|
#打包前后端二合一镜像
|
||||||
|
image: build
|
||||||
|
docker build -t ${REPOSITORY}/gin-vue-admin:${TAGS_OPT} -f deploy/docker/Dockerfile .
|
||||||
|
|
||||||
|
#尝鲜版
|
||||||
|
images: build build-image-web build-image-server
|
||||||
|
docker build -t ${REPOSITORY}/all:${TAGS_OPT} -f deploy/docker/Dockerfile .
|
||||||
|
|
||||||
|
#swagger 文档生成
|
||||||
|
doc:
|
||||||
|
@cd server && swag init
|
||||||
|
|
||||||
|
#插件快捷打包: make plugin PLUGIN="这里是插件文件夹名称,默认为email"
|
||||||
|
plugin:
|
||||||
|
if [ -d ".plugin" ];then rm -rf .plugin ; else echo "OK!"; fi && mkdir -p .plugin/${PLUGIN}/{server/plugin,web/plugin} \
|
||||||
|
&& if [ -d "server/plugin/${PLUGIN}" ];then cp -r server/plugin/${PLUGIN} .plugin/${PLUGIN}/server/plugin/ ; else echo "OK!"; fi \
|
||||||
|
&& if [ -d "web/src/plugin/${PLUGIN}" ];then cp -r web/src/plugin/${PLUGIN} .plugin/${PLUGIN}/web/plugin/ ; else echo "OK!"; fi \
|
||||||
|
&& cd .plugin && zip -r ${PLUGIN}.zip ${PLUGIN} && mv ${PLUGIN}.zip ../ && cd ..
|
||||||
90
deploy/docker-compose/docker-compose.yaml
Normal file
90
deploy/docker-compose/docker-compose.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
# 声明一个名为network的networks,subnet为network的子网地址,默认网关是177.7.0.1
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipam:
|
||||||
|
driver: default
|
||||||
|
config:
|
||||||
|
- subnet: '177.7.0.0/16'
|
||||||
|
|
||||||
|
# 设置mysql,redis持久化保存
|
||||||
|
volumes:
|
||||||
|
mysql:
|
||||||
|
redis:
|
||||||
|
|
||||||
|
services:
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: ../../web
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
container_name: gva-web
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '8080:8080'
|
||||||
|
depends_on:
|
||||||
|
- server
|
||||||
|
command: [ 'nginx-debug', '-g', 'daemon off;' ]
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipv4_address: 177.7.0.11
|
||||||
|
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: ../../server
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
container_name: gva-server
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '8888:8888'
|
||||||
|
depends_on:
|
||||||
|
mysql:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
links:
|
||||||
|
- mysql
|
||||||
|
- redis
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipv4_address: 177.7.0.12
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0.21 # 如果您是 arm64 架构:如 MacOS 的 M1,请修改镜像为 image: mysql/mysql-server:8.0.21
|
||||||
|
container_name: gva-mysql
|
||||||
|
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci #设置utf8字符集
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "13306:3306" # host物理直接映射端口为13306
|
||||||
|
environment:
|
||||||
|
#MYSQL_ROOT_PASSWORD: 'Aa@6447985' # root管理员用户密码
|
||||||
|
MYSQL_DATABASE: 'qmPlus' # 初始化启动时要创建的数据库的名称
|
||||||
|
MYSQL_USER: 'gva'
|
||||||
|
MYSQL_PASSWORD: 'Aa@6447985'
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "gva", "-pAa@6447985"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
volumes:
|
||||||
|
- mysql:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipv4_address: 177.7.0.13
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6.0.6
|
||||||
|
container_name: gva-redis # 容器名
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- '16379:6379'
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "redis-cli ping | grep PONG || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
volumes:
|
||||||
|
- redis:/data
|
||||||
|
networks:
|
||||||
|
network:
|
||||||
|
ipv4_address: 177.7.0.14
|
||||||
18
deploy/docker/Dockerfile
Normal file
18
deploy/docker/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
FROM centos:7
|
||||||
|
WORKDIR /opt
|
||||||
|
ENV LANG=en_US.utf8
|
||||||
|
COPY deploy/docker/entrypoint.sh .
|
||||||
|
COPY build/ /usr/share/nginx/html/
|
||||||
|
COPY server/config.yaml /usr/share/nginx/html/config.yaml
|
||||||
|
COPY web/.docker-compose/nginx/conf.d/nginx.conf /etc/nginx/conf.d/nginx.conf
|
||||||
|
RUN set -ex \
|
||||||
|
&& echo "LANG=en_US.utf8" > /etc/locale.conf \
|
||||||
|
&& echo "net.core.somaxconn = 1024" >> /etc/sysctl.conf \
|
||||||
|
&& echo "vm.overcommit_memory = 1" >> /etc/sysctl.conf \
|
||||||
|
&& yum -y install epel-release \
|
||||||
|
&& yum -y localinstall http://mirrors.ustc.edu.cn/mysql-repo/mysql57-community-release-el7.rpm \
|
||||||
|
&& yum -y install mysql-community-server git redis nginx go npm --nogpgcheck && chmod +x ./entrypoint.sh \
|
||||||
|
&& npm install -g yarn && go env -w GO111MODULE=on && go env -w GOPROXY=https://goproxy.cn,direct \
|
||||||
|
&& echo "start" > /dev/null
|
||||||
|
EXPOSE 80
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
19
deploy/docker/entrypoint.sh
Normal file
19
deploy/docker/entrypoint.sh
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
if [ ! -d "/var/lib/mysql/gva" ]; then
|
||||||
|
mysqld --initialize-insecure --user=mysql --datadir=/var/lib/mysql
|
||||||
|
mysqld --daemonize --user=mysql
|
||||||
|
sleep 5s
|
||||||
|
mysql -uroot -e "create database gva default charset 'utf8' collate 'utf8_bin'; grant all on gva.* to 'root'@'127.0.0.1' identified by '123456'; flush privileges;"
|
||||||
|
else
|
||||||
|
mysqld --daemonize --user=mysql
|
||||||
|
fi
|
||||||
|
redis-server &
|
||||||
|
if [ "$1" = "actions" ]; then
|
||||||
|
cd /opt/gva/server && go run main.go &
|
||||||
|
cd /opt/gva/web/ && yarn serve &
|
||||||
|
else
|
||||||
|
/usr/sbin/nginx &
|
||||||
|
cd /usr/share/nginx/html/ && ./server &
|
||||||
|
fi
|
||||||
|
echo "gva ALL start!!!"
|
||||||
|
tail -f /dev/null
|
||||||
148
deploy/kubernetes/server/gva-server-configmap.yaml
Normal file
148
deploy/kubernetes/server/gva-server-configmap.yaml
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: config.yaml
|
||||||
|
annotations:
|
||||||
|
flipped-aurora/gin-vue-admin: backend
|
||||||
|
github: "https://git.echol.cn/loser/st.git"
|
||||||
|
app.kubernetes.io/version: 0.0.1
|
||||||
|
labels:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
data:
|
||||||
|
config.yaml: |
|
||||||
|
# git.echol.cn/loser/st/server Global Configuration
|
||||||
|
|
||||||
|
# jwt configuration
|
||||||
|
jwt:
|
||||||
|
signing-key: 'qmPlus'
|
||||||
|
expires-time: 604800
|
||||||
|
buffer-time: 86400
|
||||||
|
|
||||||
|
# zap logger configuration
|
||||||
|
zap:
|
||||||
|
level: 'info'
|
||||||
|
format: 'console'
|
||||||
|
prefix: '[git.echol.cn/loser/st/server]'
|
||||||
|
director: 'log'
|
||||||
|
link-name: 'latest_log'
|
||||||
|
show-line: true
|
||||||
|
encode-level: 'LowercaseColorLevelEncoder'
|
||||||
|
stacktrace-key: 'stacktrace'
|
||||||
|
log-in-console: true
|
||||||
|
|
||||||
|
# redis configuration
|
||||||
|
redis:
|
||||||
|
db: 0
|
||||||
|
addr: '127.0.0.1:6379'
|
||||||
|
password: ''
|
||||||
|
|
||||||
|
# email configuration
|
||||||
|
email:
|
||||||
|
to: 'xxx@qq.com'
|
||||||
|
port: 465
|
||||||
|
from: 'xxx@163.com'
|
||||||
|
host: 'smtp.163.com'
|
||||||
|
is-ssl: true
|
||||||
|
secret: 'xxx'
|
||||||
|
nickname: 'test'
|
||||||
|
|
||||||
|
# casbin configuration
|
||||||
|
casbin:
|
||||||
|
model-path: './resource/rbac_model.conf'
|
||||||
|
|
||||||
|
# system configuration
|
||||||
|
system:
|
||||||
|
env: 'develop' # 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-multipoint: false
|
||||||
|
|
||||||
|
# captcha configuration
|
||||||
|
captcha:
|
||||||
|
key-long: 6
|
||||||
|
img-width: 240
|
||||||
|
img-height: 80
|
||||||
|
|
||||||
|
# mysql connect configuration
|
||||||
|
# 未初始化之前请勿手动修改数据库信息!!!如果一定要手动初始化请看(https://www.git.echol.cn/loser/st/server.com/docs/first)
|
||||||
|
mysql:
|
||||||
|
path: ''
|
||||||
|
config: ''
|
||||||
|
db-name: ''
|
||||||
|
username: ''
|
||||||
|
password: ''
|
||||||
|
max-idle-conns: 10
|
||||||
|
max-open-conns: 100
|
||||||
|
log-mode: false
|
||||||
|
log-zap: ""
|
||||||
|
|
||||||
|
# local configuration
|
||||||
|
local:
|
||||||
|
path: 'uploads/file'
|
||||||
|
|
||||||
|
# autocode configuration
|
||||||
|
autocode:
|
||||||
|
transfer-restart: true
|
||||||
|
root: ""
|
||||||
|
server: /server
|
||||||
|
server-api: /api/v1/autocode
|
||||||
|
server-initialize: /initialize
|
||||||
|
server-model: /model/autocode
|
||||||
|
server-request: /model/autocode/request/
|
||||||
|
server-router: /router/autocode
|
||||||
|
server-service: /service/autocode
|
||||||
|
web: /web/src
|
||||||
|
web-api: /api
|
||||||
|
web-flow: /view
|
||||||
|
web-form: /view
|
||||||
|
web-table: /view
|
||||||
|
|
||||||
|
# qiniu configuration (请自行七牛申请对应的 公钥 私钥 bucket 和 域名地址)
|
||||||
|
qiniu:
|
||||||
|
zone: 'ZoneHuaDong'
|
||||||
|
bucket: ''
|
||||||
|
img-path: ''
|
||||||
|
use-https: false
|
||||||
|
access-key: ''
|
||||||
|
secret-key: ''
|
||||||
|
use-cdn-domains: false
|
||||||
|
|
||||||
|
|
||||||
|
# aliyun oss configuration
|
||||||
|
aliyun-oss:
|
||||||
|
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: 'xxxxxxxx'
|
||||||
|
secret-key: 'xxxxxxxx'
|
||||||
|
base-url: 'https://gin.vue.admin'
|
||||||
|
path-prefix: 'git.echol.cn/loser/st/server'
|
||||||
|
|
||||||
|
# excel configuration
|
||||||
|
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: 需要清理的表名
|
||||||
|
# compareField: 需要比较时间的字段
|
||||||
|
# interval: 时间间隔, 具体配置详看 time.ParseDuration() 中字符串表示 且不能为负数
|
||||||
|
# 2160h = 24 * 30 * 3 -> 三个月
|
||||||
|
{ tableName: "sys_operation_records" , compareField: "created_at", interval: "2160h" },
|
||||||
|
{ tableName: "jwt_blacklists" , compareField: "created_at", interval: "168h" }
|
||||||
|
#{ tableName: "log2" , compareField: "created_at", interval: "2160h" }
|
||||||
|
]
|
||||||
74
deploy/kubernetes/server/gva-server-deployment.yaml
Normal file
74
deploy/kubernetes/server/gva-server-deployment.yaml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: gva-server
|
||||||
|
annotations:
|
||||||
|
flipped-aurora/gin-vue-admin: backend
|
||||||
|
github: "https://git.echol.cn/loser/st.git"
|
||||||
|
app.kubernetes.io/version: 0.0.1
|
||||||
|
labels:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: gin-vue-admin-container
|
||||||
|
image: registry.cn-hangzhou.aliyuncs.com/gva/server:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8888
|
||||||
|
name: http
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /go/src/git.echol.cn/loser/st/server/config.docker.yaml
|
||||||
|
name: config
|
||||||
|
subPath: config.yaml
|
||||||
|
- mountPath: /etc/localtime
|
||||||
|
name: localtime
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 1000m
|
||||||
|
memory: 2000Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 200Mi
|
||||||
|
livenessProbe:
|
||||||
|
failureThreshold: 1
|
||||||
|
periodSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 8888
|
||||||
|
timeoutSeconds: 1
|
||||||
|
readinessProbe:
|
||||||
|
failureThreshold: 3
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 8888
|
||||||
|
timeoutSeconds: 1
|
||||||
|
startupProbe:
|
||||||
|
failureThreshold: 40
|
||||||
|
periodSeconds: 5
|
||||||
|
successThreshold: 1
|
||||||
|
tcpSocket:
|
||||||
|
port: 8888
|
||||||
|
timeoutSeconds: 1
|
||||||
|
#imagePullSecrets:
|
||||||
|
# - name: docker-registry
|
||||||
|
volumes:
|
||||||
|
- name: localtime
|
||||||
|
hostPath:
|
||||||
|
path: /etc/localtime
|
||||||
|
- name: config
|
||||||
|
configMap:
|
||||||
|
name: config.yaml
|
||||||
21
deploy/kubernetes/server/gva-server-service.yaml
Normal file
21
deploy/kubernetes/server/gva-server-service.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gva-server
|
||||||
|
annotations:
|
||||||
|
flipped-aurora/gin-vue-admin: backend
|
||||||
|
github: "https://git.echol.cn/loser/st.git"
|
||||||
|
app.kubernetes.io/version: 0.0.1
|
||||||
|
labels:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: gva-server
|
||||||
|
version: gva-vue3
|
||||||
|
ports:
|
||||||
|
- port: 8888
|
||||||
|
name: http
|
||||||
|
targetPort: 8888
|
||||||
|
type: ClusterIP
|
||||||
|
# type: NodePort
|
||||||
32
deploy/kubernetes/web/gva-web-configmap.yaml
Normal file
32
deploy/kubernetes/web/gva-web-configmap.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: my.conf
|
||||||
|
data:
|
||||||
|
my.conf: |
|
||||||
|
server {
|
||||||
|
listen 8080;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
#charset koi8-r;
|
||||||
|
#access_log logs/host.access.log main;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
rewrite ^/api/(.*)$ /$1 break; #重写
|
||||||
|
proxy_pass http://gva-server:8888; # 设置代理服务器的协议和地址
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/swagger/index.html {
|
||||||
|
proxy_pass http://gva-server:8888/swagger/index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
deploy/kubernetes/web/gva-web-deploymemt.yaml
Normal file
51
deploy/kubernetes/web/gva-web-deploymemt.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: gva-web
|
||||||
|
annotations:
|
||||||
|
flipped-aurora/gin-vue-admin: ui
|
||||||
|
github: "https://git.echol.cn/loser/st.git"
|
||||||
|
app.kubernetes.io/version: 0.0.1
|
||||||
|
labels:
|
||||||
|
app: gva-web
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: gva-web
|
||||||
|
version: gva-vue3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gva-web
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: gin-vue-admin-nginx-container
|
||||||
|
image: registry.cn-hangzhou.aliyuncs.com/gva/web:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8080
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 1000Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 100Mi
|
||||||
|
volumeMounts:
|
||||||
|
- mountPath: /etc/nginx/conf.d/
|
||||||
|
name: nginx-config
|
||||||
|
volumes:
|
||||||
|
- name: nginx-config
|
||||||
|
configMap:
|
||||||
|
name: my.conf
|
||||||
18
deploy/kubernetes/web/gva-web-ingress.yaml
Normal file
18
deploy/kubernetes/web/gva-web-ingress.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: gva-ingress
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/ingress.class: "nginx"
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: demo.gin-vue-admin.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: gva-web
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
21
deploy/kubernetes/web/gva-web-service.yaml
Normal file
21
deploy/kubernetes/web/gva-web-service.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: gva-web
|
||||||
|
annotations:
|
||||||
|
flipped-aurora/gin-vue-admin: ui
|
||||||
|
github: "https://git.echol.cn/loser/st.git"
|
||||||
|
app.kubernetes.io/version: 0.0.1
|
||||||
|
labels:
|
||||||
|
app: gva-web
|
||||||
|
version: gva-vue3
|
||||||
|
spec:
|
||||||
|
# type: NodePort
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
selector:
|
||||||
|
app: gva-web
|
||||||
|
version: gva-vue3
|
||||||
BIN
docs/gin-vue-admin.png
Normal file
BIN
docs/gin-vue-admin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 103 KiB |
49
gin-vue-admin.code-workspace
Normal file
49
gin-vue-admin.code-workspace
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "server",
|
||||||
|
"name": "backend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "web",
|
||||||
|
"name": "frontend"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".",
|
||||||
|
"name": "root"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"go.toolsEnvVars": {
|
||||||
|
"GOPROXY": "https://goproxy.cn,direct",
|
||||||
|
"GONOPROXY": "none;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"launch": {
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Backend",
|
||||||
|
"cwd": "${workspaceFolder:backend}",
|
||||||
|
"program": "${workspaceFolder:backend}/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"cwd": "${workspaceFolder:frontend}",
|
||||||
|
"name": "Frontend",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": ["run-script", "serve"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compounds": [
|
||||||
|
{
|
||||||
|
"name": "Both (Backend & Frontend)",
|
||||||
|
"configurations": ["Backend", "Frontend"],
|
||||||
|
"stopAll": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
31
server/Dockerfile
Normal file
31
server/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
|
WORKDIR /go/src/git.echol.cn/loser/st/server
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go env -w GO111MODULE=on \
|
||||||
|
&& go env -w GOPROXY=https://goproxy.cn,direct \
|
||||||
|
&& go env -w CGO_ENABLED=0 \
|
||||||
|
&& go env \
|
||||||
|
&& go mod tidy \
|
||||||
|
&& go build -o server .
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
LABEL MAINTAINER="SliverHorn@sliver_horn@qq.com"
|
||||||
|
# 设置时区
|
||||||
|
ENV TZ=Asia/Shanghai
|
||||||
|
RUN apk update && apk add --no-cache tzdata openntpd \
|
||||||
|
&& ln -sf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||||
|
|
||||||
|
WORKDIR /go/src/git.echol.cn/loser/st/server
|
||||||
|
|
||||||
|
COPY --from=0 /go/src/git.echol.cn/loser/st/server/server ./
|
||||||
|
COPY --from=0 /go/src/git.echol.cn/loser/st/server/resource ./resource/
|
||||||
|
COPY --from=0 /go/src/git.echol.cn/loser/st/server/config.docker.yaml ./
|
||||||
|
|
||||||
|
# 挂载目录:如果使用了sqlite数据库,容器命令示例:docker run -d -v /宿主机路径/gva.db:/go/src/git.echol.cn/loser/st/server/gva.db -p 8888:8888 --name gva-server-v1 gva-server:1.0
|
||||||
|
# VOLUME ["/go/src/git.echol.cn/loser/st/server"]
|
||||||
|
|
||||||
|
EXPOSE 8888
|
||||||
|
ENTRYPOINT ./server -c config.docker.yaml
|
||||||
54
server/README.md
Normal file
54
server/README.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
## server项目结构
|
||||||
|
|
||||||
|
```shell
|
||||||
|
├── api
|
||||||
|
│ └── v1
|
||||||
|
├── config
|
||||||
|
├── core
|
||||||
|
├── docs
|
||||||
|
├── global
|
||||||
|
├── initialize
|
||||||
|
│ └── internal
|
||||||
|
├── middleware
|
||||||
|
├── model
|
||||||
|
│ ├── request
|
||||||
|
│ └── response
|
||||||
|
├── packfile
|
||||||
|
├── resource
|
||||||
|
│ ├── excel
|
||||||
|
│ ├── page
|
||||||
|
│ └── template
|
||||||
|
├── router
|
||||||
|
├── service
|
||||||
|
├── source
|
||||||
|
└── utils
|
||||||
|
├── timer
|
||||||
|
└── upload
|
||||||
|
```
|
||||||
|
|
||||||
|
| 文件夹 | 说明 | 描述 |
|
||||||
|
| ------------ | ----------------------- | --------------------------- |
|
||||||
|
| `api` | api层 | api层 |
|
||||||
|
| `--v1` | v1版本接口 | v1版本接口 |
|
||||||
|
| `config` | 配置包 | config.yaml对应的配置结构体 |
|
||||||
|
| `core` | 核心文件 | 核心组件(zap, viper, server)的初始化 |
|
||||||
|
| `docs` | swagger文档目录 | swagger文档目录 |
|
||||||
|
| `global` | 全局对象 | 全局对象 |
|
||||||
|
| `initialize` | 初始化 | router,redis,gorm,validator, timer的初始化 |
|
||||||
|
| `--internal` | 初始化内部函数 | gorm 的 longger 自定义,在此文件夹的函数只能由 `initialize` 层进行调用 |
|
||||||
|
| `middleware` | 中间件层 | 用于存放 `gin` 中间件代码 |
|
||||||
|
| `model` | 模型层 | 模型对应数据表 |
|
||||||
|
| `--request` | 入参结构体 | 接收前端发送到后端的数据。 |
|
||||||
|
| `--response` | 出参结构体 | 返回给前端的数据结构体 |
|
||||||
|
| `packfile` | 静态文件打包 | 静态文件打包 |
|
||||||
|
| `resource` | 静态资源文件夹 | 负责存放静态文件 |
|
||||||
|
| `--excel` | excel导入导出默认路径 | excel导入导出默认路径 |
|
||||||
|
| `--page` | 表单生成器 | 表单生成器 打包后的dist |
|
||||||
|
| `--template` | 模板 | 模板文件夹,存放的是代码生成器的模板 |
|
||||||
|
| `router` | 路由层 | 路由层 |
|
||||||
|
| `service` | service层 | 存放业务逻辑问题 |
|
||||||
|
| `source` | source层 | 存放初始化数据的函数 |
|
||||||
|
| `utils` | 工具包 | 工具函数封装 |
|
||||||
|
| `--timer` | timer | 定时器接口封装 |
|
||||||
|
| `--upload` | oss | oss接口封装 |
|
||||||
|
|
||||||
5
server/api/v1/app/enter.go
Normal file
5
server/api/v1/app/enter.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
type ApiGroup struct {
|
||||||
|
AuthApi
|
||||||
|
}
|
||||||
15
server/api/v1/enter.go
Normal file
15
server/api/v1/enter.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/api/v1/app"
|
||||||
|
"git.echol.cn/loser/st/server/api/v1/example"
|
||||||
|
"git.echol.cn/loser/st/server/api/v1/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ApiGroupApp = new(ApiGroup)
|
||||||
|
|
||||||
|
type ApiGroup struct {
|
||||||
|
SystemApiGroup system.ApiGroup
|
||||||
|
ExampleApiGroup example.ApiGroup
|
||||||
|
AppApiGroup app.ApiGroup
|
||||||
|
}
|
||||||
15
server/api/v1/example/enter.go
Normal file
15
server/api/v1/example/enter.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import "git.echol.cn/loser/st/server/service"
|
||||||
|
|
||||||
|
type ApiGroup struct {
|
||||||
|
CustomerApi
|
||||||
|
FileUploadAndDownloadApi
|
||||||
|
AttachmentCategoryApi
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
customerService = service.ServiceGroupApp.ExampleServiceGroup.CustomerService
|
||||||
|
fileUploadAndDownloadService = service.ServiceGroupApp.ExampleServiceGroup.FileUploadAndDownloadService
|
||||||
|
attachmentCategoryService = service.ServiceGroupApp.ExampleServiceGroup.AttachmentCategoryService
|
||||||
|
)
|
||||||
82
server/api/v1/example/exa_attachment_category.go
Normal file
82
server/api/v1/example/exa_attachment_category.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
common "git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/example"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AttachmentCategoryApi struct{}
|
||||||
|
|
||||||
|
// GetCategoryList
|
||||||
|
// @Tags GetCategoryList
|
||||||
|
// @Summary 媒体库分类列表
|
||||||
|
// @Security AttachmentCategory
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=example.ExaAttachmentCategory,msg=string} "媒体库分类列表"
|
||||||
|
// @Router /attachmentCategory/getCategoryList [get]
|
||||||
|
func (a *AttachmentCategoryApi) GetCategoryList(c *gin.Context) {
|
||||||
|
res, err := attachmentCategoryService.GetCategoryList()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取分类列表失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取分类列表失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(res, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddCategory
|
||||||
|
// @Tags AddCategory
|
||||||
|
// @Summary 添加媒体库分类
|
||||||
|
// @Security AttachmentCategory
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaAttachmentCategory true "媒体库分类数据"// @Success 200 {object} response.Response{msg=string} "添加媒体库分类"
|
||||||
|
// @Router /attachmentCategory/addCategory [post]
|
||||||
|
func (a *AttachmentCategoryApi) AddCategory(c *gin.Context) {
|
||||||
|
var req example.ExaAttachmentCategory
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
global.GVA_LOG.Error("参数错误!", zap.Error(err))
|
||||||
|
response.FailWithMessage("参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := attachmentCategoryService.AddCategory(&req); err != nil {
|
||||||
|
global.GVA_LOG.Error("创建/更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建/更新失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建/更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteCategory
|
||||||
|
// @Tags DeleteCategory
|
||||||
|
// @Summary 删除分类
|
||||||
|
// @Security AttachmentCategory
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body common.GetById true "分类id"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除分类"
|
||||||
|
// @Router /attachmentCategory/deleteCategory [post]
|
||||||
|
func (a *AttachmentCategoryApi) DeleteCategory(c *gin.Context) {
|
||||||
|
var req common.GetById
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
response.FailWithMessage("参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.ID == 0 {
|
||||||
|
response.FailWithMessage("参数错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := attachmentCategoryService.DeleteCategory(&req.ID); err != nil {
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
156
server/api/v1/example/exa_breakpoint_continue.go
Normal file
156
server/api/v1/example/exa_breakpoint_continue.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/model/example"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
exampleRes "git.echol.cn/loser/st/server/model/example/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BreakpointContinue
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 断点续传到服务器
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param file formData file true "an example for breakpoint resume, 断点续传示例"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "断点续传到服务器"
|
||||||
|
// @Router /fileUploadAndDownload/breakpointContinue [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) BreakpointContinue(c *gin.Context) {
|
||||||
|
fileMd5 := c.Request.FormValue("fileMd5")
|
||||||
|
fileName := c.Request.FormValue("fileName")
|
||||||
|
chunkMd5 := c.Request.FormValue("chunkMd5")
|
||||||
|
chunkNumber, _ := strconv.Atoi(c.Request.FormValue("chunkNumber"))
|
||||||
|
chunkTotal, _ := strconv.Atoi(c.Request.FormValue("chunkTotal"))
|
||||||
|
_, FileHeader, err := c.Request.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("接收文件失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f, err := FileHeader.Open()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("文件读取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("文件读取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func(f multipart.File) {
|
||||||
|
err := f.Close()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}(f)
|
||||||
|
cen, _ := io.ReadAll(f)
|
||||||
|
if !utils.CheckMd5(cen, chunkMd5) {
|
||||||
|
global.GVA_LOG.Error("检查md5失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("检查md5失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查找或创建记录失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查找或创建记录失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pathC, err := utils.BreakPointContinue(cen, fileName, chunkNumber, chunkTotal, fileMd5)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("断点续传失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("断点续传失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = fileUploadAndDownloadService.CreateFileChunk(file.ID, pathC, chunkNumber); err != nil {
|
||||||
|
global.GVA_LOG.Error("创建文件记录失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建文件记录失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("切片创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFile
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 查找文件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param file formData file true "Find the file, 查找文件"
|
||||||
|
// @Success 200 {object} response.Response{data=exampleRes.FileResponse,msg=string} "查找文件,返回包括文件详情"
|
||||||
|
// @Router /fileUploadAndDownload/findFile [get]
|
||||||
|
func (b *FileUploadAndDownloadApi) FindFile(c *gin.Context) {
|
||||||
|
fileMd5 := c.Query("fileMd5")
|
||||||
|
fileName := c.Query("fileName")
|
||||||
|
chunkTotal, _ := strconv.Atoi(c.Query("chunkTotal"))
|
||||||
|
file, err := fileUploadAndDownloadService.FindOrCreateFile(fileMd5, fileName, chunkTotal)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查找失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查找失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(exampleRes.FileResponse{File: file}, "查找成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BreakpointContinueFinish
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 创建文件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param file formData file true "上传文件完成"
|
||||||
|
// @Success 200 {object} response.Response{data=exampleRes.FilePathResponse,msg=string} "创建文件,返回包括文件路径"
|
||||||
|
// @Router /fileUploadAndDownload/findFile [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) BreakpointContinueFinish(c *gin.Context) {
|
||||||
|
fileMd5 := c.Query("fileMd5")
|
||||||
|
fileName := c.Query("fileName")
|
||||||
|
filePath, err := utils.MakeFile(fileName, fileMd5)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("文件创建失败!", zap.Error(err))
|
||||||
|
response.FailWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(exampleRes.FilePathResponse{FilePath: filePath}, "文件创建成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveChunk
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 删除切片
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param file formData file true "删除缓存切片"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除切片"
|
||||||
|
// @Router /fileUploadAndDownload/removeChunk [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) RemoveChunk(c *gin.Context) {
|
||||||
|
var file example.ExaFile
|
||||||
|
err := c.ShouldBindJSON(&file)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 路径穿越拦截
|
||||||
|
if strings.Contains(file.FilePath, "..") || strings.Contains(file.FilePath, "../") || strings.Contains(file.FilePath, "./") || strings.Contains(file.FilePath, ".\\") {
|
||||||
|
response.FailWithMessage("非法路径,禁止删除", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.RemoveChunk(file.FileMd5)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("缓存切片删除失败!", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fileUploadAndDownloadService.DeleteFileChunk(file.FileMd5, file.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error(err.Error(), zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("缓存切片删除成功", c)
|
||||||
|
}
|
||||||
176
server/api/v1/example/exa_customer.go
Normal file
176
server/api/v1/example/exa_customer.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/example"
|
||||||
|
exampleRes "git.echol.cn/loser/st/server/model/example/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CustomerApi struct{}
|
||||||
|
|
||||||
|
// CreateExaCustomer
|
||||||
|
// @Tags ExaCustomer
|
||||||
|
// @Summary 创建客户
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaCustomer true "客户用户名, 客户手机号码"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建客户"
|
||||||
|
// @Router /customer/customer [post]
|
||||||
|
func (e *CustomerApi) CreateExaCustomer(c *gin.Context) {
|
||||||
|
var customer example.ExaCustomer
|
||||||
|
err := c.ShouldBindJSON(&customer)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(customer, utils.CustomerVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customer.SysUserID = utils.GetUserID(c)
|
||||||
|
customer.SysUserAuthorityID = utils.GetUserAuthorityId(c)
|
||||||
|
err = customerService.CreateExaCustomer(customer)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteExaCustomer
|
||||||
|
// @Tags ExaCustomer
|
||||||
|
// @Summary 删除客户
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaCustomer true "客户ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除客户"
|
||||||
|
// @Router /customer/customer [delete]
|
||||||
|
func (e *CustomerApi) DeleteExaCustomer(c *gin.Context) {
|
||||||
|
var customer example.ExaCustomer
|
||||||
|
err := c.ShouldBindJSON(&customer)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(customer.GVA_MODEL, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = customerService.DeleteExaCustomer(customer)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateExaCustomer
|
||||||
|
// @Tags ExaCustomer
|
||||||
|
// @Summary 更新客户信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaCustomer true "客户ID, 客户信息"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新客户信息"
|
||||||
|
// @Router /customer/customer [put]
|
||||||
|
func (e *CustomerApi) UpdateExaCustomer(c *gin.Context) {
|
||||||
|
var customer example.ExaCustomer
|
||||||
|
err := c.ShouldBindJSON(&customer)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(customer.GVA_MODEL, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(customer, utils.CustomerVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = customerService.UpdateExaCustomer(&customer)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExaCustomer
|
||||||
|
// @Tags ExaCustomer
|
||||||
|
// @Summary 获取单一客户信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query example.ExaCustomer true "客户ID"
|
||||||
|
// @Success 200 {object} response.Response{data=exampleRes.ExaCustomerResponse,msg=string} "获取单一客户信息,返回包括客户详情"
|
||||||
|
// @Router /customer/customer [get]
|
||||||
|
func (e *CustomerApi) GetExaCustomer(c *gin.Context) {
|
||||||
|
var customer example.ExaCustomer
|
||||||
|
err := c.ShouldBindQuery(&customer)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(customer.GVA_MODEL, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := customerService.GetExaCustomer(customer.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(exampleRes.ExaCustomerResponse{Customer: data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExaCustomerList
|
||||||
|
// @Tags ExaCustomer
|
||||||
|
// @Summary 分页获取权限客户列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query request.PageInfo true "页码, 每页大小"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取权限客户列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /customer/customerList [get]
|
||||||
|
func (e *CustomerApi) GetExaCustomerList(c *gin.Context) {
|
||||||
|
var pageInfo request.PageInfo
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(pageInfo, utils.PageInfoVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
customerList, total, err := customerService.GetCustomerInfoList(utils.GetUserAuthorityId(c), pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: customerList,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
135
server/api/v1/example/exa_file_upload_download.go
Normal file
135
server/api/v1/example/exa_file_upload_download.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/example"
|
||||||
|
"git.echol.cn/loser/st/server/model/example/request"
|
||||||
|
exampleRes "git.echol.cn/loser/st/server/model/example/response"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileUploadAndDownloadApi struct{}
|
||||||
|
|
||||||
|
// UploadFile
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 上传文件示例
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param file formData file true "上传文件示例"
|
||||||
|
// @Success 200 {object} response.Response{data=exampleRes.ExaFileResponse,msg=string} "上传文件示例,返回包括文件详情"
|
||||||
|
// @Router /fileUploadAndDownload/upload [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) UploadFile(c *gin.Context) {
|
||||||
|
var file example.ExaFileUploadAndDownload
|
||||||
|
noSave := c.DefaultQuery("noSave", "0")
|
||||||
|
_, header, err := c.Request.FormFile("file")
|
||||||
|
classId, _ := strconv.Atoi(c.DefaultPostForm("classId", "0"))
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("接收文件失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("接收文件失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err = fileUploadAndDownloadService.UploadFile(header, noSave, classId) // 文件上传后拿到文件路径
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("上传文件失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("上传文件失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(exampleRes.ExaFileResponse{File: file}, "上传成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditFileName 编辑文件名或者备注
|
||||||
|
func (b *FileUploadAndDownloadApi) EditFileName(c *gin.Context) {
|
||||||
|
var file example.ExaFileUploadAndDownload
|
||||||
|
err := c.ShouldBindJSON(&file)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = fileUploadAndDownloadService.EditFileName(file)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("编辑失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("编辑失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("编辑成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFile
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 删除文件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaFileUploadAndDownload true "传入文件里面id即可"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除文件"
|
||||||
|
// @Router /fileUploadAndDownload/deleteFile [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) DeleteFile(c *gin.Context) {
|
||||||
|
var file example.ExaFileUploadAndDownload
|
||||||
|
err := c.ShouldBindJSON(&file)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := fileUploadAndDownloadService.DeleteFile(file); err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileList
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 分页文件列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.ExaAttachmentCategorySearch true "页码, 每页大小, 分类id"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页文件列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /fileUploadAndDownload/getFileList [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) GetFileList(c *gin.Context) {
|
||||||
|
var pageInfo request.ExaAttachmentCategorySearch
|
||||||
|
err := c.ShouldBindJSON(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := fileUploadAndDownloadService.GetFileRecordInfoList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportURL
|
||||||
|
// @Tags ExaFileUploadAndDownload
|
||||||
|
// @Summary 导入URL
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body example.ExaFileUploadAndDownload true "对象"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "导入URL"
|
||||||
|
// @Router /fileUploadAndDownload/importURL [post]
|
||||||
|
func (b *FileUploadAndDownloadApi) ImportURL(c *gin.Context) {
|
||||||
|
var file []example.ExaFileUploadAndDownload
|
||||||
|
err := c.ShouldBindJSON(&file)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := fileUploadAndDownloadService.ImportURL(&file); err != nil {
|
||||||
|
global.GVA_LOG.Error("导入URL失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("导入URL失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("导入URL成功", c)
|
||||||
|
}
|
||||||
115
server/api/v1/system/auto_code_history.go
Normal file
115
server/api/v1/system/auto_code_history.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
common "git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
request "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoCodeHistoryApi struct{}
|
||||||
|
|
||||||
|
// First
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 获取meta信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "请求参数"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取meta信息"
|
||||||
|
// @Router /autoCode/getMeta [post]
|
||||||
|
func (a *AutoCodeHistoryApi) First(c *gin.Context) {
|
||||||
|
var info common.GetById
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := autoCodeHistoryService.First(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"meta": data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 删除回滚记录
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "请求参数"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除回滚记录"
|
||||||
|
// @Router /autoCode/delSysHistory [post]
|
||||||
|
func (a *AutoCodeHistoryApi) Delete(c *gin.Context) {
|
||||||
|
var info common.GetById
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodeHistoryService.Delete(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollBack
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 回滚自动生成代码
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.SysAutoHistoryRollBack true "请求参数"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "回滚自动生成代码"
|
||||||
|
// @Router /autoCode/rollback [post]
|
||||||
|
func (a *AutoCodeHistoryApi) RollBack(c *gin.Context) {
|
||||||
|
var info request.SysAutoHistoryRollBack
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodeHistoryService.RollBack(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("回滚成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetList
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 查询回滚记录
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body common.PageInfo true "请求参数"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "查询回滚记录,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /autoCode/getSysHistory [post]
|
||||||
|
func (a *AutoCodeHistoryApi) GetList(c *gin.Context) {
|
||||||
|
var info common.PageInfo
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := autoCodeHistoryService.GetList(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: info.Page,
|
||||||
|
PageSize: info.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
144
server/api/v1/system/auto_code_mcp.go
Normal file
144
server/api/v1/system/auto_code_mcp.go
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/mcp/client"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/mark3labs/mcp-go/mcp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags mcp
|
||||||
|
// @Summary 自动McpTool
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /autoCode/mcp [post]
|
||||||
|
func (a *AutoCodeTemplateApi) MCP(c *gin.Context) {
|
||||||
|
var info request.AutoMcpTool
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
toolFilePath, err := autoCodeTemplateService.CreateMcp(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
global.GVA_LOG.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功,MCP Tool路径:"+toolFilePath, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags mcp
|
||||||
|
// @Summary 自动McpTool
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoMcpTool true "创建自动代码"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /autoCode/mcpList [post]
|
||||||
|
func (a *AutoCodeTemplateApi) MCPList(c *gin.Context) {
|
||||||
|
|
||||||
|
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||||
|
|
||||||
|
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||||
|
defer testClient.Close()
|
||||||
|
toolsRequest := mcp.ListToolsRequest{}
|
||||||
|
|
||||||
|
list, err := testClient.ListTools(c.Request.Context(), toolsRequest)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
global.GVA_LOG.Error(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mcpServerConfig := map[string]interface{}{
|
||||||
|
"mcpServers": map[string]interface{}{
|
||||||
|
global.GVA_CONFIG.MCP.Name: map[string]string{
|
||||||
|
"url": baseUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
response.OkWithData(gin.H{
|
||||||
|
"mcpServerConfig": mcpServerConfig,
|
||||||
|
"list": list,
|
||||||
|
}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags mcp
|
||||||
|
// @Summary 测试McpTool
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body object true "调用MCP Tool的参数"
|
||||||
|
// @Success 200 {object} response.Response "{"success":true,"data":{},"msg":"测试成功"}"
|
||||||
|
// @Router /autoCode/mcpTest [post]
|
||||||
|
func (a *AutoCodeTemplateApi) MCPTest(c *gin.Context) {
|
||||||
|
// 定义接口请求结构
|
||||||
|
var testRequest struct {
|
||||||
|
Name string `json:"name" binding:"required"` // 工具名称
|
||||||
|
Arguments map[string]interface{} `json:"arguments" binding:"required"` // 工具参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定JSON请求体
|
||||||
|
if err := c.ShouldBindJSON(&testRequest); err != nil {
|
||||||
|
response.FailWithMessage("参数解析失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建MCP客户端
|
||||||
|
baseUrl := fmt.Sprintf("http://127.0.0.1:%d%s", global.GVA_CONFIG.System.Addr, global.GVA_CONFIG.MCP.SSEPath)
|
||||||
|
testClient, err := client.NewClient(baseUrl, "testClient", "v1.0.0", global.GVA_CONFIG.MCP.Name)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("创建MCP客户端失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer testClient.Close()
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
// 初始化MCP连接
|
||||||
|
initRequest := mcp.InitializeRequest{}
|
||||||
|
initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
|
||||||
|
initRequest.Params.ClientInfo = mcp.Implementation{
|
||||||
|
Name: "testClient",
|
||||||
|
Version: "v1.0.0",
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = testClient.Initialize(ctx, initRequest)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("初始化MCP连接失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建工具调用请求
|
||||||
|
request := mcp.CallToolRequest{}
|
||||||
|
request.Params.Name = testRequest.Name
|
||||||
|
request.Params.Arguments = testRequest.Arguments
|
||||||
|
|
||||||
|
// 调用工具
|
||||||
|
result, err := testClient.CallTool(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage("工具调用失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理响应结果
|
||||||
|
if len(result.Content) == 0 {
|
||||||
|
response.FailWithMessage("工具未返回任何内容", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回结果
|
||||||
|
response.OkWithData(result.Content, c)
|
||||||
|
}
|
||||||
100
server/api/v1/system/auto_code_package.go
Normal file
100
server/api/v1/system/auto_code_package.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
common "git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoCodePackageApi struct{}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags AutoCodePackage
|
||||||
|
// @Summary 创建package
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.SysAutoCodePackageCreate true "创建package"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||||
|
// @Router /autoCode/createPackage [post]
|
||||||
|
func (a *AutoCodePackageApi) Create(c *gin.Context) {
|
||||||
|
var info request.SysAutoCodePackageCreate
|
||||||
|
_ = c.ShouldBindJSON(&info)
|
||||||
|
if err := utils.Verify(info, utils.AutoPackageVerify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.Contains(info.PackageName, "\\") || strings.Contains(info.PackageName, "/") || strings.Contains(info.PackageName, "..") {
|
||||||
|
response.FailWithMessage("包名不合法", c)
|
||||||
|
return
|
||||||
|
} // PackageName可能导致路径穿越的问题 / 和 \ 都要防止
|
||||||
|
err := autoCodePackageService.Create(c.Request.Context(), &info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 删除package
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body common.GetById true "创建package"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "删除package成功"
|
||||||
|
// @Router /autoCode/delPackage [post]
|
||||||
|
func (a *AutoCodePackageApi) Delete(c *gin.Context) {
|
||||||
|
var info common.GetById
|
||||||
|
_ = c.ShouldBindJSON(&info)
|
||||||
|
err := autoCodePackageService.Delete(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// All
|
||||||
|
// @Tags AutoCodePackage
|
||||||
|
// @Summary 获取package
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||||
|
// @Router /autoCode/getPackage [post]
|
||||||
|
func (a *AutoCodePackageApi) All(c *gin.Context) {
|
||||||
|
data, err := autoCodePackageService.All(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"pkgs": data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates
|
||||||
|
// @Tags AutoCodePackage
|
||||||
|
// @Summary 获取package
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "创建package成功"
|
||||||
|
// @Router /autoCode/getTemplates [get]
|
||||||
|
func (a *AutoCodePackageApi) Templates(c *gin.Context) {
|
||||||
|
data, err := autoCodePackageService.Templates(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(data, "获取成功", c)
|
||||||
|
}
|
||||||
218
server/api/v1/system/auto_code_plugin.go
Normal file
218
server/api/v1/system/auto_code_plugin.go
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/plugin/plugin-tool/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoCodePluginApi struct{}
|
||||||
|
|
||||||
|
// Install
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 安装插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept multipart/form-data
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param plug formData file true "this is a test file"
|
||||||
|
// @Success 200 {object} response.Response{data=[]interface{},msg=string} "安装插件成功"
|
||||||
|
// @Router /autoCode/installPlugin [post]
|
||||||
|
func (a *AutoCodePluginApi) Install(c *gin.Context) {
|
||||||
|
header, err := c.FormFile("plug")
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
web, server, err := autoCodePluginService.Install(header)
|
||||||
|
webStr := "web插件安装成功"
|
||||||
|
serverStr := "server插件安装成功"
|
||||||
|
if web == -1 {
|
||||||
|
webStr = "web端插件未成功安装,请按照文档自行解压安装,如果为纯后端插件请忽略此条提示"
|
||||||
|
}
|
||||||
|
if server == -1 {
|
||||||
|
serverStr = "server端插件未成功安装,请按照文档自行解压安装,如果为纯前端插件请忽略此条提示"
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData([]interface{}{
|
||||||
|
gin.H{
|
||||||
|
"code": web,
|
||||||
|
"msg": webStr,
|
||||||
|
},
|
||||||
|
gin.H{
|
||||||
|
"code": server,
|
||||||
|
"msg": serverStr,
|
||||||
|
}}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Packaged
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 打包插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param plugName query string true "插件名称"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||||
|
// @Router /autoCode/pubPlug [post]
|
||||||
|
func (a *AutoCodePluginApi) Packaged(c *gin.Context) {
|
||||||
|
plugName := c.Query("plugName")
|
||||||
|
zipPath, err := autoCodePluginService.PubPlug(plugName)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("打包失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("打包失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage(fmt.Sprintf("打包成功,文件路径为:%s", zipPath), c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitMenu
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 打包插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||||
|
// @Router /autoCode/initMenu [post]
|
||||||
|
func (a *AutoCodePluginApi) InitMenu(c *gin.Context) {
|
||||||
|
var menuInfo request.InitMenu
|
||||||
|
err := c.ShouldBindJSON(&menuInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodePluginService.InitMenu(menuInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建初始化Menu失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建初始化Menu失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("文件变更成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitAPI
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 打包插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||||
|
// @Router /autoCode/initAPI [post]
|
||||||
|
func (a *AutoCodePluginApi) InitAPI(c *gin.Context) {
|
||||||
|
var apiInfo request.InitApi
|
||||||
|
err := c.ShouldBindJSON(&apiInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodePluginService.InitAPI(apiInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建初始化API失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建初始化API失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("文件变更成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitDictionary
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 打包插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "打包插件成功"
|
||||||
|
// @Router /autoCode/initDictionary [post]
|
||||||
|
func (a *AutoCodePluginApi) InitDictionary(c *gin.Context) {
|
||||||
|
var dictInfo request.InitDictionary
|
||||||
|
err := c.ShouldBindJSON(&dictInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodePluginService.InitDictionary(dictInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建初始化Dictionary失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建初始化Dictionary失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("文件变更成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPluginList
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 获取插件列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=[]systemRes.PluginInfo} "获取插件列表成功"
|
||||||
|
// @Router /autoCode/getPluginList [get]
|
||||||
|
func (a *AutoCodePluginApi) GetPluginList(c *gin.Context) {
|
||||||
|
serverDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Server, "plugin")
|
||||||
|
webDir := filepath.Join(global.GVA_CONFIG.AutoCode.Root, global.GVA_CONFIG.AutoCode.Web, "plugin")
|
||||||
|
|
||||||
|
serverEntries, _ := os.ReadDir(serverDir)
|
||||||
|
webEntries, _ := os.ReadDir(webDir)
|
||||||
|
|
||||||
|
configMap := make(map[string]string)
|
||||||
|
|
||||||
|
for _, entry := range serverEntries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
configMap[entry.Name()] = "server"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range webEntries {
|
||||||
|
if entry.IsDir() {
|
||||||
|
if val, ok := configMap[entry.Name()]; ok {
|
||||||
|
if val == "server" {
|
||||||
|
configMap[entry.Name()] = "full"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configMap[entry.Name()] = "web"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var list []systemRes.PluginInfo
|
||||||
|
for k, v := range configMap {
|
||||||
|
apis, menus, dicts := utils.GetPluginData(k)
|
||||||
|
list = append(list, systemRes.PluginInfo{
|
||||||
|
PluginName: k,
|
||||||
|
PluginType: v,
|
||||||
|
Apis: apis,
|
||||||
|
Menus: menus,
|
||||||
|
Dictionaries: dicts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response.OkWithDetailed(list, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove
|
||||||
|
// @Tags AutoCodePlugin
|
||||||
|
// @Summary 删除插件
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param pluginName query string true "插件名称"
|
||||||
|
// @Param pluginType query string true "插件类型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除插件成功"
|
||||||
|
// @Router /autoCode/removePlugin [post]
|
||||||
|
func (a *AutoCodePluginApi) Remove(c *gin.Context) {
|
||||||
|
pluginName := c.Query("pluginName")
|
||||||
|
pluginType := c.Query("pluginType")
|
||||||
|
err := autoCodePluginService.Remove(pluginName, pluginType)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
121
server/api/v1/system/auto_code_template.go
Normal file
121
server/api/v1/system/auto_code_template.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoCodeTemplateApi struct{}
|
||||||
|
|
||||||
|
// Preview
|
||||||
|
// @Tags AutoCodeTemplate
|
||||||
|
// @Summary 预览创建后的代码
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoCode true "预览创建代码"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "预览创建后的代码"
|
||||||
|
// @Router /autoCode/preview [post]
|
||||||
|
func (a *AutoCodeTemplateApi) Preview(c *gin.Context) {
|
||||||
|
var info request.AutoCode
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(info, utils.AutoCodeVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = info.Pretreatment()
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
info.PackageT = utils.FirstUpper(info.Package)
|
||||||
|
autoCode, err := autoCodeTemplateService.Preview(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error(err.Error(), zap.Error(err))
|
||||||
|
response.FailWithMessage("预览失败:"+err.Error(), c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(gin.H{"autoCode": autoCode}, "预览成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create
|
||||||
|
// @Tags AutoCodeTemplate
|
||||||
|
// @Summary 自动代码模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoCode true "创建自动代码"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /autoCode/createTemp [post]
|
||||||
|
func (a *AutoCodeTemplateApi) Create(c *gin.Context) {
|
||||||
|
var info request.AutoCode
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(info, utils.AutoCodeVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = info.Pretreatment()
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = autoCodeTemplateService.Create(c.Request.Context(), info)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFunc
|
||||||
|
// @Tags AddFunc
|
||||||
|
// @Summary 增加方法
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.AutoCode true "增加方法"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /autoCode/addFunc [post]
|
||||||
|
func (a *AutoCodeTemplateApi) AddFunc(c *gin.Context) {
|
||||||
|
var info request.AutoFunc
|
||||||
|
err := c.ShouldBindJSON(&info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var tempMap map[string]string
|
||||||
|
if info.IsPreview {
|
||||||
|
info.Router = "填充router"
|
||||||
|
info.FuncName = "填充funcName"
|
||||||
|
info.Method = "填充method"
|
||||||
|
info.Description = "填充description"
|
||||||
|
tempMap, err = autoCodeTemplateService.GetApiAndServer(info)
|
||||||
|
} else {
|
||||||
|
err = autoCodeTemplateService.AddFunc(info)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("注入失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("注入失败", c)
|
||||||
|
} else {
|
||||||
|
if info.IsPreview {
|
||||||
|
response.OkWithDetailed(tempMap, "注入成功", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("注入成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
57
server/api/v1/system/enter.go
Normal file
57
server/api/v1/system/enter.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import "git.echol.cn/loser/st/server/service"
|
||||||
|
|
||||||
|
type ApiGroup struct {
|
||||||
|
DBApi
|
||||||
|
JwtApi
|
||||||
|
BaseApi
|
||||||
|
SystemApi
|
||||||
|
CasbinApi
|
||||||
|
AutoCodeApi
|
||||||
|
SystemApiApi
|
||||||
|
AuthorityApi
|
||||||
|
DictionaryApi
|
||||||
|
AuthorityMenuApi
|
||||||
|
OperationRecordApi
|
||||||
|
DictionaryDetailApi
|
||||||
|
AuthorityBtnApi
|
||||||
|
SysExportTemplateApi
|
||||||
|
AutoCodePluginApi
|
||||||
|
AutoCodePackageApi
|
||||||
|
AutoCodeHistoryApi
|
||||||
|
AutoCodeTemplateApi
|
||||||
|
SysParamsApi
|
||||||
|
SysVersionApi
|
||||||
|
SysErrorApi
|
||||||
|
LoginLogApi
|
||||||
|
ApiTokenApi
|
||||||
|
SkillsApi
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
apiService = service.ServiceGroupApp.SystemServiceGroup.ApiService
|
||||||
|
jwtService = service.ServiceGroupApp.SystemServiceGroup.JwtService
|
||||||
|
menuService = service.ServiceGroupApp.SystemServiceGroup.MenuService
|
||||||
|
userService = service.ServiceGroupApp.SystemServiceGroup.UserService
|
||||||
|
initDBService = service.ServiceGroupApp.SystemServiceGroup.InitDBService
|
||||||
|
casbinService = service.ServiceGroupApp.SystemServiceGroup.CasbinService
|
||||||
|
baseMenuService = service.ServiceGroupApp.SystemServiceGroup.BaseMenuService
|
||||||
|
authorityService = service.ServiceGroupApp.SystemServiceGroup.AuthorityService
|
||||||
|
dictionaryService = service.ServiceGroupApp.SystemServiceGroup.DictionaryService
|
||||||
|
authorityBtnService = service.ServiceGroupApp.SystemServiceGroup.AuthorityBtnService
|
||||||
|
systemConfigService = service.ServiceGroupApp.SystemServiceGroup.SystemConfigService
|
||||||
|
sysParamsService = service.ServiceGroupApp.SystemServiceGroup.SysParamsService
|
||||||
|
operationRecordService = service.ServiceGroupApp.SystemServiceGroup.OperationRecordService
|
||||||
|
dictionaryDetailService = service.ServiceGroupApp.SystemServiceGroup.DictionaryDetailService
|
||||||
|
autoCodeService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeService
|
||||||
|
autoCodePluginService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePlugin
|
||||||
|
autoCodePackageService = service.ServiceGroupApp.SystemServiceGroup.AutoCodePackage
|
||||||
|
autoCodeHistoryService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeHistory
|
||||||
|
autoCodeTemplateService = service.ServiceGroupApp.SystemServiceGroup.AutoCodeTemplate
|
||||||
|
sysVersionService = service.ServiceGroupApp.SystemServiceGroup.SysVersionService
|
||||||
|
sysErrorService = service.ServiceGroupApp.SystemServiceGroup.SysErrorService
|
||||||
|
loginLogService = service.ServiceGroupApp.SystemServiceGroup.LoginLogService
|
||||||
|
apiTokenService = service.ServiceGroupApp.SystemServiceGroup.ApiTokenService
|
||||||
|
skillsService = service.ServiceGroupApp.SystemServiceGroup.SkillsService
|
||||||
|
)
|
||||||
323
server/api/v1/system/sys_api.go
Normal file
323
server/api/v1/system/sys_api.go
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemApiApi struct{}
|
||||||
|
|
||||||
|
// CreateApi
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 创建基础api
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysApi true "api路径, api中文描述, api组, 方法"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建基础api"
|
||||||
|
// @Router /api/createApi [post]
|
||||||
|
func (s *SystemApiApi) CreateApi(c *gin.Context) {
|
||||||
|
var api system.SysApi
|
||||||
|
err := c.ShouldBindJSON(&api)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(api, utils.ApiVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.CreateApi(api)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncApi
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 同步API
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "同步API"
|
||||||
|
// @Router /api/syncApi [get]
|
||||||
|
func (s *SystemApiApi) SyncApi(c *gin.Context) {
|
||||||
|
newApis, deleteApis, ignoreApis, err := apiService.SyncApi()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("同步失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("同步失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(gin.H{
|
||||||
|
"newApis": newApis,
|
||||||
|
"deleteApis": deleteApis,
|
||||||
|
"ignoreApis": ignoreApis,
|
||||||
|
}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApiGroups
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 获取API分组
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "获取API分组"
|
||||||
|
// @Router /api/getApiGroups [get]
|
||||||
|
func (s *SystemApiApi) GetApiGroups(c *gin.Context) {
|
||||||
|
groups, apiGroupMap, err := apiService.GetApiGroups()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(gin.H{
|
||||||
|
"groups": groups,
|
||||||
|
"apiGroupMap": apiGroupMap,
|
||||||
|
}, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IgnoreApi
|
||||||
|
// @Tags IgnoreApi
|
||||||
|
// @Summary 忽略API
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "同步API"
|
||||||
|
// @Router /api/ignoreApi [post]
|
||||||
|
func (s *SystemApiApi) IgnoreApi(c *gin.Context) {
|
||||||
|
var ignoreApi system.SysIgnoreApi
|
||||||
|
err := c.ShouldBindJSON(&ignoreApi)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.IgnoreApi(ignoreApi)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("忽略失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("忽略失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnterSyncApi
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 确认同步API
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "确认同步API"
|
||||||
|
// @Router /api/enterSyncApi [post]
|
||||||
|
func (s *SystemApiApi) EnterSyncApi(c *gin.Context) {
|
||||||
|
var syncApi systemRes.SysSyncApis
|
||||||
|
err := c.ShouldBindJSON(&syncApi)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.EnterSyncApi(syncApi)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("忽略失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("忽略失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.Ok(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteApi
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 删除api
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysApi true "ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除api"
|
||||||
|
// @Router /api/deleteApi [post]
|
||||||
|
func (s *SystemApiApi) DeleteApi(c *gin.Context) {
|
||||||
|
var api system.SysApi
|
||||||
|
err := c.ShouldBindJSON(&api)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(api.GVA_MODEL, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.DeleteApi(api)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApiList
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 分页获取API列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.SearchApiParams true "分页获取API列表"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取API列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /api/getApiList [post]
|
||||||
|
func (s *SystemApiApi) GetApiList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.SearchApiParams
|
||||||
|
err := c.ShouldBindJSON(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(pageInfo.PageInfo, utils.PageInfoVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := apiService.GetAPIInfoList(pageInfo.SysApi, pageInfo.PageInfo, pageInfo.OrderKey, pageInfo.Desc)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApiById
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 根据id获取api
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "根据id获取api"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysAPIResponse} "根据id获取api,返回包括api详情"
|
||||||
|
// @Router /api/getApiById [post]
|
||||||
|
func (s *SystemApiApi) GetApiById(c *gin.Context) {
|
||||||
|
var idInfo request.GetById
|
||||||
|
err := c.ShouldBindJSON(&idInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(idInfo, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api, err := apiService.GetApiById(idInfo.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysAPIResponse{Api: api}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateApi
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 修改基础api
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysApi true "api路径, api中文描述, api组, 方法"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "修改基础api"
|
||||||
|
// @Router /api/updateApi [post]
|
||||||
|
func (s *SystemApiApi) UpdateApi(c *gin.Context) {
|
||||||
|
var api system.SysApi
|
||||||
|
err := c.ShouldBindJSON(&api)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(api, utils.ApiVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.UpdateApi(api)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("修改失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("修改成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllApis
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 获取所有的Api 不分页
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysAPIListResponse,msg=string} "获取所有的Api 不分页,返回包括api列表"
|
||||||
|
// @Router /api/getAllApis [post]
|
||||||
|
func (s *SystemApiApi) GetAllApis(c *gin.Context) {
|
||||||
|
authorityID := utils.GetUserAuthorityId(c)
|
||||||
|
apis, err := apiService.GetAllApis(authorityID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysAPIListResponse{Apis: apis}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteApisByIds
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 删除选中Api
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.IdsReq true "ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除选中Api"
|
||||||
|
// @Router /api/deleteApisByIds [delete]
|
||||||
|
func (s *SystemApiApi) DeleteApisByIds(c *gin.Context) {
|
||||||
|
var ids request.IdsReq
|
||||||
|
err := c.ShouldBindJSON(&ids)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiService.DeleteApisByIds(ids)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FreshCasbin
|
||||||
|
// @Tags SysApi
|
||||||
|
// @Summary 刷新casbin缓存
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "刷新成功"
|
||||||
|
// @Router /api/freshCasbin [get]
|
||||||
|
func (s *SystemApiApi) FreshCasbin(c *gin.Context) {
|
||||||
|
err := casbinService.FreshCasbin()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("刷新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("刷新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("刷新成功", c)
|
||||||
|
}
|
||||||
81
server/api/v1/system/sys_api_token.go
Normal file
81
server/api/v1/system/sys_api_token.go
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
sysReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiTokenApi struct{}
|
||||||
|
|
||||||
|
// CreateApiToken 签发Token
|
||||||
|
func (s *ApiTokenApi) CreateApiToken(c *gin.Context) {
|
||||||
|
var req struct {
|
||||||
|
UserID uint `json:"userId"`
|
||||||
|
AuthorityID uint `json:"authorityId"`
|
||||||
|
Days int `json:"days"` // -1为永久, 其他为天数
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := system.SysApiToken{
|
||||||
|
UserID: req.UserID,
|
||||||
|
AuthorityID: req.AuthorityID,
|
||||||
|
Remark: req.Remark,
|
||||||
|
}
|
||||||
|
|
||||||
|
jwtStr, err := apiTokenService.CreateApiToken(token, req.Days)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("签发失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("签发失败: "+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.OkWithDetailed(gin.H{"token": jwtStr}, "签发成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetApiTokenList 获取列表
|
||||||
|
func (s *ApiTokenApi) GetApiTokenList(c *gin.Context) {
|
||||||
|
var pageInfo sysReq.SysApiTokenSearch
|
||||||
|
err := c.ShouldBindJSON(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := apiTokenService.GetApiTokenList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteApiToken 作废Token
|
||||||
|
func (s *ApiTokenApi) DeleteApiToken(c *gin.Context) {
|
||||||
|
var req system.SysApiToken
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = apiTokenService.DeleteApiToken(req.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("作废失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("作废失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("作废成功", c)
|
||||||
|
}
|
||||||
202
server/api/v1/system/sys_authority.go
Normal file
202
server/api/v1/system/sys_authority.go
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthorityApi struct{}
|
||||||
|
|
||||||
|
// CreateAuthority
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 创建角色
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysAuthority true "权限id, 权限名, 父角色id"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "创建角色,返回包括系统角色详情"
|
||||||
|
// @Router /authority/createAuthority [post]
|
||||||
|
func (a *AuthorityApi) CreateAuthority(c *gin.Context) {
|
||||||
|
var authority, authBack system.SysAuthority
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err = c.ShouldBindJSON(&authority); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = utils.Verify(authority, utils.AuthorityVerify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if *authority.ParentId == 0 && global.GVA_CONFIG.System.UseStrictAuth {
|
||||||
|
authority.ParentId = utils.Pointer(utils.GetUserAuthorityId(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
if authBack, err = authorityService.CreateAuthority(authority); err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = casbinService.FreshCasbin()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建成功,权限刷新失败。", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建成功,权限刷新失败。"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyAuthority
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 拷贝角色
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body response.SysAuthorityCopyResponse true "旧角色id, 新权限id, 新权限名, 新父角色id"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "拷贝角色,返回包括系统角色详情"
|
||||||
|
// @Router /authority/copyAuthority [post]
|
||||||
|
func (a *AuthorityApi) CopyAuthority(c *gin.Context) {
|
||||||
|
var copyInfo systemRes.SysAuthorityCopyResponse
|
||||||
|
err := c.ShouldBindJSON(©Info)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(copyInfo, utils.OldAuthorityVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(copyInfo.Authority, utils.AuthorityVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||||
|
authBack, err := authorityService.CopyAuthority(adminAuthorityID, copyInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("拷贝失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("拷贝失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authBack}, "拷贝成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAuthority
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 删除角色
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysAuthority true "删除角色"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除角色"
|
||||||
|
// @Router /authority/deleteAuthority [post]
|
||||||
|
func (a *AuthorityApi) DeleteAuthority(c *gin.Context) {
|
||||||
|
var authority system.SysAuthority
|
||||||
|
var err error
|
||||||
|
if err = c.ShouldBindJSON(&authority); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = utils.Verify(authority, utils.AuthorityIdVerify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 删除角色之前需要判断是否有用户正在使用此角色
|
||||||
|
if err = authorityService.DeleteAuthority(&authority); err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = casbinService.FreshCasbin()
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAuthority
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 更新角色信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysAuthority true "权限id, 权限名, 父角色id"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysAuthorityResponse,msg=string} "更新角色信息,返回包括系统角色详情"
|
||||||
|
// @Router /authority/updateAuthority [put]
|
||||||
|
func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
|
||||||
|
var auth system.SysAuthority
|
||||||
|
err := c.ShouldBindJSON(&auth)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(auth, utils.AuthorityVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
authority, err := authorityService.UpdateAuthority(auth)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysAuthorityResponse{Authority: authority}, "更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorityList
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 分页获取角色列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取角色列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /authority/getAuthorityList [post]
|
||||||
|
func (a *AuthorityApi) GetAuthorityList(c *gin.Context) {
|
||||||
|
authorityID := utils.GetUserAuthorityId(c)
|
||||||
|
list, err := authorityService.GetAuthorityInfoList(authorityID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(list, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDataAuthority
|
||||||
|
// @Tags Authority
|
||||||
|
// @Summary 设置角色资源权限
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysAuthority true "设置角色资源权限"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "设置角色资源权限"
|
||||||
|
// @Router /authority/setDataAuthority [post]
|
||||||
|
func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
|
||||||
|
var auth system.SysAuthority
|
||||||
|
err := c.ShouldBindJSON(&auth)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(auth, utils.AuthorityIdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||||
|
err = authorityService.SetDataAuthority(adminAuthorityID, auth)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("设置成功", c)
|
||||||
|
}
|
||||||
80
server/api/v1/system/sys_authority_btn.go
Normal file
80
server/api/v1/system/sys_authority_btn.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthorityBtnApi struct{}
|
||||||
|
|
||||||
|
// GetAuthorityBtn
|
||||||
|
// @Tags AuthorityBtn
|
||||||
|
// @Summary 获取权限按钮
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.SysAuthorityBtnReq true "菜单id, 角色id, 选中的按钮id"
|
||||||
|
// @Success 200 {object} response.Response{data=response.SysAuthorityBtnRes,msg=string} "返回列表成功"
|
||||||
|
// @Router /authorityBtn/getAuthorityBtn [post]
|
||||||
|
func (a *AuthorityBtnApi) GetAuthorityBtn(c *gin.Context) {
|
||||||
|
var req request.SysAuthorityBtnReq
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res, err := authorityBtnService.GetAuthorityBtn(req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(res, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthorityBtn
|
||||||
|
// @Tags AuthorityBtn
|
||||||
|
// @Summary 设置权限按钮
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.SysAuthorityBtnReq true "菜单id, 角色id, 选中的按钮id"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "返回列表成功"
|
||||||
|
// @Router /authorityBtn/setAuthorityBtn [post]
|
||||||
|
func (a *AuthorityBtnApi) SetAuthorityBtn(c *gin.Context) {
|
||||||
|
var req request.SysAuthorityBtnReq
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = authorityBtnService.SetAuthorityBtn(req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("分配失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("分配失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("分配成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CanRemoveAuthorityBtn
|
||||||
|
// @Tags AuthorityBtn
|
||||||
|
// @Summary 设置权限按钮
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||||
|
// @Router /authorityBtn/canRemoveAuthorityBtn [post]
|
||||||
|
func (a *AuthorityBtnApi) CanRemoveAuthorityBtn(c *gin.Context) {
|
||||||
|
id := c.Query("id")
|
||||||
|
err := authorityBtnService.CanRemoveAuthorityBtn(id)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
117
server/api/v1/system/sys_auto_code.go
Normal file
117
server/api/v1/system/sys_auto_code.go
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/model/common"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AutoCodeApi struct{}
|
||||||
|
|
||||||
|
// GetDB
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 获取当前所有数据库
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前所有数据库"
|
||||||
|
// @Router /autoCode/getDB [get]
|
||||||
|
func (autoApi *AutoCodeApi) GetDB(c *gin.Context) {
|
||||||
|
businessDB := c.Query("businessDB")
|
||||||
|
dbs, err := autoCodeService.Database(businessDB).GetDB(businessDB)
|
||||||
|
var dbList []map[string]interface{}
|
||||||
|
for _, db := range global.GVA_CONFIG.DBList {
|
||||||
|
var item = make(map[string]interface{})
|
||||||
|
item["aliasName"] = db.AliasName
|
||||||
|
item["dbName"] = db.Dbname
|
||||||
|
item["disable"] = db.Disable
|
||||||
|
item["dbtype"] = db.Type
|
||||||
|
dbList = append(dbList, item)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(gin.H{"dbs": dbs, "dbList": dbList}, "获取成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTables
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 获取当前数据库所有表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前数据库所有表"
|
||||||
|
// @Router /autoCode/getTables [get]
|
||||||
|
func (autoApi *AutoCodeApi) GetTables(c *gin.Context) {
|
||||||
|
dbName := c.Query("dbName")
|
||||||
|
businessDB := c.Query("businessDB")
|
||||||
|
if dbName == "" {
|
||||||
|
dbName = *global.GVA_ACTIVE_DBNAME
|
||||||
|
if businessDB != "" {
|
||||||
|
for _, db := range global.GVA_CONFIG.DBList {
|
||||||
|
if db.AliasName == businessDB {
|
||||||
|
dbName = db.Dbname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tables, err := autoCodeService.Database(businessDB).GetTables(businessDB, dbName)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询table失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询table失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(gin.H{"tables": tables}, "获取成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetColumn
|
||||||
|
// @Tags AutoCode
|
||||||
|
// @Summary 获取当前表所有字段
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取当前表所有字段"
|
||||||
|
// @Router /autoCode/getColumn [get]
|
||||||
|
func (autoApi *AutoCodeApi) GetColumn(c *gin.Context) {
|
||||||
|
businessDB := c.Query("businessDB")
|
||||||
|
dbName := c.Query("dbName")
|
||||||
|
if dbName == "" {
|
||||||
|
dbName = *global.GVA_ACTIVE_DBNAME
|
||||||
|
if businessDB != "" {
|
||||||
|
for _, db := range global.GVA_CONFIG.DBList {
|
||||||
|
if db.AliasName == businessDB {
|
||||||
|
dbName = db.Dbname
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tableName := c.Query("tableName")
|
||||||
|
columns, err := autoCodeService.Database(businessDB).GetColumn(businessDB, tableName, dbName)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(gin.H{"columns": columns}, "获取成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (autoApi *AutoCodeApi) LLMAuto(c *gin.Context) {
|
||||||
|
var llm common.JSONMap
|
||||||
|
if err := c.ShouldBindJSON(&llm); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := autoCodeService.LLMAuto(c.Request.Context(), llm)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("大模型生成失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("大模型生成失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(data, c)
|
||||||
|
}
|
||||||
70
server/api/v1/system/sys_captcha.go
Normal file
70
server/api/v1/system/sys_captcha.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/mojocn/base64Captcha"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 当开启多服务器部署时,替换下面的配置,使用redis共享存储验证码
|
||||||
|
// var store = captcha.NewDefaultRedisStore()
|
||||||
|
var store = base64Captcha.DefaultMemStore
|
||||||
|
|
||||||
|
type BaseApi struct{}
|
||||||
|
|
||||||
|
// Captcha
|
||||||
|
// @Tags Base
|
||||||
|
// @Summary 生成验证码
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysCaptchaResponse,msg=string} "生成验证码,返回包括随机数id,base64,验证码长度,是否开启验证码"
|
||||||
|
// @Router /base/captcha [post]
|
||||||
|
func (b *BaseApi) Captcha(c *gin.Context) {
|
||||||
|
// 判断验证码是否开启
|
||||||
|
openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数
|
||||||
|
openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
|
||||||
|
key := c.ClientIP()
|
||||||
|
v, ok := global.BlackCache.Get(key)
|
||||||
|
if !ok {
|
||||||
|
global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
var oc bool
|
||||||
|
if openCaptcha == 0 || openCaptcha < interfaceToInt(v) {
|
||||||
|
oc = true
|
||||||
|
}
|
||||||
|
// 字符,公式,验证码配置
|
||||||
|
// 生成默认数字的driver
|
||||||
|
driver := base64Captcha.NewDriverDigit(global.GVA_CONFIG.Captcha.ImgHeight, global.GVA_CONFIG.Captcha.ImgWidth, global.GVA_CONFIG.Captcha.KeyLong, 0.7, 80)
|
||||||
|
// cp := base64Captcha.NewCaptcha(driver, store.UseWithCtx(c)) // v8下使用redis
|
||||||
|
cp := base64Captcha.NewCaptcha(driver, store)
|
||||||
|
id, b64s, _, err := cp.Generate()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("验证码获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("验证码获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysCaptchaResponse{
|
||||||
|
CaptchaId: id,
|
||||||
|
PicPath: b64s,
|
||||||
|
CaptchaLength: global.GVA_CONFIG.Captcha.KeyLong,
|
||||||
|
OpenCaptcha: oc,
|
||||||
|
}, "验证码获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 类型转换
|
||||||
|
func interfaceToInt(v interface{}) (i int) {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case int:
|
||||||
|
i = v
|
||||||
|
default:
|
||||||
|
i = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
69
server/api/v1/system/sys_casbin.go
Normal file
69
server/api/v1/system/sys_casbin.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CasbinApi struct{}
|
||||||
|
|
||||||
|
// UpdateCasbin
|
||||||
|
// @Tags Casbin
|
||||||
|
// @Summary 更新角色api权限
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.CasbinInReceive true "权限id, 权限模型列表"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新角色api权限"
|
||||||
|
// @Router /casbin/UpdateCasbin [post]
|
||||||
|
func (cas *CasbinApi) UpdateCasbin(c *gin.Context) {
|
||||||
|
var cmr request.CasbinInReceive
|
||||||
|
err := c.ShouldBindJSON(&cmr)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(cmr, utils.AuthorityIdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||||
|
err = casbinService.UpdateCasbin(adminAuthorityID, cmr.AuthorityId, cmr.CasbinInfos)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPolicyPathByAuthorityId
|
||||||
|
// @Tags Casbin
|
||||||
|
// @Summary 获取权限列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.CasbinInReceive true "权限id, 权限模型列表"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.PolicyPathResponse,msg=string} "获取权限列表,返回包括casbin详情列表"
|
||||||
|
// @Router /casbin/getPolicyPathByAuthorityId [post]
|
||||||
|
func (cas *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) {
|
||||||
|
var casbin request.CasbinInReceive
|
||||||
|
err := c.ShouldBindJSON(&casbin)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(casbin, utils.AuthorityIdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
paths := casbinService.GetPolicyPathByAuthorityId(casbin.AuthorityId)
|
||||||
|
response.OkWithDetailed(systemRes.PolicyPathResponse{Paths: paths}, "获取成功", c)
|
||||||
|
}
|
||||||
191
server/api/v1/system/sys_dictionary.go
Normal file
191
server/api/v1/system/sys_dictionary.go
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictionaryApi struct{}
|
||||||
|
|
||||||
|
// CreateSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 创建SysDictionary
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionary true "SysDictionary模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建SysDictionary"
|
||||||
|
// @Router /sysDictionary/createSysDictionary [post]
|
||||||
|
func (s *DictionaryApi) CreateSysDictionary(c *gin.Context) {
|
||||||
|
var dictionary system.SysDictionary
|
||||||
|
err := c.ShouldBindJSON(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryService.CreateSysDictionary(dictionary)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 删除SysDictionary
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionary true "SysDictionary模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除SysDictionary"
|
||||||
|
// @Router /sysDictionary/deleteSysDictionary [delete]
|
||||||
|
func (s *DictionaryApi) DeleteSysDictionary(c *gin.Context) {
|
||||||
|
var dictionary system.SysDictionary
|
||||||
|
err := c.ShouldBindJSON(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryService.DeleteSysDictionary(dictionary)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 更新SysDictionary
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionary true "SysDictionary模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新SysDictionary"
|
||||||
|
// @Router /sysDictionary/updateSysDictionary [put]
|
||||||
|
func (s *DictionaryApi) UpdateSysDictionary(c *gin.Context) {
|
||||||
|
var dictionary system.SysDictionary
|
||||||
|
err := c.ShouldBindJSON(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryService.UpdateSysDictionary(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 用id查询SysDictionary
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysDictionary true "ID或字典英名"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysDictionary"
|
||||||
|
// @Router /sysDictionary/findSysDictionary [get]
|
||||||
|
func (s *DictionaryApi) FindSysDictionary(c *gin.Context) {
|
||||||
|
var dictionary system.SysDictionary
|
||||||
|
err := c.ShouldBindQuery(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sysDictionary, err := dictionaryService.GetSysDictionary(dictionary.Type, dictionary.ID, dictionary.Status)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("字典未创建或未开启!", zap.Error(err))
|
||||||
|
response.FailWithMessage("字典未创建或未开启", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"resysDictionary": sysDictionary}, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysDictionaryList
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 分页获取SysDictionary列表
|
||||||
|
// @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) {
|
||||||
|
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)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(list, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 导出字典JSON(包含字典详情)
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysDictionary true "字典ID"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "导出字典JSON"
|
||||||
|
// @Router /sysDictionary/exportSysDictionary [get]
|
||||||
|
func (s *DictionaryApi) ExportSysDictionary(c *gin.Context) {
|
||||||
|
var dictionary system.SysDictionary
|
||||||
|
err := c.ShouldBindQuery(&dictionary)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if dictionary.ID == 0 {
|
||||||
|
response.FailWithMessage("字典ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
exportData, err := dictionaryService.ExportSysDictionary(dictionary.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("导出失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("导出失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(exportData, "导出成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportSysDictionary
|
||||||
|
// @Tags SysDictionary
|
||||||
|
// @Summary 导入字典JSON(包含字典详情)
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.ImportSysDictionaryRequest true "字典JSON数据"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "导入字典"
|
||||||
|
// @Router /sysDictionary/importSysDictionary [post]
|
||||||
|
func (s *DictionaryApi) ImportSysDictionary(c *gin.Context) {
|
||||||
|
var req request.ImportSysDictionaryRequest
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryService.ImportSysDictionary(req.Json)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("导入失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("导入失败: "+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("导入成功", c)
|
||||||
|
}
|
||||||
267
server/api/v1/system/sys_dictionary_detail.go
Normal file
267
server/api/v1/system/sys_dictionary_detail.go
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DictionaryDetailApi struct{}
|
||||||
|
|
||||||
|
// CreateSysDictionaryDetail
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 创建SysDictionaryDetail
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionaryDetail true "SysDictionaryDetail模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建SysDictionaryDetail"
|
||||||
|
// @Router /sysDictionaryDetail/createSysDictionaryDetail [post]
|
||||||
|
func (s *DictionaryDetailApi) CreateSysDictionaryDetail(c *gin.Context) {
|
||||||
|
var detail system.SysDictionaryDetail
|
||||||
|
err := c.ShouldBindJSON(&detail)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryDetailService.CreateSysDictionaryDetail(detail)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysDictionaryDetail
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 删除SysDictionaryDetail
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionaryDetail true "SysDictionaryDetail模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除SysDictionaryDetail"
|
||||||
|
// @Router /sysDictionaryDetail/deleteSysDictionaryDetail [delete]
|
||||||
|
func (s *DictionaryDetailApi) DeleteSysDictionaryDetail(c *gin.Context) {
|
||||||
|
var detail system.SysDictionaryDetail
|
||||||
|
err := c.ShouldBindJSON(&detail)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryDetailService.DeleteSysDictionaryDetail(detail)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSysDictionaryDetail
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 更新SysDictionaryDetail
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysDictionaryDetail true "更新SysDictionaryDetail"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新SysDictionaryDetail"
|
||||||
|
// @Router /sysDictionaryDetail/updateSysDictionaryDetail [put]
|
||||||
|
func (s *DictionaryDetailApi) UpdateSysDictionaryDetail(c *gin.Context) {
|
||||||
|
var detail system.SysDictionaryDetail
|
||||||
|
err := c.ShouldBindJSON(&detail)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = dictionaryDetailService.UpdateSysDictionaryDetail(&detail)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysDictionaryDetail
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 用id查询SysDictionaryDetail
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysDictionaryDetail true "用id查询SysDictionaryDetail"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysDictionaryDetail"
|
||||||
|
// @Router /sysDictionaryDetail/findSysDictionaryDetail [get]
|
||||||
|
func (s *DictionaryDetailApi) FindSysDictionaryDetail(c *gin.Context) {
|
||||||
|
var detail system.SysDictionaryDetail
|
||||||
|
err := c.ShouldBindQuery(&detail)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(detail, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reSysDictionaryDetail, err := dictionaryDetailService.GetSysDictionaryDetail(detail.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"reSysDictionaryDetail": reSysDictionaryDetail}, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysDictionaryDetailList
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 分页获取SysDictionaryDetail列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query request.SysDictionaryDetailSearch true "页码, 每页大小, 搜索条件"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysDictionaryDetail列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /sysDictionaryDetail/getSysDictionaryDetailList [get]
|
||||||
|
func (s *DictionaryDetailApi) GetSysDictionaryDetailList(c *gin.Context) {
|
||||||
|
var pageInfo request.SysDictionaryDetailSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := dictionaryDetailService.GetSysDictionaryDetailInfoList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryTreeList
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 获取字典详情树形结构
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param sysDictionaryID query int true "字典ID"
|
||||||
|
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
|
||||||
|
// @Router /sysDictionaryDetail/getDictionaryTreeList [get]
|
||||||
|
func (s *DictionaryDetailApi) GetDictionaryTreeList(c *gin.Context) {
|
||||||
|
sysDictionaryID := c.Query("sysDictionaryID")
|
||||||
|
if sysDictionaryID == "" {
|
||||||
|
response.FailWithMessage("字典ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uint
|
||||||
|
if idUint64, err := strconv.ParseUint(sysDictionaryID, 10, 32); err != nil {
|
||||||
|
response.FailWithMessage("字典ID格式错误", c)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
id = uint(idUint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := dictionaryDetailService.GetDictionaryTreeList(id)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryTreeListByType
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 根据字典类型获取字典详情树形结构
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param type query string true "字典类型"
|
||||||
|
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情树形结构"
|
||||||
|
// @Router /sysDictionaryDetail/getDictionaryTreeListByType [get]
|
||||||
|
func (s *DictionaryDetailApi) GetDictionaryTreeListByType(c *gin.Context) {
|
||||||
|
dictType := c.Query("type")
|
||||||
|
if dictType == "" {
|
||||||
|
response.FailWithMessage("字典类型不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := dictionaryDetailService.GetDictionaryTreeListByType(dictType)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryDetailsByParent
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 根据父级ID获取字典详情
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query request.GetDictionaryDetailsByParentRequest true "查询参数"
|
||||||
|
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情列表"
|
||||||
|
// @Router /sysDictionaryDetail/getDictionaryDetailsByParent [get]
|
||||||
|
func (s *DictionaryDetailApi) GetDictionaryDetailsByParent(c *gin.Context) {
|
||||||
|
var req request.GetDictionaryDetailsByParentRequest
|
||||||
|
err := c.ShouldBindQuery(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, err := dictionaryDetailService.GetDictionaryDetailsByParent(req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDictionaryPath
|
||||||
|
// @Tags SysDictionaryDetail
|
||||||
|
// @Summary 获取字典详情的完整路径
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param id query uint true "字典详情ID"
|
||||||
|
// @Success 200 {object} response.Response{data=[]system.SysDictionaryDetail,msg=string} "获取字典详情路径"
|
||||||
|
// @Router /sysDictionaryDetail/getDictionaryPath [get]
|
||||||
|
func (s *DictionaryDetailApi) GetDictionaryPath(c *gin.Context) {
|
||||||
|
idStr := c.Query("id")
|
||||||
|
if idStr == "" {
|
||||||
|
response.FailWithMessage("字典详情ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var id uint
|
||||||
|
if idUint64, err := strconv.ParseUint(idStr, 10, 32); err != nil {
|
||||||
|
response.FailWithMessage("字典详情ID格式错误", c)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
id = uint(idUint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err := dictionaryDetailService.GetDictionaryPath(id)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"path": path}, "获取成功", c)
|
||||||
|
}
|
||||||
199
server/api/v1/system/sys_error.go
Normal file
199
server/api/v1/system/sys_error.go
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysErrorApi struct{}
|
||||||
|
|
||||||
|
// CreateSysError 创建错误日志
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 创建错误日志
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysError true "创建错误日志"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||||
|
// @Router /sysError/createSysError [post]
|
||||||
|
func (sysErrorApi *SysErrorApi) CreateSysError(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
var sysError system.SysError
|
||||||
|
err := c.ShouldBindJSON(&sysError)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = sysErrorService.CreateSysError(ctx, &sysError)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysError 删除错误日志
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 删除错误日志
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysError true "删除错误日志"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||||
|
// @Router /sysError/deleteSysError [delete]
|
||||||
|
func (sysErrorApi *SysErrorApi) DeleteSysError(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
ID := c.Query("ID")
|
||||||
|
err := sysErrorService.DeleteSysError(ctx, ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysErrorByIds 批量删除错误日志
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 批量删除错误日志
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||||
|
// @Router /sysError/deleteSysErrorByIds [delete]
|
||||||
|
func (sysErrorApi *SysErrorApi) DeleteSysErrorByIds(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
IDs := c.QueryArray("IDs[]")
|
||||||
|
err := sysErrorService.DeleteSysErrorByIds(ctx, IDs)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("批量删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSysError 更新错误日志
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 更新错误日志
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysError true "更新错误日志"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||||
|
// @Router /sysError/updateSysError [put]
|
||||||
|
func (sysErrorApi *SysErrorApi) UpdateSysError(c *gin.Context) {
|
||||||
|
// 从ctx获取标准context进行业务行为
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
var sysError system.SysError
|
||||||
|
err := c.ShouldBindJSON(&sysError)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = sysErrorService.UpdateSysError(ctx, sysError)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysError 用id查询错误日志
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 用id查询错误日志
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param ID query uint true "用id查询错误日志"
|
||||||
|
// @Success 200 {object} response.Response{data=system.SysError,msg=string} "查询成功"
|
||||||
|
// @Router /sysError/findSysError [get]
|
||||||
|
func (sysErrorApi *SysErrorApi) FindSysError(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
ID := c.Query("ID")
|
||||||
|
resysError, err := sysErrorService.GetSysError(ctx, ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(resysError, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysErrorList 分页获取错误日志列表
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 分页获取错误日志列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query systemReq.SysErrorSearch true "分页获取错误日志列表"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||||
|
// @Router /sysError/getSysErrorList [get]
|
||||||
|
func (sysErrorApi *SysErrorApi) GetSysErrorList(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
var pageInfo systemReq.SysErrorSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := sysErrorService.GetSysErrorInfoList(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysErrorSolution 触发错误日志的异步处理
|
||||||
|
// @Tags SysError
|
||||||
|
// @Summary 根据ID触发处理:标记为处理中,1分钟后自动改为处理完成
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param id query string true "错误日志ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "处理已提交"
|
||||||
|
// @Router /sysError/getSysErrorSolution [get]
|
||||||
|
func (sysErrorApi *SysErrorApi) GetSysErrorSolution(c *gin.Context) {
|
||||||
|
// 创建业务用Context
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
// 兼容 id 与 ID 两种参数
|
||||||
|
ID := c.Query("id")
|
||||||
|
if ID == "" {
|
||||||
|
response.FailWithMessage("缺少参数: id", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := sysErrorService.GetSysErrorSolution(ctx, ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("处理触发失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("处理触发失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response.OkWithMessage("已提交至AI处理", c)
|
||||||
|
}
|
||||||
456
server/api/v1/system/sys_export_template.go
Normal file
456
server/api/v1/system/sys_export_template.go
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"git.echol.cn/loser/st/server/service"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 用于token一次性存储
|
||||||
|
var (
|
||||||
|
exportTokenCache = make(map[string]interface{})
|
||||||
|
exportTokenExpiration = make(map[string]time.Time)
|
||||||
|
tokenMutex sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// 五分钟检测窗口过期
|
||||||
|
func cleanupExpiredTokens() {
|
||||||
|
for {
|
||||||
|
time.Sleep(5 * time.Minute)
|
||||||
|
tokenMutex.Lock()
|
||||||
|
now := time.Now()
|
||||||
|
for token, expiry := range exportTokenExpiration {
|
||||||
|
if now.After(expiry) {
|
||||||
|
delete(exportTokenCache, token)
|
||||||
|
delete(exportTokenExpiration, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tokenMutex.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go cleanupExpiredTokens()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SysExportTemplateApi struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
var sysExportTemplateService = service.ServiceGroupApp.SystemServiceGroup.SysExportTemplateService
|
||||||
|
|
||||||
|
// PreviewSQL 预览最终生成的SQL
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 预览最终生成的SQL(不执行查询,仅返回SQL字符串)
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param templateID query string true "导出模板ID"
|
||||||
|
// @Param params query string false "查询参数编码字符串,参考 ExportExcel 组件"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]string} "获取成功"
|
||||||
|
// @Router /sysExportTemplate/previewSQL [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) PreviewSQL(c *gin.Context) {
|
||||||
|
templateID := c.Query("templateID")
|
||||||
|
if templateID == "" {
|
||||||
|
response.FailWithMessage("模板ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接复用导出接口的参数组织方式:使用 URL Query,其中 params 为内部编码的查询字符串
|
||||||
|
queryParams := c.Request.URL.Query()
|
||||||
|
|
||||||
|
if sqlPreview, err := sysExportTemplateService.PreviewSQL(templateID, queryParams); err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithData(gin.H{"sql": sqlPreview}, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateSysExportTemplate 创建导出模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 创建导出模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysExportTemplate true "创建导出模板"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"创建成功"}"
|
||||||
|
// @Router /sysExportTemplate/createSysExportTemplate [post]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) CreateSysExportTemplate(c *gin.Context) {
|
||||||
|
var sysExportTemplate system.SysExportTemplate
|
||||||
|
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verify := utils.Rules{
|
||||||
|
"Name": {utils.NotEmpty()},
|
||||||
|
}
|
||||||
|
if err := utils.Verify(sysExportTemplate, verify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysExportTemplateService.CreateSysExportTemplate(&sysExportTemplate); err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysExportTemplate 删除导出模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 删除导出模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysExportTemplate true "删除导出模板"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"删除成功"}"
|
||||||
|
// @Router /sysExportTemplate/deleteSysExportTemplate [delete]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplate(c *gin.Context) {
|
||||||
|
var sysExportTemplate system.SysExportTemplate
|
||||||
|
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysExportTemplateService.DeleteSysExportTemplate(sysExportTemplate); err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysExportTemplateByIds 批量删除导出模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 批量删除导出模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.IdsReq true "批量删除导出模板"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"批量删除成功"}"
|
||||||
|
// @Router /sysExportTemplate/deleteSysExportTemplateByIds [delete]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) DeleteSysExportTemplateByIds(c *gin.Context) {
|
||||||
|
var IDS request.IdsReq
|
||||||
|
err := c.ShouldBindJSON(&IDS)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysExportTemplateService.DeleteSysExportTemplateByIds(IDS); err != nil {
|
||||||
|
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("批量删除失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("批量删除成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSysExportTemplate 更新导出模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 更新导出模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysExportTemplate true "更新导出模板"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"更新成功"}"
|
||||||
|
// @Router /sysExportTemplate/updateSysExportTemplate [put]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) UpdateSysExportTemplate(c *gin.Context) {
|
||||||
|
var sysExportTemplate system.SysExportTemplate
|
||||||
|
err := c.ShouldBindJSON(&sysExportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verify := utils.Rules{
|
||||||
|
"Name": {utils.NotEmpty()},
|
||||||
|
}
|
||||||
|
if err := utils.Verify(sysExportTemplate, verify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysExportTemplateService.UpdateSysExportTemplate(sysExportTemplate); err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysExportTemplate 用id查询导出模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 用id查询导出模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysExportTemplate true "用id查询导出模板"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"查询成功"}"
|
||||||
|
// @Router /sysExportTemplate/findSysExportTemplate [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) FindSysExportTemplate(c *gin.Context) {
|
||||||
|
var sysExportTemplate system.SysExportTemplate
|
||||||
|
err := c.ShouldBindQuery(&sysExportTemplate)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if resysExportTemplate, err := sysExportTemplateService.GetSysExportTemplate(sysExportTemplate.ID); err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithData(gin.H{"resysExportTemplate": resysExportTemplate}, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysExportTemplateList 分页获取导出模板列表
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 分页获取导出模板列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query systemReq.SysExportTemplateSearch true "分页获取导出模板列表"
|
||||||
|
// @Success 200 {string} string "{"success":true,"data":{},"msg":"获取成功"}"
|
||||||
|
// @Router /sysExportTemplate/getSysExportTemplateList [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) GetSysExportTemplateList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.SysExportTemplateSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if list, total, err := sysExportTemplateService.GetSysExportTemplateInfoList(pageInfo); err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportExcel 导出表格token
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 导出表格
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Router /sysExportTemplate/exportExcel [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcel(c *gin.Context) {
|
||||||
|
templateID := c.Query("templateID")
|
||||||
|
if templateID == "" {
|
||||||
|
response.FailWithMessage("模板ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
queryParams := c.Request.URL.Query()
|
||||||
|
|
||||||
|
//创造一次性token
|
||||||
|
token := utils.RandomString(32) // 随机32位
|
||||||
|
|
||||||
|
// 记录本次请求参数
|
||||||
|
exportParams := map[string]interface{}{
|
||||||
|
"templateID": templateID,
|
||||||
|
"queryParams": queryParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数保留记录完成鉴权
|
||||||
|
tokenMutex.Lock()
|
||||||
|
exportTokenCache[token] = exportParams
|
||||||
|
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||||
|
tokenMutex.Unlock()
|
||||||
|
|
||||||
|
// 生成一次性链接
|
||||||
|
exportUrl := fmt.Sprintf("/sysExportTemplate/exportExcelByToken?token=%s", token)
|
||||||
|
response.OkWithData(exportUrl, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportExcelByToken 导出表格
|
||||||
|
// @Tags ExportExcelByToken
|
||||||
|
// @Summary 导出表格
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Router /sysExportTemplate/exportExcelByToken [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) ExportExcelByToken(c *gin.Context) {
|
||||||
|
token := c.Query("token")
|
||||||
|
if token == "" {
|
||||||
|
response.FailWithMessage("导出token不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token并且从缓存中剔除
|
||||||
|
tokenMutex.RLock()
|
||||||
|
exportParamsRaw, exists := exportTokenCache[token]
|
||||||
|
expiry, _ := exportTokenExpiration[token]
|
||||||
|
tokenMutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists || time.Now().After(expiry) {
|
||||||
|
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||||
|
response.FailWithMessage("导出token无效或已过期", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从token获取参数
|
||||||
|
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
global.GVA_LOG.Error("解析导出参数失败!")
|
||||||
|
response.FailWithMessage("解析导出参数失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取导出参数
|
||||||
|
templateID := exportParams["templateID"].(string)
|
||||||
|
queryParams := exportParams["queryParams"].(url.Values)
|
||||||
|
|
||||||
|
// 清理一次性token
|
||||||
|
tokenMutex.Lock()
|
||||||
|
delete(exportTokenCache, token)
|
||||||
|
delete(exportTokenExpiration, token)
|
||||||
|
tokenMutex.Unlock()
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
if file, name, err := sysExportTemplateService.ExportExcel(templateID, queryParams); err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+utils.RandomString(6)+".xlsx"))
|
||||||
|
c.Header("success", "true")
|
||||||
|
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportTemplate 导出表格模板
|
||||||
|
// @Tags SysExportTemplate
|
||||||
|
// @Summary 导出表格模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Router /sysExportTemplate/exportTemplate [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplate(c *gin.Context) {
|
||||||
|
templateID := c.Query("templateID")
|
||||||
|
if templateID == "" {
|
||||||
|
response.FailWithMessage("模板ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创造一次性token
|
||||||
|
token := utils.RandomString(32) // 随机32位
|
||||||
|
|
||||||
|
// 记录本次请求参数
|
||||||
|
exportParams := map[string]interface{}{
|
||||||
|
"templateID": templateID,
|
||||||
|
"isTemplate": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 参数保留记录完成鉴权
|
||||||
|
tokenMutex.Lock()
|
||||||
|
exportTokenCache[token] = exportParams
|
||||||
|
exportTokenExpiration[token] = time.Now().Add(30 * time.Minute)
|
||||||
|
tokenMutex.Unlock()
|
||||||
|
|
||||||
|
// 生成一次性链接
|
||||||
|
exportUrl := fmt.Sprintf("/sysExportTemplate/exportTemplateByToken?token=%s", token)
|
||||||
|
response.OkWithData(exportUrl, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExportTemplateByToken 通过token导出表格模板
|
||||||
|
// @Tags ExportTemplateByToken
|
||||||
|
// @Summary 通过token导出表格模板
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Router /sysExportTemplate/exportTemplateByToken [get]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) ExportTemplateByToken(c *gin.Context) {
|
||||||
|
token := c.Query("token")
|
||||||
|
if token == "" {
|
||||||
|
response.FailWithMessage("导出token不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取token并且从缓存中剔除
|
||||||
|
tokenMutex.RLock()
|
||||||
|
exportParamsRaw, exists := exportTokenCache[token]
|
||||||
|
expiry, _ := exportTokenExpiration[token]
|
||||||
|
tokenMutex.RUnlock()
|
||||||
|
|
||||||
|
if !exists || time.Now().After(expiry) {
|
||||||
|
global.GVA_LOG.Error("导出token无效或已过期!")
|
||||||
|
response.FailWithMessage("导出token无效或已过期", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从token获取参数
|
||||||
|
exportParams, ok := exportParamsRaw.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
global.GVA_LOG.Error("解析导出参数失败!")
|
||||||
|
response.FailWithMessage("解析导出参数失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为模板导出
|
||||||
|
isTemplate, _ := exportParams["isTemplate"].(bool)
|
||||||
|
if !isTemplate {
|
||||||
|
global.GVA_LOG.Error("token类型错误!")
|
||||||
|
response.FailWithMessage("token类型错误", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取导出参数
|
||||||
|
templateID := exportParams["templateID"].(string)
|
||||||
|
|
||||||
|
// 清理一次性token
|
||||||
|
tokenMutex.Lock()
|
||||||
|
delete(exportTokenCache, token)
|
||||||
|
delete(exportTokenExpiration, token)
|
||||||
|
tokenMutex.Unlock()
|
||||||
|
|
||||||
|
// 导出模板
|
||||||
|
if file, name, err := sysExportTemplateService.ExportTemplate(templateID); err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
} else {
|
||||||
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", name+"模板.xlsx"))
|
||||||
|
c.Header("success", "true")
|
||||||
|
c.Data(http.StatusOK, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", file.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImportExcel 导入表格
|
||||||
|
// @Tags SysImportTemplate
|
||||||
|
// @Summary 导入表格
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Router /sysExportTemplate/importExcel [post]
|
||||||
|
func (sysExportTemplateApi *SysExportTemplateApi) ImportExcel(c *gin.Context) {
|
||||||
|
templateID := c.Query("templateID")
|
||||||
|
if templateID == "" {
|
||||||
|
response.FailWithMessage("模板ID不能为空", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("文件获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("文件获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := sysExportTemplateService.ImportExcel(templateID, file); err != nil {
|
||||||
|
global.GVA_LOG.Error(err.Error(), zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("导入成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
59
server/api/v1/system/sys_initdb.go
Normal file
59
server/api/v1/system/sys_initdb.go
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBApi struct{}
|
||||||
|
|
||||||
|
// InitDB
|
||||||
|
// @Tags InitDB
|
||||||
|
// @Summary 初始化用户数据库
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.InitDB true "初始化数据库参数"
|
||||||
|
// @Success 200 {object} response.Response{data=string} "初始化用户数据库"
|
||||||
|
// @Router /init/initdb [post]
|
||||||
|
func (i *DBApi) InitDB(c *gin.Context) {
|
||||||
|
if global.GVA_DB != nil {
|
||||||
|
global.GVA_LOG.Error("已存在数据库配置!")
|
||||||
|
response.FailWithMessage("已存在数据库配置", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var dbInfo request.InitDB
|
||||||
|
if err := c.ShouldBindJSON(&dbInfo); err != nil {
|
||||||
|
global.GVA_LOG.Error("参数校验不通过!", zap.Error(err))
|
||||||
|
response.FailWithMessage("参数校验不通过", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := initDBService.InitDB(dbInfo); err != nil {
|
||||||
|
global.GVA_LOG.Error("自动创建数据库失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("自动创建数据库失败,请查看后台日志,检查后在进行初始化", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("自动创建数据库成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckDB
|
||||||
|
// @Tags CheckDB
|
||||||
|
// @Summary 初始化用户数据库
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "初始化用户数据库"
|
||||||
|
// @Router /init/checkdb [post]
|
||||||
|
func (i *DBApi) CheckDB(c *gin.Context) {
|
||||||
|
var (
|
||||||
|
message = "前往初始化数据库"
|
||||||
|
needInit = true
|
||||||
|
)
|
||||||
|
|
||||||
|
if global.GVA_DB != nil {
|
||||||
|
message = "数据库无需初始化"
|
||||||
|
needInit = false
|
||||||
|
}
|
||||||
|
global.GVA_LOG.Info(message)
|
||||||
|
response.OkWithDetailed(gin.H{"needInit": needInit}, message, c)
|
||||||
|
}
|
||||||
33
server/api/v1/system/sys_jwt_blacklist.go
Normal file
33
server/api/v1/system/sys_jwt_blacklist.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type JwtApi struct{}
|
||||||
|
|
||||||
|
// JsonInBlacklist
|
||||||
|
// @Tags Jwt
|
||||||
|
// @Summary jwt加入黑名单
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "jwt加入黑名单"
|
||||||
|
// @Router /jwt/jsonInBlacklist [post]
|
||||||
|
func (j *JwtApi) JsonInBlacklist(c *gin.Context) {
|
||||||
|
token := utils.GetToken(c)
|
||||||
|
jwt := system.JwtBlacklist{Jwt: token}
|
||||||
|
err := jwtService.JsonInBlacklist(jwt)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("jwt作废失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("jwt作废失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.ClearToken(c)
|
||||||
|
response.OkWithMessage("jwt作废成功", c)
|
||||||
|
}
|
||||||
82
server/api/v1/system/sys_login_log.go
Normal file
82
server/api/v1/system/sys_login_log.go
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoginLogApi struct{}
|
||||||
|
|
||||||
|
func (s *LoginLogApi) DeleteLoginLog(c *gin.Context) {
|
||||||
|
var loginLog system.SysLoginLog
|
||||||
|
err := c.ShouldBindJSON(&loginLog)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = loginLogService.DeleteLoginLog(loginLog)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginLogApi) DeleteLoginLogByIds(c *gin.Context) {
|
||||||
|
var SDS request.IdsReq
|
||||||
|
err := c.ShouldBindJSON(&SDS)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = loginLogService.DeleteLoginLogByIds(SDS)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("批量删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("批量删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginLogApi) FindLoginLog(c *gin.Context) {
|
||||||
|
var loginLog system.SysLoginLog
|
||||||
|
err := c.ShouldBindQuery(&loginLog)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reLoginLog, err := loginLogService.GetLoginLog(loginLog.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(reLoginLog, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LoginLogApi) GetLoginLogList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.SysLoginLogSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := loginLogService.GetLoginLogInfoList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
265
server/api/v1/system/sys_menu.go
Normal file
265
server/api/v1/system/sys_menu.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AuthorityMenuApi struct{}
|
||||||
|
|
||||||
|
// GetMenu
|
||||||
|
// @Tags AuthorityMenu
|
||||||
|
// @Summary 获取用户动态路由
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.Empty true "空"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysMenusResponse,msg=string} "获取用户动态路由,返回包括系统菜单详情列表"
|
||||||
|
// @Router /menu/getMenu [post]
|
||||||
|
func (a *AuthorityMenuApi) GetMenu(c *gin.Context) {
|
||||||
|
menus, err := menuService.GetMenuTree(utils.GetUserAuthorityId(c))
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if menus == nil {
|
||||||
|
menus = []system.SysMenu{}
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBaseMenuTree
|
||||||
|
// @Tags AuthorityMenu
|
||||||
|
// @Summary 获取用户动态路由
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.Empty true "空"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysBaseMenusResponse,msg=string} "获取用户动态路由,返回包括系统菜单列表"
|
||||||
|
// @Router /menu/getBaseMenuTree [post]
|
||||||
|
func (a *AuthorityMenuApi) GetBaseMenuTree(c *gin.Context) {
|
||||||
|
authority := utils.GetUserAuthorityId(c)
|
||||||
|
menus, err := menuService.GetBaseMenuTree(authority)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysBaseMenusResponse{Menus: menus}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddMenuAuthority
|
||||||
|
// @Tags AuthorityMenu
|
||||||
|
// @Summary 增加menu和角色关联关系
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.AddMenuAuthorityInfo true "角色ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "增加menu和角色关联关系"
|
||||||
|
// @Router /menu/addMenuAuthority [post]
|
||||||
|
func (a *AuthorityMenuApi) AddMenuAuthority(c *gin.Context) {
|
||||||
|
var authorityMenu systemReq.AddMenuAuthorityInfo
|
||||||
|
err := c.ShouldBindJSON(&authorityMenu)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := utils.Verify(authorityMenu, utils.AuthorityIdVerify); err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adminAuthorityID := utils.GetUserAuthorityId(c)
|
||||||
|
if err := menuService.AddMenuAuthority(authorityMenu.Menus, adminAuthorityID, authorityMenu.AuthorityId); err != nil {
|
||||||
|
global.GVA_LOG.Error("添加失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("添加失败", c)
|
||||||
|
} else {
|
||||||
|
response.OkWithMessage("添加成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMenuAuthority
|
||||||
|
// @Tags AuthorityMenu
|
||||||
|
// @Summary 获取指定角色menu
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetAuthorityId true "角色ID"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取指定角色menu"
|
||||||
|
// @Router /menu/getMenuAuthority [post]
|
||||||
|
func (a *AuthorityMenuApi) GetMenuAuthority(c *gin.Context) {
|
||||||
|
var param request.GetAuthorityId
|
||||||
|
err := c.ShouldBindJSON(¶m)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(param, utils.AuthorityIdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menus, err := menuService.GetMenuAuthority(¶m)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithDetailed(systemRes.SysMenusResponse{Menus: menus}, "获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBaseMenu
|
||||||
|
// @Tags Menu
|
||||||
|
// @Summary 新增菜单
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysBaseMenu true "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "新增菜单"
|
||||||
|
// @Router /menu/addBaseMenu [post]
|
||||||
|
func (a *AuthorityMenuApi) AddBaseMenu(c *gin.Context) {
|
||||||
|
var menu system.SysBaseMenu
|
||||||
|
err := c.ShouldBindJSON(&menu)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(menu, utils.MenuVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(menu.Meta, utils.MenuMetaVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = menuService.AddBaseMenu(menu)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("添加失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("添加失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("添加成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteBaseMenu
|
||||||
|
// @Tags Menu
|
||||||
|
// @Summary 删除菜单
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "菜单id"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除菜单"
|
||||||
|
// @Router /menu/deleteBaseMenu [post]
|
||||||
|
func (a *AuthorityMenuApi) DeleteBaseMenu(c *gin.Context) {
|
||||||
|
var menu request.GetById
|
||||||
|
err := c.ShouldBindJSON(&menu)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(menu, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = baseMenuService.DeleteBaseMenu(menu.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateBaseMenu
|
||||||
|
// @Tags Menu
|
||||||
|
// @Summary 更新菜单
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysBaseMenu true "路由path, 父菜单ID, 路由name, 对应前端文件路径, 排序标记"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新菜单"
|
||||||
|
// @Router /menu/updateBaseMenu [post]
|
||||||
|
func (a *AuthorityMenuApi) UpdateBaseMenu(c *gin.Context) {
|
||||||
|
var menu system.SysBaseMenu
|
||||||
|
err := c.ShouldBindJSON(&menu)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(menu, utils.MenuVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(menu.Meta, utils.MenuMetaVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = baseMenuService.UpdateBaseMenu(menu)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBaseMenuById
|
||||||
|
// @Tags Menu
|
||||||
|
// @Summary 根据id获取菜单
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "菜单id"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysBaseMenuResponse,msg=string} "根据id获取菜单,返回包括系统菜单列表"
|
||||||
|
// @Router /menu/getBaseMenuById [post]
|
||||||
|
func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) {
|
||||||
|
var idInfo request.GetById
|
||||||
|
err := c.ShouldBindJSON(&idInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(idInfo, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
menu, err := baseMenuService.GetBaseMenuById(idInfo.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMenuList
|
||||||
|
// @Tags Menu
|
||||||
|
// @Summary 分页获取基础menu列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.PageInfo true "页码, 每页大小"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取基础menu列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /menu/getMenuList [post]
|
||||||
|
func (a *AuthorityMenuApi) GetMenuList(c *gin.Context) {
|
||||||
|
authorityID := utils.GetUserAuthorityId(c)
|
||||||
|
menuList, err := menuService.GetInfoList(authorityID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(menuList, "获取成功", c)
|
||||||
|
}
|
||||||
124
server/api/v1/system/sys_operation_record.go
Normal file
124
server/api/v1/system/sys_operation_record.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OperationRecordApi struct{}
|
||||||
|
|
||||||
|
// DeleteSysOperationRecord
|
||||||
|
// @Tags SysOperationRecord
|
||||||
|
// @Summary 删除SysOperationRecord
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysOperationRecord true "SysOperationRecord模型"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除SysOperationRecord"
|
||||||
|
// @Router /sysOperationRecord/deleteSysOperationRecord [delete]
|
||||||
|
func (s *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) {
|
||||||
|
var sysOperationRecord system.SysOperationRecord
|
||||||
|
err := c.ShouldBindJSON(&sysOperationRecord)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = operationRecordService.DeleteSysOperationRecord(sysOperationRecord)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysOperationRecordByIds
|
||||||
|
// @Tags SysOperationRecord
|
||||||
|
// @Summary 批量删除SysOperationRecord
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.IdsReq true "批量删除SysOperationRecord"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "批量删除SysOperationRecord"
|
||||||
|
// @Router /sysOperationRecord/deleteSysOperationRecordByIds [delete]
|
||||||
|
func (s *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) {
|
||||||
|
var IDS request.IdsReq
|
||||||
|
err := c.ShouldBindJSON(&IDS)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = operationRecordService.DeleteSysOperationRecordByIds(IDS)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("批量删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("批量删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysOperationRecord
|
||||||
|
// @Tags SysOperationRecord
|
||||||
|
// @Summary 用id查询SysOperationRecord
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysOperationRecord true "Id"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "用id查询SysOperationRecord"
|
||||||
|
// @Router /sysOperationRecord/findSysOperationRecord [get]
|
||||||
|
func (s *OperationRecordApi) FindSysOperationRecord(c *gin.Context) {
|
||||||
|
var sysOperationRecord system.SysOperationRecord
|
||||||
|
err := c.ShouldBindQuery(&sysOperationRecord)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(sysOperationRecord, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reSysOperationRecord, err := operationRecordService.GetSysOperationRecord(sysOperationRecord.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"reSysOperationRecord": reSysOperationRecord}, "查询成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysOperationRecordList
|
||||||
|
// @Tags SysOperationRecord
|
||||||
|
// @Summary 分页获取SysOperationRecord列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query request.SysOperationRecordSearch true "页码, 每页大小, 搜索条件"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取SysOperationRecord列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /sysOperationRecord/getSysOperationRecordList [get]
|
||||||
|
func (s *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.SysOperationRecordSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := operationRecordService.GetSysOperationRecordInfoList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
171
server/api/v1/system/sys_params.go
Normal file
171
server/api/v1/system/sys_params.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SysParamsApi struct{}
|
||||||
|
|
||||||
|
// CreateSysParams 创建参数
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 创建参数
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysParams true "创建参数"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "创建成功"
|
||||||
|
// @Router /sysParams/createSysParams [post]
|
||||||
|
func (sysParamsApi *SysParamsApi) CreateSysParams(c *gin.Context) {
|
||||||
|
var sysParams system.SysParams
|
||||||
|
err := c.ShouldBindJSON(&sysParams)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = sysParamsService.CreateSysParams(&sysParams)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysParams 删除参数
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 删除参数
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysParams true "删除参数"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除成功"
|
||||||
|
// @Router /sysParams/deleteSysParams [delete]
|
||||||
|
func (sysParamsApi *SysParamsApi) DeleteSysParams(c *gin.Context) {
|
||||||
|
ID := c.Query("ID")
|
||||||
|
err := sysParamsService.DeleteSysParams(ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteSysParamsByIds 批量删除参数
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 批量删除参数
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "批量删除成功"
|
||||||
|
// @Router /sysParams/deleteSysParamsByIds [delete]
|
||||||
|
func (sysParamsApi *SysParamsApi) DeleteSysParamsByIds(c *gin.Context) {
|
||||||
|
IDs := c.QueryArray("IDs[]")
|
||||||
|
err := sysParamsService.DeleteSysParamsByIds(IDs)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("批量删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("批量删除失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("批量删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateSysParams 更新参数
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 更新参数
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysParams true "更新参数"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "更新成功"
|
||||||
|
// @Router /sysParams/updateSysParams [put]
|
||||||
|
func (sysParamsApi *SysParamsApi) UpdateSysParams(c *gin.Context) {
|
||||||
|
var sysParams system.SysParams
|
||||||
|
err := c.ShouldBindJSON(&sysParams)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = sysParamsService.UpdateSysParams(sysParams)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("更新失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("更新失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("更新成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindSysParams 用id查询参数
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 用id查询参数
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query system.SysParams true "用id查询参数"
|
||||||
|
// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "查询成功"
|
||||||
|
// @Router /sysParams/findSysParams [get]
|
||||||
|
func (sysParamsApi *SysParamsApi) FindSysParams(c *gin.Context) {
|
||||||
|
ID := c.Query("ID")
|
||||||
|
resysParams, err := sysParamsService.GetSysParams(ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("查询失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("查询失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithData(resysParams, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysParamsList 分页获取参数列表
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 分页获取参数列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data query systemReq.SysParamsSearch true "分页获取参数列表"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功"
|
||||||
|
// @Router /sysParams/getSysParamsList [get]
|
||||||
|
func (sysParamsApi *SysParamsApi) GetSysParamsList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.SysParamsSearch
|
||||||
|
err := c.ShouldBindQuery(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := sysParamsService.GetSysParamsInfoList(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSysParam 根据key获取参数value
|
||||||
|
// @Tags SysParams
|
||||||
|
// @Summary 根据key获取参数value
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param key query string true "key"
|
||||||
|
// @Success 200 {object} response.Response{data=system.SysParams,msg=string} "获取成功"
|
||||||
|
// @Router /sysParams/getSysParam [get]
|
||||||
|
func (sysParamsApi *SysParamsApi) GetSysParam(c *gin.Context) {
|
||||||
|
k := c.Query("key")
|
||||||
|
params, err := sysParamsService.GetSysParam(k)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(params, "获取成功", c)
|
||||||
|
}
|
||||||
219
server/api/v1/system/sys_skills.go
Normal file
219
server/api/v1/system/sys_skills.go
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SkillsApi struct{}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetTools(c *gin.Context) {
|
||||||
|
data, err := skillsService.Tools(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取工具列表失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取工具列表失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"tools": data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetSkillList(c *gin.Context) {
|
||||||
|
var req request.SkillToolRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
data, err := skillsService.List(c.Request.Context(), req.Tool)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取技能列表失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取技能列表失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"skills": data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetSkillDetail(c *gin.Context) {
|
||||||
|
var req request.SkillDetailRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
data, err := skillsService.Detail(c.Request.Context(), req.Tool, req.Skill)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取技能详情失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取技能详情失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"detail": data}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveSkill(c *gin.Context) {
|
||||||
|
var req request.SkillSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.Save(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存技能失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存技能失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) CreateScript(c *gin.Context) {
|
||||||
|
var req request.SkillScriptCreateRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
fileName, content, err := skillsService.CreateScript(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建脚本失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建脚本失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetScript(c *gin.Context) {
|
||||||
|
var req request.SkillFileRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
content, err := skillsService.GetScript(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("读取脚本失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("读取脚本失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveScript(c *gin.Context) {
|
||||||
|
var req request.SkillFileSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.SaveScript(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存脚本失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存脚本失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) CreateResource(c *gin.Context) {
|
||||||
|
var req request.SkillResourceCreateRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
fileName, content, err := skillsService.CreateResource(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建资源失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建资源失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetResource(c *gin.Context) {
|
||||||
|
var req request.SkillFileRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
content, err := skillsService.GetResource(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("读取资源失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("读取资源失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveResource(c *gin.Context) {
|
||||||
|
var req request.SkillFileSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.SaveResource(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存资源失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存资源失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) CreateReference(c *gin.Context) {
|
||||||
|
var req request.SkillReferenceCreateRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
fileName, content, err := skillsService.CreateReference(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建参考失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建参考失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetReference(c *gin.Context) {
|
||||||
|
var req request.SkillFileRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
content, err := skillsService.GetReference(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("读取参考失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("读取参考失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveReference(c *gin.Context) {
|
||||||
|
var req request.SkillFileSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.SaveReference(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存参考失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存参考失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) CreateTemplate(c *gin.Context) {
|
||||||
|
var req request.SkillTemplateCreateRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
fileName, content, err := skillsService.CreateTemplate(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("创建模板失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("创建模板失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"fileName": fileName, "content": content}, "创建成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetTemplate(c *gin.Context) {
|
||||||
|
var req request.SkillFileRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
content, err := skillsService.GetTemplate(c.Request.Context(), req)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("读取模板失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("读取模板失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"content": content}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveTemplate(c *gin.Context) {
|
||||||
|
var req request.SkillFileSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.SaveTemplate(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存模板失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存模板失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) GetGlobalConstraint(c *gin.Context) {
|
||||||
|
var req request.SkillToolRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
content, exists, err := skillsService.GetGlobalConstraint(c.Request.Context(), req.Tool)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("读取全局约束失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("读取全局约束失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"content": content, "exists": exists}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) {
|
||||||
|
var req request.SkillGlobalConstraintSaveRequest
|
||||||
|
_ = c.ShouldBindJSON(&req)
|
||||||
|
if err := skillsService.SaveGlobalConstraint(c.Request.Context(), req); err != nil {
|
||||||
|
global.GVA_LOG.Error("保存全局约束失败", zap.Error(err))
|
||||||
|
response.FailWithMessage("保存全局约束失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("保存成功", c)
|
||||||
|
}
|
||||||
89
server/api/v1/system/sys_system.go
Normal file
89
server/api/v1/system/sys_system.go
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SystemApi struct{}
|
||||||
|
|
||||||
|
// GetSystemConfig
|
||||||
|
// @Tags System
|
||||||
|
// @Summary 获取配置文件内容
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysConfigResponse,msg=string} "获取配置文件内容,返回包括系统配置"
|
||||||
|
// @Router /system/getSystemConfig [post]
|
||||||
|
func (s *SystemApi) GetSystemConfig(c *gin.Context) {
|
||||||
|
config, err := systemConfigService.GetSystemConfig()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysConfigResponse{Config: config}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSystemConfig
|
||||||
|
// @Tags System
|
||||||
|
// @Summary 设置配置文件内容
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.System true "设置配置文件内容"
|
||||||
|
// @Success 200 {object} response.Response{data=string} "设置配置文件内容"
|
||||||
|
// @Router /system/setSystemConfig [post]
|
||||||
|
func (s *SystemApi) SetSystemConfig(c *gin.Context) {
|
||||||
|
var sys system.System
|
||||||
|
err := c.ShouldBindJSON(&sys)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = systemConfigService.SetSystemConfig(sys)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("设置成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReloadSystem
|
||||||
|
// @Tags System
|
||||||
|
// @Summary 重载系统
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "重载系统"
|
||||||
|
// @Router /system/reloadSystem [post]
|
||||||
|
func (s *SystemApi) ReloadSystem(c *gin.Context) {
|
||||||
|
// 触发系统重载事件
|
||||||
|
err := utils.GlobalSystemEvents.TriggerReload()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("重载系统失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("重载系统失败:"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("重载系统成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServerInfo
|
||||||
|
// @Tags System
|
||||||
|
// @Summary 获取服务器信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取服务器信息"
|
||||||
|
// @Router /system/getServerInfo [post]
|
||||||
|
func (s *SystemApi) GetServerInfo(c *gin.Context) {
|
||||||
|
server, err := systemConfigService.GetServerInfo()
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"server": server}, "获取成功", c)
|
||||||
|
}
|
||||||
516
server/api/v1/system/sys_user.go
Normal file
516
server/api/v1/system/sys_user.go
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/request"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Login
|
||||||
|
// @Tags Base
|
||||||
|
// @Summary 用户登录
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.Login true "用户名, 密码, 验证码"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.LoginResponse,msg=string} "返回包括用户信息,token,过期时间"
|
||||||
|
// @Router /base/login [post]
|
||||||
|
func (b *BaseApi) Login(c *gin.Context) {
|
||||||
|
var l systemReq.Login
|
||||||
|
err := c.ShouldBindJSON(&l)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(l, utils.LoginVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := c.ClientIP()
|
||||||
|
// 判断验证码是否开启
|
||||||
|
openCaptcha := global.GVA_CONFIG.Captcha.OpenCaptcha // 是否开启防爆次数
|
||||||
|
openCaptchaTimeOut := global.GVA_CONFIG.Captcha.OpenCaptchaTimeOut // 缓存超时时间
|
||||||
|
v, ok := global.BlackCache.Get(key)
|
||||||
|
if !ok {
|
||||||
|
global.BlackCache.Set(key, 1, time.Second*time.Duration(openCaptchaTimeOut))
|
||||||
|
}
|
||||||
|
|
||||||
|
var oc bool = openCaptcha == 0 || openCaptcha < interfaceToInt(v)
|
||||||
|
if oc && (l.Captcha == "" || l.CaptchaId == "" || !store.Verify(l.CaptchaId, l.Captcha, true)) {
|
||||||
|
// 验证码次数+1
|
||||||
|
global.BlackCache.Increment(key, 1)
|
||||||
|
response.FailWithMessage("验证码错误", c)
|
||||||
|
// 记录登录失败日志
|
||||||
|
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||||
|
Username: l.Username,
|
||||||
|
Ip: c.ClientIP(),
|
||||||
|
Agent: c.Request.UserAgent(),
|
||||||
|
Status: false,
|
||||||
|
ErrorMessage: "验证码错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
// 记录登录失败日志
|
||||||
|
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||||
|
Username: l.Username,
|
||||||
|
Ip: c.ClientIP(),
|
||||||
|
Agent: c.Request.UserAgent(),
|
||||||
|
Status: false,
|
||||||
|
ErrorMessage: "用户名不存在或者密码错误",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.Enable != 1 {
|
||||||
|
global.GVA_LOG.Error("登陆失败! 用户被禁止登录!")
|
||||||
|
// 验证码次数+1
|
||||||
|
global.BlackCache.Increment(key, 1)
|
||||||
|
response.FailWithMessage("用户被禁止登录", c)
|
||||||
|
// 记录登录失败日志
|
||||||
|
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||||
|
Username: l.Username,
|
||||||
|
Ip: c.ClientIP(),
|
||||||
|
Agent: c.Request.UserAgent(),
|
||||||
|
Status: false,
|
||||||
|
ErrorMessage: "用户被禁止登录",
|
||||||
|
UserID: user.ID,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.TokenNext(c, *user)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenNext 登录以后签发jwt
|
||||||
|
func (b *BaseApi) TokenNext(c *gin.Context, user system.SysUser) {
|
||||||
|
token, claims, err := utils.LoginToken(&user)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取token失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取token失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 记录登录成功日志
|
||||||
|
loginLogService.CreateLoginLog(system.SysLoginLog{
|
||||||
|
Username: user.Username,
|
||||||
|
Ip: c.ClientIP(),
|
||||||
|
Agent: c.Request.UserAgent(),
|
||||||
|
Status: true,
|
||||||
|
UserID: user.ID,
|
||||||
|
ErrorMessage: "登录成功",
|
||||||
|
})
|
||||||
|
if !global.GVA_CONFIG.System.UseMultipoint {
|
||||||
|
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||||
|
response.OkWithDetailed(systemRes.LoginResponse{
|
||||||
|
User: user,
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||||
|
}, "登录成功", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if jwtStr, err := jwtService.GetRedisJWT(user.Username); err == redis.Nil {
|
||||||
|
if err := utils.SetRedisJWT(token, user.Username); err != nil {
|
||||||
|
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置登录状态失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||||
|
response.OkWithDetailed(systemRes.LoginResponse{
|
||||||
|
User: user,
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||||
|
}, "登录成功", c)
|
||||||
|
} else if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置登录状态失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置登录状态失败", c)
|
||||||
|
} else {
|
||||||
|
var blackJWT system.JwtBlacklist
|
||||||
|
blackJWT.Jwt = jwtStr
|
||||||
|
if err := jwtService.JsonInBlacklist(blackJWT); err != nil {
|
||||||
|
response.FailWithMessage("jwt作废失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := utils.SetRedisJWT(token, user.GetUsername()); err != nil {
|
||||||
|
response.FailWithMessage("设置登录状态失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SetToken(c, token, int(claims.RegisteredClaims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||||
|
response.OkWithDetailed(systemRes.LoginResponse{
|
||||||
|
User: user,
|
||||||
|
Token: token,
|
||||||
|
ExpiresAt: claims.RegisteredClaims.ExpiresAt.Unix() * 1000,
|
||||||
|
}, "登录成功", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 用户注册账号
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.Register true "用户名, 昵称, 密码, 角色ID"
|
||||||
|
// @Success 200 {object} response.Response{data=systemRes.SysUserResponse,msg=string} "用户注册账号,返回包括用户信息"
|
||||||
|
// @Router /user/admin_register [post]
|
||||||
|
func (b *BaseApi) Register(c *gin.Context) {
|
||||||
|
var r systemReq.Register
|
||||||
|
err := c.ShouldBindJSON(&r)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(r, utils.RegisterVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var authorities []system.SysAuthority
|
||||||
|
for _, v := range r.AuthorityIds {
|
||||||
|
authorities = append(authorities, system.SysAuthority{
|
||||||
|
AuthorityId: v,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
user := &system.SysUser{Username: r.Username, NickName: r.NickName, Password: r.Password, HeaderImg: r.HeaderImg, AuthorityId: r.AuthorityId, Authorities: authorities, Enable: r.Enable, Phone: r.Phone, Email: r.Email}
|
||||||
|
userReturn, err := userService.Register(*user)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("注册失败!", zap.Error(err))
|
||||||
|
response.FailWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(systemRes.SysUserResponse{User: userReturn}, "注册成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangePassword
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 用户修改密码
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.ChangePasswordReq true "用户名, 原密码, 新密码"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "用户修改密码"
|
||||||
|
// @Router /user/changePassword [post]
|
||||||
|
func (b *BaseApi) ChangePassword(c *gin.Context) {
|
||||||
|
var req systemReq.ChangePasswordReq
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(req, utils.ChangePasswordVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uid := utils.GetUserID(c)
|
||||||
|
u := &system.SysUser{GVA_MODEL: global.GVA_MODEL{ID: uid}, Password: req.Password}
|
||||||
|
err = userService.ChangePassword(u, req.NewPassword)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("修改失败,原密码与当前账户不符", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("修改成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserList
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 分页获取用户列表
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.GetUserList true "页码, 每页大小"
|
||||||
|
// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "分页获取用户列表,返回包括列表,总数,页码,每页数量"
|
||||||
|
// @Router /user/getUserList [post]
|
||||||
|
func (b *BaseApi) GetUserList(c *gin.Context) {
|
||||||
|
var pageInfo systemReq.GetUserList
|
||||||
|
err := c.ShouldBindJSON(&pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(pageInfo, utils.PageInfoVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, total, err := userService.GetUserInfoList(pageInfo)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(response.PageResult{
|
||||||
|
List: list,
|
||||||
|
Total: total,
|
||||||
|
Page: pageInfo.Page,
|
||||||
|
PageSize: pageInfo.PageSize,
|
||||||
|
}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAuthority
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 更改用户权限
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.SetUserAuth true "用户UUID, 角色ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "设置用户权限"
|
||||||
|
// @Router /user/setUserAuthority [post]
|
||||||
|
func (b *BaseApi) SetUserAuthority(c *gin.Context) {
|
||||||
|
var sua systemReq.SetUserAuth
|
||||||
|
err := c.ShouldBindJSON(&sua)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if UserVerifyErr := utils.Verify(sua, utils.SetUserAuthorityVerify); UserVerifyErr != nil {
|
||||||
|
response.FailWithMessage(UserVerifyErr.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userID := utils.GetUserID(c)
|
||||||
|
err = userService.SetUserAuthority(userID, sua.AuthorityId)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
claims := utils.GetUserInfo(c)
|
||||||
|
claims.AuthorityId = sua.AuthorityId
|
||||||
|
token, err := utils.NewJWT().CreateToken(*claims)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.Header("new-token", token)
|
||||||
|
c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
|
||||||
|
utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||||
|
response.OkWithMessage("修改成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserAuthorities
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 设置用户权限
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body systemReq.SetUserAuthorities true "用户UUID, 角色ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "设置用户权限"
|
||||||
|
// @Router /user/setUserAuthorities [post]
|
||||||
|
func (b *BaseApi) SetUserAuthorities(c *gin.Context) {
|
||||||
|
var sua systemReq.SetUserAuthorities
|
||||||
|
err := c.ShouldBindJSON(&sua)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
authorityID := utils.GetUserAuthorityId(c)
|
||||||
|
err = userService.SetUserAuthorities(authorityID, sua.ID, sua.AuthorityIds)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("修改失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("修改失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("修改成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteUser
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 删除用户
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body request.GetById true "用户ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "删除用户"
|
||||||
|
// @Router /user/deleteUser [delete]
|
||||||
|
func (b *BaseApi) DeleteUser(c *gin.Context) {
|
||||||
|
var reqId request.GetById
|
||||||
|
err := c.ShouldBindJSON(&reqId)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(reqId, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
jwtId := utils.GetUserID(c)
|
||||||
|
if jwtId == uint(reqId.ID) {
|
||||||
|
response.FailWithMessage("删除失败, 无法删除自己。", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = userService.DeleteUser(reqId.ID)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("删除失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("删除失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("删除成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserInfo
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 设置用户信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysUser true "ID, 用户名, 昵称, 头像链接"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户信息"
|
||||||
|
// @Router /user/setUserInfo [put]
|
||||||
|
func (b *BaseApi) SetUserInfo(c *gin.Context) {
|
||||||
|
var user systemReq.ChangeUserInfo
|
||||||
|
err := c.ShouldBindJSON(&user)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = utils.Verify(user, utils.IdVerify)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(user.AuthorityIds) != 0 {
|
||||||
|
authorityID := utils.GetUserAuthorityId(c)
|
||||||
|
err = userService.SetUserAuthorities(authorityID, user.ID, user.AuthorityIds)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = userService.SetUserInfo(system.SysUser{
|
||||||
|
GVA_MODEL: global.GVA_MODEL{
|
||||||
|
ID: user.ID,
|
||||||
|
},
|
||||||
|
NickName: user.NickName,
|
||||||
|
HeaderImg: user.HeaderImg,
|
||||||
|
Phone: user.Phone,
|
||||||
|
Email: user.Email,
|
||||||
|
Enable: user.Enable,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("设置成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSelfInfo
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 设置用户信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysUser true "ID, 用户名, 昵称, 头像链接"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户信息"
|
||||||
|
// @Router /user/SetSelfInfo [put]
|
||||||
|
func (b *BaseApi) SetSelfInfo(c *gin.Context) {
|
||||||
|
var user systemReq.ChangeUserInfo
|
||||||
|
err := c.ShouldBindJSON(&user)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
user.ID = utils.GetUserID(c)
|
||||||
|
err = userService.SetSelfInfo(system.SysUser{
|
||||||
|
GVA_MODEL: global.GVA_MODEL{
|
||||||
|
ID: user.ID,
|
||||||
|
},
|
||||||
|
NickName: user.NickName,
|
||||||
|
HeaderImg: user.HeaderImg,
|
||||||
|
Phone: user.Phone,
|
||||||
|
Email: user.Email,
|
||||||
|
Enable: user.Enable,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("设置成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSelfSetting
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 设置用户配置
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body map[string]interface{} true "用户配置数据"
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "设置用户配置"
|
||||||
|
// @Router /user/SetSelfSetting [put]
|
||||||
|
func (b *BaseApi) SetSelfSetting(c *gin.Context) {
|
||||||
|
var req common.JSONMap
|
||||||
|
err := c.ShouldBindJSON(&req)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userService.SetSelfSetting(req, utils.GetUserID(c))
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("设置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("设置失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("设置成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 获取用户信息
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @accept application/json
|
||||||
|
// @Produce application/json
|
||||||
|
// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取用户信息"
|
||||||
|
// @Router /user/getUserInfo [get]
|
||||||
|
func (b *BaseApi) GetUserInfo(c *gin.Context) {
|
||||||
|
uuid := utils.GetUserUuid(c)
|
||||||
|
ReqUser, err := userService.GetUserInfo(uuid)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("获取失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("获取失败", c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithDetailed(gin.H{"userInfo": ReqUser}, "获取成功", c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPassword
|
||||||
|
// @Tags SysUser
|
||||||
|
// @Summary 重置用户密码
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Produce application/json
|
||||||
|
// @Param data body system.SysUser true "ID"
|
||||||
|
// @Success 200 {object} response.Response{msg=string} "重置用户密码"
|
||||||
|
// @Router /user/resetPassword [post]
|
||||||
|
func (b *BaseApi) ResetPassword(c *gin.Context) {
|
||||||
|
var rps systemReq.ResetPassword
|
||||||
|
err := c.ShouldBindJSON(&rps)
|
||||||
|
if err != nil {
|
||||||
|
response.FailWithMessage(err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = userService.ResetPassword(rps.ID, rps.Password)
|
||||||
|
if err != nil {
|
||||||
|
global.GVA_LOG.Error("重置失败!", zap.Error(err))
|
||||||
|
response.FailWithMessage("重置失败"+err.Error(), c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response.OkWithMessage("重置成功", c)
|
||||||
|
}
|
||||||
486
server/api/v1/system/sys_version.go
Normal file
486
server/api/v1/system/sys_version.go
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/common/response"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
systemReq "git.echol.cn/loser/st/server/model/system/request"
|
||||||
|
systemRes "git.echol.cn/loser/st/server/model/system/response"
|
||||||
|
"git.echol.cn/loser/st/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)
|
||||||
|
}
|
||||||
285
server/config.docker.yaml
Normal file
285
server/config.docker.yaml
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
# git.echol.cn/loser/st/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: "[git.echol.cn/loser/st/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: 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: ''
|
||||||
|
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: 'git.echol.cn/loser/st/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
|
||||||
|
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: git.echol.cn/loser/st/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: git.echol.cn/loser/st/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
|
||||||
|
|
||||||
|
# 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 # 布尔值
|
||||||
|
mcp:
|
||||||
|
name: GVA_MCP
|
||||||
|
version: v1.0.0
|
||||||
|
sse_path: /sse
|
||||||
|
message_path: /message
|
||||||
|
url_prefix: ''
|
||||||
|
addr: 8889
|
||||||
|
separate: false
|
||||||
251
server/config.yaml
Normal file
251
server/config.yaml
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
aliyun-oss:
|
||||||
|
endpoint: yourEndpoint
|
||||||
|
access-key-id: yourAccessKeyId
|
||||||
|
access-key-secret: yourAccessKeySecret
|
||||||
|
bucket-name: yourBucketName
|
||||||
|
bucket-url: yourBucketUrl
|
||||||
|
base-path: yourBasePath
|
||||||
|
autocode:
|
||||||
|
web: web/src
|
||||||
|
root: /Users/en/GolandProjects/st
|
||||||
|
server: server
|
||||||
|
module: git.echol.cn/loser/st/server
|
||||||
|
ai-path: ""
|
||||||
|
aws-s3:
|
||||||
|
bucket: xxxxx-10005608
|
||||||
|
region: ap-shanghai
|
||||||
|
endpoint: ""
|
||||||
|
secret-id: your-secret-id
|
||||||
|
secret-key: your-secret-key
|
||||||
|
base-url: https://gin.vue.admin
|
||||||
|
path-prefix: git.echol.cn/loser/st/server
|
||||||
|
s3-force-path-style: false
|
||||||
|
disable-ssl: false
|
||||||
|
captcha:
|
||||||
|
key-long: 4
|
||||||
|
img-width: 240
|
||||||
|
img-height: 80
|
||||||
|
open-captcha: 0
|
||||||
|
open-captcha-timeout: 3600
|
||||||
|
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
|
||||||
|
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
|
||||||
|
jwt:
|
||||||
|
signing-key: 53d59b59-dba8-4f83-886e-e5bd1bf3cbda
|
||||||
|
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: ""
|
||||||
|
addr: 8889
|
||||||
|
separate: false
|
||||||
|
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: st_dev
|
||||||
|
username: postgres
|
||||||
|
password: loser765911.
|
||||||
|
path: 149.88.74.188
|
||||||
|
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: "sys-cache"
|
||||||
|
addr: 219.152.55.29:6379
|
||||||
|
password: "THBA@6688"
|
||||||
|
db: 7
|
||||||
|
useCluster: false
|
||||||
|
clusterAddrs:
|
||||||
|
- 172.21.0.3:7000
|
||||||
|
- 172.21.0.4:7001
|
||||||
|
- 172.21.0.2:7002
|
||||||
|
redis-list:
|
||||||
|
- name: app-cache
|
||||||
|
addr: 219.152.55.29:6379
|
||||||
|
password: "THBA@6688"
|
||||||
|
db: 6
|
||||||
|
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: true
|
||||||
|
use-mongo: false
|
||||||
|
use-strict-auth: false
|
||||||
|
disable-auto-migrate: 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: git.echol.cn/loser/st/server
|
||||||
|
zap:
|
||||||
|
level: info
|
||||||
|
prefix: '[git.echol.cn/loser/st/server]'
|
||||||
|
format: console
|
||||||
|
director: log
|
||||||
|
encode-level: LowercaseColorLevelEncoder
|
||||||
|
stacktrace-key: stacktrace
|
||||||
|
show-line: true
|
||||||
|
log-in-console: true
|
||||||
|
retention-day: -1
|
||||||
22
server/config/auto_code.go
Normal file
22
server/config/auto_code.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Autocode struct {
|
||||||
|
Web string `mapstructure:"web" json:"web" yaml:"web"`
|
||||||
|
Root string `mapstructure:"root" json:"root" yaml:"root"`
|
||||||
|
Server string `mapstructure:"server" json:"server" yaml:"server"`
|
||||||
|
Module string `mapstructure:"module" json:"module" yaml:"module"`
|
||||||
|
AiPath string `mapstructure:"ai-path" json:"ai-path" yaml:"ai-path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Autocode) WebRoot() string {
|
||||||
|
webs := strings.Split(a.Web, "/")
|
||||||
|
if len(webs) == 0 {
|
||||||
|
webs = strings.Split(a.Web, "\\")
|
||||||
|
}
|
||||||
|
return filepath.Join(webs...)
|
||||||
|
}
|
||||||
9
server/config/captcha.go
Normal file
9
server/config/captcha.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
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代表错误三次后出现验证码
|
||||||
|
OpenCaptchaTimeOut int `mapstructure:"open-captcha-timeout" json:"open-captcha-timeout" yaml:"open-captcha-timeout"` // 防爆破验证码超时时间,单位:s(秒)
|
||||||
|
}
|
||||||
40
server/config/config.go
Normal file
40
server/config/config.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
JWT JWT `mapstructure:"jwt" json:"jwt" yaml:"jwt"`
|
||||||
|
Zap Zap `mapstructure:"zap" json:"zap" yaml:"zap"`
|
||||||
|
Redis Redis `mapstructure:"redis" json:"redis" yaml:"redis"`
|
||||||
|
RedisList []Redis `mapstructure:"redis-list" json:"redis-list" yaml:"redis-list"`
|
||||||
|
Mongo Mongo `mapstructure:"mongo" json:"mongo" yaml:"mongo"`
|
||||||
|
Email Email `mapstructure:"email" json:"email" yaml:"email"`
|
||||||
|
System System `mapstructure:"system" json:"system" yaml:"system"`
|
||||||
|
Captcha Captcha `mapstructure:"captcha" json:"captcha" yaml:"captcha"`
|
||||||
|
// auto
|
||||||
|
AutoCode Autocode `mapstructure:"autocode" json:"autocode" yaml:"autocode"`
|
||||||
|
// gorm
|
||||||
|
Mysql Mysql `mapstructure:"mysql" json:"mysql" yaml:"mysql"`
|
||||||
|
Mssql Mssql `mapstructure:"mssql" json:"mssql" yaml:"mssql"`
|
||||||
|
Pgsql Pgsql `mapstructure:"pgsql" json:"pgsql" yaml:"pgsql"`
|
||||||
|
Oracle Oracle `mapstructure:"oracle" json:"oracle" yaml:"oracle"`
|
||||||
|
Sqlite Sqlite `mapstructure:"sqlite" json:"sqlite" yaml:"sqlite"`
|
||||||
|
DBList []SpecializedDB `mapstructure:"db-list" json:"db-list" yaml:"db-list"`
|
||||||
|
// oss
|
||||||
|
Local Local `mapstructure:"local" json:"local" yaml:"local"`
|
||||||
|
Qiniu Qiniu `mapstructure:"qiniu" json:"qiniu" yaml:"qiniu"`
|
||||||
|
AliyunOSS AliyunOSS `mapstructure:"aliyun-oss" json:"aliyun-oss" yaml:"aliyun-oss"`
|
||||||
|
HuaWeiObs HuaWeiObs `mapstructure:"hua-wei-obs" json:"hua-wei-obs" yaml:"hua-wei-obs"`
|
||||||
|
TencentCOS TencentCOS `mapstructure:"tencent-cos" json:"tencent-cos" yaml:"tencent-cos"`
|
||||||
|
AwsS3 AwsS3 `mapstructure:"aws-s3" json:"aws-s3" yaml:"aws-s3"`
|
||||||
|
CloudflareR2 CloudflareR2 `mapstructure:"cloudflare-r2" json:"cloudflare-r2" yaml:"cloudflare-r2"`
|
||||||
|
Minio Minio `mapstructure:"minio" json:"minio" yaml:"minio"`
|
||||||
|
|
||||||
|
Excel Excel `mapstructure:"excel" json:"excel" yaml:"excel"`
|
||||||
|
|
||||||
|
DiskList []DiskList `mapstructure:"disk-list" json:"disk-list" yaml:"disk-list"`
|
||||||
|
|
||||||
|
// 跨域配置
|
||||||
|
Cors CORS `mapstructure:"cors" json:"cors" yaml:"cors"`
|
||||||
|
|
||||||
|
// MCP配置
|
||||||
|
MCP MCP `mapstructure:"mcp" json:"mcp" yaml:"mcp"`
|
||||||
|
}
|
||||||
14
server/config/cors.go
Normal file
14
server/config/cors.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type CORS struct {
|
||||||
|
Mode string `mapstructure:"mode" json:"mode" yaml:"mode"`
|
||||||
|
Whitelist []CORSWhitelist `mapstructure:"whitelist" json:"whitelist" yaml:"whitelist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CORSWhitelist struct {
|
||||||
|
AllowOrigin string `mapstructure:"allow-origin" json:"allow-origin" yaml:"allow-origin"`
|
||||||
|
AllowMethods string `mapstructure:"allow-methods" json:"allow-methods" yaml:"allow-methods"`
|
||||||
|
AllowHeaders string `mapstructure:"allow-headers" json:"allow-headers" yaml:"allow-headers"`
|
||||||
|
ExposeHeaders string `mapstructure:"expose-headers" json:"expose-headers" yaml:"expose-headers"`
|
||||||
|
AllowCredentials bool `mapstructure:"allow-credentials" json:"allow-credentials" yaml:"allow-credentials"`
|
||||||
|
}
|
||||||
53
server/config/db_list.go
Normal file
53
server/config/db_list.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DsnProvider interface {
|
||||||
|
Dsn() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Embeded 结构体可以压平到上一层,从而保持 config 文件的结构和原来一样
|
||||||
|
// 见 playground: https://go.dev/play/p/KIcuhqEoxmY
|
||||||
|
|
||||||
|
// GeneralDB 也被 Pgsql 和 Mysql 原样使用
|
||||||
|
type GeneralDB struct {
|
||||||
|
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 数据库前缀
|
||||||
|
Port string `mapstructure:"port" json:"port" yaml:"port"` // 数据库端口
|
||||||
|
Config string `mapstructure:"config" json:"config" yaml:"config"` // 高级配置
|
||||||
|
Dbname string `mapstructure:"db-name" json:"db-name" yaml:"db-name"` // 数据库名
|
||||||
|
Username string `mapstructure:"username" json:"username" yaml:"username"` // 数据库账号
|
||||||
|
Password string `mapstructure:"password" json:"password" yaml:"password"` // 数据库密码
|
||||||
|
Path string `mapstructure:"path" json:"path" yaml:"path"` // 数据库地址
|
||||||
|
Engine string `mapstructure:"engine" json:"engine" yaml:"engine" default:"InnoDB"` // 数据库引擎,默认InnoDB
|
||||||
|
LogMode string `mapstructure:"log-mode" json:"log-mode" yaml:"log-mode"` // 是否开启Gorm全局日志
|
||||||
|
MaxIdleConns int `mapstructure:"max-idle-conns" json:"max-idle-conns" yaml:"max-idle-conns"` // 空闲中的最大连接数
|
||||||
|
MaxOpenConns int `mapstructure:"max-open-conns" json:"max-open-conns" yaml:"max-open-conns"` // 打开到数据库的最大连接数
|
||||||
|
Singular bool `mapstructure:"singular" json:"singular" yaml:"singular"` // 是否开启全局禁用复数,true表示开启
|
||||||
|
LogZap bool `mapstructure:"log-zap" json:"log-zap" yaml:"log-zap"` // 是否通过zap写入日志文件
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c GeneralDB) LogLevel() logger.LogLevel {
|
||||||
|
switch strings.ToLower(c.LogMode) {
|
||||||
|
case "silent":
|
||||||
|
return logger.Silent
|
||||||
|
case "error":
|
||||||
|
return logger.Error
|
||||||
|
case "warn":
|
||||||
|
return logger.Warn
|
||||||
|
case "info":
|
||||||
|
return logger.Info
|
||||||
|
default:
|
||||||
|
return logger.Info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SpecializedDB struct {
|
||||||
|
Type string `mapstructure:"type" json:"type" yaml:"type"`
|
||||||
|
AliasName string `mapstructure:"alias-name" json:"alias-name" yaml:"alias-name"`
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
Disable bool `mapstructure:"disable" json:"disable" yaml:"disable"`
|
||||||
|
}
|
||||||
9
server/config/disk.go
Normal file
9
server/config/disk.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Disk struct {
|
||||||
|
MountPoint string `mapstructure:"mount-point" json:"mount-point" yaml:"mount-point"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiskList struct {
|
||||||
|
Disk `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
12
server/config/email.go
Normal file
12
server/config/email.go
Normal file
@@ -0,0 +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
|
||||||
|
IsLoginAuth bool `mapstructure:"is-loginauth" json:"is-loginauth" yaml:"is-loginauth"` // 是否LoginAuth 是否使用LoginAuth认证方式(适用于IBM、微软邮箱服务器等)
|
||||||
|
}
|
||||||
5
server/config/excel.go
Normal file
5
server/config/excel.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Excel struct {
|
||||||
|
Dir string `mapstructure:"dir" json:"dir" yaml:"dir"`
|
||||||
|
}
|
||||||
10
server/config/gorm_mssql.go
Normal file
10
server/config/gorm_mssql.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Mssql struct {
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dsn "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
|
||||||
|
func (m *Mssql) Dsn() string {
|
||||||
|
return "sqlserver://" + m.Username + ":" + m.Password + "@" + m.Path + ":" + m.Port + "?database=" + m.Dbname + "&encrypt=disable"
|
||||||
|
}
|
||||||
9
server/config/gorm_mysql.go
Normal file
9
server/config/gorm_mysql.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Mysql struct {
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mysql) Dsn() string {
|
||||||
|
return m.Username + ":" + m.Password + "@tcp(" + m.Path + ":" + m.Port + ")/" + m.Dbname + "?" + m.Config
|
||||||
|
}
|
||||||
18
server/config/gorm_oracle.go
Normal file
18
server/config/gorm_oracle.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Oracle struct {
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Oracle) Dsn() string {
|
||||||
|
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
|
||||||
|
|
||||||
|
}
|
||||||
17
server/config/gorm_pgsql.go
Normal file
17
server/config/gorm_pgsql.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Pgsql struct {
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dsn 基于配置文件获取 dsn
|
||||||
|
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||||
|
func (p *Pgsql) Dsn() string {
|
||||||
|
return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + p.Dbname + " port=" + p.Port + " " + p.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// LinkDsn 根据 dbname 生成 dsn
|
||||||
|
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||||
|
func (p *Pgsql) LinkDsn(dbname string) string {
|
||||||
|
return "host=" + p.Path + " user=" + p.Username + " password=" + p.Password + " dbname=" + dbname + " port=" + p.Port + " " + p.Config
|
||||||
|
}
|
||||||
13
server/config/gorm_sqlite.go
Normal file
13
server/config/gorm_sqlite.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sqlite struct {
|
||||||
|
GeneralDB `yaml:",inline" mapstructure:",squash"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Sqlite) Dsn() string {
|
||||||
|
return filepath.Join(s.Path, s.Dbname+".db")
|
||||||
|
}
|
||||||
8
server/config/jwt.go
Normal file
8
server/config/jwt.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type JWT struct {
|
||||||
|
SigningKey string `mapstructure:"signing-key" json:"signing-key" yaml:"signing-key"` // jwt签名
|
||||||
|
ExpiresTime string `mapstructure:"expires-time" json:"expires-time" yaml:"expires-time"` // 过期时间
|
||||||
|
BufferTime string `mapstructure:"buffer-time" json:"buffer-time" yaml:"buffer-time"` // 缓冲时间
|
||||||
|
Issuer string `mapstructure:"issuer" json:"issuer" yaml:"issuer"` // 签发者
|
||||||
|
}
|
||||||
11
server/config/mcp.go
Normal file
11
server/config/mcp.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type MCP struct {
|
||||||
|
Name string `mapstructure:"name" json:"name" yaml:"name"` // MCP名称
|
||||||
|
Version string `mapstructure:"version" json:"version" yaml:"version"` // MCP版本
|
||||||
|
SSEPath string `mapstructure:"sse_path" json:"sse_path" yaml:"sse_path"` // SSE路径
|
||||||
|
MessagePath string `mapstructure:"message_path" json:"message_path" yaml:"message_path"` // 消息路径
|
||||||
|
UrlPrefix string `mapstructure:"url_prefix" json:"url_prefix" yaml:"url_prefix"` // URL前缀
|
||||||
|
Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` // 独立MCP服务端口
|
||||||
|
Separate bool `mapstructure:"separate" json:"separate" yaml:"separate"` // 是否独立运行MCP服务
|
||||||
|
}
|
||||||
41
server/config/mongo.go
Normal file
41
server/config/mongo.go
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mongo struct {
|
||||||
|
Coll string `json:"coll" yaml:"coll" mapstructure:"coll"` // collection name
|
||||||
|
Options string `json:"options" yaml:"options" mapstructure:"options"` // mongodb options
|
||||||
|
Database string `json:"database" yaml:"database" mapstructure:"database"` // database name
|
||||||
|
Username string `json:"username" yaml:"username" mapstructure:"username"` // 用户名
|
||||||
|
Password string `json:"password" yaml:"password" mapstructure:"password"` // 密码
|
||||||
|
AuthSource string `json:"auth-source" yaml:"auth-source" mapstructure:"auth-source"` // 验证数据库
|
||||||
|
MinPoolSize uint64 `json:"min-pool-size" yaml:"min-pool-size" mapstructure:"min-pool-size"` // 最小连接池
|
||||||
|
MaxPoolSize uint64 `json:"max-pool-size" yaml:"max-pool-size" mapstructure:"max-pool-size"` // 最大连接池
|
||||||
|
SocketTimeoutMs int64 `json:"socket-timeout-ms" yaml:"socket-timeout-ms" mapstructure:"socket-timeout-ms"` // socket超时时间
|
||||||
|
ConnectTimeoutMs int64 `json:"connect-timeout-ms" yaml:"connect-timeout-ms" mapstructure:"connect-timeout-ms"` // 连接超时时间
|
||||||
|
IsZap bool `json:"is-zap" yaml:"is-zap" mapstructure:"is-zap"` // 是否开启zap日志
|
||||||
|
Hosts []*MongoHost `json:"hosts" yaml:"hosts" mapstructure:"hosts"` // 主机列表
|
||||||
|
}
|
||||||
|
|
||||||
|
type MongoHost struct {
|
||||||
|
Host string `json:"host" yaml:"host" mapstructure:"host"` // ip地址
|
||||||
|
Port string `json:"port" yaml:"port" mapstructure:"port"` // 端口
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uri .
|
||||||
|
func (x *Mongo) Uri() string {
|
||||||
|
length := len(x.Hosts)
|
||||||
|
hosts := make([]string, 0, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
if x.Hosts[i].Host != "" && x.Hosts[i].Port != "" {
|
||||||
|
hosts = append(hosts, x.Hosts[i].Host+":"+x.Hosts[i].Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x.Options != "" {
|
||||||
|
return fmt.Sprintf("mongodb://%s/%s?%s", strings.Join(hosts, ","), x.Database, x.Options)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("mongodb://%s/%s", strings.Join(hosts, ","), x.Database)
|
||||||
|
}
|
||||||
10
server/config/oss_aliyun.go
Normal file
10
server/config/oss_aliyun.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type AliyunOSS struct {
|
||||||
|
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
|
||||||
|
AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
|
||||||
|
AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"`
|
||||||
|
BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
|
||||||
|
BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"`
|
||||||
|
BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"`
|
||||||
|
}
|
||||||
13
server/config/oss_aws.go
Normal file
13
server/config/oss_aws.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type AwsS3 struct {
|
||||||
|
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
|
||||||
|
Region string `mapstructure:"region" json:"region" yaml:"region"`
|
||||||
|
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
|
||||||
|
SecretID string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"`
|
||||||
|
SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
|
||||||
|
BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
|
||||||
|
PathPrefix string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"`
|
||||||
|
S3ForcePathStyle bool `mapstructure:"s3-force-path-style" json:"s3-force-path-style" yaml:"s3-force-path-style"`
|
||||||
|
DisableSSL bool `mapstructure:"disable-ssl" json:"disable-ssl" yaml:"disable-ssl"`
|
||||||
|
}
|
||||||
10
server/config/oss_cloudflare.go
Normal file
10
server/config/oss_cloudflare.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type CloudflareR2 struct {
|
||||||
|
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
|
||||||
|
BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
|
||||||
|
Path string `mapstructure:"path" json:"path" yaml:"path"`
|
||||||
|
AccountID string `mapstructure:"account-id" json:"account-id" yaml:"account-id"`
|
||||||
|
AccessKeyID string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
|
||||||
|
SecretAccessKey string `mapstructure:"secret-access-key" json:"secret-access-key" yaml:"secret-access-key"`
|
||||||
|
}
|
||||||
9
server/config/oss_huawei.go
Normal file
9
server/config/oss_huawei.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type HuaWeiObs struct {
|
||||||
|
Path string `mapstructure:"path" json:"path" yaml:"path"`
|
||||||
|
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
|
||||||
|
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
|
||||||
|
AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"`
|
||||||
|
SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
|
||||||
|
}
|
||||||
6
server/config/oss_local.go
Normal file
6
server/config/oss_local.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Local struct {
|
||||||
|
Path string `mapstructure:"path" json:"path" yaml:"path"` // 本地文件访问路径
|
||||||
|
StorePath string `mapstructure:"store-path" json:"store-path" yaml:"store-path"` // 本地文件存储路径
|
||||||
|
}
|
||||||
11
server/config/oss_minio.go
Normal file
11
server/config/oss_minio.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Minio struct {
|
||||||
|
Endpoint string `mapstructure:"endpoint" json:"endpoint" yaml:"endpoint"`
|
||||||
|
AccessKeyId string `mapstructure:"access-key-id" json:"access-key-id" yaml:"access-key-id"`
|
||||||
|
AccessKeySecret string `mapstructure:"access-key-secret" json:"access-key-secret" yaml:"access-key-secret"`
|
||||||
|
BucketName string `mapstructure:"bucket-name" json:"bucket-name" yaml:"bucket-name"`
|
||||||
|
UseSSL bool `mapstructure:"use-ssl" json:"use-ssl" yaml:"use-ssl"`
|
||||||
|
BasePath string `mapstructure:"base-path" json:"base-path" yaml:"base-path"`
|
||||||
|
BucketUrl string `mapstructure:"bucket-url" json:"bucket-url" yaml:"bucket-url"`
|
||||||
|
}
|
||||||
11
server/config/oss_qiniu.go
Normal file
11
server/config/oss_qiniu.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Qiniu struct {
|
||||||
|
Zone string `mapstructure:"zone" json:"zone" yaml:"zone"` // 存储区域
|
||||||
|
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"` // 空间名称
|
||||||
|
ImgPath string `mapstructure:"img-path" json:"img-path" yaml:"img-path"` // CDN加速域名
|
||||||
|
AccessKey string `mapstructure:"access-key" json:"access-key" yaml:"access-key"` // 秘钥AK
|
||||||
|
SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"` // 秘钥SK
|
||||||
|
UseHTTPS bool `mapstructure:"use-https" json:"use-https" yaml:"use-https"` // 是否使用https
|
||||||
|
UseCdnDomains bool `mapstructure:"use-cdn-domains" json:"use-cdn-domains" yaml:"use-cdn-domains"` // 上传是否使用CDN上传加速
|
||||||
|
}
|
||||||
10
server/config/oss_tencent.go
Normal file
10
server/config/oss_tencent.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type TencentCOS struct {
|
||||||
|
Bucket string `mapstructure:"bucket" json:"bucket" yaml:"bucket"`
|
||||||
|
Region string `mapstructure:"region" json:"region" yaml:"region"`
|
||||||
|
SecretID string `mapstructure:"secret-id" json:"secret-id" yaml:"secret-id"`
|
||||||
|
SecretKey string `mapstructure:"secret-key" json:"secret-key" yaml:"secret-key"`
|
||||||
|
BaseURL string `mapstructure:"base-url" json:"base-url" yaml:"base-url"`
|
||||||
|
PathPrefix string `mapstructure:"path-prefix" json:"path-prefix" yaml:"path-prefix"`
|
||||||
|
}
|
||||||
10
server/config/redis.go
Normal file
10
server/config/redis.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Redis struct {
|
||||||
|
Name string `mapstructure:"name" json:"name" yaml:"name"` // 代表当前实例的名字
|
||||||
|
Addr string `mapstructure:"addr" json:"addr" yaml:"addr"` // 服务器地址:端口
|
||||||
|
Password string `mapstructure:"password" json:"password" yaml:"password"` // 密码
|
||||||
|
DB int `mapstructure:"db" json:"db" yaml:"db"` // 单实例模式下redis的哪个数据库
|
||||||
|
UseCluster bool `mapstructure:"useCluster" json:"useCluster" yaml:"useCluster"` // 是否使用集群模式
|
||||||
|
ClusterAddrs []string `mapstructure:"clusterAddrs" json:"clusterAddrs" yaml:"clusterAddrs"` // 集群模式下的节点地址列表
|
||||||
|
}
|
||||||
15
server/config/system.go
Normal file
15
server/config/system.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type System struct {
|
||||||
|
DbType string `mapstructure:"db-type" json:"db-type" yaml:"db-type"` // 数据库类型:mysql(默认)|sqlite|sqlserver|postgresql
|
||||||
|
OssType string `mapstructure:"oss-type" json:"oss-type" yaml:"oss-type"` // Oss类型
|
||||||
|
RouterPrefix string `mapstructure:"router-prefix" json:"router-prefix" yaml:"router-prefix"`
|
||||||
|
Addr int `mapstructure:"addr" json:"addr" yaml:"addr"` // 端口值
|
||||||
|
LimitCountIP int `mapstructure:"iplimit-count" json:"iplimit-count" yaml:"iplimit-count"`
|
||||||
|
LimitTimeIP int `mapstructure:"iplimit-time" json:"iplimit-time" yaml:"iplimit-time"`
|
||||||
|
UseMultipoint bool `mapstructure:"use-multipoint" json:"use-multipoint" yaml:"use-multipoint"` // 多点登录拦截
|
||||||
|
UseRedis bool `mapstructure:"use-redis" json:"use-redis" yaml:"use-redis"` // 使用redis
|
||||||
|
UseMongo bool `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"` // 使用mongo
|
||||||
|
UseStrictAuth bool `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式
|
||||||
|
DisableAutoMigrate bool `mapstructure:"disable-auto-migrate" json:"disable-auto-migrate" yaml:"disable-auto-migrate"` // 自动迁移数据库表结构,生产环境建议设为false,手动迁移
|
||||||
|
}
|
||||||
71
server/config/zap.go
Normal file
71
server/config/zap.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Zap struct {
|
||||||
|
Level string `mapstructure:"level" json:"level" yaml:"level"` // 级别
|
||||||
|
Prefix string `mapstructure:"prefix" json:"prefix" yaml:"prefix"` // 日志前缀
|
||||||
|
Format string `mapstructure:"format" json:"format" yaml:"format"` // 输出
|
||||||
|
Director string `mapstructure:"director" json:"director" yaml:"director"` // 日志文件夹
|
||||||
|
EncodeLevel string `mapstructure:"encode-level" json:"encode-level" yaml:"encode-level"` // 编码级
|
||||||
|
StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktrace-key" yaml:"stacktrace-key"` // 栈名
|
||||||
|
ShowLine bool `mapstructure:"show-line" json:"show-line" yaml:"show-line"` // 显示行
|
||||||
|
LogInConsole bool `mapstructure:"log-in-console" json:"log-in-console" yaml:"log-in-console"` // 输出控制台
|
||||||
|
RetentionDay int `mapstructure:"retention-day" json:"retention-day" yaml:"retention-day"` // 日志保留天数
|
||||||
|
}
|
||||||
|
|
||||||
|
// Levels 根据字符串转化为 zapcore.Levels
|
||||||
|
func (c *Zap) Levels() []zapcore.Level {
|
||||||
|
levels := make([]zapcore.Level, 0, 7)
|
||||||
|
level, err := zapcore.ParseLevel(c.Level)
|
||||||
|
if err != nil {
|
||||||
|
level = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
for ; level <= zapcore.FatalLevel; level++ {
|
||||||
|
levels = append(levels, level)
|
||||||
|
}
|
||||||
|
return levels
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Zap) Encoder() zapcore.Encoder {
|
||||||
|
config := zapcore.EncoderConfig{
|
||||||
|
TimeKey: "time",
|
||||||
|
NameKey: "name",
|
||||||
|
LevelKey: "level",
|
||||||
|
CallerKey: "caller",
|
||||||
|
MessageKey: "message",
|
||||||
|
StacktraceKey: c.StacktraceKey,
|
||||||
|
LineEnding: zapcore.DefaultLineEnding,
|
||||||
|
EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
|
||||||
|
encoder.AppendString(c.Prefix + t.Format("2006-01-02 15:04:05.000"))
|
||||||
|
},
|
||||||
|
EncodeLevel: c.LevelEncoder(),
|
||||||
|
EncodeCaller: zapcore.FullCallerEncoder,
|
||||||
|
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||||
|
}
|
||||||
|
if c.Format == "json" {
|
||||||
|
return zapcore.NewJSONEncoder(config)
|
||||||
|
}
|
||||||
|
return zapcore.NewConsoleEncoder(config)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// LevelEncoder 根据 EncodeLevel 返回 zapcore.LevelEncoder
|
||||||
|
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||||
|
func (c *Zap) LevelEncoder() zapcore.LevelEncoder {
|
||||||
|
switch {
|
||||||
|
case c.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
|
||||||
|
return zapcore.LowercaseLevelEncoder
|
||||||
|
case c.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
|
||||||
|
return zapcore.LowercaseColorLevelEncoder
|
||||||
|
case c.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
|
||||||
|
return zapcore.CapitalLevelEncoder
|
||||||
|
case c.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
|
||||||
|
return zapcore.CapitalColorLevelEncoder
|
||||||
|
default:
|
||||||
|
return zapcore.LowercaseLevelEncoder
|
||||||
|
}
|
||||||
|
}
|
||||||
9
server/core/internal/constant.go
Normal file
9
server/core/internal/constant.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigEnv = "GVA_CONFIG"
|
||||||
|
ConfigDefaultFile = "config.yaml"
|
||||||
|
ConfigTestFile = "config.test.yaml"
|
||||||
|
ConfigDebugFile = "config.debug.yaml"
|
||||||
|
ConfigReleaseFile = "config.release.yaml"
|
||||||
|
)
|
||||||
125
server/core/internal/cutter.go
Normal file
125
server/core/internal/cutter.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cutter 实现 io.Writer 接口
|
||||||
|
// 用于日志切割, strings.Join([]string{director,layout, formats..., level+".log"}, os.PathSeparator)
|
||||||
|
type Cutter struct {
|
||||||
|
level string // 日志级别(debug, info, warn, error, dpanic, panic, fatal)
|
||||||
|
layout string // 时间格式 2006-01-02 15:04:05
|
||||||
|
formats []string // 自定义参数([]string{Director,"2006-01-02", "business"(此参数可不写), level+".log"}
|
||||||
|
director string // 日志文件夹
|
||||||
|
retentionDay int //日志保留天数
|
||||||
|
file *os.File // 文件句柄
|
||||||
|
mutex *sync.RWMutex // 读写锁
|
||||||
|
}
|
||||||
|
|
||||||
|
type CutterOption func(*Cutter)
|
||||||
|
|
||||||
|
// CutterWithLayout 时间格式
|
||||||
|
func CutterWithLayout(layout string) CutterOption {
|
||||||
|
return func(c *Cutter) {
|
||||||
|
c.layout = layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CutterWithFormats 格式化参数
|
||||||
|
func CutterWithFormats(format ...string) CutterOption {
|
||||||
|
return func(c *Cutter) {
|
||||||
|
if len(format) > 0 {
|
||||||
|
c.formats = format
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCutter(director string, level string, retentionDay int, options ...CutterOption) *Cutter {
|
||||||
|
rotate := &Cutter{
|
||||||
|
level: level,
|
||||||
|
director: director,
|
||||||
|
retentionDay: retentionDay,
|
||||||
|
mutex: new(sync.RWMutex),
|
||||||
|
}
|
||||||
|
for i := 0; i < len(options); i++ {
|
||||||
|
options[i](rotate)
|
||||||
|
}
|
||||||
|
return rotate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write satisfies the io.Writer interface. It writes to the
|
||||||
|
// appropriate file handle that is currently being used.
|
||||||
|
// If we have reached rotation time, the target file gets
|
||||||
|
// automatically rotated, and also purged if necessary.
|
||||||
|
func (c *Cutter) Write(bytes []byte) (n int, err error) {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer func() {
|
||||||
|
if c.file != nil {
|
||||||
|
_ = c.file.Close()
|
||||||
|
c.file = nil
|
||||||
|
}
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
length := len(c.formats)
|
||||||
|
values := make([]string, 0, 3+length)
|
||||||
|
values = append(values, c.director)
|
||||||
|
if c.layout != "" {
|
||||||
|
values = append(values, time.Now().Format(c.layout))
|
||||||
|
}
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
values = append(values, c.formats[i])
|
||||||
|
}
|
||||||
|
values = append(values, c.level+".log")
|
||||||
|
filename := filepath.Join(values...)
|
||||||
|
director := filepath.Dir(filename)
|
||||||
|
err = os.MkdirAll(director, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := removeNDaysFolders(c.director, c.retentionDay)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("清理过期日志失败", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return c.file.Write(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cutter) Sync() error {
|
||||||
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
|
if c.file != nil {
|
||||||
|
return c.file.Sync()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 增加日志目录文件清理 小于等于零的值默认忽略不再处理
|
||||||
|
func removeNDaysFolders(dir string, days int) error {
|
||||||
|
if days <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
cutoff := time.Now().AddDate(0, 0, -days)
|
||||||
|
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() && info.ModTime().Before(cutoff) && path != dir {
|
||||||
|
err = os.RemoveAll(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
133
server/core/internal/zap_core.go
Normal file
133
server/core/internal/zap_core.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/model/system"
|
||||||
|
"git.echol.cn/loser/st/server/service"
|
||||||
|
astutil "git.echol.cn/loser/st/server/utils/ast"
|
||||||
|
"git.echol.cn/loser/st/server/utils/stacktrace"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ZapCore struct {
|
||||||
|
level zapcore.Level
|
||||||
|
zapcore.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewZapCore(level zapcore.Level) *ZapCore {
|
||||||
|
entity := &ZapCore{level: level}
|
||||||
|
syncer := entity.WriteSyncer()
|
||||||
|
levelEnabler := zap.LevelEnablerFunc(func(l zapcore.Level) bool {
|
||||||
|
return l == level
|
||||||
|
})
|
||||||
|
entity.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, levelEnabler)
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) WriteSyncer(formats ...string) zapcore.WriteSyncer {
|
||||||
|
cutter := NewCutter(
|
||||||
|
global.GVA_CONFIG.Zap.Director,
|
||||||
|
z.level.String(),
|
||||||
|
global.GVA_CONFIG.Zap.RetentionDay,
|
||||||
|
CutterWithLayout(time.DateOnly),
|
||||||
|
CutterWithFormats(formats...),
|
||||||
|
)
|
||||||
|
if global.GVA_CONFIG.Zap.LogInConsole {
|
||||||
|
multiSyncer := zapcore.NewMultiWriteSyncer(os.Stdout, cutter)
|
||||||
|
return zapcore.AddSync(multiSyncer)
|
||||||
|
}
|
||||||
|
return zapcore.AddSync(cutter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) Enabled(level zapcore.Level) bool {
|
||||||
|
return z.level == level
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) With(fields []zapcore.Field) zapcore.Core {
|
||||||
|
return z.Core.With(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) Check(entry zapcore.Entry, check *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||||
|
if z.Enabled(entry.Level) {
|
||||||
|
return check.AddCore(entry, z)
|
||||||
|
}
|
||||||
|
return check
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||||
|
for i := 0; i < len(fields); i++ {
|
||||||
|
if fields[i].Key == "business" || fields[i].Key == "folder" || fields[i].Key == "directory" {
|
||||||
|
syncer := z.WriteSyncer(fields[i].String)
|
||||||
|
z.Core = zapcore.NewCore(global.GVA_CONFIG.Zap.Encoder(), syncer, z.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 先写入原日志目标
|
||||||
|
err := z.Core.Write(entry, fields)
|
||||||
|
|
||||||
|
// 捕捉 Error 及以上级别日志并入库,且可提取 zap.Error(err) 的错误内容
|
||||||
|
if entry.Level >= zapcore.ErrorLevel {
|
||||||
|
// 避免与 GORM zap 写入互相递归:跳过由 gorm logger writer 触发的日志
|
||||||
|
if strings.Contains(entry.Caller.File, "gorm_logger_writer.go") {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
form := "后端"
|
||||||
|
level := entry.Level.String()
|
||||||
|
// 生成基础信息
|
||||||
|
info := entry.Message
|
||||||
|
|
||||||
|
// 提取 zap.Error(err) 内容
|
||||||
|
var errStr string
|
||||||
|
for i := 0; i < len(fields); i++ {
|
||||||
|
f := fields[i]
|
||||||
|
if f.Type == zapcore.ErrorType || f.Key == "error" || f.Key == "err" {
|
||||||
|
if f.Interface != nil {
|
||||||
|
errStr = fmt.Sprintf("%v", f.Interface)
|
||||||
|
} else if f.String != "" {
|
||||||
|
errStr = f.String
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if errStr != "" {
|
||||||
|
info = fmt.Sprintf("%s | 错误: %s", info, errStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 附加来源与堆栈信息
|
||||||
|
if entry.Caller.File != "" {
|
||||||
|
info = fmt.Sprintf("%s \n 源文件:%s:%d", info, entry.Caller.File, entry.Caller.Line)
|
||||||
|
}
|
||||||
|
stack := entry.Stack
|
||||||
|
if stack != "" {
|
||||||
|
info = fmt.Sprintf("%s \n 调用栈:%s", info, stack)
|
||||||
|
// 解析最终业务调用方,并提取其方法源码
|
||||||
|
if frame, ok := stacktrace.FindFinalCaller(stack); ok {
|
||||||
|
fnName, fnSrc, sLine, eLine, exErr := astutil.ExtractFuncSourceByPosition(frame.File, frame.Line)
|
||||||
|
if exErr == nil {
|
||||||
|
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s lines %d-%d)\n----- 产生日志的方法代码如下 -----\n%s", info, frame.File, frame.Line, fnName, sLine, eLine, fnSrc)
|
||||||
|
} else {
|
||||||
|
info = fmt.Sprintf("%s \n 最终调用方法:%s:%d (%s) | extract_err=%v", info, frame.File, frame.Line, fnName, exErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用后台上下文,避免依赖 gin.Context
|
||||||
|
ctx := context.Background()
|
||||||
|
_ = service.ServiceGroupApp.SystemServiceGroup.SysErrorService.CreateSysError(ctx, &system.SysError{
|
||||||
|
Form: &form,
|
||||||
|
Info: &info,
|
||||||
|
Level: level,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *ZapCore) Sync() error {
|
||||||
|
return z.Core.Sync()
|
||||||
|
}
|
||||||
44
server/core/server.go
Normal file
44
server/core/server.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/initialize"
|
||||||
|
"git.echol.cn/loser/st/server/service/system"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RunServer() {
|
||||||
|
if global.GVA_CONFIG.System.UseRedis {
|
||||||
|
// 初始化redis服务
|
||||||
|
initialize.Redis()
|
||||||
|
if global.GVA_CONFIG.System.UseMultipoint {
|
||||||
|
initialize.RedisList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if global.GVA_CONFIG.System.UseMongo {
|
||||||
|
err := initialize.Mongo.Initialization()
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error(fmt.Sprintf("%+v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 从db加载jwt数据
|
||||||
|
if global.GVA_DB != nil {
|
||||||
|
system.LoadAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
Router := initialize.Routers()
|
||||||
|
|
||||||
|
address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr)
|
||||||
|
|
||||||
|
fmt.Printf(`
|
||||||
|
默认自动化文档地址:http://127.0.0.1%s/swagger/index.html
|
||||||
|
默认MCP SSE地址:http://127.0.0.1%s%s
|
||||||
|
默认MCP Message地址:http://127.0.0.1%s%s
|
||||||
|
默认前端文件运行地址:http://127.0.0.1:8080
|
||||||
|
`, address, address, global.GVA_CONFIG.MCP.SSEPath, address, global.GVA_CONFIG.MCP.MessagePath)
|
||||||
|
initServer(address, Router, 10*time.Minute, 10*time.Minute)
|
||||||
|
}
|
||||||
60
server/core/server_run.go
Normal file
60
server/core/server_run.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type server interface {
|
||||||
|
ListenAndServe() error
|
||||||
|
Shutdown(context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// initServer 启动服务并实现优雅关闭
|
||||||
|
func initServer(address string, router *gin.Engine, readTimeout, writeTimeout time.Duration) {
|
||||||
|
// 创建服务
|
||||||
|
srv := &http.Server{
|
||||||
|
Addr: address,
|
||||||
|
Handler: router,
|
||||||
|
ReadTimeout: readTimeout,
|
||||||
|
WriteTimeout: writeTimeout,
|
||||||
|
MaxHeaderBytes: 1 << 20,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在goroutine中启动服务
|
||||||
|
go func() {
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
|
fmt.Printf("listen: %s\n", err)
|
||||||
|
zap.L().Error("server启动失败", zap.Error(err))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 等待中断信号以优雅地关闭服务器
|
||||||
|
quit := make(chan os.Signal, 1)
|
||||||
|
// kill (无参数) 默认发送 syscall.SIGTERM
|
||||||
|
// kill -2 发送 syscall.SIGINT
|
||||||
|
// kill -9 发送 syscall.SIGKILL,但是无法被捕获,所以不需要添加
|
||||||
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-quit
|
||||||
|
zap.L().Info("关闭WEB服务...")
|
||||||
|
|
||||||
|
// 设置5秒的超时时间
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err := srv.Shutdown(ctx); err != nil {
|
||||||
|
zap.L().Fatal("WEB服务关闭异常", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
zap.L().Info("WEB服务已关闭")
|
||||||
|
}
|
||||||
76
server/core/viper.go
Normal file
76
server/core/viper.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/core/internal"
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Viper 配置
|
||||||
|
func Viper() *viper.Viper {
|
||||||
|
config := getConfigPath()
|
||||||
|
|
||||||
|
v := viper.New()
|
||||||
|
v.SetConfigFile(config)
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
err := v.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("fatal error config file: %w", err))
|
||||||
|
}
|
||||||
|
v.WatchConfig()
|
||||||
|
|
||||||
|
v.OnConfigChange(func(e fsnotify.Event) {
|
||||||
|
fmt.Println("config file changed:", e.Name)
|
||||||
|
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err = v.Unmarshal(&global.GVA_CONFIG); err != nil {
|
||||||
|
panic(fmt.Errorf("fatal error unmarshal config: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
// root 适配性 根据root位置去找到对应迁移位置,保证root路径有效
|
||||||
|
global.GVA_CONFIG.AutoCode.Root, _ = filepath.Abs("..")
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// getConfigPath 获取配置文件路径, 优先级: 命令行 > 环境变量 > 默认值
|
||||||
|
func getConfigPath() (config string) {
|
||||||
|
// `-c` flag parse
|
||||||
|
flag.StringVar(&config, "c", "", "choose config file.")
|
||||||
|
flag.Parse()
|
||||||
|
if config != "" { // 命令行参数不为空 将值赋值于config
|
||||||
|
fmt.Printf("您正在使用命令行的 '-c' 参数传递的值, config 的路径为 %s\n", config)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if env := os.Getenv(internal.ConfigEnv); env != "" { // 判断环境变量 GVA_CONFIG
|
||||||
|
config = env
|
||||||
|
fmt.Printf("您正在使用 %s 环境变量, config 的路径为 %s\n", internal.ConfigEnv, config)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch gin.Mode() { // 根据 gin 模式文件名
|
||||||
|
case gin.DebugMode:
|
||||||
|
config = internal.ConfigDebugFile
|
||||||
|
case gin.ReleaseMode:
|
||||||
|
config = internal.ConfigReleaseFile
|
||||||
|
case gin.TestMode:
|
||||||
|
config = internal.ConfigTestFile
|
||||||
|
}
|
||||||
|
fmt.Printf("您正在使用 gin 的 %s 模式运行, config 的路径为 %s\n", gin.Mode(), config)
|
||||||
|
|
||||||
|
_, err := os.Stat(config)
|
||||||
|
if err != nil || os.IsNotExist(err) {
|
||||||
|
config = internal.ConfigDefaultFile
|
||||||
|
fmt.Printf("配置文件路径不存在, 使用默认配置文件路径: %s\n", config)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
36
server/core/zap.go
Normal file
36
server/core/zap.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package core
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"git.echol.cn/loser/st/server/core/internal"
|
||||||
|
"git.echol.cn/loser/st/server/global"
|
||||||
|
"git.echol.cn/loser/st/server/utils"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zap 获取 zap.Logger
|
||||||
|
// Author [SliverHorn](https://github.com/SliverHorn)
|
||||||
|
func Zap() (logger *zap.Logger) {
|
||||||
|
if ok, _ := utils.PathExists(global.GVA_CONFIG.Zap.Director); !ok { // 判断是否有Director文件夹
|
||||||
|
fmt.Printf("create %v directory\n", global.GVA_CONFIG.Zap.Director)
|
||||||
|
_ = os.Mkdir(global.GVA_CONFIG.Zap.Director, os.ModePerm)
|
||||||
|
}
|
||||||
|
levels := global.GVA_CONFIG.Zap.Levels()
|
||||||
|
length := len(levels)
|
||||||
|
cores := make([]zapcore.Core, 0, length)
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
core := internal.NewZapCore(levels[i])
|
||||||
|
cores = append(cores, core)
|
||||||
|
}
|
||||||
|
// 构建基础 logger(错误级别的入库逻辑已在自定义 ZapCore 中处理)
|
||||||
|
logger = zap.New(zapcore.NewTee(cores...))
|
||||||
|
// 启用 Error 及以上级别的堆栈捕捉,确保 entry.Stack 可用
|
||||||
|
opts := []zap.Option{zap.AddStacktrace(zapcore.ErrorLevel)}
|
||||||
|
if global.GVA_CONFIG.Zap.ShowLine {
|
||||||
|
opts = append(opts, zap.AddCaller())
|
||||||
|
}
|
||||||
|
logger = logger.WithOptions(opts...)
|
||||||
|
return logger
|
||||||
|
}
|
||||||
9314
server/docs/docs.go
Normal file
9314
server/docs/docs.go
Normal file
File diff suppressed because it is too large
Load Diff
9286
server/docs/swagger.json
Normal file
9286
server/docs/swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
5677
server/docs/swagger.yaml
Normal file
5677
server/docs/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
68
server/global/global.go
Normal file
68
server/global/global.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package global
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/mark3labs/mcp-go/server"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/qiniu/qmgo"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/utils/timer"
|
||||||
|
"github.com/songzhibin97/gkit/cache/local_cache"
|
||||||
|
|
||||||
|
"golang.org/x/sync/singleflight"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"git.echol.cn/loser/st/server/config"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
GVA_DB *gorm.DB
|
||||||
|
GVA_DBList map[string]*gorm.DB
|
||||||
|
GVA_REDIS redis.UniversalClient
|
||||||
|
GVA_REDISList map[string]redis.UniversalClient
|
||||||
|
GVA_MONGO *qmgo.QmgoClient
|
||||||
|
GVA_CONFIG config.Server
|
||||||
|
GVA_VP *viper.Viper
|
||||||
|
// GVA_LOG *oplogging.Logger
|
||||||
|
GVA_LOG *zap.Logger
|
||||||
|
GVA_Timer timer.Timer = timer.NewTimerTask()
|
||||||
|
GVA_Concurrency_Control = &singleflight.Group{}
|
||||||
|
GVA_ROUTERS gin.RoutesInfo
|
||||||
|
GVA_ACTIVE_DBNAME *string
|
||||||
|
GVA_MCP_SERVER *server.MCPServer
|
||||||
|
BlackCache local_cache.Cache
|
||||||
|
lock sync.RWMutex
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGlobalDBByDBName 通过名称获取db list中的db
|
||||||
|
func GetGlobalDBByDBName(dbname string) *gorm.DB {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
return GVA_DBList[dbname]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGetGlobalDBByDBName 通过名称获取db 如果不存在则panic
|
||||||
|
func MustGetGlobalDBByDBName(dbname string) *gorm.DB {
|
||||||
|
lock.RLock()
|
||||||
|
defer lock.RUnlock()
|
||||||
|
db, ok := GVA_DBList[dbname]
|
||||||
|
if !ok || db == nil {
|
||||||
|
panic("db no init")
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRedis(name string) redis.UniversalClient {
|
||||||
|
redis, ok := GVA_REDISList[name]
|
||||||
|
if !ok || redis == nil {
|
||||||
|
panic(fmt.Sprintf("redis `%s` no init", name))
|
||||||
|
}
|
||||||
|
return redis
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user