feat(架构): 完善基础架构设计

This commit is contained in:
2025-07-02 16:17:59 +08:00
parent 03e615a8fd
commit 5b4392894f
89 changed files with 18555 additions and 3521 deletions

View File

@@ -20,7 +20,7 @@ func NewResponseBuilder() interfaces.ResponseBuilder {
// Success 成功响应
func (r *ResponseBuilder) Success(c *gin.Context, data interface{}, message ...string) {
msg := "Success"
msg := "操作成功"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -38,7 +38,7 @@ func (r *ResponseBuilder) Success(c *gin.Context, data interface{}, message ...s
// Created 创建成功响应
func (r *ResponseBuilder) Created(c *gin.Context, data interface{}, message ...string) {
msg := "Created successfully"
msg := "创建成功"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -58,7 +58,7 @@ func (r *ResponseBuilder) Created(c *gin.Context, data interface{}, message ...s
func (r *ResponseBuilder) Error(c *gin.Context, err error) {
// 根据错误类型确定状态码
statusCode := http.StatusInternalServerError
message := "Internal server error"
message := "服务器内部错误"
errorDetail := err.Error()
// 这里可以根据不同的错误类型设置不同的状态码
@@ -93,7 +93,7 @@ func (r *ResponseBuilder) BadRequest(c *gin.Context, message string, errors ...i
// Unauthorized 401错误响应
func (r *ResponseBuilder) Unauthorized(c *gin.Context, message ...string) {
msg := "Unauthorized"
msg := "用户未登录或认证已过期"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -110,7 +110,7 @@ func (r *ResponseBuilder) Unauthorized(c *gin.Context, message ...string) {
// Forbidden 403错误响应
func (r *ResponseBuilder) Forbidden(c *gin.Context, message ...string) {
msg := "Forbidden"
msg := "权限不足,无法访问此资源"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -127,7 +127,7 @@ func (r *ResponseBuilder) Forbidden(c *gin.Context, message ...string) {
// NotFound 404错误响应
func (r *ResponseBuilder) NotFound(c *gin.Context, message ...string) {
msg := "Resource not found"
msg := "请求的资源不存在"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -156,7 +156,7 @@ func (r *ResponseBuilder) Conflict(c *gin.Context, message string) {
// InternalError 500错误响应
func (r *ResponseBuilder) InternalError(c *gin.Context, message ...string) {
msg := "Internal server error"
msg := "服务器内部错误"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}
@@ -175,7 +175,7 @@ func (r *ResponseBuilder) InternalError(c *gin.Context, message ...string) {
func (r *ResponseBuilder) Paginated(c *gin.Context, data interface{}, pagination interfaces.PaginationMeta) {
response := interfaces.APIResponse{
Success: true,
Message: "Success",
Message: "查询成功",
Data: data,
Pagination: &pagination,
RequestID: r.getRequestID(c),
@@ -215,9 +215,35 @@ func BuildPagination(page, pageSize int, total int64) interfaces.PaginationMeta
// CustomResponse 自定义响应
func (r *ResponseBuilder) CustomResponse(c *gin.Context, statusCode int, data interface{}) {
var message string
switch statusCode {
case http.StatusOK:
message = "请求成功"
case http.StatusCreated:
message = "创建成功"
case http.StatusNoContent:
message = "无内容"
case http.StatusBadRequest:
message = "请求参数错误"
case http.StatusUnauthorized:
message = "认证失败"
case http.StatusForbidden:
message = "权限不足"
case http.StatusNotFound:
message = "资源不存在"
case http.StatusConflict:
message = "资源冲突"
case http.StatusTooManyRequests:
message = "请求过于频繁"
case http.StatusInternalServerError:
message = "服务器内部错误"
default:
message = "未知状态"
}
response := interfaces.APIResponse{
Success: statusCode >= 200 && statusCode < 300,
Message: http.StatusText(statusCode),
Message: message,
Data: data,
RequestID: r.getRequestID(c),
Timestamp: time.Now().Unix(),
@@ -230,7 +256,7 @@ func (r *ResponseBuilder) CustomResponse(c *gin.Context, statusCode int, data in
func (r *ResponseBuilder) ValidationError(c *gin.Context, errors interface{}) {
response := interfaces.APIResponse{
Success: false,
Message: "Validation failed",
Message: "请求参数验证失败",
Errors: errors,
RequestID: r.getRequestID(c),
Timestamp: time.Now().Unix(),
@@ -241,7 +267,7 @@ func (r *ResponseBuilder) ValidationError(c *gin.Context, errors interface{}) {
// TooManyRequests 限流错误响应
func (r *ResponseBuilder) TooManyRequests(c *gin.Context, message ...string) {
msg := "Too many requests"
msg := "请求过于频繁,请稍后再试"
if len(message) > 0 && message[0] != "" {
msg = message[0]
}

View File

@@ -8,6 +8,8 @@ import (
"time"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"go.uber.org/zap"
"tyapi-server/internal/config"
@@ -51,7 +53,7 @@ func (r *GinRouter) RegisterHandler(handler interfaces.HTTPHandler) error {
// 注册路由
r.engine.Handle(handler.GetMethod(), handler.GetPath(), append(middlewares, handler.Handle)...)
r.logger.Info("Registered HTTP handler",
r.logger.Info("已注册HTTP处理器",
zap.String("method", handler.GetMethod()),
zap.String("path", handler.GetPath()))
@@ -62,7 +64,7 @@ func (r *GinRouter) RegisterHandler(handler interfaces.HTTPHandler) error {
func (r *GinRouter) RegisterMiddleware(middleware interfaces.Middleware) error {
r.middlewares = append(r.middlewares, middleware)
r.logger.Info("Registered middleware",
r.logger.Info("已注册中间件",
zap.String("name", middleware.GetName()),
zap.Int("priority", middleware.GetPriority()))
@@ -93,7 +95,7 @@ func (r *GinRouter) Start(addr string) error {
IdleTimeout: r.config.Server.IdleTimeout,
}
r.logger.Info("Starting HTTP server", zap.String("addr", addr))
r.logger.Info("正在启动HTTP服务器", zap.String("addr", addr))
// 启动服务器
if err := r.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
@@ -109,15 +111,15 @@ func (r *GinRouter) Stop(ctx context.Context) error {
return nil
}
r.logger.Info("Stopping HTTP server...")
r.logger.Info("正在关闭HTTP服务器...")
// 优雅关闭服务器
if err := r.server.Shutdown(ctx); err != nil {
r.logger.Error("Failed to shutdown server gracefully", zap.Error(err))
r.logger.Error("优雅关闭服务器失败", zap.Error(err))
return err
}
r.logger.Info("HTTP server stopped")
r.logger.Info("HTTP服务器已关闭")
return nil
}
@@ -137,7 +139,7 @@ func (r *GinRouter) applyMiddlewares() {
for _, middleware := range r.middlewares {
if middleware.IsGlobal() {
r.engine.Use(middleware.Handle())
r.logger.Debug("Applied global middleware",
r.logger.Debug("已应用全局中间件",
zap.String("name", middleware.GetName()),
zap.Int("priority", middleware.GetPriority()))
}
@@ -156,6 +158,18 @@ func (r *GinRouter) SetupDefaultRoutes() {
})
})
// 详细健康检查
r.engine.GET("/health/detailed", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"status": "healthy",
"timestamp": time.Now().Unix(),
"service": r.config.App.Name,
"version": r.config.App.Version,
"uptime": time.Now().Unix(),
"environment": r.config.App.Env,
})
})
// API信息
r.engine.GET("/info", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
@@ -166,11 +180,37 @@ func (r *GinRouter) SetupDefaultRoutes() {
})
})
// Swagger文档路由 (仅在开发环境启用)
if !r.config.App.IsProduction() {
// Swagger UI
r.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
// API文档重定向
r.engine.GET("/docs", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "/swagger/index.html")
})
// API文档信息
r.engine.GET("/api/docs", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"swagger_ui": fmt.Sprintf("http://%s/swagger/index.html", c.Request.Host),
"openapi_json": fmt.Sprintf("http://%s/swagger/doc.json", c.Request.Host),
"redoc": fmt.Sprintf("http://%s/redoc", c.Request.Host),
"message": "API文档已可用",
})
})
r.logger.Info("Swagger documentation enabled",
zap.String("swagger_url", "/swagger/index.html"),
zap.String("docs_url", "/docs"),
zap.String("api_docs_url", "/api/docs"))
}
// 404处理
r.engine.NoRoute(func(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"message": "Route not found",
"message": "路由未找到",
"path": c.Request.URL.Path,
"method": c.Request.Method,
"timestamp": time.Now().Unix(),
@@ -181,7 +221,7 @@ func (r *GinRouter) SetupDefaultRoutes() {
r.engine.NoMethod(func(c *gin.Context) {
c.JSON(http.StatusMethodNotAllowed, gin.H{
"success": false,
"message": "Method not allowed",
"message": "请求方法不允许",
"path": c.Request.URL.Path,
"method": c.Request.Method,
"timestamp": time.Now().Unix(),

View File

@@ -42,13 +42,13 @@ func (v *RequestValidator) Validate(c *gin.Context, dto interface{}) error {
// ValidateQuery 验证查询参数
func (v *RequestValidator) ValidateQuery(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindQuery(dto); err != nil {
v.response.BadRequest(c, "Invalid query parameters", err.Error())
v.response.BadRequest(c, "查询参数格式错误", err.Error())
return err
}
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrors(err)
v.response.BadRequest(c, "Validation failed", validationErrors)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
@@ -57,13 +57,13 @@ func (v *RequestValidator) ValidateQuery(c *gin.Context, dto interface{}) error
// ValidateParam 验证路径参数
func (v *RequestValidator) ValidateParam(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindUri(dto); err != nil {
v.response.BadRequest(c, "Invalid path parameters", err.Error())
v.response.BadRequest(c, "路径参数格式错误", err.Error())
return err
}
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrors(err)
v.response.BadRequest(c, "Validation failed", validationErrors)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
@@ -73,7 +73,7 @@ func (v *RequestValidator) ValidateParam(c *gin.Context, dto interface{}) error
func (v *RequestValidator) BindAndValidate(c *gin.Context, dto interface{}) error {
// 绑定请求体
if err := c.ShouldBindJSON(dto); err != nil {
v.response.BadRequest(c, "Invalid request body", err.Error())
v.response.BadRequest(c, "请求体格式错误", err.Error())
return err
}
@@ -115,44 +115,74 @@ func (v *RequestValidator) getErrorMessage(fieldError validator.FieldError) stri
tag := fieldError.Tag()
param := fieldError.Param()
fieldDisplayName := v.getFieldDisplayName(field)
switch tag {
case "required":
return fmt.Sprintf("%s is required", field)
return fmt.Sprintf("%s 不能为空", fieldDisplayName)
case "email":
return fmt.Sprintf("%s must be a valid email address", field)
return fmt.Sprintf("%s 必须是有效的邮箱地址", fieldDisplayName)
case "min":
return fmt.Sprintf("%s must be at least %s characters", field, param)
return fmt.Sprintf("%s 长度不能少于 %s 位", fieldDisplayName, param)
case "max":
return fmt.Sprintf("%s must be at most %s characters", field, param)
return fmt.Sprintf("%s 长度不能超过 %s 位", fieldDisplayName, param)
case "len":
return fmt.Sprintf("%s must be exactly %s characters", field, param)
return fmt.Sprintf("%s 长度必须为 %s 位", fieldDisplayName, param)
case "gt":
return fmt.Sprintf("%s must be greater than %s", field, param)
return fmt.Sprintf("%s 必须大于 %s", fieldDisplayName, param)
case "gte":
return fmt.Sprintf("%s must be greater than or equal to %s", field, param)
return fmt.Sprintf("%s 必须大于等于 %s", fieldDisplayName, param)
case "lt":
return fmt.Sprintf("%s must be less than %s", field, param)
return fmt.Sprintf("%s 必须小于 %s", fieldDisplayName, param)
case "lte":
return fmt.Sprintf("%s must be less than or equal to %s", field, param)
return fmt.Sprintf("%s 必须小于等于 %s", fieldDisplayName, param)
case "oneof":
return fmt.Sprintf("%s must be one of [%s]", field, param)
return fmt.Sprintf("%s 必须是以下值之一:[%s]", fieldDisplayName, param)
case "url":
return fmt.Sprintf("%s must be a valid URL", field)
return fmt.Sprintf("%s 必须是有效的URL地址", fieldDisplayName)
case "alpha":
return fmt.Sprintf("%s must contain only alphabetic characters", field)
return fmt.Sprintf("%s 只能包含字母", fieldDisplayName)
case "alphanum":
return fmt.Sprintf("%s must contain only alphanumeric characters", field)
return fmt.Sprintf("%s 只能包含字母和数字", fieldDisplayName)
case "numeric":
return fmt.Sprintf("%s must be numeric", field)
return fmt.Sprintf("%s 必须是数字", fieldDisplayName)
case "phone":
return fmt.Sprintf("%s must be a valid phone number", field)
return fmt.Sprintf("%s 必须是有效的手机号", fieldDisplayName)
case "username":
return fmt.Sprintf("%s must be a valid username", field)
return fmt.Sprintf("%s 格式不正确,只能包含字母、数字、下划线,且不能以数字开头", fieldDisplayName)
case "strong_password":
return fmt.Sprintf("%s 强度不足必须包含大小写字母和数字且不少于8位", fieldDisplayName)
case "eqfield":
return fmt.Sprintf("%s 必须与 %s 一致", fieldDisplayName, v.getFieldDisplayName(param))
default:
return fmt.Sprintf("%s is invalid", field)
return fmt.Sprintf("%s 格式不正确", fieldDisplayName)
}
}
// getFieldDisplayName 获取字段显示名称(中文)
func (v *RequestValidator) getFieldDisplayName(field string) string {
fieldNames := map[string]string{
"phone": "手机号",
"password": "密码",
"confirm_password": "确认密码",
"old_password": "原密码",
"new_password": "新密码",
"confirm_new_password": "确认新密码",
"code": "验证码",
"username": "用户名",
"email": "邮箱",
"display_name": "显示名称",
"scene": "使用场景",
"Password": "密码",
"NewPassword": "新密码",
}
if displayName, exists := fieldNames[field]; exists {
return displayName
}
return field
}
// toSnakeCase 转换为snake_case
func (v *RequestValidator) toSnakeCase(str string) string {
var result strings.Builder

View File

@@ -0,0 +1,294 @@
package http
import (
"strings"
"tyapi-server/internal/shared/interfaces"
"github.com/gin-gonic/gin"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_translations "github.com/go-playground/validator/v10/translations/zh"
)
// RequestValidatorZh 中文验证器实现
type RequestValidatorZh struct {
validator *validator.Validate
translator ut.Translator
response interfaces.ResponseBuilder
}
// NewRequestValidatorZh 创建支持中文翻译的请求验证器
func NewRequestValidatorZh(response interfaces.ResponseBuilder) interfaces.RequestValidator {
// 创建验证器实例
validate := validator.New()
// 创建中文locale
zhLocale := zh.New()
uni := ut.New(zhLocale, zhLocale)
// 获取中文翻译器
trans, _ := uni.GetTranslator("zh")
// 注册中文翻译
zh_translations.RegisterDefaultTranslations(validate, trans)
// 注册自定义验证器
registerCustomValidatorsZh(validate, trans)
return &RequestValidatorZh{
validator: validate,
translator: trans,
response: response,
}
}
// Validate 验证请求体
func (v *RequestValidatorZh) Validate(c *gin.Context, dto interface{}) error {
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrorsZh(err)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
}
// ValidateQuery 验证查询参数
func (v *RequestValidatorZh) ValidateQuery(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindQuery(dto); err != nil {
v.response.BadRequest(c, "查询参数格式错误", err.Error())
return err
}
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrorsZh(err)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
}
// ValidateParam 验证路径参数
func (v *RequestValidatorZh) ValidateParam(c *gin.Context, dto interface{}) error {
if err := c.ShouldBindUri(dto); err != nil {
v.response.BadRequest(c, "路径参数格式错误", err.Error())
return err
}
if err := v.validator.Struct(dto); err != nil {
validationErrors := v.formatValidationErrorsZh(err)
v.response.ValidationError(c, validationErrors)
return err
}
return nil
}
// BindAndValidate 绑定并验证请求
func (v *RequestValidatorZh) BindAndValidate(c *gin.Context, dto interface{}) error {
// 绑定请求体
if err := c.ShouldBindJSON(dto); err != nil {
v.response.BadRequest(c, "请求体格式错误", err.Error())
return err
}
// 验证数据
return v.Validate(c, dto)
}
// formatValidationErrorsZh 格式化验证错误(中文翻译版)
func (v *RequestValidatorZh) formatValidationErrorsZh(err error) map[string][]string {
errors := make(map[string][]string)
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, fieldError := range validationErrors {
fieldName := v.getFieldNameZh(fieldError)
// 首先尝试使用翻译器获取翻译后的错误消息
errorMessage := fieldError.Translate(v.translator)
// 如果翻译后的消息包含英文字段名,则替换为中文字段名
fieldDisplayName := v.getFieldDisplayName(fieldError.Field())
if fieldDisplayName != fieldError.Field() {
// 替换字段名为中文
errorMessage = strings.ReplaceAll(errorMessage, fieldError.Field(), fieldDisplayName)
}
if _, exists := errors[fieldName]; !exists {
errors[fieldName] = []string{}
}
errors[fieldName] = append(errors[fieldName], errorMessage)
}
}
return errors
}
// getFieldNameZh 获取字段名JSON标签优先
func (v *RequestValidatorZh) getFieldNameZh(fieldError validator.FieldError) string {
fieldName := fieldError.Field()
return v.toSnakeCase(fieldName)
}
// getFieldDisplayName 获取字段显示名称(中文)
func (v *RequestValidatorZh) getFieldDisplayName(field string) string {
fieldNames := map[string]string{
"phone": "手机号",
"password": "密码",
"confirm_password": "确认密码",
"old_password": "原密码",
"new_password": "新密码",
"confirm_new_password": "确认新密码",
"code": "验证码",
"username": "用户名",
"email": "邮箱",
"display_name": "显示名称",
"scene": "使用场景",
"Password": "密码",
"NewPassword": "新密码",
"ConfirmPassword": "确认密码",
}
if displayName, exists := fieldNames[field]; exists {
return displayName
}
return field
}
// toSnakeCase 转换为snake_case
func (v *RequestValidatorZh) toSnakeCase(str string) string {
var result strings.Builder
for i, r := range str {
if i > 0 && (r >= 'A' && r <= 'Z') {
result.WriteRune('_')
}
result.WriteRune(r)
}
return strings.ToLower(result.String())
}
// registerCustomValidatorsZh 注册自定义验证器和中文翻译
func registerCustomValidatorsZh(v *validator.Validate, trans ut.Translator) {
// 注册手机号验证器
v.RegisterValidation("phone", validatePhoneZh)
v.RegisterTranslation("phone", trans, func(ut ut.Translator) error {
return ut.Add("phone", "{0}必须是有效的手机号", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("phone", fe.Field())
return t
})
// 注册用户名验证器
v.RegisterValidation("username", validateUsernameZh)
v.RegisterTranslation("username", trans, func(ut ut.Translator) error {
return ut.Add("username", "{0}格式不正确,只能包含字母、数字、下划线,且不能以数字开头", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("username", fe.Field())
return t
})
// 注册密码强度验证器
v.RegisterValidation("strong_password", validateStrongPasswordZh)
v.RegisterTranslation("strong_password", trans, func(ut ut.Translator) error {
return ut.Add("strong_password", "{0}强度不足必须包含大小写字母和数字且不少于8位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("strong_password", fe.Field())
return t
})
// 自定义eqfield翻译
v.RegisterTranslation("eqfield", trans, func(ut ut.Translator) error {
return ut.Add("eqfield", "{0}必须等于{1}", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("eqfield", fe.Field(), fe.Param())
return t
})
}
// validatePhoneZh 验证手机号
func validatePhoneZh(fl validator.FieldLevel) bool {
phone := fl.Field().String()
if phone == "" {
return true // 空值由required标签处理
}
// 中国手机号验证11位以1开头
if len(phone) != 11 {
return false
}
if !strings.HasPrefix(phone, "1") {
return false
}
// 检查是否全是数字
for _, r := range phone {
if r < '0' || r > '9' {
return false
}
}
return true
}
// validateUsernameZh 验证用户名
func validateUsernameZh(fl validator.FieldLevel) bool {
username := fl.Field().String()
if username == "" {
return true // 空值由required标签处理
}
// 用户名规则3-30个字符只能包含字母、数字、下划线不能以数字开头
if len(username) < 3 || len(username) > 30 {
return false
}
// 不能以数字开头
if username[0] >= '0' && username[0] <= '9' {
return false
}
// 只能包含字母、数字、下划线
for _, r := range username {
if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') {
return false
}
}
return true
}
// validateStrongPasswordZh 验证密码强度
func validateStrongPasswordZh(fl validator.FieldLevel) bool {
password := fl.Field().String()
if password == "" {
return true // 空值由required标签处理
}
// 密码强度规则至少8个字符包含大小写字母、数字
if len(password) < 8 {
return false
}
hasUpper := false
hasLower := false
hasDigit := false
for _, r := range password {
switch {
case r >= 'A' && r <= 'Z':
hasUpper = true
case r >= 'a' && r <= 'z':
hasLower = true
case r >= '0' && r <= '9':
hasDigit = true
}
}
return hasUpper && hasLower && hasDigit
}
// ValidateStruct 直接验证结构体
func (v *RequestValidatorZh) ValidateStruct(dto interface{}) error {
return v.validator.Struct(dto)
}