This commit is contained in:
2026-05-08 11:30:05 +08:00
commit c0ac84fac8
563 changed files with 64232 additions and 0 deletions

View File

@@ -0,0 +1,234 @@
package middleware
import (
"context"
"net/http"
"strings"
"bd-server/app/main/api/internal/config"
"bd-server/app/main/model"
jwtx "bd-server/common/jwt"
"bd-server/common/result"
"bd-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/rest/httpx"
)
const (
// 定义错误码
AdminErrCodeUnauthorized = 401
)
type AdminAuthInterceptorMiddleware struct {
Config config.Config
// 注入model依赖
AdminUserModel model.AdminUserModel
AdminUserRoleModel model.AdminUserRoleModel
AdminRoleModel model.AdminRoleModel
AdminApiModel model.AdminApiModel
AdminRoleApiModel model.AdminRoleApiModel
}
func NewAdminAuthInterceptorMiddleware(c config.Config,
adminUserModel model.AdminUserModel,
adminUserRoleModel model.AdminUserRoleModel,
adminRoleModel model.AdminRoleModel,
adminApiModel model.AdminApiModel,
adminRoleApiModel model.AdminRoleApiModel) *AdminAuthInterceptorMiddleware {
return &AdminAuthInterceptorMiddleware{
Config: c,
AdminUserModel: adminUserModel,
AdminUserRoleModel: adminUserRoleModel,
AdminRoleModel: adminRoleModel,
AdminApiModel: adminApiModel,
AdminRoleApiModel: adminRoleApiModel,
}
}
func (m *AdminAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 1. JWT 校验
claims, err := m.validateJWT(r)
if err != nil {
m.writeUnauthorizedResponse(w, err)
return
}
// 2. 检查用户类型是否为管理员
if claims.UserType != model.UserTypeAdmin {
authErr := errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "用户类型错误,需要管理员权限")
m.writeUnauthorizedResponse(w, authErr)
return
}
// 3. 检查平台标识
if claims.Platform != model.PlatformAdmin {
authErr := errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "平台标识错误,需要管理员平台")
m.writeUnauthorizedResponse(w, authErr)
return
}
// 4. 将用户信息放入上下文
ctx := context.WithValue(r.Context(), jwtx.ExtraKey, claims)
r = r.WithContext(ctx)
// 5. API 权限校验
if err := m.validateApiPermission(r.Context(), claims.UserId, r.Method, r.URL.Path); err != nil {
m.writeUnauthorizedResponse(w, err)
return
}
// 6. 通过所有校验,继续处理请求
next(w, r)
}
}
// writeUnauthorizedResponse 写入401未授权响应
func (m *AdminAuthInterceptorMiddleware) writeUnauthorizedResponse(w http.ResponseWriter, err error) {
errcode := xerr.TOKEN_EXPIRE_ERROR
errmsg := xerr.MapErrMsg(errcode)
// 从错误中提取错误码和错误消息
causeErr := errors.Cause(err)
if e, ok := causeErr.(*xerr.CodeError); ok {
errcode = e.GetErrCode()
errmsg = e.GetErrMsg()
}
// 返回401 HTTP状态码
httpx.WriteJson(w, http.StatusUnauthorized, result.Error(errcode, errmsg))
}
// validateJWT 验证JWT token
func (m *AdminAuthInterceptorMiddleware) validateJWT(r *http.Request) (*jwtx.JwtClaims, error) {
// 从请求头中获取Authorization字段
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "缺少Authorization头")
}
// 去掉Bearer前缀
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == authHeader {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "Authorization头格式错误缺少Bearer前缀")
}
claims, err := jwtx.ParseJwtToken(token, m.Config.AdminConfig.AccessSecret)
if err != nil {
// 根据错误类型返回不同的错误码
errMsg := err.Error()
if strings.Contains(errMsg, "token已过期") || strings.Contains(errMsg, "expired") {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "token解析失败: %v", err)
}
// 其他JWT解析错误使用统一的token过期错误码更符合用户理解
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TOKEN_EXPIRE_ERROR), "token解析失败: %v", err)
}
return claims, nil
}
// validateApiPermission 验证API权限
func (m *AdminAuthInterceptorMiddleware) validateApiPermission(ctx context.Context, userId int64, method, path string) error {
// 1. 获取用户角色
userRoles, err := m.getUserRoles(ctx, userId)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "获取用户角色失败: %v", err)
}
if len(userRoles) == 0 {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "用户没有分配角色")
}
// 2. 检查是否为超级管理员
if m.isSuperAdmin(ctx, userRoles) {
// 超级管理员拥有所有权限,直接放行
return nil
}
// 3. 获取当前请求的API信息
api, err := m.getApiByMethodAndPath(ctx, method, path)
if err != nil {
// 如果API不存在可能是公开接口放行
return nil
}
// 4. 检查用户角色是否有该API权限
hasPermission, err := m.checkRoleApiPermission(ctx, userRoles, api.Id)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DB_ERROR), "检查API权限失败: %v", err)
}
if !hasPermission {
return errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "权限不足,无法访问该接口")
}
return nil
}
// getUserRoles 获取用户角色
func (m *AdminAuthInterceptorMiddleware) getUserRoles(ctx context.Context, userId int64) ([]int64, error) {
builder := m.AdminUserRoleModel.SelectBuilder().Where("user_id = ?", userId)
userRoles, err := m.AdminUserRoleModel.FindAll(ctx, builder, "")
if err != nil {
return nil, err
}
var roleIds []int64
for _, userRole := range userRoles {
roleIds = append(roleIds, userRole.RoleId)
}
return roleIds, nil
}
// isSuperAdmin 检查是否为超级管理员
func (m *AdminAuthInterceptorMiddleware) isSuperAdmin(ctx context.Context, roleIds []int64) bool {
// 检查是否有超级管理员角色
for _, roleId := range roleIds {
role, err := m.AdminRoleModel.FindOne(ctx, roleId)
if err != nil {
continue
}
// 检查是否为超级管理员角色
if role.RoleCode == model.AdminRoleCodeSuper {
return true
}
}
return false
}
// getApiByMethodAndPath 根据方法和路径获取API信息
func (m *AdminAuthInterceptorMiddleware) getApiByMethodAndPath(ctx context.Context, method, path string) (*model.AdminApi, error) {
builder := m.AdminApiModel.SelectBuilder().
Where("method = ? AND url = ? AND status = ?", method, path, 1)
apis, err := m.AdminApiModel.FindAll(ctx, builder, "")
if err != nil {
return nil, err
}
if len(apis) == 0 {
return nil, errors.New("API不存在")
}
return apis[0], nil
}
// checkRoleApiPermission 检查角色是否有API权限
func (m *AdminAuthInterceptorMiddleware) checkRoleApiPermission(ctx context.Context, roleIds []int64, apiId int64) (bool, error) {
for _, roleId := range roleIds {
// 检查角色是否有该API权限
_, err := m.AdminRoleApiModel.FindOneByRoleIdApiId(ctx, roleId, apiId)
if err == nil {
// 找到权限记录,说明有权限
return true, nil
}
// 如果错误不是NotFound说明是其他错误
if !errors.Is(err, model.ErrNotFound) {
return false, err
}
}
return false, nil
}

View File

@@ -0,0 +1,54 @@
package middleware
import (
"context"
"net/http"
"bd-server/app/main/api/internal/config"
jwtx "bd-server/common/jwt"
"bd-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/rest/httpx"
)
const (
// 定义错误码
ErrCodeUnauthorized = 401
)
type AuthInterceptorMiddleware struct {
Config config.Config
}
func NewAuthInterceptorMiddleware(c config.Config) *AuthInterceptorMiddleware {
return &AuthInterceptorMiddleware{
Config: c,
}
}
func (m *AuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 从请求头中获取Authorization字段
authHeader := r.Header.Get("Authorization")
// 如果没有Authorization头直接放行
if authHeader == "" {
next(w, r)
return
}
// 解析JWT令牌
claims, err := jwtx.ParseJwtToken(authHeader, m.Config.JwtAuth.AccessSecret)
if err != nil {
// JWT解析失败返回401错误
httpx.Error(w, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err))
return
}
ctx := context.WithValue(r.Context(), jwtx.ExtraKey, claims)
// 使用新的上下文继续处理请求
next(w, r.WithContext(ctx))
}
}

View File

@@ -0,0 +1,29 @@
package middleware
import (
"context"
"net/http"
)
const (
PlatformKey = "X-Platform"
)
func GlobalSourceInterceptor(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 获取请求头 X-Platform 的值
platform := r.Header.Get(PlatformKey)
// 将值放入新的 context 中
ctx := r.Context()
if platform != "" {
ctx = context.WithValue(ctx, "platform", platform)
}
// 通过 r.WithContext 将更新后的 ctx 传递给后续的处理函数
r = r.WithContext(ctx)
// 传递给下一个处理器
next(w, r)
}
}

View File

@@ -0,0 +1,83 @@
package middleware
import (
"net/http"
"time"
"bd-server/app/main/api/internal/config"
"bd-server/app/main/model"
jwtx "bd-server/common/jwt"
)
const (
HeaderMembershipExpired = "X-Membership-Expired"
)
// MembershipExpiredInterceptor 检测代理会员是否过期,过期则写入响应头
// 由于是全局中间件(在 AuthInterceptor 之前执行),需要自己从请求头解析 JWT
type MembershipExpiredInterceptor struct {
AgentModel model.AgentModel
Config config.Config
}
func NewMembershipExpiredInterceptor(agentModel model.AgentModel, c config.Config) *MembershipExpiredInterceptor {
return &MembershipExpiredInterceptor{
AgentModel: agentModel,
Config: c,
}
}
func (m *MembershipExpiredInterceptor) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 先检测会员过期状态(必须在 next() 之前设置响应头,否则响应已发送 header 无法生效)
needNotifyExpired := false
// 由于全局中间件在路由中间件之前执行context 中可能没有 claims
// 先尝试从 context 获取,如果没有则自己从 Authorization 头解析
claims := getClaimsFromContext(r)
if claims == nil {
claims = parseClaimsFromAuthHeader(r, m.Config.JwtAuth.AccessSecret)
}
if claims != nil && claims.UserType == model.UserTypeNormal && r.URL.Path != "/api/v1/agent/info" {
agent, err := m.AgentModel.FindOneByUserId(r.Context(), claims.UserId)
if err == nil && agent != nil {
// 到期时间已过,说明会员已过期(不管等级是否已降级为 normal
if agent.MembershipExpiryTime.Valid && !agent.MembershipExpiryTime.Time.After(time.Now()) {
needNotifyExpired = true
}
}
}
// 在 next() 之前设置响应头(此时 headers 还没发送给客户端)
if needNotifyExpired {
w.Header().Set(HeaderMembershipExpired, "true")
}
// 执行业务逻辑
next(w, r)
}
}
func getClaimsFromContext(r *http.Request) *jwtx.JwtClaims {
value := r.Context().Value(jwtx.ExtraKey)
if value == nil {
return nil
}
if claims, ok := value.(*jwtx.JwtClaims); ok {
return claims
}
return nil
}
func parseClaimsFromAuthHeader(r *http.Request, secret string) *jwtx.JwtClaims {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return nil
}
claims, err := jwtx.ParseJwtToken(authHeader, secret)
if err != nil {
return nil
}
return claims
}

View File

@@ -0,0 +1,64 @@
package middleware
import (
"net/http"
"bd-server/app/main/model"
"bd-server/common/ctxdata"
"bd-server/common/result"
"bd-server/common/xerr"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/rest/httpx"
)
// 用户封禁状态0 可用1 禁用
const userDisableStatus = 1
type UserAuthInterceptorMiddleware struct {
UserModel model.UserModel
}
func NewUserAuthInterceptorMiddleware(userModel model.UserModel) *UserAuthInterceptorMiddleware {
return &UserAuthInterceptorMiddleware{UserModel: userModel}
}
func (m *UserAuthInterceptorMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
claims, err := ctxdata.GetClaimsFromCtx(r.Context())
if err != nil {
m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(ErrCodeUnauthorized), "token解析失败: %v", err))
return
}
if claims.UserType == model.UserTypeTemp {
m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(xerr.USER_NEED_BIND_MOBILE), "请先绑定手机号"))
return
}
// 封禁校验:用户已被禁用则直接拒绝
user, err := m.UserModel.FindOne(r.Context(), claims.UserId)
if err != nil {
m.writeErrorResponse(w, http.StatusUnauthorized, errors.Wrapf(xerr.NewErrCode(xerr.SERVER_COMMON_ERROR), "获取用户信息失败: %v", err))
return
}
if user.Disable == userDisableStatus {
m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_DISABLED))
return
}
if user.CancelledAt.Valid {
m.writeErrorResponse(w, http.StatusForbidden, xerr.NewErrCode(xerr.USER_CANCELLED))
return
}
next(w, r)
}
}
// writeErrorResponse 统一返回 code + msg便于前端展示提示信息
func (m *UserAuthInterceptorMiddleware) writeErrorResponse(w http.ResponseWriter, statusCode int, err error) {
errcode := xerr.SERVER_COMMON_ERROR
errmsg := xerr.MapErrMsg(errcode)
if e, ok := errors.Cause(err).(*xerr.CodeError); ok {
errcode = e.GetErrCode()
errmsg = e.GetErrMsg()
}
httpx.WriteJson(w, statusCode, result.Error(errcode, errmsg))
}