主要变更: - 采用 go-zero 框架替代 Gin,提升开发效率 - 重构项目结构,API 文件模块化组织 - 将 model 移至 api/internal/model 目录 - 移除 common 包,改为标准 pkg 目录结构 - 实现统一的仓储模式,支持配置驱动数据库切换 - 简化测试策略,专注 API 集成测试 - 更新 CLAUDE.md 文档,提供详细的开发指导 技术栈更新: - 框架: Gin → go-zero v1.6.0+ - 代码生成: 引入 goctl 工具 - 架构模式: 四层架构 → go-zero 三层架构 (Handler→Logic→Model) - 项目布局: 遵循 Go 社区标准和 go-zero 最佳实践
34 KiB
34 KiB
Backend API Service - CLAUDE.md
本文件为 Claude Code 在后端 API 服务模块中工作时提供指导。
🎯 模块概览
这是一个基于 Go + go-zero 框架的 REST API 后端服务,利用 go-zero 生态工具快速开发,采用 go-zero 推荐的项目架构。
主要特性
- 🏗️ go-zero 标准架构 (API → Logic → Model)
- 🚀 代码自动生成 (通过 .api 文件和 goctl 工具)
- 📊 多数据库支持 (PostgreSQL/SQLite 通过配置切换 + Redis)
- 🔐 JWT 认证 + 中间件系统
- 📁 文件上传和存储管理
- 🔗 链路追踪和监控
- 🛡️ 熔断、限流、负载均衡
- 📚 API 文档自动生成
技术栈
- 语言: Go 1.23+
- 框架: go-zero v1.6.0+
- 数据库: PostgreSQL/SQLite (通过配置切换) + Redis (缓存)
- ORM: GORM v1.30.0 (统一数据访问)
- 认证: JWT (内置 JWT 支持)
- 日志: go-zero 内置日志系统
- 配置: go-zero 配置系统
- 工具: goctl (代码生成工具)
- 容器化: Docker + Docker Compose
📁 简洁架构设计
go-zero 项目结构 (优化版)
backend/
├── CLAUDE.md # 📋 当前文件 - 后端总览
├── go.mod # Go 模块文件
├── go.sum # 依赖锁定文件
├── Makefile # 构建脚本
├── api/ # 🌐 API 服务模块
│ ├── CLAUDE.md # API 模块开发指导
│ ├── desc/ # 📝 API 定义文件目录
│ │ ├── photography.api # 📋 主 API 文件 (导入其他模块)
│ │ ├── user.api # 用户接口定义
│ │ ├── photo.api # 照片接口定义
│ │ ├── category.api # 分类接口定义
│ │ ├── auth.api # 认证接口定义
│ │ └── common.api # 公共类型定义
│ ├── etc/ # ⚙️ 配置文件
│ │ ├── photography-api.yaml # 主配置文件
│ │ ├── photography-dev.yaml # 开发环境配置
│ │ └── photography-prod.yaml # 生产环境配置
│ ├── internal/ # 📦 内部模块
│ │ ├── config/ # 配置结构
│ │ │ └── config.go # 配置定义
│ │ ├── handler/ # 🎯 处理器 (goctl 自动生成)
│ │ │ ├── user/ # 用户处理器
│ │ │ ├── photo/ # 照片处理器
│ │ │ ├── category/ # 分类处理器
│ │ │ └── auth/ # 认证处理器
│ │ ├── logic/ # 🧠 业务逻辑 (goctl 自动生成)
│ │ │ ├── user/ # 用户业务逻辑
│ │ │ ├── photo/ # 照片业务逻辑
│ │ │ ├── category/ # 分类业务逻辑
│ │ │ └── auth/ # 认证业务逻辑
│ │ ├── svc/ # 🔧 服务上下文
│ │ │ └── servicecontext.go # 服务上下文定义
│ │ ├── types/ # 📦 类型定义 (goctl 自动生成)
│ │ │ └── types.go # 请求/响应类型
│ │ ├── middleware/ # 🛡️ 中间件
│ │ │ ├── auth.go # 认证中间件
│ │ │ ├── cors.go # CORS 中间件
│ │ │ └── logger.go # 日志中间件
│ │ └── model/ # 📊 数据模型模块 (内部)
│ │ ├── CLAUDE.md # 数据模型开发指导
│ │ ├── sql/ # SQL 定义文件
│ │ │ ├── user.sql # 用户表结构
│ │ │ ├── photo.sql # 照片表结构
│ │ │ └── category.sql # 分类表结构
│ │ ├── user.go # 用户模型 (goctl 自动生成)
│ │ ├── photo.go # 照片模型 (goctl 自动生成)
│ │ ├── category.go # 分类模型 (goctl 自动生成)
│ │ └── vars.go # 模型变量定义
│ └── photography.go # 🚀 服务入口 (goctl 自动生成)
├── pkg/ # 📦 可导出包 (业务解耦)
│ ├── CLAUDE.md # 公共包开发指导
│ ├── errorx/ # 错误处理包
│ │ └── errorx.go # 统一错误定义
│ ├── response/ # 响应处理包
│ │ └── response.go # 统一响应格式
│ ├── utils/ # 通用工具包
│ │ ├── jwt/ # JWT 工具
│ │ │ └── jwt.go # JWT 实现
│ │ ├── hash/ # 哈希工具
│ │ │ └── hash.go # 哈希实现
│ │ ├── file/ # 文件处理工具
│ │ │ └── file.go # 文件处理实现
│ │ └── database/ # 数据库工具
│ │ └── database.go # 数据库连接工厂
│ └── constants/ # 常量定义包
│ └── constants.go # 全局常量定义
├── configs/ # 📋 配置文件目录
│ ├── sql/ # SQL 初始化文件
│ │ ├── init.sql # 数据库初始化
│ │ └── seed.sql # 种子数据
│ └── docker/ # Docker 相关配置
│ ├── Dockerfile # Docker 镜像定义
│ ├── docker-compose.yml # 本地开发环境
│ └── docker-compose.prod.yml # 生产环境配置
├── scripts/ # 🛠️ 脚本目录
│ ├── build.sh # 构建脚本
│ ├── deploy.sh # 部署脚本
│ └── gen-code.sh # 代码生成脚本
├── deploy/ # 🚀 部署配置
│ ├── k8s/ # Kubernetes 配置
│ └── systemd/ # Systemd 配置
└── tests/ # 🧪 测试模块 (简化)
├── CLAUDE.md # 测试开发指导
├── api_test.go # API 集成测试
└── benchmark_test.go # 性能测试
go-zero 三层架构
🎯 Handler 层 (api/internal/handler/)
- 职责: HTTP 请求处理、参数验证、响应封装
- 特点: 由 goctl 工具自动生成,专注于 HTTP 协议处理
- 文件: 按业务模块分组的处理器文件
🧠 Logic 层 (api/internal/logic/)
- 职责: 业务逻辑处理、数据处理、服务编排
- 特点: 由 goctl 工具生成框架,开发者填充业务逻辑
- 文件: 按业务模块分组的逻辑文件
📊 Model 层 (model/)
- 职责: 数据模型定义、数据库操作
- 特点: 由 goctl 工具从 SQL 文件自动生成
- 文件: 按数据表生成的模型文件
go-zero 设计原则
- 代码生成驱动: 通过 .api 文件和 goctl 工具自动生成代码
- 配置驱动: 所有配置统一管理,支持多环境热加载
- 中间件体系: 丰富的内置中间件,支持自定义扩展
- 错误处理: 统一的错误处理和响应格式
- 服务治理: 内置熔断、限流、负载均衡等微服务组件
- 链路追踪: 内置 OpenTelemetry 支持,便于问题排查
- 数据库无关: 统一的模型接口,支持多种数据库类型
🚀 快速开始
环境准备
# 1. 安装 Go 1.23+
go version
# 2. 安装 goctl 工具
go install github.com/zeromicro/go-zero/tools/goctl@latest
# 3. 验证安装
goctl --version
项目初始化
# 1. 创建项目目录
mkdir photography-backend && cd photography-backend
# 2. 初始化 Go 模块
go mod init photography-backend
# 3. 创建 API 定义文件
mkdir -p api/desc
快速开发流程
# 1. 定义 API 接口 (api/photography.api)
# 2. 生成 API 服务代码
goctl api go -api api/photography.api -dir api/
# 3. 定义数据库表结构 (model/*.sql)
# 4. 生成数据模型代码
goctl model mysql ddl -src model/user.sql -dir model/
# 5. 启动开发服务器
cd api && go run photography.go -f etc/photography-dev.yaml
开发模式
- 快速开发: 使用 SQLite 进行本地开发,无需额外数据库
- 生产模式: 使用 PostgreSQL + Redis,完整的生产环境配置
- 测试模式: 使用内存数据库,用于单元测试和集成测试
🔧 go-zero 开发规范
API 文件组织规范
主 API 文件 (api/desc/photography.api)
syntax = "v1"
info(
title: "Photography API"
desc: "摄影作品集 API 服务"
author: "Developer"
email: "dev@example.com"
version: "v1.0.0"
)
// 导入其他模块的 API 定义
import "common.api"
import "auth.api"
import "user.api"
import "photo.api"
import "category.api"
公共类型定义 (api/desc/common.api)
syntax = "v1"
// 公共响应结构
type BaseResponse {
Code int `json:"code"`
Message string `json:"message"`
}
type PageRequest {
Page int `form:"page,default=1"`
PageSize int `form:"page_size,default=10"`
}
type PageResponse {
Total int64 `json:"total"`
Page int `json:"page"`
Size int `json:"size"`
}
用户模块 API (api/desc/user.api)
syntax = "v1"
import "common.api"
// 用户管理
@server(
group: user
prefix: /api/v1/users
jwt: Auth
)
service photography-api {
@doc "获取用户列表"
@handler getUserList
get / (GetUserListReq) returns (GetUserListResp)
@doc "创建用户"
@handler createUser
post / (CreateUserReq) returns (CreateUserResp)
@doc "获取用户详情"
@handler getUser
get /:id (GetUserReq) returns (GetUserResp)
}
type GetUserListReq {
PageRequest
Keyword string `form:"keyword,optional"`
}
type GetUserListResp {
BaseResponse
Data UserListData `json:"data"`
}
type UserListData {
PageResponse
Users []User `json:"users"`
}
type User {
Id int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Avatar string `json:"avatar"`
Status int `json:"status"`
CreateAt int64 `json:"create_at"`
}
代码生成规范
- API 服务生成:
# 从主 API 文件生成代码 goctl api go -api api/desc/photography.api -dir api/ - 数据模型生成:
# 从 SQL DDL 生成模型到 internal/model goctl model mysql ddl -src api/internal/model/sql/user.sql -dir api/internal/model/ - 代码更新: 重新生成时保持手动修改的业务逻辑
- 文件组织: API 按模块分文件,model 放在 internal 目录
- 命名规范: 遵循 go-zero 的命名约定
业务逻辑开发规范
// Logic 层开发示例 (api/internal/logic/user/getuserlistlogic.go)
type GetUserListLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetUserListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserListLogic {
return &GetUserListLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetUserListLogic) GetUserList(req *types.GetUserListReq) (resp *types.GetUserListResp, err error) {
// 业务逻辑处理
users, total, err := l.svcCtx.UserModel.FindList(l.ctx, req.Page, req.PageSize, req.Keyword)
if err != nil {
return nil, errorx.NewDefaultError("获取用户列表失败")
}
return &types.GetUserListResp{
Code: 200,
Message: "success",
Data: types.UserList{
Total: total,
Users: users,
},
}, nil
}
数据模型开发规范
// api/internal/model/user.go (由 goctl 自动生成)
type User struct {
Id int64 `db:"id"`
Username string `db:"username"`
Email string `db:"email"`
Avatar string `db:"avatar"`
Status int64 `db:"status"`
CreateAt time.Time `db:"create_at"`
UpdateAt time.Time `db:"update_at"`
}
// 自定义方法 (在生成的模型中添加)
func (u *UserModel) FindList(ctx context.Context, page, pageSize int, keyword string) ([]*User, int64, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE 1=1", userRows, u.table)
args := []interface{}{}
if keyword != "" {
query += " AND (username LIKE ? OR email LIKE ?)"
args = append(args, "%"+keyword+"%", "%"+keyword+"%")
}
// 获取总数
countQuery := strings.Replace(query, userRows, "COUNT(*)", 1)
var total int64
err := u.conn.QueryRowCtx(ctx, &total, countQuery, args...)
if err != nil {
return nil, 0, err
}
// 分页查询
query += " ORDER BY id DESC LIMIT ? OFFSET ?"
args = append(args, pageSize, (page-1)*pageSize)
var users []*User
err = u.conn.QueryRowsCtx(ctx, &users, query, args...)
return users, total, err
}
// SQL DDL 文件示例 (api/internal/model/sql/user.sql)
/*
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL DEFAULT '' COMMENT '用户名',
`email` varchar(255) NOT NULL DEFAULT '' COMMENT '邮箱',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1-正常,0-禁用',
`create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
*/
中间件开发规范
// api/internal/middleware/auth.go
type AuthMiddleware struct {
secret string
}
func NewAuthMiddleware(secret string) *AuthMiddleware {
return &AuthMiddleware{secret: secret}
}
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// JWT 验证逻辑
token := r.Header.Get("Authorization")
if token == "" {
httpx.ErrorCtx(r.Context(), w, errorx.NewCodeError(401, "未授权"))
return
}
// 解析和验证 JWT
claims, err := jwt.ParseToken(token, m.secret)
if err != nil {
httpx.ErrorCtx(r.Context(), w, errorx.NewCodeError(401, "无效的令牌"))
return
}
// 将用户信息存入上下文
ctx := context.WithValue(r.Context(), "userId", claims.UserId)
next(w, r.WithContext(ctx))
})
}
统一响应格式
// pkg/response/response.go
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func Success(data interface{}) *Response {
return &Response{
Code: 200,
Message: "success",
Data: data,
}
}
func Error(code int, message string) *Response {
return &Response{
Code: code,
Message: message,
}
}
错误处理规范
// pkg/errorx/errorx.go
type CodeError struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
func (e *CodeError) Error() string {
return e.Msg
}
func NewCodeError(code int, msg string) error {
return &CodeError{Code: code, Msg: msg}
}
func NewDefaultError(msg string) error {
return NewCodeError(500, msg)
}
// pkg/constants/constants.go - 错误码定义
const (
UserNotFoundError = 10001
InvalidParameterError = 10002
PermissionDeniedError = 10003
InternalError = 50001
)
// 在 logic 中使用
func (l *GetUserLogic) GetUser(req *types.GetUserReq) (*types.GetUserResp, error) {
user, err := l.svcCtx.UserModel.FindOne(l.ctx, req.Id)
if err != nil {
if err == model.ErrNotFound {
return nil, errorx.NewCodeError(constants.UserNotFoundError, "用户不存在")
}
return nil, errorx.NewDefaultError("获取用户信息失败")
}
return &types.GetUserResp{
Code: 200,
Message: "success",
Data: user,
}, nil
}
配置管理规范
# api/etc/photography-dev.yaml
Name: photography-api
Host: 0.0.0.0
Port: 8080
Mode: dev
# 数据库配置 (支持多种数据库类型)
Database:
Type: sqlite # postgres, sqlite
Host: localhost # PostgreSQL
Port: 5432
Name: photography
User: postgres
Password: password
SSLMode: disable
FilePath: ./data/photography.db # SQLite
# Redis 配置
Redis:
Host: localhost:6379
Password: ""
DB: 0
# JWT 配置
Auth:
AccessSecret: your-access-secret
AccessExpire: 86400 # 24小时
# 日志配置
Log:
ServiceName: photography-api
Mode: console
Level: info
数据库配置结构
// api/internal/config/config.go
type Config struct {
rest.RestConf
Database DatabaseConfig
Redis RedisConfig
Auth AuthConfig
}
type DatabaseConfig struct {
Type string `json:",default=sqlite"`
Host string `json:",default=localhost"`
Port int `json:",default=5432"`
Name string `json:",default=photography"`
User string `json:",default=postgres"`
Password string `json:",optional"`
SSLMode string `json:",default=disable"`
FilePath string `json:",default=./data/photography.db"`
}
type RedisConfig struct {
Host string `json:",default=localhost:6379"`
Password string `json:",optional"`
DB int `json:",default=0"`
}
type AuthConfig struct {
AccessSecret string `json:",default=your-access-secret"`
AccessExpire int64 `json:",default=86400"`
}
📊 统一仓储架构设计
数据库无关设计原则
项目采用统一的仓储接口,通过配置文件切换不同的数据库实现,业务逻辑完全与数据库类型解耦。
仓储接口设计
// 统一仓储接口
type UserRepository interface {
GetByID(ctx context.Context, id uint) (*entity.User, error)
Create(ctx context.Context, user *entity.User) error
Update(ctx context.Context, user *entity.User) error
Delete(ctx context.Context, id uint) error
List(ctx context.Context, opts *dto.ListOptions) ([]*entity.User, int64, error)
GetByEmail(ctx context.Context, email string) (*entity.User, error)
GetByUsername(ctx context.Context, username string) (*entity.User, error)
}
// 数据库实现 (database/user_repository.go)
type userRepository struct {
db *gorm.DB
logger logger.Logger
}
func NewUserRepository(db *gorm.DB, logger logger.Logger) UserRepository {
return &userRepository{
db: db,
logger: logger,
}
}
// 所有数据库都使用相同的实现
func (r *userRepository) GetByID(ctx context.Context, id uint) (*entity.User, error) {
var user entity.User
err := r.db.WithContext(ctx).First(&user, id).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, ErrUserNotFound
}
return nil, err
}
return &user, nil
}
配置驱动的数据库切换
// 配置结构
type DatabaseConfig struct {
Type string `mapstructure:"type"` // "postgres", "sqlite"
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Name string `mapstructure:"name"`
User string `mapstructure:"user"`
Password string `mapstructure:"password"`
SSLMode string `mapstructure:"ssl_mode"`
FilePath string `mapstructure:"file_path"` // SQLite 文件路径
}
// 数据库连接工厂
func NewDatabase(config *DatabaseConfig) (*gorm.DB, error) {
var dsn string
var dialector gorm.Dialector
switch config.Type {
case "postgres":
dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s",
config.Host, config.User, config.Password, config.Name, config.Port, config.SSLMode)
dialector = postgres.Open(dsn)
case "sqlite":
dsn = config.FilePath
dialector = sqlite.Open(dsn)
default:
return nil, fmt.Errorf("unsupported database type: %s", config.Type)
}
db, err := gorm.Open(dialector, &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return nil, err
}
return db, nil
}
功能差异处理策略
// 高级功能接口 (可选实现)
type AdvancedUserRepository interface {
UserRepository
// PostgreSQL 特有功能
FullTextSearch(ctx context.Context, query string) ([]*entity.User, error)
BulkUpsert(ctx context.Context, users []*entity.User) error
}
// 实现中处理差异
func (r *userRepository) FullTextSearch(ctx context.Context, query string) ([]*entity.User, error) {
// 检查数据库类型
if r.db.Dialector.Name() == "postgres" {
// PostgreSQL 全文搜索
var users []*entity.User
err := r.db.WithContext(ctx).
Where("to_tsvector('simple', name || ' ' || email) @@ plainto_tsquery('simple', ?)", query).
Find(&users).Error
return users, err
}
// SQLite 降级为 LIKE 搜索
var users []*entity.User
err := r.db.WithContext(ctx).
Where("name LIKE ? OR email LIKE ?", "%"+query+"%", "%"+query+"%").
Find(&users).Error
return users, err
}
主要实体
- User: 用户信息和权限
- Photo: 照片信息和元数据
- Category: 照片分类
- Tag: 照片标签
- Album: 相册管理
关系设计
User (1:N) Photo
Photo (N:M) Category
Photo (N:M) Tag
User (1:N) Album
Album (N:M) Photo
🔐 认证和授权
JWT 认证流程
- 用户登录 → 验证凭据
- 生成 JWT Token (AccessToken + RefreshToken)
- 客户端携带 Token 访问受保护资源
- 服务器验证 Token 有效性
权限角色
- Admin: 系统管理员 (所有权限)
- Editor: 内容编辑者 (内容管理)
- User: 普通用户 (查看权限)
🧪 简化测试策略
测试类型 (简化)
- API 集成测试: 测试完整的 API 请求响应流程
- 性能测试: 基本的接口响应时间测试
测试工具 (最小化)
- Go Testing: 内置测试框架
- httptest: HTTP 服务测试
- go-zero 测试工具: 内置测试支持
测试示例
// tests/api_test.go
func TestGetUserList(t *testing.T) {
// 使用 httptest 测试 API
req := httptest.NewRequest("GET", "/api/v1/users?page=1&page_size=10", nil)
w := httptest.NewRecorder()
// 调用 handler
handler := getUserListHandler()
handler.ServeHTTP(w, req)
// 验证响应
assert.Equal(t, 200, w.Code)
var resp types.GetUserListResp
err := json.Unmarshal(w.Body.Bytes(), &resp)
assert.NoError(t, err)
assert.Equal(t, 200, resp.Code)
}
// tests/benchmark_test.go
func BenchmarkGetUserList(b *testing.B) {
for i := 0; i < b.N; i++ {
req := httptest.NewRequest("GET", "/api/v1/users", nil)
w := httptest.NewRecorder()
handler := getUserListHandler()
handler.ServeHTTP(w, req)
}
}
📚 API 文档
文档生成
- Swagger/OpenAPI: 自动生成 API 文档
- Postman Collection: 接口测试集合
- README: 快速开始指南
文档维护
- 接口变更时同步更新文档
- 提供完整的请求/响应示例
- 包含错误码和处理说明
🚀 部署配置
环境变量
# 数据库配置 (PostgreSQL)
DB_TYPE=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=photography
DB_USER=postgres
DB_PASSWORD=password
DB_SSLMODE=disable
# 数据库配置 (SQLite)
DB_TYPE=sqlite
DB_FILE_PATH=./data/photography.db
# JWT 配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h
# 文件存储
STORAGE_TYPE=local
STORAGE_PATH=./uploads
Docker 部署
# 构建镜像
make build-image
# 启动服务
make prod-up
# 查看日志
make logs
📋 常用命令
goctl 代码生成命令
# 生成 API 服务 (从主 API 文件)
goctl api go -api api/desc/photography.api -dir api/
# 生成数据模型 (从 SQL DDL)
goctl model mysql ddl -src api/internal/model/sql/user.sql -dir api/internal/model/
# 生成数据模型 (从数据库)
goctl model mysql datasource -url="user:password@tcp(localhost:3306)/database" -table="user" -dir api/internal/model/
# 生成 Docker 文件
goctl docker -go photography.go
# 生成 Kubernetes 配置
goctl kube deploy -name photography-api -namespace default -image photography-api:latest -o deploy.yaml
# 一键生成脚本 (scripts/gen-code.sh)
#!/bin/bash
echo "生成 API 代码..."
goctl api go -api api/desc/photography.api -dir api/
echo "生成数据模型..."
goctl model mysql ddl -src api/internal/model/sql/user.sql -dir api/internal/model/
goctl model mysql ddl -src api/internal/model/sql/photo.sql -dir api/internal/model/
goctl model mysql ddl -src api/internal/model/sql/category.sql -dir api/internal/model/
echo "代码生成完成!"
开发命令
# 启动开发服务器
go run api/photography.go -f api/etc/photography-dev.yaml
# 代码格式化
go fmt ./...
# 代码检查
go vet ./...
# 测试
go test ./tests/...
# 构建
go build -o bin/photography-api api/photography.go
# 使用 Makefile
make dev # 启动开发服务器
make build # 构建二进制文件
make gen # 生成代码
make test # 运行测试
数据库命令 (简化)
# 执行 SQL 文件
sqlite3 data/photography.db < api/internal/model/sql/user.sql
# 或者使用 PostgreSQL
psql -U postgres -d photography -f api/internal/model/sql/user.sql
# 初始化所有表
cat api/internal/model/sql/*.sql | sqlite3 data/photography.db
Docker 命令
# 构建镜像
docker build -t photography-api .
# 运行容器
docker run -p 8080:8080 photography-api
# 使用 docker-compose
docker-compose up -d
🔍 问题排查
常见问题
- 数据库连接失败: 检查配置文件和环境变量
- JWT 验证失败: 检查密钥配置和 Token 格式
- 文件上传失败: 检查存储配置和权限设置
- API 响应慢: 检查数据库查询和缓存配置
日志查看
# 查看应用日志
tail -f logs/app.log
# 查看错误日志
tail -f logs/error.log
# 查看访问日志
tail -f logs/access.log
🎯 模块工作指南
根据工作内容选择模块
🌐 API 接口开发
cd api/
# 参考 api/CLAUDE.md
适用场景:
- API 定义文件编写 (
api/desc/*.api) - Handler 处理器开发 (
api/internal/handler/) - Logic 业务逻辑实现 (
api/internal/logic/) - 中间件开发 (
api/internal/middleware/)
📊 数据模型开发
cd api/internal/model/
# 参考 api/internal/model/CLAUDE.md
适用场景:
- SQL 表结构设计 (
api/internal/model/sql/) - 数据模型生成和自定义方法
- 数据访问层实现
📦 公共包开发
cd pkg/
# 参考 pkg/CLAUDE.md
适用场景:
- 错误处理 (
pkg/errorx/) - 响应格式 (
pkg/response/) - 通用工具 (
pkg/utils/) - 全局常量 (
pkg/constants/)
🧪 测试开发
cd tests/
# 参考 tests/CLAUDE.md
适用场景: API 集成测试、性能基准测试
🐳 部署配置
cd configs/docker/
# 参考 configs/docker/README.md
适用场景: Docker 镜像构建、容器化部署、环境配置
go-zero 开发流程 (优化版)
1. 设计 API 接口
# 1.1 编辑公共类型 api/desc/common.api
# 1.2 编辑具体模块 api/desc/user.api, api/desc/photo.api
# 1.3 在主文件中导入 api/desc/photography.api
2. 创建数据表结构
# 2.1 设计 SQL DDL api/internal/model/sql/user.sql
# 2.2 生成数据模型
goctl model mysql ddl -src api/internal/model/sql/user.sql -dir api/internal/model/
3. 生成代码框架
# 3.1 使用脚本一键生成
chmod +x scripts/gen-code.sh && ./scripts/gen-code.sh
# 3.2 或手动生成
goctl api go -api api/desc/photography.api -dir api/
4. 实现业务逻辑
# 4.1 在 api/internal/logic/ 中实现具体的业务逻辑
# 4.2 在 api/internal/svc/servicecontext.go 中注入依赖
# 4.3 在 pkg/ 中开发通用工具和错误处理
5. 配置和测试
# 5.1 配置数据库连接 api/etc/photography-dev.yaml
# 5.2 运行服务
go run api/photography.go -f api/etc/photography-dev.yaml
# 5.3 编写和运行测试
go test ./tests/...
🔄 最佳实践
go-zero 开发流程
- API 优先设计: 先定义 .api 文件,明确接口契约
- 代码生成驱动: 使用 goctl 工具生成框架代码
- 业务逻辑专注: 在 Logic 层专注实现业务逻辑
- 配置驱动: 通过配置文件管理不同环境
- 简化测试: 专注 API 集成测试,减少复杂度
代码质量 (简化)
- go fmt: 使用标准格式化工具
- go vet: 运行静态分析检查
- API 测试: 确保接口功能正常
- 配置验证: 确保不同环境配置正确
go-zero 优势
- 快速开发: 通过代码生成大幅提升开发效率
- 生态丰富: 内置中间件、链路追踪、服务治理
- 配置简单: YAML 配置文件,易于理解和维护
- 部署方便: 单二进制文件,容器化友好
📈 项目状态
已完成功能
- ✅ go-zero 架构设计
- ✅ 统一数据库模式 (配置驱动切换)
- ✅ JWT 认证中间件
- ✅ API 代码生成框架
- ✅ Docker 部署支持
- ✅ 基础 CRUD 接口
开发中功能
- 🔄 API 接口实现 (通过 goctl 快速生成)
- 🔄 数据模型完善
- 🔄 简化测试用例
计划中功能
- 📋 文件上传功能
- 📋 缓存集成
- 📋 链路追踪
- 📋 性能监控
🔧 开发环境
必需工具
- Go 1.23+: 编程语言
- goctl: go-zero 代码生成工具
- SQLite/PostgreSQL: 数据库 (可配置切换)
- Redis (可选): 缓存数据库
- Docker (可选): 容器化部署
推荐工具
- GoLand/VSCode: 代码编辑器 (支持 .api 文件语法高亮)
- Postman/curl: API 测试
- SQLite Browser/DBeaver: 数据库管理
💡 go-zero 开发技巧
快速开发技巧
- 模板化开发: 使用 goctl 模板快速生成标准代码
- 配置热更新: 修改配置文件无需重启服务
- 内置中间件: 使用 go-zero 内置的限流、熔断等中间件
- 链路追踪: 开启内置的链路追踪,便于问题定位
性能优化 (内置支持)
- 连接池: go-zero 自动管理数据库连接池
- 缓存集成: 简单配置即可集成 Redis 缓存
- 负载均衡: 内置多种负载均衡算法
- 服务发现: 支持多种服务发现机制
安全防护 (中间件)
- JWT 中间件: 内置 JWT 认证中间件
- CORS 中间件: 跨域资源共享支持
- 限流中间件: 防止接口被恶意调用
- 参数验证: 通过 .api 文件定义自动验证
部署优化
- 单二进制: 编译生成单个可执行文件
- Docker 友好: 通过 goctl 生成 Dockerfile
- 配置外置: 通过环境变量或配置文件管理
- 健康检查: 内置健康检查接口
🚀 快速上手总结
- 安装 goctl:
go install github.com/zeromicro/go-zero/tools/goctl@latest - 创建 API 文件: 定义接口和数据结构
- 生成代码:
goctl api go -api photography.api -dir api/ - 实现业务逻辑: 在 Logic 层编写具体实现
- 配置数据库: 修改配置文件,支持多种数据库
- 启动服务:
go run photography.go -f etc/config.yaml
通过 go-zero 生态工具,可以极大地加快开发进度,专注于业务逻辑实现,而无需关心框架细节。
📋 项目结构优化总结
解决的问题
1. ✅ API 文件组织问题
原问题: API 文件会不会有多个?是不是应该放一个目录中?
解决方案:
- 将所有 API 文件统一放在
api/desc/目录中 - 按功能模块分割:
user.api,photo.api,category.api,auth.api - 使用主文件
photography.api通过import导入各模块 - 公共类型定义放在
common.api中,避免重复
2. ✅ Model 目录位置问题
原问题: model 是不是应该放到 internal/model 目录下?
解决方案:
- 将 model 从根目录移至
api/internal/model/ - SQL 文件组织在
api/internal/model/sql/目录 - 符合 Go 项目中 internal 包的语义(仅内部使用)
- 与 go-zero 推荐的项目结构保持一致
3. ✅ 公共包命名问题
原问题: 不想有 common 包,业务解耦的放在 pkg
解决方案:
- 移除
common/目录,改为pkg/目录 - 按功能细分:
pkg/errorx/,pkg/response/,pkg/utils/,pkg/constants/ - 符合 Go 社区标准,
pkg/目录用于可被外部导入的包 - 更好的包命名,避免泛泛的 "common" 命名
4. ✅ 其他结构优化
配置文件整理:
- Docker 相关配置移至
configs/docker/ - SQL 初始化文件移至
configs/sql/ - 脚本文件统一放在
scripts/目录
工具包细分:
- JWT 工具:
pkg/utils/jwt/ - 哈希工具:
pkg/utils/hash/ - 文件工具:
pkg/utils/file/ - 数据库工具:
pkg/utils/database/
新结构的优势
1. 🎯 清晰的模块边界
- API 定义、处理逻辑、数据模型各司其职
- 内部包和外部包职责分明
- 按功能域组织,便于团队协作
2. 🚀 高效的开发流程
- 通过脚本一键生成所有代码框架
- 模块化的 API 定义,支持并行开发
- 统一的错误处理和响应格式
3. 🛠️ 易于维护和扩展
- SQL 文件集中管理,便于数据库版本控制
- 公共工具包可被多个服务复用
- 配置文件按环境分离,支持多环境部署
4. 📏 符合 Go 社区标准
- 遵循 Go 项目布局标准
- internal 包的正确使用
- pkg 包的合理组织
这种结构既保持了 go-zero 的开发效率优势,又符合 Go 社区的最佳实践,为项目的长期维护奠定了良好基础。