From ed8bee3d77d42b526035ad508f8cf95bafc136c1 Mon Sep 17 00:00:00 2001 From: oneao Date: Tue, 12 Aug 2025 21:47:37 +0800 Subject: [PATCH] u --- .../go-project/base-farmework/cmd/app/main.go | 8 +-- .../app/{config.yaml => application.yaml} | 0 .../base-farmework/internal/app/api/auth.go | 1 + .../internal/app/handle/auth.go | 31 +++++++++ .../base-farmework/internal/app/router.go | 3 +- .../config.go => pkg/config/application.go} | 4 +- .../{configs/app => pkg/config}/db.go | 2 +- .../base-farmework/pkg/middleware/auth.go | 44 +++++++----- .../base-farmework/pkg/router/context.go | 69 +++++++++++++++++-- .../base-farmework/pkg/utils/jwt.go | 32 ++++++--- .../pkg/utils/response/response.go | 34 ++++----- 11 files changed, 171 insertions(+), 57 deletions(-) rename code/go-project/base-farmework/configs/app/{config.yaml => application.yaml} (100%) create mode 100644 code/go-project/base-farmework/internal/app/api/auth.go create mode 100644 code/go-project/base-farmework/internal/app/handle/auth.go rename code/go-project/base-farmework/{configs/app/config.go => pkg/config/application.go} (86%) rename code/go-project/base-farmework/{configs/app => pkg/config}/db.go (99%) diff --git a/code/go-project/base-farmework/cmd/app/main.go b/code/go-project/base-farmework/cmd/app/main.go index 4e218a1f..2865f0b4 100644 --- a/code/go-project/base-farmework/cmd/app/main.go +++ b/code/go-project/base-farmework/cmd/app/main.go @@ -1,8 +1,8 @@ package main import ( - appConfig "base-framework/configs/app" "base-framework/internal/app" + commonConfig "base-framework/pkg/config" "log" "net/http" "strconv" @@ -11,12 +11,12 @@ import ( ) func main() { - _, err := appConfig.InitAppConfig("./configs/app/config.yaml") + _, err := commonConfig.InitApplicationConfig("./configs/app/application.yaml") if err != nil { log.Fatalf("加载配置失败: %v", err) } - err = appConfig.InitDBConfig("D:\\db.yaml") + err = commonConfig.InitDBConfig("./configs/app/db.yaml") if err != nil { log.Fatalf("加载数据库配置失败: %v", err) return @@ -24,7 +24,7 @@ func main() { r := app.InitAppRouter() - err = http.ListenAndServe(":"+strconv.Itoa(appConfig.Server.Port), r) + err = http.ListenAndServe(":"+strconv.Itoa(commonConfig.Server.Port), r) if err != nil { log.Fatalf("server start failed: %v", err) } diff --git a/code/go-project/base-farmework/configs/app/config.yaml b/code/go-project/base-farmework/configs/app/application.yaml similarity index 100% rename from code/go-project/base-farmework/configs/app/config.yaml rename to code/go-project/base-farmework/configs/app/application.yaml diff --git a/code/go-project/base-farmework/internal/app/api/auth.go b/code/go-project/base-farmework/internal/app/api/auth.go new file mode 100644 index 00000000..778f64ec --- /dev/null +++ b/code/go-project/base-farmework/internal/app/api/auth.go @@ -0,0 +1 @@ +package api diff --git a/code/go-project/base-farmework/internal/app/handle/auth.go b/code/go-project/base-farmework/internal/app/handle/auth.go new file mode 100644 index 00000000..c4b1604f --- /dev/null +++ b/code/go-project/base-farmework/internal/app/handle/auth.go @@ -0,0 +1,31 @@ +package handle + +import "base-framework/pkg/router" + +func Login(c *router.Context) { + // 直接用 BindJSON 绑定请求体 JSON 到结构体 + var req struct { + OrgID string `json:"orgID"` + UserID string `json:"userID"` + Password string `json:"password"` + } + if err := c.BindJSON(&req); err != nil { + c.JSON(400, map[string]string{"error": "请求体格式错误"}) + return + } + + // 模拟校验 + if req.UserID == "" || req.Password == "" { + c.JSON(400, map[string]string{"error": "账号或密码不能为空"}) + return + } + + // 登录成功,返回 token 示例 + c.JSON(200, map[string]interface{}{ + "code": "0000", + "message": "登录成功", + "data": map[string]string{ + "token": "这里是token字符串", + }, + }) +} diff --git a/code/go-project/base-farmework/internal/app/router.go b/code/go-project/base-farmework/internal/app/router.go index ad167d3a..99c416b8 100644 --- a/code/go-project/base-farmework/internal/app/router.go +++ b/code/go-project/base-farmework/internal/app/router.go @@ -10,8 +10,9 @@ func InitAppRouter() *router.Router { r := router.NewRouter() r.Use(middleware.Recover()) - //r.Use(middleware.Auth()).ExcludePaths("/login") + r.Use(middleware.Auth()).ExcludePaths("/login") r.Use(middleware.Logger()) + initApi(r) return r diff --git a/code/go-project/base-farmework/configs/app/config.go b/code/go-project/base-farmework/pkg/config/application.go similarity index 86% rename from code/go-project/base-farmework/configs/app/config.go rename to code/go-project/base-farmework/pkg/config/application.go index 1384f7fa..d498464b 100644 --- a/code/go-project/base-farmework/configs/app/config.go +++ b/code/go-project/base-farmework/pkg/config/application.go @@ -1,4 +1,4 @@ -package app +package config import ( "time" @@ -20,7 +20,7 @@ var ( JWT JWTConfig ) -func InitAppConfig(configPath string) (error, error) { +func InitApplicationConfig(configPath string) (error, error) { v := viper.New() v.SetConfigFile(configPath) v.SetConfigType("yaml") diff --git a/code/go-project/base-farmework/configs/app/db.go b/code/go-project/base-farmework/pkg/config/db.go similarity index 99% rename from code/go-project/base-farmework/configs/app/db.go rename to code/go-project/base-farmework/pkg/config/db.go index 6b2f02f1..8bc062ef 100644 --- a/code/go-project/base-farmework/configs/app/db.go +++ b/code/go-project/base-farmework/pkg/config/db.go @@ -1,4 +1,4 @@ -package app +package config import ( "fmt" diff --git a/code/go-project/base-farmework/pkg/middleware/auth.go b/code/go-project/base-farmework/pkg/middleware/auth.go index a92945f9..12217f5d 100644 --- a/code/go-project/base-farmework/pkg/middleware/auth.go +++ b/code/go-project/base-farmework/pkg/middleware/auth.go @@ -1,9 +1,12 @@ package middleware import ( + "base-framework/pkg/config" "base-framework/pkg/router" "base-framework/pkg/utils" - "net/http" + "base-framework/pkg/utils/response" + "errors" + "fmt" "strings" ) @@ -13,43 +16,50 @@ func Auth() router.HandlerFunc { userIdHeader := c.Header("user_id") orgIDHeader := c.Header("org_id") - if tokenHeader == "" { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization header missing"}) - return - } - if userIdHeader == "" { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "user_id header missing"}) - return - } - if orgIDHeader == "" { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "org_id header missing"}) + // 缺少登录信息 + if tokenHeader == "" || userIdHeader == "" || orgIDHeader == "" { + response.Error(c).Code(response.CodeNoLogin).Send() return } + // Bearer token 格式校验 parts := strings.Fields(tokenHeader) - if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization header format must be Bearer {token}"}) + if len(parts) != 2 || strings.ToLower(parts[0]) != "Bearer" { + response.Error(c).Code(response.CodeInvalidToken).Send() return } + // 解析 token tokenStr := parts[1] claims, err := utils.VerifyToken(tokenStr) if err != nil { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "Invalid token: " + err.Error()}) + switch { + case errors.Is(err, utils.ErrTokenExpired): + response.Error(c).Code(response.CodeLoginExpired).Send() + default: + response.Error(c).Code(response.CodeInvalidToken).Send() + } return } + // user_id 校验 if claims.UserID != userIdHeader { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "user_id does not match token"}) + response.Error(c).Code(response.CodeInvalidToken).Send() return } + // org_id 校验 if claims.OrgID != orgIDHeader { - c.JSON(http.StatusUnauthorized, map[string]string{"error": "org_id does not match token"}) + response.Error(c).Code(response.CodeInvalidToken).Send() return } - // 认证通过,继续执行后续中间件或处理器 + configs := config.GetDBConfigs() + + for k := range configs { + fmt.Println(k) + } + c.Next() } } diff --git a/code/go-project/base-farmework/pkg/router/context.go b/code/go-project/base-farmework/pkg/router/context.go index 36cb8672..bb48752c 100644 --- a/code/go-project/base-farmework/pkg/router/context.go +++ b/code/go-project/base-farmework/pkg/router/context.go @@ -1,23 +1,58 @@ package router import ( + "bytes" "encoding/json" + "io" "net/http" + "sync" ) +// BodyCache 专门负责缓存请求体,保证只读一次 +type BodyCache struct { + once sync.Once + Data []byte + Err error +} + +// Load 读取请求体并缓存,只执行一次 +func (b *BodyCache) Load(r *http.Request) ([]byte, error) { + b.once.Do(func() { + if r.Body == nil { + b.Err = http.ErrBodyNotAllowed + return + } + var buf bytes.Buffer + b.Err = func() error { + _, err := io.Copy(&buf, r.Body) + return err + }() + if b.Err != nil { + return + } + b.Data = buf.Bytes() + // 重新设置请求体方便后续读取 + r.Body = io.NopCloser(bytes.NewReader(b.Data)) + }) + return b.Data, b.Err +} + +// Context 自定义请求上下文,组合 BodyCache type Context struct { Writer http.ResponseWriter Request *http.Request Params map[string]string - index int - handlers []HandlerFunc + Index int + Handlers []HandlerFunc + + BodyCache BodyCache // 请求体缓存 } // Next 执行下一个中间件或处理函数 func (c *Context) Next() { - c.index++ - if c.index < len(c.handlers) { - c.handlers[c.index](c) + c.Index++ + if c.Index < len(c.Handlers) { + c.Handlers[c.Index](c) } } @@ -31,7 +66,7 @@ func (c *Context) Header(key string) string { return c.Request.Header.Get(key) } -// JSON 返回JSON格式响应 +// JSON 返回 JSON 格式响应 func (c *Context) JSON(statusCode int, data interface{}) { c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8") c.Writer.WriteHeader(statusCode) @@ -39,3 +74,25 @@ func (c *Context) JSON(statusCode int, data interface{}) { http.Error(c.Writer, err.Error(), http.StatusInternalServerError) } } + +// Body 方便读取请求体,实际调用 BodyCache 的 Load 方法 +func (c *Context) Body() ([]byte, error) { + return c.BodyCache.Load(c.Request) +} + +// BindJSON 反序列化 JSON 请求体到 obj +func (c *Context) BindJSON(obj interface{}) error { + body, err := c.Body() + if err != nil { + return err + } + return json.Unmarshal(body, obj) +} + +// PostForm 获取表单参数 +func (c *Context) PostForm(key string) string { + if err := c.Request.ParseForm(); err != nil { + return "" + } + return c.Request.FormValue(key) +} diff --git a/code/go-project/base-farmework/pkg/utils/jwt.go b/code/go-project/base-farmework/pkg/utils/jwt.go index 13388c13..6c24ae57 100644 --- a/code/go-project/base-farmework/pkg/utils/jwt.go +++ b/code/go-project/base-farmework/pkg/utils/jwt.go @@ -1,22 +1,28 @@ package utils import ( - appConfig "base-framework/configs/app" + commonConfig "base-framework/pkg/config" "errors" - "github.com/golang-jwt/jwt" "time" + + "github.com/golang-jwt/jwt" ) -// CustomClaims 定义自己的 payload 结构,可以根据需要扩展 +var ( + ErrTokenExpired = errors.New("token expired") + ErrTokenInvalid = errors.New("token invalid") +) + +// CustomClaims 定义自己的 payload 结构 type CustomClaims struct { OrgID string `json:"org_id"` UserID string `json:"user_id"` jwt.StandardClaims } -// CreateToken 创建一个JWT token +// CreateToken 创建一个 JWT token func CreateToken(orgID, userID string) (string, error) { - expireTime := time.Now().Add(appConfig.JWT.Expiry).Unix() + expireTime := time.Now().Add(commonConfig.JWT.Expiry).Unix() claims := CustomClaims{ OrgID: orgID, @@ -28,29 +34,35 @@ func CreateToken(orgID, userID string) (string, error) { } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - signKey := []byte(appConfig.JWT.Secret) + signKey := []byte(commonConfig.JWT.Secret) return token.SignedString(signKey) } // VerifyToken 验证并解析 JWT token func VerifyToken(tokenString string) (*CustomClaims, error) { - signKey := []byte(appConfig.JWT.Secret) + signKey := []byte(commonConfig.JWT.Secret) token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { // 校验签名算法 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, errors.New("unexpected signing method") + return nil, ErrTokenInvalid } return signKey, nil }) if err != nil { - return nil, err + // 判断是否过期错误 + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorExpired != 0 { + return nil, ErrTokenExpired + } + } + return nil, ErrTokenInvalid } if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { return claims, nil } - return nil, errors.New("invalid token") + return nil, ErrTokenInvalid } diff --git a/code/go-project/base-farmework/pkg/utils/response/response.go b/code/go-project/base-farmework/pkg/utils/response/response.go index 50f2f194..c0808eb7 100644 --- a/code/go-project/base-farmework/pkg/utils/response/response.go +++ b/code/go-project/base-farmework/pkg/utils/response/response.go @@ -12,23 +12,27 @@ type Result struct { Data interface{} `json:"data,omitempty"` } -// 常量状态码 const ( - CodeSuccess = "0000" - CodeFail = "9999" + // 通用 + CodeSuccess = "0000" // 成功 + CodeFail = "0001" // 失败 + + // 鉴权 / 登录 + CodeNoLogin = "1001" // 未登录 + CodeLoginExpired = "1002" // 登录过期 + CodeInvalidToken = "1003" // Token 无效 + CodeInvalidOrgCode = "1004" // 机构码有误 + CodeInvalidAccount = "1005" // 账号或密码有误 ) -// code 对应默认提示 var codeMessages = map[string]string{ - CodeSuccess: "请求成功", - CodeFail: "请求失败", - - "1001": "缺少用户ID", - "1002": "未授权", - "1003": "无权限访问", - "1004": "资源不存在", - - "9000": "系统内部错误", + CodeSuccess: "请求成功", + CodeFail: "请求失败", + CodeNoLogin: "未登录", + CodeLoginExpired: "登录已过期", + CodeInvalidToken: "Token 无效", + CodeInvalidOrgCode: "机构码有误", + CodeInvalidAccount: "账号或密码有误", } // 兜底提示 @@ -65,9 +69,7 @@ func Error(c *router.Context) *Builder { // Code 设置状态码(自动填充默认提示,除非后面手动改) func (b *Builder) Code(code string) *Builder { b.result.Code = code - if b.result.Message == "" || b.result.Message == getMessage(b.result.Code) { - b.result.Message = getMessage(code) - } + b.result.Message = getMessage(code) return b }