From c83b773a4053af7d764bf6bcfda13dab316e9672 Mon Sep 17 00:00:00 2001 From: Yvan <8574526@qq,com> Date: Wed, 7 Jan 2026 14:50:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E4=B8=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/biz/system/jwt_blacklist.go | 60 +++++++ internal/data/system/jwt_blacklist.go | 37 +++++ internal/data/system/user.go | 48 +++++- internal/server/middleware/jwt.go | 8 + internal/service/system/jwt_blacklist.go | 40 +++++ internal/service/system/operation_record.go | 173 +++++++++----------- 6 files changed, 263 insertions(+), 103 deletions(-) create mode 100644 internal/biz/system/jwt_blacklist.go create mode 100644 internal/data/system/jwt_blacklist.go create mode 100644 internal/service/system/jwt_blacklist.go diff --git a/internal/biz/system/jwt_blacklist.go b/internal/biz/system/jwt_blacklist.go new file mode 100644 index 0000000..e63f28b --- /dev/null +++ b/internal/biz/system/jwt_blacklist.go @@ -0,0 +1,60 @@ +package system + +import "context" + +// JwtBlacklist JWT黑名单实体 +type JwtBlacklist struct { + ID uint + Jwt string +} + +// JwtBlacklistRepo JWT黑名单仓储接口 +type JwtBlacklistRepo interface { + Create(ctx context.Context, jwt string) error + IsInBlacklist(ctx context.Context, jwt string) bool + LoadAll(ctx context.Context) ([]string, error) +} + +// JwtBlacklistUsecase JWT黑名单用例 +type JwtBlacklistUsecase struct { + repo JwtBlacklistRepo + cache map[string]struct{} // 内存缓存 +} + +// NewJwtBlacklistUsecase 创建JWT黑名单用例 +func NewJwtBlacklistUsecase(repo JwtBlacklistRepo) *JwtBlacklistUsecase { + uc := &JwtBlacklistUsecase{ + repo: repo, + cache: make(map[string]struct{}), + } + // 加载黑名单到缓存 + uc.LoadAll(context.Background()) + return uc +} + +// JoinBlacklist 加入黑名单 +func (uc *JwtBlacklistUsecase) JoinBlacklist(ctx context.Context, jwt string) error { + if err := uc.repo.Create(ctx, jwt); err != nil { + return err + } + uc.cache[jwt] = struct{}{} + return nil +} + +// IsInBlacklist 检查是否在黑名单中 +func (uc *JwtBlacklistUsecase) IsInBlacklist(jwt string) bool { + _, ok := uc.cache[jwt] + return ok +} + +// LoadAll 加载所有黑名单到缓存 +func (uc *JwtBlacklistUsecase) LoadAll(ctx context.Context) error { + jwts, err := uc.repo.LoadAll(ctx) + if err != nil { + return err + } + for _, jwt := range jwts { + uc.cache[jwt] = struct{}{} + } + return nil +} diff --git a/internal/data/system/jwt_blacklist.go b/internal/data/system/jwt_blacklist.go new file mode 100644 index 0000000..9cae902 --- /dev/null +++ b/internal/data/system/jwt_blacklist.go @@ -0,0 +1,37 @@ +package system + +import ( + "context" + + "kra/internal/biz/system" + "kra/internal/data/model" + "kra/internal/data/query" + + "gorm.io/gorm" +) + +type jwtBlacklistRepo struct { + db *gorm.DB +} + +// NewJwtBlacklistRepo 创建JWT黑名单仓储 +func NewJwtBlacklistRepo(db *gorm.DB) system.JwtBlacklistRepo { + query.SetDefault(db) + return &jwtBlacklistRepo{db: db} +} + +func (r *jwtBlacklistRepo) Create(ctx context.Context, jwt string) error { + m := &model.JwtBlacklist{Jwt: jwt} + return query.JwtBlacklist.WithContext(ctx).Create(m) +} + +func (r *jwtBlacklistRepo) IsInBlacklist(ctx context.Context, jwt string) bool { + count, _ := query.JwtBlacklist.WithContext(ctx).Where(query.JwtBlacklist.Jwt.Eq(jwt)).Count() + return count > 0 +} + +func (r *jwtBlacklistRepo) LoadAll(ctx context.Context) ([]string, error) { + var jwts []string + err := r.db.WithContext(ctx).Model(&model.JwtBlacklist{}).Pluck("jwt", &jwts).Error + return jwts, err +} diff --git a/internal/data/system/user.go b/internal/data/system/user.go index 8ce17c4..3d59ba6 100644 --- a/internal/data/system/user.go +++ b/internal/data/system/user.go @@ -220,17 +220,53 @@ func (r *userRepo) SetUserAuthorities(ctx context.Context, adminAuthorityID, use } func checkAuthorityIDAuth(tx *gorm.DB, adminAuthorityID, targetAuthorityID uint) error { + // 超级管理员(888)直接通过 if adminAuthorityID == 888 { return nil } - var authority model.SysAuthority - if err := tx.Where("authority_id = ? AND parent_id = ?", targetAuthorityID, adminAuthorityID).First(&authority).Error; err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return errors.New("您没有权限分配该角色") - } + + // 获取管理员角色的所有子角色ID(递归) + authIDs, err := getStructAuthorityList(tx, adminAuthorityID) + if err != nil { return err } - return nil + + // 检查目标角色是否在权限列表中 + for _, id := range authIDs { + if id == targetAuthorityID { + return nil + } + } + return errors.New("您没有权限分配该角色") +} + +// getStructAuthorityList 递归获取角色ID列表(包含子角色) +func getStructAuthorityList(tx *gorm.DB, authorityID uint) ([]uint, error) { + var auth model.SysAuthority + if err := tx.First(&auth, "authority_id = ?", authorityID).Error; err != nil { + return nil, err + } + + var list []uint + var children []model.SysAuthority + if err := tx.Where("parent_id = ?", authorityID).Find(&children).Error; err != nil { + return nil, err + } + + for _, child := range children { + list = append(list, uint(child.AuthorityID)) + childList, err := getStructAuthorityList(tx, uint(child.AuthorityID)) + if err == nil { + list = append(list, childList...) + } + } + + // 如果是顶级角色,包含自己 + if auth.ParentID == 0 { + list = append(list, authorityID) + } + + return list, nil } func (r *userRepo) CheckUserHasAuthority(ctx context.Context, userId, authorityId uint) (bool, error) { diff --git a/internal/server/middleware/jwt.go b/internal/server/middleware/jwt.go index d4a8c96..c00b40a 100644 --- a/internal/server/middleware/jwt.go +++ b/internal/server/middleware/jwt.go @@ -154,3 +154,11 @@ func GetAuthorityID(ctx context.Context) uint { } return 0 } + +// GetToken 从context获取token +func GetToken(ctx context.Context) string { + if tr, ok := transport.FromServerContext(ctx); ok { + return extractToken(tr) + } + return "" +} diff --git a/internal/service/system/jwt_blacklist.go b/internal/service/system/jwt_blacklist.go new file mode 100644 index 0000000..be364b7 --- /dev/null +++ b/internal/service/system/jwt_blacklist.go @@ -0,0 +1,40 @@ +package system + +import ( + "kra/internal/biz/system" + "kra/internal/server/middleware" + + "github.com/go-kratos/kratos/v2/errors" + "github.com/go-kratos/kratos/v2/transport/http" +) + +// JwtBlacklistService JWT黑名单服务 +type JwtBlacklistService struct { + uc *system.JwtBlacklistUsecase +} + +// NewJwtBlacklistService 创建JWT黑名单服务 +func NewJwtBlacklistService(uc *system.JwtBlacklistUsecase) *JwtBlacklistService { + return &JwtBlacklistService{uc: uc} +} + +// JoinBlacklist 加入黑名单(登出时调用) +func (s *JwtBlacklistService) JoinBlacklist(ctx http.Context) error { + token := middleware.GetToken(ctx) + if token == "" { + return errors.BadRequest("TOKEN_EMPTY", "token为空") + } + if err := s.uc.JoinBlacklist(ctx, token); err != nil { + return errors.InternalServer("BLACKLIST_ERROR", err.Error()) + } + return ctx.Result(200, map[string]any{ + "code": 0, + "msg": "登出成功", + }) +} + +// RegisterRoutes 注册路由 +func (s *JwtBlacklistService) RegisterRoutes(srv *http.Server) { + r := srv.Route("/") + r.POST("/jwt/jsonInBlacklist", s.JoinBlacklist) +} diff --git a/internal/service/system/operation_record.go b/internal/service/system/operation_record.go index 71dbe4f..bd1b74a 100644 --- a/internal/service/system/operation_record.go +++ b/internal/service/system/operation_record.go @@ -1,7 +1,7 @@ package system import ( - "context" + "fmt" "kra/internal/biz/system" @@ -21,7 +21,7 @@ func NewOperationRecordService(uc *system.OperationRecordUsecase) *OperationReco // OperationRecordInfo 操作记录信息 type OperationRecordInfo struct { - ID uint `json:"ID"` + ID uint `json:"id"` IP string `json:"ip"` Method string `json:"method"` Path string `json:"path"` @@ -35,30 +35,6 @@ type OperationRecordInfo struct { User *UserInfo `json:"user,omitempty"` } -// DeleteOperationRecord 删除操作记录 -func (s *OperationRecordService) DeleteOperationRecord(ctx context.Context, id uint) error { - return s.uc.DeleteOperationRecord(ctx, id) -} - -// DeleteOperationRecordByIdsRequest 批量删除请求 -type DeleteOperationRecordByIdsRequest struct { - Ids []uint `json:"ids"` -} - -// DeleteOperationRecordByIds 批量删除操作记录 -func (s *OperationRecordService) DeleteOperationRecordByIds(ctx context.Context, req *DeleteOperationRecordByIdsRequest) error { - return s.uc.DeleteOperationRecordByIds(ctx, req.Ids) -} - -// GetOperationRecord 获取操作记录 -func (s *OperationRecordService) GetOperationRecord(ctx context.Context, id uint) (*OperationRecordInfo, error) { - record, err := s.uc.GetOperationRecord(ctx, id) - if err != nil { - return nil, err - } - return toOperationRecordInfo(record), nil -} - // GetOperationRecordListRequest 获取操作记录列表请求 type GetOperationRecordListRequest struct { Page int `json:"page"` @@ -66,6 +42,7 @@ type GetOperationRecordListRequest struct { Method string `json:"method"` Path string `json:"path"` Status int `json:"status"` + UserID uint `json:"user_id"` } // GetOperationRecordListResponse 获取操作记录列表响应 @@ -76,8 +53,30 @@ type GetOperationRecordListResponse struct { PageSize int `json:"pageSize"` } -// GetOperationRecordList 获取操作记录列表 -func (s *OperationRecordService) GetOperationRecordList(ctx context.Context, req *GetOperationRecordListRequest) (*GetOperationRecordListResponse, error) { +// DeleteOperationRecordRequest 删除操作记录请求 +type DeleteOperationRecordRequest struct { + ID uint `json:"id"` +} + +// DeleteOperationRecordByIdsRequest 批量删除操作记录请求 +type DeleteOperationRecordByIdsRequest struct { + IDs []uint `json:"ids"` +} + +// RegisterRoutes 注册路由 +func (s *OperationRecordService) RegisterRoutes(srv *http.Server) { + r := srv.Route("/") + r.POST("/sysOperationRecord/getSysOperationRecordList", s.handleGetList) + r.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDelete) + r.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteByIds) + r.GET("/sysOperationRecord/findSysOperationRecord", s.handleFind) +} + +func (s *OperationRecordService) handleGetList(ctx http.Context) error { + var req GetOperationRecordListRequest + if err := ctx.Bind(&req); err != nil { + return err + } filters := make(map[string]interface{}) if req.Method != "" { filters["method"] = req.Method @@ -88,26 +87,63 @@ func (s *OperationRecordService) GetOperationRecordList(ctx context.Context, req if req.Status != 0 { filters["status"] = req.Status } - + if req.UserID != 0 { + filters["user_id"] = req.UserID + } list, total, err := s.uc.GetOperationRecordList(ctx, req.Page, req.PageSize, filters) if err != nil { - return nil, err + return errors.InternalServer("LIST_ERROR", err.Error()) } - - result := make([]*OperationRecordInfo, len(list)) - for i, r := range list { - result[i] = toOperationRecordInfo(r) - } - - return &GetOperationRecordListResponse{ - List: result, + resp := &GetOperationRecordListResponse{ + List: make([]*OperationRecordInfo, len(list)), Total: total, Page: req.Page, PageSize: req.PageSize, - }, nil + } + for i, r := range list { + resp.List[i] = toOperationRecordInfo(r) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": resp}) +} + +func (s *OperationRecordService) handleDelete(ctx http.Context) error { + var req DeleteOperationRecordRequest + if err := ctx.Bind(&req); err != nil { + return err + } + if err := s.uc.DeleteOperationRecord(ctx, req.ID); err != nil { + return errors.InternalServer("DELETE_ERROR", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"}) +} + +func (s *OperationRecordService) handleDeleteByIds(ctx http.Context) error { + var req DeleteOperationRecordByIdsRequest + if err := ctx.Bind(&req); err != nil { + return err + } + if err := s.uc.DeleteOperationRecordByIds(ctx, req.IDs); err != nil { + return errors.InternalServer("DELETE_ERROR", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "批量删除成功"}) +} + +func (s *OperationRecordService) handleFind(ctx http.Context) error { + idStr := ctx.Request().URL.Query().Get("id") + if idStr == "" { + return errors.BadRequest("INVALID_ID", "id参数不能为空") + } + var id uint + if _, err := fmt.Sscanf(idStr, "%d", &id); err != nil { + return errors.BadRequest("INVALID_ID", "id参数格式错误") + } + record, err := s.uc.GetOperationRecord(ctx, id) + if err != nil { + return errors.NotFound("NOT_FOUND", err.Error()) + } + return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": toOperationRecordInfo(record)}) } -// 转换函数 func toOperationRecordInfo(r *system.OperationRecord) *OperationRecordInfo { info := &OperationRecordInfo{ ID: r.ID, @@ -127,60 +163,3 @@ func toOperationRecordInfo(r *system.OperationRecord) *OperationRecordInfo { } return info } - -// RegisterRoutes 注册路由 -func (s *OperationRecordService) RegisterRoutes(srv *http.Server) { - r := srv.Route("/") - - r.DELETE("/sysOperationRecord/deleteSysOperationRecord", s.handleDeleteOperationRecord) - r.DELETE("/sysOperationRecord/deleteSysOperationRecordByIds", s.handleDeleteOperationRecordByIds) - r.GET("/sysOperationRecord/findSysOperationRecord", s.handleGetOperationRecord) - r.GET("/sysOperationRecord/getSysOperationRecordList", s.handleGetOperationRecordList) -} - -// HTTP Handlers -func (s *OperationRecordService) handleDeleteOperationRecord(ctx http.Context) error { - var req struct { - ID uint `json:"ID"` - } - if err := ctx.Bind(&req); err != nil { - return err - } - if err := s.DeleteOperationRecord(ctx, req.ID); err != nil { - return errors.BadRequest("DELETE_FAILED", err.Error()) - } - return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"}) -} - -func (s *OperationRecordService) handleDeleteOperationRecordByIds(ctx http.Context) error { - var req DeleteOperationRecordByIdsRequest - if err := ctx.Bind(&req); err != nil { - return err - } - if err := s.DeleteOperationRecordByIds(ctx, &req); err != nil { - return errors.BadRequest("DELETE_FAILED", err.Error()) - } - return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"}) -} - -func (s *OperationRecordService) handleGetOperationRecord(ctx http.Context) error { - idStr := ctx.Query().Get("ID") - id, _ := parseUint(idStr) - resp, err := s.GetOperationRecord(ctx, id) - if err != nil { - return errors.NotFound("NOT_FOUND", err.Error()) - } - return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": map[string]any{"reSysOperationRecord": resp}}) -} - -func (s *OperationRecordService) handleGetOperationRecordList(ctx http.Context) error { - var req GetOperationRecordListRequest - if err := ctx.BindQuery(&req); err != nil { - return err - } - resp, err := s.GetOperationRecordList(ctx, &req) - if err != nil { - return errors.InternalServer("LIST_ERROR", err.Error()) - } - return ctx.Result(200, map[string]any{"code": 0, "msg": "获取成功", "data": resp}) -}