mcpField
G
gin

gin

github.com/youlaitech開発6665 回インストール2 週間前に更新
リポジトリ

gin について

youlai-gin の規約に基づく、Gin バックエンド向け Go 開発ガイドラインのスキルです。設計、REST API、認証、エラー処理を扱います。

主な機能

  1. 01Handler・Service・Repository のレイヤー規約
  2. 02REST API のパスとレスポンス規約
  3. 03JWT 認証と権限制御
  4. 04統一されたエラー処理フロー
  5. 05命名・コメント・コードレビュー項目

活用シーン

  1. 01Gin プロジェクトでの REST API 実装
  2. 02GORM を使ったデータアクセス層の作成
  3. 03JWT ログインと権限チェックの追加
  4. 04プロジェクト構成やエラー処理のレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/youlaitech/youlai-skills --skill gin

インストール後、Claude Code で/ginを入力して読み込まれたか確認します。

詳細

Gin バックエンド開発規約

本規約は、現在の youlai-gin のコード(モジュール名 youlai-gin)を基準とし、Uber Go Style Guide および Go CodeReviewComments コミュニティの慣例を取り入れています。

適用条件

  • Gin プロジェクトの開発
  • REST API の実装
  • データアクセスでの GORM の使用
  • JWT 認証の実装
  • 権限制御の実装

Part 1: 技術スタック

レイヤー選定技術説明
実行環境Go 1.25+標準ライブラリが堅牢で、並行処理をネイティブにサポート
Web フレームワークGin高性能 HTTP ルーター
ORMGORM 2.0チェーン形式のクエリー
データベースMySQL 8.xInnoDB エンジン
キャッシュRedis 7.x (go-redis)分散キャッシュ/Token ストレージ
認証golang-jwt v5JWT の発行/検証
ログzap + lumberjack によるローテーション構造化ログ
API ドキュメントswaggo/swag (swag init -o api)OpenAPI を自動生成

Part 2: ディレクトリ構造

基本ディレクトリは Standard Go Project Layout を参考にします。internal/ は業務モジュールごとに分け、各モジュールには handler/model/service/router を配置します。

├── main.go                         # 程序入口(配置/日志/数据库/存储初始化 + 优雅关闭)
├── api/                            # Swagger 产物(swag init -o api 生成:docs.go/swagger.json/swagger.yaml)
├── internal/                       # 私有代码(Go 编译器强制不可被外部导入)
│   ├── auth/                       # 认证模块(账密/短信/扫码/小程序登录)
│   │   ├── handler/ model/ service/ router.go
│   ├── system/                     # 系统管理(按实体分子模块)
│   │   ├── user/ role/ menu/ dept/ dict/ config/ notice/ log/
│   │   │   └── handler/ model/ repository/ service/
│   │   └── router.go               # 模块装配(构造注入或函数注册)
│   ├── codegen/                    # 代码生成器
│   ├── file/                       # 文件模块
│   ├── message/                    # 消息模块(SSE 推送)
│   ├── common/                     # 公共基础设施(禁止依赖业务模块)
│   │   ├── auth/                   # JWT / Redis TokenManager + 认证/权限中间件
│   │   ├── config/                 # 配置加载(viper)
│   │   ├── context/                # gin 上下文取用户信息、操作人注入
│   │   ├── database/               # GORM 初始化 + 分页
│   │   ├── excel/ json/ logger/    # 导出、序列化、结构化日志
│   │   ├── permission/             # 权限服务 + 数据权限(datascope)
│   │   ├── redis/                  # Redis 客户端 + key 规范
│   │   ├── storage/                # 文件存储(local/s3/aliyun 工厂)
│   │   ├── utils/                  # 文件/密码/树/验证码工具(历史包,新代码不再往里堆)
│   │   ├── validator/              # 参数绑定与校验(BindJSON/BindQuery)
│   │   └── response.go             # 统一响应(包名 common,导入时别名 response)
│   ├── middleware/                 # 全局中间件(error_handler/operation_log/rate_limiter/requestid)
│   └── router/register.go          # 路由注册入口
├── pkg/                            # 公共库(可被外部导入,禁止依赖 internal/)
│   ├── constant/                   # 业务常量 + 错误码(阿里规范 "00000"/"A0400"…)
│   ├── enums/                      # 操作类型 / 日志模块枚举
│   ├── errs/                       # AppError 统一错误(Code/Msg/HTTPStatus/Err/Stack)
│   ├── gormx/                      # GORM 扩展(审计钩子、PatchMap)
│   ├── model/                      # BaseEntity/BaseQuery/PagedData/Option/KeyValue
│   └── types/                      # BigInt/FlexInt/LocalTime 序列化类型
├── configs/                        # dev/prod/test.yaml
├── docs/                           # 项目文档与图片(非 Swagger 产物)
├── docker/docker-compose.yml
├── sql/mysql/                      # 建库脚本
└── go.mod / go.sum

設計原則:

  • internal/ は外部からインポートできません(コンパイラーによる厳格な制約)。pkg/ は再利用できますが、internal/ を import してはいけません
  • internal/common/ はインフラストラクチャ層です。業務モジュール(internal/system など)を import してはいけません
  • 業務モジュールは Handler → Service → Repository のレイヤー構成とし、model には entity/form/query/vo を配置します
  • 目標構成(新規モジュールでは必須):構造体とコンストラクター注入を使用します。例:dept:
deptHandler.NewHandler(deptService.NewService(deptRepo.NewRepository(db))).RegisterRoutes(r)

user/role/config/notice/log などのパッケージレベル関数形式は過去の実装です。新しいコードでは踏襲しません。

レイヤーディレクトリ責務禁止事項
Handlerhandler/パラメーターのバインド、service の呼び出し、レスポンスの書き込み業務ロジックを記述しない。GORM に触れない
Modelmodel/entity/form/query/voロジックを持たない
Repositoryrepository/GORM クエリー業務判断を記述しない
Serviceservice/業務ロジック*gin.Context を受け取らない。第 1 引数は context.Context

Part 3: 命名規約

3.1 ファイル名
種類規約例
Handler{name}_handler.gouser_handler.go
Service{name}_service.gouser_service.go
Repository{name}_repo.gouser_repo.go
Entitymodel/ 配下に entity.gomodel/entity.go
フォーム/クエリー/ビューform.go / query.go / vo.go同上
3.2 命名原則
  • 簡潔にし、冗長さを避ける:パッケージ名がすでにコンテキストを示すため、型名にパッケージ名を重複させません
  • エクスポートする名前は大文字で始めます(GetByID)。エクスポートしない名前は小文字で始めます(parseToken)
  • context.Context は常に関数の第 1 引数にします
  • 禁止:pkg/model を common として別名指定する(誤解を招き、internal/common と混同するため)
  • 標準ライブラリと衝突するパッケージ名は避けます(過去の実装である internal/common/context は、インポート時に appContext を別名として使用します)
// ❌ 冗余:调用时变成 user.UserService
package user
type UserService struct{}

// ✅ 包名已提供语义
package user
type Service struct{}
3.3 メソッド名(新しいコードでは必須)
操作接頭辞例
単件取得GetGet(ctx, id)
一覧取得ListList(ctx, query)
ページネーション付き取得PagePage(ctx, query)
新規作成CreateCreate(ctx, form)
更新UpdateUpdate(ctx, form)
削除DeleteDelete(ctx, id)
ドロップダウン項目OptionsOptions(ctx)
存在確認ExistsExistsByCode(ctx, code)
  • メソッド名にエンティティ名を含めません。レシーバーがすでに dept.Repository であるため、GetDeptList ではなく List と記述します
  • 過去の実装である Save*(作成と更新を統合したもの)や GetXxxList は踏襲しません
3.4 変数名
種類規約例
変数camelCaseuserList
定数PascalCaseMaxSize
非公開変数小文字で開始internalState
ブール値Is/Has/Can を接頭辞にするisDeleted, hasPermission
3.5 import の並べ替え(goimports -local youlai-gin で必ず実行)
import (
    // 1. 标准库
    "context"
    "time"

    // 2. 第三方库
    "github.com/gin-gonic/gin"
    "gorm.io/gorm"

    // 3. 本项目内部
    "youlai-gin/internal/common/response"
    "youlai-gin/pkg/errs"
)

Part 4: RESTful API 規約

4.1 標準 CRUD パス(ユーザーを例にします)
操作メソッドパス
ページネーション付き一覧GET/api/v1/users(pageNum/pageSize/キーワードは query で渡します)
フォーム表示GET/api/v1/users/:userId/form
新規作成POST/api/v1/users
更新PUT/api/v1/users/:userId
削除(複数件可)DELETE/api/v1/users/:ids(カンマ区切り)
ステータス変更PATCH/api/v1/users/:userId/status
ドロップダウン項目GET/api/v1/users/options
インポート/エクスポートPOST/GET/api/v1/users/import / /api/v1/users/export
4.2 Handler のテンプレート
// Page 用户分页列表
// @Summary 用户分页列表
// @Tags 02.用户接口
// @Param query query model.UserQuery false "查询参数"
// @Success 200 {object} common.Result
// @Router /api/v1/users [get]
func (h *Handler) Page(c *gin.Context) {
    var query model.UserQuery
    if err := validator.BindQuery(c, &query); err != nil {
        c.Error(err)
        return
    }
    result, err := h.svc.Page(c.Request.Context(), &query)
    if err != nil {
        c.Error(err)
        return
    }
    response.OkPaged(c, result)
}

ポイント:バインドには validator.BindJSON/BindQuery を使用します(ラップ済みの *errs.AppError が返されます)。エラーが発生したら、必ず c.Error(err) + return を実行します。レスポンスには必ず response.Ok/OkPaged/OkMsg を使用します。


Part 5: レスポンス形式と例外処理

5.1 統一レスポンス(internal/common/response.go、パッケージ名 common)
type Result struct {
    Code string      `json:"code"` // 阿里错误码:00000 成功 / A0400 参数错 / B0001 系统错
    Msg  string      `json:"msg"`
    Data interface{} `json:"data"`
}
関数用途
Ok(c, data)成功 + データ
OkPage(c, list, pageNum, pageSize, total) / OkPaged(c, *model.PagedData)ページネーション
OkMsg(c, msg)成功 + メッセージのみ
FromAppError(c, ae)AppError に従って出力(実際の HTTP ステータスコードを使用)。エラーミドルウェア専用
5.2 错误流:c.Error + 全局中间件
// pkg/errs
type AppError struct {
    Code       string `json:"code"`
    Msg        string `json:"msg"`
    HTTPStatus int    `json:"-"`
    Err        error  `json:"-"`  // 仅日志,不返回客户端
    Stack      string `json:"-"`
}
  • Handler 只写 c.Error(errs.BadRequest("具体信息")) / 透传 service 返回的 error
  • middleware.ErrorHandler() 统一 FromAppError 输出;类型判断用 errors.As,不用裸断言
  • 禁止 handler 直接调用 response.Fail/BadRequest(遗留函数,待删除)
  • 错误只在顶层 log 一次,中间层 fmt.Errorf("...: %w", err) 包装

Part 6: 实体规范

// pkg/model.BaseEntity:公共审计字段 + 手动软删除
type BaseEntity struct {
    CreateBy   *types.BigInt   `gorm:"column:create_by" json:"createBy,omitempty"`
    CreateTime types.LocalTime `gorm:"column:create_time;autoCreateTime" json:"createTime,omitempty"`
    UpdateBy   *types.BigInt   `gorm:"column:update_by" json:"updateBy,omitempty"`
    UpdateTime types.LocalTime `gorm:"column:update_time;autoUpdateTime" json:"updateTime,omitempty"`
    IsDeleted  int             `gorm:"column:is_deleted;default:0" json:"-"`
}

// 业务实体:主键用 types.BigInt(解决 JS 大数精度),显式 column 标签
type User struct {
    ID       types.BigInt `gorm:"primaryKey;autoIncrement" json:"id"`
    Username string       `gorm:"column:username;not null" json:"username"`
    Password string       `gorm:"column:password;not null" json:"-"`
    DeptID   types.BigInt `gorm:"column:dept_id" json:"deptId"`
    common.BaseEntity
}

func (User) TableName() string { return "sys_user" }
  • 软删除走 is_deleted 标志位(不是 gorm.DeletedAt),查询需带条件
  • create_by/update_by 由 gormx 审计钩子从 ctx 填充,同一张表只允许一个实体定义

Part 7: Service 规范

// Repository 接口定义在消费方(service 包),便于替换与测试
type Repository interface {
    Page(ctx context.Context, query *model.UserQuery) ([]model.User, int64, error)
    Create(ctx context.Context, user *model.User) error
}

type Service struct {
    repo Repository
}

func NewService(repo Repository) *Service { return &Service{repo: repo} }

func (s *Service) Create(ctx context.Context, form *model.UserForm) error {
    exists, err := s.repo.ExistsByUsername(ctx, form.Username)
    if err != nil {
        return err
    }
    if exists {
        return errs.BadRequest("用户名已存在")
    }
    user := &model.User{Username: form.Username, Password: utils.HashPassword(form.Password)}
    return s.repo.Create(ctx, user)
}
  • 禁止 service/repo 方法接收 *gin.Context;需要操作人时由 handler 传入 appContext.OperatorCtx(c)(已注入 operator,供审计钩子使用)
  • repo 方法统一 ctx 第一参数 + r.db.WithContext(ctx)
  • 业务失败返回 errs.*,不用 panic

Part 8: 认证规范

8.1 TokenManager 与中间件
// internal/common/auth:TokenManager 接口(JWT/Redis 双实现),由 config.Security 决定
tokenManager, _ := auth.CreateTokenManager(&config.Cfg.Security)
authorized.Use(pkgAuth.Middleware(tokenManager))     // 验签 + 写入用户上下文
r.GET("...", auth.RequirePermission("sys:user:list"), handler) // 权限点
8.2 权限标识格式
模块:实体:操作
sys:user:create / sys:user:update / sys:user:delete / sys:role:list
8.3 取当前用户
  • appContext.GetCurrentUserID(c) / GetCurrentUser(c):返回 (v, error),错误即 errs.Unauthorized,直接 c.Error
  • 需要写操作人:appContext.OperatorCtx(c) 转 context.Context 传给 service

Part 9: 注释规范

  • 导出符号必须有 godoc 注释(中文可,信号字保留英文)
  • Swagger 注解:@Summary / @Tags / @Param / @Success / @Router;Tags 用 01.认证接口 这类带序号分组
  • 注释写 Why 不写 What;不写"用于 XXX 流程"这类会腐烂的上下文
  • 生成物:swag init -o api(输出到 api/,勿手改)
// Page 分页查询用户列表。
//
// 根据关键词模糊搜索用户名/昵称/手机号,支持状态筛选。
//
// @Summary 用户分页列表
// @Tags 02.用户接口
// @Param query query model.UserQuery false "查询参数"
// @Success 200 {object} common.Result
// @Router /api/v1/users [get]
func (h *Handler) Page(c *gin.Context) { ... }

Part 10: 代码质量检查清单

  • 遵循 RESTful 路径规范(Part 4.1)
  • 错误走 c.Error + ErrorHandler,禁止 handler 直接 response.Fail
  • service/repo 不出现 *gin.Context,ctx 为第一参数
  • strconv.ParseInt/Atoi 必须检查错误
  • 文件上传校验大小和类型
  • 实体内嵌 pkg/model.BaseEntity,软删用 is_deleted
  • 新 API 有 Swaggo 注解,swag init -o api 重新生成
  • goimports -w -local youlai-gin + go vet 通过,CI 加 golangci-lint
  • import 三组有序(标准库 → 第三方 → 项目内)
  • 导出符号有 godoc 注释
  • 错误判断用 errors.Is/As,不裸类型断言
常见反模式
反模式正确做法
fmt.Printf("debug: %v", data)slog/zap 结构化日志
panic(err) 处理业务错误返回 errs.*,handler c.Error
strconv.ParseInt(s, 10, 64) 忽略 err必须检查错误
Context 塞进结构体字段Context 作为函数第一个参数
service 收 *gin.Contexthandler 传 OperatorCtx(c) / c.Request.Context()
层层 log.Error + return err只在顶层 log 一次,中间 %w 包装
HTTP 调用无超时context.WithTimeout(ctx, 30*time.Second)
堆栈返回给调用方错误信息脱敏,堆栈只写日志
新建 utils/common 杂物包按域命名(如 excel、storage),工具函数放进使用它的包
把 pkg/model 别名为 common直接用包名 model
同一张表多个实体定义实体唯一定义,跨层复用
悬空的 DI 图 / 无人调用的导出函数删掉或补全,死代码不过夜

コメント