493 lines
12 KiB
Plaintext
493 lines
12 KiB
Plaintext
---
|
||
globs: backend/*,backend/**/*.go
|
||
alwaysApply: false
|
||
---
|
||
# Go-Zero 框架开发规则
|
||
|
||
## 🚀 架构规范
|
||
|
||
### 核心概念
|
||
- **Handler**: HTTP请求处理层,只做参数验证和调用Logic
|
||
- **Logic**: 业务逻辑层,包含核心业务代码
|
||
- **Model**: 数据访问层,处理数据库操作
|
||
- **Types**: 请求/响应类型定义
|
||
|
||
### 文件结构
|
||
```
|
||
backend/
|
||
├── cmd/api/main.go # 服务启动入口
|
||
├── etc/photography-api.yaml # 配置文件
|
||
├── api/desc/ # API定义文件
|
||
├── internal/
|
||
│ ├── handler/ # HTTP处理器
|
||
│ ├── logic/ # 业务逻辑
|
||
│ ├── model/ # 数据模型
|
||
│ ├── middleware/ # 中间件
|
||
│ ├── svc/ # 服务上下文
|
||
│ └── types/ # 类型定义
|
||
└── pkg/ # 工具包
|
||
```
|
||
|
||
## 🎯 开发流程
|
||
|
||
### 1. API定义
|
||
在 `api/desc/` 目录下定义接口:
|
||
```api
|
||
service photography-api {
|
||
@handler uploadPhoto
|
||
post /api/v1/photos (UploadPhotoRequest) returns (UploadPhotoResponse)
|
||
}
|
||
|
||
type UploadPhotoRequest {
|
||
Title string `form:"title"`
|
||
Description string `form:"description,optional"`
|
||
File string `form:"file"`
|
||
}
|
||
|
||
type UploadPhotoResponse {
|
||
Id string `json:"id"`
|
||
Title string `json:"title"`
|
||
Filename string `json:"filename"`
|
||
Thumbnail string `json:"thumbnail"`
|
||
}
|
||
```
|
||
|
||
### 2. 代码生成
|
||
```bash
|
||
cd backend
|
||
make api # 生成handler和logic骨架
|
||
```
|
||
|
||
### 3. Handler实现
|
||
```go
|
||
func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) {
|
||
var req types.UploadPhotoRequest
|
||
if err := httpx.Parse(r, &req); err != nil {
|
||
httpx.ErrorCtx(r.Context(), w, err)
|
||
return
|
||
}
|
||
|
||
l := logic.NewUploadPhotoLogic(r.Context(), h.svcCtx)
|
||
resp, err := l.UploadPhoto(&req)
|
||
response.Response(w, resp, err)
|
||
}
|
||
```
|
||
|
||
### 4. Logic实现
|
||
```go
|
||
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) {
|
||
// 1. 参数验证
|
||
if req.Title == "" {
|
||
return nil, errorx.NewDefaultError("照片标题不能为空")
|
||
}
|
||
|
||
// 2. 业务逻辑
|
||
photoID := uuid.New().String()
|
||
|
||
// 3. 数据持久化
|
||
photo := &model.Photo{
|
||
ID: photoID,
|
||
Title: req.Title,
|
||
// ...
|
||
}
|
||
|
||
err := l.svcCtx.PhotoModel.Insert(l.ctx, photo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &types.UploadPhotoResponse{
|
||
Id: photoID,
|
||
Title: req.Title,
|
||
// ...
|
||
}, nil
|
||
}
|
||
```
|
||
|
||
## 🔧 工具包使用
|
||
|
||
### 文件处理
|
||
使用 [pkg/utils/file/file.go](mdc:backend/pkg/utils/file/file.go):
|
||
```go
|
||
import "photography/pkg/utils/file"
|
||
|
||
// 验证图片文件
|
||
if !file.IsImageFile(filename) {
|
||
return errors.New("不支持的文件类型")
|
||
}
|
||
|
||
// 保存文件并生成缩略图
|
||
savedPath, thumbnail, err := file.SaveImage(fileData, filename)
|
||
```
|
||
|
||
### JWT认证
|
||
使用 [pkg/utils/jwt/jwt.go](mdc:backend/pkg/utils/jwt/jwt.go):
|
||
```go
|
||
import "photography/pkg/utils/jwt"
|
||
|
||
// 生成token
|
||
token, err := jwt.GenerateToken(userID, username)
|
||
|
||
// 验证token
|
||
userID, err := jwt.ParseToken(tokenString)
|
||
```
|
||
|
||
### 错误处理
|
||
使用 [pkg/errorx/errorx.go](mdc:backend/pkg/errorx/errorx.go):
|
||
```go
|
||
import "photography/pkg/errorx"
|
||
|
||
// 业务错误
|
||
return nil, errorx.NewDefaultError("用户不存在")
|
||
|
||
// 自定义错误码
|
||
return nil, errorx.NewCodeError(40001, "参数错误")
|
||
```
|
||
|
||
## 🛡️ 中间件
|
||
|
||
### JWT认证中间件
|
||
在 `internal/middleware/auth.go` 中实现:
|
||
```go
|
||
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
token := r.Header.Get("Authorization")
|
||
if token == "" {
|
||
httpx.Error(w, errors.New("未提供认证token"))
|
||
return
|
||
}
|
||
|
||
userID, err := jwt.ParseToken(strings.TrimPrefix(token, "Bearer "))
|
||
if err != nil {
|
||
httpx.Error(w, errors.New("token无效"))
|
||
return
|
||
}
|
||
|
||
// 将用户ID注入到context
|
||
ctx := context.WithValue(r.Context(), "userID", userID)
|
||
next(w, r.WithContext(ctx))
|
||
})
|
||
}
|
||
```
|
||
|
||
## 📊 数据模型
|
||
|
||
### 模型定义示例
|
||
```go
|
||
type Photo struct {
|
||
ID string `db:"id" json:"id"`
|
||
Title string `db:"title" json:"title"`
|
||
Description string `db:"description" json:"description"`
|
||
Filename string `db:"filename" json:"filename"`
|
||
Thumbnail string `db:"thumbnail" json:"thumbnail"`
|
||
CategoryID string `db:"category_id" json:"category_id"`
|
||
UserID string `db:"user_id" json:"user_id"`
|
||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||
}
|
||
```
|
||
|
||
### 数据库操作
|
||
```go
|
||
// 插入
|
||
err := l.svcCtx.PhotoModel.Insert(l.ctx, photo)
|
||
|
||
// 查询
|
||
photo, err := l.svcCtx.PhotoModel.FindOne(l.ctx, photoID)
|
||
|
||
// 更新
|
||
err := l.svcCtx.PhotoModel.Update(l.ctx, photo)
|
||
|
||
// 删除
|
||
err := l.svcCtx.PhotoModel.Delete(l.ctx, photoID)
|
||
```
|
||
|
||
## 🔄 配置管理
|
||
|
||
### 配置文件: `etc/photography-api.yaml`
|
||
```yaml
|
||
Name: photography-api
|
||
Host: 0.0.0.0
|
||
Port: 8888
|
||
|
||
Auth:
|
||
AccessSecret: your-secret-key
|
||
AccessExpire: 86400
|
||
|
||
DataSource: photography.db
|
||
|
||
Log:
|
||
ServiceName: photography-api
|
||
Mode: file
|
||
Path: logs
|
||
Level: info
|
||
```
|
||
|
||
## 🧪 测试规范
|
||
|
||
### 单元测试
|
||
```go
|
||
func TestUploadPhotoLogic(t *testing.T) {
|
||
// 准备测试数据
|
||
req := &types.UploadPhotoRequest{
|
||
Title: "测试照片",
|
||
File: "test.jpg",
|
||
}
|
||
|
||
// 执行测试
|
||
logic := NewUploadPhotoLogic(context.Background(), svcCtx)
|
||
resp, err := logic.UploadPhoto(req)
|
||
|
||
// 断言结果
|
||
assert.NoError(t, err)
|
||
assert.NotEmpty(t, resp.Id)
|
||
assert.Equal(t, "测试照片", resp.Title)
|
||
}
|
||
```
|
||
|
||
参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解当前后端开发进度。
|
||
# Go-Zero 框架开发规则
|
||
|
||
## 🚀 架构规范
|
||
|
||
### 核心概念
|
||
- **Handler**: HTTP请求处理层,只做参数验证和调用Logic
|
||
- **Logic**: 业务逻辑层,包含核心业务代码
|
||
- **Model**: 数据访问层,处理数据库操作
|
||
- **Types**: 请求/响应类型定义
|
||
|
||
### 文件结构
|
||
```
|
||
backend/
|
||
├── cmd/api/main.go # 服务启动入口
|
||
├── etc/photography-api.yaml # 配置文件
|
||
├── api/desc/ # API定义文件
|
||
├── internal/
|
||
│ ├── handler/ # HTTP处理器
|
||
│ ├── logic/ # 业务逻辑
|
||
│ ├── model/ # 数据模型
|
||
│ ├── middleware/ # 中间件
|
||
│ ├── svc/ # 服务上下文
|
||
│ └── types/ # 类型定义
|
||
└── pkg/ # 工具包
|
||
```
|
||
|
||
## 🎯 开发流程
|
||
|
||
### 1. API定义
|
||
在 `api/desc/` 目录下定义接口:
|
||
```api
|
||
service photography-api {
|
||
@handler uploadPhoto
|
||
post /api/v1/photos (UploadPhotoRequest) returns (UploadPhotoResponse)
|
||
}
|
||
|
||
type UploadPhotoRequest {
|
||
Title string `form:"title"`
|
||
Description string `form:"description,optional"`
|
||
File string `form:"file"`
|
||
}
|
||
|
||
type UploadPhotoResponse {
|
||
Id string `json:"id"`
|
||
Title string `json:"title"`
|
||
Filename string `json:"filename"`
|
||
Thumbnail string `json:"thumbnail"`
|
||
}
|
||
```
|
||
|
||
### 2. 代码生成
|
||
```bash
|
||
cd backend
|
||
make api # 生成handler和logic骨架
|
||
```
|
||
|
||
### 3. Handler实现
|
||
```go
|
||
func (h *UploadPhotoHandler) UploadPhoto(w http.ResponseWriter, r *http.Request) {
|
||
var req types.UploadPhotoRequest
|
||
if err := httpx.Parse(r, &req); err != nil {
|
||
httpx.ErrorCtx(r.Context(), w, err)
|
||
return
|
||
}
|
||
|
||
l := logic.NewUploadPhotoLogic(r.Context(), h.svcCtx)
|
||
resp, err := l.UploadPhoto(&req)
|
||
response.Response(w, resp, err)
|
||
}
|
||
```
|
||
|
||
### 4. Logic实现
|
||
```go
|
||
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) {
|
||
// 1. 参数验证
|
||
if req.Title == "" {
|
||
return nil, errorx.NewDefaultError("照片标题不能为空")
|
||
}
|
||
|
||
// 2. 业务逻辑
|
||
photoID := uuid.New().String()
|
||
|
||
// 3. 数据持久化
|
||
photo := &model.Photo{
|
||
ID: photoID,
|
||
Title: req.Title,
|
||
// ...
|
||
}
|
||
|
||
err := l.svcCtx.PhotoModel.Insert(l.ctx, photo)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &types.UploadPhotoResponse{
|
||
Id: photoID,
|
||
Title: req.Title,
|
||
// ...
|
||
}, nil
|
||
}
|
||
```
|
||
|
||
## 🔧 工具包使用
|
||
|
||
### 文件处理
|
||
使用 [pkg/utils/file/file.go](mdc:backend/pkg/utils/file/file.go):
|
||
```go
|
||
import "photography/pkg/utils/file"
|
||
|
||
// 验证图片文件
|
||
if !file.IsImageFile(filename) {
|
||
return errors.New("不支持的文件类型")
|
||
}
|
||
|
||
// 保存文件并生成缩略图
|
||
savedPath, thumbnail, err := file.SaveImage(fileData, filename)
|
||
```
|
||
|
||
### JWT认证
|
||
使用 [pkg/utils/jwt/jwt.go](mdc:backend/pkg/utils/jwt/jwt.go):
|
||
```go
|
||
import "photography/pkg/utils/jwt"
|
||
|
||
// 生成token
|
||
token, err := jwt.GenerateToken(userID, username)
|
||
|
||
// 验证token
|
||
userID, err := jwt.ParseToken(tokenString)
|
||
```
|
||
|
||
### 错误处理
|
||
使用 [pkg/errorx/errorx.go](mdc:backend/pkg/errorx/errorx.go):
|
||
```go
|
||
import "photography/pkg/errorx"
|
||
|
||
// 业务错误
|
||
return nil, errorx.NewDefaultError("用户不存在")
|
||
|
||
// 自定义错误码
|
||
return nil, errorx.NewCodeError(40001, "参数错误")
|
||
```
|
||
|
||
## 🛡️ 中间件
|
||
|
||
### JWT认证中间件
|
||
在 `internal/middleware/auth.go` 中实现:
|
||
```go
|
||
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
|
||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
token := r.Header.Get("Authorization")
|
||
if token == "" {
|
||
httpx.Error(w, errors.New("未提供认证token"))
|
||
return
|
||
}
|
||
|
||
userID, err := jwt.ParseToken(strings.TrimPrefix(token, "Bearer "))
|
||
if err != nil {
|
||
httpx.Error(w, errors.New("token无效"))
|
||
return
|
||
}
|
||
|
||
// 将用户ID注入到context
|
||
ctx := context.WithValue(r.Context(), "userID", userID)
|
||
next(w, r.WithContext(ctx))
|
||
})
|
||
}
|
||
```
|
||
|
||
## 📊 数据模型
|
||
|
||
### 模型定义示例
|
||
```go
|
||
type Photo struct {
|
||
ID string `db:"id" json:"id"`
|
||
Title string `db:"title" json:"title"`
|
||
Description string `db:"description" json:"description"`
|
||
Filename string `db:"filename" json:"filename"`
|
||
Thumbnail string `db:"thumbnail" json:"thumbnail"`
|
||
CategoryID string `db:"category_id" json:"category_id"`
|
||
UserID string `db:"user_id" json:"user_id"`
|
||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||
}
|
||
```
|
||
|
||
### 数据库操作
|
||
```go
|
||
// 插入
|
||
err := l.svcCtx.PhotoModel.Insert(l.ctx, photo)
|
||
|
||
// 查询
|
||
photo, err := l.svcCtx.PhotoModel.FindOne(l.ctx, photoID)
|
||
|
||
// 更新
|
||
err := l.svcCtx.PhotoModel.Update(l.ctx, photo)
|
||
|
||
// 删除
|
||
err := l.svcCtx.PhotoModel.Delete(l.ctx, photoID)
|
||
```
|
||
|
||
## 🔄 配置管理
|
||
|
||
### 配置文件: `etc/photography-api.yaml`
|
||
```yaml
|
||
Name: photography-api
|
||
Host: 0.0.0.0
|
||
Port: 8888
|
||
|
||
Auth:
|
||
AccessSecret: your-secret-key
|
||
AccessExpire: 86400
|
||
|
||
DataSource: photography.db
|
||
|
||
Log:
|
||
ServiceName: photography-api
|
||
Mode: file
|
||
Path: logs
|
||
Level: info
|
||
```
|
||
|
||
## 🧪 测试规范
|
||
|
||
### 单元测试
|
||
```go
|
||
func TestUploadPhotoLogic(t *testing.T) {
|
||
// 准备测试数据
|
||
req := &types.UploadPhotoRequest{
|
||
Title: "测试照片",
|
||
File: "test.jpg",
|
||
}
|
||
|
||
// 执行测试
|
||
logic := NewUploadPhotoLogic(context.Background(), svcCtx)
|
||
resp, err := logic.UploadPhoto(req)
|
||
|
||
// 断言结果
|
||
assert.NoError(t, err)
|
||
assert.NotEmpty(t, resp.Id)
|
||
assert.Equal(t, "测试照片", resp.Title)
|
||
}
|
||
```
|
||
|
||
参考 [TASK_PROGRESS.md](mdc:TASK_PROGRESS.md) 了解当前后端开发进度。
|