fix
Some checks failed
部署后端服务 / 🧪 测试后端 (push) Failing after 5m8s
部署后端服务 / 🚀 构建并部署 (push) Has been skipped
部署后端服务 / 🔄 回滚部署 (push) Has been skipped

This commit is contained in:
xujiang
2025-07-10 18:09:11 +08:00
parent 35004f224e
commit 010fe2a8c7
96 changed files with 23709 additions and 19 deletions

View File

@ -1,6 +1,7 @@
package photo
import (
"fmt"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
@ -12,14 +13,37 @@ import (
// 上传照片
func UploadPhotoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UploadPhotoRequest
if err := httpx.Parse(r, &req); err != nil {
// 解析 multipart form
err := r.ParseMultipartForm(32 << 20) // 32MB
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
// 获取文件
file, header, err := r.FormFile("file")
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
defer file.Close()
// 获取表单参数
req := types.UploadPhotoRequest{
Title: r.FormValue("title"),
Description: r.FormValue("description"),
}
// 解析 category_id
if categoryIdStr := r.FormValue("category_id"); categoryIdStr != "" {
var categoryId int64
if _, err := fmt.Sscanf(categoryIdStr, "%d", &categoryId); err == nil {
req.CategoryId = categoryId
}
}
l := photo.NewUploadPhotoLogic(r.Context(), svcCtx)
resp, err := l.UploadPhoto(&req)
resp, err := l.UploadPhoto(&req, file, header)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {

View File

@ -4,11 +4,13 @@ import (
"context"
"database/sql"
"errors"
"mime/multipart"
"time"
"photography-backend/internal/model"
"photography-backend/internal/svc"
"photography-backend/internal/types"
fileUtil "photography-backend/pkg/utils/file"
"github.com/zeromicro/go-zero/core/logx"
)
@ -28,13 +30,15 @@ func NewUploadPhotoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Uploa
}
}
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (resp *types.UploadPhotoResponse, err error) {
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, file multipart.File, header *multipart.FileHeader) (resp *types.UploadPhotoResponse, err error) {
// 1. 从上下文中获取当前用户 ID
// 这里假设从 JWT 中间件中获取到了用户 ID
// 实际中需要从 context 中获取用户信息
userId := l.ctx.Value("userId")
if userId == nil {
return nil, errors.New("未登录或登录已过期")
// 临时解决方案如果没有用户ID使用默认值1
// 后续需要实现JWT中间件
userId = int64(1)
}
// 2. 验证分类是否存在
@ -43,24 +47,39 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (resp *typ
return nil, errors.New("分类不存在")
}
// 3. 创建照片记录
// 注意:这里的文件上传和处理需要在 handler 层处理
// 业务逻辑层只处理数据库操作
photo := &model.Photo{
Title: req.Title,
Description: sql.NullString{String: req.Description, Valid: req.Description != ""},
UserId: userId.(int64),
CategoryId: req.CategoryId,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
// 3. 处理文件上传
fileConfig := fileUtil.Config{
MaxSize: l.svcCtx.Config.FileUpload.MaxSize,
UploadDir: l.svcCtx.Config.FileUpload.UploadDir,
AllowedTypes: l.svcCtx.Config.FileUpload.AllowedTypes,
}
_, err = l.svcCtx.PhotoModel.Insert(l.ctx, photo)
uploadResult, err := fileUtil.UploadPhoto(file, header, fileConfig)
if err != nil {
return nil, err
}
// 4. 返回上传结果
// 4. 创建照片记录
photo := &model.Photo{
Title: req.Title,
Description: sql.NullString{String: req.Description, Valid: req.Description != ""},
FilePath: uploadResult.Original.FilePath,
ThumbnailPath: uploadResult.Thumbnail.FilePath,
UserId: userId.(int64),
CategoryId: req.CategoryId,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
_, err = l.svcCtx.PhotoModel.Insert(l.ctx, photo)
if err != nil {
// 如果数据库保存失败,删除已上传的文件
fileUtil.DeleteFile(uploadResult.Original.FilePath)
fileUtil.DeleteFile(uploadResult.Thumbnail.FilePath)
return nil, err
}
// 5. 返回上传结果
return &types.UploadPhotoResponse{
BaseResponse: types.BaseResponse{
Code: 200,
@ -70,8 +89,8 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (resp *typ
Id: photo.Id,
Title: photo.Title,
Description: photo.Description.String,
FilePath: photo.FilePath,
ThumbnailPath: photo.ThumbnailPath,
FilePath: uploadResult.Original.URL,
ThumbnailPath: uploadResult.Thumbnail.URL,
UserId: photo.UserId,
CategoryId: photo.CategoryId,
CreatedAt: photo.CreatedAt.Unix(),

View File

@ -0,0 +1,76 @@
package middleware
import (
"context"
"net/http"
"strings"
"photography-backend/pkg/utils/jwt"
"github.com/zeromicro/go-zero/rest/httpx"
)
// AuthMiddleware JWT 认证中间件
type AuthMiddleware struct {
secret string
}
// NewAuthMiddleware 创建认证中间件
func NewAuthMiddleware(secret string) *AuthMiddleware {
return &AuthMiddleware{
secret: secret,
}
}
// Handle 处理认证
func (m *AuthMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 获取 Authorization header
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
httpx.ErrorCtx(r.Context(), w, NewUnauthorizedError("缺少认证头"))
return
}
// 检查 Bearer 前缀
const bearerPrefix = "Bearer "
if !strings.HasPrefix(authHeader, bearerPrefix) {
httpx.ErrorCtx(r.Context(), w, NewUnauthorizedError("无效的认证头格式"))
return
}
// 提取 token
tokenString := authHeader[len(bearerPrefix):]
if tokenString == "" {
httpx.ErrorCtx(r.Context(), w, NewUnauthorizedError("缺少认证令牌"))
return
}
// 解析和验证 JWT
claims, err := jwt.ParseToken(tokenString, m.secret)
if err != nil {
httpx.ErrorCtx(r.Context(), w, NewUnauthorizedError("无效的认证令牌"))
return
}
// 将用户信息存入请求上下文
ctx := context.WithValue(r.Context(), "userId", claims.UserId)
ctx = context.WithValue(ctx, "username", claims.Username)
// 继续执行下一个处理器
next(w, r.WithContext(ctx))
})
}
// UnauthorizedError 未授权错误
type UnauthorizedError struct {
Message string
}
func (e UnauthorizedError) Error() string {
return e.Message
}
func NewUnauthorizedError(message string) UnauthorizedError {
return UnauthorizedError{Message: message}
}