后端重构
This commit is contained in:
@@ -14,6 +14,7 @@ var ProviderSet = wire.NewSet(
|
||||
system.NewAuthorityBtnUsecase,
|
||||
system.NewMenuUsecase,
|
||||
system.NewDictionaryUsecase,
|
||||
system.NewCasbinUsecase,
|
||||
system.NewJwtBlacklistUsecase,
|
||||
system.NewOperationRecordUsecase,
|
||||
system.NewParamsUsecase,
|
||||
|
||||
@@ -162,6 +162,18 @@ func (uc *ApiUsecase) GetAPIInfoList(ctx context.Context, page, pageSize int, fi
|
||||
return uc.repo.List(ctx, page, pageSize, filters, orderKey, desc)
|
||||
}
|
||||
|
||||
// GetApiList 分页获取API列表(简化版本)
|
||||
func (uc *ApiUsecase) GetApiList(ctx context.Context, page, pageSize int, filters map[string]interface{}) ([]*Api, int64, error) {
|
||||
// 转换 filters
|
||||
strFilters := make(map[string]string)
|
||||
for k, v := range filters {
|
||||
if s, ok := v.(string); ok {
|
||||
strFilters[k] = s
|
||||
}
|
||||
}
|
||||
return uc.repo.List(ctx, page, pageSize, strFilters, "", false)
|
||||
}
|
||||
|
||||
// GetAllApis 获取所有API(根据权限过滤)
|
||||
func (uc *ApiUsecase) GetAllApis(ctx context.Context, authorityID uint) ([]*Api, error) {
|
||||
parentAuthorityID, err := uc.authRepo.GetParentAuthorityID(ctx, authorityID)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// CasbinUsecase Casbin用例
|
||||
type CasbinUsecase struct {
|
||||
repo CasbinRepo
|
||||
authUc *AuthorityUsecase
|
||||
apiUc *ApiUsecase
|
||||
useStrictAuth bool
|
||||
}
|
||||
|
||||
// NewCasbinUsecase 创建Casbin用例
|
||||
func NewCasbinUsecase(repo CasbinRepo, authUc *AuthorityUsecase, apiUc *ApiUsecase) *CasbinUsecase {
|
||||
return &CasbinUsecase{
|
||||
repo: repo,
|
||||
authUc: authUc,
|
||||
apiUc: apiUc,
|
||||
useStrictAuth: false,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateCasbin 更新Casbin权限
|
||||
func (uc *CasbinUsecase) UpdateCasbin(adminAuthorityID, authorityId uint, casbinInfos []struct {
|
||||
Path string
|
||||
Method string
|
||||
}) error {
|
||||
// 转换为 CasbinRule
|
||||
rules := make([]CasbinRule, len(casbinInfos))
|
||||
for i, info := range casbinInfos {
|
||||
rules[i] = CasbinRule{
|
||||
Path: info.Path,
|
||||
Method: info.Method,
|
||||
}
|
||||
}
|
||||
|
||||
return uc.repo.UpdateCasbin(adminAuthorityID, authorityId, rules)
|
||||
}
|
||||
|
||||
// GetPolicyPathByAuthorityId 获取权限路径
|
||||
func (uc *CasbinUsecase) GetPolicyPathByAuthorityId(authorityId uint) []CasbinRule {
|
||||
return uc.repo.GetPolicyPathByAuthorityId(authorityId)
|
||||
}
|
||||
|
||||
// FreshCasbin 刷新Casbin缓存
|
||||
func (uc *CasbinUsecase) FreshCasbin() error {
|
||||
return uc.repo.FreshCasbin()
|
||||
}
|
||||
|
||||
// CheckPermission 检查权限
|
||||
func (uc *CasbinUsecase) CheckPermission(ctx context.Context, sub, obj, act string) (bool, error) {
|
||||
// 这里可以添加权限检查逻辑
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ClearCasbin 清除Casbin规则
|
||||
func (uc *CasbinUsecase) ClearCasbin(v int, p ...string) bool {
|
||||
return uc.repo.ClearCasbin(v, p...)
|
||||
}
|
||||
|
||||
// AddPolicies 添加策略
|
||||
func (uc *CasbinUsecase) AddPolicies(rules [][]string) error {
|
||||
return uc.repo.AddPolicies(rules)
|
||||
}
|
||||
|
||||
// RemoveFilteredPolicy 删除筛选的策略
|
||||
func (uc *CasbinUsecase) RemoveFilteredPolicy(authorityId string) error {
|
||||
return uc.repo.RemoveFilteredPolicy(authorityId)
|
||||
}
|
||||
|
||||
// UpdateCasbinApi API更新随动
|
||||
func (uc *CasbinUsecase) UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod string) error {
|
||||
return uc.repo.UpdateCasbinApi(oldPath, newPath, oldMethod, newMethod)
|
||||
}
|
||||
|
||||
// ValidateCasbinInfos 验证Casbin信息(严格模式)
|
||||
func (uc *CasbinUsecase) ValidateCasbinInfos(ctx context.Context, adminAuthorityID uint, casbinInfos []CasbinRule) error {
|
||||
if !uc.useStrictAuth {
|
||||
return nil
|
||||
}
|
||||
|
||||
apis, err := uc.apiUc.GetAllApis(ctx, adminAuthorityID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, info := range 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 nil
|
||||
}
|
||||
@@ -41,12 +41,22 @@ func (uc *JwtBlacklistUsecase) JoinBlacklist(ctx context.Context, jwt string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
// JsonInBlacklist JWT加入黑名单(GVA兼容方法名)
|
||||
func (uc *JwtBlacklistUsecase) JsonInBlacklist(ctx context.Context, jwt string) error {
|
||||
return uc.JoinBlacklist(ctx, jwt)
|
||||
}
|
||||
|
||||
// IsInBlacklist 检查是否在黑名单中
|
||||
func (uc *JwtBlacklistUsecase) IsInBlacklist(jwt string) bool {
|
||||
_, ok := uc.cache[jwt]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsBlacklist 检查是否在黑名单中(GVA兼容方法名)
|
||||
func (uc *JwtBlacklistUsecase) IsBlacklist(ctx context.Context, jwt string) bool {
|
||||
return uc.IsInBlacklist(jwt)
|
||||
}
|
||||
|
||||
// LoadAll 加载所有黑名单到缓存
|
||||
func (uc *JwtBlacklistUsecase) LoadAll(ctx context.Context) error {
|
||||
jwts, err := uc.repo.LoadAll(ctx)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package conf
|
||||
|
||||
import "github.com/google/wire"
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ProviderSet is conf providers.
|
||||
var ProviderSet = wire.NewSet(
|
||||
@@ -10,8 +13,15 @@ var ProviderSet = wire.NewSet(
|
||||
ProvideCaptcha,
|
||||
ProvideSystem,
|
||||
ProvideUseStrictAuth,
|
||||
ProvideZapLogger,
|
||||
)
|
||||
|
||||
// ProvideZapLogger 提供zap日志实例
|
||||
func ProvideZapLogger() *zap.Logger {
|
||||
logger, _ := zap.NewProduction()
|
||||
return logger
|
||||
}
|
||||
|
||||
// ProvideServer 提供Server配置
|
||||
func ProvideServer(c *Bootstrap) *Server {
|
||||
return c.Server
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/internal/conf"
|
||||
handler "kra/internal/server/handler/system"
|
||||
"kra/internal/server/middleware"
|
||||
"kra/internal/server/router"
|
||||
"kra/pkg/jwt"
|
||||
|
||||
"github.com/casbin/casbin/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
kgin "github.com/go-kratos/gin"
|
||||
"github.com/go-kratos/kratos/v2/middleware/recovery"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NewGinRouter 创建Gin路由引擎(交给kratos http server管理)
|
||||
func NewGinRouter(
|
||||
c *conf.Server,
|
||||
logger *zap.Logger,
|
||||
jwtPkg *jwt.JWT,
|
||||
enforcer *casbin.SyncedCachedEnforcer,
|
||||
jwtBlacklistUsecase *system.JwtBlacklistUsecase,
|
||||
userUsecase *system.UserUsecase,
|
||||
apiUsecase *system.ApiUsecase,
|
||||
authorityUsecase *system.AuthorityUsecase,
|
||||
authorityBtnUsecase *system.AuthorityBtnUsecase,
|
||||
menuUsecase *system.MenuUsecase,
|
||||
dictionaryUsecase *system.DictionaryUsecase,
|
||||
casbinUsecase *system.CasbinUsecase,
|
||||
operationRecordUsecase *system.OperationRecordUsecase,
|
||||
paramsUsecase *system.ParamsUsecase,
|
||||
) *gin.Engine {
|
||||
gin.SetMode(gin.DebugMode)
|
||||
|
||||
engine := gin.New()
|
||||
|
||||
// 使用kratos中间件包装
|
||||
engine.Use(kgin.Middlewares(recovery.Recovery()))
|
||||
|
||||
// 设置自定义中间件
|
||||
engine.Use(middleware.GinRecovery(true))
|
||||
engine.Use(middleware.Cors())
|
||||
|
||||
// 设置日志
|
||||
middleware.SetRecoveryLogger(logger)
|
||||
middleware.SetOperationLogger(logger)
|
||||
|
||||
// 设置JWT配置
|
||||
middleware.SetJWTConfig(&middleware.JWTConfig{
|
||||
JWT: jwtPkg,
|
||||
BlacklistChecker: &blacklistChecker{uc: jwtBlacklistUsecase},
|
||||
UseMultipoint: false,
|
||||
})
|
||||
|
||||
// 设置Casbin配置
|
||||
middleware.SetCasbinConfig(&middleware.CasbinConfig{
|
||||
RouterPrefix: "",
|
||||
})
|
||||
|
||||
// 初始化handler依赖
|
||||
handler.InitUsecases(
|
||||
userUsecase,
|
||||
apiUsecase,
|
||||
authorityUsecase,
|
||||
authorityBtnUsecase,
|
||||
menuUsecase,
|
||||
dictionaryUsecase,
|
||||
casbinUsecase,
|
||||
jwtBlacklistUsecase,
|
||||
operationRecordUsecase,
|
||||
paramsUsecase,
|
||||
)
|
||||
handler.SetJWTInstance(jwtPkg)
|
||||
|
||||
// 创建路由组
|
||||
publicGroup := engine.Group("")
|
||||
privateGroup := engine.Group("")
|
||||
privateGroup.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler())
|
||||
|
||||
// 注册路由
|
||||
systemRouter := router.RouterGroupApp.System
|
||||
systemRouter.InitBaseRouter(publicGroup)
|
||||
systemRouter.InitUserRouter(privateGroup)
|
||||
systemRouter.InitApiRouter(privateGroup, publicGroup)
|
||||
systemRouter.InitAuthorityRouter(privateGroup)
|
||||
systemRouter.InitMenuRouter(privateGroup)
|
||||
systemRouter.InitDictionaryRouter(privateGroup)
|
||||
systemRouter.InitCasbinRouter(privateGroup)
|
||||
systemRouter.InitJwtRouter(privateGroup)
|
||||
systemRouter.InitOperationRecordRouter(privateGroup)
|
||||
systemRouter.InitParamsRouter(privateGroup)
|
||||
|
||||
return engine
|
||||
}
|
||||
|
||||
// blacklistChecker JWT黑名单检查器
|
||||
type blacklistChecker struct {
|
||||
uc *system.JwtBlacklistUsecase
|
||||
}
|
||||
|
||||
func (b *blacklistChecker) IsBlacklist(jwt string) bool {
|
||||
return b.uc.IsBlacklist(context.Background(), jwt)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package system
|
||||
|
||||
import "kra/internal/biz/system"
|
||||
|
||||
// ApiGroup 系统API组
|
||||
type ApiGroup struct {
|
||||
BaseApi
|
||||
UserApi
|
||||
ApiApi
|
||||
AuthorityApi
|
||||
MenuApi
|
||||
DictionaryApi
|
||||
CasbinApi
|
||||
JwtApi
|
||||
OperationRecordApi
|
||||
ParamsApi
|
||||
}
|
||||
|
||||
// 业务层依赖
|
||||
var (
|
||||
userUsecase *system.UserUsecase
|
||||
apiUsecase *system.ApiUsecase
|
||||
authorityUsecase *system.AuthorityUsecase
|
||||
authorityBtnUsecase *system.AuthorityBtnUsecase
|
||||
menuUsecase *system.MenuUsecase
|
||||
dictionaryUsecase *system.DictionaryUsecase
|
||||
casbinUsecase *system.CasbinUsecase
|
||||
jwtBlacklistUsecase *system.JwtBlacklistUsecase
|
||||
operationRecordUsecase *system.OperationRecordUsecase
|
||||
paramsUsecase *system.ParamsUsecase
|
||||
)
|
||||
|
||||
// InitUsecases 初始化业务层依赖
|
||||
func InitUsecases(
|
||||
user *system.UserUsecase,
|
||||
api *system.ApiUsecase,
|
||||
authority *system.AuthorityUsecase,
|
||||
authorityBtn *system.AuthorityBtnUsecase,
|
||||
menu *system.MenuUsecase,
|
||||
dictionary *system.DictionaryUsecase,
|
||||
casbin *system.CasbinUsecase,
|
||||
jwtBlacklist *system.JwtBlacklistUsecase,
|
||||
operationRecord *system.OperationRecordUsecase,
|
||||
params *system.ParamsUsecase,
|
||||
) {
|
||||
userUsecase = user
|
||||
apiUsecase = api
|
||||
authorityUsecase = authority
|
||||
authorityBtnUsecase = authorityBtn
|
||||
menuUsecase = menu
|
||||
dictionaryUsecase = dictionary
|
||||
casbinUsecase = casbin
|
||||
jwtBlacklistUsecase = jwtBlacklist
|
||||
operationRecordUsecase = operationRecord
|
||||
paramsUsecase = params
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ApiApi struct{}
|
||||
|
||||
// CreateApiRequest 创建API请求
|
||||
type CreateApiRequest struct {
|
||||
Path string `json:"path" binding:"required"`
|
||||
Description string `json:"description" binding:"required"`
|
||||
ApiGroup string `json:"apiGroup" binding:"required"`
|
||||
Method string `json:"method" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateApiRequest 更新API请求
|
||||
type UpdateApiRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
Description string `json:"description" binding:"required"`
|
||||
ApiGroup string `json:"apiGroup" binding:"required"`
|
||||
Method string `json:"method" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteApiRequest 删除API请求
|
||||
type DeleteApiRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// GetApiListRequest 获取API列表请求
|
||||
type GetApiListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Path string `json:"path"`
|
||||
Description string `json:"description"`
|
||||
ApiGroup string `json:"apiGroup"`
|
||||
Method string `json:"method"`
|
||||
OrderKey string `json:"orderKey"`
|
||||
Desc bool `json:"desc"`
|
||||
}
|
||||
|
||||
// GetApiByIdRequest 根据ID获取API请求
|
||||
type GetApiByIdRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteApisByIdsRequest 批量删除API请求
|
||||
type DeleteApisByIdsRequest struct {
|
||||
Ids []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
// CreateApi 创建API
|
||||
func (a *ApiApi) CreateApi(c *gin.Context) {
|
||||
var req CreateApiRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
api := &system.Api{
|
||||
Path: req.Path,
|
||||
Description: req.Description,
|
||||
ApiGroup: req.ApiGroup,
|
||||
Method: req.Method,
|
||||
}
|
||||
|
||||
if err := apiUsecase.CreateApi(c, api); err != nil {
|
||||
response.FailWithMessage("创建失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteApi 删除API
|
||||
func (a *ApiApi) DeleteApi(c *gin.Context) {
|
||||
var req DeleteApiRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := apiUsecase.DeleteApi(c, req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateApi 更新API
|
||||
func (a *ApiApi) UpdateApi(c *gin.Context) {
|
||||
var req UpdateApiRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
api := &system.Api{
|
||||
ID: req.ID,
|
||||
Path: req.Path,
|
||||
Description: req.Description,
|
||||
ApiGroup: req.ApiGroup,
|
||||
Method: req.Method,
|
||||
}
|
||||
|
||||
if err := apiUsecase.UpdateApi(c, api); err != nil {
|
||||
response.FailWithMessage("修改失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
// GetApiList 获取API列表
|
||||
func (a *ApiApi) GetApiList(c *gin.Context) {
|
||||
var req GetApiListRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.Path != "" {
|
||||
filters["path"] = req.Path
|
||||
}
|
||||
if req.Description != "" {
|
||||
filters["description"] = req.Description
|
||||
}
|
||||
if req.ApiGroup != "" {
|
||||
filters["api_group"] = req.ApiGroup
|
||||
}
|
||||
if req.Method != "" {
|
||||
filters["method"] = req.Method
|
||||
}
|
||||
|
||||
list, total, err := apiUsecase.GetApiList(c, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetApiById 根据ID获取API
|
||||
func (a *ApiApi) GetApiById(c *gin.Context) {
|
||||
var req GetApiByIdRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
api, err := apiUsecase.GetApiById(c, req.ID)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"api": api}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetAllApis 获取所有API
|
||||
func (a *ApiApi) GetAllApis(c *gin.Context) {
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
apis, err := apiUsecase.GetAllApis(c, authorityID)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"apis": apis}, "获取成功", c)
|
||||
}
|
||||
|
||||
// DeleteApisByIds 批量删除API
|
||||
func (a *ApiApi) DeleteApisByIds(c *gin.Context) {
|
||||
var req DeleteApisByIdsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := apiUsecase.DeleteApisByIds(c, req.Ids); err != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FreshCasbin 刷新Casbin缓存
|
||||
func (a *ApiApi) FreshCasbin(c *gin.Context) {
|
||||
if err := casbinUsecase.FreshCasbin(); err != nil {
|
||||
response.FailWithMessage("刷新失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("刷新成功", c)
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
jwtPkg "kra/pkg/jwt"
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthorityApi struct{}
|
||||
|
||||
// CreateAuthorityRequest 创建角色请求
|
||||
type CreateAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
AuthorityName string `json:"authorityName" binding:"required"`
|
||||
ParentId *uint `json:"parentId"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
}
|
||||
|
||||
// UpdateAuthorityRequest 更新角色请求
|
||||
type UpdateAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
AuthorityName string `json:"authorityName" binding:"required"`
|
||||
ParentId *uint `json:"parentId"`
|
||||
DefaultRouter string `json:"defaultRouter"`
|
||||
}
|
||||
|
||||
// DeleteAuthorityRequest 删除角色请求
|
||||
type DeleteAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
}
|
||||
|
||||
// CopyAuthorityRequest 复制角色请求
|
||||
type CopyAuthorityRequest struct {
|
||||
OldAuthorityId uint `json:"oldAuthorityId" binding:"required"`
|
||||
Authority CreateAuthorityRequest `json:"authority" binding:"required"`
|
||||
}
|
||||
|
||||
// SetDataAuthorityRequest 设置数据权限请求
|
||||
type SetDataAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
DataAuthorityId []uint `json:"dataAuthorityId"`
|
||||
}
|
||||
|
||||
// GetAuthorityListRequest 获取角色列表请求
|
||||
type GetAuthorityListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
}
|
||||
|
||||
// CreateAuthority 创建角色
|
||||
func (a *AuthorityApi) CreateAuthority(c *gin.Context) {
|
||||
var req CreateAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
authority := &system.Authority{
|
||||
AuthorityId: req.AuthorityId,
|
||||
AuthorityName: req.AuthorityName,
|
||||
ParentId: req.ParentId,
|
||||
DefaultRouter: req.DefaultRouter,
|
||||
}
|
||||
|
||||
created, err := authorityUsecase.CreateAuthority(c, authority)
|
||||
if err != nil {
|
||||
response.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"authority": created}, "创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteAuthority 删除角色
|
||||
func (a *AuthorityApi) DeleteAuthority(c *gin.Context) {
|
||||
var req DeleteAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := authorityUsecase.DeleteAuthority(c, req.AuthorityId); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateAuthority 更新角色
|
||||
func (a *AuthorityApi) UpdateAuthority(c *gin.Context) {
|
||||
var req UpdateAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
authority := &system.Authority{
|
||||
AuthorityId: req.AuthorityId,
|
||||
AuthorityName: req.AuthorityName,
|
||||
ParentId: req.ParentId,
|
||||
DefaultRouter: req.DefaultRouter,
|
||||
}
|
||||
|
||||
updated, err := authorityUsecase.UpdateAuthority(c, authority)
|
||||
if err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"authority": updated}, "更新成功", c)
|
||||
}
|
||||
|
||||
// GetAuthorityList 获取角色列表
|
||||
func (a *AuthorityApi) GetAuthorityList(c *gin.Context) {
|
||||
// 从JWT获取当前用户的角色ID
|
||||
claims, exists := c.Get("claims")
|
||||
if !exists {
|
||||
response.FailWithMessage("获取用户信息失败", c)
|
||||
return
|
||||
}
|
||||
customClaims := claims.(*jwtPkg.CustomClaims)
|
||||
|
||||
list, err := authorityUsecase.GetAuthorityInfoList(c, customClaims.AuthorityID)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||
}
|
||||
|
||||
// CopyAuthority 复制角色
|
||||
func (a *AuthorityApi) CopyAuthority(c *gin.Context) {
|
||||
var req CopyAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从JWT获取当前用户的角色ID
|
||||
claims, exists := c.Get("claims")
|
||||
if !exists {
|
||||
response.FailWithMessage("获取用户信息失败", c)
|
||||
return
|
||||
}
|
||||
customClaims := claims.(*jwtPkg.CustomClaims)
|
||||
|
||||
authority := &system.Authority{
|
||||
AuthorityId: req.Authority.AuthorityId,
|
||||
AuthorityName: req.Authority.AuthorityName,
|
||||
ParentId: req.Authority.ParentId,
|
||||
DefaultRouter: req.Authority.DefaultRouter,
|
||||
}
|
||||
|
||||
created, err := authorityUsecase.CopyAuthority(c, customClaims.AuthorityID, req.OldAuthorityId, authority)
|
||||
if err != nil {
|
||||
response.FailWithMessage("复制失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"authority": created}, "复制成功", c)
|
||||
}
|
||||
|
||||
// SetDataAuthority 设置数据权限
|
||||
func (a *AuthorityApi) SetDataAuthority(c *gin.Context) {
|
||||
var req SetDataAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 从JWT获取当前用户的角色ID
|
||||
claims, exists := c.Get("claims")
|
||||
if !exists {
|
||||
response.FailWithMessage("获取用户信息失败", c)
|
||||
return
|
||||
}
|
||||
customClaims := claims.(*jwtPkg.CustomClaims)
|
||||
|
||||
if err := authorityUsecase.SetDataAuthority(c, customClaims.AuthorityID, req.AuthorityId, req.DataAuthorityId); err != nil {
|
||||
response.FailWithMessage("设置失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/pkg/jwt"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BaseApi struct{}
|
||||
|
||||
// LoginRequest 登录请求
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Captcha string `json:"captcha"`
|
||||
CaptchaId string `json:"captchaId"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
User interface{} `json:"user"`
|
||||
Token string `json:"token"`
|
||||
ExpiresAt int64 `json:"expiresAt"`
|
||||
}
|
||||
|
||||
// JWT实例(需要在初始化时设置)
|
||||
var jwtInstance *jwt.JWT
|
||||
|
||||
// SetJWTInstance 设置JWT实例
|
||||
func SetJWTInstance(j *jwt.JWT) {
|
||||
jwtInstance = j
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (b *BaseApi) Login(c *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := userUsecase.Login(c, req.Username, req.Password)
|
||||
if err != nil {
|
||||
response.FailWithMessage("用户名不存在或者密码错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if user.Enable != 1 {
|
||||
response.FailWithMessage("用户被禁止登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
// 生成token
|
||||
claims := jwtInstance.CreateClaims(jwt.BaseClaims{
|
||||
UUID: user.UUID.String(),
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
NickName: user.NickName,
|
||||
AuthorityID: user.AuthorityId,
|
||||
})
|
||||
|
||||
token, err := jwtInstance.CreateToken(claims.BaseClaims)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取token失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SetToken(c, token, int(jwtInstance.ExpiresAt.Seconds()))
|
||||
|
||||
response.OkWithDetailed(LoginResponse{
|
||||
User: toUserResponse(user),
|
||||
Token: token,
|
||||
ExpiresAt: claims.ExpiresAt.UnixMilli(),
|
||||
}, "登录成功", c)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CasbinApi struct{}
|
||||
|
||||
// CasbinRule Casbin规则
|
||||
type CasbinRule struct {
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
// UpdateCasbinRequest 更新Casbin请求
|
||||
type UpdateCasbinRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
CasbinInfos []CasbinRule `json:"casbinInfos"`
|
||||
}
|
||||
|
||||
// GetPolicyPathByAuthorityIdRequest 获取权限路径请求
|
||||
type GetPolicyPathByAuthorityIdRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateCasbin 更新Casbin权限
|
||||
func (ca *CasbinApi) UpdateCasbin(c *gin.Context) {
|
||||
var req UpdateCasbinRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为业务层格式
|
||||
rules := make([]struct {
|
||||
Path string
|
||||
Method string
|
||||
}, len(req.CasbinInfos))
|
||||
for i, r := range req.CasbinInfos {
|
||||
rules[i] = struct {
|
||||
Path string
|
||||
Method string
|
||||
}{
|
||||
Path: r.Path,
|
||||
Method: r.Method,
|
||||
}
|
||||
}
|
||||
|
||||
if err := casbinUsecase.UpdateCasbin(0, req.AuthorityId, rules); err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// GetPolicyPathByAuthorityId 获取权限路径
|
||||
func (ca *CasbinApi) GetPolicyPathByAuthorityId(c *gin.Context) {
|
||||
var req GetPolicyPathByAuthorityIdRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
paths := casbinUsecase.GetPolicyPathByAuthorityId(req.AuthorityId)
|
||||
|
||||
response.OkWithDetailed(gin.H{"paths": paths}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DictionaryApi struct{}
|
||||
|
||||
// CreateDictionaryRequest 创建字典请求
|
||||
type CreateDictionaryRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type string `json:"type" binding:"required"`
|
||||
Status *bool `json:"status"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// UpdateDictionaryRequest 更新字典请求
|
||||
type UpdateDictionaryRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Status *bool `json:"status"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// GetDictionaryListRequest 获取字典列表请求
|
||||
type GetDictionaryListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CreateSysDictionary 创建字典
|
||||
func (d *DictionaryApi) CreateSysDictionary(c *gin.Context) {
|
||||
var req CreateDictionaryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
dict := &system.Dictionary{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Status: req.Status,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
|
||||
if err := dictionaryUsecase.CreateDictionary(c, dict); err != nil {
|
||||
response.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysDictionary 删除字典
|
||||
func (d *DictionaryApi) DeleteSysDictionary(c *gin.Context) {
|
||||
idStr := c.Query("ID")
|
||||
if idStr == "" {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
id, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := dictionaryUsecase.DeleteDictionary(c, uint(id)); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateSysDictionary 更新字典
|
||||
func (d *DictionaryApi) UpdateSysDictionary(c *gin.Context) {
|
||||
var req UpdateDictionaryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
dict := &system.Dictionary{
|
||||
ID: req.ID,
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
Status: req.Status,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
|
||||
if err := dictionaryUsecase.UpdateDictionary(c, dict); err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// FindSysDictionary 根据ID或type获取字典
|
||||
func (d *DictionaryApi) FindSysDictionary(c *gin.Context) {
|
||||
idStr := c.Query("ID")
|
||||
dictType := c.Query("type")
|
||||
|
||||
var id uint
|
||||
if idStr != "" {
|
||||
idUint, err := strconv.ParseUint(idStr, 10, 64)
|
||||
if err != nil {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
id = uint(idUint)
|
||||
}
|
||||
|
||||
dict, err := dictionaryUsecase.GetDictionary(c, dictType, id, nil)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"resysDictionary": dict}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysDictionaryList 获取字典列表
|
||||
func (d *DictionaryApi) GetSysDictionaryList(c *gin.Context) {
|
||||
name := c.Query("name")
|
||||
|
||||
list, err := dictionaryUsecase.GetDictionaryList(c, name)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"list": list}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type JwtApi struct{}
|
||||
|
||||
// JsonInBlacklist JWT加入黑名单
|
||||
func (j *JwtApi) JsonInBlacklist(c *gin.Context) {
|
||||
token := utils.GetToken(c)
|
||||
if token == "" {
|
||||
response.FailWithMessage("token不存在", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := jwtBlacklistUsecase.JsonInBlacklist(c, token); err != nil {
|
||||
response.FailWithMessage("jwt作废失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
utils.ClearToken(c)
|
||||
response.OkWithMessage("jwt作废成功", c)
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MenuApi struct{}
|
||||
|
||||
// AddBaseMenuRequest 添加菜单请求
|
||||
type AddBaseMenuRequest struct {
|
||||
ParentId uint `json:"parentId"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Component string `json:"component"`
|
||||
Sort int `json:"sort"`
|
||||
Meta map[string]interface{} `json:"meta"`
|
||||
Parameters []MenuParameter `json:"parameters"`
|
||||
MenuBtn []MenuBtn `json:"menuBtn"`
|
||||
}
|
||||
|
||||
// MenuParameter 菜单参数
|
||||
type MenuParameter struct {
|
||||
Type string `json:"type"`
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// MenuBtn 菜单按钮
|
||||
type MenuBtn struct {
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// UpdateBaseMenuRequest 更新菜单请求
|
||||
type UpdateBaseMenuRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
ParentId uint `json:"parentId"`
|
||||
Path string `json:"path" binding:"required"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Hidden bool `json:"hidden"`
|
||||
Component string `json:"component"`
|
||||
Sort int `json:"sort"`
|
||||
Meta map[string]interface{} `json:"meta"`
|
||||
Parameters []MenuParameter `json:"parameters"`
|
||||
MenuBtn []MenuBtn `json:"menuBtn"`
|
||||
}
|
||||
|
||||
// DeleteBaseMenuRequest 删除菜单请求
|
||||
type DeleteBaseMenuRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// GetMenuByIdRequest 根据ID获取菜单请求
|
||||
type GetMenuByIdRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// AddMenuAuthorityRequest 添加菜单权限请求
|
||||
type AddMenuAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
Menus []uint `json:"menus"`
|
||||
}
|
||||
|
||||
// GetMenuAuthorityRequest 获取菜单权限请求
|
||||
type GetMenuAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
}
|
||||
|
||||
// AddBaseMenu 添加菜单
|
||||
func (m *MenuApi) AddBaseMenu(c *gin.Context) {
|
||||
var req AddBaseMenuRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
menu := &system.BaseMenu{
|
||||
ParentId: req.ParentId,
|
||||
Path: req.Path,
|
||||
Name: req.Name,
|
||||
Hidden: req.Hidden,
|
||||
Component: req.Component,
|
||||
Sort: req.Sort,
|
||||
}
|
||||
|
||||
if err := menuUsecase.AddBaseMenu(c, menu); err != nil {
|
||||
response.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteBaseMenu 删除菜单
|
||||
func (m *MenuApi) DeleteBaseMenu(c *gin.Context) {
|
||||
var req DeleteBaseMenuRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := menuUsecase.DeleteBaseMenu(c, req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateBaseMenu 更新菜单
|
||||
func (m *MenuApi) UpdateBaseMenu(c *gin.Context) {
|
||||
var req UpdateBaseMenuRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
menu := &system.BaseMenu{
|
||||
ID: req.ID,
|
||||
ParentId: req.ParentId,
|
||||
Path: req.Path,
|
||||
Name: req.Name,
|
||||
Hidden: req.Hidden,
|
||||
Component: req.Component,
|
||||
Sort: req.Sort,
|
||||
}
|
||||
|
||||
if err := menuUsecase.UpdateBaseMenu(c, menu); err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// GetMenu 获取用户菜单
|
||||
func (m *MenuApi) GetMenu(c *gin.Context) {
|
||||
authorityId := utils.GetUserAuthorityId(c)
|
||||
menus, err := menuUsecase.GetMenuTree(c, authorityId)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetMenuList 获取菜单列表
|
||||
func (m *MenuApi) GetMenuList(c *gin.Context) {
|
||||
authorityId := utils.GetUserAuthorityId(c)
|
||||
menus, err := menuUsecase.GetInfoList(c, authorityId)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"list": menus}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetBaseMenuTree 获取菜单树
|
||||
func (m *MenuApi) GetBaseMenuTree(c *gin.Context) {
|
||||
authorityId := utils.GetUserAuthorityId(c)
|
||||
menus, err := menuUsecase.GetBaseMenuTree(c, authorityId)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetBaseMenuById 根据ID获取菜单
|
||||
func (m *MenuApi) GetBaseMenuById(c *gin.Context) {
|
||||
var req GetMenuByIdRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
menu, err := menuUsecase.GetBaseMenuById(c, req.ID)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"menu": menu}, "获取成功", c)
|
||||
}
|
||||
|
||||
// AddMenuAuthority 添加菜单权限
|
||||
func (m *MenuApi) AddMenuAuthority(c *gin.Context) {
|
||||
var req AddMenuAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前用户的角色ID
|
||||
adminAuthorityId := utils.GetUserAuthorityId(c)
|
||||
|
||||
// 构建菜单列表
|
||||
var menus []*system.BaseMenu
|
||||
for _, menuId := range req.Menus {
|
||||
menus = append(menus, &system.BaseMenu{ID: menuId})
|
||||
}
|
||||
|
||||
if err := menuUsecase.AddMenuAuthority(c, menus, adminAuthorityId, req.AuthorityId); err != nil {
|
||||
response.FailWithMessage("添加失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("添加成功", c)
|
||||
}
|
||||
|
||||
// GetMenuAuthority 获取菜单权限
|
||||
func (m *MenuApi) GetMenuAuthority(c *gin.Context) {
|
||||
var req GetMenuAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
menus, err := menuUsecase.GetMenuAuthority(c, req.AuthorityId)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"menus": menus}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type OperationRecordApi struct{}
|
||||
|
||||
// DeleteOperationRecordRequest 删除操作记录请求
|
||||
type DeleteOperationRecordRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteOperationRecordByIdsRequest 批量删除操作记录请求
|
||||
type DeleteOperationRecordByIdsRequest struct {
|
||||
Ids []uint `json:"ids" binding:"required"`
|
||||
}
|
||||
|
||||
// GetOperationRecordListRequest 获取操作记录列表请求
|
||||
type GetOperationRecordListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Status int `json:"status"`
|
||||
}
|
||||
|
||||
// DeleteSysOperationRecord 删除操作记录
|
||||
func (o *OperationRecordApi) DeleteSysOperationRecord(c *gin.Context) {
|
||||
var req DeleteOperationRecordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := operationRecordUsecase.DeleteOperationRecord(c, req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysOperationRecordByIds 批量删除操作记录
|
||||
func (o *OperationRecordApi) DeleteSysOperationRecordByIds(c *gin.Context) {
|
||||
var req DeleteOperationRecordByIdsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := operationRecordUsecase.DeleteOperationRecordByIds(c, req.Ids); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// FindSysOperationRecord 根据ID获取操作记录
|
||||
func (o *OperationRecordApi) FindSysOperationRecord(c *gin.Context) {
|
||||
idStr := c.Query("ID")
|
||||
if idStr == "" {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
var id uint
|
||||
// 简单转换
|
||||
for _, ch := range idStr {
|
||||
if ch >= '0' && ch <= '9' {
|
||||
id = id*10 + uint(ch-'0')
|
||||
}
|
||||
}
|
||||
|
||||
record, err := operationRecordUsecase.GetOperationRecord(c, id)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"reSysOperationRecord": record}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysOperationRecordList 获取操作记录列表
|
||||
func (o *OperationRecordApi) GetSysOperationRecordList(c *gin.Context) {
|
||||
var req GetOperationRecordListRequest
|
||||
// GET请求从query获取参数
|
||||
req.Page = 1
|
||||
req.PageSize = 10
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.Method != "" {
|
||||
filters["method"] = req.Method
|
||||
}
|
||||
if req.Path != "" {
|
||||
filters["path"] = req.Path
|
||||
}
|
||||
if req.Status != 0 {
|
||||
filters["status"] = req.Status
|
||||
}
|
||||
|
||||
list, total, err := operationRecordUsecase.GetOperationRecordList(c, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/biz/system"
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ParamsApi struct{}
|
||||
|
||||
// CreateParamsRequest 创建参数请求
|
||||
type CreateParamsRequest struct {
|
||||
Key string `json:"key" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// UpdateParamsRequest 更新参数请求
|
||||
type UpdateParamsRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Key string `json:"key" binding:"required"`
|
||||
Value string `json:"value" binding:"required"`
|
||||
Name string `json:"name"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
// DeleteParamsRequest 删除参数请求
|
||||
type DeleteParamsRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// GetParamsListRequest 获取参数列表请求
|
||||
type GetParamsListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Key string `json:"key"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// CreateSysParams 创建参数
|
||||
func (p *ParamsApi) CreateSysParams(c *gin.Context) {
|
||||
var req CreateParamsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
params := &system.Params{
|
||||
Key: req.Key,
|
||||
Value: req.Value,
|
||||
Name: req.Name,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
|
||||
if err := paramsUsecase.Create(c, params); err != nil {
|
||||
response.FailWithMessage("创建失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("创建成功", c)
|
||||
}
|
||||
|
||||
// DeleteSysParams 删除参数
|
||||
func (p *ParamsApi) DeleteSysParams(c *gin.Context) {
|
||||
var req DeleteParamsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := paramsUsecase.Delete(c, req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// UpdateSysParams 更新参数
|
||||
func (p *ParamsApi) UpdateSysParams(c *gin.Context) {
|
||||
var req UpdateParamsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
params := &system.Params{
|
||||
ID: req.ID,
|
||||
Key: req.Key,
|
||||
Value: req.Value,
|
||||
Name: req.Name,
|
||||
Desc: req.Desc,
|
||||
}
|
||||
|
||||
if err := paramsUsecase.Update(c, params); err != nil {
|
||||
response.FailWithMessage("更新失败: "+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("更新成功", c)
|
||||
}
|
||||
|
||||
// GetSysParams 根据Key获取参数
|
||||
func (p *ParamsApi) GetSysParams(c *gin.Context) {
|
||||
key := c.Query("key")
|
||||
if key == "" {
|
||||
response.FailWithMessage("参数错误", c)
|
||||
return
|
||||
}
|
||||
|
||||
params, err := paramsUsecase.GetByKey(c, key)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"sysParams": params}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetSysParamsList 获取参数列表
|
||||
func (p *ParamsApi) GetSysParamsList(c *gin.Context) {
|
||||
var req GetParamsListRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// GET请求使用默认值
|
||||
req.Page = 1
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if req.Key != "" {
|
||||
filters["key"] = req.Key
|
||||
}
|
||||
if req.Name != "" {
|
||||
filters["name"] = req.Name
|
||||
}
|
||||
|
||||
list, total, err := paramsUsecase.GetList(c, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
@@ -0,0 +1,426 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/internal/biz/system"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UserApi struct{}
|
||||
|
||||
// RegisterRequest 注册请求
|
||||
type RegisterRequest struct {
|
||||
Username string `json:"userName" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
Password string `json:"password" binding:"required"`
|
||||
NewPassword string `json:"newPassword" binding:"required"`
|
||||
}
|
||||
|
||||
// ResetPasswordRequest 重置密码请求
|
||||
type ResetPasswordRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// GetUserListRequest 获取用户列表请求
|
||||
type GetUserListRequest struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Username string `json:"username"`
|
||||
NickName string `json:"nickName"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// SetUserInfoRequest 设置用户信息请求
|
||||
type SetUserInfoRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
Enable int `json:"enable"`
|
||||
AuthorityIds []uint `json:"authorityIds"`
|
||||
}
|
||||
|
||||
// SetSelfInfoRequest 设置自己信息请求
|
||||
type SetSelfInfoRequest struct {
|
||||
NickName string `json:"nickName"`
|
||||
HeaderImg string `json:"headerImg"`
|
||||
Phone string `json:"phone"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
// SetUserAuthorityRequest 设置用户角色请求
|
||||
type SetUserAuthorityRequest struct {
|
||||
AuthorityId uint `json:"authorityId" binding:"required"`
|
||||
}
|
||||
|
||||
// SetUserAuthoritiesRequest 设置用户多角色请求
|
||||
type SetUserAuthoritiesRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
AuthorityIds []uint `json:"authorityIds" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteUserRequest 删除用户请求
|
||||
type DeleteUserRequest struct {
|
||||
ID uint `json:"ID" binding:"required"`
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (u *UserApi) Register(c *gin.Context) {
|
||||
var req RegisterRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
user := &system.User{
|
||||
Username: req.Username,
|
||||
Password: req.Password,
|
||||
NickName: req.NickName,
|
||||
HeaderImg: req.HeaderImg,
|
||||
AuthorityId: req.AuthorityId,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
Enable: req.Enable,
|
||||
}
|
||||
|
||||
created, err := userUsecase.Register(c, user)
|
||||
if err != nil {
|
||||
response.FailWithDetailed(gin.H{"user": created}, "注册失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"user": toUserResponse(created)}, "注册成功", c)
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (u *UserApi) ChangePassword(c *gin.Context) {
|
||||
var req ChangePasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
userID := utils.GetUserID(c)
|
||||
if userID == 0 {
|
||||
response.FailWithMessage("请先登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userUsecase.ChangePassword(c, userID, req.Password, req.NewPassword); err != nil {
|
||||
response.FailWithMessage("修改失败,原密码与当前账户不符", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
func (u *UserApi) ResetPassword(c *gin.Context) {
|
||||
var req ResetPasswordRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userUsecase.ResetPassword(c, req.ID, req.Password); err != nil {
|
||||
response.FailWithMessage("重置失败"+err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("重置成功", c)
|
||||
}
|
||||
|
||||
// GetUserInfo 获取用户信息
|
||||
func (u *UserApi) GetUserInfo(c *gin.Context) {
|
||||
uuid := utils.GetUserUuid(c)
|
||||
user, err := userUsecase.GetUserInfo(c, uuid.String())
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithDetailed(gin.H{"userInfo": toUserResponse(user)}, "获取成功", c)
|
||||
}
|
||||
|
||||
// GetUserList 获取用户列表
|
||||
func (u *UserApi) GetUserList(c *gin.Context) {
|
||||
var req GetUserListRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
if req.Page == 0 {
|
||||
req.Page = 1
|
||||
}
|
||||
if req.PageSize == 0 {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
filters := make(map[string]string)
|
||||
if req.Username != "" {
|
||||
filters["username"] = req.Username
|
||||
}
|
||||
if req.NickName != "" {
|
||||
filters["nick_name"] = req.NickName
|
||||
}
|
||||
if req.Phone != "" {
|
||||
filters["phone"] = req.Phone
|
||||
}
|
||||
if req.Email != "" {
|
||||
filters["email"] = req.Email
|
||||
}
|
||||
|
||||
users, total, err := userUsecase.GetUserList(c, req.Page, req.PageSize, filters)
|
||||
if err != nil {
|
||||
response.FailWithMessage("获取失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
list := make([]interface{}, len(users))
|
||||
for i, u := range users {
|
||||
list[i] = toUserResponse(u)
|
||||
}
|
||||
|
||||
response.OkWithDetailed(response.PageResult{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, "获取成功", c)
|
||||
}
|
||||
|
||||
// SetUserInfo 设置用户信息
|
||||
func (u *UserApi) SetUserInfo(c *gin.Context) {
|
||||
var req SetUserInfoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置用户角色
|
||||
if len(req.AuthorityIds) > 0 {
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
if err := userUsecase.SetUserAuthorities(c, authorityID, req.ID, req.AuthorityIds); err != nil {
|
||||
response.FailWithMessage("设置失败", c)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
user := &system.User{
|
||||
ID: req.ID,
|
||||
NickName: req.NickName,
|
||||
HeaderImg: req.HeaderImg,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
Enable: req.Enable,
|
||||
}
|
||||
|
||||
if err := userUsecase.SetUserInfo(c, user); err != nil {
|
||||
response.FailWithMessage("设置失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// SetSelfInfo 设置自己信息
|
||||
func (u *UserApi) SetSelfInfo(c *gin.Context) {
|
||||
var req SetSelfInfoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
userID := utils.GetUserID(c)
|
||||
if userID == 0 {
|
||||
response.FailWithMessage("请先登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
user := &system.User{
|
||||
ID: userID,
|
||||
NickName: req.NickName,
|
||||
HeaderImg: req.HeaderImg,
|
||||
Phone: req.Phone,
|
||||
Email: req.Email,
|
||||
}
|
||||
|
||||
if err := userUsecase.SetSelfInfo(c, user); err != nil {
|
||||
response.FailWithMessage("设置失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// SetSelfSetting 设置自己配置
|
||||
func (u *UserApi) SetSelfSetting(c *gin.Context) {
|
||||
var req map[string]interface{}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
userID := utils.GetUserID(c)
|
||||
if userID == 0 {
|
||||
response.FailWithMessage("请先登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
setting, _ := json.Marshal(req)
|
||||
if err := userUsecase.SetSelfSetting(c, userID, setting); err != nil {
|
||||
response.FailWithMessage("设置失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("设置成功", c)
|
||||
}
|
||||
|
||||
// DeleteUser 删除用户
|
||||
func (u *UserApi) DeleteUser(c *gin.Context) {
|
||||
var req DeleteUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
currentUserID := utils.GetUserID(c)
|
||||
if currentUserID == req.ID {
|
||||
response.FailWithMessage("删除失败, 无法删除自己。", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userUsecase.DeleteUser(c, req.ID); err != nil {
|
||||
response.FailWithMessage("删除失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("删除成功", c)
|
||||
}
|
||||
|
||||
// SetUserAuthority 设置用户角色
|
||||
func (u *UserApi) SetUserAuthority(c *gin.Context) {
|
||||
var req SetUserAuthorityRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
userID := utils.GetUserID(c)
|
||||
if userID == 0 {
|
||||
response.FailWithMessage("请先登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
if err := userUsecase.SetUserAuthority(c, userID, req.AuthorityId); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
// 重新生成token
|
||||
claims := utils.GetUserInfo(c)
|
||||
if claims == nil {
|
||||
response.FailWithMessage("请先登录", c)
|
||||
return
|
||||
}
|
||||
|
||||
claims.AuthorityID = req.AuthorityId
|
||||
token, err := jwtInstance.CreateToken(claims.BaseClaims)
|
||||
if err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("new-token", token)
|
||||
c.Header("new-expires-at", strconv.FormatInt(claims.ExpiresAt.Unix(), 10))
|
||||
utils.SetToken(c, token, int(claims.ExpiresAt.Unix()-time.Now().Unix()))
|
||||
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
// SetUserAuthorities 设置用户多角色
|
||||
func (u *UserApi) SetUserAuthorities(c *gin.Context) {
|
||||
var req SetUserAuthoritiesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.FailWithMessage(err.Error(), c)
|
||||
return
|
||||
}
|
||||
|
||||
authorityID := utils.GetUserAuthorityId(c)
|
||||
if err := userUsecase.SetUserAuthorities(c, authorityID, req.ID, req.AuthorityIds); err != nil {
|
||||
response.FailWithMessage("修改失败", c)
|
||||
return
|
||||
}
|
||||
|
||||
response.OkWithMessage("修改成功", c)
|
||||
}
|
||||
|
||||
// toUserResponse 转换用户响应
|
||||
func toUserResponse(user *system.User) map[string]interface{} {
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"ID": user.ID,
|
||||
"uuid": user.UUID.String(),
|
||||
"userName": user.Username,
|
||||
"nickName": user.NickName,
|
||||
"sideMode": user.SideMode,
|
||||
"headerImg": user.HeaderImg,
|
||||
"baseColor": user.BaseColor,
|
||||
"authorityId": user.AuthorityId,
|
||||
"phone": user.Phone,
|
||||
"email": user.Email,
|
||||
"enable": user.Enable,
|
||||
}
|
||||
|
||||
if user.OriginSetting != nil {
|
||||
result["originSetting"] = string(user.OriginSetting)
|
||||
}
|
||||
|
||||
if user.Authority != nil {
|
||||
result["authority"] = map[string]interface{}{
|
||||
"authorityId": user.Authority.AuthorityId,
|
||||
"authorityName": user.Authority.AuthorityName,
|
||||
"parentId": user.Authority.ParentId,
|
||||
"defaultRouter": user.Authority.DefaultRouter,
|
||||
}
|
||||
}
|
||||
|
||||
if len(user.Authorities) > 0 {
|
||||
authorities := make([]map[string]interface{}, len(user.Authorities))
|
||||
for i, a := range user.Authorities {
|
||||
authorities[i] = map[string]interface{}{
|
||||
"authorityId": a.AuthorityId,
|
||||
"authorityName": a.AuthorityName,
|
||||
"parentId": a.ParentId,
|
||||
"defaultRouter": a.DefaultRouter,
|
||||
}
|
||||
}
|
||||
result["authorities"] = authorities
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
+8
-71
@@ -1,47 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"kra/internal/conf"
|
||||
"kra/internal/server/middleware"
|
||||
"kra/internal/service/system"
|
||||
"kra/pkg/jwt"
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/middleware/recovery"
|
||||
"github.com/go-kratos/kratos/v2/middleware/selector"
|
||||
"github.com/gin-gonic/gin"
|
||||
khttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// NewHTTPServer new an HTTP server.
|
||||
func NewHTTPServer(
|
||||
c *conf.Server,
|
||||
jwtPkg *jwt.JWT,
|
||||
userSvc *system.UserService,
|
||||
apiSvc *system.ApiService,
|
||||
authoritySvc *system.AuthorityService,
|
||||
authorityBtnSvc *system.AuthorityBtnService,
|
||||
captchaSvc *system.CaptchaService,
|
||||
casbinSvc *system.CasbinService,
|
||||
menuSvc *system.MenuService,
|
||||
dictionarySvc *system.DictionaryService,
|
||||
jwtBlacklistSvc *system.JwtBlacklistService,
|
||||
operationRecordSvc *system.OperationRecordService,
|
||||
paramsSvc *system.ParamsService,
|
||||
systemConfigSvc *system.SystemConfigService,
|
||||
) *khttp.Server {
|
||||
var opts = []khttp.ServerOption{
|
||||
khttp.Middleware(
|
||||
recovery.Recovery(),
|
||||
// JWT认证中间件(排除公开路由)
|
||||
selector.Server(
|
||||
middleware.JWTAuthSimple(jwtPkg),
|
||||
).Match(newWhiteListMatcher()).Build(),
|
||||
),
|
||||
khttp.ResponseEncoder(response.Encoder),
|
||||
khttp.ErrorEncoder(response.ErrorEncoder),
|
||||
}
|
||||
// NewHTTPServer 创建HTTP服务器,将gin路由挂载到kratos http server
|
||||
func NewHTTPServer(c *conf.Server, ginRouter *gin.Engine) *khttp.Server {
|
||||
var opts = []khttp.ServerOption{}
|
||||
|
||||
if c.Http.Network != "" {
|
||||
opts = append(opts, khttp.Network(c.Http.Network))
|
||||
}
|
||||
@@ -51,43 +20,11 @@ func NewHTTPServer(
|
||||
if c.Http.Timeout != nil {
|
||||
opts = append(opts, khttp.Timeout(c.Http.Timeout.AsDuration()))
|
||||
}
|
||||
|
||||
srv := khttp.NewServer(opts...)
|
||||
|
||||
// 创建路由组
|
||||
rg := &system.RouterGroup{
|
||||
Public: srv.Route("/"),
|
||||
Private: srv.Route("/"),
|
||||
PrivateWithRecord: srv.Route("/"),
|
||||
}
|
||||
|
||||
// 注册各模块路由
|
||||
userSvc.RegisterRoutes(rg)
|
||||
apiSvc.RegisterRoutes(rg)
|
||||
authoritySvc.RegisterRoutes(rg)
|
||||
authorityBtnSvc.RegisterRoutes(rg)
|
||||
captchaSvc.RegisterRoutes(rg)
|
||||
casbinSvc.RegisterRoutes(rg)
|
||||
menuSvc.RegisterRoutes(rg)
|
||||
dictionarySvc.RegisterRoutes(rg)
|
||||
jwtBlacklistSvc.RegisterRoutes(rg)
|
||||
operationRecordSvc.RegisterRoutes(rg)
|
||||
paramsSvc.RegisterRoutes(rg)
|
||||
systemConfigSvc.RegisterRoutes(rg)
|
||||
// 关键:将gin路由挂载到kratos http server
|
||||
srv.HandlePrefix("/", ginRouter)
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
// newWhiteListMatcher 创建白名单匹配器(这些路由不需要JWT认证)
|
||||
func newWhiteListMatcher() selector.MatchFunc {
|
||||
whiteList := map[string]struct{}{
|
||||
"/base/login": {},
|
||||
"/base/captcha": {},
|
||||
"/api/freshCasbin": {},
|
||||
}
|
||||
return func(ctx context.Context, operation string) bool {
|
||||
if _, ok := whiteList[operation]; ok {
|
||||
return false // 不需要认证
|
||||
}
|
||||
return true // 需要认证
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
|
||||
pkgcasbin "kra/pkg/casbin"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrPermissionDenied = errors.New("权限不足")
|
||||
)
|
||||
|
||||
// CasbinRBAC Casbin权限中间件
|
||||
func CasbinRBAC(routerPrefix string) middleware.Middleware {
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
claims, ok := GetClaims(ctx)
|
||||
if !ok {
|
||||
return nil, ErrMissingToken
|
||||
}
|
||||
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
// 获取请求路径
|
||||
path := tr.Operation()
|
||||
if routerPrefix != "" {
|
||||
path = strings.TrimPrefix(path, routerPrefix)
|
||||
}
|
||||
|
||||
// 获取请求方法
|
||||
act := "GET"
|
||||
if header := tr.RequestHeader(); header != nil {
|
||||
if method := header.Get(":method"); method != "" {
|
||||
act = method
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户角色
|
||||
sub := strconv.Itoa(int(claims.AuthorityID))
|
||||
|
||||
// 检查权限
|
||||
enforcer := pkgcasbin.GetEnforcer()
|
||||
if enforcer == nil {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
success, err := enforcer.Enforce(sub, path, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !success {
|
||||
return nil, ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
kratoshttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// EmailSender 邮件发送接口
|
||||
type EmailSender interface {
|
||||
SendError(subject, body string) error
|
||||
}
|
||||
|
||||
// ErrorToEmail 错误邮件通知中间件
|
||||
func ErrorToEmail(sender EmailSender, logger log.Logger) middleware.Middleware {
|
||||
helper := log.NewHelper(logger)
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var (
|
||||
username string
|
||||
body string
|
||||
method string
|
||||
path string
|
||||
ip string
|
||||
)
|
||||
|
||||
// 获取用户信息
|
||||
if claims, ok := GetClaims(ctx); ok {
|
||||
username = claims.Username
|
||||
}
|
||||
if username == "" {
|
||||
username = "Unknown"
|
||||
}
|
||||
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
path = tr.Operation()
|
||||
if header := tr.RequestHeader(); header != nil {
|
||||
method = header.Get(":method")
|
||||
}
|
||||
|
||||
if ht, ok := tr.(kratoshttp.Transporter); ok {
|
||||
r := ht.Request()
|
||||
ip = getClientIP(r)
|
||||
|
||||
// 读取body
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
reply, err := handler(ctx, req)
|
||||
latency := time.Since(start)
|
||||
|
||||
// 如果有错误,发送邮件
|
||||
if err != nil && sender != nil {
|
||||
subject := fmt.Sprintf("%s %s调用了%s报错了", username, ip, path)
|
||||
content := fmt.Sprintf(
|
||||
"接收到的请求为%s\n请求方式为%s\n报错信息如下%s\n耗时%s\n",
|
||||
body, method, err.Error(), latency.String(),
|
||||
)
|
||||
if sendErr := sender.SendError(subject, content); sendErr != nil {
|
||||
helper.Error("ErrorToEmail Failed, err:", sendErr)
|
||||
}
|
||||
}
|
||||
|
||||
return reply, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kra/pkg/casbin"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CasbinConfig Casbin中间件配置
|
||||
type CasbinConfig struct {
|
||||
RouterPrefix string // 路由前缀,需要从路径中去除
|
||||
}
|
||||
|
||||
// 全局配置
|
||||
var casbinConfig *CasbinConfig
|
||||
|
||||
// SetCasbinConfig 设置Casbin配置
|
||||
func SetCasbinConfig(cfg *CasbinConfig) {
|
||||
casbinConfig = cfg
|
||||
}
|
||||
|
||||
// CasbinHandler Casbin权限拦截器(与 gva 保持一致)
|
||||
func CasbinHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
waitUse, _ := utils.GetClaims(c)
|
||||
if waitUse == nil {
|
||||
response.FailWithDetailed(gin.H{}, "权限不足", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 获取请求的PATH
|
||||
path := c.Request.URL.Path
|
||||
// 去除路由前缀
|
||||
if casbinConfig != nil && casbinConfig.RouterPrefix != "" {
|
||||
path = strings.TrimPrefix(path, casbinConfig.RouterPrefix)
|
||||
}
|
||||
|
||||
// 获取请求方法
|
||||
act := c.Request.Method
|
||||
// 获取用户的角色
|
||||
sub := strconv.Itoa(int(waitUse.AuthorityID))
|
||||
|
||||
// 判断策略中是否存在
|
||||
e := casbin.GetEnforcer()
|
||||
if e == nil {
|
||||
response.FailWithDetailed(gin.H{}, "Casbin未初始化", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
success, _ := e.Enforce(sub, path, act)
|
||||
if !success {
|
||||
response.FailWithDetailed(gin.H{}, "权限不足", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"kra/pkg/jwt"
|
||||
"kra/pkg/response"
|
||||
"kra/pkg/utils"
|
||||
|
||||
jwtv5 "github.com/golang-jwt/jwt/v5"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// BlacklistChecker JWT黑名单检查接口
|
||||
type BlacklistChecker interface {
|
||||
IsBlacklist(jwt string) bool
|
||||
}
|
||||
|
||||
// JWTConfig JWT中间件配置
|
||||
type JWTConfig struct {
|
||||
JWT *jwt.JWT
|
||||
BlacklistChecker BlacklistChecker
|
||||
UseMultipoint bool
|
||||
SetRedisJWT func(token, username string) error
|
||||
}
|
||||
|
||||
// 全局配置(需要在初始化时设置)
|
||||
var jwtConfig *JWTConfig
|
||||
|
||||
// SetJWTConfig 设置JWT配置
|
||||
func SetJWTConfig(cfg *JWTConfig) {
|
||||
jwtConfig = cfg
|
||||
// 同时设置 utils 包的 JWT 实例
|
||||
if cfg != nil && cfg.JWT != nil {
|
||||
utils.SetJWT(cfg.JWT)
|
||||
}
|
||||
}
|
||||
|
||||
// JWTAuth JWT认证中间件(与 gva 保持一致)
|
||||
func JWTAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 我们这里jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
|
||||
token := utils.GetToken(c)
|
||||
if token == "" {
|
||||
response.NoAuth("未登录或非法访问,请登录", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查黑名单
|
||||
if jwtConfig != nil && jwtConfig.BlacklistChecker != nil && jwtConfig.BlacklistChecker.IsBlacklist(token) {
|
||||
response.NoAuth("您的帐户异地登陆或令牌失效", c)
|
||||
utils.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if jwtConfig == nil || jwtConfig.JWT == nil {
|
||||
response.NoAuth("JWT配置错误", c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// parseToken 解析token包含的信息
|
||||
claims, err := jwtConfig.JWT.ParseToken(token)
|
||||
if err != nil {
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
response.NoAuth("登录已过期,请重新登录", c)
|
||||
utils.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
response.NoAuth(err.Error(), c)
|
||||
utils.ClearToken(c)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 已登录用户被管理员禁用 需要使该用户的jwt失效 此处比较消耗性能 如果需要 请自行打开
|
||||
// 用户被删除的逻辑 需要优化 此处比较消耗性能 如果需要 请自行打开
|
||||
|
||||
c.Set("claims", claims)
|
||||
|
||||
// 检查是否需要刷新token
|
||||
if claims.ExpiresAt.Unix()-time.Now().Unix() < claims.BufferTime {
|
||||
claims.ExpiresAt = jwtv5.NewNumericDate(time.Now().Add(jwtConfig.JWT.ExpiresAt))
|
||||
newToken, newClaims, err := jwtConfig.JWT.CreateTokenByOldToken(token, *claims)
|
||||
if err == nil {
|
||||
c.Header("new-token", newToken)
|
||||
c.Header("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
|
||||
utils.SetToken(c, newToken, int(jwtConfig.JWT.ExpiresAt.Seconds()/60))
|
||||
|
||||
// 多点登录记录新JWT
|
||||
if jwtConfig.UseMultipoint && jwtConfig.SetRedisJWT != nil {
|
||||
_ = jwtConfig.SetRedisJWT(newToken, newClaims.Username)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Next()
|
||||
|
||||
// 处理后续中间件设置的新token
|
||||
if newToken, exists := c.Get("new-token"); exists {
|
||||
c.Header("new-token", newToken.(string))
|
||||
}
|
||||
if newExpiresAt, exists := c.Get("new-expires-at"); exists {
|
||||
c.Header("new-expires-at", newExpiresAt.(string))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"kra/pkg/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var respPool sync.Pool
|
||||
var bufferSize = 1024
|
||||
|
||||
func init() {
|
||||
respPool.New = func() interface{} {
|
||||
return make([]byte, bufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
// OperationRecord 操作记录结构
|
||||
type OperationRecord struct {
|
||||
IP string `json:"ip"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Status int `json:"status"`
|
||||
Latency time.Duration `json:"latency"`
|
||||
Agent string `json:"agent"`
|
||||
ErrorMessage string `json:"error_message"`
|
||||
Body string `json:"body"`
|
||||
Resp string `json:"resp"`
|
||||
UserID int `json:"user_id"`
|
||||
}
|
||||
|
||||
// OperationRecordCreator 操作记录创建接口
|
||||
type OperationRecordCreator interface {
|
||||
CreateOperationRecord(record *OperationRecord) error
|
||||
}
|
||||
|
||||
// 全局操作记录创建器
|
||||
var operationRecordCreator OperationRecordCreator
|
||||
var operationLogger *zap.Logger
|
||||
|
||||
// SetOperationRecordCreator 设置操作记录创建器
|
||||
func SetOperationRecordCreator(creator OperationRecordCreator) {
|
||||
operationRecordCreator = creator
|
||||
}
|
||||
|
||||
// SetOperationLogger 设置操作记录日志
|
||||
func SetOperationLogger(logger *zap.Logger) {
|
||||
operationLogger = logger
|
||||
}
|
||||
|
||||
// OperationRecordMiddleware 操作记录中间件(与 gva 保持一致)
|
||||
func OperationRecordMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
var body []byte
|
||||
var userId int
|
||||
|
||||
if c.Request.Method != http.MethodGet {
|
||||
var err error
|
||||
body, err = io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
if operationLogger != nil {
|
||||
operationLogger.Error("read body from request error:", zap.Error(err))
|
||||
}
|
||||
} else {
|
||||
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||
}
|
||||
} else {
|
||||
query := c.Request.URL.RawQuery
|
||||
query, _ = url.QueryUnescape(query)
|
||||
split := strings.Split(query, "&")
|
||||
m := make(map[string]string)
|
||||
for _, v := range split {
|
||||
kv := strings.Split(v, "=")
|
||||
if len(kv) == 2 {
|
||||
m[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
body, _ = json.Marshal(&m)
|
||||
}
|
||||
|
||||
claims, _ := utils.GetClaims(c)
|
||||
if claims != nil && claims.BaseClaims.ID != 0 {
|
||||
userId = int(claims.BaseClaims.ID)
|
||||
} else {
|
||||
id, err := strconv.Atoi(c.Request.Header.Get("x-user-id"))
|
||||
if err != nil {
|
||||
userId = 0
|
||||
}
|
||||
userId = id
|
||||
}
|
||||
|
||||
record := OperationRecord{
|
||||
IP: c.ClientIP(),
|
||||
Method: c.Request.Method,
|
||||
Path: c.Request.URL.Path,
|
||||
Agent: c.Request.UserAgent(),
|
||||
Body: "",
|
||||
UserID: userId,
|
||||
}
|
||||
|
||||
// 上传文件时候 中间件日志进行裁断操作
|
||||
if strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data") {
|
||||
record.Body = "[文件]"
|
||||
} else {
|
||||
if len(body) > bufferSize {
|
||||
record.Body = "[超出记录长度]"
|
||||
} else {
|
||||
record.Body = string(body)
|
||||
}
|
||||
}
|
||||
|
||||
writer := responseBodyWriter{
|
||||
ResponseWriter: c.Writer,
|
||||
body: &bytes.Buffer{},
|
||||
}
|
||||
c.Writer = writer
|
||||
now := time.Now()
|
||||
|
||||
c.Next()
|
||||
|
||||
latency := time.Since(now)
|
||||
record.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String()
|
||||
record.Status = c.Writer.Status()
|
||||
record.Latency = latency
|
||||
record.Resp = writer.body.String()
|
||||
|
||||
// 检查是否为下载响应,如果是则截断
|
||||
if strings.Contains(c.Writer.Header().Get("Pragma"), "public") ||
|
||||
strings.Contains(c.Writer.Header().Get("Expires"), "0") ||
|
||||
strings.Contains(c.Writer.Header().Get("Cache-Control"), "must-revalidate, post-check=0, pre-check=0") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Type"), "application/force-download") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Type"), "application/octet-stream") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Type"), "application/vnd.ms-excel") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Type"), "application/download") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Disposition"), "attachment") ||
|
||||
strings.Contains(c.Writer.Header().Get("Content-Transfer-Encoding"), "binary") {
|
||||
if len(record.Resp) > bufferSize {
|
||||
record.Resp = "[超出记录长度]"
|
||||
}
|
||||
}
|
||||
|
||||
// 保存操作记录
|
||||
if operationRecordCreator != nil {
|
||||
if err := operationRecordCreator.CreateOperationRecord(&record); err != nil {
|
||||
if operationLogger != nil {
|
||||
operationLogger.Error("create operation record error:", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type responseBodyWriter struct {
|
||||
gin.ResponseWriter
|
||||
body *bytes.Buffer
|
||||
}
|
||||
|
||||
func (r responseBodyWriter) Write(b []byte) (int, error) {
|
||||
r.body.Write(b)
|
||||
return r.ResponseWriter.Write(b)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"kra/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// 全局日志实例
|
||||
var recoveryLogger *zap.Logger
|
||||
|
||||
// SetRecoveryLogger 设置Recovery日志
|
||||
func SetRecoveryLogger(logger *zap.Logger) {
|
||||
recoveryLogger = logger
|
||||
}
|
||||
|
||||
// GinRecovery 自定义Recovery中间件(与 gva 保持一致)
|
||||
func GinRecovery(stack bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
// 检查是否为断开的连接
|
||||
var brokenPipe bool
|
||||
if ne, ok := err.(*net.OpError); ok {
|
||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") ||
|
||||
strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
httpRequest, _ := httputil.DumpRequest(c.Request, false)
|
||||
if brokenPipe {
|
||||
if recoveryLogger != nil {
|
||||
recoveryLogger.Error(c.Request.URL.Path,
|
||||
zap.Any("error", err),
|
||||
zap.String("request", string(httpRequest)),
|
||||
)
|
||||
}
|
||||
// 如果连接已断开,无法写入状态
|
||||
c.Error(err.(error))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if stack {
|
||||
if recoveryLogger != nil {
|
||||
recoveryLogger.Error("[Recovery from panic]",
|
||||
zap.Any("error", err),
|
||||
zap.String("request", string(httpRequest)),
|
||||
zap.String("stack", string(debug.Stack())),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if recoveryLogger != nil {
|
||||
recoveryLogger.Error("[Recovery from panic]",
|
||||
zap.Any("error", err),
|
||||
zap.String("request", string(httpRequest)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
response.FailWithMessage("服务器内部错误", c)
|
||||
c.Abort()
|
||||
}
|
||||
}()
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// Cors 跨域中间件
|
||||
func Cors() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
method := c.Request.Method
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
|
||||
c.Header("Access-Control-Allow-Origin", origin)
|
||||
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token,Authorization,Token,X-Token,X-User-Id")
|
||||
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT")
|
||||
c.Header("Access-Control-Expose-Headers", "Content-Length,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Content-Type,New-Token,New-Expires-At")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
|
||||
// 放行所有OPTIONS方法
|
||||
if method == "OPTIONS" {
|
||||
c.AbortWithStatus(http.StatusNoContent)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/metadata"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
"github.com/go-kratos/kratos/v2/transport/http"
|
||||
|
||||
pkgjwt "kra/pkg/jwt"
|
||||
)
|
||||
|
||||
const (
|
||||
// metadata keys for claims
|
||||
mdKeyUserID = "x-md-user-id"
|
||||
mdKeyUsername = "x-md-username"
|
||||
mdKeyAuthorityID = "x-md-authority-id"
|
||||
mdKeyUUID = "x-md-uuid"
|
||||
mdKeyClaims = "x-md-claims"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrMissingToken = errors.New("未登录或非法访问")
|
||||
ErrInvalidToken = errors.New("无效的token")
|
||||
ErrBlacklistJWT = errors.New("您的帐户异地登陆或令牌失效")
|
||||
)
|
||||
|
||||
// BlacklistChecker JWT黑名单检查接口
|
||||
type BlacklistChecker interface {
|
||||
IsBlacklist(jwt string) bool
|
||||
}
|
||||
|
||||
// JWTAuthConfig JWT认证配置
|
||||
type JWTAuthConfig struct {
|
||||
JWT *pkgjwt.JWT
|
||||
BlacklistChecker BlacklistChecker
|
||||
UseMultipoint bool
|
||||
SetRedisJWT func(token, username string) error
|
||||
}
|
||||
|
||||
// JWTAuth JWT认证中间件
|
||||
func JWTAuth(cfg JWTAuthConfig) middleware.Middleware {
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
token := extractToken(tr)
|
||||
if token == "" {
|
||||
return nil, ErrMissingToken
|
||||
}
|
||||
|
||||
// 检查黑名单
|
||||
if cfg.BlacklistChecker != nil && cfg.BlacklistChecker.IsBlacklist(token) {
|
||||
// 清除token
|
||||
if ht, ok := tr.(http.Transporter); ok {
|
||||
clearToken(ht)
|
||||
}
|
||||
return nil, ErrBlacklistJWT
|
||||
}
|
||||
|
||||
claims, err := cfg.JWT.ParseToken(token)
|
||||
if err != nil {
|
||||
if ht, ok := tr.(http.Transporter); ok {
|
||||
clearToken(ht)
|
||||
}
|
||||
if errors.Is(err, pkgjwt.ErrTokenExpired) {
|
||||
return nil, errors.New("登录已过期,请重新登录")
|
||||
}
|
||||
return nil, ErrInvalidToken
|
||||
}
|
||||
|
||||
// 将claims存入metadata
|
||||
claimsJSON, _ := json.Marshal(claims)
|
||||
ctx = metadata.AppendToClientContext(ctx,
|
||||
mdKeyUserID, strconv.FormatUint(uint64(claims.BaseClaims.ID), 10),
|
||||
mdKeyUsername, claims.Username,
|
||||
mdKeyAuthorityID, strconv.FormatUint(uint64(claims.AuthorityID), 10),
|
||||
mdKeyUUID, claims.UUID,
|
||||
mdKeyClaims, string(claimsJSON),
|
||||
)
|
||||
|
||||
// 检查是否需要刷新token
|
||||
if cfg.JWT.NeedRefresh(claims) {
|
||||
newToken, newClaims, err := cfg.JWT.CreateTokenByOldToken(token, *claims)
|
||||
if err == nil {
|
||||
if ht, ok := tr.(http.Transporter); ok {
|
||||
ht.ReplyHeader().Set("new-token", newToken)
|
||||
ht.ReplyHeader().Set("new-expires-at", strconv.FormatInt(newClaims.ExpiresAt.Unix(), 10))
|
||||
setToken(ht, newToken, int(time.Until(newClaims.ExpiresAt.Time).Seconds()/60))
|
||||
}
|
||||
// 多点登录记录新JWT
|
||||
if cfg.UseMultipoint && cfg.SetRedisJWT != nil {
|
||||
_ = cfg.SetRedisJWT(newToken, newClaims.Username)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// JWTAuthSimple 简化版JWT认证中间件(不含黑名单检查)
|
||||
func JWTAuthSimple(j *pkgjwt.JWT) middleware.Middleware {
|
||||
return JWTAuth(JWTAuthConfig{JWT: j})
|
||||
}
|
||||
|
||||
// extractToken 从请求中提取token
|
||||
func extractToken(tr transport.Transporter) string {
|
||||
// 优先从Authorization头获取
|
||||
auth := tr.RequestHeader().Get("Authorization")
|
||||
if auth != "" {
|
||||
if strings.HasPrefix(auth, "Bearer ") {
|
||||
return strings.TrimPrefix(auth, "Bearer ")
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
// 从x-token头获取
|
||||
token := tr.RequestHeader().Get("x-token")
|
||||
if token != "" {
|
||||
return token
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// clearToken 清除token
|
||||
func clearToken(ht http.Transporter) {
|
||||
ht.ReplyHeader().Set("x-token", "")
|
||||
}
|
||||
|
||||
// setToken 设置token到cookie
|
||||
func setToken(ht http.Transporter, token string, maxAge int) {
|
||||
// 通过header设置cookie信息,让前端处理
|
||||
ht.ReplyHeader().Set("x-token", token)
|
||||
}
|
||||
|
||||
// GetClaims 从context获取claims
|
||||
func GetClaims(ctx context.Context) (*pkgjwt.CustomClaims, bool) {
|
||||
if md, ok := metadata.FromServerContext(ctx); ok {
|
||||
claimsStr := md.Get(mdKeyClaims)
|
||||
if claimsStr != "" {
|
||||
var claims pkgjwt.CustomClaims
|
||||
if err := json.Unmarshal([]byte(claimsStr), &claims); err == nil {
|
||||
return &claims, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// GetUserID 从context获取用户ID
|
||||
func GetUserID(ctx context.Context) uint {
|
||||
if md, ok := metadata.FromServerContext(ctx); ok {
|
||||
if idStr := md.Get(mdKeyUserID); idStr != "" {
|
||||
if id, err := strconv.ParseUint(idStr, 10, 64); err == nil {
|
||||
return uint(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetUsername 从context获取用户名
|
||||
func GetUsername(ctx context.Context) string {
|
||||
if md, ok := metadata.FromServerContext(ctx); ok {
|
||||
return md.Get(mdKeyUsername)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetAuthorityID 从context获取角色ID
|
||||
func GetAuthorityID(ctx context.Context) uint {
|
||||
if md, ok := metadata.FromServerContext(ctx); ok {
|
||||
if idStr := md.Get(mdKeyAuthorityID); idStr != "" {
|
||||
if id, err := strconv.ParseUint(idStr, 10, 64); err == nil {
|
||||
return uint(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetToken 从context获取token
|
||||
func GetToken(ctx context.Context) string {
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
return extractToken(tr)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
kratoshttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
)
|
||||
|
||||
// LogLayout 日志layout
|
||||
type LogLayout struct {
|
||||
Time time.Time `json:"time"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Query string `json:"query,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
IP string `json:"ip"`
|
||||
UserAgent string `json:"user_agent"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Cost time.Duration `json:"cost"`
|
||||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
// LoggerConfig 日志中间件配置
|
||||
type LoggerConfig struct {
|
||||
// Filter 用户自定义过滤
|
||||
Filter func(ctx context.Context, path string) bool
|
||||
// FilterKeyword 关键字过滤
|
||||
FilterKeyword func(layout *LogLayout) bool
|
||||
// AuthProcess 鉴权处理
|
||||
AuthProcess func(ctx context.Context, layout *LogLayout)
|
||||
// Print 日志处理
|
||||
Print func(LogLayout)
|
||||
// Source 服务唯一标识
|
||||
Source string
|
||||
}
|
||||
|
||||
// Logger 日志中间件
|
||||
func Logger(cfg LoggerConfig) middleware.Middleware {
|
||||
if cfg.Print == nil {
|
||||
cfg.Print = func(layout LogLayout) {
|
||||
v, _ := json.Marshal(layout)
|
||||
log.Info(string(v))
|
||||
}
|
||||
}
|
||||
if cfg.Source == "" {
|
||||
cfg.Source = "Kratos"
|
||||
}
|
||||
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var (
|
||||
path string
|
||||
query string
|
||||
body string
|
||||
ip string
|
||||
userAgent string
|
||||
)
|
||||
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
path = tr.Operation()
|
||||
if header := tr.RequestHeader(); header != nil {
|
||||
userAgent = header.Get("User-Agent")
|
||||
}
|
||||
|
||||
if ht, ok := tr.(kratoshttp.Transporter); ok {
|
||||
r := ht.Request()
|
||||
query = r.URL.RawQuery
|
||||
ip = getClientIP(r)
|
||||
|
||||
// 读取body(仅在未过滤时)
|
||||
if cfg.Filter == nil || !cfg.Filter(ctx, path) {
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要过滤
|
||||
if cfg.Filter != nil && cfg.Filter(ctx, path) {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
reply, err := handler(ctx, req)
|
||||
cost := time.Since(start)
|
||||
|
||||
layout := LogLayout{
|
||||
Time: time.Now(),
|
||||
Path: path,
|
||||
Query: query,
|
||||
Body: body,
|
||||
IP: ip,
|
||||
UserAgent: userAgent,
|
||||
Cost: cost,
|
||||
Source: cfg.Source,
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
layout.Error = err.Error()
|
||||
}
|
||||
|
||||
// 处理鉴权信息
|
||||
if cfg.AuthProcess != nil {
|
||||
cfg.AuthProcess(ctx, &layout)
|
||||
}
|
||||
|
||||
// 关键字过滤
|
||||
if cfg.FilterKeyword != nil {
|
||||
cfg.FilterKeyword(&layout)
|
||||
}
|
||||
|
||||
// 输出日志
|
||||
cfg.Print(layout)
|
||||
|
||||
return reply, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultLogger 默认日志中间件
|
||||
func DefaultLogger() middleware.Middleware {
|
||||
return Logger(LoggerConfig{
|
||||
Source: "Kratos",
|
||||
})
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
kratoshttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const maxBodySize = 1024
|
||||
|
||||
// OperationRecord 操作记录
|
||||
type OperationRecord struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
CreatedAt time.Time `gorm:"index"`
|
||||
Ip string `gorm:"column:ip;comment:请求ip"`
|
||||
Method string `gorm:"column:method;comment:请求方法"`
|
||||
Path string `gorm:"column:path;comment:请求路径"`
|
||||
Status int `gorm:"column:status;comment:请求状态"`
|
||||
Latency time.Duration `gorm:"column:latency;comment:延迟"`
|
||||
Agent string `gorm:"column:agent;comment:代理"`
|
||||
ErrorMessage string `gorm:"column:error_message;comment:错误信息"`
|
||||
Body string `gorm:"type:text;column:body;comment:请求Body"`
|
||||
Resp string `gorm:"type:text;column:resp;comment:响应Body"`
|
||||
UserID int `gorm:"column:user_id;comment:用户id"`
|
||||
}
|
||||
|
||||
func (OperationRecord) TableName() string {
|
||||
return "sys_operation_records"
|
||||
}
|
||||
|
||||
// OperationLog 操作日志中间件
|
||||
func OperationLog(db *gorm.DB, logger log.Logger) middleware.Middleware {
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var (
|
||||
body string
|
||||
userID int
|
||||
ip string
|
||||
method string
|
||||
path string
|
||||
agent string
|
||||
)
|
||||
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
path = tr.Operation()
|
||||
if header := tr.RequestHeader(); header != nil {
|
||||
method = header.Get(":method")
|
||||
agent = header.Get("User-Agent")
|
||||
}
|
||||
|
||||
// 获取HTTP请求信息
|
||||
if ht, ok := tr.(kratoshttp.Transporter); ok {
|
||||
r := ht.Request()
|
||||
ip = getClientIP(r)
|
||||
|
||||
if r.Method != http.MethodGet {
|
||||
bodyBytes, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||
if len(bodyBytes) > maxBodySize {
|
||||
body = "[超出记录长度]"
|
||||
} else {
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
} else {
|
||||
query := r.URL.RawQuery
|
||||
query, _ = url.QueryUnescape(query)
|
||||
params := parseQuery(query)
|
||||
bodyBytes, _ := json.Marshal(params)
|
||||
body = string(bodyBytes)
|
||||
}
|
||||
|
||||
// 检查是否为文件上传
|
||||
if strings.Contains(r.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
body = "[文件]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户ID
|
||||
if claims, ok := GetClaims(ctx); ok {
|
||||
userID = int(claims.BaseClaims.ID)
|
||||
} else {
|
||||
// 尝试从header获取
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
if ht, ok := tr.(kratoshttp.Transporter); ok {
|
||||
if idStr := ht.Request().Header.Get("x-user-id"); idStr != "" {
|
||||
if id, err := strconv.Atoi(idStr); err == nil {
|
||||
userID = id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
reply, err := handler(ctx, req)
|
||||
latency := time.Since(start)
|
||||
|
||||
// 记录响应
|
||||
var resp string
|
||||
if reply != nil {
|
||||
respBytes, _ := json.Marshal(reply)
|
||||
if len(respBytes) > maxBodySize {
|
||||
resp = "[超出记录长度]"
|
||||
} else {
|
||||
resp = string(respBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 记录错误
|
||||
var errMsg string
|
||||
status := 200
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
status = 500
|
||||
}
|
||||
|
||||
// 保存记录
|
||||
record := OperationRecord{
|
||||
Ip: ip,
|
||||
Method: method,
|
||||
Path: path,
|
||||
Status: status,
|
||||
Latency: latency,
|
||||
Agent: agent,
|
||||
ErrorMessage: errMsg,
|
||||
Body: body,
|
||||
Resp: resp,
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if db != nil {
|
||||
go func() {
|
||||
if err := db.Create(&record).Error; err != nil {
|
||||
log.NewHelper(logger).Error("create operation record error:", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return reply, err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
"github.com/go-kratos/kratos/v2/transport"
|
||||
kratoshttp "github.com/go-kratos/kratos/v2/transport/http"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// LimitConfig 限流配置
|
||||
type LimitConfig struct {
|
||||
// GenerationKey 根据业务生成key
|
||||
GenerationKey func(ctx context.Context, path string) string
|
||||
// CheckOrMark 检查函数
|
||||
CheckOrMark func(ctx context.Context, key string, expire int, limit int) error
|
||||
// Expire key过期时间(秒)
|
||||
Expire int
|
||||
// Limit 周期内限制次数
|
||||
Limit int
|
||||
}
|
||||
|
||||
// RateLimit 限流中间件
|
||||
func RateLimit(cfg LimitConfig) middleware.Middleware {
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
var key string
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
path := tr.Operation()
|
||||
key = cfg.GenerationKey(ctx, path)
|
||||
}
|
||||
|
||||
if err := cfg.CheckOrMark(ctx, key, cfg.Expire, cfg.Limit); err != nil {
|
||||
return nil, errors.New(429, "RATE_LIMIT", err.Error())
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultGenerationKey 默认生成key(基于IP)
|
||||
func DefaultGenerationKey(ctx context.Context, path string) string {
|
||||
if tr, ok := transport.FromServerContext(ctx); ok {
|
||||
if ht, ok := tr.(kratoshttp.Transporter); ok {
|
||||
return "Limit_" + getClientIP(ht.Request())
|
||||
}
|
||||
}
|
||||
return "Limit_unknown"
|
||||
}
|
||||
|
||||
// NewRedisCheckOrMark 创建基于Redis的检查函数
|
||||
func NewRedisCheckOrMark(rdb *redis.Client) func(ctx context.Context, key string, expire int, limit int) error {
|
||||
return func(ctx context.Context, key string, expire int, limit int) error {
|
||||
if rdb == nil {
|
||||
return nil
|
||||
}
|
||||
return SetLimitWithTime(ctx, rdb, key, limit, time.Duration(expire)*time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// SetLimitWithTime 设置访问次数
|
||||
func SetLimitWithTime(ctx context.Context, rdb *redis.Client, key string, limit int, expiration time.Duration) error {
|
||||
count, err := rdb.Exists(ctx, key).Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
// key不存在,创建并设置过期时间
|
||||
pipe := rdb.TxPipeline()
|
||||
pipe.Incr(ctx, key)
|
||||
pipe.Expire(ctx, key, expiration)
|
||||
_, err = pipe.Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// key存在,检查次数
|
||||
times, err := rdb.Get(ctx, key).Int()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if times >= limit {
|
||||
// 获取剩余时间
|
||||
ttl, err := rdb.PTTL(ctx, key).Result()
|
||||
if err != nil {
|
||||
return fmt.Errorf("请求太过频繁,请稍后再试")
|
||||
}
|
||||
return fmt.Errorf("请求太过频繁, 请 %s 后尝试", ttl.String())
|
||||
}
|
||||
|
||||
return rdb.Incr(ctx, key).Err()
|
||||
}
|
||||
|
||||
// DefaultRateLimit 默认限流中间件
|
||||
func DefaultRateLimit(rdb *redis.Client, expire, limit int) middleware.Middleware {
|
||||
return RateLimit(LimitConfig{
|
||||
GenerationKey: DefaultGenerationKey,
|
||||
CheckOrMark: NewRedisCheckOrMark(rdb),
|
||||
Expire: expire,
|
||||
Limit: limit,
|
||||
})
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/log"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
)
|
||||
|
||||
// Recovery panic恢复中间件
|
||||
func Recovery(logger log.Logger) middleware.Middleware {
|
||||
helper := log.NewHelper(logger)
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (reply interface{}, err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
// 检查是否为断开的连接
|
||||
var brokenPipe bool
|
||||
if ne, ok := rerr.(*net.OpError); ok {
|
||||
if se, ok := ne.Err.(*os.SyscallError); ok {
|
||||
errStr := strings.ToLower(se.Error())
|
||||
if strings.Contains(errStr, "broken pipe") ||
|
||||
strings.Contains(errStr, "connection reset by peer") {
|
||||
brokenPipe = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stack := string(debug.Stack())
|
||||
if brokenPipe {
|
||||
helper.Errorw(
|
||||
"msg", "[Recovery from panic]",
|
||||
"error", rerr,
|
||||
"broken_pipe", true,
|
||||
)
|
||||
} else {
|
||||
helper.Errorw(
|
||||
"msg", "[Recovery from panic]",
|
||||
"error", rerr,
|
||||
"stack", stack,
|
||||
)
|
||||
}
|
||||
|
||||
err = errors.InternalServer("PANIC", "服务器内部错误")
|
||||
}
|
||||
}()
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kratos/kratos/v2/errors"
|
||||
"github.com/go-kratos/kratos/v2/middleware"
|
||||
)
|
||||
|
||||
// Timeout 超时中间件
|
||||
func Timeout(timeout time.Duration) middleware.Middleware {
|
||||
return func(handler middleware.Handler) middleware.Handler {
|
||||
return func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// 使用 buffered channel 避免 goroutine 泄漏
|
||||
type result struct {
|
||||
reply interface{}
|
||||
err error
|
||||
}
|
||||
done := make(chan result, 1)
|
||||
panicChan := make(chan interface{}, 1)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
select {
|
||||
case panicChan <- p:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
reply, err := handler(ctx, req)
|
||||
select {
|
||||
case done <- result{reply: reply, err: err}:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case p := <-panicChan:
|
||||
panic(p)
|
||||
case r := <-done:
|
||||
return r.reply, r.err
|
||||
case <-ctx.Done():
|
||||
return nil, errors.GatewayTimeout("TIMEOUT", "请求超时")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package router
|
||||
|
||||
import "kra/internal/server/router/system"
|
||||
|
||||
var RouterGroupApp = new(RouterGroup)
|
||||
|
||||
type RouterGroup struct {
|
||||
System system.RouterGroup
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package system
|
||||
|
||||
import api "kra/internal/server/handler/system"
|
||||
|
||||
type RouterGroup struct {
|
||||
BaseRouter
|
||||
UserRouter
|
||||
ApiRouter
|
||||
AuthorityRouter
|
||||
MenuRouter
|
||||
DictionaryRouter
|
||||
CasbinRouter
|
||||
JwtRouter
|
||||
OperationRecordRouter
|
||||
ParamsRouter
|
||||
}
|
||||
|
||||
var (
|
||||
baseApi = api.BaseApi{}
|
||||
userApi = api.UserApi{}
|
||||
apiApi = api.ApiApi{}
|
||||
authorityApi = api.AuthorityApi{}
|
||||
menuApi = api.MenuApi{}
|
||||
dictionaryApi = api.DictionaryApi{}
|
||||
casbinApi = api.CasbinApi{}
|
||||
jwtApi = api.JwtApi{}
|
||||
operationRecordApi = api.OperationRecordApi{}
|
||||
paramsApi = api.ParamsApi{}
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ApiRouter struct{}
|
||||
|
||||
func (r *ApiRouter) InitApiRouter(Router *gin.RouterGroup, PublicRouter *gin.RouterGroup) {
|
||||
apiRouter := Router.Group("api").Use(middleware.OperationRecordMiddleware())
|
||||
apiRouterWithoutRecord := Router.Group("api")
|
||||
apiPublicRouter := PublicRouter.Group("api")
|
||||
{
|
||||
apiRouter.POST("createApi", apiApi.CreateApi)
|
||||
apiRouter.POST("deleteApi", apiApi.DeleteApi)
|
||||
apiRouter.POST("updateApi", apiApi.UpdateApi)
|
||||
apiRouter.POST("getApiById", apiApi.GetApiById)
|
||||
apiRouter.DELETE("deleteApisByIds", apiApi.DeleteApisByIds)
|
||||
}
|
||||
{
|
||||
apiRouterWithoutRecord.POST("getAllApis", apiApi.GetAllApis)
|
||||
apiRouterWithoutRecord.POST("getApiList", apiApi.GetApiList)
|
||||
}
|
||||
{
|
||||
apiPublicRouter.GET("freshCasbin", apiApi.FreshCasbin)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthorityRouter struct{}
|
||||
|
||||
func (r *AuthorityRouter) InitAuthorityRouter(Router *gin.RouterGroup) {
|
||||
authorityRouter := Router.Group("authority").Use(middleware.OperationRecordMiddleware())
|
||||
authorityRouterWithoutRecord := Router.Group("authority")
|
||||
{
|
||||
authorityRouter.POST("createAuthority", authorityApi.CreateAuthority)
|
||||
authorityRouter.POST("deleteAuthority", authorityApi.DeleteAuthority)
|
||||
authorityRouter.PUT("updateAuthority", authorityApi.UpdateAuthority)
|
||||
authorityRouter.POST("copyAuthority", authorityApi.CopyAuthority)
|
||||
authorityRouter.POST("setDataAuthority", authorityApi.SetDataAuthority)
|
||||
}
|
||||
{
|
||||
authorityRouterWithoutRecord.POST("getAuthorityList", authorityApi.GetAuthorityList)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package system
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type BaseRouter struct{}
|
||||
|
||||
func (r *BaseRouter) InitBaseRouter(Router *gin.RouterGroup) {
|
||||
baseRouter := Router.Group("base")
|
||||
{
|
||||
baseRouter.POST("login", baseApi.Login)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type CasbinRouter struct{}
|
||||
|
||||
func (r *CasbinRouter) InitCasbinRouter(Router *gin.RouterGroup) {
|
||||
casbinRouter := Router.Group("casbin").Use(middleware.OperationRecordMiddleware())
|
||||
casbinRouterWithoutRecord := Router.Group("casbin")
|
||||
{
|
||||
casbinRouter.POST("updateCasbin", casbinApi.UpdateCasbin)
|
||||
}
|
||||
{
|
||||
casbinRouterWithoutRecord.POST("getPolicyPathByAuthorityId", casbinApi.GetPolicyPathByAuthorityId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DictionaryRouter struct{}
|
||||
|
||||
func (r *DictionaryRouter) InitDictionaryRouter(Router *gin.RouterGroup) {
|
||||
dictionaryRouter := Router.Group("sysDictionary").Use(middleware.OperationRecordMiddleware())
|
||||
dictionaryRouterWithoutRecord := Router.Group("sysDictionary")
|
||||
{
|
||||
dictionaryRouter.POST("createSysDictionary", dictionaryApi.CreateSysDictionary)
|
||||
dictionaryRouter.DELETE("deleteSysDictionary", dictionaryApi.DeleteSysDictionary)
|
||||
dictionaryRouter.PUT("updateSysDictionary", dictionaryApi.UpdateSysDictionary)
|
||||
}
|
||||
{
|
||||
dictionaryRouterWithoutRecord.GET("findSysDictionary", dictionaryApi.FindSysDictionary)
|
||||
dictionaryRouterWithoutRecord.GET("getSysDictionaryList", dictionaryApi.GetSysDictionaryList)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package system
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type JwtRouter struct{}
|
||||
|
||||
func (r *JwtRouter) InitJwtRouter(Router *gin.RouterGroup) {
|
||||
jwtRouter := Router.Group("jwt")
|
||||
{
|
||||
jwtRouter.POST("jsonInBlacklist", jwtApi.JsonInBlacklist)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type MenuRouter struct{}
|
||||
|
||||
func (r *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) {
|
||||
menuRouter := Router.Group("menu").Use(middleware.OperationRecordMiddleware())
|
||||
menuRouterWithoutRecord := Router.Group("menu")
|
||||
{
|
||||
menuRouter.POST("addBaseMenu", menuApi.AddBaseMenu)
|
||||
menuRouter.POST("addMenuAuthority", menuApi.AddMenuAuthority)
|
||||
menuRouter.POST("deleteBaseMenu", menuApi.DeleteBaseMenu)
|
||||
menuRouter.POST("updateBaseMenu", menuApi.UpdateBaseMenu)
|
||||
}
|
||||
{
|
||||
menuRouterWithoutRecord.POST("getMenu", menuApi.GetMenu)
|
||||
menuRouterWithoutRecord.POST("getMenuList", menuApi.GetMenuList)
|
||||
menuRouterWithoutRecord.POST("getBaseMenuTree", menuApi.GetBaseMenuTree)
|
||||
menuRouterWithoutRecord.POST("getMenuAuthority", menuApi.GetMenuAuthority)
|
||||
menuRouterWithoutRecord.POST("getBaseMenuById", menuApi.GetBaseMenuById)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package system
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type OperationRecordRouter struct{}
|
||||
|
||||
func (r *OperationRecordRouter) InitOperationRecordRouter(Router *gin.RouterGroup) {
|
||||
operationRecordRouter := Router.Group("sysOperationRecord")
|
||||
{
|
||||
operationRecordRouter.DELETE("deleteSysOperationRecord", operationRecordApi.DeleteSysOperationRecord)
|
||||
operationRecordRouter.DELETE("deleteSysOperationRecordByIds", operationRecordApi.DeleteSysOperationRecordByIds)
|
||||
operationRecordRouter.GET("findSysOperationRecord", operationRecordApi.FindSysOperationRecord)
|
||||
operationRecordRouter.GET("getSysOperationRecordList", operationRecordApi.GetSysOperationRecordList)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ParamsRouter struct{}
|
||||
|
||||
func (r *ParamsRouter) InitParamsRouter(Router *gin.RouterGroup) {
|
||||
paramsRouter := Router.Group("sysParams").Use(middleware.OperationRecordMiddleware())
|
||||
paramsRouterWithoutRecord := Router.Group("sysParams")
|
||||
{
|
||||
paramsRouter.POST("createSysParams", paramsApi.CreateSysParams)
|
||||
paramsRouter.DELETE("deleteSysParams", paramsApi.DeleteSysParams)
|
||||
paramsRouter.PUT("updateSysParams", paramsApi.UpdateSysParams)
|
||||
}
|
||||
{
|
||||
paramsRouterWithoutRecord.GET("getSysParams", paramsApi.GetSysParams)
|
||||
paramsRouterWithoutRecord.GET("getSysParamsList", paramsApi.GetSysParamsList)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"kra/internal/server/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type UserRouter struct{}
|
||||
|
||||
func (r *UserRouter) InitUserRouter(Router *gin.RouterGroup) {
|
||||
userRouter := Router.Group("user").Use(middleware.OperationRecordMiddleware())
|
||||
userRouterWithoutRecord := Router.Group("user")
|
||||
{
|
||||
userRouter.POST("admin_register", userApi.Register)
|
||||
userRouter.POST("changePassword", userApi.ChangePassword)
|
||||
userRouter.POST("setUserAuthority", userApi.SetUserAuthority)
|
||||
userRouter.DELETE("deleteUser", userApi.DeleteUser)
|
||||
userRouter.PUT("setUserInfo", userApi.SetUserInfo)
|
||||
userRouter.PUT("setSelfInfo", userApi.SetSelfInfo)
|
||||
userRouter.POST("setUserAuthorities", userApi.SetUserAuthorities)
|
||||
userRouter.POST("resetPassword", userApi.ResetPassword)
|
||||
userRouter.PUT("setSelfSetting", userApi.SetSelfSetting)
|
||||
}
|
||||
{
|
||||
userRouterWithoutRecord.POST("getUserList", userApi.GetUserList)
|
||||
userRouterWithoutRecord.GET("getUserInfo", userApi.GetUserInfo)
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,4 @@ import (
|
||||
)
|
||||
|
||||
// ProviderSet is server providers.
|
||||
var ProviderSet = wire.NewSet(NewGRPCServer, NewHTTPServer)
|
||||
var ProviderSet = wire.NewSet(NewGRPCServer, NewGinRouter, NewHTTPServer)
|
||||
|
||||
Reference in New Issue
Block a user