From 5e3380f5ef9eb34aa8a8520b519f4dd545235eb9 Mon Sep 17 00:00:00 2001 From: Echo <1711788888@qq.com> Date: Fri, 13 Mar 2026 21:12:17 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E6=9B=B4=E6=96=B0=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/api/v1/system/sys_api.go | 58 +++++ server/api/v1/system/sys_authority.go | 55 ++++ server/api/v1/system/sys_menu.go | 70 ++++++ server/api/v1/system/sys_skills.go | 44 ++++ server/config/system.go | 1 - server/core/server.go | 3 + server/global/version.go | 2 +- server/go.mod | 13 +- server/go.sum | 58 ++--- server/initialize/ensure_tables.go | 5 + server/initialize/router.go | 49 +--- server/model/common/request/common.go | 6 +- server/model/system/request/sys_api.go | 7 + server/model/system/request/sys_menu.go | 6 + server/model/system/request/sys_skills.go | 16 ++ server/model/system/request/sys_user.go | 8 + server/plugin/announcement/api/enter.go | 10 + server/plugin/announcement/api/info.go | 183 ++++++++++++++ server/plugin/announcement/config/config.go | 4 + server/plugin/announcement/gen/gen.go | 18 ++ server/plugin/announcement/initialize/api.go | 50 ++++ .../announcement/initialize/dictionary.go | 13 + server/plugin/announcement/initialize/gorm.go | 21 ++ server/plugin/announcement/initialize/menu.go | 23 ++ .../plugin/announcement/initialize/router.go | 15 ++ .../plugin/announcement/initialize/viper.go | 18 ++ server/plugin/announcement/model/info.go | 20 ++ .../plugin/announcement/model/request/info.go | 13 + server/plugin/announcement/plugin.go | 33 +++ server/plugin/announcement/plugin/plugin.go | 5 + server/plugin/announcement/router/enter.go | 10 + server/plugin/announcement/router/info.go | 31 +++ server/plugin/announcement/service/enter.go | 5 + server/plugin/announcement/service/info.go | 78 ++++++ server/plugin/register.go | 4 + server/router/system/sys_api.go | 6 +- server/router/system/sys_authority.go | 4 +- server/router/system/sys_menu.go | 2 + server/router/system/sys_skills.go | 8 +- server/service/system/sys_authority.go | 79 ++++++ server/service/system/sys_casbin.go | 42 ++++ server/service/system/sys_menu.go | 59 +++++ server/service/system/sys_skills.go | 236 ++++++++++++++++++ server/service/system/sys_user.go | 20 +- web-app/vite.config.ts | 2 + 45 files changed, 1310 insertions(+), 103 deletions(-) create mode 100644 server/plugin/announcement/api/enter.go create mode 100644 server/plugin/announcement/api/info.go create mode 100644 server/plugin/announcement/config/config.go create mode 100644 server/plugin/announcement/gen/gen.go create mode 100644 server/plugin/announcement/initialize/api.go create mode 100644 server/plugin/announcement/initialize/dictionary.go create mode 100644 server/plugin/announcement/initialize/gorm.go create mode 100644 server/plugin/announcement/initialize/menu.go create mode 100644 server/plugin/announcement/initialize/router.go create mode 100644 server/plugin/announcement/initialize/viper.go create mode 100644 server/plugin/announcement/model/info.go create mode 100644 server/plugin/announcement/model/request/info.go create mode 100644 server/plugin/announcement/plugin.go create mode 100644 server/plugin/announcement/plugin/plugin.go create mode 100644 server/plugin/announcement/router/enter.go create mode 100644 server/plugin/announcement/router/info.go create mode 100644 server/plugin/announcement/service/enter.go create mode 100644 server/plugin/announcement/service/info.go diff --git a/server/api/v1/system/sys_api.go b/server/api/v1/system/sys_api.go index bcc0e7d..49486b9 100644 --- a/server/api/v1/system/sys_api.go +++ b/server/api/v1/system/sys_api.go @@ -321,3 +321,61 @@ func (s *SystemApiApi) FreshCasbin(c *gin.Context) { } response.OkWithMessage("刷新成功", c) } + +// GetApiRoles +// @Tags SysApi +// @Summary 获取拥有指定API权限的角色ID列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param path query string true "API路径" +// @Param method query string true "请求方法" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功" +// @Router /api/getApiRoles [get] +func (s *SystemApiApi) GetApiRoles(c *gin.Context) { + path := c.Query("path") + method := c.Query("method") + if path == "" || method == "" { + response.FailWithMessage("API路径和请求方法不能为空", c) + return + } + authorityIds, err := casbinService.GetAuthoritiesByApi(path, method) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + if authorityIds == nil { + authorityIds = []uint{} + } + response.OkWithDetailed(authorityIds, "获取成功", c) +} + +// SetApiRoles +// @Tags SysApi +// @Summary 全量覆盖某API关联的角色列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SetApiAuthorities true "API路径、请求方法和角色ID列表" +// @Success 200 {object} response.Response{msg=string} "设置成功" +// @Router /api/setApiRoles [post] +func (s *SystemApiApi) SetApiRoles(c *gin.Context) { + var req systemReq.SetApiAuthorities + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if req.Path == "" || req.Method == "" { + response.FailWithMessage("API路径和请求方法不能为空", c) + return + } + if err := casbinService.SetApiAuthorities(req.Path, req.Method, req.AuthorityIds); err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败"+err.Error(), c) + return + } + // 刷新casbin缓存使策略立即生效 + _ = casbinService.FreshCasbin() + response.OkWithMessage("设置成功", c) +} diff --git a/server/api/v1/system/sys_authority.go b/server/api/v1/system/sys_authority.go index 635e0dd..b692b1f 100644 --- a/server/api/v1/system/sys_authority.go +++ b/server/api/v1/system/sys_authority.go @@ -4,6 +4,7 @@ import ( "git.echol.cn/loser/st/server/global" "git.echol.cn/loser/st/server/model/common/response" "git.echol.cn/loser/st/server/model/system" + systemReq "git.echol.cn/loser/st/server/model/system/request" systemRes "git.echol.cn/loser/st/server/model/system/response" "git.echol.cn/loser/st/server/utils" @@ -200,3 +201,57 @@ func (a *AuthorityApi) SetDataAuthority(c *gin.Context) { } response.OkWithMessage("设置成功", c) } + +// GetUsersByAuthority +// @Tags Authority +// @Summary 获取拥有指定角色的用户ID列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param authorityId query uint true "角色ID" +// @Success 200 {object} response.Response{data=[]uint,msg=string} "获取成功" +// @Router /authority/getUsersByAuthority [get] +func (a *AuthorityApi) GetUsersByAuthority(c *gin.Context) { + var req systemReq.SetRoleUsers + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + userIds, err := authorityService.GetUserIdsByAuthorityId(req.AuthorityId) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + if userIds == nil { + userIds = []uint{} + } + response.OkWithDetailed(userIds, "获取成功", c) +} + +// SetRoleUsers +// @Tags Authority +// @Summary 全量覆盖某角色关联的用户列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SetRoleUsers true "角色ID和用户ID列表" +// @Success 200 {object} response.Response{msg=string} "设置成功" +// @Router /authority/setRoleUsers [post] +func (a *AuthorityApi) SetRoleUsers(c *gin.Context) { + var req systemReq.SetRoleUsers + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if req.AuthorityId == 0 { + response.FailWithMessage("角色ID不能为空", c) + return + } + if err := authorityService.SetRoleUsers(req.AuthorityId, req.UserIds); err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败"+err.Error(), c) + return + } + response.OkWithMessage("设置成功", c) +} diff --git a/server/api/v1/system/sys_menu.go b/server/api/v1/system/sys_menu.go index 8d2f998..325c19f 100644 --- a/server/api/v1/system/sys_menu.go +++ b/server/api/v1/system/sys_menu.go @@ -244,6 +244,76 @@ func (a *AuthorityMenuApi) GetBaseMenuById(c *gin.Context) { response.OkWithDetailed(systemRes.SysBaseMenuResponse{Menu: menu}, "获取成功", c) } +// GetMenuRoles +// @Tags AuthorityMenu +// @Summary 获取拥有指定菜单的角色ID列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param menuId query uint true "菜单ID" +// @Success 200 {object} response.Response{data=map[string]interface{},msg=string} "获取成功" +// @Router /menu/getMenuRoles [get] +func (a *AuthorityMenuApi) GetMenuRoles(c *gin.Context) { + var req systemReq.SetMenuAuthorities + if err := c.ShouldBindQuery(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if req.MenuId == 0 { + response.FailWithMessage("菜单ID不能为空", c) + return + } + authorityIds, err := menuService.GetAuthoritiesByMenuId(req.MenuId) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + if authorityIds == nil { + authorityIds = []uint{} + } + defaultRouterAuthorityIds, err := menuService.GetDefaultRouterAuthorityIds(req.MenuId) + if err != nil { + global.GVA_LOG.Error("获取首页角色失败!", zap.Error(err)) + response.FailWithMessage("获取失败"+err.Error(), c) + return + } + if defaultRouterAuthorityIds == nil { + defaultRouterAuthorityIds = []uint{} + } + response.OkWithDetailed(gin.H{ + "authorityIds": authorityIds, + "defaultRouterAuthorityIds": defaultRouterAuthorityIds, + }, "获取成功", c) +} + +// SetMenuRoles +// @Tags AuthorityMenu +// @Summary 全量覆盖某菜单关联的角色列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body systemReq.SetMenuAuthorities true "菜单ID和角色ID列表" +// @Success 200 {object} response.Response{msg=string} "设置成功" +// @Router /menu/setMenuRoles [post] +func (a *AuthorityMenuApi) SetMenuRoles(c *gin.Context) { + var req systemReq.SetMenuAuthorities + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage(err.Error(), c) + return + } + if req.MenuId == 0 { + response.FailWithMessage("菜单ID不能为空", c) + return + } + if err := menuService.SetMenuAuthorities(req.MenuId, req.AuthorityIds); err != nil { + global.GVA_LOG.Error("设置失败!", zap.Error(err)) + response.FailWithMessage("设置失败"+err.Error(), c) + return + } + response.OkWithMessage("设置成功", c) +} + // GetMenuList // @Tags Menu // @Summary 分页获取基础menu列表 diff --git a/server/api/v1/system/sys_skills.go b/server/api/v1/system/sys_skills.go index 167dec8..b761072 100644 --- a/server/api/v1/system/sys_skills.go +++ b/server/api/v1/system/sys_skills.go @@ -1,6 +1,8 @@ package system import ( + "net/http" + "git.echol.cn/loser/st/server/global" "git.echol.cn/loser/st/server/model/common/response" "git.echol.cn/loser/st/server/model/system/request" @@ -55,6 +57,17 @@ func (s *SkillsApi) SaveSkill(c *gin.Context) { response.OkWithMessage("保存成功", c) } +func (s *SkillsApi) DeleteSkill(c *gin.Context) { + var req request.SkillDeleteRequest + _ = c.ShouldBindJSON(&req) + if err := skillsService.Delete(c.Request.Context(), req); err != nil { + global.GVA_LOG.Error("删除技能失败", zap.Error(err)) + response.FailWithMessage("删除技能失败: "+err.Error(), c) + return + } + response.OkWithMessage("删除成功", c) +} + func (s *SkillsApi) CreateScript(c *gin.Context) { var req request.SkillScriptCreateRequest _ = c.ShouldBindJSON(&req) @@ -217,3 +230,34 @@ func (s *SkillsApi) SaveGlobalConstraint(c *gin.Context) { } response.OkWithMessage("保存成功", c) } + +func (s *SkillsApi) PackageSkill(c *gin.Context) { + var req request.SkillPackageRequest + _ = c.ShouldBindJSON(&req) + + fileName, data, err := skillsService.Package(c.Request.Context(), req) + if err != nil { + global.GVA_LOG.Error("打包技能失败", zap.Error(err)) + response.FailWithMessage("打包技能失败: "+err.Error(), c) + return + } + + c.Header("Content-Type", "application/zip") + c.Header("Content-Disposition", "attachment; filename=\""+fileName+"\"") + c.Data(http.StatusOK, "application/zip", data) +} + +func (s *SkillsApi) DownloadOnlineSkill(c *gin.Context) { + var req request.DownloadOnlineSkillReq + if err := c.ShouldBindJSON(&req); err != nil { + response.FailWithMessage("参数错误", c) + return + } + + if err := skillsService.DownloadOnlineSkill(c.Request.Context(), req); err != nil { + global.GVA_LOG.Error("下载在线技能失败", zap.Error(err)) + response.FailWithMessage("下载在线技能失败: "+err.Error(), c) + return + } + response.OkWithMessage("下载成功", c) +} diff --git a/server/config/system.go b/server/config/system.go index 4f09773..78977fa 100644 --- a/server/config/system.go +++ b/server/config/system.go @@ -12,5 +12,4 @@ type System struct { UseMongo bool `mapstructure:"use-mongo" json:"use-mongo" yaml:"use-mongo"` // 使用mongo UseStrictAuth bool `mapstructure:"use-strict-auth" json:"use-strict-auth" yaml:"use-strict-auth"` // 使用树形角色分配模式 DisableAutoMigrate bool `mapstructure:"disable-auto-migrate" json:"disable-auto-migrate" yaml:"disable-auto-migrate"` // 自动迁移数据库表结构,生产环境建议设为false,手动迁移 - DataDir string `mapstructure:"data-dir" json:"data-dir" yaml:"data-dir"` // 数据目录 } diff --git a/server/core/server.go b/server/core/server.go index 7d5c65c..a6e633e 100644 --- a/server/core/server.go +++ b/server/core/server.go @@ -35,6 +35,9 @@ func RunServer() { address := fmt.Sprintf(":%d", global.GVA_CONFIG.System.Addr) fmt.Printf(` + 欢迎使用 gin-vue-admin + 当前版本:%s + 插件市场:https://plugin.gin-vue-admin.com 默认自动化文档地址:http://127.0.0.1%s/swagger/index.html 默认MCP SSE地址:http://127.0.0.1%s%s 默认MCP Message地址:http://127.0.0.1%s%s diff --git a/server/global/version.go b/server/global/version.go index 544d423..1fa4bf5 100644 --- a/server/global/version.go +++ b/server/global/version.go @@ -4,7 +4,7 @@ package global // 目前只有Version正式使用 其余为预留 const ( // Version 当前版本号 - Version = "v2.8.9" + Version = "v2.9.0" // AppName 应用名称 AppName = "Gin-Vue-Admin" // Description 应用描述 diff --git a/server/go.mod b/server/go.mod index 90468ce..8e0daf9 100644 --- a/server/go.mod +++ b/server/go.mod @@ -6,7 +6,7 @@ toolchain go1.24.2 require ( github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible - github.com/aws/aws-sdk-go v1.55.6 + github.com/aws/aws-sdk-go v1.55.8 github.com/casbin/casbin/v2 v2.103.0 github.com/casbin/gorm-adapter/v3 v3.32.0 github.com/dzwvip/gorm-oracle v0.1.2 @@ -20,13 +20,11 @@ require ( github.com/gookit/color v1.5.4 github.com/huaweicloud/huaweicloud-sdk-go-obs v3.24.9+incompatible github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible - github.com/lib/pq v1.10.9 github.com/mark3labs/mcp-go v0.41.1 github.com/mholt/archives v0.1.1 github.com/minio/minio-go/v7 v7.0.84 github.com/mojocn/base64Captcha v1.3.8 github.com/otiai10/copy v1.14.1 - github.com/pgvector/pgvector-go v0.3.0 github.com/pkg/errors v0.9.1 github.com/qiniu/go-sdk/v7 v7.25.2 github.com/qiniu/qmgo v1.1.9 @@ -53,6 +51,7 @@ require ( gorm.io/driver/mysql v1.5.7 gorm.io/driver/postgres v1.5.11 gorm.io/driver/sqlserver v1.5.4 + gorm.io/gen v0.3.26 gorm.io/gorm v1.25.12 ) @@ -121,7 +120,6 @@ require ( github.com/magiconair/properties v1.8.9 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/microsoft/go-mssqldb v1.8.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/minlz v1.0.0 // indirect @@ -174,13 +172,14 @@ require ( golang.org/x/arch v0.13.0 // indirect golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect golang.org/x/image v0.23.0 // indirect - golang.org/x/net v0.37.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.29.0 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gorm.io/driver/sqlite v1.5.0 // indirect + gorm.io/hints v1.1.2 // indirect gorm.io/plugin/dbresolver v1.5.3 // indirect modernc.org/fileutil v1.3.0 // indirect modernc.org/libc v1.61.9 // indirect diff --git a/server/go.sum b/server/go.sum index 0361ac4..0045e64 100644 --- a/server/go.sum +++ b/server/go.sum @@ -15,8 +15,6 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -entgo.io/ent v0.14.3 h1:wokAV/kIlH9TeklJWGGS7AYJdVckr0DloWjIcO9iIIQ= -entgo.io/ent v0.14.3/go.mod h1:aDPE/OziPEu8+OWbzy4UlvWmD2/kbRuWfK2A40hcxJM= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= @@ -56,8 +54,8 @@ github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= -github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= -github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= +github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= @@ -152,10 +150,6 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-pg/pg/v10 v10.11.0 h1:CMKJqLgTrfpE/aOVeLdybezR2om071Vh38OLZjsyMI0= -github.com/go-pg/pg/v10 v10.11.0/go.mod h1:4BpHRoxE61y4Onpof3x1a2SQvi9c+q1dJnrNdMjsroA= -github.com/go-pg/zerochecker v0.2.0 h1:pp7f72c3DobMWOb2ErtZsnrPaSvHd2W4o9//8HtF4mU= -github.com/go-pg/zerochecker v0.2.0/go.mod h1:NJZ4wKL0NmTtz0GKCoJ8kym6Xn/EQzXRl2OnAe7MmDo= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -214,9 +208,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -284,8 +277,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -322,8 +313,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= @@ -377,8 +368,6 @@ github.com/otiai10/mint v1.6.3 h1:87qsV/aw1F5as1eH1zS/yqHY85ANKVMgkDrf9rcxbQs= github.com/otiai10/mint v1.6.3/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pgvector/pgvector-go v0.3.0 h1:Ij+Yt78R//uYqs3Zk35evZFvr+G0blW0OUN+Q2D1RWc= -github.com/pgvector/pgvector-go v0.3.0/go.mod h1:duFy+PXWfW7QQd5ibqutBO4GxLsUZ9RVXhFZGIBsWSA= github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU= github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= @@ -481,8 +470,6 @@ github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZ github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= github.com/tklauser/numcpus v0.9.0 h1:lmyCHtANi8aRUgkckBgoDk1nHCux3n2cgkJLXdQGPDo= github.com/tklauser/numcpus v0.9.0/go.mod h1:SN6Nq1O3VychhC1npsWostA+oW+VOQTxZrS604NSRyI= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= -github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= @@ -492,24 +479,8 @@ github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU= github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= -github.com/uptrace/bun v1.1.12 h1:sOjDVHxNTuM6dNGaba0wUuz7KvDE1BmNu9Gqs2gJSXQ= -github.com/uptrace/bun v1.1.12/go.mod h1:NPG6JGULBeQ9IU6yHp7YGELRa5Agmd7ATZdz4tGZ6z0= -github.com/uptrace/bun/dialect/pgdialect v1.1.12 h1:m/CM1UfOkoBTglGO5CUTKnIKKOApOYxkcP2qn0F9tJk= -github.com/uptrace/bun/dialect/pgdialect v1.1.12/go.mod h1:Ij6WIxQILxLlL2frUBxUBOZJtLElD2QQNDcu/PWDHTc= -github.com/uptrace/bun/driver/pgdriver v1.1.12 h1:3rRWB1GK0psTJrHwxzNfEij2MLibggiLdTqjTtfHc1w= -github.com/uptrace/bun/driver/pgdriver v1.1.12/go.mod h1:ssYUP+qwSEgeDDS1xm2XBip9el1y9Mi5mTAvLoiADLM= -github.com/vmihailenco/bufpool v0.1.11 h1:gOq2WmBrq0i2yW5QJ16ykccQ4wH9UyEsgLm6czKAd94= -github.com/vmihailenco/bufpool v0.1.11/go.mod h1:AFf/MOy3l2CFTKbxwt0mp2MwnqjNEs5H/UxrkA5jxTQ= -github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= -github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= -github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc= -github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= -github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -606,8 +577,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -636,8 +607,8 @@ golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -761,8 +732,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= +golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -827,12 +798,17 @@ gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c= gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I= gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g= gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g= +gorm.io/gen v0.3.26 h1:sFf1j7vNStimPRRAtH4zz5NiHM+1dr6eA9aaRdplyhY= +gorm.io/gen v0.3.26/go.mod h1:a5lq5y3w4g5LMxBcw0wnO6tYUCdNutWODq5LrIt75LE= gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= +gorm.io/hints v1.1.2 h1:b5j0kwk5p4+3BtDtYqqfY+ATSxjj+6ptPgVveuynn9o= +gorm.io/hints v1.1.2/go.mod h1:/ARdpUHAtyEMCh5NNi3tI7FsGh+Cj/MIUlvNxCNCFWg= gorm.io/plugin/dbresolver v1.5.3 h1:wFwINGZZmttuu9h7XpvbDHd8Lf9bb8GNzp/NpAMV2wU= gorm.io/plugin/dbresolver v1.5.3/go.mod h1:TSrVhaUg2DZAWP3PrHlDlITEJmNOkL0tFTjvTEsQ4XE= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -840,8 +816,6 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -mellium.im/sasl v0.3.1 h1:wE0LW6g7U83vhvxjC1IY8DnXM+EU095yeo8XClvCdfo= -mellium.im/sasl v0.3.1/go.mod h1:xm59PUYpZHhgQ9ZqoJ5QaCqzWMi8IeS49dhp6plPCzw= modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/ccgo/v4 v4.23.13 h1:PFiaemQwE/jdwi8XEHyEV+qYWoIuikLP3T4rvDeJb00= diff --git a/server/initialize/ensure_tables.go b/server/initialize/ensure_tables.go index 022b917..e9bc04c 100644 --- a/server/initialize/ensure_tables.go +++ b/server/initialize/ensure_tables.go @@ -5,6 +5,7 @@ import ( "git.echol.cn/loser/st/server/model/example" sysModel "git.echol.cn/loser/st/server/model/system" + "git.echol.cn/loser/st/server/plugin/announcement/model" "git.echol.cn/loser/st/server/service/system" adapter "github.com/casbin/gorm-adapter/v3" "gorm.io/gorm" @@ -64,6 +65,8 @@ func (e *ensureTables) MigrateTable(ctx context.Context) (context.Context, error example.ExaFileChunk{}, example.ExaFileUploadAndDownload{}, example.ExaAttachmentCategory{}, + + model.Info{}, } for _, t := range tables { _ = db.AutoMigrate(&t) @@ -103,6 +106,8 @@ func (e *ensureTables) TableCreated(ctx context.Context) bool { example.ExaFileChunk{}, example.ExaFileUploadAndDownload{}, example.ExaAttachmentCategory{}, + + model.Info{}, } yes := true for _, t := range tables { diff --git a/server/initialize/router.go b/server/initialize/router.go index 8e5a7b5..f06d053 100644 --- a/server/initialize/router.go +++ b/server/initialize/router.go @@ -35,21 +35,12 @@ func (fs justFilesFilesystem) Open(name string) (http.File, error) { func Routers() *gin.Engine { Router := gin.New() - - // 设置文件上传大小限制(10MB) - Router.MaxMultipartMemory = 10 << 20 // 10 MB - // 使用自定义的 Recovery 中间件,记录 panic 并入库 Router.Use(middleware.GinRecovery(true)) if gin.Mode() == gin.DebugMode { Router.Use(gin.Logger()) } - // 跨域配置(前台应用需要) - // 必须在静态文件路由之前注册,否则静态文件跨域会失败 - Router.Use(middleware.Cors()) - global.GVA_LOG.Info("use middleware cors") - if !global.GVA_CONFIG.MCP.Separate { sseServer := McpRun() @@ -66,26 +57,6 @@ func Routers() *gin.Engine { systemRouter := router.RouterGroupApp.System exampleRouter := router.RouterGroupApp.Example - appRouter := router.RouterGroupApp.App // 前台应用路由 - - // SillyTavern 核心脚本静态文件服务 - // 所有核心文件存储在 data/st-core-scripts/ 下,完全独立于 web-app/ 目录 - stCorePath := "data/st-core-scripts" - if _, err := os.Stat(stCorePath); err == nil { - Router.Static("/scripts", stCorePath+"/scripts") - Router.Static("/css", stCorePath+"/css") - Router.Static("/img", stCorePath+"/img") - Router.Static("/webfonts", stCorePath+"/webfonts") - Router.Static("/lib", stCorePath+"/lib") // SillyTavern 依赖的第三方库 - Router.Static("/locales", stCorePath+"/locales") // 国际化文件 - Router.StaticFile("/script.js", stCorePath+"/script.js") // SillyTavern 主入口 - Router.StaticFile("/lib.js", stCorePath+"/lib.js") // Webpack 编译后的 lib.js - global.GVA_LOG.Info("SillyTavern 核心脚本服务已启动: " + stCorePath) - } else { - global.GVA_LOG.Warn("SillyTavern 核心脚本目录不存在: " + stCorePath) - } - - // 管理后台前端静态文件(web) // 如果想要不使用nginx代理前端网页,可以修改 web/.env.production 下的 // VUE_APP_BASE_API = / // VUE_APP_BASE_PATH = http://localhost @@ -95,7 +66,10 @@ func Routers() *gin.Engine { // Router.StaticFile("/", "./dist/index.html") // 前端网页入口页面 Router.StaticFS(global.GVA_CONFIG.Local.StorePath, justFilesFilesystem{http.Dir(global.GVA_CONFIG.Local.StorePath)}) // Router.Use(middleware.LoadTls()) // 如果需要使用https 请打开此中间件 然后前往 core/server.go 将启动模式 更变为 Router.RunTLS("端口","你的cre/pem文件","你的key文件") - + // 跨域,如需跨域可以打开下面的注释 + // Router.Use(middleware.Cors()) // 直接放行全部跨域请求 + // Router.Use(middleware.CorsByRules()) // 按照配置的规则放行跨域请求 + // global.GVA_LOG.Info("use middleware cors") docs.SwaggerInfo.BasePath = global.GVA_CONFIG.System.RouterPrefix Router.GET(global.GVA_CONFIG.System.RouterPrefix+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) global.GVA_LOG.Info("register swagger handler") @@ -137,26 +111,13 @@ func Routers() *gin.Engine { systemRouter.InitSysErrorRouter(PrivateGroup, PublicGroup) // 错误日志 systemRouter.InitLoginLogRouter(PrivateGroup) // 登录日志 systemRouter.InitApiTokenRouter(PrivateGroup) // apiToken签发 - systemRouter.InitSkillsRouter(PrivateGroup) // Skills 定义器 + systemRouter.InitSkillsRouter(PrivateGroup, PublicGroup) // Skills 定义器 exampleRouter.InitCustomerRouter(PrivateGroup) // 客户路由 exampleRouter.InitFileUploadAndDownloadRouter(PrivateGroup) // 文件上传下载功能路由 exampleRouter.InitAttachmentCategoryRouterRouter(PrivateGroup) // 文件上传下载分类 } - // 前台应用路由(新增) - { - appGroup := PublicGroup.Group("app") // 统一使用 /app 前缀 - appRouter.InitAuthRouter(appGroup) // 认证路由:/app/auth/* 和 /app/user/* - appRouter.InitCharacterRouter(appGroup) // 角色卡路由:/app/character/* - appRouter.InitConversationRouter(appGroup) // 对话路由:/app/conversation/* - appRouter.InitAIConfigRouter(appGroup) // AI配置路由:/app/ai-config/* - appRouter.InitPresetRouter(appGroup) // 预设路由:/app/preset/* - appRouter.InitUploadRouter(appGroup) // 上传路由:/app/upload/* - appRouter.InitWorldbookRouter(appGroup) // 世界书路由:/app/worldbook/* - appRouter.InitRegexScriptRouter(appGroup) // 正则脚本路由:/app/regex/* - } - //插件路由安装 InstallPlugin(PrivateGroup, PublicGroup, Router) diff --git a/server/model/common/request/common.go b/server/model/common/request/common.go index b07611d..c729f3d 100644 --- a/server/model/common/request/common.go +++ b/server/model/common/request/common.go @@ -6,9 +6,9 @@ import ( // PageInfo Paging common input parameter structure type PageInfo struct { - Page int `json:"page" form:"page,default=1"` // 页码 - PageSize int `json:"pageSize" form:"pageSize,default=20"` // 每页大小 - Keyword string `json:"keyword" form:"keyword"` // 关键字 + Page int `json:"page" form:"page"` // 页码 + PageSize int `json:"pageSize" form:"pageSize"` // 每页大小 + Keyword string `json:"keyword" form:"keyword"` // 关键字 } func (r *PageInfo) Paginate() func(db *gorm.DB) *gorm.DB { diff --git a/server/model/system/request/sys_api.go b/server/model/system/request/sys_api.go index 9f87cd0..8d69696 100644 --- a/server/model/system/request/sys_api.go +++ b/server/model/system/request/sys_api.go @@ -12,3 +12,10 @@ type SearchApiParams struct { OrderKey string `json:"orderKey"` // 排序 Desc bool `json:"desc"` // 排序方式:升序false(默认)|降序true } + +// SetApiAuthorities 通过API路径和方法全量覆盖关联角色列表 +type SetApiAuthorities struct { + Path string `json:"path" form:"path"` // API路径 + Method string `json:"method" form:"method"` // 请求方法 + AuthorityIds []uint `json:"authorityIds" form:"authorityIds"` // 角色ID列表 +} diff --git a/server/model/system/request/sys_menu.go b/server/model/system/request/sys_menu.go index 2b6fa1e..9dcc7e1 100644 --- a/server/model/system/request/sys_menu.go +++ b/server/model/system/request/sys_menu.go @@ -11,6 +11,12 @@ type AddMenuAuthorityInfo struct { AuthorityId uint `json:"authorityId"` // 角色ID } +// SetMenuAuthorities 通过菜单ID全量覆盖关联角色列表 +type SetMenuAuthorities struct { + MenuId uint `json:"menuId" form:"menuId"` // 菜单ID + AuthorityIds []uint `json:"authorityIds" form:"authorityIds"` // 角色ID列表 +} + func DefaultMenu() []system.SysBaseMenu { return []system.SysBaseMenu{{ GVA_MODEL: global.GVA_MODEL{ID: 1}, diff --git a/server/model/system/request/sys_skills.go b/server/model/system/request/sys_skills.go index 953fbd6..b94c0a1 100644 --- a/server/model/system/request/sys_skills.go +++ b/server/model/system/request/sys_skills.go @@ -11,6 +11,16 @@ type SkillDetailRequest struct { Skill string `json:"skill"` } +type SkillDeleteRequest struct { + Tool string `json:"tool"` + Skill string `json:"skill"` +} + +type SkillPackageRequest struct { + Tool string `json:"tool"` + Skill string `json:"skill"` +} + type SkillSaveRequest struct { Tool string `json:"tool"` Skill string `json:"skill"` @@ -62,3 +72,9 @@ type SkillGlobalConstraintSaveRequest struct { Content string `json:"content"` SyncTools []string `json:"syncTools"` } + +type DownloadOnlineSkillReq struct { + Tool string `json:"tool" binding:"required"` + ID uint `json:"id" binding:"required"` + Version string `json:"version" binding:"required"` +} diff --git a/server/model/system/request/sys_user.go b/server/model/system/request/sys_user.go index a7e6544..54b5d19 100644 --- a/server/model/system/request/sys_user.go +++ b/server/model/system/request/sys_user.go @@ -66,4 +66,12 @@ type GetUserList struct { NickName string `json:"nickName" form:"nickName"` Phone string `json:"phone" form:"phone"` Email string `json:"email" form:"email"` + OrderKey string `json:"orderKey" form:"orderKey"` // 排序 + Desc bool `json:"desc" form:"desc"` // 排序方式:升序false(默认)|降序true +} + +// SetRoleUsers 通过角色ID全量覆盖关联用户列表 +type SetRoleUsers struct { + AuthorityId uint `json:"authorityId" form:"authorityId"` // 角色ID + UserIds []uint `json:"userIds" form:"userIds"` // 用户ID列表 } diff --git a/server/plugin/announcement/api/enter.go b/server/plugin/announcement/api/enter.go new file mode 100644 index 0000000..d357733 --- /dev/null +++ b/server/plugin/announcement/api/enter.go @@ -0,0 +1,10 @@ +package api + +import "git.echol.cn/loser/st/server/plugin/announcement/service" + +var ( + Api = new(api) + serviceInfo = service.Service.Info +) + +type api struct{ Info info } diff --git a/server/plugin/announcement/api/info.go b/server/plugin/announcement/api/info.go new file mode 100644 index 0000000..d5b8d05 --- /dev/null +++ b/server/plugin/announcement/api/info.go @@ -0,0 +1,183 @@ +package api + +import ( + "git.echol.cn/loser/st/server/global" + "git.echol.cn/loser/st/server/model/common/response" + "git.echol.cn/loser/st/server/plugin/announcement/model" + "git.echol.cn/loser/st/server/plugin/announcement/model/request" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +var Info = new(info) + +type info struct{} + +// CreateInfo 创建公告 +// @Tags Info +// @Summary 创建公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "创建公告" +// @Success 200 {object} response.Response{msg=string} "创建成功" +// @Router /info/createInfo [post] +func (a *info) CreateInfo(c *gin.Context) { + var info model.Info + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = serviceInfo.CreateInfo(&info) + if err != nil { + global.GVA_LOG.Error("创建失败!", zap.Error(err)) + response.FailWithMessage("创建失败", c) + return + } + response.OkWithMessage("创建成功", c) +} + +// DeleteInfo 删除公告 +// @Tags Info +// @Summary 删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "删除公告" +// @Success 200 {object} response.Response{msg=string} "删除成功" +// @Router /info/deleteInfo [delete] +func (a *info) DeleteInfo(c *gin.Context) { + ID := c.Query("ID") + err := serviceInfo.DeleteInfo(ID) + if err != nil { + global.GVA_LOG.Error("删除失败!", zap.Error(err)) + response.FailWithMessage("删除失败", c) + return + } + response.OkWithMessage("删除成功", c) +} + +// DeleteInfoByIds 批量删除公告 +// @Tags Info +// @Summary 批量删除公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{msg=string} "批量删除成功" +// @Router /info/deleteInfoByIds [delete] +func (a *info) DeleteInfoByIds(c *gin.Context) { + IDs := c.QueryArray("IDs[]") + if err := serviceInfo.DeleteInfoByIds(IDs); err != nil { + global.GVA_LOG.Error("批量删除失败!", zap.Error(err)) + response.FailWithMessage("批量删除失败", c) + return + } + response.OkWithMessage("批量删除成功", c) +} + +// UpdateInfo 更新公告 +// @Tags Info +// @Summary 更新公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data body model.Info true "更新公告" +// @Success 200 {object} response.Response{msg=string} "更新成功" +// @Router /info/updateInfo [put] +func (a *info) UpdateInfo(c *gin.Context) { + var info model.Info + err := c.ShouldBindJSON(&info) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + err = serviceInfo.UpdateInfo(info) + if err != nil { + global.GVA_LOG.Error("更新失败!", zap.Error(err)) + response.FailWithMessage("更新失败", c) + return + } + response.OkWithMessage("更新成功", c) +} + +// FindInfo 用id查询公告 +// @Tags Info +// @Summary 用id查询公告 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query model.Info true "用id查询公告" +// @Success 200 {object} response.Response{data=model.Info,msg=string} "查询成功" +// @Router /info/findInfo [get] +func (a *info) FindInfo(c *gin.Context) { + ID := c.Query("ID") + reinfo, err := serviceInfo.GetInfo(ID) + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithData(reinfo, c) +} + +// GetInfoList 分页获取公告列表 +// @Tags Info +// @Summary 分页获取公告列表 +// @Security ApiKeyAuth +// @accept application/json +// @Produce application/json +// @Param data query request.InfoSearch true "分页获取公告列表" +// @Success 200 {object} response.Response{data=response.PageResult,msg=string} "获取成功" +// @Router /info/getInfoList [get] +func (a *info) GetInfoList(c *gin.Context) { + var pageInfo request.InfoSearch + err := c.ShouldBindQuery(&pageInfo) + if err != nil { + response.FailWithMessage(err.Error(), c) + return + } + list, total, err := serviceInfo.GetInfoInfoList(pageInfo) + if err != nil { + global.GVA_LOG.Error("获取失败!", zap.Error(err)) + response.FailWithMessage("获取失败", c) + return + } + response.OkWithDetailed(response.PageResult{ + List: list, + Total: total, + Page: pageInfo.Page, + PageSize: pageInfo.PageSize, + }, "获取成功", c) +} + +// GetInfoDataSource 获取Info的数据源 +// @Tags Info +// @Summary 获取Info的数据源 +// @accept application/json +// @Produce application/json +// @Success 200 {object} response.Response{data=object,msg=string} "查询成功" +// @Router /info/getInfoDataSource [get] +func (a *info) GetInfoDataSource(c *gin.Context) { + // 此接口为获取数据源定义的数据 + dataSource, err := serviceInfo.GetInfoDataSource() + if err != nil { + global.GVA_LOG.Error("查询失败!", zap.Error(err)) + response.FailWithMessage("查询失败", c) + return + } + response.OkWithData(dataSource, c) +} + +// GetInfoPublic 不需要鉴权的公告接口 +// @Tags Info +// @Summary 不需要鉴权的公告接口 +// @accept application/json +// @Produce application/json +// @Param data query request.InfoSearch true "分页获取公告列表" +// @Success 200 {object} response.Response{data=object,msg=string} "获取成功" +// @Router /info/getInfoPublic [get] +func (a *info) GetInfoPublic(c *gin.Context) { + // 此接口不需要鉴权 示例为返回了一个固定的消息接口,一般本接口用于C端服务,需要自己实现业务逻辑 + response.OkWithDetailed(gin.H{"info": "不需要鉴权的公告接口信息"}, "获取成功", c) +} diff --git a/server/plugin/announcement/config/config.go b/server/plugin/announcement/config/config.go new file mode 100644 index 0000000..809bc99 --- /dev/null +++ b/server/plugin/announcement/config/config.go @@ -0,0 +1,4 @@ +package config + +type Config struct { +} diff --git a/server/plugin/announcement/gen/gen.go b/server/plugin/announcement/gen/gen.go new file mode 100644 index 0000000..b781314 --- /dev/null +++ b/server/plugin/announcement/gen/gen.go @@ -0,0 +1,18 @@ +package main + +import ( + "path/filepath" //go:generate go mod tidy + + "gorm.io/gen" + //go:generate go mod download + //go:generate go run gen.go + "git.echol.cn/loser/st/server/plugin/announcement/model" +) + +func main() { + g := gen.NewGenerator(gen.Config{OutPath: filepath.Join("..", "..", "..", "announcement", "blender", "model", "dao"), Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface}) + g.ApplyBasic( + new(model.Info), + ) + g.Execute() +} diff --git a/server/plugin/announcement/initialize/api.go b/server/plugin/announcement/initialize/api.go new file mode 100644 index 0000000..3932c89 --- /dev/null +++ b/server/plugin/announcement/initialize/api.go @@ -0,0 +1,50 @@ +package initialize + +import ( + "context" + + model "git.echol.cn/loser/st/server/model/system" + "git.echol.cn/loser/st/server/plugin/plugin-tool/utils" +) + +func Api(ctx context.Context) { + entities := []model.SysApi{ + { + Path: "/info/createInfo", + Description: "新建公告", + ApiGroup: "公告", + Method: "POST", + }, + { + Path: "/info/deleteInfo", + Description: "删除公告", + ApiGroup: "公告", + Method: "DELETE", + }, + { + Path: "/info/deleteInfoByIds", + Description: "批量删除公告", + ApiGroup: "公告", + Method: "DELETE", + }, + { + Path: "/info/updateInfo", + Description: "更新公告", + ApiGroup: "公告", + Method: "PUT", + }, + { + Path: "/info/findInfo", + Description: "根据ID获取公告", + ApiGroup: "公告", + Method: "GET", + }, + { + Path: "/info/getInfoList", + Description: "获取公告列表", + ApiGroup: "公告", + Method: "GET", + }, + } + utils.RegisterApis(entities...) +} diff --git a/server/plugin/announcement/initialize/dictionary.go b/server/plugin/announcement/initialize/dictionary.go new file mode 100644 index 0000000..0b891dd --- /dev/null +++ b/server/plugin/announcement/initialize/dictionary.go @@ -0,0 +1,13 @@ +package initialize + +import ( + "context" + + model "git.echol.cn/loser/st/server/model/system" + "git.echol.cn/loser/st/server/plugin/plugin-tool/utils" +) + +func Dictionary(ctx context.Context) { + entities := []model.SysDictionary{} + utils.RegisterDictionaries(entities...) +} diff --git a/server/plugin/announcement/initialize/gorm.go b/server/plugin/announcement/initialize/gorm.go new file mode 100644 index 0000000..777ecae --- /dev/null +++ b/server/plugin/announcement/initialize/gorm.go @@ -0,0 +1,21 @@ +package initialize + +import ( + "context" + "fmt" + + "git.echol.cn/loser/st/server/global" + "git.echol.cn/loser/st/server/plugin/announcement/model" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Gorm(ctx context.Context) { + err := global.GVA_DB.WithContext(ctx).AutoMigrate( + new(model.Info), + ) + if err != nil { + err = errors.Wrap(err, "注册表失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/server/plugin/announcement/initialize/menu.go b/server/plugin/announcement/initialize/menu.go new file mode 100644 index 0000000..097bc8f --- /dev/null +++ b/server/plugin/announcement/initialize/menu.go @@ -0,0 +1,23 @@ +package initialize + +import ( + "context" + + model "git.echol.cn/loser/st/server/model/system" + "git.echol.cn/loser/st/server/plugin/plugin-tool/utils" +) + +func Menu(ctx context.Context) { + entities := []model.SysBaseMenu{ + { + ParentId: 9, + Path: "anInfo", + Name: "anInfo", + Hidden: false, + Component: "plugin/announcement/view/info.vue", + Sort: 5, + Meta: model.Meta{Title: "公告管理", Icon: "box"}, + }, + } + utils.RegisterMenus(entities...) +} diff --git a/server/plugin/announcement/initialize/router.go b/server/plugin/announcement/initialize/router.go new file mode 100644 index 0000000..84bb9fe --- /dev/null +++ b/server/plugin/announcement/initialize/router.go @@ -0,0 +1,15 @@ +package initialize + +import ( + "git.echol.cn/loser/st/server/global" + "git.echol.cn/loser/st/server/middleware" + "git.echol.cn/loser/st/server/plugin/announcement/router" + "github.com/gin-gonic/gin" +) + +func Router(engine *gin.Engine) { + public := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + private := engine.Group(global.GVA_CONFIG.System.RouterPrefix).Group("") + private.Use(middleware.JWTAuth()).Use(middleware.CasbinHandler()) + router.Router.Info.Init(public, private) +} diff --git a/server/plugin/announcement/initialize/viper.go b/server/plugin/announcement/initialize/viper.go new file mode 100644 index 0000000..218dbdc --- /dev/null +++ b/server/plugin/announcement/initialize/viper.go @@ -0,0 +1,18 @@ +package initialize + +import ( + "fmt" + + "git.echol.cn/loser/st/server/global" + "git.echol.cn/loser/st/server/plugin/announcement/plugin" + "github.com/pkg/errors" + "go.uber.org/zap" +) + +func Viper() { + err := global.GVA_VP.UnmarshalKey("announcement", &plugin.Config) + if err != nil { + err = errors.Wrap(err, "初始化配置文件失败!") + zap.L().Error(fmt.Sprintf("%+v", err)) + } +} diff --git a/server/plugin/announcement/model/info.go b/server/plugin/announcement/model/info.go new file mode 100644 index 0000000..31464c8 --- /dev/null +++ b/server/plugin/announcement/model/info.go @@ -0,0 +1,20 @@ +package model + +import ( + "git.echol.cn/loser/st/server/global" + "gorm.io/datatypes" +) + +// Info 公告 结构体 +type Info struct { + global.GVA_MODEL + Title string `json:"title" form:"title" gorm:"column:title;comment:公告标题;"` //标题 + Content string `json:"content" form:"content" gorm:"column:content;comment:公告内容;type:text;"` //内容 + UserID *int `json:"userID" form:"userID" gorm:"column:user_id;comment:发布者;"` //作者 + Attachments datatypes.JSON `json:"attachments" form:"attachments" gorm:"column:attachments;comment:相关附件;" swaggertype:"array,object"` //附件 +} + +// TableName 公告 Info自定义表名 gva_announcements_info +func (Info) TableName() string { + return "gva_announcements_info" +} diff --git a/server/plugin/announcement/model/request/info.go b/server/plugin/announcement/model/request/info.go new file mode 100644 index 0000000..c3b148e --- /dev/null +++ b/server/plugin/announcement/model/request/info.go @@ -0,0 +1,13 @@ +package request + +import ( + "time" + + "git.echol.cn/loser/st/server/model/common/request" +) + +type InfoSearch struct { + StartCreatedAt *time.Time `json:"startCreatedAt" form:"startCreatedAt"` + EndCreatedAt *time.Time `json:"endCreatedAt" form:"endCreatedAt"` + request.PageInfo +} diff --git a/server/plugin/announcement/plugin.go b/server/plugin/announcement/plugin.go new file mode 100644 index 0000000..e03f779 --- /dev/null +++ b/server/plugin/announcement/plugin.go @@ -0,0 +1,33 @@ +package announcement + +import ( + "context" + + "git.echol.cn/loser/st/server/plugin/announcement/initialize" + interfaces "git.echol.cn/loser/st/server/utils/plugin/v2" + "github.com/gin-gonic/gin" +) + +var _ interfaces.Plugin = (*plugin)(nil) + +var Plugin = new(plugin) + +type plugin struct{} + +func init() { + interfaces.Register(Plugin) +} + +func (p *plugin) Register(group *gin.Engine) { + ctx := context.Background() + // 如果需要配置文件,请到config.Config中填充配置结构,且到下方发放中填入其在config.yaml中的key + // initialize.Viper() + // 安装插件时候自动注册的api数据请到下方法.Api方法中实现 + initialize.Api(ctx) + // 安装插件时候自动注册的Menu数据请到下方法.Menu方法中实现 + initialize.Menu(ctx) + // 安装插件时候自动注册的Dictionary数据请到下方法.Dictionary方法中实现 + initialize.Dictionary(ctx) + initialize.Gorm(ctx) + initialize.Router(group) +} diff --git a/server/plugin/announcement/plugin/plugin.go b/server/plugin/announcement/plugin/plugin.go new file mode 100644 index 0000000..34b3e0d --- /dev/null +++ b/server/plugin/announcement/plugin/plugin.go @@ -0,0 +1,5 @@ +package plugin + +import "git.echol.cn/loser/st/server/plugin/announcement/config" + +var Config config.Config diff --git a/server/plugin/announcement/router/enter.go b/server/plugin/announcement/router/enter.go new file mode 100644 index 0000000..5035edd --- /dev/null +++ b/server/plugin/announcement/router/enter.go @@ -0,0 +1,10 @@ +package router + +import "git.echol.cn/loser/st/server/plugin/announcement/api" + +var ( + Router = new(router) + apiInfo = api.Api.Info +) + +type router struct{ Info info } diff --git a/server/plugin/announcement/router/info.go b/server/plugin/announcement/router/info.go new file mode 100644 index 0000000..2c9192a --- /dev/null +++ b/server/plugin/announcement/router/info.go @@ -0,0 +1,31 @@ +package router + +import ( + "git.echol.cn/loser/st/server/middleware" + "github.com/gin-gonic/gin" +) + +var Info = new(info) + +type info struct{} + +// Init 初始化 公告 路由信息 +func (r *info) Init(public *gin.RouterGroup, private *gin.RouterGroup) { + { + group := private.Group("info").Use(middleware.OperationRecord()) + group.POST("createInfo", apiInfo.CreateInfo) // 新建公告 + group.DELETE("deleteInfo", apiInfo.DeleteInfo) // 删除公告 + group.DELETE("deleteInfoByIds", apiInfo.DeleteInfoByIds) // 批量删除公告 + group.PUT("updateInfo", apiInfo.UpdateInfo) // 更新公告 + } + { + group := private.Group("info") + group.GET("findInfo", apiInfo.FindInfo) // 根据ID获取公告 + group.GET("getInfoList", apiInfo.GetInfoList) // 获取公告列表 + } + { + group := public.Group("info") + group.GET("getInfoDataSource", apiInfo.GetInfoDataSource) // 获取公告数据源 + group.GET("getInfoPublic", apiInfo.GetInfoPublic) // 获取公告列表 + } +} diff --git a/server/plugin/announcement/service/enter.go b/server/plugin/announcement/service/enter.go new file mode 100644 index 0000000..988fbcd --- /dev/null +++ b/server/plugin/announcement/service/enter.go @@ -0,0 +1,5 @@ +package service + +var Service = new(service) + +type service struct{ Info info } diff --git a/server/plugin/announcement/service/info.go b/server/plugin/announcement/service/info.go new file mode 100644 index 0000000..c86ef81 --- /dev/null +++ b/server/plugin/announcement/service/info.go @@ -0,0 +1,78 @@ +package service + +import ( + "git.echol.cn/loser/st/server/global" + "git.echol.cn/loser/st/server/plugin/announcement/model" + "git.echol.cn/loser/st/server/plugin/announcement/model/request" +) + +var Info = new(info) + +type info struct{} + +// CreateInfo 创建公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) CreateInfo(info *model.Info) (err error) { + err = global.GVA_DB.Create(info).Error + return err +} + +// DeleteInfo 删除公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) DeleteInfo(ID string) (err error) { + err = global.GVA_DB.Delete(&model.Info{}, "id = ?", ID).Error + return err +} + +// DeleteInfoByIds 批量删除公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) DeleteInfoByIds(IDs []string) (err error) { + err = global.GVA_DB.Delete(&[]model.Info{}, "id in ?", IDs).Error + return err +} + +// UpdateInfo 更新公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) UpdateInfo(info model.Info) (err error) { + err = global.GVA_DB.Model(&model.Info{}).Where("id = ?", info.ID).Updates(&info).Error + return err +} + +// GetInfo 根据ID获取公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) GetInfo(ID string) (info model.Info, err error) { + err = global.GVA_DB.Where("id = ?", ID).First(&info).Error + return +} + +// GetInfoInfoList 分页获取公告记录 +// Author [piexlmax](https://github.com/piexlmax) +func (s *info) GetInfoInfoList(info request.InfoSearch) (list []model.Info, total int64, err error) { + limit := info.PageSize + offset := info.PageSize * (info.Page - 1) + // 创建db + db := global.GVA_DB.Model(&model.Info{}) + var infos []model.Info + // 如果有条件搜索 下方会自动创建搜索语句 + if info.StartCreatedAt != nil && info.EndCreatedAt != nil { + db = db.Where("created_at BETWEEN ? AND ?", info.StartCreatedAt, info.EndCreatedAt) + } + err = db.Count(&total).Error + if err != nil { + return + } + + if limit != 0 { + db = db.Limit(limit).Offset(offset) + } + err = db.Find(&infos).Error + return infos, total, err +} +func (s *info) GetInfoDataSource() (res map[string][]map[string]any, err error) { + res = make(map[string][]map[string]any) + + userID := make([]map[string]any, 0) + global.GVA_DB.Table("sys_users").Select("nick_name as label,id as value").Scan(&userID) + res["userID"] = userID + return +} diff --git a/server/plugin/register.go b/server/plugin/register.go index b0736c3..dc84a58 100644 --- a/server/plugin/register.go +++ b/server/plugin/register.go @@ -1 +1,5 @@ package plugin + +import ( + _ "git.echol.cn/loser/st/server/plugin/announcement" +) diff --git a/server/router/system/sys_api.go b/server/router/system/sys_api.go index 908e590..2045bd3 100644 --- a/server/router/system/sys_api.go +++ b/server/router/system/sys_api.go @@ -22,10 +22,12 @@ func (s *ApiRouter) InitApiRouter(Router *gin.RouterGroup, RouterPub *gin.Router apiRouter.POST("getApiById", apiRouterApi.GetApiById) // 获取单条Api消息 apiRouter.POST("updateApi", apiRouterApi.UpdateApi) // 更新api apiRouter.DELETE("deleteApisByIds", apiRouterApi.DeleteApisByIds) // 删除选中api + apiRouter.POST("setApiRoles", apiRouterApi.SetApiRoles) // 全量覆盖API关联角色 } { - apiRouterWithoutRecord.POST("getAllApis", apiRouterApi.GetAllApis) // 获取所有api - apiRouterWithoutRecord.POST("getApiList", apiRouterApi.GetApiList) // 获取Api列表 + apiRouterWithoutRecord.POST("getAllApis", apiRouterApi.GetAllApis) // 获取所有api + apiRouterWithoutRecord.POST("getApiList", apiRouterApi.GetApiList) // 获取Api列表 + apiRouterWithoutRecord.GET("getApiRoles", apiRouterApi.GetApiRoles) // 获取API关联角色ID列表 } { apiPublicRouterWithoutRecord.GET("freshCasbin", apiRouterApi.FreshCasbin) // 刷新casbin权限 diff --git a/server/router/system/sys_authority.go b/server/router/system/sys_authority.go index 884be08..310d3be 100644 --- a/server/router/system/sys_authority.go +++ b/server/router/system/sys_authority.go @@ -16,8 +16,10 @@ func (s *AuthorityRouter) InitAuthorityRouter(Router *gin.RouterGroup) { authorityRouter.PUT("updateAuthority", authorityApi.UpdateAuthority) // 更新角色 authorityRouter.POST("copyAuthority", authorityApi.CopyAuthority) // 拷贝角色 authorityRouter.POST("setDataAuthority", authorityApi.SetDataAuthority) // 设置角色资源权限 + authorityRouter.POST("setRoleUsers", authorityApi.SetRoleUsers) // 全量覆盖角色关联用户 } { - authorityRouterWithoutRecord.POST("getAuthorityList", authorityApi.GetAuthorityList) // 获取角色列表 + authorityRouterWithoutRecord.POST("getAuthorityList", authorityApi.GetAuthorityList) // 获取角色列表 + authorityRouterWithoutRecord.GET("getUsersByAuthority", authorityApi.GetUsersByAuthority) // 获取角色关联用户ID列表 } } diff --git a/server/router/system/sys_menu.go b/server/router/system/sys_menu.go index 5b4779e..b79f4d3 100644 --- a/server/router/system/sys_menu.go +++ b/server/router/system/sys_menu.go @@ -15,6 +15,7 @@ func (s *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) (R gin.IRoutes) { menuRouter.POST("addMenuAuthority", authorityMenuApi.AddMenuAuthority) // 增加menu和角色关联关系 menuRouter.POST("deleteBaseMenu", authorityMenuApi.DeleteBaseMenu) // 删除菜单 menuRouter.POST("updateBaseMenu", authorityMenuApi.UpdateBaseMenu) // 更新菜单 + menuRouter.POST("setMenuRoles", authorityMenuApi.SetMenuRoles) // 全量覆盖菜单关联角色 } { menuRouterWithoutRecord.POST("getMenu", authorityMenuApi.GetMenu) // 获取菜单树 @@ -22,6 +23,7 @@ func (s *MenuRouter) InitMenuRouter(Router *gin.RouterGroup) (R gin.IRoutes) { menuRouterWithoutRecord.POST("getBaseMenuTree", authorityMenuApi.GetBaseMenuTree) // 获取用户动态路由 menuRouterWithoutRecord.POST("getMenuAuthority", authorityMenuApi.GetMenuAuthority) // 获取指定角色menu menuRouterWithoutRecord.POST("getBaseMenuById", authorityMenuApi.GetBaseMenuById) // 根据id获取菜单 + menuRouterWithoutRecord.GET("getMenuRoles", authorityMenuApi.GetMenuRoles) // 获取菜单关联角色ID列表 } return menuRouter } diff --git a/server/router/system/sys_skills.go b/server/router/system/sys_skills.go index 9529e66..d9f21bf 100644 --- a/server/router/system/sys_skills.go +++ b/server/router/system/sys_skills.go @@ -4,13 +4,15 @@ import "github.com/gin-gonic/gin" type SkillsRouter struct{} -func (s *SkillsRouter) InitSkillsRouter(Router *gin.RouterGroup) { +func (s *SkillsRouter) InitSkillsRouter(Router *gin.RouterGroup, pubRouter *gin.RouterGroup) { skillsRouter := Router.Group("skills") + skillsRouterPub := pubRouter.Group("skills") { skillsRouter.GET("getTools", skillsApi.GetTools) skillsRouter.POST("getSkillList", skillsApi.GetSkillList) skillsRouter.POST("getSkillDetail", skillsApi.GetSkillDetail) skillsRouter.POST("saveSkill", skillsApi.SaveSkill) + skillsRouter.POST("deleteSkill", skillsApi.DeleteSkill) skillsRouter.POST("createScript", skillsApi.CreateScript) skillsRouter.POST("getScript", skillsApi.GetScript) skillsRouter.POST("saveScript", skillsApi.SaveScript) @@ -25,5 +27,9 @@ func (s *SkillsRouter) InitSkillsRouter(Router *gin.RouterGroup) { skillsRouter.POST("saveTemplate", skillsApi.SaveTemplate) skillsRouter.POST("getGlobalConstraint", skillsApi.GetGlobalConstraint) skillsRouter.POST("saveGlobalConstraint", skillsApi.SaveGlobalConstraint) + skillsRouter.POST("packageSkill", skillsApi.PackageSkill) + } + { + skillsRouterPub.POST("downloadOnlineSkill", skillsApi.DownloadOnlineSkill) } } diff --git a/server/service/system/sys_authority.go b/server/service/system/sys_authority.go index 5da40c4..5f824f4 100644 --- a/server/service/system/sys_authority.go +++ b/server/service/system/sys_authority.go @@ -331,3 +331,82 @@ func (authorityService *AuthorityService) GetParentAuthorityID(authorityID uint) } return *authority.ParentId, nil } + +// GetUserIdsByAuthorityId 获取拥有指定角色的所有用户ID +func (authorityService *AuthorityService) GetUserIdsByAuthorityId(authorityId uint) (userIds []uint, err error) { + var records []system.SysUserAuthority + err = global.GVA_DB.Where("sys_authority_authority_id = ?", authorityId).Find(&records).Error + if err != nil { + return nil, err + } + for _, r := range records { + userIds = append(userIds, r.SysUserId) + } + return userIds, nil +} + +// SetRoleUsers 全量覆盖某角色关联的用户列表 +// 入参:角色ID + 目标用户ID列表,保存时将该角色的关联关系完全替换为传入列表 +func (authorityService *AuthorityService) SetRoleUsers(authorityId uint, userIds []uint) error { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + // 1. 查出当前拥有该角色的所有用户ID + var existingRecords []system.SysUserAuthority + if err := tx.Where("sys_authority_authority_id = ?", authorityId).Find(&existingRecords).Error; err != nil { + return err + } + + currentSet := make(map[uint]struct{}) + for _, r := range existingRecords { + currentSet[r.SysUserId] = struct{}{} + } + + targetSet := make(map[uint]struct{}) + for _, id := range userIds { + targetSet[id] = struct{}{} + } + + // 2. 删除该角色所有已有的用户关联 + if err := tx.Delete(&system.SysUserAuthority{}, "sys_authority_authority_id = ?", authorityId).Error; err != nil { + return err + } + + // 3. 对被移除的用户:若该角色是其主角色,则将主角色切换为其剩余的其他角色 + for userId := range currentSet { + if _, ok := targetSet[userId]; ok { + continue // 仍在目标列表中,不处理 + } + var user system.SysUser + if err := tx.First(&user, "id = ?", userId).Error; err != nil { + continue + } + if user.AuthorityId == authorityId { + // 从剩余关联(已删除当前角色后)中找另一个角色作为主角色 + var another system.SysUserAuthority + if err := tx.Where("sys_user_id = ?", userId).First(&another).Error; err != nil { + // 没有其他角色,主角色保持不变,不做处理 + continue + } + if err := tx.Model(&system.SysUser{}).Where("id = ?", userId). + Update("authority_id", another.SysAuthorityAuthorityId).Error; err != nil { + return err + } + } + } + + // 4. 批量插入新的关联记录 + if len(userIds) > 0 { + newRecords := make([]system.SysUserAuthority, 0, len(userIds)) + for _, userId := range userIds { + newRecords = append(newRecords, system.SysUserAuthority{ + SysUserId: userId, + SysAuthorityAuthorityId: authorityId, + }) + } + if err := tx.Create(&newRecords).Error; err != nil { + return err + } + } + + return nil + }) +} diff --git a/server/service/system/sys_casbin.go b/server/service/system/sys_casbin.go index 89148ae..b93a1ff 100644 --- a/server/service/system/sys_casbin.go +++ b/server/service/system/sys_casbin.go @@ -171,3 +171,45 @@ func (casbinService *CasbinService) FreshCasbin() (err error) { err = e.LoadPolicy() return err } + +// GetAuthoritiesByApi 获取拥有指定API权限的所有角色ID +func (casbinService *CasbinService) GetAuthoritiesByApi(path, method string) (authorityIds []uint, err error) { + var rules []gormadapter.CasbinRule + err = global.GVA_DB.Where("ptype = 'p' AND v1 = ? AND v2 = ?", path, method).Find(&rules).Error + if err != nil { + return nil, err + } + for _, r := range rules { + id, e := strconv.Atoi(r.V0) + if e == nil { + authorityIds = append(authorityIds, uint(id)) + } + } + return authorityIds, nil +} + +// SetApiAuthorities 全量覆盖某API关联的角色列表 +func (casbinService *CasbinService) SetApiAuthorities(path, method string, authorityIds []uint) error { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + // 1. 删除该API所有已有的角色关联 + if err := tx.Where("ptype = 'p' AND v1 = ? AND v2 = ?", path, method).Delete(&gormadapter.CasbinRule{}).Error; err != nil { + return err + } + // 2. 批量插入新的关联记录 + if len(authorityIds) > 0 { + newRules := make([]gormadapter.CasbinRule, 0, len(authorityIds)) + for _, authorityId := range authorityIds { + newRules = append(newRules, gormadapter.CasbinRule{ + Ptype: "p", + V0: strconv.Itoa(int(authorityId)), + V1: path, + V2: method, + }) + } + if err := tx.Create(&newRules).Error; err != nil { + return err + } + } + return nil + }) +} diff --git a/server/service/system/sys_menu.go b/server/service/system/sys_menu.go index 7c84af5..15ca917 100644 --- a/server/service/system/sys_menu.go +++ b/server/service/system/sys_menu.go @@ -315,6 +315,65 @@ func (menuService *MenuService) GetMenuAuthority(info *request.GetAuthorityId) ( return menus, err } +// GetAuthoritiesByMenuId 获取拥有指定菜单的所有角色ID +func (menuService *MenuService) GetAuthoritiesByMenuId(menuId uint) (authorityIds []uint, err error) { + var records []system.SysAuthorityMenu + err = global.GVA_DB.Where("sys_base_menu_id = ?", menuId).Find(&records).Error + if err != nil { + return nil, err + } + for _, r := range records { + id, e := strconv.Atoi(r.AuthorityId) + if e == nil { + authorityIds = append(authorityIds, uint(id)) + } + } + return authorityIds, nil +} + +// GetDefaultRouterAuthorityIds 获取将指定菜单设为首页的角色ID列表 +func (menuService *MenuService) GetDefaultRouterAuthorityIds(menuId uint) (authorityIds []uint, err error) { + var menu system.SysBaseMenu + err = global.GVA_DB.First(&menu, menuId).Error + if err != nil { + return nil, err + } + var authorities []system.SysAuthority + err = global.GVA_DB.Where("default_router = ?", menu.Name).Find(&authorities).Error + if err != nil { + return nil, err + } + for _, auth := range authorities { + authorityIds = append(authorityIds, auth.AuthorityId) + } + return authorityIds, nil +} + +// SetMenuAuthorities 全量覆盖某菜单关联的角色列表 +func (menuService *MenuService) SetMenuAuthorities(menuId uint, authorityIds []uint) error { + return global.GVA_DB.Transaction(func(tx *gorm.DB) error { + // 1. 删除该菜单所有已有的角色关联 + if err := tx.Where("sys_base_menu_id = ?", menuId).Delete(&system.SysAuthorityMenu{}).Error; err != nil { + return err + } + // 2. 批量插入新的关联记录 + if len(authorityIds) > 0 { + menuIdStr := strconv.Itoa(int(menuId)) + newRecords := make([]system.SysAuthorityMenu, 0, len(authorityIds)) + for _, authorityId := range authorityIds { + newRecords = append(newRecords, system.SysAuthorityMenu{ + MenuId: menuIdStr, + AuthorityId: strconv.Itoa(int(authorityId)), + }) + } + if err := tx.Create(&newRecords).Error; err != nil { + return err + } + } + return nil + }) +} + // UserAuthorityDefaultRouter 用户角色默认路由检查 // // Author [SliverHorn](https://github.com/SliverHorn) diff --git a/server/service/system/sys_skills.go b/server/service/system/sys_skills.go index 673f957..5be747e 100644 --- a/server/service/system/sys_skills.go +++ b/server/service/system/sys_skills.go @@ -1,10 +1,15 @@ package system import ( + "archive/zip" + "bytes" "context" + "encoding/json" "errors" "fmt" + "io" "io/fs" + "net/http" "os" "path/filepath" "sort" @@ -158,6 +163,107 @@ func (s *SkillsService) Save(_ context.Context, req request.SkillSaveRequest) er return nil } +func (s *SkillsService) Delete(_ context.Context, req request.SkillDeleteRequest) error { + if strings.TrimSpace(req.Tool) == "" { + return errors.New("工具类型不能为空") + } + if !isSafeName(req.Skill) { + return errors.New("技能名称不合法") + } + skillDir, err := s.skillDir(req.Tool, req.Skill) + if err != nil { + return err + } + info, err := os.Stat(skillDir) + if err != nil { + if os.IsNotExist(err) { + return errors.New("技能不存在") + } + return err + } + if !info.IsDir() { + return errors.New("技能目录异常") + } + return os.RemoveAll(skillDir) +} + +func (s *SkillsService) Package(_ context.Context, req request.SkillPackageRequest) (string, []byte, error) { + if strings.TrimSpace(req.Tool) == "" { + return "", nil, errors.New("工具类型不能为空") + } + if !isSafeName(req.Skill) { + return "", nil, errors.New("技能名称不合法") + } + + skillDir, err := s.skillDir(req.Tool, req.Skill) + if err != nil { + return "", nil, err + } + info, err := os.Stat(skillDir) + if err != nil { + if os.IsNotExist(err) { + return "", nil, errors.New("技能不存在") + } + return "", nil, err + } + if !info.IsDir() { + return "", nil, errors.New("技能目录异常") + } + + buf := bytes.NewBuffer(nil) + zw := zip.NewWriter(buf) + + walkErr := filepath.WalkDir(skillDir, func(path string, d fs.DirEntry, walkErr error) error { + if walkErr != nil { + return walkErr + } + rel, err := filepath.Rel(skillDir, path) + if err != nil { + return err + } + if rel == "." { + return nil + } + zipName := filepath.ToSlash(rel) + if d.IsDir() { + _, err = zw.Create(strings.TrimSuffix(zipName, "/") + "/") + return err + } + + fileInfo, err := d.Info() + if err != nil { + return err + } + header, err := zip.FileInfoHeader(fileInfo) + if err != nil { + return err + } + header.Name = zipName + header.Method = zip.Deflate + + writer, err := zw.CreateHeader(header) + if err != nil { + return err + } + content, err := os.ReadFile(path) + if err != nil { + return err + } + _, err = writer.Write(content) + return err + }) + if walkErr != nil { + _ = zw.Close() + return "", nil, walkErr + } + + if err = zw.Close(); err != nil { + return "", nil, err + } + + return req.Skill + ".zip", buf.Bytes(), nil +} + func (s *SkillsService) CreateScript(_ context.Context, req request.SkillScriptCreateRequest) (string, string, error) { if !isSafeName(req.Skill) { return "", "", errors.New("技能名称不合法") @@ -279,6 +385,136 @@ func (s *SkillsService) SaveGlobalConstraint(_ context.Context, req request.Skil return nil } +func (s *SkillsService) DownloadOnlineSkill(_ context.Context, req request.DownloadOnlineSkillReq) error { + skillsDir, err := s.toolSkillsDir(req.Tool) + if err != nil { + return err + } + + body, err := json.Marshal(map[string]interface{}{ + "plugin_id": req.ID, + "version": req.Version, + }) + if err != nil { + return fmt.Errorf("构建下载请求失败: %w", err) + } + + downloadReq, err := http.NewRequest(http.MethodPost, "https://plugin.gin-vue-admin.com/api/shopPlugin/downloadSkill", bytes.NewReader(body)) + if err != nil { + return fmt.Errorf("构建下载请求失败: %w", err) + } + downloadReq.Header.Set("Content-Type", "application/json") + + downloadResp, err := http.DefaultClient.Do(downloadReq) + if err != nil { + return fmt.Errorf("下载技能失败: %w", err) + } + defer downloadResp.Body.Close() + + if downloadResp.StatusCode != http.StatusOK { + return fmt.Errorf("下载技能失败, HTTP状态码: %d", downloadResp.StatusCode) + } + + metaBody, err := io.ReadAll(downloadResp.Body) + if err != nil { + return fmt.Errorf("读取下载结果失败: %w", err) + } + + var meta struct { + Data struct { + URL string `json:"url"` + } `json:"data"` + } + if err = json.Unmarshal(metaBody, &meta); err != nil { + return fmt.Errorf("解析下载结果失败: %w", err) + } + + realDownloadURL := strings.TrimSpace(meta.Data.URL) + if realDownloadURL == "" { + return errors.New("下载结果缺少 url") + } + + zipResp, err := http.Get(realDownloadURL) + if err != nil { + return fmt.Errorf("下载压缩包失败: %w", err) + } + defer zipResp.Body.Close() + + if zipResp.StatusCode != http.StatusOK { + return fmt.Errorf("下载压缩包失败, HTTP状态码: %d", zipResp.StatusCode) + } + + tmpFile, err := os.CreateTemp("", "gva-skill-*.zip") + if err != nil { + return fmt.Errorf("创建临时文件失败: %w", err) + } + tmpPath := tmpFile.Name() + defer os.Remove(tmpPath) + + if _, err = io.Copy(tmpFile, zipResp.Body); err != nil { + tmpFile.Close() + return fmt.Errorf("保存技能包失败: %w", err) + } + tmpFile.Close() + + if err = extractZipToDir(tmpPath, skillsDir); err != nil { + return fmt.Errorf("解压技能包失败: %w", err) + } + + return nil +} + +func extractZipToDir(zipPath, destDir string) error { + r, err := zip.OpenReader(zipPath) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + name := filepath.FromSlash(f.Name) + if strings.Contains(name, "..") { + continue + } + + target := filepath.Join(destDir, name) + if !strings.HasPrefix(filepath.Clean(target), filepath.Clean(destDir)) { + continue + } + + if f.FileInfo().IsDir() { + if err := os.MkdirAll(target, os.ModePerm); err != nil { + return err + } + continue + } + + if err := os.MkdirAll(filepath.Dir(target), os.ModePerm); err != nil { + return err + } + + rc, err := f.Open() + if err != nil { + return err + } + + out, err := os.OpenFile(target, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + rc.Close() + return err + } + + _, err = io.Copy(out, rc) + rc.Close() + out.Close() + if err != nil { + return err + } + } + + return nil +} + func (s *SkillsService) toolSkillsDir(tool string) (string, error) { toolDir, ok := skillToolDirs[tool] if !ok { diff --git a/server/service/system/sys_user.go b/server/service/system/sys_user.go index bd79a32..1ef7198 100644 --- a/server/service/system/sys_user.go +++ b/server/service/system/sys_user.go @@ -109,7 +109,25 @@ func (userService *UserService) GetUserInfoList(info systemReq.GetUserList) (lis if err != nil { return } - err = db.Limit(limit).Offset(offset).Preload("Authorities").Preload("Authority").Find(&userList).Error + + orderStr := "id desc" + if info.OrderKey != "" { + allowedOrders := map[string]bool{ + "id": true, + "username": true, + "nick_name": true, + "phone": true, + "email": true, + } + if allowedOrders[info.OrderKey] { + orderStr = info.OrderKey + if info.Desc { + orderStr = info.OrderKey + " desc" + } + } + } + + err = db.Limit(limit).Offset(offset).Order(orderStr).Preload("Authorities").Preload("Authority").Find(&userList).Error return userList, total, err } diff --git a/web-app/vite.config.ts b/web-app/vite.config.ts index ff5096b..22651fb 100644 --- a/web-app/vite.config.ts +++ b/web-app/vite.config.ts @@ -14,6 +14,8 @@ export default defineConfig({ } }, server: { + host: '0.0.0.0', + port: 5174, // 开发服务器禁用缓存 headers: { 'Cache-Control': 'no-store',