更新 skill 后端开发

This commit is contained in:
zeaslity
2026-07-01 13:45:30 +08:00
parent d213a1146c
commit 9cd57b92b8
30 changed files with 1952 additions and 1643 deletions

View File

@@ -1,438 +0,0 @@
---
name: backend-go-gin-gorm
description: >
使用 Gin + GORM 生成、编写、修改、评审 production-ready 的 Go 后端代码Generate & Review Go backend code with Gin/GORM
强制分层架构 handler → service → dao/repository避免业务逻辑堆在 handlerDAO/Repo 只做数据访问与查询组装),并统一 API 响应包装
consistent response envelopecode/message/data + request_id/trace_id 等可观测字段)。接口风格默认推荐 POST + JSON RequestBody
as default必要时遵循 REST 语义与幂等约定),规范 DTO/VO/DO 命名与字段映射 conventions入参 DTO、出参 VO、持久化 DO/Model
代码注释使用中文Chinese comments for maintainability时间处理默认 Asia/Shanghaitime zone aware time handling
采用结构化日志 structured logging携带 request_id/trace_id/user_id/path/latency 等上下文),并遵循 Gin/GORM 工程化最佳实践
(transactions, context propagation, error wrapping, pagination, soft delete, optimistic locking when needed)。
触发场景 Trigger: Go 后端开发 / Gin Handler 创建 / GORM DAO/Repository 实现 / 代码走查与 Reviewrefactor suggestions, bug fixes, performance tips
argument-hint: "<动作 action> <目标 target>" 例如/ e.g.:
"create user-handler", "review service/order.go", "scaffold api/v1/product", "add repo for table/users", "optimize gorm query"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Go GIN/GORM 开发规范 Skill
## 触发条件
- 用户请求创建/修改 Go 后端代码
- 用户请求代码审查
- 用户提及 API 开发、数据库操作、统一响应、日志、时间处理
- 用户请求设计 API 接口、DTO 结构
## 上下文收集
执行前先收集项目信息:
!`ls -la go.mod go.sum 2>/dev/null || echo "No go.mod found"`
!`head -20 go.mod 2>/dev/null || echo ""`
## $ARGUMENTS 解析
期望格式:`<action> <target>`
| action | 说明 |
|--------|------|
| `create` | 创建新文件handler/service/dao/dto |
| `review` | 审查现有代码 |
| `scaffold` | 生成完整模块骨架 |
| `fix` | 修复不符合规范的代码 |
---
## Plan 阶段
### 产物清单(按 action 确定)
| action | 产物 |
|--------|------|
| `create handler` | `/api/xxx_handler.go``/internal/handler/xxx.go` |
| `create service` | `/internal/service/xxx_service.go` |
| `create dao` | `/internal/dao/xxx_dao.go` |
| `create dto` | `/internal/model/dto/xxx_dto.go` |
| `scaffold` | 上述全部 + entity |
### 决策点
1. **目录风格**:检查项目是用 `/api` 还是 `/internal/handler`
2. **模块命名**:从 $ARGUMENTS 提取资源名(如 `user``order`
3. **是否已存在**:先 Glob 检查目标文件
---
## Execute 阶段
### Handler 层编写规则
```
1. 仅做:参数解析 → 调用 service → 返回响应
2. 禁止:编写业务逻辑、直接操作数据库
3. 必须:使用 common.ResponseSuccess / common.ResponseError
4. 错误处理gorm.ErrRecordNotFound → CodeNotFound
```
### Service 层编写规则
```
1. 编排 dao 层完成业务
2. 记录关键业务日志Info 级别)
3. 错误包装fmt.Errorf("xxx: %w", err)
4. 业务异常记录 Warning 级别日志
```
### DAO 层编写规则
```
1. 封装所有 GORM 操作
2. 禁止在 service 层写 SQL
3. 复杂查询用 Raw/Exec
4. 善用链式调用,但复杂场景优先原生 SQL
```
### 统一响应格式(强制)
```go
// 成功
common.ResponseSuccess(c, data)
common.ResponseSuccessWithMessage(c, data, "创建成功")
// 失败
common.ResponseError(c, common.CodeParamError, "参数错误")
common.ResponseErrorWithDetail(c, common.CodeServerError, "系统错误", err)
```
错误码定义 → 读取 `reference/error-codes.go`
### 注释规范(强制中文)
```go
// GetUserByID 根据用户ID获取用户信息
// @param ctx context.Context - 请求上下文
// @param userID int64 - 用户唯一ID
// @return *model.User - 用户信息,未找到返回nil
// @return error - 查询错误
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*model.User, error)
```
---
## API 设计规范(强制)
### 核心原则POST + RequestBody
```
所有 API 优先使用 POST 方法,参数通过 RequestBody 传递
避免使用 PathVariables 和 RequestParams
```
### 禁止与推荐
| 禁止 | 推荐 |
|------|------|
| `GET /api/projects/{project_id}` | `POST /api/projects/detail` + RequestBody |
| `GET /api/users?role=admin&page=1` | `POST /api/users/list` + RequestBody |
| URL 中传递敏感信息 | RequestBody 传递所有参数 |
### API 路径命名规范
| 操作 | 后缀 | 示例 |
|------|------|------|
| 列表查询 | `/list` | `POST /api/projects/list` |
| 详情查询 | `/detail` | `POST /api/projects/detail` |
| 创建 | `/create` | `POST /api/projects/create` |
| 更新 | `/update` | `POST /api/projects/update` |
| 删除 | `/delete` | `POST /api/projects/delete` |
| 同步 | `/sync` | `POST /api/jenkins/organizations/sync` |
| 触发 | `/trigger` | `POST /api/builds/trigger` |
### DTO 命名规范
| 类型 | 命名格式 | 示例 |
|------|----------|------|
| 列表请求 | `List{资源}Request` | `ListBuildsRequest` |
| 详情请求 | `Get{资源}Request` | `GetBuildRequest` |
| 创建请求 | `Create{资源}Request` | `CreateProjectRequest` |
| 更新请求 | `Update{资源}Request` | `UpdateProjectRequest` |
| 删除请求 | `Delete{资源}Request` | `DeleteProjectRequest` |
| 列表响应 | `List{资源}Response` | `ListBuildsResponse` |
| 详情响应 | `{资源}DetailResponse` | `BuildDetailResponse` |
### 通用分页结构
```go
// 请求
type PageRequest struct {
Page int `json:"page" binding:"required,min=1"`
PageSize int `json:"page_size" binding:"required,min=1,max=100"`
}
// 响应
type ListResponse struct {
List []interface{} `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
```
### 模块错误码范围
| 范围 | 模块 |
|------|------|
| 0 | 成功 |
| 1000-1999 | 通用错误 |
| 2000-2999 | 用户/权限 |
| 3000-3999 | Jenkins |
| 4000-4999 | 项目管理 |
| 5000-5999 | Exchange-Hub |
详细规范 → 读取 `reference/api-design-spec.md`
---
## 日志规范(强制)
### 指定框架
项目统一使用 `rmdc-common/wdd_log/log_utils.go`
### 日志级别使用场景
| 级别 | 使用场景 | 示例 |
|------|----------|------|
| `Debug` | 开发调试,详细流程、变量值 | `log.Debug(ctx, "查询参数", map[string]interface{}{"userID": id})` |
| `Info` | 关键业务节点 | `log.Info(ctx, "用户登录成功", ...)` / `log.Info(ctx, "订单创建成功", ...)` |
| `Warning` | 可预期非致命异常,程序可继续 | `log.Warning(ctx, "外部API超时,启用备用方案", ...)` |
| `Error` | 严重错误,业务流程中断 | `log.Error(ctx, "数据库连接失败", ...)` 必须记录堆栈 |
### 日志内容要求
```
1. 简练、关键
2. 必须包含 TraceID、UserID 等追溯信息
3. Error 级别必须记录完整错误堆栈
```
### 日志记录位置
| 层级 | 记录内容 |
|------|----------|
| Handler | 使用 `ResponseErrorWithDetail` 自动记录 Error 日志 |
| Service | 关键业务操作记录 Info业务异常记录 Warning |
| DAO | 一般不记录日志,错误向上抛出 |
---
## 时间处理(强制东八区)
### 核心规则
```
时区Asia/Shanghai (UTC+8)
格式RFC3339
```
### 禁止与必须
| 禁止 | 必须使用 |
|------|----------|
| `time.Now()` | `TimeUtils.Now()` |
| `time.Parse()` | `TimeUtils.Parse()` |
| 直接格式化 | `TimeUtils.Format()` |
### 工具库位置
- 后端:`rmdc-common/utils/TimeUtils.go`
- 前端:`TonyMask/src/utils/timeUtils.ts`
### 使用示例
```go
// ✅ 正确
now := TimeUtils.Now()
timestamp := TimeUtils.Now().Format(time.RFC3339)
// ❌ 错误
now := time.Now() // 禁止直接使用
```
---
## 框架使用规范
### GIN 框架
#### 路由组织(强制分组)
```go
// ✅ 正确:使用路由分组
v1 := r.Group("/api/v1")
{
users := v1.Group("/users")
{
users.GET("/:id", userHandler.GetByID)
users.POST("/", userHandler.Create)
}
}
// ❌ 错误:扁平路由
r.GET("/api/v1/users/:id", ...)
```
#### 中间件使用
```go
// 全局中间件
r.Use(middleware.Recovery()) // 恢复
r.Use(middleware.Logger()) // 日志
r.Use(middleware.CORS()) // 跨域
// 路由组中间件
authGroup := r.Group("/admin")
authGroup.Use(middleware.Auth())
```
#### 响应规范
```
所有 API 响应必须通过 pkg/common 统一响应函数
禁止直接使用 c.JSON()、c.String() 等
```
### GORM 框架
#### 操作位置
```
所有 GORM 操作必须在 dao 层
严禁在 service 层拼接查询
```
#### 链式调用 vs 原生 SQL
| 场景 | 推荐方式 |
|------|----------|
| 简单 CRUD | 链式调用 `db.Where().First()` |
| 复杂查询(多表 JOIN、子查询 | `Raw()` / `Exec()` 原生 SQL |
| 批量操作 | `Raw()` / `Exec()` 保证性能 |
```go
// 简单查询 - 链式调用
db.Where("status = ?", 1).Find(&users)
// 复杂查询 - 原生 SQL
db.Raw(`
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = ?
GROUP BY u.id
`, 1).Scan(&results)
```
#### 错误处理
```go
// 必须处理 ErrRecordNotFound
if errors.Is(err, gorm.ErrRecordNotFound) {
common.ResponseError(c, common.CodeNotFound, "资源不存在")
return
}
```
---
## Verify 阶段 Checklist
### 结构检查
- [ ] 依赖方向正确handler → service → dao无反向引用
- [ ] handler 层无业务逻辑
- [ ] dao 层无 service 引用
- [ ] 使用 internal 包保护私有代码
### 响应检查
- [ ] 所有 API 使用 `common.ResponseSuccess/Error`
- [ ] 错误码来自 `common.Code*` 常量
- [ ] 时间戳格式为 RFC3339
- [ ] 无直接 `c.JSON()` 调用
### 代码检查
- [ ] 公开函数/结构体有中文注释
- [ ] 注释格式:`// 函数名 功能描述`
- [ ] 无直接 `time.Now()` 调用
- [ ] 无丢弃的 error`_ = err` 禁止)
- [ ] 包名小写无下划线
### 日志检查
- [ ] 使用项目统一日志库
- [ ] Error 日志包含完整堆栈
- [ ] 关键业务操作有 Info 日志
- [ ] 日志包含 TraceID 等追溯信息
### GORM 检查
- [ ] `gorm.ErrRecordNotFound` 已处理
- [ ] 复杂查询在 dao 层使用 Raw/Exec
- [ ] 无 service 层直接 DB 操作
### GIN 检查
- [ ] 使用路由分组组织 API
- [ ] 通用逻辑使用中间件处理
- [ ] 响应通过统一函数返回
### API 设计检查
- [ ] 使用 POST + RequestBody非 GET + PathVariables
- [ ] API 路径使用正确后缀(/list, /detail, /create 等)
- [ ] DTO 命名符合规范List/Get/Create/Update/Delete + 资源 + Request/Response
- [ ] 分页请求嵌入 PageRequest
- [ ] 分页响应包含 list/total/page/page_size
- [ ] 敏感信息不在 URL 中
- [ ] 请求体必须验证ShouldBindJSON
---
## 常见陷阱
| 陷阱 | 正确做法 |
|------|----------|
| handler 写业务逻辑 | 移到 service 层 |
| 直接 `c.JSON()` | 用 `common.ResponseSuccess()` |
| 忽略 `ErrRecordNotFound` | 转为 `CodeNotFound` 返回 |
| `time.Now()` | `TimeUtils.Now()` |
| 英文注释 | 改为中文 |
| dao 引用 service | 违反依赖原则,重构 |
| service 写 SQL | 移到 dao 层 |
| 扁平路由 | 使用 Router Group |
| 日志缺少上下文 | 添加 TraceID、UserID |
| Error 日志无堆栈 | 记录完整错误信息 |
| `GET /api/users/{id}` | `POST /api/users/detail` + RequestBody |
| URL 传参数 `?page=1` | RequestBody 传递 |
| DTO 命名不规范 | 使用 `List/Get/Create/Update/Delete` + 资源名 |
| 敏感信息在 URL | 移到 RequestBody |
---
## Reference 文件索引
| 场景 | 读取文件 |
|------|----------|
| 需要完整目录结构说明 | `reference/project-structure.md` |
| 需要响应结构体定义 | `reference/api-response-spec.md` |
| 需要错误码完整列表 | `reference/error-codes.go` |
| 需要编码规范细节 | `reference/coding-standards.md` |
| 需要日志使用详细说明 | `reference/logging-standards.md` |
| 需要时间处理详细说明 | `reference/time-handling.md` |
| 需要框架使用详细说明 | `reference/framework-usage.md` |
| 需要 API 设计详细说明 | `reference/api-design-spec.md` |
| 需要代码示例 | `examples/*.go` |
---
## 快速命令
验证项目结构:
```bash
./scripts/validate-structure.sh
```

View File

@@ -1,55 +0,0 @@
package dao
import (
"context"
"gorm.io/gorm"
"my-project/internal/model/entity"
)
// UserDAO 用户数据访问对象
type UserDAO struct {
db *gorm.DB
}
// NewUserDAO 创建用户DAO实例
// @param db *gorm.DB - 数据库连接
// @return *UserDAO - DAO实例
func NewUserDAO(db *gorm.DB) *UserDAO {
return &UserDAO{db: db}
}
// FindByID 根据ID查询用户
// @param ctx context.Context - 请求上下文
// @param id int64 - 用户ID
// @return *entity.User - 用户实体
// @return error - 查询错误,未找到返回gorm.ErrRecordNotFound
func (d *UserDAO) FindByID(ctx context.Context, id int64) (*entity.User, error) {
var user entity.User
if err := d.db.WithContext(ctx).First(&user, id).Error; err != nil {
return nil, err
}
return &user, nil
}
// Create 创建用户
// @param ctx context.Context - 请求上下文
// @param user *entity.User - 用户实体
// @return error - 创建错误
func (d *UserDAO) Create(ctx context.Context, user *entity.User) error {
return d.db.WithContext(ctx).Create(user).Error
}
// FindByEmail 根据邮箱查询用户
// @param ctx context.Context - 请求上下文
// @param email string - 用户邮箱
// @return *entity.User - 用户实体
// @return error - 查询错误
func (d *UserDAO) FindByEmail(ctx context.Context, email string) (*entity.User, error) {
var user entity.User
if err := d.db.WithContext(ctx).Where("email = ?", email).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
}

View File

@@ -1,70 +0,0 @@
package handler
import (
"errors"
"strconv"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"my-project/internal/model/dto"
"my-project/internal/service"
"my-project/pkg/common"
)
// UserHandler 用户相关API处理器
type UserHandler struct {
userService *service.UserService
}
// NewUserHandler 创建用户Handler实例
// @param userService *service.UserService - 用户服务
// @return *UserHandler - Handler实例
func NewUserHandler(userService *service.UserService) *UserHandler {
return &UserHandler{userService: userService}
}
// GetUserByID 根据ID获取用户信息
// @param c *gin.Context - GIN上下文
func (h *UserHandler) GetUserByID(c *gin.Context) {
// 1. 参数解析
idStr := c.Param("id")
userID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
common.ResponseError(c, common.CodeParamError, "用户ID格式错误")
return
}
// 2. 调用Service
user, err := h.userService.GetUserByID(c.Request.Context(), userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
common.ResponseError(c, common.CodeNotFound, "用户不存在")
return
}
common.ResponseErrorWithDetail(c, common.CodeServerError, "获取用户失败", err)
return
}
// 3. 成功响应
common.ResponseSuccess(c, user)
}
// CreateUser 创建用户
// @param c *gin.Context - GIN上下文
func (h *UserHandler) CreateUser(c *gin.Context) {
var req dto.CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ResponseErrorWithDetail(c, common.CodeValidationFail, "参数验证失败", err)
return
}
user, err := h.userService.CreateUser(c.Request.Context(), &req)
if err != nil {
common.ResponseErrorWithDetail(c, common.CodeBusiness, "创建用户失败", err)
return
}
common.ResponseSuccessWithMessage(c, user, "用户创建成功")
}

View File

@@ -1,60 +0,0 @@
package service
import (
"context"
"fmt"
"my-project/internal/dao"
"my-project/internal/model/dto"
"my-project/internal/model/entity"
"my-project/pkg/log"
)
// UserService 用户业务服务
type UserService struct {
userDAO *dao.UserDAO
}
// NewUserService 创建用户服务实例
// @param userDAO *dao.UserDAO - 用户数据访问对象
// @return *UserService - 服务实例
func NewUserService(userDAO *dao.UserDAO) *UserService {
return &UserService{userDAO: userDAO}
}
// GetUserByID 根据用户ID获取用户信息
// @param ctx context.Context - 请求上下文
// @param userID int64 - 用户唯一ID
// @return *entity.User - 用户实体
// @return error - 查询错误
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*entity.User, error) {
user, err := s.userDAO.FindByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("查询用户失败: %w", err)
}
return user, nil
}
// CreateUser 创建新用户
// @param ctx context.Context - 请求上下文
// @param req *dto.CreateUserRequest - 创建请求
// @return *entity.User - 创建的用户实体
// @return error - 创建错误
func (s *UserService) CreateUser(ctx context.Context, req *dto.CreateUserRequest) (*entity.User, error) {
user := &entity.User{
Username: req.Username,
Email: req.Email,
}
if err := s.userDAO.Create(ctx, user); err != nil {
return nil, fmt.Errorf("创建用户失败: %w", err)
}
// 记录关键业务日志
log.Info(ctx, "用户创建成功", map[string]interface{}{
"userID": user.ID,
"username": user.Username,
})
return user, nil
}

View File

@@ -1,332 +0,0 @@
# API 设计规范
## 核心原则
### 1. 使用 POST + RequestBody
> **核心规范**: 所有 API 优先使用 POST 方法,参数通过 RequestBody 传递
```go
// ✅ 推荐方式
POST /api/jenkins/builds/list
{
"organization_folder": "Backend",
"repository_name": "cmii-fly-center",
"branch_name": "master",
"page": 1,
"page_size": 10
}
// ❌ 避免使用
GET /api/jenkins/organizations/{org}/repositories/{repo}/branches/{branch}/builds?page=1&page_size=10
```
### 2. 避免 PathVariables
```go
// ❌ 不推荐
GET /api/projects/{project_id}
GET /api/builds/{build_id}/console
// ✅ 推荐
POST /api/projects/detail
{
"project_id": "namespace_abc12345"
}
POST /api/builds/console
{
"organization_folder": "Backend",
"repository_name": "cmii-fly-center",
"branch_name": "master",
"build_number": 123
}
```
### 3. 避免 RequestParams
```go
// ❌ 不推荐
GET /api/users/list?role=admin&status=active&page=1
// ✅ 推荐
POST /api/users/list
{
"role": "admin",
"status": "active",
"page": 1,
"page_size": 20
}
```
---
## 统一响应格式
### 成功响应
```json
{
"code": 0,
"message": "success",
"data": {
// 业务数据
}
}
```
### 分页响应
```json
{
"code": 0,
"message": "success",
"data": {
"list": [...],
"total": 100,
"page": 1,
"page_size": 20
}
}
```
### 错误响应
```json
{
"code": 1001,
"message": "参数错误: organization_folder不能为空",
"data": null
}
```
---
## 请求结构规范
### 通用分页请求
```go
type PageRequest struct {
Page int `json:"page" binding:"required,min=1"`
PageSize int `json:"page_size" binding:"required,min=1,max=100"`
}
```
### 通用筛选请求
```go
type ListRequest struct {
PageRequest
Keyword string `json:"keyword,omitempty"` // 搜索关键词
Status string `json:"status,omitempty"` // 状态筛选
SortBy string `json:"sort_by,omitempty"` // 排序字段
SortOrder string `json:"sort_order,omitempty"` // asc/desc
}
```
---
## API 命名规范
### 操作类型后缀
| 操作 | 后缀 | 示例 |
|------|------|------|
| 列表查询 | `/list` | `/api/projects/list` |
| 详情查询 | `/detail` | `/api/projects/detail` |
| 创建 | `/create` | `/api/projects/create` |
| 更新 | `/update` | `/api/projects/update` |
| 删除 | `/delete` | `/api/projects/delete` |
| 同步 | `/sync` | `/api/jenkins/organizations/sync` |
| 触发 | `/trigger` | `/api/builds/trigger` |
| 导出 | `/export` | `/api/projects/export` |
### 模块前缀
| 模块 | 前缀 |
|------|------|
| Jenkins | `/api/jenkins/` |
| 项目管理 | `/api/projects/` |
| 用户 | `/api/users/` |
| 权限 | `/api/permissions/` |
| 权限-Jenkins | `/api/permissions/jenkins/` |
| 权限-项目 | `/api/permissions/projects/` |
| 审计 | `/api/audit/` |
| Exchange-Hub | `/api/exchange-hub/` |
| DCU | `/api/dcu/` |
---
## Handler 实现模板
```go
// ListBuilds 获取构建列表
// @Summary 获取构建列表
// @Tags 构建管理
// @Accept json
// @Produce json
// @Param request body dto.ListBuildsRequest true "请求参数"
// @Success 200 {object} response.Response{data=dto.ListBuildsResponse}
// @Router /api/jenkins/builds/list [post]
func (h *BuildHandler) ListBuilds(c *gin.Context) {
var req dto.ListBuildsRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.ParamError(c, err)
return
}
resp, err := h.buildService.ListBuilds(c.Request.Context(), &req)
if err != nil {
response.Error(c, err)
return
}
response.Success(c, resp)
}
```
---
## DTO 设计规范
### 请求 DTO 命名
```go
// 列表请求: List{资源}Request
type ListBuildsRequest struct {
PageRequest
OrganizationFolder string `json:"organization_folder" binding:"required"`
RepositoryName string `json:"repository_name" binding:"required"`
BranchName string `json:"branch_name,omitempty"`
}
// 详情请求: Get{资源}Request 或 {资源}DetailRequest
type GetBuildRequest struct {
OrganizationFolder string `json:"organization_folder" binding:"required"`
RepositoryName string `json:"repository_name" binding:"required"`
BranchName string `json:"branch_name" binding:"required"`
BuildNumber int `json:"build_number" binding:"required"`
}
// 创建请求: Create{资源}Request
type CreateProjectRequest struct {
Name string `json:"name" binding:"required"`
Namespace string `json:"namespace" binding:"required"`
Province string `json:"province" binding:"required"`
City string `json:"city" binding:"required"`
}
// 更新请求: Update{资源}Request
type UpdateProjectRequest struct {
ProjectID string `json:"project_id" binding:"required"`
Name string `json:"name,omitempty"`
Province string `json:"province,omitempty"`
City string `json:"city,omitempty"`
}
// 删除请求: Delete{资源}Request
type DeleteProjectRequest struct {
ProjectID string `json:"project_id" binding:"required"`
}
```
### 响应 DTO 命名
```go
// 列表响应: List{资源}Response
type ListBuildsResponse struct {
List []*BuildDTO `json:"list"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// 详情响应: {资源}DetailResponse 或直接使用 {资源}DTO
type BuildDetailResponse struct {
*BuildDTO
ConsoleOutput string `json:"console_output,omitempty"`
}
```
---
## 错误码规范
### 错误码范围
| 范围 | 模块 |
|------|------|
| 1000-1999 | 通用错误 |
| 2000-2999 | 用户/权限 |
| 3000-3999 | Jenkins模块 |
| 4000-4999 | 项目管理 |
| 5000-5999 | Exchange-Hub |
| 6000-6999 | Watchdog |
### 通用错误码
| 错误码 | 说明 |
|--------|------|
| 0 | 成功 |
| 1001 | 参数错误 |
| 1002 | 未授权 |
| 1003 | 禁止访问 |
| 1004 | 资源不存在 |
| 1005 | 内部错误 |
---
## 前端调用示例
```typescript
// api/modules/jenkins.ts
export const jenkinsApi = {
// 获取构建列表
listBuilds: (data: ListBuildsRequest) =>
request.post<ListBuildsResponse>('/api/jenkins/builds/list', data),
// 触发构建
triggerBuild: (data: TriggerBuildRequest) =>
request.post<TriggerBuildResponse>('/api/jenkins/builds/trigger', data),
// 获取构建详情
getBuildDetail: (data: GetBuildRequest) =>
request.post<BuildDetailResponse>('/api/jenkins/builds/detail', data),
};
```
---
## 安全规范
### 1. 敏感字段不出现在 URL
```go
// ❌ 敏感信息泄露到URL
GET /api/auth/login?username=admin&password=123456
// ✅ 使用RequestBody
POST /api/auth/login
{
"username": "admin",
"password": "123456"
}
```
### 2. 必须验证请求体
```go
func (h *Handler) CreateProject(c *gin.Context) {
var req dto.CreateProjectRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.ParamError(c, err)
return
}
// 后续处理...
}
```
### 3. 审计敏感操作
所有写操作需通过审计中间件记录。

View File

@@ -1,35 +0,0 @@
# API 响应规范
## 统一响应结构
```go
type Response struct {
Code int `json:"code"` // 业务状态码0=成功
Status int `json:"status"` // HTTP 状态码
Timestamp string `json:"timestamp"` // RFC3339 东八区
Data interface{} `json:"data"` // 业务数据
Message string `json:"message,omitempty"` // 消息
Error string `json:"error,omitempty"` // 错误详情
}
```
## 使用函数
| 场景 | 函数 |
|------|------|
| 查询成功 | `ResponseSuccess(c, data)` |
| 操作成功 | `ResponseSuccessWithMessage(c, data, "msg")` |
| 普通错误 | `ResponseError(c, code, "msg")` |
| 详细错误 | `ResponseErrorWithDetail(c, code, "msg", err)` |
## HTTP 状态码映射
| 业务码 | HTTP 状态码 |
|--------|-------------|
| CodeSuccess | 200 |
| CodeParamError, CodeValidationFail | 400 |
| CodeUnauthorized | 401 |
| CodeForbidden | 403 |
| CodeNotFound | 404 |
| CodeTimeout | 408 |
| 其他 | 500 |

View File

@@ -1,44 +0,0 @@
# 编码规范
## 命名规范
| 类型 | 规则 | 示例 |
|------|------|------|
| 包名 | 小写单词,无下划线 | `service`, `utils` |
| 变量/函数 | 驼峰命名 | `getUserByID` |
| 公开标识 | 首字母大写 | `GetUserByID` |
| 接口 | 单方法以 `er` 结尾 | `Reader`, `Writer` |
## 注释规范(中文,必须)
```go
// GetUserByID 根据用户ID获取用户信息
// @param ctx context.Context - 请求上下文
// @param userID int64 - 用户唯一ID
// @return *model.User - 用户信息
// @return error - 查询错误
func (s *UserService) GetUserByID(ctx context.Context, userID int64) (*model.User, error)
```
## 错误处理
1. 必须 `if err != nil` 处理
2.`fmt.Errorf("xxx: %w", err)` 包装
3. 禁止 `_ = err` 丢弃错误
4. Handler 层必须通过统一响应返回
## 日志级别
| 级别 | 用途 |
|------|------|
| Debug | 开发调试,详细流程 |
| Info | 关键业务节点 |
| Warning | 可预期非致命异常 |
| Error | 严重错误,必须记录堆栈 |
## 时间处理
- 时区Asia/Shanghai (UTC+8)
- 格式RFC3339
- 禁止:`time.Now()`
- 使用:`TimeUtils.Now()`

View File

@@ -1,35 +0,0 @@
package common
// 业务状态码常量
const (
CodeSuccess = 0 // 成功
CodeServerError = 10001 // 服务器内部错误
CodeParamError = 10002 // 参数错误
CodeUnauthorized = 10003 // 未授权
CodeForbidden = 10004 // 禁止访问
CodeNotFound = 10005 // 资源不存在
CodeTimeout = 10006 // 请求超时
CodeValidationFail = 10007 // 验证失败
CodeBusiness = 20001 // 业务逻辑错误 (20001-29999)
)
// CodeMessage 错误码消息映射
var CodeMessage = map[int]string{
CodeSuccess: "success",
CodeServerError: "服务器内部错误",
CodeParamError: "参数错误",
CodeUnauthorized: "未授权,请先登录",
CodeForbidden: "权限不足,禁止访问",
CodeNotFound: "请求的资源不存在",
CodeTimeout: "请求超时",
CodeValidationFail: "数据验证失败",
CodeBusiness: "业务处理失败",
}
// GetMessage 根据错误码获取默认消息
func GetMessage(code int) string {
if msg, ok := CodeMessage[code]; ok {
return msg
}
return "未知错误"
}

View File

@@ -1,264 +0,0 @@
# 框架使用规范
## GIN 框架
### 路由组织
#### 强制使用路由分组 (Router Group)
```go
func SetupRouter(r *gin.Engine) {
// API 版本分组
v1 := r.Group("/api/v1")
{
// 用户模块
users := v1.Group("/users")
{
users.GET("/", userHandler.List)
users.GET("/:id", userHandler.GetByID)
users.POST("/", userHandler.Create)
users.PUT("/:id", userHandler.Update)
users.DELETE("/:id", userHandler.Delete)
}
// 订单模块
orders := v1.Group("/orders")
{
orders.GET("/", orderHandler.List)
orders.GET("/:id", orderHandler.GetByID)
orders.POST("/", orderHandler.Create)
}
}
}
```
#### 禁止扁平路由
```go
// ❌ 错误:扁平路由,难以维护
r.GET("/api/v1/users", ...)
r.GET("/api/v1/users/:id", ...)
r.POST("/api/v1/users", ...)
r.GET("/api/v1/orders", ...)
```
### 中间件使用
#### 全局中间件
```go
func SetupMiddleware(r *gin.Engine) {
// Recovery - 恢复 panic防止程序崩溃
r.Use(middleware.Recovery())
// Logger - 请求日志记录
r.Use(middleware.Logger())
// CORS - 跨域处理
r.Use(middleware.CORS())
// TraceID - 请求追踪
r.Use(middleware.TraceID())
}
```
#### 路由组中间件
```go
// 需要认证的路由组
authGroup := r.Group("/api/v1/admin")
authGroup.Use(middleware.Auth())
{
authGroup.GET("/dashboard", adminHandler.Dashboard)
authGroup.GET("/users", adminHandler.ListUsers)
}
// 需要特定权限的路由组
superAdmin := authGroup.Group("/super")
superAdmin.Use(middleware.RequireRole("super_admin"))
{
superAdmin.DELETE("/users/:id", adminHandler.DeleteUser)
}
```
#### 常用中间件职责
| 中间件 | 职责 |
|--------|------|
| Recovery | 捕获 panic返回 500 错误 |
| Logger | 记录请求日志(方法、路径、耗时等) |
| CORS | 处理跨域请求 |
| Auth | 验证用户身份JWT/Session |
| TraceID | 生成/传递请求追踪 ID |
| RateLimit | 请求频率限制 |
### 响应规范
#### 强制使用统一响应
```go
// ✅ 正确:使用统一响应函数
common.ResponseSuccess(c, data)
common.ResponseError(c, common.CodeParamError, "参数错误")
// ❌ 错误:直接使用 GIN 原生方法
c.JSON(200, data)
c.String(200, "success")
c.AbortWithStatusJSON(400, gin.H{"error": "bad request"})
```
---
## GORM 框架
### 操作位置规范
```
所有 GORM 操作必须在 dao 层实现
严禁在 service 层直接操作数据库
```
### 查询方式选择
#### 简单 CRUD - 链式调用
```go
// 单条查询
var user entity.User
db.Where("id = ?", userID).First(&user)
// 列表查询
var users []entity.User
db.Where("status = ?", 1).
Order("created_at DESC").
Limit(10).
Offset(0).
Find(&users)
// 创建
db.Create(&user)
// 更新
db.Model(&user).Updates(map[string]interface{}{
"name": "new name",
"status": 1,
})
// 删除
db.Delete(&user, userID)
```
#### 复杂查询 - Raw/Exec
**推荐场景**
- 多表 JOIN
- 子查询
- 复杂聚合
- 批量操作
- 性能敏感场景
```go
// 多表 JOIN 查询
type UserWithOrderCount struct {
entity.User
OrderCount int64 `json:"order_count"`
}
var results []UserWithOrderCount
db.Raw(`
SELECT u.*, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = ?
GROUP BY u.id
ORDER BY order_count DESC
LIMIT ?
`, 1, 10).Scan(&results)
// 批量更新
db.Exec(`
UPDATE orders
SET status = ?
WHERE user_id = ? AND status = ?
`, "completed", userID, "pending")
// 复杂子查询
db.Raw(`
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders
WHERE amount > ?
GROUP BY user_id
HAVING COUNT(*) > ?
)
`, 1000, 5).Scan(&users)
```
### 错误处理
#### 必须处理 ErrRecordNotFound
```go
// DAO 层
func (d *UserDAO) FindByID(ctx context.Context, id int64) (*entity.User, error) {
var user entity.User
if err := d.db.WithContext(ctx).First(&user, id).Error; err != nil {
return nil, err // 包含 ErrRecordNotFound
}
return &user, nil
}
// Handler 层
user, err := h.userService.GetUserByID(ctx, userID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
common.ResponseError(c, common.CodeNotFound, "用户不存在")
return
}
common.ResponseErrorWithDetail(c, common.CodeServerError, "查询失败", err)
return
}
```
### 事务处理
```go
// Service 层事务
func (s *OrderService) CreateOrder(ctx context.Context, req *dto.CreateOrderRequest) error {
return s.db.Transaction(func(tx *gorm.DB) error {
// 1. 创建订单
order := &entity.Order{...}
if err := tx.Create(order).Error; err != nil {
return fmt.Errorf("创建订单失败: %w", err)
}
// 2. 扣减库存
if err := tx.Model(&entity.Product{}).
Where("id = ? AND stock >= ?", req.ProductID, req.Quantity).
Update("stock", gorm.Expr("stock - ?", req.Quantity)).Error; err != nil {
return fmt.Errorf("扣减库存失败: %w", err)
}
// 3. 创建支付记录
payment := &entity.Payment{...}
if err := tx.Create(payment).Error; err != nil {
return fmt.Errorf("创建支付记录失败: %w", err)
}
return nil
})
}
```
### Context 传递
```go
// 必须使用 WithContext 传递上下文
db.WithContext(ctx).First(&user, id)
db.WithContext(ctx).Create(&order)
// 支持超时控制和取消
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
db.WithContext(ctx).Find(&users)
```

View File

@@ -1,100 +0,0 @@
# 日志规范
## 指定框架
项目统一使用内部日志库:`rmdc-common/wdd_log/log_utils.go`
## 日志级别定义
### Debug
- **用途**:开发调试,记录程序执行流程、变量值等详细信息
- **场景**:默认开发日志级别
- **示例**
```go
log.Debug(ctx, "开始处理用户请求", map[string]interface{}{
"userID": userID,
"requestID": requestID,
})
```
### Info
- **用途**:记录关键业务操作节点
- **场景**:用户登录、订单创建、支付成功等关键业务
- **示例**
```go
log.Info(ctx, "用户登录成功", map[string]interface{}{
"userID": user.ID,
"username": user.Username,
"ip": c.ClientIP(),
})
log.Info(ctx, "订单创建成功", map[string]interface{}{
"orderID": order.ID,
"amount": order.Amount,
"userID": order.UserID,
})
```
### Warning
- **用途**:记录可预期的、非致命的异常情况,程序仍可继续运行
- **场景**:外部 API 超时启用备用方案、配置缺失使用默认值等
- **示例**
```go
log.Warning(ctx, "外部API调用超时,已启用备用方案", map[string]interface{}{
"api": "payment-gateway",
"timeout": "5s",
"fallback": "local-cache",
})
```
### Error
- **用途**:记录严重错误,导致当前业务流程无法继续
- **场景**:数据库连接失败、关键参数校验失败等
- **要求**:必须详细记录错误信息和堆栈
- **示例**
```go
log.Error(ctx, "数据库连接失败", map[string]interface{}{
"host": dbConfig.Host,
"port": dbConfig.Port,
"error": err.Error(),
"stack": debug.Stack(),
})
```
## 日志内容规范
### 必须包含
1. **TraceID** - 请求追踪 ID
2. **UserID** - 用户标识(如适用)
3. **操作描述** - 简练的中文描述
4. **关键参数** - 与操作相关的关键数据
### 格式要求
```go
log.Info(ctx, "操作描述", map[string]interface{}{
"key1": value1,
"key2": value2,
})
```
## 各层日志职责
### Handler 层
- 使用 `ResponseErrorWithDetail` 自动记录 Error 日志
- 一般不主动记录日志
### Service 层
- **Info**:关键业务操作成功(创建订单、支付、用户注册等)
- **Warning**:业务逻辑异常但可处理
- **Error**:通过 ResponseErrorWithDetail 在 Handler 层统一记录
### DAO 层
- 一般不记录日志
- 错误向上抛出,由 Handler 层统一处理
## 禁止事项
1. 禁止在日志中记录敏感信息密码、Token、完整银行卡号等
2. 禁止使用 `fmt.Println``log.Println`
3. 禁止在循环中大量记录日志
4. Error 日志禁止缺少堆栈信息

View File

@@ -1,39 +0,0 @@
# 项目目录结构规范
## 核心目录
| 目录 | 职责 | 禁止事项 |
|------|------|----------|
| `/api``/internal/handler` | GIN Handler 层,解析请求、调用 service、返回响应 | 禁止写业务逻辑 |
| `/internal/service` | 业务逻辑核心,编排 dao 完成功能 | - |
| `/internal/dao``/internal/repository` | 数据访问层,封装 GORM 操作 | 禁止引用 service |
| `/internal/model/entity` | 数据库表结构对应的持久化对象 | - |
| `/internal/model/dto` | API 数据传输对象(请求/响应) | - |
| `/pkg/common` | 统一响应、错误码、公共工具 | - |
| `/configs` | 配置文件 | - |
| `/cmd` | main.go 入口 | - |
## 依赖规则
```
handler → service → dao
↓ ↓ ↓
pkg/common (任意层可引用)
```
**严禁反向或跨层依赖**
## go.mod 内部模块引用
```go
module my-project
go 1.24
require (
wdd.io/TonyCommon v1.0.0
)
// 本地开发使用 replace
replace wdd.io/TonyCommon => ../TonyCommon
```

View File

@@ -1,120 +0,0 @@
# 时间处理规范
## 核心原则
所有在前端和后端之间传输、以及在数据库中存储的时间,**必须统一为东八区时间 (Asia/Shanghai, UTC+8)**。
## 指定工具库
| 端 | 工具库路径 |
|----|-----------|
| 后端 | `rmdc-common/utils/TimeUtils.go` |
| 前端 | `TonyMask/src/utils/timeUtils.ts` |
## 时间格式
- API 响应中的 `timestamp` 字段统一使用 **RFC3339** 格式
- 示例:`2024-01-15T14:30:00+08:00`
## 禁止与必须
### 禁止直接使用
```go
// ❌ 禁止
time.Now()
time.Parse(layout, value)
t.Format(layout)
```
### 必须使用工具库
```go
// ✅ 正确
TimeUtils.Now()
TimeUtils.Parse(layout, value)
TimeUtils.Format(t, layout)
```
## 常用场景示例
### 获取当前时间
```go
// ❌ 错误
now := time.Now()
// ✅ 正确
now := TimeUtils.Now()
```
### 格式化时间戳
```go
// ❌ 错误
timestamp := time.Now().Format(time.RFC3339)
// ✅ 正确
timestamp := TimeUtils.Now().Format(time.RFC3339)
```
### 解析时间字符串
```go
// ❌ 错误
t, err := time.Parse(time.RFC3339, timeStr)
// ✅ 正确
t, err := TimeUtils.Parse(time.RFC3339, timeStr)
```
### 数据库时间字段
```go
type Order struct {
ID int64 `gorm:"primaryKey"`
CreatedAt time.Time `gorm:"autoCreateTime"` // GORM 自动处理
UpdatedAt time.Time `gorm:"autoUpdateTime"` // GORM 自动处理
ExpireAt time.Time // 业务时间使用 TimeUtils
}
// 设置业务时间
order.ExpireAt = TimeUtils.Now().Add(24 * time.Hour)
```
### API 响应时间
```go
type Response struct {
Code int `json:"code"`
Status int `json:"status"`
Timestamp string `json:"timestamp"` // RFC3339 格式
Data interface{} `json:"data"`
}
// 构建响应
resp := Response{
Timestamp: TimeUtils.Now().Format(time.RFC3339),
// ...
}
```
## TimeUtils 常用方法
| 方法 | 说明 |
|------|------|
| `Now()` | 获取当前东八区时间 |
| `Parse(layout, value)` | 解析时间字符串(东八区) |
| `Format(t, layout)` | 格式化时间 |
| `StartOfDay(t)` | 获取当天零点 |
| `EndOfDay(t)` | 获取当天 23:59:59 |
| `AddDays(t, days)` | 增加天数 |
## 时区配置
确保服务器和数据库时区配置正确:
```go
// 数据库连接配置
dsn := "user:pass@tcp(host:3306)/db?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
```

View File

@@ -1,51 +0,0 @@
#!/bin/bash
# 验证 Go GIN/GORM 项目结构
set -e
echo "=== Go 项目结构验证 ==="
# 检查 go.mod
if [ ! -f "go.mod" ]; then
echo "❌ 缺少 go.mod"
exit 1
fi
echo "✅ go.mod 存在"
# 检查核心目录
DIRS=("internal/service" "internal/dao" "internal/model" "pkg/common")
for dir in "${DIRS[@]}"; do
if [ -d "$dir" ]; then
echo "$dir 存在"
else
echo "⚠️ $dir 不存在"
fi
done
# 检查 handler 目录(两种风格)
if [ -d "api" ] || [ -d "internal/handler" ]; then
echo "✅ handler 目录存在"
else
echo "⚠️ 缺少 api/ 或 internal/handler/"
fi
# 检查反向依赖dao 不应引用 service
echo ""
echo "=== 检查依赖方向 ==="
if grep -r "internal/service" internal/dao/ 2>/dev/null; then
echo "❌ dao 层存在对 service 的反向依赖"
exit 1
fi
echo "✅ 无反向依赖"
# 检查 time.Now() 使用
echo ""
echo "=== 检查 time.Now() 使用 ==="
if grep -rn "time\.Now()" --include="*.go" internal/ api/ 2>/dev/null | grep -v "_test.go"; then
echo "⚠️ 发现直接使用 time.Now(),应使用 TimeUtils.Now()"
else
echo "✅ 无直接 time.Now() 调用"
fi
echo ""
echo "=== 验证完成 ==="