任务三
This commit is contained in:
parent
c83b773a40
commit
ada3084683
|
|
@ -0,0 +1,40 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// AuthorityBtnRepo 角色按钮权限仓储接口
|
||||
type AuthorityBtnRepo interface {
|
||||
GetAuthorityBtn(ctx context.Context, authorityId, menuId uint) ([]uint, error)
|
||||
SetAuthorityBtn(ctx context.Context, authorityId, menuId uint, btnIds []uint) error
|
||||
CanRemoveAuthorityBtn(ctx context.Context, btnId uint) error
|
||||
}
|
||||
|
||||
// AuthorityBtnUsecase 角色按钮权限用例
|
||||
type AuthorityBtnUsecase struct {
|
||||
repo AuthorityBtnRepo
|
||||
}
|
||||
|
||||
// NewAuthorityBtnUsecase 创建角色按钮权限用例
|
||||
func NewAuthorityBtnUsecase(repo AuthorityBtnRepo) *AuthorityBtnUsecase {
|
||||
return &AuthorityBtnUsecase{repo: repo}
|
||||
}
|
||||
|
||||
// GetAuthorityBtn 获取角色按钮权限
|
||||
func (uc *AuthorityBtnUsecase) GetAuthorityBtn(ctx context.Context, authorityId, menuId uint) ([]uint, error) {
|
||||
return uc.repo.GetAuthorityBtn(ctx, authorityId, menuId)
|
||||
}
|
||||
|
||||
// SetAuthorityBtn 设置角色按钮权限
|
||||
func (uc *AuthorityBtnUsecase) SetAuthorityBtn(ctx context.Context, authorityId, menuId uint, btnIds []uint) error {
|
||||
return uc.repo.SetAuthorityBtn(ctx, authorityId, menuId, btnIds)
|
||||
}
|
||||
|
||||
// CanRemoveAuthorityBtn 检查按钮是否可以删除
|
||||
func (uc *AuthorityBtnUsecase) CanRemoveAuthorityBtn(ctx context.Context, btnId uint) error {
|
||||
return uc.repo.CanRemoveAuthorityBtn(ctx, btnId)
|
||||
}
|
||||
|
||||
var ErrBtnInUse = errors.New("此按钮正在被使用无法删除")
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/data/model"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type authorityBtnRepo struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewAuthorityBtnRepo 创建角色按钮权限仓储
|
||||
func NewAuthorityBtnRepo(db *gorm.DB) system.AuthorityBtnRepo {
|
||||
return &authorityBtnRepo{db: db}
|
||||
}
|
||||
|
||||
func (r *authorityBtnRepo) GetAuthorityBtn(ctx context.Context, authorityId, menuId uint) ([]uint, error) {
|
||||
var btns []model.SysAuthorityBtn
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("authority_id = ? AND sys_menu_id = ?", authorityId, menuId).
|
||||
Find(&btns).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selected := make([]uint, len(btns))
|
||||
for i, b := range btns {
|
||||
selected[i] = uint(b.SysBaseMenuBtnID)
|
||||
}
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
func (r *authorityBtnRepo) SetAuthorityBtn(ctx context.Context, authorityId, menuId uint, btnIds []uint) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 删除旧权限
|
||||
if err := tx.Where("authority_id = ? AND sys_menu_id = ?", authorityId, menuId).
|
||||
Delete(&model.SysAuthorityBtn{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 创建新权限
|
||||
if len(btnIds) > 0 {
|
||||
btns := make([]model.SysAuthorityBtn, len(btnIds))
|
||||
for i, btnId := range btnIds {
|
||||
btns[i] = model.SysAuthorityBtn{
|
||||
AuthorityID: int64(authorityId),
|
||||
SysMenuID: int64(menuId),
|
||||
SysBaseMenuBtnID: int64(btnId),
|
||||
}
|
||||
}
|
||||
if err := tx.Create(&btns).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *authorityBtnRepo) CanRemoveAuthorityBtn(ctx context.Context, btnId uint) error {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.SysAuthorityBtn{}).
|
||||
Where("sys_base_menu_btn_id = ?", btnId).Count(&count).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return errors.New("此按钮正在被使用无法删除")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// AuthorityBtnService 角色按钮权限服务
|
||||
type AuthorityBtnService struct {
|
||||
uc *system.AuthorityBtnUsecase
|
||||
}
|
||||
|
||||
// NewAuthorityBtnService 创建角色按钮权限服务
|
||||
func NewAuthorityBtnService(uc *system.AuthorityBtnUsecase) *AuthorityBtnService {
|
||||
return &AuthorityBtnService{uc: uc}
|
||||
}
|
||||
|
||||
// GetAuthorityBtnRequest 获取角色按钮权限请求
|
||||
type GetAuthorityBtnRequest struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
MenuID uint `json:"menuID"`
|
||||
}
|
||||
|
||||
// GetAuthorityBtnResponse 获取角色按钮权限响应
|
||||
type GetAuthorityBtnResponse struct {
|
||||
Selected []uint `json:"selected"`
|
||||
}
|
||||
|
||||
// SetAuthorityBtnRequest 设置角色按钮权限请求
|
||||
type SetAuthorityBtnRequest struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
MenuID uint `json:"menuID"`
|
||||
Selected []uint `json:"selected"`
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *AuthorityBtnService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/authorityBtn/getAuthorityBtn", s.handleGetAuthorityBtn)
|
||||
r.POST("/authorityBtn/setAuthorityBtn", s.handleSetAuthorityBtn)
|
||||
r.POST("/authorityBtn/canRemoveAuthorityBtn", s.handleCanRemoveAuthorityBtn)
|
||||
}
|
||||
|
||||
func (s *AuthorityBtnService) handleGetAuthorityBtn(ctx http.Context) error {
|
||||
var req GetAuthorityBtnRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
selected, err := s.uc.GetAuthorityBtn(ctx, req.AuthorityId, req.MenuID)
|
||||
if err != nil {
|
||||
return errors.InternalServer("QUERY_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "查询成功",
|
||||
"data": GetAuthorityBtnResponse{Selected: selected},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *AuthorityBtnService) handleSetAuthorityBtn(ctx http.Context) error {
|
||||
var req SetAuthorityBtnRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.uc.SetAuthorityBtn(ctx, req.AuthorityId, req.MenuID, req.Selected); err != nil {
|
||||
return errors.InternalServer("SET_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "分配成功"})
|
||||
}
|
||||
|
||||
func (s *AuthorityBtnService) handleCanRemoveAuthorityBtn(ctx http.Context) error {
|
||||
idStr := ctx.Request().URL.Query().Get("id")
|
||||
if idStr == "" {
|
||||
return errors.BadRequest("INVALID_ID", "id参数不能为空")
|
||||
}
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return errors.BadRequest("INVALID_ID", "id参数格式错误")
|
||||
}
|
||||
if err := s.uc.CanRemoveAuthorityBtn(ctx, uint(id)); err != nil {
|
||||
return errors.BadRequest("BTN_IN_USE", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "删除成功"})
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
// CaptchaService 验证码服务
|
||||
type CaptchaService struct {
|
||||
config CaptchaConfig
|
||||
store base64Captcha.Store
|
||||
blackCache *BlackCache
|
||||
}
|
||||
|
||||
// CaptchaConfig 验证码配置
|
||||
type CaptchaConfig struct {
|
||||
KeyLong int
|
||||
ImgWidth int
|
||||
ImgHeight int
|
||||
OpenCaptcha int // 防爆次数,0表示关闭
|
||||
OpenCaptchaTimeOut int // 缓存超时时间(秒)
|
||||
}
|
||||
|
||||
// BlackCache 黑名单缓存
|
||||
type BlackCache struct {
|
||||
mu sync.RWMutex
|
||||
items map[string]*cacheItem
|
||||
}
|
||||
|
||||
type cacheItem struct {
|
||||
value int
|
||||
expiration time.Time
|
||||
}
|
||||
|
||||
// NewBlackCache 创建黑名单缓存
|
||||
func NewBlackCache() *BlackCache {
|
||||
bc := &BlackCache{
|
||||
items: make(map[string]*cacheItem),
|
||||
}
|
||||
go bc.cleanup()
|
||||
return bc
|
||||
}
|
||||
|
||||
func (c *BlackCache) Set(key string, value int, duration time.Duration) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.items[key] = &cacheItem{
|
||||
value: value,
|
||||
expiration: time.Now().Add(duration),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BlackCache) Get(key string) (int, bool) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
item, ok := c.items[key]
|
||||
if !ok || time.Now().After(item.expiration) {
|
||||
return 0, false
|
||||
}
|
||||
return item.value, true
|
||||
}
|
||||
|
||||
func (c *BlackCache) cleanup() {
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
for range ticker.C {
|
||||
c.mu.Lock()
|
||||
now := time.Now()
|
||||
for k, v := range c.items {
|
||||
if now.After(v.expiration) {
|
||||
delete(c.items, k)
|
||||
}
|
||||
}
|
||||
c.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// CaptchaResponse 验证码响应
|
||||
type CaptchaResponse struct {
|
||||
CaptchaId string `json:"captchaId"`
|
||||
PicPath string `json:"picPath"`
|
||||
CaptchaLength int `json:"captchaLength"`
|
||||
OpenCaptcha bool `json:"openCaptcha"`
|
||||
}
|
||||
|
||||
// NewCaptchaService 创建验证码服务
|
||||
func NewCaptchaService(config CaptchaConfig, store base64Captcha.Store) *CaptchaService {
|
||||
if store == nil {
|
||||
store = base64Captcha.DefaultMemStore
|
||||
}
|
||||
return &CaptchaService{
|
||||
config: config,
|
||||
store: store,
|
||||
blackCache: NewBlackCache(),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *CaptchaService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/base/captcha", s.handleCaptcha)
|
||||
}
|
||||
|
||||
func (s *CaptchaService) handleCaptcha(ctx http.Context) error {
|
||||
// 获取客户端IP
|
||||
clientIP := ctx.Request().RemoteAddr
|
||||
|
||||
// 判断验证码是否开启
|
||||
openCaptcha := s.config.OpenCaptcha
|
||||
openCaptchaTimeOut := s.config.OpenCaptchaTimeOut
|
||||
|
||||
v, ok := s.blackCache.Get(clientIP)
|
||||
if !ok {
|
||||
s.blackCache.Set(clientIP, 1, time.Second*time.Duration(openCaptchaTimeOut))
|
||||
}
|
||||
|
||||
var oc bool
|
||||
if openCaptcha == 0 || openCaptcha < v {
|
||||
oc = true
|
||||
}
|
||||
|
||||
// 生成验证码
|
||||
driver := base64Captcha.NewDriverDigit(
|
||||
s.config.ImgHeight,
|
||||
s.config.ImgWidth,
|
||||
s.config.KeyLong,
|
||||
0.7,
|
||||
80,
|
||||
)
|
||||
cp := base64Captcha.NewCaptcha(driver, s.store)
|
||||
id, b64s, _, err := cp.Generate()
|
||||
if err != nil {
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 7,
|
||||
"msg": "验证码获取失败",
|
||||
})
|
||||
}
|
||||
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "验证码获取成功",
|
||||
"data": CaptchaResponse{
|
||||
CaptchaId: id,
|
||||
PicPath: b64s,
|
||||
CaptchaLength: s.config.KeyLong,
|
||||
OpenCaptcha: oc,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Verify 验证验证码
|
||||
func (s *CaptchaService) Verify(id, answer string) bool {
|
||||
return s.store.Verify(id, answer, true)
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
package system
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
kerrors "github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// CasbinService Casbin服务
|
||||
type CasbinService struct {
|
||||
casbinRepo system.CasbinRepo
|
||||
authUc *system.AuthorityUsecase
|
||||
apiUc *system.ApiUsecase
|
||||
useStrictAuth bool
|
||||
}
|
||||
|
||||
// NewCasbinService 创建Casbin服务
|
||||
func NewCasbinService(casbinRepo system.CasbinRepo, authUc *system.AuthorityUsecase, apiUc *system.ApiUsecase, useStrictAuth bool) *CasbinService {
|
||||
return &CasbinService{
|
||||
casbinRepo: casbinRepo,
|
||||
authUc: authUc,
|
||||
apiUc: apiUc,
|
||||
useStrictAuth: useStrictAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCasbinRequest 更新Casbin请求
|
||||
type UpdateCasbinRequest struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
CasbinInfos []system.CasbinRule `json:"casbinInfos"`
|
||||
}
|
||||
|
||||
// PolicyPathResponse 权限路径响应
|
||||
type PolicyPathResponse struct {
|
||||
Paths []system.CasbinRule `json:"paths"`
|
||||
}
|
||||
|
||||
// GetPolicyPathRequest 获取权限路径请求
|
||||
type GetPolicyPathRequest struct {
|
||||
AuthorityId uint `json:"authorityId"`
|
||||
}
|
||||
|
||||
// UpdateCasbin 更新Casbin权限
|
||||
func (s *CasbinService) UpdateCasbin(ctx http.Context, adminAuthorityID uint, req *UpdateCasbinRequest) error {
|
||||
// 检查权限
|
||||
if err := s.authUc.CheckAuthorityIDAuth(ctx, adminAuthorityID, req.AuthorityId); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 严格模式下检查API权限
|
||||
if s.useStrictAuth {
|
||||
apis, err := s.apiUc.GetAllApis(ctx, adminAuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, info := range req.CasbinInfos {
|
||||
hasApi := false
|
||||
for _, api := range apis {
|
||||
if api.Path == info.Path && api.Method == info.Method {
|
||||
hasApi = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasApi {
|
||||
return errors.New("存在api不在权限列表中")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s.casbinRepo.UpdateCasbin(adminAuthorityID, req.AuthorityId, req.CasbinInfos)
|
||||
}
|
||||
|
||||
// GetPolicyPathByAuthorityId 获取权限列表
|
||||
func (s *CasbinService) GetPolicyPathByAuthorityId(authorityId uint) []system.CasbinRule {
|
||||
return s.casbinRepo.GetPolicyPathByAuthorityId(authorityId)
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册路由
|
||||
func (s *CasbinService) RegisterRoutes(srv *http.Server) {
|
||||
r := srv.Route("/")
|
||||
r.POST("/casbin/UpdateCasbin", s.handleUpdateCasbin)
|
||||
r.POST("/casbin/getPolicyPathByAuthorityId", s.handleGetPolicyPath)
|
||||
}
|
||||
|
||||
func (s *CasbinService) handleUpdateCasbin(ctx http.Context) error {
|
||||
var req UpdateCasbinRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AuthorityId == 0 {
|
||||
return kerrors.BadRequest("INVALID_AUTHORITY", "角色ID不能为空")
|
||||
}
|
||||
adminAuthorityID := middleware.GetAuthorityID(ctx)
|
||||
if adminAuthorityID == 0 {
|
||||
return kerrors.Unauthorized("UNAUTHORIZED", "请先登录")
|
||||
}
|
||||
if err := s.UpdateCasbin(ctx, adminAuthorityID, &req); err != nil {
|
||||
return kerrors.InternalServer("UPDATE_ERROR", err.Error())
|
||||
}
|
||||
return ctx.Result(200, map[string]any{"code": 0, "msg": "更新成功"})
|
||||
}
|
||||
|
||||
func (s *CasbinService) handleGetPolicyPath(ctx http.Context) error {
|
||||
var req GetPolicyPathRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.AuthorityId == 0 {
|
||||
return kerrors.BadRequest("INVALID_AUTHORITY", "角色ID不能为空")
|
||||
}
|
||||
paths := s.GetPolicyPathByAuthorityId(req.AuthorityId)
|
||||
return ctx.Result(200, map[string]any{
|
||||
"code": 0,
|
||||
"msg": "获取成功",
|
||||
"data": PolicyPathResponse{Paths: paths},
|
||||
})
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func uintToStr(n uint) string {
|
||||
return strconv.FormatUint(uint64(n), 10)
|
||||
}
|
||||
Loading…
Reference in New Issue