主要变更: - 采用 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 最佳实践
1164 lines
34 KiB
Markdown
1164 lines
34 KiB
Markdown
# 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 设计原则
|
||
|
||
1. **代码生成驱动**: 通过 .api 文件和 goctl 工具自动生成代码
|
||
2. **配置驱动**: 所有配置统一管理,支持多环境热加载
|
||
3. **中间件体系**: 丰富的内置中间件,支持自定义扩展
|
||
4. **错误处理**: 统一的错误处理和响应格式
|
||
5. **服务治理**: 内置熔断、限流、负载均衡等微服务组件
|
||
6. **链路追踪**: 内置 OpenTelemetry 支持,便于问题排查
|
||
7. **数据库无关**: 统一的模型接口,支持多种数据库类型
|
||
|
||
## 🚀 快速开始
|
||
|
||
### 环境准备
|
||
```bash
|
||
# 1. 安装 Go 1.23+
|
||
go version
|
||
|
||
# 2. 安装 goctl 工具
|
||
go install github.com/zeromicro/go-zero/tools/goctl@latest
|
||
|
||
# 3. 验证安装
|
||
goctl --version
|
||
```
|
||
|
||
### 项目初始化
|
||
```bash
|
||
# 1. 创建项目目录
|
||
mkdir photography-backend && cd photography-backend
|
||
|
||
# 2. 初始化 Go 模块
|
||
go mod init photography-backend
|
||
|
||
# 3. 创建 API 定义文件
|
||
mkdir -p api/desc
|
||
```
|
||
|
||
### 快速开发流程
|
||
```bash
|
||
# 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)
|
||
```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)
|
||
```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)
|
||
```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"`
|
||
}
|
||
```
|
||
|
||
### 代码生成规范
|
||
1. **API 服务生成**:
|
||
```bash
|
||
# 从主 API 文件生成代码
|
||
goctl api go -api api/desc/photography.api -dir api/
|
||
```
|
||
2. **数据模型生成**:
|
||
```bash
|
||
# 从 SQL DDL 生成模型到 internal/model
|
||
goctl model mysql ddl -src api/internal/model/sql/user.sql -dir api/internal/model/
|
||
```
|
||
3. **代码更新**: 重新生成时保持手动修改的业务逻辑
|
||
4. **文件组织**: API 按模块分文件,model 放在 internal 目录
|
||
5. **命名规范**: 遵循 go-zero 的命名约定
|
||
|
||
### 业务逻辑开发规范
|
||
```go
|
||
// 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
|
||
}
|
||
```
|
||
|
||
### 数据模型开发规范
|
||
```go
|
||
// 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='用户表';
|
||
*/
|
||
```
|
||
|
||
### 中间件开发规范
|
||
```go
|
||
// 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))
|
||
})
|
||
}
|
||
```
|
||
|
||
### 统一响应格式
|
||
```go
|
||
// 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,
|
||
}
|
||
}
|
||
```
|
||
|
||
### 错误处理规范
|
||
```go
|
||
// 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
|
||
}
|
||
```
|
||
|
||
### 配置管理规范
|
||
```yaml
|
||
# 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
|
||
```
|
||
|
||
### 数据库配置结构
|
||
```go
|
||
// 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"`
|
||
}
|
||
```
|
||
|
||
## 📊 统一仓储架构设计
|
||
|
||
### 数据库无关设计原则
|
||
项目采用统一的仓储接口,通过配置文件切换不同的数据库实现,业务逻辑完全与数据库类型解耦。
|
||
|
||
### 仓储接口设计
|
||
```go
|
||
// 统一仓储接口
|
||
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
|
||
}
|
||
```
|
||
|
||
### 配置驱动的数据库切换
|
||
```go
|
||
// 配置结构
|
||
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
|
||
}
|
||
```
|
||
|
||
### 功能差异处理策略
|
||
```go
|
||
// 高级功能接口 (可选实现)
|
||
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 认证流程
|
||
1. 用户登录 → 验证凭据
|
||
2. 生成 JWT Token (AccessToken + RefreshToken)
|
||
3. 客户端携带 Token 访问受保护资源
|
||
4. 服务器验证 Token 有效性
|
||
|
||
### 权限角色
|
||
- **Admin**: 系统管理员 (所有权限)
|
||
- **Editor**: 内容编辑者 (内容管理)
|
||
- **User**: 普通用户 (查看权限)
|
||
|
||
## 🧪 简化测试策略
|
||
|
||
### 测试类型 (简化)
|
||
- **API 集成测试**: 测试完整的 API 请求响应流程
|
||
- **性能测试**: 基本的接口响应时间测试
|
||
|
||
### 测试工具 (最小化)
|
||
- **Go Testing**: 内置测试框架
|
||
- **httptest**: HTTP 服务测试
|
||
- **go-zero 测试工具**: 内置测试支持
|
||
|
||
### 测试示例
|
||
```go
|
||
// 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**: 快速开始指南
|
||
|
||
### 文档维护
|
||
- 接口变更时同步更新文档
|
||
- 提供完整的请求/响应示例
|
||
- 包含错误码和处理说明
|
||
|
||
## 🚀 部署配置
|
||
|
||
### 环境变量
|
||
```bash
|
||
# 数据库配置 (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 部署
|
||
```bash
|
||
# 构建镜像
|
||
make build-image
|
||
|
||
# 启动服务
|
||
make prod-up
|
||
|
||
# 查看日志
|
||
make logs
|
||
```
|
||
|
||
## 📋 常用命令
|
||
|
||
### goctl 代码生成命令
|
||
```bash
|
||
# 生成 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 "代码生成完成!"
|
||
```
|
||
|
||
### 开发命令
|
||
```bash
|
||
# 启动开发服务器
|
||
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 # 运行测试
|
||
```
|
||
|
||
### 数据库命令 (简化)
|
||
```bash
|
||
# 执行 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 命令
|
||
```bash
|
||
# 构建镜像
|
||
docker build -t photography-api .
|
||
|
||
# 运行容器
|
||
docker run -p 8080:8080 photography-api
|
||
|
||
# 使用 docker-compose
|
||
docker-compose up -d
|
||
```
|
||
|
||
## 🔍 问题排查
|
||
|
||
### 常见问题
|
||
1. **数据库连接失败**: 检查配置文件和环境变量
|
||
2. **JWT 验证失败**: 检查密钥配置和 Token 格式
|
||
3. **文件上传失败**: 检查存储配置和权限设置
|
||
4. **API 响应慢**: 检查数据库查询和缓存配置
|
||
|
||
### 日志查看
|
||
```bash
|
||
# 查看应用日志
|
||
tail -f logs/app.log
|
||
|
||
# 查看错误日志
|
||
tail -f logs/error.log
|
||
|
||
# 查看访问日志
|
||
tail -f logs/access.log
|
||
```
|
||
|
||
## 🎯 模块工作指南
|
||
|
||
### 根据工作内容选择模块
|
||
|
||
#### 🌐 API 接口开发
|
||
```bash
|
||
cd api/
|
||
# 参考 api/CLAUDE.md
|
||
```
|
||
**适用场景**:
|
||
- API 定义文件编写 (`api/desc/*.api`)
|
||
- Handler 处理器开发 (`api/internal/handler/`)
|
||
- Logic 业务逻辑实现 (`api/internal/logic/`)
|
||
- 中间件开发 (`api/internal/middleware/`)
|
||
|
||
#### 📊 数据模型开发
|
||
```bash
|
||
cd api/internal/model/
|
||
# 参考 api/internal/model/CLAUDE.md
|
||
```
|
||
**适用场景**:
|
||
- SQL 表结构设计 (`api/internal/model/sql/`)
|
||
- 数据模型生成和自定义方法
|
||
- 数据访问层实现
|
||
|
||
#### 📦 公共包开发
|
||
```bash
|
||
cd pkg/
|
||
# 参考 pkg/CLAUDE.md
|
||
```
|
||
**适用场景**:
|
||
- 错误处理 (`pkg/errorx/`)
|
||
- 响应格式 (`pkg/response/`)
|
||
- 通用工具 (`pkg/utils/`)
|
||
- 全局常量 (`pkg/constants/`)
|
||
|
||
#### 🧪 测试开发
|
||
```bash
|
||
cd tests/
|
||
# 参考 tests/CLAUDE.md
|
||
```
|
||
**适用场景**: API 集成测试、性能基准测试
|
||
|
||
#### 🐳 部署配置
|
||
```bash
|
||
cd configs/docker/
|
||
# 参考 configs/docker/README.md
|
||
```
|
||
**适用场景**: Docker 镜像构建、容器化部署、环境配置
|
||
|
||
### go-zero 开发流程 (优化版)
|
||
|
||
#### 1. 设计 API 接口
|
||
```bash
|
||
# 1.1 编辑公共类型 api/desc/common.api
|
||
# 1.2 编辑具体模块 api/desc/user.api, api/desc/photo.api
|
||
# 1.3 在主文件中导入 api/desc/photography.api
|
||
```
|
||
|
||
#### 2. 创建数据表结构
|
||
```bash
|
||
# 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. 生成代码框架
|
||
```bash
|
||
# 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. 实现业务逻辑
|
||
```bash
|
||
# 4.1 在 api/internal/logic/ 中实现具体的业务逻辑
|
||
# 4.2 在 api/internal/svc/servicecontext.go 中注入依赖
|
||
# 4.3 在 pkg/ 中开发通用工具和错误处理
|
||
```
|
||
|
||
#### 5. 配置和测试
|
||
```bash
|
||
# 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 开发流程
|
||
1. **API 优先设计**: 先定义 .api 文件,明确接口契约
|
||
2. **代码生成驱动**: 使用 goctl 工具生成框架代码
|
||
3. **业务逻辑专注**: 在 Logic 层专注实现业务逻辑
|
||
4. **配置驱动**: 通过配置文件管理不同环境
|
||
5. **简化测试**: 专注 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
|
||
- **配置外置**: 通过环境变量或配置文件管理
|
||
- **健康检查**: 内置健康检查接口
|
||
|
||
## 🚀 快速上手总结
|
||
|
||
1. **安装 goctl**: `go install github.com/zeromicro/go-zero/tools/goctl@latest`
|
||
2. **创建 API 文件**: 定义接口和数据结构
|
||
3. **生成代码**: `goctl api go -api photography.api -dir api/`
|
||
4. **实现业务逻辑**: 在 Logic 层编写具体实现
|
||
5. **配置数据库**: 修改配置文件,支持多种数据库
|
||
6. **启动服务**: `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 社区的最佳实践,为项目的长期维护奠定了良好基础。 |