diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 548c031..9531109 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -29,8 +29,8 @@ func main() { // 添加静态文件服务 server.AddRoute(rest.Route{ - Method: http.MethodGet, - Path: "/uploads/*", + Method: http.MethodGet, + Path: "/uploads/*", Handler: func(w http.ResponseWriter, r *http.Request) { http.StripPrefix("/uploads/", http.FileServer(http.Dir("uploads"))).ServeHTTP(w, r) }, diff --git a/backend/cmd/migrate/main.go b/backend/cmd/migrate/main.go index 3bbb7bc..1a3c669 100644 --- a/backend/cmd/migrate/main.go +++ b/backend/cmd/migrate/main.go @@ -95,7 +95,7 @@ func main() { } migrateSteps = len(pendingMigrations) } - + if err := migrator.Migrate(migrateSteps); err != nil { log.Fatalf("Migration failed: %v", err) } @@ -168,10 +168,10 @@ func showHelp() { func createMigrationTemplate(name string) { // 生成版本号(基于当前时间) - version := fmt.Sprintf("%d_%06d", - getCurrentTimestamp(), + version := fmt.Sprintf("%d_%06d", + getCurrentTimestamp(), getCurrentMicroseconds()) - + template := fmt.Sprintf(`// Migration: %s // Description: %s // Version: %s @@ -198,22 +198,22 @@ var migration_%s = Migration{ -- ALTER TABLE user DROP COLUMN new_field; -- Note: SQLite doesn't support DROP COLUMN %s, }`, - name, name, version, + name, name, version, version, version, name, "`", "`", "`", "`") - + filename := fmt.Sprintf("migrations/%s_%s.go", version, name) - + // 创建 migrations 目录 if err := os.MkdirAll("migrations", 0755); err != nil { log.Fatalf("Failed to create migrations directory: %v", err) } - + // 写入模板文件 if err := os.WriteFile(filename, []byte(template), 0644); err != nil { log.Fatalf("Failed to create migration template: %v", err) } - + fmt.Printf("Created migration template: %s\n", filename) fmt.Println("Please:") fmt.Println("1. Edit the migration file to add your SQL") @@ -227,4 +227,4 @@ func getCurrentTimestamp() int64 { func getCurrentMicroseconds() int { return 1 // 当天的迁移计数,可以根据需要调整 -} \ No newline at end of file +} diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index deee050..02771b5 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -7,8 +7,8 @@ import ( type Config struct { rest.RestConf - Database database.Config `json:"database"` - Auth AuthConfig `json:"auth"` + Database database.Config `json:"database"` + Auth AuthConfig `json:"auth"` FileUpload FileUploadConfig `json:"file_upload"` Middleware MiddlewareConfig `json:"middleware"` } @@ -28,6 +28,6 @@ type MiddlewareConfig struct { EnableCORS bool `json:"enable_cors"` EnableLogger bool `json:"enable_logger"` EnableErrorHandle bool `json:"enable_error_handle"` - CORSOrigins []string `json:"cors_origins"` - LogLevel string `json:"log_level"` + CORSOrigins []string `json:"cors_origins"` + LogLevel string `json:"log_level"` } diff --git a/backend/internal/handler/photo/uploadPhotoHandler.go b/backend/internal/handler/photo/uploadPhotoHandler.go index 27b3637..9a11e60 100644 --- a/backend/internal/handler/photo/uploadPhotoHandler.go +++ b/backend/internal/handler/photo/uploadPhotoHandler.go @@ -33,7 +33,7 @@ func UploadPhotoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { Title: r.FormValue("title"), Description: r.FormValue("description"), } - + // 解析 category_id if categoryIdStr := r.FormValue("category_id"); categoryIdStr != "" { var categoryId int64 diff --git a/backend/internal/logic/auth/loginLogic.go b/backend/internal/logic/auth/loginLogic.go index 5e17691..4862e98 100644 --- a/backend/internal/logic/auth/loginLogic.go +++ b/backend/internal/logic/auth/loginLogic.go @@ -39,24 +39,24 @@ func (l *LoginLogic) Login(req *types.LoginRequest) (resp *types.LoginResponse, logx.Errorf("查询用户失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 2. 验证密码 if !hash.CheckPassword(req.Password, user.Password) { return nil, errorx.NewWithCode(errorx.InvalidPassword) } - + // 3. 检查用户状态 if user.Status == 0 { return nil, errorx.NewWithCode(errorx.UserDisabled) } - + // 4. 生成 JWT token token, err := jwt.GenerateToken(user.Id, user.Username, l.svcCtx.Config.Auth.AccessSecret, time.Hour*24*7) if err != nil { logx.Errorf("生成 JWT token 失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 5. 返回登录结果 return &types.LoginResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/auth/registerLogic.go b/backend/internal/logic/auth/registerLogic.go index 06da756..a6d1a7c 100644 --- a/backend/internal/logic/auth/registerLogic.go +++ b/backend/internal/logic/auth/registerLogic.go @@ -34,34 +34,34 @@ func (l *RegisterLogic) Register(req *types.RegisterRequest) (resp *types.Regist if err == nil && existingUser != nil { return nil, errors.New("用户名已存在") } - + // 2. 检查邮箱是否已存在 existingEmail, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) if err == nil && existingEmail != nil { return nil, errors.New("邮箱已存在") } - + // 3. 加密密码 hashedPassword, err := hash.HashPassword(req.Password) if err != nil { return nil, err } - + // 4. 创建用户 user := &model.User{ - Username: req.Username, - Email: req.Email, - Password: hashedPassword, - Status: 1, // 默认激活状态 + Username: req.Username, + Email: req.Email, + Password: hashedPassword, + Status: 1, // 默认激活状态 CreatedAt: time.Now(), UpdatedAt: time.Now(), } - + _, err = l.svcCtx.UserModel.Insert(l.ctx, user) if err != nil { return nil, err } - + // 5. 返回注册结果 return &types.RegisterResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/category/createCategoryLogic.go b/backend/internal/logic/category/createCategoryLogic.go index 011b33f..d9e4baf 100644 --- a/backend/internal/logic/category/createCategoryLogic.go +++ b/backend/internal/logic/category/createCategoryLogic.go @@ -35,12 +35,12 @@ func (l *CreateCategoryLogic) CreateCategory(req *types.CreateCategoryRequest) ( CreatedAt: time.Now(), UpdatedAt: time.Now(), } - + _, err = l.svcCtx.CategoryModel.Insert(l.ctx, category) if err != nil { return nil, err } - + // 2. 返回结果 return &types.CreateCategoryResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/category/getCategoryListLogic.go b/backend/internal/logic/category/getCategoryListLogic.go index 2e42326..a24dfae 100644 --- a/backend/internal/logic/category/getCategoryListLogic.go +++ b/backend/internal/logic/category/getCategoryListLogic.go @@ -30,13 +30,13 @@ func (l *GetCategoryListLogic) GetCategoryList(req *types.GetCategoryListRequest if err != nil { return nil, err } - + // 2. 统计总数 total, err := l.svcCtx.CategoryModel.Count(l.ctx, req.Keyword) if err != nil { return nil, err } - + // 3. 转换数据结构 var categoryList []types.Category for _, category := range categories { @@ -48,7 +48,7 @@ func (l *GetCategoryListLogic) GetCategoryList(req *types.GetCategoryListRequest UpdatedAt: category.UpdatedAt.Unix(), }) } - + // 4. 返回结果 return &types.GetCategoryListResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/photo/getPhotoListLogic.go b/backend/internal/logic/photo/getPhotoListLogic.go index ea219c4..d385758 100644 --- a/backend/internal/logic/photo/getPhotoListLogic.go +++ b/backend/internal/logic/photo/getPhotoListLogic.go @@ -32,14 +32,14 @@ func (l *GetPhotoListLogic) GetPhotoList(req *types.GetPhotoListRequest) (resp * logx.Errorf("查询照片列表失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 2. 统计总数 total, err := l.svcCtx.PhotoModel.Count(l.ctx, req.CategoryId, req.UserId, req.Keyword) if err != nil { logx.Errorf("统计照片数量失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 3. 转换数据结构 var photoList []types.Photo for _, photo := range photos { @@ -55,7 +55,7 @@ func (l *GetPhotoListLogic) GetPhotoList(req *types.GetPhotoListRequest) (resp * UpdatedAt: photo.UpdatedAt.Unix(), }) } - + // 4. 返回结果 return &types.GetPhotoListResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/photo/getPhotoLogic.go b/backend/internal/logic/photo/getPhotoLogic.go index 6bf520a..24278db 100644 --- a/backend/internal/logic/photo/getPhotoLogic.go +++ b/backend/internal/logic/photo/getPhotoLogic.go @@ -36,7 +36,7 @@ func (l *GetPhotoLogic) GetPhoto(req *types.GetPhotoRequest) (resp *types.GetPho logx.Errorf("查询照片失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 2. 返回结果 return &types.GetPhotoResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/photo/uploadPhotoLogic.go b/backend/internal/logic/photo/uploadPhotoLogic.go index ce8b38e..8ddedcc 100644 --- a/backend/internal/logic/photo/uploadPhotoLogic.go +++ b/backend/internal/logic/photo/uploadPhotoLogic.go @@ -40,7 +40,7 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, file multi // 后续需要实现JWT中间件 userId = int64(1) } - + // 2. 验证分类是否存在 _, err = l.svcCtx.CategoryModel.FindOne(l.ctx, req.CategoryId) if err != nil { @@ -50,20 +50,20 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, file multi logx.Errorf("查询分类失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 3. 处理文件上传 fileConfig := fileUtil.Config{ MaxSize: l.svcCtx.Config.FileUpload.MaxSize, UploadDir: l.svcCtx.Config.FileUpload.UploadDir, AllowedTypes: l.svcCtx.Config.FileUpload.AllowedTypes, } - + uploadResult, err := fileUtil.UploadPhoto(file, header, fileConfig) if err != nil { logx.Errorf("文件上传失败: %v", err) return nil, errorx.NewWithCode(errorx.PhotoUploadFail) } - + // 4. 创建照片记录 photo := &model.Photo{ Title: req.Title, @@ -75,7 +75,7 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, file multi CreatedAt: time.Now(), UpdatedAt: time.Now(), } - + _, err = l.svcCtx.PhotoModel.Insert(l.ctx, photo) if err != nil { // 如果数据库保存失败,删除已上传的文件 @@ -84,7 +84,7 @@ func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest, file multi logx.Errorf("保存照片记录失败: %v", err) return nil, errorx.NewWithCode(errorx.ServerError) } - + // 5. 返回上传结果 return &types.UploadPhotoResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/user/deleteUserLogic.go b/backend/internal/logic/user/deleteUserLogic.go index 73e5294..c553c5e 100644 --- a/backend/internal/logic/user/deleteUserLogic.go +++ b/backend/internal/logic/user/deleteUserLogic.go @@ -50,7 +50,7 @@ func (l *DeleteUserLogic) DeleteUser(req *types.DeleteUserRequest) (resp *types. // 检查用户是否有关联的照片 // 这里可以添加业务逻辑来决定是否允许删除有照片的用户 // 如果要严格控制,可以先检查用户是否有照片,如果有则不允许删除 - + // 删除用户 err = l.svcCtx.UserModel.Delete(l.ctx, req.Id) if err != nil { diff --git a/backend/internal/logic/user/getUserListLogic.go b/backend/internal/logic/user/getUserListLogic.go index 84ac6ee..f3cdc51 100644 --- a/backend/internal/logic/user/getUserListLogic.go +++ b/backend/internal/logic/user/getUserListLogic.go @@ -30,13 +30,13 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (resp *typ if err != nil { return nil, err } - + // 2. 统计总数 total, err := l.svcCtx.UserModel.Count(l.ctx, req.Keyword) if err != nil { return nil, err } - + // 3. 转换数据结构(不返回密码) var userList []types.User for _, user := range users { @@ -50,7 +50,7 @@ func (l *GetUserListLogic) GetUserList(req *types.GetUserListRequest) (resp *typ UpdatedAt: user.UpdatedAt.Unix(), }) } - + // 4. 返回结果 return &types.GetUserListResponse{ BaseResponse: types.BaseResponse{ diff --git a/backend/internal/logic/user/uploadavatarlogic.go b/backend/internal/logic/user/uploadavatarlogic.go index b7563e4..e44ab4f 100644 --- a/backend/internal/logic/user/uploadavatarlogic.go +++ b/backend/internal/logic/user/uploadavatarlogic.go @@ -55,7 +55,7 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http // 4. 获取上传的文件 uploadedFile, header, err := r.FormFile("avatar") if err != nil { - return nil, errorx.New(errorx.ParamError, "获取上传文件失败: " + err.Error()) + return nil, errorx.New(errorx.ParamError, "获取上传文件失败: "+err.Error()) } defer uploadedFile.Close() @@ -90,10 +90,10 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http filename := fmt.Sprintf("avatar_%d_%d%s", req.Id, time.Now().Unix(), ext) avatarDir := "uploads/avatars" - + // 8. 确保头像目录存在 if err := os.MkdirAll(avatarDir, 0755); err != nil { - return nil, errorx.New(errorx.ServerError, "创建头像目录失败: " + err.Error()) + return nil, errorx.New(errorx.ServerError, "创建头像目录失败: "+err.Error()) } avatarPath := filepath.Join(avatarDir, filename) @@ -101,13 +101,13 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http // 9. 保存原始头像文件 destFile, err := os.Create(avatarPath) if err != nil { - return nil, errorx.New(errorx.ServerError, "创建头像文件失败: " + err.Error()) + return nil, errorx.New(errorx.ServerError, "创建头像文件失败: "+err.Error()) } defer destFile.Close() _, err = io.Copy(destFile, uploadedFile) if err != nil { - return nil, errorx.New(errorx.ServerError, "保存头像文件失败: " + err.Error()) + return nil, errorx.New(errorx.ServerError, "保存头像文件失败: "+err.Error()) } // 10. 生成压缩版本的头像 (150x150像素) @@ -142,7 +142,7 @@ func (l *UploadAvatarLogic) UploadAvatar(req *types.UploadAvatarRequest, r *http if avatarPath != compressedPath { os.Remove(compressedPath) } - return nil, errorx.New(errorx.ServerError, "更新用户头像失败: " + err.Error()) + return nil, errorx.New(errorx.ServerError, "更新用户头像失败: "+err.Error()) } return &types.UploadAvatarResponse{ diff --git a/backend/internal/middleware/auth.go b/backend/internal/middleware/auth.go index ecc3e5d..f063c5b 100644 --- a/backend/internal/middleware/auth.go +++ b/backend/internal/middleware/auth.go @@ -73,4 +73,4 @@ func (e UnauthorizedError) Error() string { func NewUnauthorizedError(message string) UnauthorizedError { return UnauthorizedError{Message: message} -} \ No newline at end of file +} diff --git a/backend/internal/middleware/cors.go b/backend/internal/middleware/cors.go index 20d6d90..8f49970 100644 --- a/backend/internal/middleware/cors.go +++ b/backend/internal/middleware/cors.go @@ -87,7 +87,7 @@ func NewCORSMiddleware(config CORSConfig) *CORSMiddleware { func (m *CORSMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") - + // 检查来源是否被允许 if origin != "" && m.isOriginAllowed(origin) { w.Header().Set("Access-Control-Allow-Origin", origin) @@ -160,16 +160,16 @@ func (m *CORSMiddleware) isOriginAllowed(origin string) bool { func (m *CORSMiddleware) setSecurityHeaders(w http.ResponseWriter) { // 防止点击劫持 w.Header().Set("X-Frame-Options", "DENY") - + // 防止 MIME 类型嗅探 w.Header().Set("X-Content-Type-Options", "nosniff") - + // XSS 保护 w.Header().Set("X-XSS-Protection", "1; mode=block") - + // 引用者策略 w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin") - + // 内容安全策略 (基础版) w.Header().Set("Content-Security-Policy", "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self'") -} \ No newline at end of file +} diff --git a/backend/internal/middleware/error.go b/backend/internal/middleware/error.go index a1a328b..d41780a 100644 --- a/backend/internal/middleware/error.go +++ b/backend/internal/middleware/error.go @@ -19,8 +19,8 @@ type ErrorConfig struct { EnableDetailedErrors bool // 是否启用详细错误信息 (开发环境) EnableStackTrace bool // 是否启用堆栈跟踪 EnableErrorMonitor bool // 是否启用错误监控 - IgnoreHTTPCodes []int // 忽略的HTTP状态码 (不记录为错误) - SensitiveFields []string // 敏感字段列表 (日志时隐藏) + IgnoreHTTPCodes []int // 忽略的HTTP状态码 (不记录为错误) + SensitiveFields []string // 敏感字段列表 (日志时隐藏) } // DefaultErrorConfig 默认错误配置 @@ -29,8 +29,8 @@ func DefaultErrorConfig() ErrorConfig { EnableDetailedErrors: false, // 生产环境默认关闭 EnableStackTrace: false, // 生产环境默认关闭 EnableErrorMonitor: true, - IgnoreHTTPCodes: []int{http.StatusNotFound, http.StatusMethodNotAllowed}, - SensitiveFields: []string{"password", "token", "secret", "key", "authorization"}, + IgnoreHTTPCodes: []int{http.StatusNotFound, http.StatusMethodNotAllowed}, + SensitiveFields: []string{"password", "token", "secret", "key", "authorization"}, } } @@ -111,7 +111,7 @@ func (m *ErrorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc { // handlePanic 处理panic func (m *ErrorMiddleware) handlePanic(w *errorResponseWriter, r *http.Request, err interface{}) { stack := string(debug.Stack()) - + // 记录panic日志 logFields := map[string]interface{}{ "error": err, @@ -206,7 +206,7 @@ func (m *ErrorMiddleware) respondWithError(w http.ResponseWriter, r *http.Reques // 设置HTTP状态码 httpStatus := errorx.GetHttpStatus(err.Code) - + // 设置响应头 w.Header().Set("Content-Type", "application/json") w.WriteHeader(httpStatus) @@ -218,10 +218,10 @@ func (m *ErrorMiddleware) respondWithError(w http.ResponseWriter, r *http.Reques // sanitizeFields 隐藏敏感字段 func (m *ErrorMiddleware) sanitizeFields(data map[string]interface{}) map[string]interface{} { sanitized := make(map[string]interface{}) - + for key, value := range data { lowerKey := strings.ToLower(key) - + // 检查是否为敏感字段 sensitive := false for _, sensitiveField := range m.config.SensitiveFields { @@ -230,7 +230,7 @@ func (m *ErrorMiddleware) sanitizeFields(data map[string]interface{}) map[string break } } - + if sensitive { sanitized[key] = "***REDACTED***" } else { @@ -242,7 +242,7 @@ func (m *ErrorMiddleware) sanitizeFields(data map[string]interface{}) map[string } } } - + return sanitized } @@ -322,4 +322,4 @@ var CommonErrors = struct { Code: 429, Msg: "Rate Limit Exceeded", }, -} \ No newline at end of file +} diff --git a/backend/internal/middleware/logger.go b/backend/internal/middleware/logger.go index 288d1b0..3c6c941 100644 --- a/backend/internal/middleware/logger.go +++ b/backend/internal/middleware/logger.go @@ -15,8 +15,8 @@ import ( // LoggerConfig 日志配置 type LoggerConfig struct { - EnableRequestBody bool // 是否记录请求体 - EnableResponseBody bool // 是否记录响应体 + EnableRequestBody bool // 是否记录请求体 + EnableResponseBody bool // 是否记录响应体 MaxBodySize int64 // 最大记录的请求/响应体大小 SkipPaths []string // 跳过记录的路径 SlowRequestDuration time.Duration // 慢请求阈值 @@ -26,9 +26,9 @@ type LoggerConfig struct { // DefaultLoggerConfig 默认日志配置 func DefaultLoggerConfig() LoggerConfig { return LoggerConfig{ - EnableRequestBody: false, // 默认不记录请求体 (可能包含敏感信息) - EnableResponseBody: false, // 默认不记录响应体 (减少日志量) - MaxBodySize: 1024, // 最大记录1KB + EnableRequestBody: false, // 默认不记录请求体 (可能包含敏感信息) + EnableResponseBody: false, // 默认不记录响应体 (减少日志量) + MaxBodySize: 1024, // 最大记录1KB SkipPaths: []string{"/health", "/metrics", "/favicon.ico"}, SlowRequestDuration: 1 * time.Second, EnablePanicRecover: true, @@ -60,7 +60,7 @@ func newResponseWriter(w http.ResponseWriter) *responseWriter { return &responseWriter{ ResponseWriter: w, status: http.StatusOK, - body: &bytes.Buffer{}, + body: &bytes.Buffer{}, } } @@ -68,12 +68,12 @@ func newResponseWriter(w http.ResponseWriter) *responseWriter { func (rw *responseWriter) Write(b []byte) (int, error) { size, err := rw.ResponseWriter.Write(b) rw.size += int64(size) - + // 记录响应体 (如果启用) if rw.body.Len() < int(1024) { // 限制缓存大小 rw.body.Write(b) } - + return size, err } @@ -163,7 +163,7 @@ func (m *LoggerMiddleware) generateRequestID(r *http.Request) string { if requestID := r.Header.Get("X-Request-ID"); requestID != "" { return requestID } - + // 生成新的请求ID return fmt.Sprintf("%d-%s", time.Now().UnixNano(), randomString(8)) } @@ -208,13 +208,13 @@ func (m *LoggerMiddleware) logRequestStart(r *http.Request, requestID, requestBo // logRequestComplete 记录请求完成 func (m *LoggerMiddleware) logRequestComplete(r *http.Request, requestID string, status int, size int64, duration time.Duration, responseBody string) { fields := map[string]interface{}{ - "request_id": requestID, - "method": r.Method, - "path": r.URL.Path, - "status": status, - "response_size": size, - "duration_ms": duration.Milliseconds(), - "duration": duration.String(), + "request_id": requestID, + "method": r.Method, + "path": r.URL.Path, + "status": status, + "response_size": size, + "duration_ms": duration.Milliseconds(), + "duration": duration.String(), } if responseBody != "" { @@ -267,7 +267,7 @@ func getClientIP(r *http.Request) string { return ip } } - + // 使用 RemoteAddr if ip := r.RemoteAddr; ip != "" { // 移除端口号 @@ -276,7 +276,7 @@ func getClientIP(r *http.Request) string { } return ip } - + return "unknown" } @@ -296,4 +296,4 @@ func randomString(length int) string { result[i] = charset[time.Now().UnixNano()%int64(len(charset))] } return string(result) -} \ No newline at end of file +} diff --git a/backend/internal/middleware/middleware.go b/backend/internal/middleware/middleware.go index ecc2fba..d0eddc0 100644 --- a/backend/internal/middleware/middleware.go +++ b/backend/internal/middleware/middleware.go @@ -13,11 +13,11 @@ import ( // MiddlewareManager 中间件管理器 type MiddlewareManager struct { - config config.Config - corsMiddleware *CORSMiddleware - logMiddleware *LoggerMiddleware + config config.Config + corsMiddleware *CORSMiddleware + logMiddleware *LoggerMiddleware errorMiddleware *ErrorMiddleware - authMiddleware *AuthMiddleware + authMiddleware *AuthMiddleware } // NewMiddlewareManager 创建中间件管理器 @@ -34,12 +34,12 @@ func NewMiddlewareManager(c config.Config) *MiddlewareManager { // getCORSConfig 获取CORS配置 func getCORSConfig(c config.Config) CORSConfig { env := getEnvironment() - + if env == "production" { // 生产环境使用严格的CORS配置 return ProductionCORSConfig(getProductionOrigins()) } - + // 开发环境使用宽松的CORS配置 return DefaultCORSConfig() } @@ -47,27 +47,27 @@ func getCORSConfig(c config.Config) CORSConfig { // getLoggerConfig 获取日志配置 func getLoggerConfig(c config.Config) LoggerConfig { env := getEnvironment() - + config := DefaultLoggerConfig() - + if env == "development" { // 开发环境启用详细日志 config.EnableRequestBody = true config.EnableResponseBody = true config.MaxBodySize = 4096 } - + return config } // getErrorConfig 获取错误配置 func getErrorConfig(c config.Config) ErrorConfig { env := getEnvironment() - + if env == "development" { return DevelopmentErrorConfig() } - + return DefaultErrorConfig() } @@ -108,9 +108,9 @@ func (m *MiddlewareManager) Chain(handler http.HandlerFunc, middlewares ...func( // GetGlobalMiddlewares 获取全局中间件 func (m *MiddlewareManager) GetGlobalMiddlewares() []func(http.HandlerFunc) http.HandlerFunc { return []func(http.HandlerFunc) http.HandlerFunc{ - m.errorMiddleware.Handle, // 错误处理 (最外层) - m.corsMiddleware.Handle, // CORS 处理 - m.logMiddleware.Handle, // 日志记录 + m.errorMiddleware.Handle, // 错误处理 (最外层) + m.corsMiddleware.Handle, // CORS 处理 + m.logMiddleware.Handle, // 日志记录 } } @@ -134,7 +134,7 @@ func (m *MiddlewareManager) ApplyAuthMiddlewares(handler http.HandlerFunc) http. func (m *MiddlewareManager) HealthCheck(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - w.Write([]byte(`{"status":"ok","timestamp":"` + + w.Write([]byte(`{"status":"ok","timestamp":"` + time.Now().Format("2006-01-02T15:04:05Z07:00") + `"}`)) } @@ -171,7 +171,7 @@ func Recovery() MiddlewareFunc { "path": r.URL.Path, } logx.WithContext(r.Context()).Errorf("Panic recovered in Recovery middleware: %+v", fields) - + http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() @@ -188,10 +188,10 @@ func RequestID() MiddlewareFunc { if requestID == "" { requestID = generateRequestID() } - + w.Header().Set("X-Request-ID", requestID) r.Header.Set("X-Request-ID", requestID) - + next(w, r) }) } @@ -200,4 +200,4 @@ func RequestID() MiddlewareFunc { // generateRequestID 生成请求ID func generateRequestID() string { return randomString(16) -} \ No newline at end of file +} diff --git a/backend/internal/model/categorymodel.go b/backend/internal/model/categorymodel.go index db7c6ae..41122ea 100755 --- a/backend/internal/model/categorymodel.go +++ b/backend/internal/model/categorymodel.go @@ -3,8 +3,8 @@ package model import ( "context" "fmt" - "strings" "github.com/zeromicro/go-zero/core/stores/sqlx" + "strings" ) var _ CategoryModel = (*customCategoryModel)(nil) @@ -39,20 +39,20 @@ func (m *customCategoryModel) withSession(session sqlx.Session) CategoryModel { func (m *customCategoryModel) FindList(ctx context.Context, page, pageSize int, keyword string) ([]*Category, error) { var conditions []string var args []interface{} - + if keyword != "" { conditions = append(conditions, "(`name` LIKE ? OR `description` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + offset := (page - 1) * pageSize args = append(args, pageSize, offset) - + query := fmt.Sprintf("select %s from %s%s ORDER BY `created_at` DESC LIMIT ? OFFSET ?", categoryRows, m.table, whereClause) var resp []*Category err := m.conn.QueryRowsCtx(ctx, &resp, query, args...) @@ -63,17 +63,17 @@ func (m *customCategoryModel) FindList(ctx context.Context, page, pageSize int, func (m *customCategoryModel) Count(ctx context.Context, keyword string) (int64, error) { var conditions []string var args []interface{} - + if keyword != "" { conditions = append(conditions, "(`name` LIKE ? OR `description` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + query := fmt.Sprintf("select count(*) from %s%s", m.table, whereClause) var count int64 err := m.conn.QueryRowCtx(ctx, &count, query, args...) diff --git a/backend/internal/model/photomodel.go b/backend/internal/model/photomodel.go index ba55c3e..c3b93f1 100755 --- a/backend/internal/model/photomodel.go +++ b/backend/internal/model/photomodel.go @@ -3,8 +3,8 @@ package model import ( "context" "fmt" - "strings" "github.com/zeromicro/go-zero/core/stores/sqlx" + "strings" ) var _ PhotoModel = (*customPhotoModel)(nil) @@ -39,30 +39,30 @@ func (m *customPhotoModel) withSession(session sqlx.Session) PhotoModel { func (m *customPhotoModel) FindList(ctx context.Context, page, pageSize int, categoryId, userId int64, keyword string) ([]*Photo, error) { var conditions []string var args []interface{} - + if categoryId > 0 { conditions = append(conditions, "`category_id` = ?") args = append(args, categoryId) } - + if userId > 0 { conditions = append(conditions, "`user_id` = ?") args = append(args, userId) } - + if keyword != "" { conditions = append(conditions, "(`title` LIKE ? OR `description` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + offset := (page - 1) * pageSize args = append(args, pageSize, offset) - + query := fmt.Sprintf("select %s from %s%s ORDER BY `created_at` DESC LIMIT ? OFFSET ?", photoRows, m.table, whereClause) var resp []*Photo err := m.conn.QueryRowsCtx(ctx, &resp, query, args...) @@ -73,27 +73,27 @@ func (m *customPhotoModel) FindList(ctx context.Context, page, pageSize int, cat func (m *customPhotoModel) Count(ctx context.Context, categoryId, userId int64, keyword string) (int64, error) { var conditions []string var args []interface{} - + if categoryId > 0 { conditions = append(conditions, "`category_id` = ?") args = append(args, categoryId) } - + if userId > 0 { conditions = append(conditions, "`user_id` = ?") args = append(args, userId) } - + if keyword != "" { conditions = append(conditions, "(`title` LIKE ? OR `description` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + query := fmt.Sprintf("select count(*) from %s%s", m.table, whereClause) var count int64 err := m.conn.QueryRowCtx(ctx, &count, query, args...) diff --git a/backend/internal/model/usermodel.go b/backend/internal/model/usermodel.go index 20bd4bd..f2fe634 100755 --- a/backend/internal/model/usermodel.go +++ b/backend/internal/model/usermodel.go @@ -3,8 +3,8 @@ package model import ( "context" "fmt" - "strings" "github.com/zeromicro/go-zero/core/stores/sqlx" + "strings" ) var _ UserModel = (*customUserModel)(nil) @@ -39,20 +39,20 @@ func (m *customUserModel) withSession(session sqlx.Session) UserModel { func (m *customUserModel) FindList(ctx context.Context, page, pageSize int, keyword string) ([]*User, error) { var conditions []string var args []interface{} - + if keyword != "" { conditions = append(conditions, "(`username` LIKE ? OR `email` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + offset := (page - 1) * pageSize args = append(args, pageSize, offset) - + query := fmt.Sprintf("select %s from %s%s ORDER BY `created_at` DESC LIMIT ? OFFSET ?", userRows, m.table, whereClause) var resp []*User err := m.conn.QueryRowsCtx(ctx, &resp, query, args...) @@ -63,17 +63,17 @@ func (m *customUserModel) FindList(ctx context.Context, page, pageSize int, keyw func (m *customUserModel) Count(ctx context.Context, keyword string) (int64, error) { var conditions []string var args []interface{} - + if keyword != "" { conditions = append(conditions, "(`username` LIKE ? OR `email` LIKE ?)") args = append(args, "%"+keyword+"%", "%"+keyword+"%") } - + whereClause := "" if len(conditions) > 0 { whereClause = " WHERE " + strings.Join(conditions, " AND ") } - + query := fmt.Sprintf("select count(*) from %s%s", m.table, whereClause) var count int64 err := m.conn.QueryRowCtx(ctx, &count, query, args...) diff --git a/backend/internal/svc/servicecontext.go b/backend/internal/svc/servicecontext.go index ae886f7..bca9316 100644 --- a/backend/internal/svc/servicecontext.go +++ b/backend/internal/svc/servicecontext.go @@ -2,17 +2,17 @@ package svc import ( "fmt" + "github.com/zeromicro/go-zero/core/stores/sqlx" "gorm.io/gorm" "photography-backend/internal/config" "photography-backend/internal/middleware" "photography-backend/internal/model" "photography-backend/pkg/utils/database" - "github.com/zeromicro/go-zero/core/stores/sqlx" ) type ServiceContext struct { - Config config.Config - DB *gorm.DB + Config config.Config + DB *gorm.DB UserModel model.UserModel PhotoModel model.PhotoModel CategoryModel model.CategoryModel @@ -24,13 +24,13 @@ func NewServiceContext(c config.Config) *ServiceContext { if err != nil { panic(err) } - + // Create sqlx connection for go-zero models sqlxConn := sqlx.NewSqlConn(getSQLDriverName(c.Database.Driver), getSQLDataSource(c.Database)) - + return &ServiceContext{ - Config: c, - DB: db, + Config: c, + DB: db, UserModel: model.NewUserModel(sqlxConn), PhotoModel: model.NewPhotoModel(sqlxConn), CategoryModel: model.NewCategoryModel(sqlxConn), diff --git a/backend/pkg/constants/constants.go b/backend/pkg/constants/constants.go index 4f6188c..c5010d5 100644 --- a/backend/pkg/constants/constants.go +++ b/backend/pkg/constants/constants.go @@ -4,25 +4,25 @@ const ( // 用户状态 UserStatusActive = 1 UserStatusInactive = 0 - + // 文件上传 MaxFileSize = 10 << 20 // 10MB - + // 图片类型 ImageTypeJPEG = "image/jpeg" ImageTypePNG = "image/png" ImageTypeGIF = "image/gif" ImageTypeWEBP = "image/webp" - + // 缩略图尺寸 ThumbnailWidth = 300 ThumbnailHeight = 300 - + // JWT 过期时间 TokenExpireDuration = 24 * 60 * 60 // 24小时 - + // 分页默认值 DefaultPage = 1 DefaultPageSize = 10 MaxPageSize = 100 -) \ No newline at end of file +) diff --git a/backend/pkg/errorx/errorx.go b/backend/pkg/errorx/errorx.go index 2aa30ec..07e1c45 100644 --- a/backend/pkg/errorx/errorx.go +++ b/backend/pkg/errorx/errorx.go @@ -7,14 +7,14 @@ import ( const ( // 通用错误代码 - Success = 0 - ServerError = 500 - ParamError = 400 - AuthError = 401 - NotFound = 404 - Forbidden = 403 - InvalidParameter = 400 // 与 ParamError 统一 - + Success = 0 + ServerError = 500 + ParamError = 400 + AuthError = 401 + NotFound = 404 + Forbidden = 403 + InvalidParameter = 400 // 与 ParamError 统一 + // 业务错误代码 UserNotFound = 1001 UserExists = 1002 @@ -22,32 +22,32 @@ const ( InvalidPassword = 1004 TokenExpired = 1005 TokenInvalid = 1006 - + PhotoNotFound = 2001 PhotoUploadFail = 2002 - + CategoryNotFound = 3001 CategoryExists = 3002 ) var codeText = map[int]string{ - Success: "Success", - ServerError: "Server Error", - ParamError: "Parameter Error", // ParamError 和 InvalidParameter 都映射到这里 - AuthError: "Authentication Error", - NotFound: "Not Found", - Forbidden: "Forbidden", - + Success: "Success", + ServerError: "Server Error", + ParamError: "Parameter Error", // ParamError 和 InvalidParameter 都映射到这里 + AuthError: "Authentication Error", + NotFound: "Not Found", + Forbidden: "Forbidden", + UserNotFound: "User Not Found", UserExists: "User Already Exists", UserDisabled: "User Disabled", InvalidPassword: "Invalid Password", TokenExpired: "Token Expired", TokenInvalid: "Token Invalid", - + PhotoNotFound: "Photo Not Found", PhotoUploadFail: "Photo Upload Failed", - + CategoryNotFound: "Category Not Found", CategoryExists: "Category Already Exists", } @@ -83,7 +83,7 @@ func GetHttpStatus(code int) int { switch code { case Success: return http.StatusOK - case ParamError: // ParamError 和 InvalidParameter 都是 400,所以只需要一个 case + case ParamError: // ParamError 和 InvalidParameter 都是 400,所以只需要一个 case return http.StatusBadRequest case AuthError, TokenExpired, TokenInvalid: return http.StatusUnauthorized @@ -96,4 +96,4 @@ func GetHttpStatus(code int) int { default: return http.StatusInternalServerError } -} \ No newline at end of file +} diff --git a/backend/pkg/migration/migration.go b/backend/pkg/migration/migration.go index 3e9f154..4e1d669 100644 --- a/backend/pkg/migration/migration.go +++ b/backend/pkg/migration/migration.go @@ -21,10 +21,10 @@ type Migration struct { // MigrationRecord 数据库中的迁移记录 type MigrationRecord struct { - ID uint `gorm:"primaryKey"` - Version string `gorm:"uniqueIndex;size:255;not null"` - Description string `gorm:"size:500"` - Applied bool `gorm:"default:false"` + ID uint `gorm:"primaryKey"` + Version string `gorm:"uniqueIndex;size:255;not null"` + Description string `gorm:"size:500"` + Applied bool `gorm:"default:false"` AppliedAt time.Time CreatedAt time.Time UpdatedAt time.Time @@ -66,7 +66,7 @@ func (m *Migrator) GetAppliedMigrations() ([]string, error) { if err := m.db.Where("applied = ?", true).Order("version ASC").Find(&records).Error; err != nil { return nil, err } - + versions := make([]string, len(records)) for i, record := range records { versions[i] = record.Version @@ -80,24 +80,24 @@ func (m *Migrator) GetPendingMigrations() ([]Migration, error) { if err != nil { return nil, err } - + appliedMap := make(map[string]bool) for _, version := range appliedVersions { appliedMap[version] = true } - + var pendingMigrations []Migration for _, migration := range m.migrations { if !appliedMap[migration.Version] { pendingMigrations = append(pendingMigrations, migration) } } - + // 按版本号排序 sort.Slice(pendingMigrations, func(i, j int) bool { return pendingMigrations[i].Version < pendingMigrations[j].Version }) - + return pendingMigrations, nil } @@ -106,39 +106,39 @@ func (m *Migrator) Up() error { if err := m.initMigrationTable(); err != nil { return err } - + pendingMigrations, err := m.GetPendingMigrations() if err != nil { return err } - + if len(pendingMigrations) == 0 { log.Println("No pending migrations") return nil } - + for _, migration := range pendingMigrations { log.Printf("Applying migration %s: %s", migration.Version, migration.Description) - + // 开始事务 tx := m.db.Begin() if tx.Error != nil { return tx.Error } - + // 执行迁移SQL if err := m.executeSQL(tx, migration.UpSQL); err != nil { tx.Rollback() return fmt.Errorf("failed to apply migration %s: %v", migration.Version, err) } - + // 记录迁移状态 (使用UPSERT) now := time.Now() - + // 检查记录是否已存在 var existingRecord MigrationRecord err := tx.Where("version = ?", migration.Version).First(&existingRecord).Error - + if err == gorm.ErrRecordNotFound { // 创建新记录 record := MigrationRecord{ @@ -167,15 +167,15 @@ func (m *Migrator) Up() error { tx.Rollback() return fmt.Errorf("failed to check migration record %s: %v", migration.Version, err) } - + // 提交事务 if err := tx.Commit().Error; err != nil { return fmt.Errorf("failed to commit migration %s: %v", migration.Version, err) } - + log.Printf("Successfully applied migration %s", migration.Version) } - + return nil } @@ -184,58 +184,58 @@ func (m *Migrator) Down(steps int) error { if err := m.initMigrationTable(); err != nil { return err } - + appliedVersions, err := m.GetAppliedMigrations() if err != nil { return err } - + if len(appliedVersions) == 0 { log.Println("No applied migrations to rollback") return nil } - + // 获取要回滚的迁移(从最新开始) rollbackCount := steps if rollbackCount > len(appliedVersions) { rollbackCount = len(appliedVersions) } - + for i := len(appliedVersions) - 1; i >= len(appliedVersions)-rollbackCount; i-- { version := appliedVersions[i] migration := m.findMigrationByVersion(version) if migration == nil { return fmt.Errorf("migration %s not found in migration definitions", version) } - + log.Printf("Rolling back migration %s: %s", migration.Version, migration.Description) - + // 开始事务 tx := m.db.Begin() if tx.Error != nil { return tx.Error } - + // 执行回滚SQL if err := m.executeSQL(tx, migration.DownSQL); err != nil { tx.Rollback() return fmt.Errorf("failed to rollback migration %s: %v", migration.Version, err) } - + // 更新迁移状态 if err := tx.Model(&MigrationRecord{}).Where("version = ?", version).Update("applied", false).Error; err != nil { tx.Rollback() return fmt.Errorf("failed to update migration record %s: %v", migration.Version, err) } - + // 提交事务 if err := tx.Commit().Error; err != nil { return fmt.Errorf("failed to commit rollback %s: %v", migration.Version, err) } - + log.Printf("Successfully rolled back migration %s", migration.Version) } - + return nil } @@ -244,27 +244,27 @@ func (m *Migrator) Status() error { if err := m.initMigrationTable(); err != nil { return err } - + appliedVersions, err := m.GetAppliedMigrations() if err != nil { return err } - + appliedMap := make(map[string]bool) for _, version := range appliedVersions { appliedMap[version] = true } - + // 排序所有迁移 allMigrations := m.migrations sort.Slice(allMigrations, func(i, j int) bool { return allMigrations[i].Version < allMigrations[j].Version }) - + fmt.Println("Migration Status:") fmt.Println("Version | Status | Description") fmt.Println("---------------|---------|----------------------------------") - + for _, migration := range allMigrations { status := "Pending" if appliedMap[migration.Version] { @@ -272,7 +272,7 @@ func (m *Migrator) Status() error { } fmt.Printf("%-14s | %-7s | %s\n", migration.Version, status, migration.Description) } - + return nil } @@ -280,18 +280,18 @@ func (m *Migrator) Status() error { func (m *Migrator) executeSQL(tx *gorm.DB, sqlStr string) error { // 分割SQL语句(按分号分割) statements := strings.Split(sqlStr, ";") - + for _, statement := range statements { statement = strings.TrimSpace(statement) if statement == "" { continue } - + if err := tx.Exec(statement).Error; err != nil { return err } } - + return nil } @@ -308,25 +308,25 @@ func (m *Migrator) findMigrationByVersion(version string) *Migration { // Reset 重置数据库(谨慎使用) func (m *Migrator) Reset() error { log.Println("WARNING: This will drop all tables and reset the database!") - + // 获取所有应用的迁移 appliedVersions, err := m.GetAppliedMigrations() if err != nil { return err } - + // 回滚所有迁移 if len(appliedVersions) > 0 { if err := m.Down(len(appliedVersions)); err != nil { return err } } - + // 删除迁移表 if err := m.db.Migrator().DropTable(&MigrationRecord{}); err != nil { return fmt.Errorf("failed to drop migration table: %v", err) } - + log.Println("Database reset completed") return nil } @@ -337,25 +337,25 @@ func (m *Migrator) Migrate(steps int) error { if err != nil { return err } - + if len(pendingMigrations) == 0 { log.Println("No pending migrations") return nil } - + migrateCount := steps if steps <= 0 || steps > len(pendingMigrations) { migrateCount = len(pendingMigrations) } - + // 临时修改migrations列表,只包含要执行的迁移 originalMigrations := m.migrations m.migrations = pendingMigrations[:migrateCount] - + err = m.Up() - + // 恢复原始migrations列表 m.migrations = originalMigrations - + return err -} \ No newline at end of file +} diff --git a/backend/pkg/migration/migrations.go b/backend/pkg/migration/migrations.go index f498be4..97206d9 100644 --- a/backend/pkg/migration/migrations.go +++ b/backend/pkg/migration/migrations.go @@ -309,13 +309,13 @@ func GetLatestMigrationVersion() string { if len(migrations) == 0 { return "" } - + latest := migrations[0] for _, migration := range migrations { if migration.Version > latest.Version { latest = migration } } - + return latest.Version -} \ No newline at end of file +} diff --git a/backend/pkg/response/response.go b/backend/pkg/response/response.go index a26f461..8d3e572 100644 --- a/backend/pkg/response/response.go +++ b/backend/pkg/response/response.go @@ -2,7 +2,7 @@ package response import ( "net/http" - + "github.com/zeromicro/go-zero/rest/httpx" "photography-backend/pkg/errorx" ) @@ -39,4 +39,4 @@ func Success(w http.ResponseWriter, data interface{}) { func Error(w http.ResponseWriter, err error) { Response(w, nil, err) -} \ No newline at end of file +} diff --git a/backend/pkg/utils/database/database.go b/backend/pkg/utils/database/database.go index a70cde9..810750a 100644 --- a/backend/pkg/utils/database/database.go +++ b/backend/pkg/utils/database/database.go @@ -13,7 +13,7 @@ import ( ) type Config struct { - Driver string `json:"driver"` // mysql, postgres, sqlite + Driver string `json:"driver"` // mysql, postgres, sqlite Host string `json:"host,optional"` Port int `json:"port,optional"` Username string `json:"username,optional"` @@ -27,7 +27,7 @@ type Config struct { func NewDB(config Config) (*gorm.DB, error) { var db *gorm.DB var err error - + // 配置日志 newLogger := logger.New( log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer @@ -37,7 +37,7 @@ func NewDB(config Config) (*gorm.DB, error) { Colorful: false, // 禁用彩色打印 }, ) - + switch config.Driver { case "mysql": dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local", @@ -58,20 +58,20 @@ func NewDB(config Config) (*gorm.DB, error) { default: return nil, fmt.Errorf("unsupported database driver: %s", config.Driver) } - + if err != nil { return nil, err } - + // 设置连接池 sqlDB, err := db.DB() if err != nil { return nil, err } - + sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) - + return db, nil -} \ No newline at end of file +} diff --git a/backend/pkg/utils/file/file.go b/backend/pkg/utils/file/file.go index d1f98dd..56ebbde 100644 --- a/backend/pkg/utils/file/file.go +++ b/backend/pkg/utils/file/file.go @@ -11,7 +11,7 @@ import ( "path/filepath" "strings" "time" - + "github.com/disintegration/imaging" "github.com/google/uuid" ) @@ -64,39 +64,39 @@ func SaveFile(file multipart.File, header *multipart.FileHeader, config Config) if header.Size > config.MaxSize { return nil, fmt.Errorf("文件大小超过限制: %d bytes", config.MaxSize) } - + // 检查文件类型 contentType := header.Header.Get("Content-Type") if !IsAllowedType(contentType, config.AllowedTypes) { return nil, fmt.Errorf("不支持的文件类型: %s", contentType) } - + // 生成文件名 fileName := GenerateFileName(header.Filename) - + // 创建上传目录 uploadPath := filepath.Join(config.UploadDir, "photos") if err := os.MkdirAll(uploadPath, 0755); err != nil { return nil, fmt.Errorf("创建上传目录失败: %v", err) } - + // 完整文件路径 filePath := filepath.Join(uploadPath, fileName) - + // 创建目标文件 dst, err := os.Create(filePath) if err != nil { return nil, fmt.Errorf("创建文件失败: %v", err) } defer dst.Close() - + // 复制文件内容 file.Seek(0, 0) // 重置文件指针 _, err = io.Copy(dst, file) if err != nil { return nil, fmt.Errorf("保存文件失败: %v", err) } - + // 返回文件信息 return &FileInfo{ OriginalName: header.Filename, @@ -115,35 +115,35 @@ func CreateThumbnail(originalPath string, config Config) (*FileInfo, error) { if err != nil { return nil, fmt.Errorf("打开图片失败: %v", err) } - + // 生成缩略图文件名 ext := filepath.Ext(originalPath) baseName := strings.TrimSuffix(filepath.Base(originalPath), ext) thumbnailName := baseName + "_thumb" + ext - + // 创建缩略图目录 thumbnailDir := filepath.Join(config.UploadDir, "thumbnails") if err := os.MkdirAll(thumbnailDir, 0755); err != nil { return nil, fmt.Errorf("创建缩略图目录失败: %v", err) } - + thumbnailPath := filepath.Join(thumbnailDir, thumbnailName) - + // 调整图片大小 (最大宽度 300px,保持比例) thumbnail := imaging.Resize(src, 300, 0, imaging.Lanczos) - + // 保存缩略图 err = imaging.Save(thumbnail, thumbnailPath) if err != nil { return nil, fmt.Errorf("保存缩略图失败: %v", err) } - + // 获取文件大小 stat, err := os.Stat(thumbnailPath) if err != nil { return nil, fmt.Errorf("获取缩略图信息失败: %v", err) } - + return &FileInfo{ OriginalName: thumbnailName, FileName: thumbnailName, @@ -161,13 +161,13 @@ func UploadPhoto(file multipart.File, header *multipart.FileHeader, config Confi if err != nil { return nil, err } - + // 创建缩略图 thumbnail, err := CreateThumbnail(original.FilePath, config) if err != nil { return nil, err } - + return &UploadResult{ Original: *original, Thumbnail: *thumbnail, @@ -179,11 +179,11 @@ func DeleteFile(filePath string) error { if filePath == "" { return nil } - + if _, err := os.Stat(filePath); os.IsNotExist(err) { return nil // 文件不存在,认为删除成功 } - + return os.Remove(filePath) } @@ -194,12 +194,12 @@ func GetImageDimensions(filePath string) (width, height int, err error) { return 0, 0, err } defer file.Close() - + img, _, err := image.DecodeConfig(file) if err != nil { return 0, 0, err } - + return img.Width, img.Height, nil } @@ -210,16 +210,16 @@ func ResizeImage(srcPath, destPath string, width, height int) error { if err != nil { return fmt.Errorf("打开图片失败: %v", err) } - + // 调整图片尺寸 (正方形裁剪) resized := imaging.Fill(src, width, height, imaging.Center, imaging.Lanczos) - + // 保存调整后的图片 err = imaging.Save(resized, destPath) if err != nil { return fmt.Errorf("保存调整后的图片失败: %v", err) } - + return nil } @@ -230,15 +230,15 @@ func CreateAvatar(srcPath, destPath string, size int) error { if err != nil { return fmt.Errorf("打开图片失败: %v", err) } - + // 创建正方形头像 (居中裁剪) avatar := imaging.Fill(src, size, size, imaging.Center, imaging.Lanczos) - + // 保存为JPEG格式 (压缩优化) err = imaging.Save(avatar, destPath, imaging.JPEGQuality(85)) if err != nil { return fmt.Errorf("保存头像失败: %v", err) } - + return nil -} \ No newline at end of file +} diff --git a/backend/pkg/utils/hash/hash.go b/backend/pkg/utils/hash/hash.go index 6edcd62..dd8800a 100644 --- a/backend/pkg/utils/hash/hash.go +++ b/backend/pkg/utils/hash/hash.go @@ -31,4 +31,4 @@ func SHA256(str string) string { h := sha256.New() h.Write([]byte(str)) return hex.EncodeToString(h.Sum(nil)) -} \ No newline at end of file +} diff --git a/backend/pkg/utils/jwt/jwt.go b/backend/pkg/utils/jwt/jwt.go index d75696c..54496ad 100644 --- a/backend/pkg/utils/jwt/jwt.go +++ b/backend/pkg/utils/jwt/jwt.go @@ -2,7 +2,7 @@ package jwt import ( "time" - + "github.com/golang-jwt/jwt/v5" ) @@ -23,7 +23,7 @@ func GenerateToken(userId int64, username string, secret string, expires time.Du NotBefore: jwt.NewNumericDate(now), }, } - + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret)) } @@ -32,14 +32,14 @@ func ParseToken(tokenString string, secret string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(secret), nil }) - + if err != nil { return nil, err } - + if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } - + return nil, jwt.ErrInvalidKey -} \ No newline at end of file +} diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 2ae237b..0bab446 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -15,9 +15,12 @@ const api = axios.create({ api.interceptors.request.use( (config) => { // 可以在这里添加token等认证信息 - const token = localStorage.getItem('token') - if (token) { - config.headers.Authorization = `Bearer ${token}` + // 检查是否在浏览器环境中 + if (typeof window !== 'undefined') { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } } return config }, @@ -42,9 +45,11 @@ api.interceptors.response.use( }, (error) => { if (error.response?.status === 401) { - // 处理未授权 - localStorage.removeItem('token') - window.location.href = '/login' + // 处理未授权 - 仅在浏览器环境中执行 + if (typeof window !== 'undefined') { + localStorage.removeItem('token') + window.location.href = '/login' + } } return Promise.reject(error) }