初始化2

This commit is contained in:
SunYF
2026-08-28 15:06:01 +08:00
parent b34325a16f
commit a9c3fbeb41
537 changed files with 176215 additions and 17 deletions
+132
View File
@@ -0,0 +1,132 @@
package auth
import (
"encoding/json"
"errors"
"net/http"
"strings"
"time"
"github.com/golang-jwt/jwt/v4"
)
// Claims 自定义 JWT 声明
type Claims struct {
UserID int64 `json:"userId"`
Username string `json:"username"`
RealName string `json:"realName"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// TokenInfo 签发 token 所需的用户信息
type TokenInfo struct {
UserID int64
Username string
RealName string
Role string
}
const issuer = "bj_power_workstation"
func defaultExpire(expire int64) int64 {
if expire <= 0 {
return 1800
}
return expire
}
// SignToken 签发 JWT,返回 (token, 过期秒级时间戳, err)
func SignToken(secret string, expire int64, info TokenInfo) (string, int64, error) {
exp := time.Now().Add(time.Duration(defaultExpire(expire)) * time.Second)
claims := &Claims{
UserID: info.UserID,
Username: info.Username,
RealName: info.RealName,
Role: info.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(exp),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: issuer,
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
signed, err := token.SignedString([]byte(secret))
return signed, exp.Unix(), err
}
// ParseToken 校验并解析 JWT
func ParseToken(secret, tokenStr string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("非法签名算法")
}
return []byte(secret), nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*Claims)
if !ok || !token.Valid {
return nil, errors.New("token 无效")
}
return claims, nil
}
// renewIfNearExpiry 滑动续签:剩余有效期不足一半时重签,写入响应头 X-Renewed-Token
func renewIfNearExpiry(secret string, expire int64, w http.ResponseWriter, claims *Claims) {
remaining := time.Until(claims.ExpiresAt.Time)
if remaining > 0 && remaining < time.Duration(defaultExpire(expire))*time.Second/2 {
if newToken, _, err := SignToken(secret, expire, TokenInfo{
UserID: claims.UserID,
Username: claims.Username,
RealName: claims.RealName,
Role: claims.Role,
}); err == nil {
w.Header().Set("X-Renewed-Token", newToken)
}
}
}
// unauthorized 与业务侧 {code,message,data} 封装保持一致
func unauthorized(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_ = json.NewEncoder(w).Encode(map[string]any{"code": 401, "message": msg})
}
// Interceptor 登录鉴权中间件。
// 白名单:/api/health、/api/auth/login、/pdfopen/*。
// 非 /api/ 且非 /pdfopen/ 的路径为前端静态资源,直接放行。
func Interceptor(secret string, accessExpire int64) func(next http.HandlerFunc) http.HandlerFunc {
return func(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
notAPI := !strings.HasPrefix(path, "/api/") && !strings.HasPrefix(path, "/pdfopen/")
whitelisted := path == "/api/health" ||
path == "/api/auth/login" ||
path == "/api/config" ||
strings.HasPrefix(path, "/pdfopen/")
if notAPI || whitelisted {
next.ServeHTTP(w, r)
return
}
authHeader := r.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
unauthorized(w, "未登录或 token 缺失")
return
}
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := ParseToken(secret, tokenStr)
if err != nil {
unauthorized(w, "登录已失效")
return
}
renewIfNearExpiry(secret, accessExpire, w, claims)
r.Header.Set("X-Username", claims.Username)
next.ServeHTTP(w, r)
})
}
}