u
This commit is contained in:
1 parent
9af3234672
commit
ed8bee3d77
11 files changed
+168
-54
No files matched your search
@@ -1,8 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
appConfig "base-framework/configs/app"
|
|
||||||
"base-framework/internal/app"
|
"base-framework/internal/app"
|
||||||
|
commonConfig "base-framework/pkg/config"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -11,12 +11,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
_, err := appConfig.InitAppConfig("./configs/app/config.yaml")
|
_, err := commonConfig.InitApplicationConfig("./configs/app/application.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("加载配置失败: %v", err)
|
log.Fatalf("加载配置失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = appConfig.InitDBConfig("D:\\db.yaml")
|
err = commonConfig.InitDBConfig("./configs/app/db.yaml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("加载数据库配置失败: %v", err)
|
log.Fatalf("加载数据库配置失败: %v", err)
|
||||||
return
|
return
|
||||||
@@ -24,7 +24,7 @@ func main() {
|
|||||||
|
|
||||||
r := app.InitAppRouter()
|
r := app.InitAppRouter()
|
||||||
|
|
||||||
err = http.ListenAndServe(":"+strconv.Itoa(appConfig.Server.Port), r)
|
err = http.ListenAndServe(":"+strconv.Itoa(commonConfig.Server.Port), r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("server start failed: %v", err)
|
log.Fatalf("server start failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
File renamed without changes.
@@ -0,0 +1 @@
|
|||||||
|
package api
|
||||||
@@ -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字符串",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -10,8 +10,9 @@ func InitAppRouter() *router.Router {
|
|||||||
r := router.NewRouter()
|
r := router.NewRouter()
|
||||||
|
|
||||||
r.Use(middleware.Recover())
|
r.Use(middleware.Recover())
|
||||||
//r.Use(middleware.Auth()).ExcludePaths("/login")
|
r.Use(middleware.Auth()).ExcludePaths("/login")
|
||||||
r.Use(middleware.Logger())
|
r.Use(middleware.Logger())
|
||||||
|
|
||||||
initApi(r)
|
initApi(r)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
package app
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -20,7 +20,7 @@ var (
|
|||||||
JWT JWTConfig
|
JWT JWTConfig
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitAppConfig(configPath string) (error, error) {
|
func InitApplicationConfig(configPath string) (error, error) {
|
||||||
v := viper.New()
|
v := viper.New()
|
||||||
v.SetConfigFile(configPath)
|
v.SetConfigFile(configPath)
|
||||||
v.SetConfigType("yaml")
|
v.SetConfigType("yaml")
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package app
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"base-framework/pkg/config"
|
||||||
"base-framework/pkg/router"
|
"base-framework/pkg/router"
|
||||||
"base-framework/pkg/utils"
|
"base-framework/pkg/utils"
|
||||||
"net/http"
|
"base-framework/pkg/utils/response"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,43 +16,50 @@ func Auth() router.HandlerFunc {
|
|||||||
userIdHeader := c.Header("user_id")
|
userIdHeader := c.Header("user_id")
|
||||||
orgIDHeader := c.Header("org_id")
|
orgIDHeader := c.Header("org_id")
|
||||||
|
|
||||||
if tokenHeader == "" {
|
// 缺少登录信息
|
||||||
c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization header missing"})
|
if tokenHeader == "" || userIdHeader == "" || orgIDHeader == "" {
|
||||||
return
|
response.Error(c).Code(response.CodeNoLogin).Send()
|
||||||
}
|
|
||||||
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"})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bearer token 格式校验
|
||||||
parts := strings.Fields(tokenHeader)
|
parts := strings.Fields(tokenHeader)
|
||||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
if len(parts) != 2 || strings.ToLower(parts[0]) != "Bearer" {
|
||||||
c.JSON(http.StatusUnauthorized, map[string]string{"error": "Authorization header format must be Bearer {token}"})
|
response.Error(c).Code(response.CodeInvalidToken).Send()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 解析 token
|
||||||
tokenStr := parts[1]
|
tokenStr := parts[1]
|
||||||
claims, err := utils.VerifyToken(tokenStr)
|
claims, err := utils.VerifyToken(tokenStr)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// user_id 校验
|
||||||
if claims.UserID != userIdHeader {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// org_id 校验
|
||||||
if claims.OrgID != orgIDHeader {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 认证通过,继续执行后续中间件或处理器
|
configs := config.GetDBConfigs()
|
||||||
|
|
||||||
|
for k := range configs {
|
||||||
|
fmt.Println(k)
|
||||||
|
}
|
||||||
|
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,58 @@
|
|||||||
package router
|
package router
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"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 {
|
type Context struct {
|
||||||
Writer http.ResponseWriter
|
Writer http.ResponseWriter
|
||||||
Request *http.Request
|
Request *http.Request
|
||||||
Params map[string]string
|
Params map[string]string
|
||||||
index int
|
Index int
|
||||||
handlers []HandlerFunc
|
Handlers []HandlerFunc
|
||||||
|
|
||||||
|
BodyCache BodyCache // 请求体缓存
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next 执行下一个中间件或处理函数
|
// Next 执行下一个中间件或处理函数
|
||||||
func (c *Context) Next() {
|
func (c *Context) Next() {
|
||||||
c.index++
|
c.Index++
|
||||||
if c.index < len(c.handlers) {
|
if c.Index < len(c.Handlers) {
|
||||||
c.handlers[c.index](c)
|
c.Handlers[c.Index](c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +66,7 @@ func (c *Context) Header(key string) string {
|
|||||||
return c.Request.Header.Get(key)
|
return c.Request.Header.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON 返回JSON格式响应
|
// JSON 返回 JSON 格式响应
|
||||||
func (c *Context) JSON(statusCode int, data interface{}) {
|
func (c *Context) JSON(statusCode int, data interface{}) {
|
||||||
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
c.Writer.WriteHeader(statusCode)
|
c.Writer.WriteHeader(statusCode)
|
||||||
@@ -39,3 +74,25 @@ func (c *Context) JSON(statusCode int, data interface{}) {
|
|||||||
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
|
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)
|
||||||
|
}
|
||||||
@@ -1,22 +1,28 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
appConfig "base-framework/configs/app"
|
commonConfig "base-framework/pkg/config"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/golang-jwt/jwt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CustomClaims 定义自己的 payload 结构,可以根据需要扩展
|
var (
|
||||||
|
ErrTokenExpired = errors.New("token expired")
|
||||||
|
ErrTokenInvalid = errors.New("token invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CustomClaims 定义自己的 payload 结构
|
||||||
type CustomClaims struct {
|
type CustomClaims struct {
|
||||||
OrgID string `json:"org_id"`
|
OrgID string `json:"org_id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
jwt.StandardClaims
|
jwt.StandardClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateToken 创建一个JWT token
|
// CreateToken 创建一个 JWT token
|
||||||
func CreateToken(orgID, userID string) (string, error) {
|
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{
|
claims := CustomClaims{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
@@ -28,29 +34,35 @@ func CreateToken(orgID, userID string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
signKey := []byte(appConfig.JWT.Secret)
|
signKey := []byte(commonConfig.JWT.Secret)
|
||||||
return token.SignedString(signKey)
|
return token.SignedString(signKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// VerifyToken 验证并解析 JWT token
|
// VerifyToken 验证并解析 JWT token
|
||||||
func VerifyToken(tokenString string) (*CustomClaims, error) {
|
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) {
|
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||||
// 校验签名算法
|
// 校验签名算法
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, errors.New("unexpected signing method")
|
return nil, ErrTokenInvalid
|
||||||
}
|
}
|
||||||
return signKey, nil
|
return signKey, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != 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 {
|
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
|
||||||
return claims, nil
|
return claims, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New("invalid token")
|
return nil, ErrTokenInvalid
|
||||||
}
|
}
|
||||||
@@ -12,23 +12,27 @@ type Result struct {
|
|||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 常量状态码
|
|
||||||
const (
|
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{
|
var codeMessages = map[string]string{
|
||||||
CodeSuccess: "请求成功",
|
CodeSuccess: "请求成功",
|
||||||
CodeFail: "请求失败",
|
CodeFail: "请求失败",
|
||||||
|
CodeNoLogin: "未登录",
|
||||||
"1001": "缺少用户ID",
|
CodeLoginExpired: "登录已过期",
|
||||||
"1002": "未授权",
|
CodeInvalidToken: "Token 无效",
|
||||||
"1003": "无权限访问",
|
CodeInvalidOrgCode: "机构码有误",
|
||||||
"1004": "资源不存在",
|
CodeInvalidAccount: "账号或密码有误",
|
||||||
|
|
||||||
"9000": "系统内部错误",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 兜底提示
|
// 兜底提示
|
||||||
@@ -65,9 +69,7 @@ func Error(c *router.Context) *Builder {
|
|||||||
// Code 设置状态码(自动填充默认提示,除非后面手动改)
|
// Code 设置状态码(自动填充默认提示,除非后面手动改)
|
||||||
func (b *Builder) Code(code string) *Builder {
|
func (b *Builder) Code(code string) *Builder {
|
||||||
b.result.Code = code
|
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
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user