From 64f917b9c51123ebcb90b7afb84c7bd261f094b7 Mon Sep 17 00:00:00 2001 From: ila Date: Fri, 21 Aug 2026 23:08:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20MVP-1=20=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AB=AF=20API=20=E4=B8=8E=E5=87=AD=E6=8D=AE=E5=AE=A1?= =?UTF-8?q?=E8=AE=A1=20(#23)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin/.gitignore | 1 + admin/LICENSE.md | 21 + admin/app/admin/apis/captcha.go | 37 + admin/app/admin/apis/go_admin.go | 39 + admin/app/admin/apis/sys_api.go | 148 ++ admin/app/admin/apis/sys_config.go | 313 ++++ admin/app/admin/apis/sys_dept.go | 238 +++ admin/app/admin/apis/sys_dict_data.go | 220 +++ admin/app/admin/apis/sys_dict_type.go | 210 +++ admin/app/admin/apis/sys_login_log.go | 110 ++ admin/app/admin/apis/sys_menu.go | 249 ++++ admin/app/admin/apis/sys_opera_log.go | 118 ++ admin/app/admin/apis/sys_post.go | 184 +++ admin/app/admin/apis/sys_role.go | 284 ++++ admin/app/admin/apis/sys_user.go | 459 ++++++ admin/app/admin/models/casbin_rule.go | 16 + admin/app/admin/models/datascope.go | 81 + admin/app/admin/models/initdb.go | 55 + admin/app/admin/models/sys_api.go | 91 ++ admin/app/admin/models/sys_config.go | 30 + admin/app/admin/models/sys_dept.go | 33 + admin/app/admin/models/sys_dict_data.go | 34 + admin/app/admin/models/sys_dict_type.go | 28 + admin/app/admin/models/sys_login_log.go | 72 + admin/app/admin/models/sys_menu.go | 50 + admin/app/admin/models/sys_opera_log.go | 88 ++ admin/app/admin/models/sys_post.go | 30 + admin/app/admin/models/sys_role.go | 35 + admin/app/admin/models/sys_user.go | 77 + admin/app/admin/router/init_router.go | 39 + admin/app/admin/router/router.go | 41 + admin/app/admin/router/sys_api.go | 24 + admin/app/admin/router/sys_config.go | 43 + admin/app/admin/router/sys_dept.go | 32 + admin/app/admin/router/sys_dict.go | 37 + admin/app/admin/router/sys_login_log.go | 24 + admin/app/admin/router/sys_menu.go | 33 + admin/app/admin/router/sys_opera_log.go | 23 + admin/app/admin/router/sys_post.go | 25 + admin/app/admin/router/sys_role.go | 31 + admin/app/admin/router/sys_router.go | 44 + admin/app/admin/router/sys_user.go | 39 + admin/app/admin/service/dto/sys_api.go | 95 ++ admin/app/admin/service/dto/sys_config.go | 112 ++ admin/app/admin/service/dto/sys_dept.go | 110 ++ admin/app/admin/service/dto/sys_dict_data.go | 108 ++ admin/app/admin/service/dto/sys_dict_type.go | 89 ++ admin/app/admin/service/dto/sys_login_log.go | 57 + admin/app/admin/service/dto/sys_menu.go | 159 ++ admin/app/admin/service/dto/sys_opera_log.go | 102 ++ admin/app/admin/service/dto/sys_post.go | 111 ++ admin/app/admin/service/dto/sys_role.go | 164 +++ admin/app/admin/service/dto/sys_user.go | 189 +++ admin/app/admin/service/sys_api.go | 121 ++ admin/app/admin/service/sys_config.go | 183 +++ admin/app/admin/service/sys_dept.go | 294 ++++ admin/app/admin/service/sys_dict_data.go | 120 ++ admin/app/admin/service/sys_dict_type.go | 124 ++ admin/app/admin/service/sys_login_log.go | 69 + admin/app/admin/service/sys_menu.go | 422 ++++++ admin/app/admin/service/sys_opera_log.go | 83 ++ admin/app/admin/service/sys_post.go | 104 ++ admin/app/admin/service/sys_role.go | 352 +++++ admin/app/admin/service/sys_role_menu.go | 110 ++ admin/app/admin/service/sys_user.go | 266 ++++ admin/app/chorus/errors.go | 25 + admin/app/chorus/probe.go | 49 + admin/app/chorus/router.go | 285 ++++ admin/app/chorus/router_test.go | 53 + admin/app/chorus/service.go | 1310 +++++++++++++++++ .../chorus/service_mysql_integration_test.go | 329 +++++ admin/app/chorus/types.go | 166 +++ admin/cmd/server.go | 178 +++ admin/cmd/server_test.go | 42 + admin/common/actions/create.go | 49 + admin/common/actions/delete.go | 61 + admin/common/actions/index.go | 58 + admin/common/actions/permission.go | 96 ++ admin/common/actions/type.go | 5 + admin/common/actions/update.go | 59 + admin/common/actions/view.go | 67 + admin/common/apis/api.go | 135 ++ admin/common/database/initialize.go | 66 + admin/common/database/open.go | 16 + admin/common/database/open_sqlite3.go | 19 + admin/common/dto/auto_form.go | 74 + admin/common/dto/generate.go | 106 ++ admin/common/dto/order.go | 12 + admin/common/dto/pagination.go | 20 + admin/common/dto/search.go | 84 ++ admin/common/dto/type.go | 21 + admin/common/file_store/initialize.go | 45 + admin/common/file_store/interface.go | 27 + admin/common/file_store/kodo.go | 111 ++ admin/common/file_store/obs.go | 52 + admin/common/file_store/oss.go | 48 + admin/common/global/adm.go | 11 + admin/common/global/casbin.go | 18 + admin/common/global/logo.go | 4 + admin/common/global/topic.go | 7 + admin/common/ip.go | 28 + admin/common/middleware/auth.go | 38 + admin/common/middleware/auth_test.go | 13 + admin/common/middleware/customerror.go | 61 + admin/common/middleware/db.go | 11 + admin/common/middleware/demo.go | 29 + admin/common/middleware/handler/auth.go | 182 +++ .../common/middleware/handler/httpshandler.go | 22 + admin/common/middleware/handler/login.go | 33 + admin/common/middleware/handler/ping.go | 11 + admin/common/middleware/handler/role.go | 24 + admin/common/middleware/handler/user.go | 40 + admin/common/middleware/header.go | 48 + admin/common/middleware/init.go | 35 + admin/common/middleware/logger.go | 136 ++ admin/common/middleware/permission.go | 61 + admin/common/middleware/request_id.go | 36 + admin/common/middleware/security_test.go | 14 + admin/common/middleware/sentinel.go | 30 + admin/common/middleware/settings.go | 43 + admin/common/middleware/trace.go | 27 + admin/common/models/by.go | 32 + admin/common/models/menu.go | 11 + admin/common/models/migrate.go | 12 + admin/common/models/response.go | 30 + admin/common/models/type.go | 11 + admin/common/models/user.go | 42 + admin/common/response/binding.go | 105 ++ admin/common/service/service.go | 25 + admin/common/storage/initialize.go | 50 + admin/config/extend.go | 16 + admin/config/settings.example.yml | 23 + admin/go.mod | 136 ++ admin/go.sum | 817 ++++++++++ admin/main.go | 27 + 135 files changed, 13297 insertions(+) create mode 100644 admin/.gitignore create mode 100644 admin/LICENSE.md create mode 100644 admin/app/admin/apis/captcha.go create mode 100644 admin/app/admin/apis/go_admin.go create mode 100644 admin/app/admin/apis/sys_api.go create mode 100644 admin/app/admin/apis/sys_config.go create mode 100644 admin/app/admin/apis/sys_dept.go create mode 100644 admin/app/admin/apis/sys_dict_data.go create mode 100644 admin/app/admin/apis/sys_dict_type.go create mode 100644 admin/app/admin/apis/sys_login_log.go create mode 100644 admin/app/admin/apis/sys_menu.go create mode 100644 admin/app/admin/apis/sys_opera_log.go create mode 100644 admin/app/admin/apis/sys_post.go create mode 100644 admin/app/admin/apis/sys_role.go create mode 100644 admin/app/admin/apis/sys_user.go create mode 100644 admin/app/admin/models/casbin_rule.go create mode 100644 admin/app/admin/models/datascope.go create mode 100644 admin/app/admin/models/initdb.go create mode 100644 admin/app/admin/models/sys_api.go create mode 100644 admin/app/admin/models/sys_config.go create mode 100644 admin/app/admin/models/sys_dept.go create mode 100644 admin/app/admin/models/sys_dict_data.go create mode 100644 admin/app/admin/models/sys_dict_type.go create mode 100644 admin/app/admin/models/sys_login_log.go create mode 100644 admin/app/admin/models/sys_menu.go create mode 100644 admin/app/admin/models/sys_opera_log.go create mode 100644 admin/app/admin/models/sys_post.go create mode 100644 admin/app/admin/models/sys_role.go create mode 100644 admin/app/admin/models/sys_user.go create mode 100644 admin/app/admin/router/init_router.go create mode 100644 admin/app/admin/router/router.go create mode 100644 admin/app/admin/router/sys_api.go create mode 100644 admin/app/admin/router/sys_config.go create mode 100644 admin/app/admin/router/sys_dept.go create mode 100644 admin/app/admin/router/sys_dict.go create mode 100644 admin/app/admin/router/sys_login_log.go create mode 100644 admin/app/admin/router/sys_menu.go create mode 100644 admin/app/admin/router/sys_opera_log.go create mode 100644 admin/app/admin/router/sys_post.go create mode 100644 admin/app/admin/router/sys_role.go create mode 100644 admin/app/admin/router/sys_router.go create mode 100644 admin/app/admin/router/sys_user.go create mode 100644 admin/app/admin/service/dto/sys_api.go create mode 100644 admin/app/admin/service/dto/sys_config.go create mode 100644 admin/app/admin/service/dto/sys_dept.go create mode 100644 admin/app/admin/service/dto/sys_dict_data.go create mode 100644 admin/app/admin/service/dto/sys_dict_type.go create mode 100644 admin/app/admin/service/dto/sys_login_log.go create mode 100644 admin/app/admin/service/dto/sys_menu.go create mode 100644 admin/app/admin/service/dto/sys_opera_log.go create mode 100644 admin/app/admin/service/dto/sys_post.go create mode 100644 admin/app/admin/service/dto/sys_role.go create mode 100644 admin/app/admin/service/dto/sys_user.go create mode 100644 admin/app/admin/service/sys_api.go create mode 100644 admin/app/admin/service/sys_config.go create mode 100644 admin/app/admin/service/sys_dept.go create mode 100644 admin/app/admin/service/sys_dict_data.go create mode 100644 admin/app/admin/service/sys_dict_type.go create mode 100644 admin/app/admin/service/sys_login_log.go create mode 100644 admin/app/admin/service/sys_menu.go create mode 100644 admin/app/admin/service/sys_opera_log.go create mode 100644 admin/app/admin/service/sys_post.go create mode 100644 admin/app/admin/service/sys_role.go create mode 100644 admin/app/admin/service/sys_role_menu.go create mode 100644 admin/app/admin/service/sys_user.go create mode 100644 admin/app/chorus/errors.go create mode 100644 admin/app/chorus/probe.go create mode 100644 admin/app/chorus/router.go create mode 100644 admin/app/chorus/router_test.go create mode 100644 admin/app/chorus/service.go create mode 100644 admin/app/chorus/service_mysql_integration_test.go create mode 100644 admin/app/chorus/types.go create mode 100644 admin/cmd/server.go create mode 100644 admin/cmd/server_test.go create mode 100644 admin/common/actions/create.go create mode 100644 admin/common/actions/delete.go create mode 100644 admin/common/actions/index.go create mode 100644 admin/common/actions/permission.go create mode 100644 admin/common/actions/type.go create mode 100644 admin/common/actions/update.go create mode 100644 admin/common/actions/view.go create mode 100644 admin/common/apis/api.go create mode 100644 admin/common/database/initialize.go create mode 100644 admin/common/database/open.go create mode 100644 admin/common/database/open_sqlite3.go create mode 100644 admin/common/dto/auto_form.go create mode 100644 admin/common/dto/generate.go create mode 100644 admin/common/dto/order.go create mode 100644 admin/common/dto/pagination.go create mode 100644 admin/common/dto/search.go create mode 100644 admin/common/dto/type.go create mode 100644 admin/common/file_store/initialize.go create mode 100644 admin/common/file_store/interface.go create mode 100644 admin/common/file_store/kodo.go create mode 100644 admin/common/file_store/obs.go create mode 100644 admin/common/file_store/oss.go create mode 100644 admin/common/global/adm.go create mode 100644 admin/common/global/casbin.go create mode 100644 admin/common/global/logo.go create mode 100644 admin/common/global/topic.go create mode 100644 admin/common/ip.go create mode 100644 admin/common/middleware/auth.go create mode 100644 admin/common/middleware/auth_test.go create mode 100644 admin/common/middleware/customerror.go create mode 100644 admin/common/middleware/db.go create mode 100644 admin/common/middleware/demo.go create mode 100644 admin/common/middleware/handler/auth.go create mode 100644 admin/common/middleware/handler/httpshandler.go create mode 100644 admin/common/middleware/handler/login.go create mode 100644 admin/common/middleware/handler/ping.go create mode 100644 admin/common/middleware/handler/role.go create mode 100644 admin/common/middleware/handler/user.go create mode 100644 admin/common/middleware/header.go create mode 100644 admin/common/middleware/init.go create mode 100644 admin/common/middleware/logger.go create mode 100644 admin/common/middleware/permission.go create mode 100644 admin/common/middleware/request_id.go create mode 100644 admin/common/middleware/security_test.go create mode 100644 admin/common/middleware/sentinel.go create mode 100644 admin/common/middleware/settings.go create mode 100644 admin/common/middleware/trace.go create mode 100644 admin/common/models/by.go create mode 100644 admin/common/models/menu.go create mode 100644 admin/common/models/migrate.go create mode 100644 admin/common/models/response.go create mode 100644 admin/common/models/type.go create mode 100644 admin/common/models/user.go create mode 100644 admin/common/response/binding.go create mode 100644 admin/common/service/service.go create mode 100644 admin/common/storage/initialize.go create mode 100644 admin/config/extend.go create mode 100644 admin/config/settings.example.yml create mode 100644 admin/go.mod create mode 100644 admin/go.sum create mode 100644 admin/main.go diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 0000000..5d48e98 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1 @@ +/admin.exe diff --git a/admin/LICENSE.md b/admin/LICENSE.md new file mode 100644 index 0000000..35ac77e --- /dev/null +++ b/admin/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 go-admin-team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/admin/app/admin/apis/captcha.go b/admin/app/admin/apis/captcha.go new file mode 100644 index 0000000..e884e24 --- /dev/null +++ b/admin/app/admin/apis/captcha.go @@ -0,0 +1,37 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" +) + +type System struct { + api.Api +} + +// GenerateCaptchaHandler 获取验证码 +// @Summary 获取验证码 +// @Description 获取验证码 +// @Tags 登陆 +// @Success 200 {object} response.Response{data=string,id=string,msg=string} "{"code": 200, "data": [...]}" +// @Router /api/v1/captcha [get] +func (e System) GenerateCaptchaHandler(c *gin.Context) { + if err := e.MakeContext(c).Errors; err != nil { + e.Error(500, err, "服务初始化失败!") + return + } + id, b64s, answer, err := captcha.DriverDigitFunc() + if err != nil { + e.Logger.Errorf("DriverDigitFunc error, %s", err.Error()) + e.Error(500, err, "验证码获取失败") + return + } + e.Logger.Infof("DriverDigitFunc answer: %s", answer) + e.Custom(gin.H{ + "code": 200, + "data": b64s, + "id": id, + "msg": "success", + }) +} diff --git a/admin/app/admin/apis/go_admin.go b/admin/app/admin/apis/go_admin.go new file mode 100644 index 0000000..7e62046 --- /dev/null +++ b/admin/app/admin/apis/go_admin.go @@ -0,0 +1,39 @@ +package apis + +import ( + "github.com/gin-gonic/gin" +) + +const INDEX = ` + + + + +GO-ADMIN欢迎您 + + + + + + + + +` + +func GoAdmin(c *gin.Context) { + c.Header("Content-Type", "text/html; charset=utf-8") + c.String(200, INDEX) +} diff --git a/admin/app/admin/apis/sys_api.go b/admin/app/admin/apis/sys_api.go new file mode 100644 index 0000000..cf4b940 --- /dev/null +++ b/admin/app/admin/apis/sys_api.go @@ -0,0 +1,148 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + "git.ilapage.cn/OPC/chorus/admin/common/actions" +) + +type SysApi struct { + api.Api +} + +// GetPage 获取接口管理列表 +// @Summary 获取接口管理列表 +// @Description 获取接口管理列表 +// @Tags 接口管理 +// @Param name query string false "名称" +// @Param title query string false "标题" +// @Param path query string false "地址" +// @Param action query string false "类型" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-api [get] +// @Security Bearer +func (e SysApi) GetPage(c *gin.Context) { + s := service.SysApi{} + req := dto.SysApiGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + //数据权限检查 + p := actions.GetPermissionFromContext(c) + list := make([]models.SysApi, 0) + var count int64 + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取接口管理 +// @Summary 获取接口管理 +// @Description 获取接口管理 +// @Tags 接口管理 +// @Param id path string false "id" +// @Success 200 {object} response.Response{data=models.SysApi} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-api/{id} [get] +// @Security Bearer +func (e SysApi) Get(c *gin.Context) { + req := dto.SysApiGetReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + var object models.SysApi + err = s.Get(&req, p, &object).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Update 修改接口管理 +// @Summary 修改接口管理 +// @Description 修改接口管理 +// @Tags 接口管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysApiUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/sys-api/{id} [put] +// @Security Bearer +func (e SysApi) Update(c *gin.Context) { + req := dto.SysApiUpdateReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + req.SetUpdateBy(user.GetUserId(c)) + p := actions.GetPermissionFromContext(c) + err = s.Update(&req, p) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// DeleteSysApi 删除接口管理 +// @Summary 删除接口管理 +// @Description 删除接口管理 +// @Tags 接口管理 +// @Param data body dto.SysApiDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/sys-api [delete] +// @Security Bearer +func (e SysApi) DeleteSysApi(c *gin.Context) { + req := dto.SysApiDeleteReq{} + s := service.SysApi{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + p := actions.GetPermissionFromContext(c) + err = s.Remove(&req, p) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/admin/app/admin/apis/sys_config.go b/admin/app/admin/apis/sys_config.go new file mode 100644 index 0000000..70f979a --- /dev/null +++ b/admin/app/admin/apis/sys_config.go @@ -0,0 +1,313 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysConfig struct { + api.Api +} + +// GetPage 获取配置管理列表 +// @Summary 获取配置管理列表 +// @Description 获取配置管理列表 +// @Tags 配置管理 +// @Param configName query string false "名称" +// @Param configKey query string false "key" +// @Param configType query string false "类型" +// @Param isFrontend query int false "是否前端" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response{data=response.Page{list=[]models.SysApi}} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config [get] +// @Security Bearer +func (e SysConfig) GetPage(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + + list := make([]models.SysConfig, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 获取配置管理 +// @Summary 获取配置管理 +// @Description 获取配置管理 +// @Tags 配置管理 +// @Param id path string false "id" +// @Success 200 {object} response.Response{data=models.SysConfig} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config/{id} [get] +// @Security Bearer +func (e SysConfig) Get(c *gin.Context) { + req := dto.SysConfigGetReq{} + s := service.SysConfig{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysConfig + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, err.Error()) + return + } + + e.OK(object, "查询成功") +} + +// Insert 创建配置管理 +// @Summary 创建配置管理 +// @Description 创建配置管理 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysConfigControl true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "创建成功"}" +// @Router /api/v1/sys-config [post] +// @Security Bearer +func (e SysConfig) Insert(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigControl{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改配置管理 +// @Summary 修改配置管理 +// @Description 修改配置管理 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysConfigControl true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/sys-config/{id} [put] +// @Security Bearer +func (e SysConfig) Update(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigControl{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete 删除配置管理 +// @Summary 删除配置管理 +// @Description 删除配置管理 +// @Tags 配置管理 +// @Param ids body []int false "ids" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/sys-config [delete] +// @Security Bearer +func (e SysConfig) Delete(c *gin.Context) { + s := service.SysConfig{} + req := dto.SysConfigDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// Get2SysApp 获取系统配置信息 +// @Summary 获取系统前台配置信息,主要注意这里不在验证权限 +// @Description 获取系统配置信息,主要注意这里不在验证权限 +// @Tags 配置管理 +// @Success 200 {object} response.Response{data=map[string]string} "{"code": 200, "data": [...]}" +// @Router /api/v1/app-config [get] +func (e SysConfig) Get2SysApp(c *gin.Context) { + req := dto.SysConfigGetToSysAppReq{} + s := service.SysConfig{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + return + } + // 控制只读前台的数据 + req.IsFrontend = "1" + list := make([]models.SysConfig, 0) + err = s.GetWithKeyList(&req, &list) + if err != nil { + e.Error(500, err, "查询失败") + return + } + mp := make(map[string]string) + for i := 0; i < len(list); i++ { + key := list[i].ConfigKey + if key != "" { + mp[key] = list[i].ConfigValue + } + } + e.OK(mp, "查询成功") +} + +// Get2Set 获取配置 +// @Summary 获取配置 +// @Description 界面操作设置配置值的获取 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Success 200 {object} response.Response{data=map[string]interface{}} "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/set-config [get] +// @Security Bearer +func (e SysConfig) Get2Set(c *gin.Context) { + s := service.SysConfig{} + req := make([]dto.GetSetSysConfigReq, 0) + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.GetForSet(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + m := make(map[string]interface{}, 0) + for _, v := range req { + m[v.ConfigKey] = v.ConfigValue + } + e.OK(m, "查询成功") +} + +// Update2Set 设置配置 +// @Summary 设置配置 +// @Description 界面操作设置配置值 +// @Tags 配置管理 +// @Accept application/json +// @Product application/json +// @Param data body []dto.GetSetSysConfigReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/set-config [put] +// @Security Bearer +func (e SysConfig) Update2Set(c *gin.Context) { + s := service.SysConfig{} + req := make([]dto.GetSetSysConfigReq, 0) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.UpdateForSet(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + + e.OK("", "更新成功") +} + +// GetSysConfigByKEYForService 根据Key获取SysConfig的Service +// @Summary 根据Key获取SysConfig的Service +// @Description 根据Key获取SysConfig的Service +// @Tags 配置管理 +// @Param configKey path string false "configKey" +// @Success 200 {object} response.Response{data=dto.SysConfigByKeyReq} "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-config/{id} [get] +// @Security Bearer +func (e SysConfig) GetSysConfigByKEYForService(c *gin.Context) { + var s = new(service.SysConfig) + var req = new(dto.SysConfigByKeyReq) + var resp = new(dto.GetSysConfigByKEYForServiceResp) + err := e.MakeContext(c). + MakeOrm(). + Bind(req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.GetWithKey(req, resp) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(resp, s.Msg) +} diff --git a/admin/app/admin/apis/sys_dept.go b/admin/app/admin/apis/sys_dept.go new file mode 100644 index 0000000..d6fbaf8 --- /dev/null +++ b/admin/app/admin/apis/sys_dept.go @@ -0,0 +1,238 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysDept struct { + api.Api +} + +// GetPage +// @Summary 分页部门列表数据 +// @Description 分页列表 +// @Tags 部门 +// @Param deptName query string false "deptName" +// @Param deptId query string false "deptId" +// @Param position query string false "position" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dept [get] +// @Security Bearer +func (e SysDept) GetPage(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDept, 0) + list, err = s.SetDeptPage(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "查询成功") +} + +// Get +// @Summary 获取部门数据 +// @Description 获取JSON +// @Tags 部门 +// @Param deptId path string false "deptId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dept/{deptId} [get] +// @Security Bearer +func (e SysDept) Get(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysDept + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert 添加部门 +// @Summary 添加部门 +// @Description 获取JSON +// @Tags 部门 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDeptInsertReq true "data" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/dept [post] +// @Security Bearer +func (e SysDept) Insert(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改部门 +// @Description 获取JSON +// @Tags 部门 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.SysDeptUpdateReq true "body" +// @Success 200 {string} string "{"code": 200, "message": "添加成功"}" +// @Success 200 {string} string "{"code": -1, "message": "添加失败"}" +// @Router /api/v1/dept/{deptId} [put] +// @Security Bearer +func (e SysDept) Update(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除部门 +// @Description 删除数据 +// @Tags 部门 +// @Param data body dto.SysDeptDeleteReq true "body" +// @Success 200 {string} string "{"code": 200, "message": "删除成功"}" +// @Success 200 {string} string "{"code": -1, "message": "删除失败"}" +// @Router /api/v1/dept [delete] +// @Security Bearer +func (e SysDept) Delete(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// Get2Tree 用户管理 左侧部门树 +func (e SysDept) Get2Tree(c *gin.Context) { + s := service.SysDept{} + req := dto.SysDeptGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]dto.DeptLabel, 0) + list, err = s.SetDeptTree(&req) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "") +} + +// GetDeptTreeRoleSelect TODO: 此接口需要调整不应该将list和选中放在一起 +func (e SysDept) GetDeptTreeRoleSelect(c *gin.Context) { + s := service.SysDept{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + id, err := pkg.StringToInt(c.Param("roleId")) + result, err := s.SetDeptLabel() + if err != nil { + e.Error(500, err, err.Error()) + return + } + menuIds := make([]int, 0) + if id != 0 { + menuIds, err = s.GetWithRoleId(id) + if err != nil { + e.Error(500, err, err.Error()) + return + } + } + e.OK(gin.H{ + "depts": result, + "checkedKeys": menuIds, + }, "") +} diff --git a/admin/app/admin/apis/sys_dict_data.go b/admin/app/admin/apis/sys_dict_data.go new file mode 100644 index 0000000..3f4e72d --- /dev/null +++ b/admin/app/admin/apis/sys_dict_data.go @@ -0,0 +1,220 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysDictData struct { + api.Api +} + +// GetPage +// @Summary 字典数据列表 +// @Description 获取JSON +// @Tags 字典数据 +// @Param status query string false "status" +// @Param dictCode query string false "dictCode" +// @Param dictType query string false "dictType" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/data [get] +// @Security Bearer +func (e SysDictData) GetPage(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysDictData, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 通过编码获取字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Param dictCode path int true "字典编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/data/{dictCode} [get] +// @Security Bearer +func (e SysDictData) Get(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + var object models.SysDictData + + err = s.Get(&req, &object) + if err != nil { + e.Logger.Warnf("Get error: %s", err.Error()) + e.Error(500, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 添加字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictDataInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "message": "添加成功"}" +// @Router /api/v1/dict/data [post] +// @Security Bearer +func (e SysDictData) Insert(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, "创建失败") + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改字典数据 +// @Description 获取JSON +// @Tags 字典数据 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictDataUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "修改成功"}" +// @Router /api/v1/dict/data/{dictCode} [put] +// @Security Bearer +func (e SysDictData) Update(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除字典数据 +// @Description 删除数据 +// @Tags 字典数据 +// @Param dictCode body dto.SysDictDataDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "message": "删除成功"}" +// @Router /api/v1/dict/data [delete] +// @Security Bearer +func (e SysDictData) Delete(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} + +// GetAll 数据字典根据key获取 业务页面使用 +// @Summary 数据字典根据key获取 +// @Description 数据字典根据key获取 +// @Tags 字典数据 +// @Param dictType query int true "dictType" +// @Success 200 {object} response.Response{data=[]dto.SysDictDataGetAllResp} "{"code": 200, "data": [...]}" +// @Router /api/v1/dict-data/option-select [get] +// @Security Bearer +func (e SysDictData) GetAll(c *gin.Context) { + s := service.SysDictData{} + req := dto.SysDictDataGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictData, 0) + err = s.GetAll(&req, &list) + if err != nil { + e.Error(500, err, "查询失败") + return + } + l := make([]dto.SysDictDataGetAllResp, 0) + for _, i := range list { + d := dto.SysDictDataGetAllResp{} + e.Translate(i, &d) + l = append(l, d) + } + + e.OK(l,"查询成功") +} diff --git a/admin/app/admin/apis/sys_dict_type.go b/admin/app/admin/apis/sys_dict_type.go new file mode 100644 index 0000000..85761fd --- /dev/null +++ b/admin/app/admin/apis/sys_dict_type.go @@ -0,0 +1,210 @@ +package apis + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysDictType struct { + api.Api +} + +// GetPage 字典类型列表数据 +// @Summary 字典类型列表数据 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictName query string false "dictName" +// @Param dictId query string false "dictId" +// @Param dictType query string false "dictType" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [get] +// @Security Bearer +func (e SysDictType) GetPage(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictType, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 字典类型通过字典id获取 +// @Summary 字典类型通过字典id获取 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictId path int true "字典类型编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type/{dictId} [get] +// @Security Bearer +func (e SysDictType) Get(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysDictType + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +//Insert 字典类型创建 +// @Summary 添加字典类型 +// @Description 获取JSON +// @Tags 字典类型 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictTypeInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [post] +// @Security Bearer +func (e SysDictType) Insert(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500, err,fmt.Sprintf(" 创建字典类型失败,详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改字典类型 +// @Description 获取JSON +// @Tags 字典类型 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysDictTypeUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type/{dictId} [put] +// @Security Bearer +func (e SysDictType) Update(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Error(500, err, err.Error()) + e.Logger.Error(err) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除字典类型 +// @Description 删除数据 +// @Tags 字典类型 +// @Param dictCode body dto.SysDictTypeDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type [delete] +// @Security Bearer +func (e SysDictType) Delete(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(req.GetId(), "删除成功") +} + +// GetAll +// @Summary 字典类型全部数据 代码生成使用接口 +// @Description 获取JSON +// @Tags 字典类型 +// @Param dictName query string false "dictName" +// @Param dictId query string false "dictId" +// @Param dictType query string false "dictType" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/dict/type-option-select [get] +// @Security Bearer +func (e SysDictType) GetAll(c *gin.Context) { + s := service.SysDictType{} + req :=dto.SysDictTypeGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysDictType, 0) + err = s.GetAll(&req, &list) + if err != nil { + e.Error(500, err, err.Error()) + return + } + e.OK(list, "查询成功") +} \ No newline at end of file diff --git a/admin/app/admin/apis/sys_login_log.go b/admin/app/admin/apis/sys_login_log.go new file mode 100644 index 0000000..536353e --- /dev/null +++ b/admin/app/admin/apis/sys_login_log.go @@ -0,0 +1,110 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysLoginLog struct { + api.Api +} + +// GetPage 登录日志列表 +// @Summary 登录日志列表 +// @Description 获取JSON +// @Tags 登录日志 +// @Param username query string false "用户名" +// @Param ipaddr query string false "ip地址" +// @Param loginLocation query string false "归属地" +// @Param status query string false "状态" +// @Param beginTime query string false "开始时间" +// @Param endTime query string false "结束时间" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log [get] +// @Security Bearer +func (e SysLoginLog) GetPage(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + list := make([]models.SysLoginLog, 0) + var count int64 + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 登录日志通过id获取 +// @Summary 登录日志通过id获取 +// @Description 获取JSON +// @Tags 登录日志 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log/{id} [get] +// @Security Bearer +func (e SysLoginLog) Get(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysLoginLog + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Delete 登录日志删除 +// @Summary 登录日志删除 +// @Description 登录日志删除 +// @Tags 登录日志 +// @Param data body dto.SysLoginLogDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-login-log [delete] +// @Security Bearer +func (e SysLoginLog) Delete(c *gin.Context) { + s := service.SysLoginLog{} + req :=dto.SysLoginLogDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.Remove(&req) + if err != nil { + e.Error(500, err, "删除失败") + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/admin/app/admin/apis/sys_menu.go b/admin/app/admin/apis/sys_menu.go new file mode 100644 index 0000000..4a3e6eb --- /dev/null +++ b/admin/app/admin/apis/sys_menu.go @@ -0,0 +1,249 @@ +package apis + +import ( + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysMenu struct { + api.Api +} + +// GetPage Menu列表数据 +// @Summary Menu列表数据 +// @Description 获取JSON +// @Tags 菜单 +// @Param menuName query string false "menuName" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [get] +// @Security Bearer +func (e SysMenu) GetPage(c *gin.Context) { + s := service.SysMenu{} + req := dto.SysMenuGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var list = make([]models.SysMenu, 0) + err = s.GetPage(&req, &list).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(list, "查询成功") +} + +// Get 获取菜单详情 +// @Summary Menu详情数据 +// @Description 获取JSON +// @Tags 菜单 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu/{id} [get] +// @Security Bearer +func (e SysMenu) Get(c *gin.Context) { + req := dto.SysMenuGetReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object = models.SysMenu{} + + err = s.Get(&req, &object).Error + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Insert 创建菜单 +// @Summary 创建菜单 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysMenuInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [post] +// @Security Bearer +func (e SysMenu) Insert(c *gin.Context) { + req := dto.SysMenuInsertReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req).Error + if err != nil { + e.Error(500, err, "创建失败") + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改菜单 +// @Summary 修改菜单 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param id path int true "id" +// @Param data body dto.SysMenuUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu/{id} [put] +// @Security Bearer +func (e SysMenu) Update(c *gin.Context) { + req := dto.SysMenuUpdateReq{} + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + err = s.Update(&req).Error + if err != nil { + e.Error(500, err, "更新失败") + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete 删除菜单 +// @Summary 删除菜单 +// @Description 删除数据 +// @Tags 菜单 +// @Param data body dto.SysMenuDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menu [delete] +// @Security Bearer +func (e SysMenu) Delete(c *gin.Context) { + control := new(dto.SysMenuDeleteReq) + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + Bind(control, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + err = s.Remove(control).Error + if err != nil { + e.Logger.Errorf("RemoveSysMenu error, %s", err) + e.Error(500, err, "删除失败") + return + } + e.OK(control.GetId(), "删除成功") +} + +// GetMenuRole 根据登录角色名称获取菜单列表数据(左菜单使用) +// @Summary 根据登录角色名称获取菜单列表数据(左菜单使用) +// @Description 获取JSON +// @Tags 菜单 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menurole [get] +// @Security Bearer +func (e SysMenu) GetMenuRole(c *gin.Context) { + s := new(service.SysMenu) + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + result, err := s.SetMenuRole(user.GetRoleName(c)) + + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.OK(result, "") +} + +// GetMenuTreeSelect 根据角色ID查询菜单下拉树结构 +// @Summary 角色修改使用的菜单列表 +// @Description 获取JSON +// @Tags 菜单 +// @Accept application/json +// @Product application/json +// @Param roleId path int true "roleId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/menuTreeselect/{roleId} [get] +// @Security Bearer +func (e SysMenu) GetMenuTreeSelect(c *gin.Context) { + m := service.SysMenu{} + r := service.SysRole{} + req := dto.SelectRole{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&m.Service). + MakeService(&r.Service). + Bind(&req, nil). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + result, err := m.SetLabel() + if err != nil { + e.Error(500, err, "查询失败") + return + } + + menuIds := make([]int, 0) + if req.RoleId != 0 { + menuIds, err = r.GetRoleMenuId(req.RoleId) + if err != nil { + e.Error(500, err, "") + return + } + } + e.OK(gin.H{ + "menus": result, + "checkedKeys": menuIds, + }, "获取成功") +} diff --git a/admin/app/admin/apis/sys_opera_log.go b/admin/app/admin/apis/sys_opera_log.go new file mode 100644 index 0000000..6e4ed0a --- /dev/null +++ b/admin/app/admin/apis/sys_opera_log.go @@ -0,0 +1,118 @@ +package apis + +import ( + "fmt" + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysOperaLog struct { + api.Api +} + +// GetPage 操作日志列表 +// @Summary 操作日志列表 +// @Description 获取JSON +// @Tags 操作日志 +// @Param title query string false "title" +// @Param method query string false "method" +// @Param requestMethod query string false "requestMethod" +// @Param operUrl query string false "operUrl" +// @Param operIp query string false "operIp" +// @Param status query string false "status" +// @Param beginTime query string false "beginTime" +// @Param endTime query string false "endTime" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log [get] +// @Security Bearer +func (e SysOperaLog) GetPage(c *gin.Context) { + s := service.SysOperaLog{} + req := new(dto.SysOperaLogGetPageReq) + err := e.MakeContext(c). + MakeOrm(). + Bind(req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysOperaLog, 0) + var count int64 + + err = s.GetPage(req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get 操作日志通过id获取 +// @Summary 操作日志通过id获取 +// @Description 获取JSON +// @Tags 操作日志 +// @Param id path string false "id" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log/{id} [get] +// @Security Bearer +func (e SysOperaLog) Get(c *gin.Context) { + s := new(service.SysOperaLog) + req :=dto.SysOperaLogGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysOperaLog + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Delete 操作日志删除 +// DeleteSysMenu 操作日志删除 +// @Summary 删除操作日志 +// @Description 删除数据 +// @Tags 操作日志 +// @Param data body dto.SysOperaLogDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-opera-log [delete] +// @Security Bearer +func (e SysOperaLog) Delete(c *gin.Context) { + s := new(service.SysOperaLog) + req :=dto.SysOperaLogDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + err = s.Remove(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500,err, fmt.Sprintf("删除失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} diff --git a/admin/app/admin/apis/sys_post.go b/admin/app/admin/apis/sys_post.go new file mode 100644 index 0000000..215152b --- /dev/null +++ b/admin/app/admin/apis/sys_post.go @@ -0,0 +1,184 @@ +package apis + +import ( + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysPost struct { + api.Api +} + +// GetPage +// @Summary 岗位列表数据 +// @Description 获取JSON +// @Tags 岗位 +// @Param postName query string false "postName" +// @Param postCode query string false "postCode" +// @Param postId query string false "postId" +// @Param status query string false "status" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [get] +// @Security Bearer +func (e SysPost) GetPage(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysPost, 0) + var count int64 + + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取岗位信息 +// @Description 获取JSON +// @Tags 岗位 +// @Param id path int true "编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post/{postId} [get] +// @Security Bearer +func (e SysPost) Get(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysPost + + err = s.Get(&req, &object) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位信息获取失败!错误详情:%s", err.Error())) + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 添加岗位 +// @Description 获取JSON +// @Tags 岗位 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysPostInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [post] +// @Security Bearer +func (e SysPost) Insert(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("新建岗位失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改岗位 +// @Description 获取JSON +// @Tags 岗位 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysPostUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post/{id} [put] +// @Security Bearer +func (e SysPost) Update(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Update(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位更新失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除岗位 +// @Description 删除数据 +// @Tags 岗位 +// @Param id body dto.SysPostDeleteReq true "请求参数" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/post [delete] +// @Security Bearer +func (e SysPost) Delete(c *gin.Context) { + s := service.SysPost{} + req :=dto.SysPostDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.Remove(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("岗位删除失败!错误详情:%s", err.Error())) + return + } + e.OK(req.GetId(), "删除成功") +} \ No newline at end of file diff --git a/admin/app/admin/apis/sys_role.go b/admin/app/admin/apis/sys_role.go new file mode 100644 index 0000000..05ffdb0 --- /dev/null +++ b/admin/app/admin/apis/sys_role.go @@ -0,0 +1,284 @@ +package apis + +import ( + "fmt" + "git.ilapage.cn/OPC/chorus/admin/common/global" + "net/http" + + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/sdk" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" +) + +type SysRole struct { + api.Api +} + +// GetPage +// @Summary 角色列表数据 +// @Description Get JSON +// @Tags 角色/Role +// @Param roleName query string false "roleName" +// @Param status query string false "status" +// @Param roleKey query string false "roleKey" +// @Param pageSize query int false "页条数" +// @Param pageIndex query int false "页码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [get] +// @Security Bearer +func (e SysRole) GetPage(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.Form). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + list := make([]models.SysRole, 0) + var count int64 + + err = s.GetPage(&req, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取Role数据 +// @Description 获取JSON +// @Tags 角色/Role +// @Param roleId path string false "roleId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role/{id} [get] +// @Security Bearer +func (e SysRole) Get(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleGetReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf(" %s ", err.Error())) + return + } + + var object models.SysRole + + err = s.Get(&req, &object) + if err != nil { + e.Error(http.StatusUnprocessableEntity, err, "查询失败") + return + } + + e.OK(object, "查询成功") +} + +// Insert +// @Summary 创建角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.SysRoleInsertReq true "data" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [post] +// @Security Bearer +func (e SysRole) Insert(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置创建人 + req.CreateBy = user.GetUserId(c) + if req.Status == "" { + req.Status = "2" + } + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + err = s.Insert(&req, cb) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "创建失败,"+err.Error()) + return + } + _, err = global.LoadPolicy(c) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "创建失败,"+err.Error()) + return + } + e.OK(req.GetId(), "创建成功") +} + +// Update 修改用户角色 +// @Summary 修改用户角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.SysRoleUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role/{id} [put] +// @Security Bearer +func (e SysRole) Update(c *gin.Context) { + s := service.SysRole{} + req := dto.SysRoleUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + + req.SetUpdateBy(user.GetUserId(c)) + + err = s.Update(&req, cb) + if err != nil { + e.Logger.Error(err) + return + } + + _, err = global.LoadPolicy(c) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "更新失败,"+err.Error()) + return + } + + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除用户角色 +// @Description 删除数据 +// @Tags 角色/Role +// @Param data body dto.SysRoleDeleteReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role [delete] +// @Security Bearer +func (e SysRole) Delete(c *gin.Context) { + s := new(service.SysRole) + req := dto.SysRoleDeleteReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("删除角色 %v 失败,\r\n失败信息 %s", req.Ids, err.Error())) + return + } + + cb := sdk.Runtime.GetCasbinKey(c.Request.Host) + err = s.Remove(&req, cb) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, "") + return + } + + e.OK(req.GetId(), fmt.Sprintf("删除角色角色 %v 状态成功!", req.GetId())) +} + +// Update2Status 修改用户角色状态 +// @Summary 修改用户角色 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.UpdateStatusReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role-status/{id} [put] +// @Security Bearer +func (e SysRole) Update2Status(c *gin.Context) { + s := service.SysRole{} + req := dto.UpdateStatusReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error())) + return + } + req.SetUpdateBy(user.GetUserId(c)) + err = s.UpdateStatus(&req) + if err != nil { + e.Error(500, err, fmt.Sprintf("更新角色状态失败,失败原因:%s ", err.Error())) + return + } + e.OK(req.GetId(), fmt.Sprintf("更新角色 %v 状态成功!", req.GetId())) +} + +// Update2DataScope 更新角色数据权限 +// @Summary 更新角色数据权限 +// @Description 获取JSON +// @Tags 角色/Role +// @Accept application/json +// @Product application/json +// @Param data body dto.RoleDataScopeReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/role-status/{id} [put] +// @Security Bearer +func (e SysRole) Update2DataScope(c *gin.Context) { + s := service.SysRole{} + req := dto.RoleDataScopeReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + data := &models.SysRole{ + RoleId: req.RoleId, + DataScope: req.DataScope, + DeptIds: req.DeptIds, + } + data.UpdateBy = user.GetUserId(c) + err = s.UpdateDataScope(&req).Error + if err != nil { + e.Error(500, err, fmt.Sprintf("更新角色数据权限失败!错误详情:%s", err.Error())) + return + } + e.OK(nil, "操作成功") +} diff --git a/admin/app/admin/apis/sys_user.go b/admin/app/admin/apis/sys_user.go new file mode 100644 index 0000000..99094b5 --- /dev/null +++ b/admin/app/admin/apis/sys_user.go @@ -0,0 +1,459 @@ +package apis + +import ( + "github.com/gin-gonic/gin/binding" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "golang.org/x/crypto/bcrypt" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + _ "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/google/uuid" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + "git.ilapage.cn/OPC/chorus/admin/common/actions" +) + +type SysUser struct { + api.Api +} + +// GetPage +// @Summary 列表用户信息数据 +// @Description 获取JSON +// @Tags 用户 +// @Param username query string false "username" +// @Success 200 {string} {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user [get] +// @Security Bearer +func (e SysUser) GetPage(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserGetPageReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + list := make([]models.SysUser, 0) + var count int64 + + err = s.GetPage(&req, p, &list, &count) + if err != nil { + e.Error(500, err, "查询失败") + return + } + + e.PageOK(list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") +} + +// Get +// @Summary 获取用户 +// @Description 获取JSON +// @Tags 用户 +// @Param userId path int true "用户编码" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [get] +// @Security Bearer +func (e SysUser) Get(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + var object models.SysUser + //数据权限检查 + p := actions.GetPermissionFromContext(c) + err = s.Get(&req, p, &object) + if err != nil { + e.Error(http.StatusUnprocessableEntity, err, "查询失败") + return + } + e.OK(object, "查询成功") +} + +// Insert +// @Summary 创建用户 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysUserInsertReq true "用户数据" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user [post] +// @Security Bearer +func (e SysUser) Insert(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserInsertReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 设置创建人 + req.SetCreateBy(user.GetUserId(c)) + err = s.Insert(&req) + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + e.OK(req.GetId(), "创建成功") +} + +// Update +// @Summary 修改用户数据 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.SysUserUpdateReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [put] +// @Security Bearer +func (e SysUser) Update(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserUpdateReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.Update(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// Delete +// @Summary 删除用户数据 +// @Description 删除数据 +// @Tags 用户 +// @Param userId path int true "userId" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/sys-user/{userId} [delete] +// @Security Bearer +func (e SysUser) Delete(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 设置编辑人 + req.SetUpdateBy(user.GetUserId(c)) + + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.Remove(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "删除成功") +} + +// InsetAvatar +// @Summary 修改头像 +// @Description 获取JSON +// @Tags 个人中心 +// @Accept multipart/form-data +// @Param file formData file true "file" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/avatar [post] +// @Security Bearer +func (e SysUser) InsetAvatar(c *gin.Context) { + s := service.SysUser{} + req := dto.UpdateSysUserAvatarReq{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + form, _ := c.MultipartForm() + files := form.File["upload[]"] + guid := uuid.New().String() + filPath := "static/uploadfile/" + guid + ".jpg" + for _, file := range files { + e.Logger.Debugf("upload avatar file: %s", file.Filename) + // 上传文件至指定目录 + err = c.SaveUploadedFile(file, filPath) + if err != nil { + e.Logger.Errorf("save file error, %s", err.Error()) + e.Error(500, err, "") + return + } + } + req.UserId = p.UserId + req.Avatar = "/" + filPath + + err = s.UpdateAvatar(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(filPath, "修改成功") +} + +// UpdateStatus 修改用户状态 +// @Summary 修改用户状态 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.UpdateSysUserStatusReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/status [put] +// @Security Bearer +func (e SysUser) UpdateStatus(c *gin.Context) { + s := service.SysUser{} + req := dto.UpdateSysUserStatusReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON, nil). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.UpdateStatus(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// ResetPwd 重置用户密码 +// @Summary 重置用户密码 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.ResetSysUserPwdReq true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/pwd/reset [put] +// @Security Bearer +func (e SysUser) ResetPwd(c *gin.Context) { + s := service.SysUser{} + req := dto.ResetSysUserPwdReq{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req, binding.JSON). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := actions.GetPermissionFromContext(c) + + err = s.ResetPwd(&req, p) + if err != nil { + e.Logger.Error(err) + return + } + e.OK(req.GetId(), "更新成功") +} + +// UpdatePwd +// @Summary 修改密码 +// @Description 获取JSON +// @Tags 用户 +// @Accept application/json +// @Product application/json +// @Param data body dto.PassWord true "body" +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/pwd/set [put] +// @Security Bearer +func (e SysUser) UpdatePwd(c *gin.Context) { + s := service.SysUser{} + req := dto.PassWord{} + err := e.MakeContext(c). + MakeOrm(). + Bind(&req). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + // 数据权限检查 + p := actions.GetPermissionFromContext(c) + var hash []byte + if hash, err = bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost); err != nil { + req.NewPassword = string(hash) + } + + err = s.UpdatePwd(user.GetUserId(c), req.OldPassword, req.NewPassword, p) + if err != nil { + e.Logger.Error(err) + e.Error(http.StatusForbidden, err, "密码修改失败") + return + } + + e.OK(nil, "密码修改成功") +} + +// GetProfile +// @Summary 获取个人中心用户 +// @Description 获取JSON +// @Tags 个人中心 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/user/profile [get] +// @Security Bearer +func (e SysUser) GetProfile(c *gin.Context) { + s := service.SysUser{} + req := dto.SysUserById{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + + req.Id = user.GetUserId(c) + + sysUser := models.SysUser{} + roles := make([]models.SysRole, 0) + posts := make([]models.SysPost, 0) + err = s.GetProfile(&req, &sysUser, &roles, &posts) + if err != nil { + e.Logger.Errorf("get user profile error, %s", err.Error()) + e.Error(500, err, "获取用户信息失败") + return + } + e.OK(gin.H{ + "user": sysUser, + "roles": roles, + "posts": posts, + }, "查询成功") +} + +// GetInfo +// @Summary 获取个人信息 +// @Description 获取JSON +// @Tags 个人中心 +// @Success 200 {object} response.Response "{"code": 200, "data": [...]}" +// @Router /api/v1/getinfo [get] +// @Security Bearer +func (e SysUser) GetInfo(c *gin.Context) { + req := dto.SysUserById{} + s := service.SysUser{} + r := service.SysRole{} + err := e.MakeContext(c). + MakeOrm(). + MakeService(&r.Service). + MakeService(&s.Service). + Errors + if err != nil { + e.Logger.Error(err) + e.Error(500, err, err.Error()) + return + } + p := actions.GetPermissionFromContext(c) + var roles = make([]string, 1) + roles[0] = user.GetRoleName(c) + var permissions = make([]string, 1) + permissions[0] = "*:*:*" + var buttons = make([]string, 1) + buttons[0] = "*:*:*" + + var mp = make(map[string]interface{}) + mp["roles"] = roles + if user.GetRoleName(c) == "admin" || user.GetRoleName(c) == "系统管理员" { + mp["permissions"] = permissions + mp["buttons"] = buttons + } else { + list, _ := r.GetById(user.GetRoleId(c)) + mp["permissions"] = list + mp["buttons"] = list + } + sysUser := models.SysUser{} + req.Id = user.GetUserId(c) + err = s.Get(&req, p, &sysUser) + if err != nil { + e.Error(http.StatusUnauthorized, err, "登录失败") + return + } + mp["introduction"] = " am a super administrator" + mp["avatar"] = "https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif" + if sysUser.Avatar != "" { + mp["avatar"] = sysUser.Avatar + } + mp["userName"] = sysUser.Username + mp["userId"] = sysUser.UserId + mp["deptId"] = sysUser.DeptId + mp["name"] = sysUser.NickName + mp["code"] = 200 + e.OK(mp, "") +} diff --git a/admin/app/admin/models/casbin_rule.go b/admin/app/admin/models/casbin_rule.go new file mode 100644 index 0000000..d9013ee --- /dev/null +++ b/admin/app/admin/models/casbin_rule.go @@ -0,0 +1,16 @@ +package models + +type CasbinRule struct { + ID uint `gorm:"primaryKey;autoIncrement"` + Ptype string `gorm:"size:512;uniqueIndex:unique_index"` + V0 string `gorm:"size:512;uniqueIndex:unique_index"` + V1 string `gorm:"size:512;uniqueIndex:unique_index"` + V2 string `gorm:"size:512;uniqueIndex:unique_index"` + V3 string `gorm:"size:512;uniqueIndex:unique_index"` + V4 string `gorm:"size:512;uniqueIndex:unique_index"` + V5 string `gorm:"size:512;uniqueIndex:unique_index"` +} + +func (CasbinRule) TableName() string { + return "sys_casbin_rule" +} diff --git a/admin/app/admin/models/datascope.go b/admin/app/admin/models/datascope.go new file mode 100644 index 0000000..8ccd047 --- /dev/null +++ b/admin/app/admin/models/datascope.go @@ -0,0 +1,81 @@ +package models + +import ( + "errors" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +type DataPermission struct { + DataScope string + UserId int + DeptId int + RoleId int +} + +func (e *DataPermission) GetDataScope(tableName string, db *gorm.DB) (*gorm.DB, error) { + + if !config.ApplicationConfig.EnableDP { + usageStr := `数据权限已经为您` + pkg.Green(`关闭`) + `,如需开启请参考配置文件字段说明` + log.Debug("%s\n", usageStr) + return db, nil + } + user := new(SysUser) + role := new(SysRole) + err := db.Find(user, e.UserId).Error + if err != nil { + return nil, errors.New("获取用户数据出错 msg:" + err.Error()) + } + err = db.Find(role, user.RoleId).Error + if err != nil { + return nil, errors.New("获取用户数据出错 msg:" + err.Error()) + } + if role.DataScope == "2" { + db = db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId) + } + if role.DataScope == "3" { + db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId) + } + if role.DataScope == "4" { + db = db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%") + } + if role.DataScope == "5" || role.DataScope == "" { + db = db.Where(tableName+".create_by = ?", e.UserId) + } + + return db, nil +} + +//func DataScopes(tableName string, userId int) func(db *gorm.DB) *gorm.DB { +// return func(db *gorm.DB) *gorm.DB { +// user := new(SysUser) +// role := new(SysRole) +// user.UserId = userId +// err := db.Find(user, userId).Error +// if err != nil { +// db.Error = errors.New("获取用户数据出错 msg:" + err.Error()) +// return db +// } +// err = db.Find(role, user.RoleId).Error +// if err != nil { +// db.Error = errors.New("获取用户数据出错 msg:" + err.Error()) +// return db +// } +// if role.DataScope == "2" { +// return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", user.RoleId) +// } +// if role.DataScope == "3" { +// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", user.DeptId) +// } +// if role.DataScope == "4" { +// return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%"+pkg.IntToString(user.DeptId)+"%") +// } +// if role.DataScope == "5" || role.DataScope == "" { +// return db.Where(tableName+".create_by = ?", userId) +// } +// return db +// } +//} diff --git a/admin/app/admin/models/initdb.go b/admin/app/admin/models/initdb.go new file mode 100644 index 0000000..e7bba55 --- /dev/null +++ b/admin/app/admin/models/initdb.go @@ -0,0 +1,55 @@ +package models + +import ( + "fmt" + "git.ilapage.cn/OPC/chorus/admin/common/global" + "gorm.io/gorm" + "io/ioutil" + "log" + "strings" +) + +func InitDb(db *gorm.DB) (err error) { + filePath := "config/db.sql" + err = ExecSql(db, filePath) + if global.Driver == "postgres" { + filePath = "config/pg.sql" + err = ExecSql(db, filePath) + } + return err +} + +func ExecSql(db *gorm.DB, filePath string) error { + sql, err := Ioutil(filePath) + if err != nil { + fmt.Println("数据库基础数据初始化脚本读取失败!原因:", err.Error()) + return err + } + sqlList := strings.Split(sql, ";") + for i := 0; i < len(sqlList)-1; i++ { + if strings.Contains(sqlList[i], "--") { + fmt.Println(sqlList[i]) + continue + } + sql := strings.Replace(sqlList[i]+";", "\n", "", -1) + sql = strings.TrimSpace(sql) + if err = db.Exec(sql).Error; err != nil { + log.Printf("error sql: %s", sql) + if !strings.Contains(err.Error(), "Query was empty") { + return err + } + } + } + return nil +} + +func Ioutil(filePath string) (string, error) { + if contents, err := ioutil.ReadFile(filePath); err == nil { + //因为contents是[]byte类型,直接转换成string类型后会多一行空格,需要使用strings.Replace替换换行符 + result := strings.Replace(string(contents), "\n", "", 1) + fmt.Println("Use ioutil.ReadFile to read a file:", result) + return result, nil + } else { + return "", err + } +} diff --git a/admin/app/admin/models/sys_api.go b/admin/app/admin/models/sys_api.go new file mode 100644 index 0000000..e41a708 --- /dev/null +++ b/admin/app/admin/models/sys_api.go @@ -0,0 +1,91 @@ +package models + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "regexp" + "strings" + + "github.com/bitly/go-simplejson" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/runtime" + "github.com/go-admin-team/go-admin-core/storage" + + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysApi struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` + Handle string `json:"handle" gorm:"size:128;comment:handle"` + Title string `json:"title" gorm:"size:128;comment:标题"` + Path string `json:"path" gorm:"size:128;comment:地址"` + Action string `json:"action" gorm:"size:16;comment:请求类型"` + Type string `json:"type" gorm:"size:16;comment:接口类型"` + models.ModelTime + models.ControlBy +} + +func (*SysApi) TableName() string { + return "sys_api" +} + +func (e *SysApi) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysApi) GetId() interface{} { + return e.Id +} + +func SaveSysApi(message storage.Messager) (err error) { + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + err = fmt.Errorf("json Marshal error, %v", err.Error()) + return err + } + + var l runtime.Routers + err = json.Unmarshal(rb, &l) + if err != nil { + err = fmt.Errorf("json Unmarshal error, %s", err.Error()) + return err + } + dbList := sdk.Runtime.GetDb() + for _, d := range dbList { + for _, v := range l.List { + if v.HttpMethod != "HEAD" || + strings.Contains(v.RelativePath, "/swagger/") || + strings.Contains(v.RelativePath, "/static/") || + strings.Contains(v.RelativePath, "/form-generator/") || + strings.Contains(v.RelativePath, "/sys/tables") { + + // 根据接口方法注释里的@Summary填充接口名称,适用于代码生成器 + // 可在此处增加配置路径前缀的if判断,只对代码生成的自建应用进行定向的接口名称填充 + jsonFile, _ := ioutil.ReadFile("docs/swagger.json") + jsonData, _ := simplejson.NewFromReader(bytes.NewReader(jsonFile)) + urlPath := v.RelativePath + idPatten := "(.*)/:(\\w+)" // 正则替换,把:id换成{id} + reg, _ := regexp.Compile(idPatten) + if reg.MatchString(urlPath) { + urlPath = reg.ReplaceAllString(v.RelativePath, "${1}/{${2}}") // 把:id换成{id} + } + apiTitle, _ := jsonData.Get("paths").Get(urlPath).Get(strings.ToLower(v.HttpMethod)).Get("summary").String() + + err := d.Debug().Where(SysApi{Path: v.RelativePath, Action: v.HttpMethod}). + Attrs(SysApi{Handle: v.Handler, Title: apiTitle}). + FirstOrCreate(&SysApi{}). + //Update("handle", v.Handler). + Error + if err != nil { + err := fmt.Errorf("Models SaveSysApi error: %s \r\n ", err.Error()) + return err + } + } + } + } + return nil +} diff --git a/admin/app/admin/models/sys_config.go b/admin/app/admin/models/sys_config.go new file mode 100644 index 0000000..d93151b --- /dev/null +++ b/admin/app/admin/models/sys_config.go @@ -0,0 +1,30 @@ +package models + +import ( + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysConfig struct { + models.Model + ConfigName string `json:"configName" gorm:"size:128;comment:ConfigName"` // + ConfigKey string `json:"configKey" gorm:"size:128;comment:ConfigKey"` // + ConfigValue string `json:"configValue" gorm:"size:255;comment:ConfigValue"` // + ConfigType string `json:"configType" gorm:"size:64;comment:ConfigType"` + IsFrontend string `json:"isFrontend" gorm:"size:64;comment:是否前台"` // + Remark string `json:"remark" gorm:"size:128;comment:Remark"` // + models.ControlBy + models.ModelTime +} + +func (*SysConfig) TableName() string { + return "sys_config" +} + +func (e *SysConfig) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysConfig) GetId() interface{} { + return e.Id +} diff --git a/admin/app/admin/models/sys_dept.go b/admin/app/admin/models/sys_dept.go new file mode 100644 index 0000000..62a3221 --- /dev/null +++ b/admin/app/admin/models/sys_dept.go @@ -0,0 +1,33 @@ +package models + +import "git.ilapage.cn/OPC/chorus/admin/common/models" + +type SysDept struct { + DeptId int `json:"deptId" gorm:"primaryKey;autoIncrement;"` //部门编码 + ParentId int `json:"parentId" gorm:""` //上级部门 + DeptPath string `json:"deptPath" gorm:"size:255;"` // + DeptName string `json:"deptName" gorm:"size:128;"` //部门名称 + Sort int `json:"sort" gorm:"size:4;"` //排序 + Leader string `json:"leader" gorm:"size:128;"` //负责人 + Phone string `json:"phone" gorm:"size:11;"` //手机 + Email string `json:"email" gorm:"size:64;"` //邮箱 + Status int `json:"status" gorm:"size:4;"` //状态 + models.ControlBy + models.ModelTime + DataScope string `json:"dataScope" gorm:"-"` + Params string `json:"params" gorm:"-"` + Children []SysDept `json:"children" gorm:"-"` +} + +func (*SysDept) TableName() string { + return "sys_dept" +} + +func (e *SysDept) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDept) GetId() interface{} { + return e.DeptId +} diff --git a/admin/app/admin/models/sys_dict_data.go b/admin/app/admin/models/sys_dict_data.go new file mode 100644 index 0000000..28fb4bf --- /dev/null +++ b/admin/app/admin/models/sys_dict_data.go @@ -0,0 +1,34 @@ +package models + +import ( + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysDictData struct { + DictCode int `json:"dictCode" gorm:"primaryKey;column:dict_code;autoIncrement;comment:主键编码"` + DictSort int `json:"dictSort" gorm:"size:20;comment:DictSort"` + DictLabel string `json:"dictLabel" gorm:"size:128;comment:DictLabel"` + DictValue string `json:"dictValue" gorm:"size:255;comment:DictValue"` + DictType string `json:"dictType" gorm:"size:64;comment:DictType"` + CssClass string `json:"cssClass" gorm:"size:128;comment:CssClass"` + ListClass string `json:"listClass" gorm:"size:128;comment:ListClass"` + IsDefault string `json:"isDefault" gorm:"size:8;comment:IsDefault"` + Status int `json:"status" gorm:"size:4;comment:Status"` + Default string `json:"default" gorm:"size:8;comment:Default"` + Remark string `json:"remark" gorm:"size:255;comment:Remark"` + models.ControlBy + models.ModelTime +} + +func (*SysDictData) TableName() string { + return "sys_dict_data" +} + +func (e *SysDictData) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDictData) GetId() interface{} { + return e.DictCode +} diff --git a/admin/app/admin/models/sys_dict_type.go b/admin/app/admin/models/sys_dict_type.go new file mode 100644 index 0000000..5f17953 --- /dev/null +++ b/admin/app/admin/models/sys_dict_type.go @@ -0,0 +1,28 @@ +package models + +import ( + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysDictType struct { + ID int `json:"id" gorm:"primaryKey;column:dict_id;autoIncrement;comment:主键编码"` + DictName string `json:"dictName" gorm:"size:128;comment:DictName"` + DictType string `json:"dictType" gorm:"size:128;comment:DictType"` + Status int `json:"status" gorm:"size:4;comment:Status"` + Remark string `json:"remark" gorm:"size:255;comment:Remark"` + models.ControlBy + models.ModelTime +} + +func (*SysDictType) TableName() string { + return "sys_dict_type" +} + +func (e *SysDictType) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysDictType) GetId() interface{} { + return e.ID +} diff --git a/admin/app/admin/models/sys_login_log.go b/admin/app/admin/models/sys_login_log.go new file mode 100644 index 0000000..ceff24c --- /dev/null +++ b/admin/app/admin/models/sys_login_log.go @@ -0,0 +1,72 @@ +package models + +import ( + "encoding/json" + "errors" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/storage" + + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysLoginLog struct { + models.Model + Username string `json:"username" gorm:"size:128;comment:用户名"` + Status string `json:"status" gorm:"size:4;comment:状态"` + Ipaddr string `json:"ipaddr" gorm:"size:255;comment:ip地址"` + LoginLocation string `json:"loginLocation" gorm:"size:255;comment:归属地"` + Browser string `json:"browser" gorm:"size:255;comment:浏览器"` + Os string `json:"os" gorm:"size:255;comment:系统"` + Platform string `json:"platform" gorm:"size:255;comment:固件"` + LoginTime time.Time `json:"loginTime" gorm:"comment:登录时间"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Msg string `json:"msg" gorm:"size:255;comment:信息"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + models.ControlBy +} + +func (*SysLoginLog) TableName() string { + return "sys_login_log" +} + +func (e *SysLoginLog) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysLoginLog) GetId() interface{} { + return e.Id +} + +// SaveLoginLog 从队列中获取登录日志 +func SaveLoginLog(message storage.Messager) (err error) { + //准备db + db := sdk.Runtime.GetDbByKey(message.GetPrefix()) + if db == nil { + err = errors.New("db not exist") + log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error()) + return err + } + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + log.Errorf("json Marshal error, %s", err.Error()) + return err + } + var l SysLoginLog + err = json.Unmarshal(rb, &l) + if err != nil { + log.Errorf("json Unmarshal error, %s", err.Error()) + return err + } + err = db.Create(&l).Error + if err != nil { + log.Errorf("db create error, %s", err.Error()) + return err + } + return nil +} diff --git a/admin/app/admin/models/sys_menu.go b/admin/app/admin/models/sys_menu.go new file mode 100644 index 0000000..785fe9e --- /dev/null +++ b/admin/app/admin/models/sys_menu.go @@ -0,0 +1,50 @@ +package models + +import "git.ilapage.cn/OPC/chorus/admin/common/models" + +type SysMenu struct { + MenuId int `json:"menuId" gorm:"primaryKey;autoIncrement"` + MenuName string `json:"menuName" gorm:"size:128;"` + Title string `json:"title" gorm:"size:128;"` + Icon string `json:"icon" gorm:"size:128;"` + Path string `json:"path" gorm:"size:128;"` + Paths string `json:"paths" gorm:"size:128;"` + MenuType string `json:"menuType" gorm:"size:1;"` + Action string `json:"action" gorm:"size:16;"` + Permission string `json:"permission" gorm:"size:255;"` + ParentId int `json:"parentId" gorm:"size:11;"` + NoCache bool `json:"noCache" gorm:"size:8;"` + Breadcrumb string `json:"breadcrumb" gorm:"size:255;"` + Component string `json:"component" gorm:"size:255;"` + Sort int `json:"sort" gorm:"size:4;"` + Visible string `json:"visible" gorm:"size:1;"` + IsFrame string `json:"isFrame" gorm:"size:1;DEFAULT:0;"` + SysApi []SysApi `json:"sysApi" gorm:"many2many:sys_menu_api_rule"` + Apis []int `json:"apis" gorm:"-"` + DataScope string `json:"dataScope" gorm:"-"` + Params string `json:"params" gorm:"-"` + RoleId int `gorm:"-"` + Children []SysMenu `json:"children,omitempty" gorm:"-"` + IsSelect bool `json:"is_select" gorm:"-"` + models.ControlBy + models.ModelTime +} + +type SysMenuSlice []SysMenu + +func (x SysMenuSlice) Len() int { return len(x) } +func (x SysMenuSlice) Less(i, j int) bool { return x[i].Sort < x[j].Sort } +func (x SysMenuSlice) Swap(i, j int) { x[i], x[j] = x[j], x[i] } + +func (*SysMenu) TableName() string { + return "sys_menu" +} + +func (e *SysMenu) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysMenu) GetId() interface{} { + return e.MenuId +} diff --git a/admin/app/admin/models/sys_opera_log.go b/admin/app/admin/models/sys_opera_log.go new file mode 100644 index 0000000..cbead67 --- /dev/null +++ b/admin/app/admin/models/sys_opera_log.go @@ -0,0 +1,88 @@ +package models + +import ( + "encoding/json" + "errors" + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/storage" + + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysOperaLog struct { + models.Model + Title string `json:"title" gorm:"size:255;comment:操作模块"` + BusinessType string `json:"businessType" gorm:"size:128;comment:操作类型"` + BusinessTypes string `json:"businessTypes" gorm:"size:128;comment:BusinessTypes"` + Method string `json:"method" gorm:"size:128;comment:函数"` + RequestMethod string `json:"requestMethod" gorm:"size:128;comment:请求方式 GET POST PUT DELETE"` + OperatorType string `json:"operatorType" gorm:"size:128;comment:操作类型"` + OperName string `json:"operName" gorm:"size:128;comment:操作者"` + DeptName string `json:"deptName" gorm:"size:128;comment:部门名称"` + OperUrl string `json:"operUrl" gorm:"size:255;comment:访问地址"` + OperIp string `json:"operIp" gorm:"size:128;comment:客户端ip"` + OperLocation string `json:"operLocation" gorm:"size:128;comment:访问位置"` + OperParam string `json:"operParam" gorm:"text;comment:请求参数"` + Status string `json:"status" gorm:"size:4;comment:操作状态 1:正常 2:关闭"` + OperTime time.Time `json:"operTime" gorm:"comment:操作时间"` + JsonResult string `json:"jsonResult" gorm:"size:255;comment:返回数据"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + LatencyTime string `json:"latencyTime" gorm:"size:128;comment:耗时"` + UserAgent string `json:"userAgent" gorm:"size:255;comment:ua"` + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + models.ControlBy +} + +func (*SysOperaLog) TableName() string { + return "sys_opera_log" +} + +func (e *SysOperaLog) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysOperaLog) GetId() interface{} { + return e.Id +} + +// SaveOperaLog 从队列中获取操作日志 +func SaveOperaLog(message storage.Messager) (err error) { + //准备db + db := sdk.Runtime.GetDbByKey(message.GetPrefix()) + if db == nil { + err = errors.New("db not exist") + log.Errorf("host[%s]'s %s", message.GetPrefix(), err.Error()) + // Log writing to the database ignores error + return nil + } + var rb []byte + rb, err = json.Marshal(message.GetValues()) + if err != nil { + log.Errorf("json Marshal error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + var l SysOperaLog + err = json.Unmarshal(rb, &l) + if err != nil { + log.Errorf("json Unmarshal error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + // 超出100个字符返回值截断 + if len(l.JsonResult) > 100 { + l.JsonResult = l.JsonResult[:100] + } + err = db.Create(&l).Error + if err != nil { + log.Errorf("db create error, %s", err.Error()) + // Log writing to the database ignores error + return nil + } + return nil +} diff --git a/admin/app/admin/models/sys_post.go b/admin/app/admin/models/sys_post.go new file mode 100644 index 0000000..7cce492 --- /dev/null +++ b/admin/app/admin/models/sys_post.go @@ -0,0 +1,30 @@ +package models + +import "git.ilapage.cn/OPC/chorus/admin/common/models" + +type SysPost struct { + PostId int `gorm:"primaryKey;autoIncrement" json:"postId"` //岗位编号 + PostName string `gorm:"size:128;" json:"postName"` //岗位名称 + PostCode string `gorm:"size:128;" json:"postCode"` //岗位代码 + Sort int `gorm:"size:4;" json:"sort"` //岗位排序 + Status int `gorm:"size:4;" json:"status"` //状态 + Remark string `gorm:"size:255;" json:"remark"` //描述 + models.ControlBy + models.ModelTime + + DataScope string `gorm:"-" json:"dataScope"` + Params string `gorm:"-" json:"params"` +} + +func (*SysPost) TableName() string { + return "sys_post" +} + +func (e *SysPost) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysPost) GetId() interface{} { + return e.PostId +} diff --git a/admin/app/admin/models/sys_role.go b/admin/app/admin/models/sys_role.go new file mode 100644 index 0000000..f00e534 --- /dev/null +++ b/admin/app/admin/models/sys_role.go @@ -0,0 +1,35 @@ +package models + +import "git.ilapage.cn/OPC/chorus/admin/common/models" + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // 状态 1禁用 2正常 + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + Params string `json:"params" gorm:"-"` + MenuIds []int `json:"menuIds" gorm:"-"` + DeptIds []int `json:"deptIds" gorm:"-"` + SysDept []SysDept `json:"sysDept" gorm:"many2many:sys_role_dept;foreignKey:RoleId;joinForeignKey:role_id;references:DeptId;joinReferences:dept_id;"` + SysMenu *[]SysMenu `json:"sysMenu" gorm:"many2many:sys_role_menu;foreignKey:RoleId;joinForeignKey:role_id;references:MenuId;joinReferences:menu_id;"` + models.ControlBy + models.ModelTime +} + +func (*SysRole) TableName() string { + return "sys_role" +} + +func (e *SysRole) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysRole) GetId() interface{} { + return e.RoleId +} diff --git a/admin/app/admin/models/sys_user.go b/admin/app/admin/models/sys_user.go new file mode 100644 index 0000000..261ace5 --- /dev/null +++ b/admin/app/admin/models/sys_user.go @@ -0,0 +1,77 @@ +package models + +import ( + "git.ilapage.cn/OPC/chorus/admin/common/models" + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type SysUser struct { + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"size:64;comment:用户名"` + Password string `json:"-" gorm:"size:128;comment:密码"` + NickName string `json:"nickName" gorm:"size:128;comment:昵称"` + Phone string `json:"phone" gorm:"size:11;comment:手机号"` + RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"` + Salt string `json:"-" gorm:"size:255;comment:加盐"` + Avatar string `json:"avatar" gorm:"size:255;comment:头像"` + Sex string `json:"sex" gorm:"size:255;comment:性别"` + Email string `json:"email" gorm:"size:128;comment:邮箱"` + DeptId int `json:"deptId" gorm:"size:20;comment:部门"` + PostId int `json:"postId" gorm:"size:20;comment:岗位"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Status string `json:"status" gorm:"size:4;comment:状态"` + DeptIds []int `json:"deptIds" gorm:"-"` + PostIds []int `json:"postIds" gorm:"-"` + RoleIds []int `json:"roleIds" gorm:"-"` + Dept *SysDept `json:"dept"` + models.ControlBy + models.ModelTime +} + +func (*SysUser) TableName() string { + return "sys_user" +} + +func (e *SysUser) Generate() models.ActiveRecord { + o := *e + return &o +} + +func (e *SysUser) GetId() interface{} { + return e.UserId +} + +// Encrypt 加密 +func (e *SysUser) Encrypt() (err error) { + if e.Password == "" { + return + } + + var hash []byte + if hash, err = bcrypt.GenerateFromPassword([]byte(e.Password), bcrypt.DefaultCost); err != nil { + return + } else { + e.Password = string(hash) + return + } +} + +func (e *SysUser) BeforeCreate(_ *gorm.DB) error { + return e.Encrypt() +} + +func (e *SysUser) BeforeUpdate(_ *gorm.DB) error { + var err error + if e.Password != "" { + err = e.Encrypt() + } + return err +} + +func (e *SysUser) AfterFind(_ *gorm.DB) error { + e.DeptIds = []int{e.DeptId} + e.PostIds = []int{e.PostId} + e.RoleIds = []int{e.RoleId} + return nil +} diff --git a/admin/app/admin/router/init_router.go b/admin/app/admin/router/init_router.go new file mode 100644 index 0000000..52d299c --- /dev/null +++ b/admin/app/admin/router/init_router.go @@ -0,0 +1,39 @@ +package router + +import ( + "os" + + "git.ilapage.cn/OPC/chorus/admin/app/chorus" + common "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" +) + +// InitRouter 路由初始化,不要怀疑,这里用到了 +func InitRouter() { + var r *gin.Engine + h := sdk.Runtime.GetEngine() + if h == nil { + log.Fatal("not found engine...") + os.Exit(-1) + } + switch h.(type) { + case *gin.Engine: + r = h.(*gin.Engine) + default: + log.Fatal("not support other engine") + os.Exit(-1) + } + + // the jwt middleware + authMiddleware, err := common.AuthInit() + if err != nil { + log.Fatalf("JWT Init Error, %s", err.Error()) + } + + // 注册系统路由 + InitSysRouter(r, authMiddleware) + chorus.Register(r.Group("/api/v1"), authMiddleware) + +} diff --git a/admin/app/admin/router/router.go b/admin/app/admin/router/router.go new file mode 100644 index 0000000..d2c4581 --- /dev/null +++ b/admin/app/admin/router/router.go @@ -0,0 +1,41 @@ +package router + +import ( + "github.com/gin-gonic/gin" + _ "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +var ( + routerNoCheckRole = make([]func(*gin.RouterGroup), 0) + routerCheckRole = make([]func(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware), 0) +) + +func InitExamplesRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.Engine { + + // 无需认证的路由 + examplesNoCheckRoleRouter(r) + // 需要认证的路由 + examplesCheckRoleRouter(r, authMiddleware) + + return r +} + +// 无需认证的路由示例 +func examplesNoCheckRoleRouter(r *gin.Engine) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + for _, f := range routerNoCheckRole { + f(v1) + } +} + +// 需要认证的路由示例 +func examplesCheckRoleRouter(r *gin.Engine, authMiddleware *jwtauth.GinJWTMiddleware) { + // 可根据业务需求来设置接口版本 + v1 := r.Group("/api/v1") + for _, f := range routerCheckRole { + f(v1, authMiddleware) + } +} diff --git a/admin/app/admin/router/sys_api.go b/admin/app/admin/router/sys_api.go new file mode 100644 index 0000000..1e7eb39 --- /dev/null +++ b/admin/app/admin/router/sys_api.go @@ -0,0 +1,24 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysApiRouter) +} + +// registerSysApiRouter +func registerSysApiRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysApi{} + r := v1.Group("/sys-api").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.PUT("/:id", api.Update) + } +} diff --git a/admin/app/admin/router/sys_config.go b/admin/app/admin/router/sys_config.go new file mode 100644 index 0000000..fdfca6f --- /dev/null +++ b/admin/app/admin/router/sys_config.go @@ -0,0 +1,43 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysConfigRouter) +} + +// 需认证的路由代码 +func registerSysConfigRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysConfig{} + r := v1.Group("/config").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("/configKey").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/:configKey", api.GetSysConfigByKEYForService) + } + + r2 := v1.Group("/app-config") + { + r2.GET("", api.Get2SysApp) + } + + r3 := v1.Group("/set-config").Use(authMiddleware.MiddlewareFunc()) + { + r3.PUT("", api.Update2Set) + r3.GET("", api.Get2Set) + } + +} diff --git a/admin/app/admin/router/sys_dept.go b/admin/app/admin/router/sys_dept.go new file mode 100644 index 0000000..051715e --- /dev/null +++ b/admin/app/admin/router/sys_dept.go @@ -0,0 +1,32 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysDeptRouter) +} + +// 需认证的路由代码 +func registerSysDeptRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysDept{} + + r := v1.Group("/dept").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/deptTree", api.Get2Tree) + } + +} diff --git a/admin/app/admin/router/sys_dict.go b/admin/app/admin/router/sys_dict.go new file mode 100644 index 0000000..935ca90 --- /dev/null +++ b/admin/app/admin/router/sys_dict.go @@ -0,0 +1,37 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerDictRouter) +} + +func registerDictRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + dictApi := apis.SysDictType{} + dataApi := apis.SysDictData{} + dicts := v1.Group("/dict").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + + dicts.GET("/data", dataApi.GetPage) + dicts.GET("/data/:dictCode", dataApi.Get) + dicts.POST("/data", dataApi.Insert) + dicts.PUT("/data/:dictCode", dataApi.Update) + dicts.DELETE("/data", dataApi.Delete) + + dicts.GET("/type-option-select", dictApi.GetAll) + dicts.GET("/type", dictApi.GetPage) + dicts.GET("/type/:id", dictApi.Get) + dicts.POST("/type", dictApi.Insert) + dicts.PUT("/type/:id", dictApi.Update) + dicts.DELETE("/type", dictApi.Delete) + } + opSelect := v1.Group("/dict-data").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + opSelect.GET("/option-select", dataApi.GetAll) + } +} diff --git a/admin/app/admin/router/sys_login_log.go b/admin/app/admin/router/sys_login_log.go new file mode 100644 index 0000000..5691178 --- /dev/null +++ b/admin/app/admin/router/sys_login_log.go @@ -0,0 +1,24 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysLoginLogRouter) +} + +// 需认证的路由代码 +func registerSysLoginLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysLoginLog{} + + r := v1.Group("/sys-login-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.DELETE("", api.Delete) + } +} diff --git a/admin/app/admin/router/sys_menu.go b/admin/app/admin/router/sys_menu.go new file mode 100644 index 0000000..93acc7e --- /dev/null +++ b/admin/app/admin/router/sys_menu.go @@ -0,0 +1,33 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysMenuRouter) +} + +// 需认证的路由代码 +func registerSysMenuRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysMenu{} + + r := v1.Group("/menu").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.GET("/menurole", api.GetMenuRole) + //r1.GET("/menuids", api.GetMenuIDS) + } + +} diff --git a/admin/app/admin/router/sys_opera_log.go b/admin/app/admin/router/sys_opera_log.go new file mode 100644 index 0000000..4f54b07 --- /dev/null +++ b/admin/app/admin/router/sys_opera_log.go @@ -0,0 +1,23 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysOperaLogRouter) +} + +// 需认证的路由代码 +func registerSysOperaLogRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysOperaLog{} + r := v1.Group("/sys-opera-log").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.DELETE("", api.Delete) + } +} diff --git a/admin/app/admin/router/sys_post.go b/admin/app/admin/router/sys_post.go new file mode 100644 index 0000000..b12cad5 --- /dev/null +++ b/admin/app/admin/router/sys_post.go @@ -0,0 +1,25 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSyPostRouter) +} + +// 需认证的路由代码 +func registerSyPostRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysPost{} + r := v1.Group("/post").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } +} diff --git a/admin/app/admin/router/sys_role.go b/admin/app/admin/router/sys_role.go new file mode 100644 index 0000000..20ca574 --- /dev/null +++ b/admin/app/admin/router/sys_role.go @@ -0,0 +1,31 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysRoleRouter) +} + +// 需认证的路由代码 +func registerSysRoleRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysRole{} + r := v1.Group("/role").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("/:id", api.Update) + r.DELETE("", api.Delete) + } + r1 := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + r1.PUT("/role-status", api.Update2Status) + r1.PUT("/roledatascope", api.Update2DataScope) + } +} diff --git a/admin/app/admin/router/sys_router.go b/admin/app/admin/router/sys_router.go new file mode 100644 index 0000000..ccff9a2 --- /dev/null +++ b/admin/app/admin/router/sys_router.go @@ -0,0 +1,44 @@ +package router + +import ( + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "git.ilapage.cn/OPC/chorus/admin/common/middleware/handler" +) + +func InitSysRouter(r *gin.Engine, authMiddleware *jwt.GinJWTMiddleware) *gin.RouterGroup { + g := r.Group("") + sysBaseRouter(g) + // 需要认证 + sysCheckRoleRouterInit(g, authMiddleware) + return g +} + +func sysBaseRouter(r *gin.RouterGroup) { + r.GET("/info", handler.Ping) +} + +func sysCheckRoleRouterInit(r *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + v1 := r.Group("/api/v1") + { + v1.POST("/login", authMiddleware.LoginHandler) + // Refresh time can be longer than token timeout + v1.GET("/refresh_token", authMiddleware.RefreshHandler) + } + registerBaseRouter(v1, authMiddleware) +} + +func registerBaseRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysMenu{} + api2 := apis.SysDept{} + v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()) + { + v1auth.GET("/roleMenuTreeselect/:roleId", api.GetMenuTreeSelect) + //v1.GET("/menuTreeselect", api.GetMenuTreeSelect) + v1auth.GET("/roleDeptTreeselect/:roleId", api2.GetDeptTreeRoleSelect) + v1auth.POST("/logout", handler.LogOut) + } +} diff --git a/admin/app/admin/router/sys_user.go b/admin/app/admin/router/sys_user.go new file mode 100644 index 0000000..3520cb9 --- /dev/null +++ b/admin/app/admin/router/sys_user.go @@ -0,0 +1,39 @@ +package router + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/apis" + "git.ilapage.cn/OPC/chorus/admin/common/actions" + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +func init() { + routerCheckRole = append(routerCheckRole, registerSysUserRouter) +} + +// 需认证的路由代码 +func registerSysUserRouter(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + api := apis.SysUser{} + r := v1.Group("/sys-user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + { + r.GET("", api.GetPage) + r.GET("/:id", api.Get) + r.POST("", api.Insert) + r.PUT("", api.Update) + r.DELETE("", api.Delete) + } + + user := v1.Group("/user").Use(authMiddleware.MiddlewareFunc()).Use(middleware.AuthCheckRole()).Use(actions.PermissionAction()) + { + user.GET("/profile", api.GetProfile) + user.POST("/avatar", api.InsetAvatar) + user.PUT("/pwd/set", api.UpdatePwd) + user.PUT("/pwd/reset", api.ResetPwd) + user.PUT("/status", api.UpdateStatus) + } + v1auth := v1.Group("").Use(authMiddleware.MiddlewareFunc()) + { + v1auth.GET("/getinfo", api.GetInfo) + } +} diff --git a/admin/app/admin/service/dto/sys_api.go b/admin/app/admin/service/dto/sys_api.go new file mode 100644 index 0000000..bdc79e8 --- /dev/null +++ b/admin/app/admin/service/dto/sys_api.go @@ -0,0 +1,95 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// SysApiGetPageReq 功能列表请求参数 +type SysApiGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_api" comment:"标题"` + Path string `form:"path" search:"type:contains;column:path;table:sys_api" comment:"地址"` + Action string `form:"action" search:"type:exact;column:action;table:sys_api" comment:"请求方式"` + ParentId string `form:"parentId" search:"type:exact;column:parent_id;table:sys_api" comment:"按钮id"` + Type string `form:"type" search:"-" comment:"类型"` + SysApiOrder +} + +type SysApiOrder struct { + TitleOrder string `search:"type:order;column:title;table:sys_api" form:"titleOrder"` + PathOrder string `search:"type:order;column:path;table:sys_api" form:"pathOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_api" form:"createdAtOrder"` +} + +func (m *SysApiGetPageReq) GetNeedSearch() interface{} { + return *m +} + +// SysApiInsertReq 功能创建请求参数 +type SysApiInsertReq struct { + Id int `json:"-" comment:"编码"` // 编码 + Handle string `json:"handle" comment:"handle"` + Title string `json:"title" comment:"标题"` + Path string `json:"path" comment:"地址"` + Type string `json:"type" comment:""` + Action string `json:"action" comment:"类型"` + common.ControlBy +} + +func (s *SysApiInsertReq) Generate(model *models.SysApi) { + model.Handle = s.Handle + model.Title = s.Title + model.Path = s.Path + model.Type = s.Type + model.Action = s.Action +} + +func (s *SysApiInsertReq) GetId() interface{} { + return s.Id +} + +// SysApiUpdateReq 功能更新请求参数 +type SysApiUpdateReq struct { + Id int `uri:"id" comment:"编码"` // 编码 + Handle string `json:"handle" comment:"handle"` + Title string `json:"title" comment:"标题"` + Path string `json:"path" comment:"地址"` + Type string `json:"type" comment:""` + Action string `json:"action" comment:"类型"` + common.ControlBy +} + +func (s *SysApiUpdateReq) Generate(model *models.SysApi) { + if s.Id != 0 { + model.Id = s.Id + } + model.Handle = s.Handle + model.Title = s.Title + model.Path = s.Path + model.Type = s.Type + model.Action = s.Action +} + +func (s *SysApiUpdateReq) GetId() interface{} { + return s.Id +} + +// SysApiGetReq 功能获取请求参数 +type SysApiGetReq struct { + Id int `uri:"id"` +} + +func (s *SysApiGetReq) GetId() interface{} { + return s.Id +} + +// SysApiDeleteReq 功能删除请求参数 +type SysApiDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysApiDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_config.go b/admin/app/admin/service/dto/sys_config.go new file mode 100644 index 0000000..403bd65 --- /dev/null +++ b/admin/app/admin/service/dto/sys_config.go @@ -0,0 +1,112 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// SysConfigGetPageReq 列表或者搜索使用结构体 +type SysConfigGetPageReq struct { + dto.Pagination `search:"-"` + ConfigName string `form:"configName" search:"type:contains;column:config_name;table:sys_config"` + ConfigKey string `form:"configKey" search:"type:contains;column:config_key;table:sys_config"` + ConfigType string `form:"configType" search:"type:exact;column:config_type;table:sys_config"` + IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"` + SysConfigOrder +} + +type SysConfigOrder struct { + IdOrder string `search:"type:order;column:id;table:sys_config" form:"idOrder"` + ConfigNameOrder string `search:"type:order;column:config_name;table:sys_config" form:"configNameOrder"` + ConfigKeyOrder string `search:"type:order;column:config_key;table:sys_config" form:"configKeyOrder"` + ConfigTypeOrder string `search:"type:order;column:config_type;table:sys_config" form:"configTypeOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_config" form:"createdAtOrder"` +} + +func (m *SysConfigGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysConfigGetToSysAppReq struct { + IsFrontend string `form:"isFrontend" search:"type:exact;column:is_frontend;table:sys_config"` +} + +func (m *SysConfigGetToSysAppReq) GetNeedSearch() interface{} { + return *m +} + +// SysConfigControl 增、改使用的结构体 +type SysConfigControl struct { + Id int `uri:"Id" comment:"编码"` // 编码 + ConfigName string `json:"configName" comment:""` + ConfigKey string `uri:"configKey" json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` + ConfigType string `json:"configType" comment:""` + IsFrontend string `json:"isFrontend"` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型 +func (s *SysConfigControl) Generate(model *models.SysConfig) { + if s.Id == 0 { + model.Model = common.Model{Id: s.Id} + } + model.ConfigName = s.ConfigName + model.ConfigKey = s.ConfigKey + model.ConfigValue = s.ConfigValue + model.ConfigType = s.ConfigType + model.IsFrontend = s.IsFrontend + model.Remark = s.Remark + +} + +// GetId 获取数据对应的ID +func (s *SysConfigControl) GetId() interface{} { + return s.Id +} + +// GetSetSysConfigReq 增、改使用的结构体 +type GetSetSysConfigReq struct { + ConfigKey string `json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` +} + +// Generate 结构体数据转化 从 SysConfigControl 至 system.SysConfig 对应的模型 +func (s *GetSetSysConfigReq) Generate(model *models.SysConfig) { + model.ConfigValue = s.ConfigValue +} + +type UpdateSetSysConfigReq map[string]string + +// SysConfigByKeyReq 根据Key获取配置 +type SysConfigByKeyReq struct { + ConfigKey string `uri:"configKey" search:"type:contains;column:config_key;table:sys_config"` +} + +func (m *SysConfigByKeyReq) GetNeedSearch() interface{} { + return *m +} + +type GetSysConfigByKEYForServiceResp struct { + ConfigKey string `json:"configKey" comment:""` + ConfigValue string `json:"configValue" comment:""` +} + +type SysConfigGetReq struct { + Id int `uri:"id"` +} + +func (s *SysConfigGetReq) GetId() interface{} { + return s.Id +} + +type SysConfigDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysConfigDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_dept.go b/admin/app/admin/service/dto/sys_dept.go new file mode 100644 index 0000000..aaaffb7 --- /dev/null +++ b/admin/app/admin/service/dto/sys_dept.go @@ -0,0 +1,110 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// SysDeptGetPageReq 列表或者搜索使用结构体 +type SysDeptGetPageReq struct { + DeptId int `form:"deptId" search:"type:exact;column:dept_id;table:sys_dept" comment:"id"` //id + ParentId int `form:"parentId" search:"type:exact;column:parent_id;table:sys_dept" comment:"上级部门"` //上级部门 + DeptPath string `form:"deptPath" search:"type:exact;column:dept_path;table:sys_dept" comment:""` //路径 + DeptName string `form:"deptName" search:"type:exact;column:dept_name;table:sys_dept" comment:"部门名称"` //部门名称 + Sort int `form:"sort" search:"type:exact;column:sort;table:sys_dept" comment:"排序"` //排序 + Leader string `form:"leader" search:"type:exact;column:leader;table:sys_dept" comment:"负责人"` //负责人 + Phone string `form:"phone" search:"type:exact;column:phone;table:sys_dept" comment:"手机"` //手机 + Email string `form:"email" search:"type:exact;column:email;table:sys_dept" comment:"邮箱"` //邮箱 + Status string `form:"status" search:"type:exact;column:status;table:sys_dept" comment:"状态"` //状态 +} + +func (m *SysDeptGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDeptInsertReq struct { + DeptId int `uri:"id" comment:"编码"` // 编码 + ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门 + DeptPath string `json:"deptPath" comment:""` //路径 + DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称 + Sort int `json:"sort" comment:"排序" vd:"?"` //排序 + Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人 + Phone string `json:"phone" comment:"手机" vd:"?"` //手机 + Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱 + Status int `json:"status" comment:"状态" vd:"$>0"` //状态 + common.ControlBy +} + +func (s *SysDeptInsertReq) Generate(model *models.SysDept) { + if s.DeptId != 0 { + model.DeptId = s.DeptId + } + model.DeptName = s.DeptName + model.ParentId = s.ParentId + model.DeptPath = s.DeptPath + model.Sort = s.Sort + model.Leader = s.Leader + model.Phone = s.Phone + model.Email = s.Email + model.Status = s.Status +} + +// GetId 获取数据对应的ID +func (s *SysDeptInsertReq) GetId() interface{} { + return s.DeptId +} + +type SysDeptUpdateReq struct { + DeptId int `uri:"id" comment:"编码"` // 编码 + ParentId int `json:"parentId" comment:"上级部门" vd:"?"` //上级部门 + DeptPath string `json:"deptPath" comment:""` //路径 + DeptName string `json:"deptName" comment:"部门名称" vd:"len($)>0"` //部门名称 + Sort int `json:"sort" comment:"排序" vd:"?"` //排序 + Leader string `json:"leader" comment:"负责人" vd:"@:len($)>0; msg:'leader不能为空'"` //负责人 + Phone string `json:"phone" comment:"手机" vd:"?"` //手机 + Email string `json:"email" comment:"邮箱" vd:"?"` //邮箱 + Status int `json:"status" comment:"状态" vd:"$>0"` //状态 + common.ControlBy +} + +// Generate 结构体数据转化 从 SysDeptControl 至 SysDept 对应的模型 +func (s *SysDeptUpdateReq) Generate(model *models.SysDept) { + if s.DeptId != 0 { + model.DeptId = s.DeptId + } + model.DeptName = s.DeptName + model.ParentId = s.ParentId + model.DeptPath = s.DeptPath + model.Sort = s.Sort + model.Leader = s.Leader + model.Phone = s.Phone + model.Email = s.Email + model.Status = s.Status +} + +// GetId 获取数据对应的ID +func (s *SysDeptUpdateReq) GetId() interface{} { + return s.DeptId +} + +type SysDeptGetReq struct { + Id int `uri:"id"` +} + +func (s *SysDeptGetReq) GetId() interface{} { + return s.Id +} + +type SysDeptDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysDeptDeleteReq) GetId() interface{} { + return s.Ids +} + +type DeptLabel struct { + Id int `gorm:"-" json:"id"` + Label string `gorm:"-" json:"label"` + Children []DeptLabel `gorm:"-" json:"children"` +} diff --git a/admin/app/admin/service/dto/sys_dict_data.go b/admin/app/admin/service/dto/sys_dict_data.go new file mode 100644 index 0000000..57b1f62 --- /dev/null +++ b/admin/app/admin/service/dto/sys_dict_data.go @@ -0,0 +1,108 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysDictDataGetPageReq struct { + dto.Pagination `search:"-"` + Id int `form:"id" search:"type:exact;column:dict_code;table:sys_dict_data" comment:""` + DictLabel string `form:"dictLabel" search:"type:contains;column:dict_label;table:sys_dict_data" comment:""` + DictValue string `form:"dictValue" search:"type:contains;column:dict_value;table:sys_dict_data" comment:""` + DictType string `form:"dictType" search:"type:contains;column:dict_type;table:sys_dict_data" comment:""` + Status string `form:"status" search:"type:exact;column:status;table:sys_dict_data" comment:""` +} + +func (m *SysDictDataGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDictDataGetAllResp struct { + DictLabel string `json:"label"` + DictValue string `json:"value"` +} + +type SysDictDataInsertReq struct { + Id int `json:"-" comment:""` + DictSort int `json:"dictSort" comment:""` + DictLabel string `json:"dictLabel" comment:""` + DictValue string `json:"dictValue" comment:""` + DictType string `json:"dictType" comment:""` + CssClass string `json:"cssClass" comment:""` + ListClass string `json:"listClass" comment:""` + IsDefault string `json:"isDefault" comment:""` + Status int `json:"status" comment:""` + Default string `json:"default" comment:""` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +func (s *SysDictDataInsertReq) Generate(model *models.SysDictData) { + model.DictCode = s.Id + model.DictSort = s.DictSort + model.DictLabel = s.DictLabel + model.DictValue = s.DictValue + model.DictType = s.DictType + model.CssClass = s.CssClass + model.ListClass = s.ListClass + model.IsDefault = s.IsDefault + model.Status = s.Status + model.Default = s.Default + model.Remark = s.Remark +} + +func (s *SysDictDataInsertReq) GetId() interface{} { + return s.Id +} + +type SysDictDataUpdateReq struct { + Id int `uri:"dictCode" comment:""` + DictSort int `json:"dictSort" comment:""` + DictLabel string `json:"dictLabel" comment:""` + DictValue string `json:"dictValue" comment:""` + DictType string `json:"dictType" comment:""` + CssClass string `json:"cssClass" comment:""` + ListClass string `json:"listClass" comment:""` + IsDefault string `json:"isDefault" comment:""` + Status int `json:"status" comment:""` + Default string `json:"default" comment:""` + Remark string `json:"remark" comment:""` + common.ControlBy +} + +func (s *SysDictDataUpdateReq) Generate(model *models.SysDictData) { + model.DictCode = s.Id + model.DictSort = s.DictSort + model.DictLabel = s.DictLabel + model.DictValue = s.DictValue + model.DictType = s.DictType + model.CssClass = s.CssClass + model.ListClass = s.ListClass + model.IsDefault = s.IsDefault + model.Status = s.Status + model.Default = s.Default + model.Remark = s.Remark +} + +func (s *SysDictDataUpdateReq) GetId() interface{} { + return s.Id +} + +type SysDictDataGetReq struct { + Id int `uri:"dictCode"` +} + +func (s *SysDictDataGetReq) GetId() interface{} { + return s.Id +} + +type SysDictDataDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy `json:"-"` +} + +func (s *SysDictDataDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_dict_type.go b/admin/app/admin/service/dto/sys_dict_type.go new file mode 100644 index 0000000..028d8ef --- /dev/null +++ b/admin/app/admin/service/dto/sys_dict_type.go @@ -0,0 +1,89 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysDictTypeGetPageReq struct { + dto.Pagination `search:"-"` + DictId []int `form:"dictId" search:"type:in;column:dict_id;table:sys_dict_type"` + DictName string `form:"dictName" search:"type:icontains;column:dict_name;table:sys_dict_type"` + DictType string `form:"dictType" search:"type:icontains;column:dict_type;table:sys_dict_type"` + Status int `form:"status" search:"type:exact;column:status;table:sys_dict_type"` +} + +type SysDictTypeOrder struct { + DictIdOrder string `search:"type:order;column:dict_id;table:sys_dict_type" form:"dictIdOrder"` +} + +func (m *SysDictTypeGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysDictTypeInsertReq struct { + Id int `uri:"id"` + DictName string `json:"dictName"` + DictType string `json:"dictType"` + Status int `json:"status"` + Remark string `json:"remark"` + common.ControlBy +} + +func (s *SysDictTypeInsertReq) Generate(model *models.SysDictType) { + if s.Id != 0 { + model.ID = s.Id + } + model.DictName = s.DictName + model.DictType = s.DictType + model.Status = s.Status + model.Remark = s.Remark + +} + +func (s *SysDictTypeInsertReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeUpdateReq struct { + Id int `uri:"id"` + DictName string `json:"dictName"` + DictType string `json:"dictType"` + Status int `json:"status"` + Remark string `json:"remark"` + common.ControlBy +} + +func (s *SysDictTypeUpdateReq) Generate(model *models.SysDictType) { + if s.Id != 0 { + model.ID = s.Id + } + model.DictName = s.DictName + model.DictType = s.DictType + model.Status = s.Status + model.Remark = s.Remark + +} + +func (s *SysDictTypeUpdateReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeGetReq struct { + Id int `uri:"id"` +} + +func (s *SysDictTypeGetReq) GetId() interface{} { + return s.Id +} + +type SysDictTypeDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysDictTypeDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_login_log.go b/admin/app/admin/service/dto/sys_login_log.go new file mode 100644 index 0000000..03fe437 --- /dev/null +++ b/admin/app/admin/service/dto/sys_login_log.go @@ -0,0 +1,57 @@ +package dto + +import ( + "time" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysLoginLogGetPageReq struct { + dto.Pagination `search:"-"` + Username string `form:"username" search:"type:exact;column:username;table:sys_login_log" comment:"用户名"` + Status string `form:"status" search:"type:exact;column:status;table:sys_login_log" comment:"状态"` + Ipaddr string `form:"ipaddr" search:"type:exact;column:ipaddr;table:sys_login_log" comment:"ip地址"` + LoginLocation string `form:"loginLocation" search:"type:exact;column:login_location;table:sys_login_log" comment:"归属地"` + BeginTime string `form:"beginTime" search:"type:gte;column:ctime;table:sys_login_log" comment:"创建时间"` + EndTime string `form:"endTime" search:"type:lte;column:ctime;table:sys_login_log" comment:"创建时间"` + SysLoginLogOrder +} + +type SysLoginLogOrder struct { + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_login_log" form:"createdAtOrder"` +} + +func (m *SysLoginLogGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysLoginLogControl struct { + ID int `uri:"Id" comment:"主键"` // 主键 + Username string `json:"username" comment:"用户名"` + Status string `json:"status" comment:"状态"` + Ipaddr string `json:"ipaddr" comment:"ip地址"` + LoginLocation string `json:"loginLocation" comment:"归属地"` + Browser string `json:"browser" comment:"浏览器"` + Os string `json:"os" comment:"系统"` + Platform string `json:"platform" comment:"固件"` + LoginTime time.Time `json:"loginTime" comment:"登录时间"` + Remark string `json:"remark" comment:"备注"` + Msg string `json:"msg" comment:"信息"` +} + +type SysLoginLogGetReq struct { + Id int `uri:"id"` +} + +func (s *SysLoginLogGetReq) GetId() interface{} { + return s.Id +} + +// SysLoginLogDeleteReq 功能删除请求参数 +type SysLoginLogDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysLoginLogDeleteReq) GetId() interface{} { + return s.Ids +} \ No newline at end of file diff --git a/admin/app/admin/service/dto/sys_menu.go b/admin/app/admin/service/dto/sys_menu.go new file mode 100644 index 0000000..05bfcc1 --- /dev/null +++ b/admin/app/admin/service/dto/sys_menu.go @@ -0,0 +1,159 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + common "git.ilapage.cn/OPC/chorus/admin/common/models" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +// SysMenuGetPageReq 列表或者搜索使用结构体 +type SysMenuGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_menu" comment:"菜单名称"` // 菜单名称 + Visible int `form:"visible" search:"type:exact;column:visible;table:sys_menu" comment:"显示状态"` // 显示状态 +} + +func (m *SysMenuGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysMenuInsertReq struct { + MenuId int `uri:"id" comment:"编码"` // 编码 + MenuName string `form:"menuName" comment:"菜单name"` //菜单name + Title string `form:"title" comment:"显示名称"` //显示名称 + Icon string `form:"icon" comment:"图标"` //图标 + Path string `form:"path" comment:"路径"` //路径 + Paths string `form:"paths" comment:"id路径"` //id路径 + MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型 + SysApi []models.SysApi `form:"sysApi"` + Apis []int `form:"apis"` + Action string `form:"action" comment:"请求方式"` //请求方式 + Permission string `form:"permission" comment:"权限编码"` //权限编码 + ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单 + NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存 + Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑 + Component string `form:"component" comment:"组件"` //组件 + Sort int `form:"sort" comment:"排序"` //排序 + Visible string `form:"visible" comment:"是否显示"` //是否显示 + IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame + common.ControlBy +} + +func (s *SysMenuInsertReq) Generate(model *models.SysMenu) { + if s.MenuId != 0 { + model.MenuId = s.MenuId + } + model.MenuName = s.MenuName + model.Title = s.Title + model.Icon = s.Icon + model.Path = s.Path + model.Paths = s.Paths + model.MenuType = s.MenuType + model.Action = s.Action + model.SysApi = s.SysApi + model.Permission = s.Permission + model.ParentId = s.ParentId + model.NoCache = s.NoCache + model.Breadcrumb = s.Breadcrumb + model.Component = s.Component + model.Sort = s.Sort + model.Visible = s.Visible + model.IsFrame = s.IsFrame + if s.CreateBy != 0 { + model.CreateBy = s.CreateBy + } + if s.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } +} + +func (s *SysMenuInsertReq) GetId() interface{} { + return s.MenuId +} + +type SysMenuUpdateReq struct { + MenuId int `uri:"id" comment:"编码"` // 编码 + MenuName string `form:"menuName" comment:"菜单name"` //菜单name + Title string `form:"title" comment:"显示名称"` //显示名称 + Icon string `form:"icon" comment:"图标"` //图标 + Path string `form:"path" comment:"路径"` //路径 + Paths string `form:"paths" comment:"id路径"` //id路径 + MenuType string `form:"menuType" comment:"菜单类型"` //菜单类型 + SysApi []models.SysApi `form:"sysApi"` + Apis []int `form:"apis"` + Action string `form:"action" comment:"请求方式"` //请求方式 + Permission string `form:"permission" comment:"权限编码"` //权限编码 + ParentId int `form:"parentId" comment:"上级菜单"` //上级菜单 + NoCache bool `form:"noCache" comment:"是否缓存"` //是否缓存 + Breadcrumb string `form:"breadcrumb" comment:"是否面包屑"` //是否面包屑 + Component string `form:"component" comment:"组件"` //组件 + Sort int `form:"sort" comment:"排序"` //排序 + Visible string `form:"visible" comment:"是否显示"` //是否显示 + IsFrame string `form:"isFrame" comment:"是否frame"` //是否frame + common.ControlBy +} + +func (s *SysMenuUpdateReq) Generate(model *models.SysMenu) { + if s.MenuId != 0 { + model.MenuId = s.MenuId + } + model.MenuName = s.MenuName + model.Title = s.Title + model.Icon = s.Icon + model.Path = s.Path + model.Paths = s.Paths + model.MenuType = s.MenuType + model.Action = s.Action + model.SysApi = s.SysApi + model.Permission = s.Permission + model.ParentId = s.ParentId + model.NoCache = s.NoCache + model.Breadcrumb = s.Breadcrumb + model.Component = s.Component + model.Sort = s.Sort + model.Visible = s.Visible + model.IsFrame = s.IsFrame + if s.CreateBy != 0 { + model.CreateBy = s.CreateBy + } + if s.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } +} + +func (s *SysMenuUpdateReq) GetId() interface{} { + return s.MenuId +} + +type SysMenuGetReq struct { + Id int `uri:"id"` +} + +func (s *SysMenuGetReq) GetId() interface{} { + return s.Id +} + +type SysMenuDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysMenuDeleteReq) GetId() interface{} { + return s.Ids +} + +type MenuLabel struct { + Id int `json:"id,omitempty" gorm:"-"` + Label string `json:"label,omitempty" gorm:"-"` + Children []MenuLabel `json:"children,omitempty" gorm:"-"` +} + +type MenuRole struct { + models.SysMenu + IsSelect bool `json:"is_select" gorm:"-"` +} + +type SelectRole struct { + RoleId int `uri:"roleId"` +} diff --git a/admin/app/admin/service/dto/sys_opera_log.go b/admin/app/admin/service/dto/sys_opera_log.go new file mode 100644 index 0000000..af3646b --- /dev/null +++ b/admin/app/admin/service/dto/sys_opera_log.go @@ -0,0 +1,102 @@ +package dto + +import ( + "time" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +const ( + OperaStatusEnabel = "1" // 状态-正常 + OperaStatusDisable = "2" // 状态-关闭 +) + +type SysOperaLogGetPageReq struct { + dto.Pagination `search:"-"` + Title string `form:"title" search:"type:contains;column:title;table:sys_opera_log" comment:"操作模块"` + Method string `form:"method" search:"type:contains;column:method;table:sys_opera_log" comment:"函数"` + RequestMethod string `form:"requestMethod" search:"type:contains;column:request_method;table:sys_opera_log" comment:"请求方式: GET POST PUT DELETE"` + OperUrl string `form:"operUrl" search:"type:contains;column:oper_url;table:sys_opera_log" comment:"访问地址"` + OperIp string `form:"operIp" search:"type:exact;column:oper_ip;table:sys_opera_log" comment:"客户端ip"` + Status int `form:"status" search:"type:exact;column:status;table:sys_opera_log" comment:"状态 1:正常 2:关闭"` + BeginTime string `form:"beginTime" search:"type:gte;column:created_at;table:sys_opera_log" comment:"创建时间"` + EndTime string `form:"endTime" search:"type:lte;column:created_at;table:sys_opera_log" comment:"更新时间"` + SysOperaLogOrder +} + +type SysOperaLogOrder struct { + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_opera_log" form:"createdAtOrder"` +} + +func (m *SysOperaLogGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysOperaLogControl struct { + ID int `uri:"Id" comment:"编码"` // 编码 + Title string `json:"title" comment:"操作模块"` + BusinessType string `json:"businessType" comment:"操作类型"` + BusinessTypes string `json:"businessTypes" comment:""` + Method string `json:"method" comment:"函数"` + RequestMethod string `json:"requestMethod" comment:"请求方式"` + OperatorType string `json:"operatorType" comment:"操作类型"` + OperName string `json:"operName" comment:"操作者"` + DeptName string `json:"deptName" comment:"部门名称"` + OperUrl string `json:"operUrl" comment:"访问地址"` + OperIp string `json:"operIp" comment:"客户端ip"` + OperLocation string `json:"operLocation" comment:"访问位置"` + OperParam string `json:"operParam" comment:"请求参数"` + Status string `json:"status" comment:"操作状态"` + OperTime time.Time `json:"operTime" comment:"操作时间"` + JsonResult string `json:"jsonResult" comment:"返回数据"` + Remark string `json:"remark" comment:"备注"` + LatencyTime string `json:"latencyTime" comment:"耗时"` + UserAgent string `json:"userAgent" comment:"ua"` +} + +func (s *SysOperaLogControl) Generate() (*models.SysOperaLog, error) { + return &models.SysOperaLog{ + Model: common.Model{Id: s.ID}, + Title: s.Title, + BusinessType: s.BusinessType, + BusinessTypes: s.BusinessTypes, + Method: s.Method, + RequestMethod: s.RequestMethod, + OperatorType: s.OperatorType, + OperName: s.OperName, + DeptName: s.DeptName, + OperUrl: s.OperUrl, + OperIp: s.OperIp, + OperLocation: s.OperLocation, + OperParam: s.OperParam, + Status: s.Status, + OperTime: s.OperTime, + JsonResult: s.JsonResult, + Remark: s.Remark, + LatencyTime: s.LatencyTime, + UserAgent: s.UserAgent, + }, nil +} + +func (s *SysOperaLogControl) GetId() interface{} { + return s.ID +} + +type SysOperaLogGetReq struct { + Id int `uri:"id"` +} + +func (s *SysOperaLogGetReq) GetId() interface{} { + return s.Id +} + +// SysOperaLogDeleteReq 功能删除请求参数 +type SysOperaLogDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysOperaLogDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_post.go b/admin/app/admin/service/dto/sys_post.go new file mode 100644 index 0000000..aff6c7a --- /dev/null +++ b/admin/app/admin/service/dto/sys_post.go @@ -0,0 +1,111 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + common "git.ilapage.cn/OPC/chorus/admin/common/models" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +// SysPostPageReq 列表或者搜索使用结构体 +type SysPostPageReq struct { + dto.Pagination `search:"-"` + PostId int `form:"postId" search:"type:exact;column:post_id;table:sys_post" comment:"id"` // id + PostName string `form:"postName" search:"type:contains;column:post_name;table:sys_post" comment:"名称"` // 名称 + PostCode string `form:"postCode" search:"type:contains;column:post_code;table:sys_post" comment:"编码"` // 编码 + Sort int `form:"sort" search:"type:exact;column:sort;table:sys_post" comment:"排序"` // 排序 + Status int `form:"status" search:"type:exact;column:status;table:sys_post" comment:"状态"` // 状态 + Remark string `form:"remark" search:"type:exact;column:remark;table:sys_post" comment:"备注"` // 备注 +} + +func (m *SysPostPageReq) GetNeedSearch() interface{} { + return *m +} + +// SysPostInsertReq 增使用的结构体 +type SysPostInsertReq struct { + PostId int `uri:"id" comment:"id"` + PostName string `form:"postName" comment:"名称"` + PostCode string `form:"postCode" comment:"编码"` + Sort int `form:"sort" comment:"排序"` + Status int `form:"status" comment:"状态"` + Remark string `form:"remark" comment:"备注"` + common.ControlBy +} + +func (s *SysPostInsertReq) Generate(model *models.SysPost) { + model.PostName = s.PostName + model.PostCode = s.PostCode + model.Sort = s.Sort + model.Status = s.Status + model.Remark = s.Remark + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +// GetId 获取数据对应的ID +func (s *SysPostInsertReq) GetId() interface{} { + return s.PostId +} + +// SysPostUpdateReq 改使用的结构体 +type SysPostUpdateReq struct { + PostId int `uri:"id" comment:"id"` + PostName string `form:"postName" comment:"名称"` + PostCode string `form:"postCode" comment:"编码"` + Sort int `form:"sort" comment:"排序"` + Status int `form:"status" comment:"状态"` + Remark string `form:"remark" comment:"备注"` + common.ControlBy +} + +func (s *SysPostUpdateReq) Generate(model *models.SysPost) { + model.PostId = s.PostId + model.PostName = s.PostName + model.PostCode = s.PostCode + model.Sort = s.Sort + model.Status = s.Status + model.Remark = s.Remark + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +func (s *SysPostUpdateReq) GetId() interface{} { + return s.PostId +} + +// SysPostGetReq 获取单个的结构体 +type SysPostGetReq struct { + Id int `uri:"id"` +} + +func (s *SysPostGetReq) GetId() interface{} { + return s.Id +} + +// SysPostDeleteReq 删除的结构体 +type SysPostDeleteReq struct { + Ids []int `json:"ids"` + common.ControlBy +} + +func (s *SysPostDeleteReq) Generate(model *models.SysPost) { + if s.ControlBy.UpdateBy != 0 { + model.UpdateBy = s.UpdateBy + } + if s.ControlBy.CreateBy != 0 { + model.CreateBy = s.CreateBy + } +} + +func (s *SysPostDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/app/admin/service/dto/sys_role.go b/admin/app/admin/service/dto/sys_role.go new file mode 100644 index 0000000..d2b44b9 --- /dev/null +++ b/admin/app/admin/service/dto/sys_role.go @@ -0,0 +1,164 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + common "git.ilapage.cn/OPC/chorus/admin/common/models" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysRoleGetPageReq struct { + dto.Pagination `search:"-"` + + RoleId int `form:"roleId" search:"type:exact;column:role_id;table:sys_role" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" search:"type:exact;column:role_name;table:sys_role" comment:"角色名称"` // 角色名称 + Status string `form:"status" search:"type:exact;column:status;table:sys_role" comment:"状态"` // 状态 + RoleKey string `form:"roleKey" search:"type:exact;column:role_key;table:sys_role" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" search:"type:exact;column:role_sort;table:sys_role" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" search:"type:exact;column:flag;table:sys_role" comment:"标记"` // 标记 + Remark string `form:"remark" search:"type:exact;column:remark;table:sys_role" comment:"备注"` // 备注 + Admin bool `form:"admin" search:"type:exact;column:admin;table:sys_role" comment:"是否管理员"` + DataScope string `form:"dataScope" search:"type:exact;column:data_scope;table:sys_role" comment:"是否管理员"` +} + +type SysRoleOrder struct { + RoleIdOrder string `search:"type:order;column:role_id;table:sys_role" form:"roleIdOrder"` + RoleNameOrder string `search:"type:order;column:role_name;table:sys_role" form:"roleNameOrder"` + RoleSortOrder string `search:"type:order;column:role_sort;table:sys_role" form:"usernameOrder"` + StatusOrder string `search:"type:order;column:status;table:sys_role" form:"statusOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_role" form:"createdAtOrder"` +} + +func (m *SysRoleGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type SysRoleInsertReq struct { + RoleId int `uri:"id" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" comment:"角色名称"` // 角色名称 + Status string `form:"status" comment:"状态"` // 状态 1禁用 2正常 + RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" comment:"标记"` // 标记 + Remark string `form:"remark" comment:"备注"` // 备注 + Admin bool `form:"admin" comment:"是否管理员"` + DataScope string `form:"dataScope"` + SysMenu []models.SysMenu `form:"sysMenu"` + MenuIds []int `form:"menuIds"` + SysDept []models.SysDept `form:"sysDept"` + DeptIds []int `form:"deptIds"` + common.ControlBy +} + +func (s *SysRoleInsertReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.RoleName = s.RoleName + model.Status = s.Status + model.RoleKey = s.RoleKey + model.RoleSort = s.RoleSort + model.Flag = s.Flag + model.Remark = s.Remark + model.Admin = s.Admin + model.DataScope = s.DataScope + model.SysMenu = &s.SysMenu + model.SysDept = s.SysDept +} + +func (s *SysRoleInsertReq) GetId() interface{} { + return s.RoleId +} + +type SysRoleUpdateReq struct { + RoleId int `uri:"id" comment:"角色编码"` // 角色编码 + RoleName string `form:"roleName" comment:"角色名称"` // 角色名称 + Status string `form:"status" comment:"状态"` // 状态 + RoleKey string `form:"roleKey" comment:"角色代码"` // 角色代码 + RoleSort int `form:"roleSort" comment:"角色排序"` // 角色排序 + Flag string `form:"flag" comment:"标记"` // 标记 + Remark string `form:"remark" comment:"备注"` // 备注 + Admin bool `form:"admin" comment:"是否管理员"` + DataScope string `form:"dataScope"` + SysMenu []models.SysMenu `form:"sysMenu"` + MenuIds []int `form:"menuIds"` + SysDept []models.SysDept `form:"sysDept"` + DeptIds []int `form:"deptIds"` + common.ControlBy +} + +func (s *SysRoleUpdateReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.RoleName = s.RoleName + model.Status = s.Status + model.RoleKey = s.RoleKey + model.RoleSort = s.RoleSort + model.Flag = s.Flag + model.Remark = s.Remark + model.Admin = s.Admin + model.DataScope = s.DataScope + model.SysMenu = &s.SysMenu + model.SysDept = s.SysDept +} + +func (s *SysRoleUpdateReq) GetId() interface{} { + return s.RoleId +} + +type UpdateStatusReq struct { + RoleId int `form:"roleId" comment:"角色编码"` // 角色编码 + Status string `form:"status" comment:"状态"` // 状态 + common.ControlBy +} + +func (s *UpdateStatusReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.Status = s.Status +} + +func (s *UpdateStatusReq) GetId() interface{} { + return s.RoleId +} + +type SysRoleByName struct { + RoleName string `form:"role"` // 角色编码 +} + +type SysRoleGetReq struct { + Id int `uri:"id"` +} + +func (s *SysRoleGetReq) GetId() interface{} { + return s.Id +} + +type SysRoleDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *SysRoleDeleteReq) GetId() interface{} { + return s.Ids +} + +// RoleDataScopeReq 角色数据权限修改 +type RoleDataScopeReq struct { + RoleId int `json:"roleId" binding:"required"` + DataScope string `json:"dataScope" binding:"required"` + DeptIds []int `json:"deptIds"` +} + +func (s *RoleDataScopeReq) Generate(model *models.SysRole) { + if s.RoleId != 0 { + model.RoleId = s.RoleId + } + model.DataScope = s.DataScope + model.DeptIds = s.DeptIds +} + +type DeptIdList struct { + DeptId int `json:"DeptId"` +} diff --git a/admin/app/admin/service/dto/sys_user.go b/admin/app/admin/service/dto/sys_user.go new file mode 100644 index 0000000..56f4565 --- /dev/null +++ b/admin/app/admin/service/dto/sys_user.go @@ -0,0 +1,189 @@ +package dto + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + common "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type SysUserGetPageReq struct { + dto.Pagination `search:"-"` + UserId int `form:"userId" search:"type:exact;column:user_id;table:sys_user" comment:"用户ID"` + Username string `form:"username" search:"type:contains;column:username;table:sys_user" comment:"用户名"` + NickName string `form:"nickName" search:"type:contains;column:nick_name;table:sys_user" comment:"昵称"` + Phone string `form:"phone" search:"type:contains;column:phone;table:sys_user" comment:"手机号"` + RoleId string `form:"roleId" search:"type:exact;column:role_id;table:sys_user" comment:"角色ID"` + Sex string `form:"sex" search:"type:exact;column:sex;table:sys_user" comment:"性别"` + Email string `form:"email" search:"type:contains;column:email;table:sys_user" comment:"邮箱"` + PostId string `form:"postId" search:"type:exact;column:post_id;table:sys_user" comment:"岗位"` + Status string `form:"status" search:"type:exact;column:status;table:sys_user" comment:"状态"` + DeptJoin `search:"type:left;on:dept_id:dept_id;table:sys_user;join:sys_dept"` + SysUserOrder +} + +type SysUserOrder struct { + UserIdOrder string `search:"type:order;column:user_id;table:sys_user" form:"userIdOrder"` + UsernameOrder string `search:"type:order;column:username;table:sys_user" form:"usernameOrder"` + StatusOrder string `search:"type:order;column:status;table:sys_user" form:"statusOrder"` + CreatedAtOrder string `search:"type:order;column:created_at;table:sys_user" form:"createdAtOrder"` +} + +type DeptJoin struct { + DeptId string `search:"type:contains;column:dept_path;table:sys_dept" form:"deptId"` +} + +func (m *SysUserGetPageReq) GetNeedSearch() interface{} { + return *m +} + +type ResetSysUserPwdReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID + Password string `json:"password" comment:"密码" vd:"len($)>0"` + common.ControlBy +} + +func (s *ResetSysUserPwdReq) GetId() interface{} { + return s.UserId +} + +func (s *ResetSysUserPwdReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Password = s.Password +} + +type UpdateSysUserAvatarReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"len($)>0"` // 用户ID + Avatar string `json:"avatar" comment:"头像" vd:"len($)>0"` + common.ControlBy +} + +func (s *UpdateSysUserAvatarReq) GetId() interface{} { + return s.UserId +} + +func (s *UpdateSysUserAvatarReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Avatar = s.Avatar +} + +type UpdateSysUserStatusReq struct { + UserId int `json:"userId" comment:"用户ID" vd:"$>0"` // 用户ID + Status string `json:"status" comment:"状态" vd:"len($)>0"` + common.ControlBy +} + +func (s *UpdateSysUserStatusReq) GetId() interface{} { + return s.UserId +} + +func (s *UpdateSysUserStatusReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Status = s.Status +} + +type SysUserInsertReq struct { + UserId int `json:"userId" comment:"用户ID"` // 用户ID + Username string `json:"username" comment:"用户名" vd:"len($)>0"` + Password string `json:"password" comment:"密码"` + NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"` + Phone string `json:"phone" comment:"手机号" vd:"len($)>0"` + RoleId int `json:"roleId" comment:"角色ID"` + Avatar string `json:"avatar" comment:"头像"` + Sex string `json:"sex" comment:"性别"` + Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"` + DeptId int `json:"deptId" comment:"部门" vd:"$>0"` + PostId int `json:"postId" comment:"岗位"` + Remark string `json:"remark" comment:"备注"` + Status string `json:"status" comment:"状态" vd:"len($)>0" default:"1"` + common.ControlBy +} + +func (s *SysUserInsertReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Username = s.Username + model.Password = s.Password + model.NickName = s.NickName + model.Phone = s.Phone + model.RoleId = s.RoleId + model.Avatar = s.Avatar + model.Sex = s.Sex + model.Email = s.Email + model.DeptId = s.DeptId + model.PostId = s.PostId + model.Remark = s.Remark + model.Status = s.Status + model.CreateBy = s.CreateBy +} + +func (s *SysUserInsertReq) GetId() interface{} { + return s.UserId +} + +type SysUserUpdateReq struct { + UserId int `json:"userId" comment:"用户ID"` // 用户ID + Username string `json:"username" comment:"用户名" vd:"len($)>0"` + NickName string `json:"nickName" comment:"昵称" vd:"len($)>0"` + Phone string `json:"phone" comment:"手机号" vd:"len($)>0"` + RoleId int `json:"roleId" comment:"角色ID"` + Avatar string `json:"avatar" comment:"头像"` + Sex string `json:"sex" comment:"性别"` + Email string `json:"email" comment:"邮箱" vd:"len($)>0,email"` + DeptId int `json:"deptId" comment:"部门" vd:"$>0"` + PostId int `json:"postId" comment:"岗位"` + Remark string `json:"remark" comment:"备注"` + Status string `json:"status" comment:"状态" default:"1"` + common.ControlBy +} + +func (s *SysUserUpdateReq) Generate(model *models.SysUser) { + if s.UserId != 0 { + model.UserId = s.UserId + } + model.Username = s.Username + model.NickName = s.NickName + model.Phone = s.Phone + model.RoleId = s.RoleId + model.Avatar = s.Avatar + model.Sex = s.Sex + model.Email = s.Email + model.DeptId = s.DeptId + model.PostId = s.PostId + model.Remark = s.Remark + model.Status = s.Status +} + +func (s *SysUserUpdateReq) GetId() interface{} { + return s.UserId +} + +type SysUserById struct { + dto.ObjectById + common.ControlBy +} + +func (s *SysUserById) GetId() interface{} { + if len(s.Ids) > 0 { + s.Ids = append(s.Ids, s.Id) + return s.Ids + } + return s.Id +} + +func (s *SysUserById) GenerateM() (common.ActiveRecord, error) { + return &models.SysUser{}, nil +} + +// PassWord 密码 +type PassWord struct { + NewPassword string `json:"newPassword" vd:"len($)>0"` + OldPassword string `json:"oldPassword" vd:"len($)>0"` +} diff --git a/admin/app/admin/service/sys_api.go b/admin/app/admin/service/sys_api.go new file mode 100644 index 0000000..35adfff --- /dev/null +++ b/admin/app/admin/service/sys_api.go @@ -0,0 +1,121 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/go-admin-team/go-admin-core/sdk/runtime" + "github.com/go-admin-team/go-admin-core/sdk/service" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + "git.ilapage.cn/OPC/chorus/admin/common/actions" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/global" +) + +type SysApi struct { + service.Service +} + +// GetPage 获取SysApi列表 +func (e *SysApi) GetPage(c *dto.SysApiGetPageReq, p *actions.DataPermission, list *[]models.SysApi, count *int64) error { + var err error + var data models.SysApi + + orm := e.Orm.Debug().Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ) + if c.Type != "" { + qType := c.Type + if qType == "暂无" { + qType = "" + } + if global.Driver == "postgres" { + orm = orm.Where("type = ?", qType) + } else { + orm = orm.Where("`type` = ?", qType) + } + + } + err = orm.Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysApiPage error:%s", err) + return err + } + return nil +} + +// Get 获取SysApi对象with id +func (e *SysApi) Get(d *dto.SysApiGetReq, p *actions.DataPermission, model *models.SysApi) *SysApi { + var data models.SysApi + err := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ). + FirstOrInit(model, d.GetId()).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if model.Id == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return e + } + return e +} + +// Update 修改SysApi对象 +func (e *SysApi) Update(c *dto.SysApiUpdateReq, p *actions.DataPermission) error { + var model = models.SysApi{} + db := e.Orm.Debug().First(&model, c.GetId()) + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + c.Generate(&model) + db = e.Orm.Save(&model) + if err := db.Error; err != nil { + e.Log.Errorf("Service UpdateSysApi error:%s", err) + return err + } + + return nil +} + +// Remove 删除SysApi +func (e *SysApi) Remove(d *dto.SysApiDeleteReq, p *actions.DataPermission) error { + var data models.SysApi + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, d.GetId()) + if err := db.Error; err != nil { + e.Log.Errorf("Service RemoveSysApi error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// CheckStorageSysApi 创建SysApi对象 +func (e *SysApi) CheckStorageSysApi(c *[]runtime.Router) error { + for _, v := range *c { + err := e.Orm.Debug().Where(models.SysApi{Path: v.RelativePath, Action: v.HttpMethod}). + Attrs(models.SysApi{Handle: v.Handler}). + FirstOrCreate(&models.SysApi{}).Error + if err != nil { + err := fmt.Errorf("Service CheckStorageSysApi error: %s \r\n ", err.Error()) + return err + } + } + return nil +} diff --git a/admin/app/admin/service/sys_config.go b/admin/app/admin/service/sys_config.go new file mode 100644 index 0000000..8bb1c01 --- /dev/null +++ b/admin/app/admin/service/sys_config.go @@ -0,0 +1,183 @@ +package service + +import ( + "errors" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysConfig struct { + service.Service +} + +// GetPage 获取SysConfig列表 +func (e *SysConfig) GetPage(c *dto.SysConfigGetPageReq, list *[]models.SysConfig, count *int64) error { + err := e.Orm. + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + return nil +} + +// Get 获取SysConfig对象 +func (e *SysConfig) Get(d *dto.SysConfigGetReq, model *models.SysConfig) error { + err := e.Orm. + FirstOrInit(model, d.GetId()). + Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return err + } + if model.Id == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return err + } + return nil +} + +// Insert 创建SysConfig对象 +func (e *SysConfig) Insert(c *dto.SysConfigControl) error { + var err error + var data models.SysConfig + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("Service InsertSysConfig error:%s", err) + return err + } + return nil +} + +// Update 修改SysConfig对象 +func (e *SysConfig) Update(c *dto.SysConfigControl) error { + var err error + var model = models.SysConfig{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(&model) + err = db.Error + if err != nil { + e.Log.Errorf("Service UpdateSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// SetSysConfig 修改SysConfig对象 +func (e *SysConfig) SetSysConfig(c *[]dto.GetSetSysConfigReq) error { + var err error + for _, req := range *c { + var model = models.SysConfig{} + e.Orm.Where("config_key = ?", req.ConfigKey).First(&model) + if model.Id != 0 { + req.Generate(&model) + db := e.Orm.Save(&model) + err = db.Error + if err != nil { + e.Log.Errorf("Service SetSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + } + } + return nil +} + +func (e *SysConfig) GetForSet(c *[]dto.GetSetSysConfigReq) error { + var err error + var data models.SysConfig + + err = e.Orm.Model(&data). + Find(c).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + return nil +} + +func (e *SysConfig) UpdateForSet(c *[]dto.GetSetSysConfigReq) error { + m := *c + for _, req := range m { + var data models.SysConfig + if err := e.Orm.Where("config_key = ?", req.ConfigKey). + First(&data).Error; err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + if data.ConfigValue != req.ConfigValue { + data.ConfigValue = req.ConfigValue + + if err := e.Orm.Save(&data).Error; err != nil { + e.Log.Errorf("Service GetSysConfigPage error:%s", err) + return err + } + } + + } + + return nil +} + +// Remove 删除SysConfig +func (e *SysConfig) Remove(d *dto.SysConfigDeleteReq) error { + var err error + var data models.SysConfig + + db := e.Orm.Delete(&data, d.Ids) + if err = db.Error; err != nil { + e.Log.Errorf("Service RemoveSysConfig error:%s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetWithKey 根据Key获取SysConfig +func (e *SysConfig) GetWithKey(c *dto.SysConfigByKeyReq, resp *dto.GetSysConfigByKEYForServiceResp) error { + var err error + var data models.SysConfig + err = e.Orm.Table(data.TableName()).Where("config_key = ?", c.ConfigKey).First(resp).Error + if err != nil { + e.Log.Errorf("At Service GetSysConfigByKEY Error:%s", err) + return err + } + + return nil +} + +func (e *SysConfig) GetWithKeyList(c *dto.SysConfigGetToSysAppReq, list *[]models.SysConfig) error { + err := e.Orm. + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("Service GetSysConfigByKey error:%s", err) + return err + } + return nil +} diff --git a/admin/app/admin/service/sys_dept.go b/admin/app/admin/service/sys_dept.go new file mode 100644 index 0000000..c25da12 --- /dev/null +++ b/admin/app/admin/service/sys_dept.go @@ -0,0 +1,294 @@ +package service + +import ( + "errors" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysDept struct { + service.Service +} + +// GetPage 获取SysDept列表 +//func (e *SysDept) GetPage(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error { +// var err error +// var data models.SysDept +// +// err = e.Orm.Model(&data). +// Scopes( +// cDto.MakeCondition(c.GetNeedSearch()), +// ). +// Find(list).Error +// if err != nil { +// e.Log.Errorf("db error:%s", err) +// return err +// } +// return nil +//} + +// Get 获取SysDept对象 +func (e *SysDept) Get(d *dto.SysDeptGetReq, model *models.SysDept) error { + var err error + var data models.SysDept + + err = e.Orm.Model(&data). + FirstOrInit(model, d.GetId()). + Error + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return err + } + if model.DeptId == 0 { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysApi error: %s", err) + _ = e.AddError(err) + return err + } + return nil +} + +// Insert 创建SysDept对象 +func (e *SysDept) Insert(c *dto.SysDeptInsertReq) error { + var err error + var data models.SysDept + c.Generate(&data) + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + err = tx.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + deptPath := pkg.IntToString(data.DeptId) + "/" + if data.ParentId != 0 { + var deptP models.SysDept + tx.First(&deptP, data.ParentId) + deptPath = deptP.DeptPath + deptPath + } else { + deptPath = "/0/" + deptPath + } + var mp = map[string]string{} + mp["dept_path"] = deptPath + if err = tx.Model(&data).Update("dept_path", deptPath).Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Update 修改SysDept对象 +func (e *SysDept) Update(c *dto.SysDeptUpdateReq) error { + var err error + var model = models.SysDept{} + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + tx.First(&model, c.GetId()) + c.Generate(&model) + + deptPath := pkg.IntToString(model.DeptId) + "/" + if model.ParentId != 0 { + var deptP models.SysDept + tx.First(&deptP, model.ParentId) + deptPath = deptP.DeptPath + deptPath + } else { + deptPath = "/0/" + deptPath + } + model.DeptPath = deptPath + db := tx.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("UpdateSysDept error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// Remove 删除SysDept +func (e *SysDept) Remove(d *dto.SysDeptDeleteReq) error { + var err error + var data models.SysDept + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetSysDeptList 获取组织数据 +func (e *SysDept) getList(c *dto.SysDeptGetPageReq, list *[]models.SysDept) error { + var err error + var data models.SysDept + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// SetDeptTree 设置组织数据 +func (e *SysDept) SetDeptTree(c *dto.SysDeptGetPageReq) (m []dto.DeptLabel, err error) { + var list []models.SysDept + err = e.getList(c, &list) + + m = make([]dto.DeptLabel, 0) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + e := dto.DeptLabel{} + e.Id = list[i].DeptId + e.Label = list[i].DeptName + deptsInfo := deptTreeCall(&list, e) + + m = append(m, deptsInfo) + } + return +} + +// Call 递归构造组织数据 +func deptTreeCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel { + list := *deptList + childrenList := make([]dto.DeptLabel, 0) + for j := 0; j < len(list); j++ { + if dept.Id != list[j].ParentId { + continue + } + mi := dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}} + ms := deptTreeCall(deptList, mi) + childrenList = append(childrenList, ms) + } + dept.Children = childrenList + return dept +} + +// SetDeptPage 设置dept页面数据 +func (e *SysDept) SetDeptPage(c *dto.SysDeptGetPageReq) (m []models.SysDept, err error) { + var list []models.SysDept + err = e.getList(c, &list) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + info := e.deptPageCall(&list, list[i]) + m = append(m, info) + } + return +} + +func (e *SysDept) deptPageCall(deptlist *[]models.SysDept, menu models.SysDept) models.SysDept { + list := *deptlist + childrenList := make([]models.SysDept, 0) + for j := 0; j < len(list); j++ { + if menu.DeptId != list[j].ParentId { + continue + } + mi := models.SysDept{} + mi.DeptId = list[j].DeptId + mi.ParentId = list[j].ParentId + mi.DeptPath = list[j].DeptPath + mi.DeptName = list[j].DeptName + mi.Sort = list[j].Sort + mi.Leader = list[j].Leader + mi.Phone = list[j].Phone + mi.Email = list[j].Email + mi.Status = list[j].Status + mi.CreatedAt = list[j].CreatedAt + mi.Children = []models.SysDept{} + ms := e.deptPageCall(deptlist, mi) + childrenList = append(childrenList, ms) + } + menu.Children = childrenList + return menu +} + +// GetWithRoleId 获取角色的部门ID集合 +func (e *SysDept) GetWithRoleId(roleId int) ([]int, error) { + deptIds := make([]int, 0) + deptList := make([]dto.DeptIdList, 0) + if err := e.Orm.Table("sys_role_dept"). + Select("sys_role_dept.dept_id"). + Joins("LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id"). + Where("role_id = ? ", roleId). + Where(" sys_role_dept.dept_id not in(select sys_dept.parent_id from sys_role_dept LEFT JOIN sys_dept on sys_dept.dept_id=sys_role_dept.dept_id where role_id =? )", roleId). + Find(&deptList).Error; err != nil { + return nil, err + } + for i := 0; i < len(deptList); i++ { + deptIds = append(deptIds, deptList[i].DeptId) + } + return deptIds, nil +} + +func (e *SysDept) SetDeptLabel() (m []dto.DeptLabel, err error) { + list := make([]models.SysDept, 0) + err = e.Orm.Find(&list).Error + if err != nil { + log.Error("find dept list error, %s", err.Error()) + return + } + m = make([]dto.DeptLabel, 0) + var item dto.DeptLabel + for i := range list { + if list[i].ParentId != 0 { + continue + } + item = dto.DeptLabel{} + item.Id = list[i].DeptId + item.Label = list[i].DeptName + deptInfo := deptLabelCall(&list, item) + m = append(m, deptInfo) + } + return +} + +// deptLabelCall +func deptLabelCall(deptList *[]models.SysDept, dept dto.DeptLabel) dto.DeptLabel { + list := *deptList + var mi dto.DeptLabel + childrenList := make([]dto.DeptLabel, 0) + for j := 0; j < len(list); j++ { + if dept.Id != list[j].ParentId { + continue + } + mi = dto.DeptLabel{Id: list[j].DeptId, Label: list[j].DeptName, Children: []dto.DeptLabel{}} + ms := deptLabelCall(deptList, mi) + childrenList = append(childrenList, ms) + } + dept.Children = childrenList + return dept +} diff --git a/admin/app/admin/service/sys_dict_data.go b/admin/app/admin/service/sys_dict_data.go new file mode 100644 index 0000000..fb19e13 --- /dev/null +++ b/admin/app/admin/service/sys_dict_data.go @@ -0,0 +1,120 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysDictData struct { + service.Service +} + +// GetPage 获取列表 +func (e *SysDictData) GetPage(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData, count *int64) error { + var err error + var data models.SysDictData + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取对象 +func (e *SysDictData) Get(d *dto.SysDictDataGetReq, model *models.SysDictData) error { + var err error + var data models.SysDictData + + db := e.Orm.Model(&data). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建对象 +func (e *SysDictData) Insert(c *dto.SysDictDataInsertReq) error { + var err error + var data = new(models.SysDictData) + c.Generate(data) + err = e.Orm.Create(data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改对象 +func (e *SysDictData) Update(c *dto.SysDictDataUpdateReq) error { + var err error + var model = models.SysDictData{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(model) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除 +func (e *SysDictData) Remove(c *dto.SysDictDataDeleteReq) error { + var err error + var data models.SysDictData + + db := e.Orm.Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetAll 获取所有 +func (e *SysDictData) GetAll(c *dto.SysDictDataGetPageReq, list *[]models.SysDictData) error { + var err error + var data models.SysDictData + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} diff --git a/admin/app/admin/service/sys_dict_type.go b/admin/app/admin/service/sys_dict_type.go new file mode 100644 index 0000000..a41ed7e --- /dev/null +++ b/admin/app/admin/service/sys_dict_type.go @@ -0,0 +1,124 @@ +package service + +import ( + "errors" + "fmt" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysDictType struct { + service.Service +} + +// GetPage 获取列表 +func (e *SysDictType) GetPage(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType, count *int64) error { + var err error + var data models.SysDictType + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取对象 +func (e *SysDictType) Get(d *dto.SysDictTypeGetReq, model *models.SysDictType) error { + var err error + + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建对象 +func (e *SysDictType) Insert(c *dto.SysDictTypeInsertReq) error { + var err error + var data models.SysDictType + c.Generate(&data) + var count int64 + e.Orm.Model(&data).Where("dict_type = ?", data.DictType).Count(&count) + if count > 0 { + return fmt.Errorf("当前字典类型[%s]已经存在!", data.DictType) + } + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改对象 +func (e *SysDictType) Update(c *dto.SysDictTypeUpdateReq) error { + var err error + var model = models.SysDictType{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + db := e.Orm.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除 +func (e *SysDictType) Remove(d *dto.SysDictTypeDeleteReq) error { + var err error + var data models.SysDictType + + db := e.Orm.Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} + +// GetAll 获取所有 +func (e *SysDictType) GetAll(c *dto.SysDictTypeGetPageReq, list *[]models.SysDictType) error { + var err error + var data models.SysDictType + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} diff --git a/admin/app/admin/service/sys_login_log.go b/admin/app/admin/service/sys_login_log.go new file mode 100644 index 0000000..10ad32b --- /dev/null +++ b/admin/app/admin/service/sys_login_log.go @@ -0,0 +1,69 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysLoginLog struct { + service.Service +} + +// GetPage 获取SysLoginLog列表 +func (e *SysLoginLog) GetPage(c *dto.SysLoginLogGetPageReq, list *[]models.SysLoginLog, count *int64) error { + var err error + var data models.SysLoginLog + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Get 获取SysLoginLog对象 +func (e *SysLoginLog) Get(d *dto.SysLoginLogGetReq, model *models.SysLoginLog) error { + var err error + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Remove 删除SysLoginLog +func (e *SysLoginLog) Remove(c *dto.SysLoginLogDeleteReq) error { + var err error + var data models.SysLoginLog + + db := e.Orm.Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} diff --git a/admin/app/admin/service/sys_menu.go b/admin/app/admin/service/sys_menu.go new file mode 100644 index 0000000..70adc41 --- /dev/null +++ b/admin/app/admin/service/sys_menu.go @@ -0,0 +1,422 @@ +package service + +import ( + "fmt" + "sort" + "strings" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/pkg/errors" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" + cModels "git.ilapage.cn/OPC/chorus/admin/common/models" + + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +type SysMenu struct { + service.Service +} + +// GetPage 获取SysMenu列表 +func (e *SysMenu) GetPage(c *dto.SysMenuGetPageReq, menus *[]models.SysMenu) *SysMenu { + var menu = make([]models.SysMenu, 0) + err := e.getPage(c, &menu).Error + if err != nil { + _ = e.AddError(err) + return e + } + for i := 0; i < len(menu); i++ { + if menu[i].ParentId != 0 { + continue + } + menusInfo := menuCall(&menu, menu[i]) + *menus = append(*menus, menusInfo) + } + return e +} + +// getPage 菜单分页列表 +func (e *SysMenu) getPage(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) *SysMenu { + var err error + var data models.SysMenu + + err = e.Orm.Model(&data). + Scopes( + cDto.OrderDest("sort", false), + cDto.MakeCondition(c.GetNeedSearch()), + ).Preload("SysApi"). + Find(list).Error + if err != nil { + e.Log.Errorf("getSysMenuPage error:%s", err) + _ = e.AddError(err) + return e + } + return e +} + +// Get 获取SysMenu对象 +func (e *SysMenu) Get(d *dto.SysMenuGetReq, model *models.SysMenu) *SysMenu { + var err error + var data models.SysMenu + + db := e.Orm.Model(&data).Preload("SysApi"). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("GetSysMenu error:%s", err) + _ = e.AddError(err) + return e + } + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + apis := make([]int, 0) + for _, v := range model.SysApi { + apis = append(apis, v.Id) + } + model.Apis = apis + return e +} + +// Insert 创建SysMenu对象 +func (e *SysMenu) Insert(c *dto.SysMenuInsertReq) *SysMenu { + var err error + var data models.SysMenu + c.Generate(&data) + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + err = tx.Where("id in ?", c.Apis).Find(&data.SysApi).Error + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + err = tx.Create(&data).Error + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + c.MenuId = data.MenuId + err = e.initPaths(tx, &data) + if err != nil { + tx.Rollback() + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + } + tx.Commit() + return e +} + +func (e *SysMenu) initPaths(tx *gorm.DB, menu *models.SysMenu) error { + var err error + var data models.SysMenu + parentMenu := new(models.SysMenu) + if menu.ParentId != 0 { + err = tx.Model(&data).First(parentMenu, menu.ParentId).Error + if err != nil { + return err + } + if parentMenu.Paths == "" { + err = errors.New("父级paths异常,请尝试对当前节点父级菜单进行更新操作!") + return err + } + menu.Paths = parentMenu.Paths + "/" + pkg.IntToString(menu.MenuId) + } else { + menu.Paths = "/0/" + pkg.IntToString(menu.MenuId) + } + err = tx.Model(&data).Where("menu_id = ?", menu.MenuId).Update("paths", menu.Paths).Error + return err +} + +// Update 修改SysMenu对象 +func (e *SysMenu) Update(c *dto.SysMenuUpdateReq) *SysMenu { + var err error + tx := e.Orm.Debug().Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + var alist = make([]models.SysApi, 0) + var model = models.SysMenu{} + tx.Preload("SysApi").First(&model, c.GetId()) + oldPath := model.Paths + tx.Where("id in ?", c.Apis).Find(&alist) + err = tx.Model(&model).Association("SysApi").Delete(model.SysApi) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + _ = e.AddError(err) + return e + } + c.Generate(&model) + model.SysApi = alist + db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if db.RowsAffected == 0 { + _ = e.AddError(errors.New("无权更新该数据")) + return e + } + var menuList []models.SysMenu + tx.Where("paths like ?", oldPath+"%").Find(&menuList) + for _, v := range menuList { + v.Paths = strings.Replace(v.Paths, oldPath, model.Paths, 1) + tx.Model(&v).Update("paths", v.Paths) + } + return e +} + +// Remove 删除SysMenu +func (e *SysMenu) Remove(d *dto.SysMenuDeleteReq) *SysMenu { + var err error + var data models.SysMenu + + db := e.Orm.Model(&data).Delete(&data, d.Ids) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + _ = e.AddError(err) + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + _ = e.AddError(err) + } + return e +} + +// GetList 获取菜单数据 +func (e *SysMenu) GetList(c *dto.SysMenuGetPageReq, list *[]models.SysMenu) error { + var err error + var data models.SysMenu + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + ). + Find(list).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// SetLabel 修改角色中 设置菜单基础数据 +func (e *SysMenu) SetLabel() (m []dto.MenuLabel, err error) { + var list []models.SysMenu + err = e.GetList(&dto.SysMenuGetPageReq{}, &list) + + m = make([]dto.MenuLabel, 0) + for i := 0; i < len(list); i++ { + if list[i].ParentId != 0 { + continue + } + e := dto.MenuLabel{} + e.Id = list[i].MenuId + e.Label = list[i].Title + deptsInfo := menuLabelCall(&list, e) + + m = append(m, deptsInfo) + } + return +} + +// GetSysMenuByRoleName 左侧菜单 +func (e *SysMenu) GetSysMenuByRoleName(roleName ...string) ([]models.SysMenu, error) { + var MenuList []models.SysMenu + var role models.SysRole + var err error + admin := false + for _, s := range roleName { + if s == "admin" { + admin = true + } + } + + if len(roleName) > 0 && admin { + var data []models.SysMenu + err = e.Orm.Where(" menu_type in ('M','C')"). + Order("sort"). + Find(&data). + Error + MenuList = data + } else { + err = e.Orm.Model(&role).Preload("SysMenu", func(db *gorm.DB) *gorm.DB { + return db.Where(" menu_type in ('M','C')").Order("sort") + }).Where("role_name in ?", roleName).Find(&role). + Error + MenuList = *role.SysMenu + } + + if err != nil { + e.Log.Errorf("db error:%s", err) + } + return MenuList, err +} + +// menuLabelCall 递归构造组织数据 +func menuLabelCall(eList *[]models.SysMenu, dept dto.MenuLabel) dto.MenuLabel { + list := *eList + + min := make([]dto.MenuLabel, 0) + for j := 0; j < len(list); j++ { + + if dept.Id != list[j].ParentId { + continue + } + mi := dto.MenuLabel{} + mi.Id = list[j].MenuId + mi.Label = list[j].Title + mi.Children = []dto.MenuLabel{} + if list[j].MenuType != "F" { + ms := menuLabelCall(eList, mi) + min = append(min, ms) + } else { + min = append(min, mi) + } + } + if len(min) > 0 { + dept.Children = min + } else { + dept.Children = nil + } + return dept +} + +// menuCall 构建菜单树 +func menuCall(menuList *[]models.SysMenu, menu models.SysMenu) models.SysMenu { + list := *menuList + + min := make([]models.SysMenu, 0) + for j := 0; j < len(list); j++ { + + if menu.MenuId != list[j].ParentId { + continue + } + mi := models.SysMenu{} + mi.MenuId = list[j].MenuId + mi.MenuName = list[j].MenuName + mi.Title = list[j].Title + mi.Icon = list[j].Icon + mi.Path = list[j].Path + mi.MenuType = list[j].MenuType + mi.Action = list[j].Action + mi.Permission = list[j].Permission + mi.ParentId = list[j].ParentId + mi.NoCache = list[j].NoCache + mi.Breadcrumb = list[j].Breadcrumb + mi.Component = list[j].Component + mi.Sort = list[j].Sort + mi.Visible = list[j].Visible + mi.CreatedAt = list[j].CreatedAt + mi.SysApi = list[j].SysApi + mi.Children = []models.SysMenu{} + + if mi.MenuType != cModels.Button { + ms := menuCall(menuList, mi) + min = append(min, ms) + } else { + min = append(min, mi) + } + } + menu.Children = min + return menu +} + +func menuDistinct(menuList []models.SysMenu) (result []models.SysMenu) { + distinctMap := make(map[int]struct{}, len(menuList)) + for _, menu := range menuList { + if _, ok := distinctMap[menu.MenuId]; !ok { + distinctMap[menu.MenuId] = struct{}{} + result = append(result, menu) + } + } + return result +} + +func recursiveSetMenu(orm *gorm.DB, mIds []int, menus *[]models.SysMenu) error { + if len(mIds) == 0 || menus == nil { + return nil + } + var subMenus []models.SysMenu + err := orm.Where(fmt.Sprintf(" menu_type in ('%s', '%s', '%s') and menu_id in ?", + cModels.Directory, cModels.Menu, cModels.Button), mIds).Order("sort").Find(&subMenus).Error + if err != nil { + return err + } + + subIds := make([]int, 0) + for _, menu := range subMenus { + if menu.ParentId != 0 { + subIds = append(subIds, menu.ParentId) + } + if menu.MenuType != cModels.Button { + *menus = append(*menus, menu) + } + } + return recursiveSetMenu(orm, subIds, menus) +} + +// SetMenuRole 获取左侧菜单树使用 +func (e *SysMenu) SetMenuRole(roleName string) (m []models.SysMenu, err error) { + menus, err := e.getByRoleName(roleName) + m = make([]models.SysMenu, 0) + for i := 0; i < len(menus); i++ { + if menus[i].ParentId != 0 { + continue + } + menusInfo := menuCall(&menus, menus[i]) + m = append(m, menusInfo) + } + return +} + +func (e *SysMenu) getByRoleName(roleName string) ([]models.SysMenu, error) { + var role models.SysRole + var err error + data := make([]models.SysMenu, 0) + + if roleName == "admin" { + err = e.Orm.Where(" menu_type in ('M','C') and deleted_at is null"). + Order("sort"). + Find(&data). + Error + err = errors.WithStack(err) + } else { + role.RoleKey = roleName + err = e.Orm.Model(&role).Where("role_key = ? ", roleName).Preload("SysMenu").First(&role).Error + + if role.SysMenu != nil { + mIds := make([]int, 0) + for _, menu := range *role.SysMenu { + mIds = append(mIds, menu.MenuId) + } + if err := recursiveSetMenu(e.Orm, mIds, &data); err != nil { + return nil, err + } + + data = menuDistinct(data) + } + } + + sort.Sort(models.SysMenuSlice(data)) + return data, err +} diff --git a/admin/app/admin/service/sys_opera_log.go b/admin/app/admin/service/sys_opera_log.go new file mode 100644 index 0000000..7b3f9f4 --- /dev/null +++ b/admin/app/admin/service/sys_opera_log.go @@ -0,0 +1,83 @@ +package service + +import ( + "errors" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" +) + +type SysOperaLog struct { + service.Service +} + +// GetPage 获取SysOperaLog列表 +func (e *SysOperaLog) GetPage(c *dto.SysOperaLogGetPageReq, list *[]models.SysOperaLog, count *int64) error { + var err error + var data models.SysOperaLog + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("Service GetSysOperaLogPage error:%s", err.Error()) + return err + } + return nil +} + +// Get 获取SysOperaLog对象 +func (e *SysOperaLog) Get(d *dto.SysOperaLogGetReq, model *models.SysOperaLog) error { + var data models.SysOperaLog + + err := e.Orm.Model(&data). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("Service GetSysOperaLog error:%s", err.Error()) + return err + } + if err != nil { + e.Log.Errorf("Service GetSysOperaLog error:%s", err.Error()) + return err + } + return nil +} + +// Insert 创建SysOperaLog对象 +func (e *SysOperaLog) Insert(model *models.SysOperaLog) error { + var err error + var data models.SysOperaLog + + err = e.Orm.Model(&data). + Create(model).Error + if err != nil { + e.Log.Errorf("Service InsertSysOperaLog error:%s", err.Error()) + return err + } + return nil +} + +// Remove 删除SysOperaLog +func (e *SysOperaLog) Remove(d *dto.SysOperaLogDeleteReq) error { + var err error + var data models.SysOperaLog + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service RemoveSysOperaLog error:%s", err.Error()) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} diff --git a/admin/app/admin/service/sys_post.go b/admin/app/admin/service/sys_post.go new file mode 100644 index 0000000..f8f9b95 --- /dev/null +++ b/admin/app/admin/service/sys_post.go @@ -0,0 +1,104 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysPost struct { + service.Service +} + +// GetPage 获取SysPost列表 +func (e *SysPost) GetPage(c *dto.SysPostPageReq, list *[]models.SysPost, count *int64) error { + var err error + var data models.SysPost + + err = e.Orm.Model(&data). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s \r", err) + return err + } + return nil +} + +// Get 获取SysPost对象 +func (e *SysPost) Get(d *dto.SysPostGetReq, model *models.SysPost) error { + var err error + var data models.SysPost + + db := e.Orm.Model(&data). + First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Insert 创建SysPost对象 +func (e *SysPost) Insert(c *dto.SysPostInsertReq) error { + var err error + var data models.SysPost + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Update 修改SysPost对象 +func (e *SysPost) Update(c *dto.SysPostUpdateReq) error { + var err error + var model = models.SysPost{} + e.Orm.First(&model, c.GetId()) + c.Generate(&model) + + db := e.Orm.Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + return nil +} + +// Remove 删除SysPost +func (e *SysPost) Remove(d *dto.SysPostDeleteReq) error { + var err error + var data models.SysPost + + db := e.Orm.Model(&data).Delete(&data, d.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Delete error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("无权删除该数据") + return err + } + return nil +} diff --git a/admin/app/admin/service/sys_role.go b/admin/app/admin/service/sys_role.go new file mode 100644 index 0000000..fc28f28 --- /dev/null +++ b/admin/app/admin/service/sys_role.go @@ -0,0 +1,352 @@ +package service + +import ( + "errors" + + "github.com/go-admin-team/go-admin-core/sdk/config" + "gorm.io/gorm/clause" + + "github.com/casbin/casbin/v2" + + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysRole struct { + service.Service +} + +// GetPage 获取SysRole列表 +func (e *SysRole) GetPage(c *dto.SysRoleGetPageReq, list *[]models.SysRole, count *int64) error { + var err error + var data models.SysRole + + err = e.Orm.Model(&data).Preload("SysMenu"). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + return nil +} + +// Get 获取SysRole对象 +func (e *SysRole) Get(d *dto.SysRoleGetReq, model *models.SysRole) error { + var err error + db := e.Orm.First(model, d.GetId()) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + return err + } + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + model.MenuIds, err = e.GetRoleMenuId(model.RoleId) + if err != nil { + e.Log.Errorf("get menuIds error, %s", err.Error()) + return err + } + return nil +} + +// Insert 创建SysRole对象 +func (e *SysRole) Insert(c *dto.SysRoleInsertReq, cb *casbin.SyncedEnforcer) error { + var err error + var data models.SysRole + var dataMenu []models.SysMenu + err = e.Orm.Preload("SysApi").Where("menu_id in ?", c.MenuIds).Find(&dataMenu).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + c.SysMenu = dataMenu + c.Generate(&data) + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx = e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var count int64 + err = tx.Model(&data).Where("role_key = ?", c.RoleKey).Count(&count).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + + if count > 0 { + err = errors.New("roleKey已存在,需更换在提交!") + e.Log.Errorf("db error:%s", err) + return err + } + + err = tx.Create(&data).Error + if err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + + mp := make(map[string]interface{}, 0) + polices := make([][]string, 0) + for _, menu := range dataMenu { + for _, api := range menu.SysApi { + if mp[data.RoleKey+"-"+api.Path+"-"+api.Action] != "" { + mp[data.RoleKey+"-"+api.Path+"-"+api.Action] = "" + polices = append(polices, []string{data.RoleKey, api.Path, api.Action}) + } + } + } + + if len(polices) <= 0 { + return nil + } + + // 写入 sys_casbin_rule 权限表里 当前角色数据的记录 + _, err = cb.AddNamedPolicies("p", polices) + if err != nil { + return err + } + + return nil +} + +// Update 修改SysRole对象 +func (e *SysRole) Update(c *dto.SysRoleUpdateReq, cb *casbin.SyncedEnforcer) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx = e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + var mlist = make([]models.SysMenu, 0) + tx.Preload("SysMenu").First(&model, c.GetId()) + tx.Preload("SysApi").Where("menu_id in ?", c.MenuIds).Find(&mlist) + err = tx.Model(&model).Association("SysMenu").Delete(model.SysMenu) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + return err + } + c.Generate(&model) + model.SysMenu = &mlist + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + // 清除 sys_casbin_rule 权限表里 当前角色的所有记录 + _, err = cb.RemoveFilteredPolicy(0, model.RoleKey) + if err != nil { + e.Log.Errorf("delete policy error:%s", err) + return err + } + mp := make(map[string]interface{}, 0) + polices := make([][]string, 0) + for _, menu := range mlist { + for _, api := range menu.SysApi { + if mp[model.RoleKey+"-"+api.Path+"-"+api.Action] != "" { + mp[model.RoleKey+"-"+api.Path+"-"+api.Action] = "" + //_, err = cb.AddNamedPolicy("p", model.RoleKey, api.Path, api.Action) + polices = append(polices, []string{model.RoleKey, api.Path, api.Action}) + } + } + } + if len(polices) <= 0 { + return nil + } + + // 写入 sys_casbin_rule 权限表里 当前角色数据的记录 + _, err = cb.AddNamedPolicies("p", polices) + if err != nil { + return err + } + return nil +} + +// Remove 删除SysRole +func (e *SysRole) Remove(c *dto.SysRoleDeleteReq, cb *casbin.SyncedEnforcer) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx = e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + tx.Preload("SysMenu").Preload("SysDept").First(&model, c.GetId()) + //删除 SysRole 时,同时删除角色所有 关联其它表 记录 (SysMenu 和 SysMenu) + db := tx.Select(clause.Associations).Delete(&model) + + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + + // 清除 sys_casbin_rule 权限表里 当前角色的所有记录 + _, _ = cb.RemoveFilteredPolicy(0, model.RoleKey) + + return nil +} + +// GetRoleMenuId 获取角色对应的菜单ids +func (e *SysRole) GetRoleMenuId(roleId int) ([]int, error) { + menuIds := make([]int, 0) + model := models.SysRole{} + model.RoleId = roleId + if err := e.Orm.Model(&model).Preload("SysMenu").First(&model).Error; err != nil { + return nil, err + } + l := *model.SysMenu + for i := 0; i < len(l); i++ { + menuIds = append(menuIds, l[i].MenuId) + } + return menuIds, nil +} + +func (e *SysRole) UpdateDataScope(c *dto.RoleDataScopeReq) *SysRole { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx = e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var dlist = make([]models.SysDept, 0) + var model = models.SysRole{} + tx.Preload("SysDept").First(&model, c.RoleId) + tx.Where("dept_id in ?", c.DeptIds).Find(&dlist) + // 删除SysRole 和 SysDept 的关联关系 + err = tx.Model(&model).Association("SysDept").Delete(model.SysDept) + if err != nil { + e.Log.Errorf("delete SysDept error:%s", err) + _ = e.AddError(err) + return e + } + c.Generate(&model) + model.SysDept = dlist + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Model(&model).Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if db.RowsAffected == 0 { + _ = e.AddError(errors.New("无权更新该数据")) + return e + } + return e +} + +// UpdateStatus 修改SysRole对象status +func (e *SysRole) UpdateStatus(c *dto.UpdateStatusReq) error { + var err error + tx := e.Orm + if config.DatabaseConfig.Driver != "sqlite3" { + tx = e.Orm.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + } + var model = models.SysRole{} + tx.First(&model, c.GetId()) + c.Generate(&model) + // 更新关联的数据,使用 FullSaveAssociations 模式 + db := tx.Session(&gorm.Session{FullSaveAssociations: true}).Debug().Save(&model) + if err = db.Error; err != nil { + e.Log.Errorf("db error:%s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + return nil +} + +// GetWithName 获取SysRole对象 +func (e *SysRole) GetWithName(d *dto.SysRoleByName, model *models.SysRole) *SysRole { + var err error + db := e.Orm.Where("role_name = ?", d.RoleName).First(model) + err = db.Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + if err != nil { + e.Log.Errorf("db error:%s", err) + _ = e.AddError(err) + return e + } + model.MenuIds, err = e.GetRoleMenuId(model.RoleId) + if err != nil { + e.Log.Errorf("get menuIds error, %s", err.Error()) + _ = e.AddError(err) + return e + } + return e +} + +// GetById 获取SysRole对象 +func (e *SysRole) GetById(roleId int) ([]string, error) { + permissions := make([]string, 0) + model := models.SysRole{} + model.RoleId = roleId + if err := e.Orm.Model(&model).Preload("SysMenu").First(&model).Error; err != nil { + return nil, err + } + l := *model.SysMenu + for i := 0; i < len(l); i++ { + if l[i].Permission != "" { + permissions = append(permissions, l[i].Permission) + } + } + return permissions, nil +} diff --git a/admin/app/admin/service/sys_role_menu.go b/admin/app/admin/service/sys_role_menu.go new file mode 100644 index 0000000..9d5d38b --- /dev/null +++ b/admin/app/admin/service/sys_role_menu.go @@ -0,0 +1,110 @@ +package service + +import ( + "github.com/go-admin-team/go-admin-core/sdk/service" +) + +// SysRoleMenu 即将弃用结构体 +type SysRoleMenu struct { + service.Service +} + +//func (e *SysRoleMenu) ReloadRule(tx *gorm.DB, roleId int, menuId []int) (err error) { +// var role models.SysRole +// +// msgID := e.MsgID +// +// menu := make([]models.Menu, 0) +// roleMenu := make([]models.RoleMenu, len(menuId)) +// casbinRule := make([]models.CasbinRule, 0) +// //先删除所有的 +// err = e.DeleteRoleMenu(tx, roleId) +// if err != nil { +// return +// } +// +// // 在事务中做一些数据库操作(从这一点使用'tx',而不是'db') +// err = tx.Where("role_id = ?", roleId).First(&role).Error +// if err != nil { +// log.Errorf("msgID[%s] get role error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("menu_id in (?)", menuId). +// //Select("path, action, menu_id, menu_type"). +// Find(&menu).Error +// if err != nil { +// log.Errorf("msgID[%s] get menu error, %s", msgID, err.Error()) +// return +// } +// for i := range menu { +// roleMenu[i] = models.RoleMenu{ +// RoleId: role.RoleId, +// MenuId: menu[i].MenuId, +// RoleName: role.RoleKey, +// } +// if menu[i].MenuType == "A" { +// casbinRule = append(casbinRule, models.CasbinRule{ +// PType: "p", +// V0: role.RoleKey, +// V1: menu[i].Path, +// V2: menu[i].Action, +// }) +// } +// } +// err = tx.Create(&roleMenu).Error +// if err != nil { +// log.Errorf("msgID[%s] batch create role's menu error, %s", msgID, err.Error()) +// return +// } +// if len(casbinRule) > 0 { +// err = tx.Create(&casbinRule).Error +// if err != nil { +// log.Errorf("msgID[%s] batch create casbin rule error, %s", msgID, err.Error()) +// return +// } +// } +// +// return +//} + +//func (e *SysRoleMenu) DeleteRoleMenu(tx *gorm.DB, roleId int) (err error) { +// msgID := e.MsgID +// err = tx.Where("role_id = ?", roleId). +// Delete(&models.SysRoleDept{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete role's dept error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("role_id = ?", roleId). +// Delete(&models.RoleMenu{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete role's menu error, %s", msgID, err.Error()) +// return +// } +// var role models.SysRole +// err = tx.Where("role_id = ?", roleId). +// First(&role).Error +// if err != nil { +// log.Errorf("msgID[%s] get role error, %s", msgID, err.Error()) +// return +// } +// err = tx.Where("v0 = ?", role.RoleKey). +// Delete(&models.CasbinRule{}).Error +// if err != nil { +// log.Errorf("msgID[%s] delete casbin rule error, %s", msgID, err.Error()) +// return +// } +// return +//} +// +//func (e *SysRoleMenu) GetIDS(tx *gorm.DB, roleName string) ([]models.MenuPath, error) { +// var r []models.MenuPath +// table := tx.Select("sys_menu.path").Table("sys_role_menu") +// table = table.Joins("left join sys_role on sys_role.role_id=sys_role_menu.role_id") +// table = table.Joins("left join sys_menu on sys_menu.id=sys_role_menu.menu_id") +// table = table.Where("sys_role.role_name = ? and sys_menu.type=1", roleName) +// if err := table.Find(&r).Error; err != nil { +// return nil, err +// } +// return r, nil +//} \ No newline at end of file diff --git a/admin/app/admin/service/sys_user.go b/admin/app/admin/service/sys_user.go new file mode 100644 index 0000000..626fa35 --- /dev/null +++ b/admin/app/admin/service/sys_user.go @@ -0,0 +1,266 @@ +package service + +import ( + "errors" + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/service" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/common/actions" + cDto "git.ilapage.cn/OPC/chorus/admin/common/dto" +) + +type SysUser struct { + service.Service +} + +// GetPage 获取SysUser列表 +func (e *SysUser) GetPage(c *dto.SysUserGetPageReq, p *actions.DataPermission, list *[]models.SysUser, count *int64) error { + var err error + var data models.SysUser + + err = e.Orm.Debug().Preload("Dept"). + Scopes( + cDto.MakeCondition(c.GetNeedSearch()), + cDto.Paginate(c.GetPageSize(), c.GetPageIndex()), + actions.Permission(data.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(count).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Get 获取SysUser对象 +func (e *SysUser) Get(d *dto.SysUserById, p *actions.DataPermission, model *models.SysUser) error { + var data models.SysUser + + err := e.Orm.Model(&data).Debug(). + Scopes( + actions.Permission(data.TableName(), p), + ). + First(model, d.GetId()).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + err = errors.New("查看对象不存在或无权查看") + e.Log.Errorf("db error: %s", err) + return err + } + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Insert 创建SysUser对象 +func (e *SysUser) Insert(c *dto.SysUserInsertReq) error { + var err error + var data models.SysUser + var i int64 + err = e.Orm.Model(&data).Where("username = ?", c.Username).Count(&i).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if i > 0 { + err := errors.New("用户名已存在!") + e.Log.Errorf("db error: %s", err) + return err + } + c.Generate(&data) + err = e.Orm.Create(&data).Error + if err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + return nil +} + +// Update 修改SysUser对象 +func (e *SysUser) Update(c *dto.SysUserUpdateReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + c.Generate(&model) + update := e.Orm.Model(&model).Where("user_id = ?", &model.UserId).Omit("password", "salt").Updates(&model) + if err = update.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if update.RowsAffected == 0 { + err = errors.New("update userinfo error") + log.Warnf("db update error") + return err + } + return nil +} + +// UpdateAvatar 更新用户头像 +func (e *SysUser) UpdateAvatar(c *dto.UpdateSysUserAvatarReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error + if err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + return nil +} + +// UpdateStatus 更新用户状态 +func (e *SysUser) UpdateStatus(c *dto.UpdateSysUserStatusReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + + } + err = e.Orm.Table(model.TableName()).Where("user_id =? ", c.UserId).Updates(c).Error + if err != nil { + e.Log.Errorf("Service UpdateSysUser error: %s", err) + return err + } + return nil +} + +// ResetPwd 重置用户密码 +func (e *SysUser) ResetPwd(c *dto.ResetSysUserPwdReq, p *actions.DataPermission) error { + var err error + var model models.SysUser + db := e.Orm.Scopes( + actions.Permission(model.TableName(), p), + ).First(&model, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("At Service ResetSysUserPwd error: %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权更新该数据") + } + c.Generate(&model) + err = e.Orm.Omit("username", "nick_name", "phone", "role_id", "avatar", "sex").Save(&model).Error + if err != nil { + e.Log.Errorf("At Service ResetSysUserPwd error: %s", err) + return err + } + return nil +} + +// Remove 删除SysUser +func (e *SysUser) Remove(c *dto.SysUserById, p *actions.DataPermission) error { + var err error + var data models.SysUser + + db := e.Orm.Model(&data). + Scopes( + actions.Permission(data.TableName(), p), + ).Delete(&data, c.GetId()) + if err = db.Error; err != nil { + e.Log.Errorf("Error found in RemoveSysUser : %s", err) + return err + } + if db.RowsAffected == 0 { + return errors.New("无权删除该数据") + } + return nil +} + +// UpdatePwd 修改SysUser对象密码 +func (e *SysUser) UpdatePwd(id int, oldPassword, newPassword string, p *actions.DataPermission) error { + var err error + + if newPassword == "" { + return nil + } + c := &models.SysUser{} + + err = e.Orm.Model(c). + Scopes( + actions.Permission(c.TableName(), p), + ).Select("UserId", "Password", "Salt"). + First(c, id).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return errors.New("无权更新该数据") + } + e.Log.Errorf("db error: %s", err) + return err + } + var ok bool + ok, err = pkg.CompareHashAndPassword(c.Password, oldPassword) + if err != nil { + e.Log.Errorf("CompareHashAndPassword error, %s", err.Error()) + return err + } + if !ok { + err = errors.New("incorrect Password") + e.Log.Warnf("user[%d] %s", id, err.Error()) + return err + } + c.Password = newPassword + db := e.Orm.Model(c).Where("user_id = ?", id). + Select("Password", "Salt"). + Updates(c) + if err = db.Error; err != nil { + e.Log.Errorf("db error: %s", err) + return err + } + if db.RowsAffected == 0 { + err = errors.New("set password error") + log.Warnf("db update error") + return err + } + return nil +} + +func (e *SysUser) GetProfile(c *dto.SysUserById, user *models.SysUser, roles *[]models.SysRole, posts *[]models.SysPost) error { + err := e.Orm.Preload("Dept").First(user, c.GetId()).Error + if err != nil { + return err + } + err = e.Orm.Find(roles, user.RoleId).Error + if err != nil { + return err + } + err = e.Orm.Find(posts, user.PostIds).Error + if err != nil { + return err + } + + return nil +} diff --git a/admin/app/chorus/errors.go b/admin/app/chorus/errors.go new file mode 100644 index 0000000..d5ede58 --- /dev/null +++ b/admin/app/chorus/errors.go @@ -0,0 +1,25 @@ +package chorus + +import ( + "errors" + "fmt" +) + +var ( + ErrNotFound = errors.New("chorus resource not found") + ErrConflict = errors.New("chorus configuration version conflict") + ErrConnectivityDisabled = errors.New("connectivity checks require explicit authorization") +) + +type FieldError struct { + Field string `json:"field"` + Message string `json:"message"` +} + +func (e FieldError) Error() string { + return fmt.Sprintf("invalid %s: %s", e.Field, e.Message) +} + +type CooldownError struct{ RetryAfterSeconds uint32 } + +func (e CooldownError) Error() string { return "connectivity check is cooling down" } diff --git a/admin/app/chorus/probe.go b/admin/app/chorus/probe.go new file mode 100644 index 0000000..b89d435 --- /dev/null +++ b/admin/app/chorus/probe.go @@ -0,0 +1,49 @@ +package chorus + +import ( + "context" + "encoding/base64" + "errors" + + "git.ilapage.cn/OPC/chorus/internal/core/model" + "git.ilapage.cn/OPC/chorus/internal/core/provider" +) + +const connectivityPrompt = "Chorus connectivity probe. Reply with OK." + +type LiveProbe struct{ http provider.HTTPClient } + +func NewLiveProbe(httpClient provider.HTTPClient) (*LiveProbe, error) { + if httpClient == nil { + return nil, errors.New("safe provider HTTP client is required") + } + return &LiveProbe{http: httpClient}, nil +} + +func (p *LiveProbe) Probe(ctx context.Context, cfg ProbeConfiguration) error { + client, err := provider.NewOpenAI(p.http, provider.OpenAIConfig{ + BaseURL: cfg.BaseURL, AuthType: cfg.AuthType, APIKey: cfg.APIKey, + ExtraBody: cfg.ExtraBody, MaxResponseBytes: cfg.MaxResponseBytes, + }) + if err != nil { + return errors.New("prepare connectivity probe") + } + probeCtx, cancel := context.WithTimeout(ctx, cfg.Timeout) + defer cancel() + request := provider.Request{Kind: cfg.Kind, APIType: cfg.APIType, ModelID: cfg.ModelID, RenderedPrompt: connectivityPrompt} + if cfg.APIType == model.APIImagesEdits { + content, err := base64.StdEncoding.DecodeString("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWP4z8DwHwAFgAI/ScL4WQAAAABJRU5ErkJggg==") + if err != nil { + return errors.New("prepare connectivity image") + } + defer clear(content) + request.Inputs = []provider.Input{{Content: content, MIMEType: "image/png", Role: model.RolePrimary, Position: 0}} + } + outputs, err := client.Generate(probeCtx, request) + if err != nil || len(outputs) == 0 { + return errors.New("connectivity probe failed") + } + return nil +} + +var _ ProbeRunner = (*LiveProbe)(nil) diff --git a/admin/app/chorus/router.go b/admin/app/chorus/router.go new file mode 100644 index 0000000..ec1a69f --- /dev/null +++ b/admin/app/chorus/router.go @@ -0,0 +1,285 @@ +package chorus + +import ( + "errors" + "net/http" + "strconv" + "strings" + + "git.ilapage.cn/OPC/chorus/admin/common/middleware" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type serviceFactory func(*gin.Context) (*Service, error) + +var configuredFactory serviceFactory + +// Configure installs the service factory used by the fixed go-admin router. +// The caller is responsible for supplying the repository-wide key ring and +// safe HTTP probe configuration during server setup. +func Configure(cipherConfig func(*gorm.DB) (*Service, error)) { + configuredFactory = func(c *gin.Context) (*Service, error) { + value, ok := c.Get("db") + if !ok { + return nil, errors.New("go-admin database context is unavailable") + } + db, ok := value.(*gorm.DB) + if !ok || db == nil { + return nil, errors.New("go-admin database context is invalid") + } + return cipherConfig(db) + } +} + +// Register attaches the extension to the normal go-admin JWT and Casbin +// chain. It does not create a parallel authentication path. +func Register(v1 *gin.RouterGroup, authMiddleware *jwt.GinJWTMiddleware) { + if configuredFactory == nil { + panic("chorus admin service is not configured") + } + register(v1, authMiddleware.MiddlewareFunc(), middleware.AuthCheckRole(), configuredFactory) +} + +// RegisterWithService is kept for handler tests and for explicit embedding. +// Production callers should use Register so that go-admin's JWT and Casbin +// middleware remain mandatory. +func RegisterWithService(v1 *gin.RouterGroup, authentication, authorization gin.HandlerFunc, service *Service) { + register(v1, authentication, authorization, func(*gin.Context) (*Service, error) { return service, nil }) +} + +func register(v1 *gin.RouterGroup, authentication, authorization gin.HandlerFunc, factory serviceFactory) { + r := v1.Group("/chorus").Use(authentication, authorization) + r.GET("/providers", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.Providers(c.Request.Context()) + })) + r.POST("/providers", withBody[ProviderInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, input ProviderInput) (any, error) { + return service.CreateProvider(c.Request.Context(), actor, requestID, input) + })) + r.GET("/providers/:id", withID(factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64) (any, error) { + return service.Provider(c.Request.Context(), id) + })) + r.PUT("/providers/:id", withIDBody[ProviderInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64, input ProviderInput) (any, error) { + return service.UpdateProvider(c.Request.Context(), actor, requestID, id, input) + })) + r.PUT("/providers/:id/credential", withIDBody[struct { + APIKey string `json:"api_key"` + }](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64, input struct { + APIKey string `json:"api_key"` + }) (any, error) { + return service.RotateCredential(c.Request.Context(), actor, requestID, id, input.APIKey) + })) + r.POST("/providers/:id/credential/:version/activate", withTwoIDs(factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id, version uint64) (any, error) { + if version > uint64(^uint32(0)) { + return nil, FieldError{Field: "credential_version", Message: "is too large"} + } + return service.ActivateCredential(c.Request.Context(), actor, requestID, id, uint32(version)) + })) + + r.GET("/provider-models", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.ProviderModels(c.Request.Context()) + })) + r.POST("/provider-models", withBody[ProviderModelInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, input ProviderModelInput) (any, error) { + return service.CreateProviderModel(c.Request.Context(), actor, requestID, input) + })) + r.PUT("/provider-models/:id", withIDBody[ProviderModelInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64, input ProviderModelInput) (any, error) { + return service.UpdateProviderModel(c.Request.Context(), actor, requestID, id, input) + })) + r.POST("/provider-models/:id/connectivity-checks", withID(factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64) (any, error) { + return service.StartConnectivityCheck(c.Request.Context(), actor, requestID, id) + })) + + r.GET("/prompt-templates", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.PromptTemplates(c.Request.Context()) + })) + r.POST("/prompt-templates", withBody[PromptTemplateInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, input PromptTemplateInput) (any, error) { + return service.CreatePromptTemplate(c.Request.Context(), actor, requestID, input) + })) + r.PUT("/prompt-templates/:id", withIDBody[PromptTemplateInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64, input PromptTemplateInput) (any, error) { + return service.UpdatePromptTemplate(c.Request.Context(), actor, requestID, id, input) + })) + + r.GET("/route-pools", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.RoutePools(c.Request.Context()) + })) + r.POST("/route-pools", withBody[RoutePoolInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, input RoutePoolInput) (any, error) { + return service.CreateRoutePool(c.Request.Context(), actor, requestID, input) + })) + r.PUT("/route-pools/:id", withIDBody[RoutePoolInput](factory, func(c *gin.Context, service *Service, actor uint64, requestID string, id uint64, input RoutePoolInput) (any, error) { + return service.UpdateRoutePool(c.Request.Context(), actor, requestID, id, input) + })) + + r.GET("/users", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.Users(c.Request.Context()) + })) + r.GET("/generations", withService(factory, func(c *gin.Context, service *Service, actor uint64, requestID string) (any, error) { + return service.Generations(c.Request.Context()) + })) +} + +type endpoint func(*gin.Context, *Service, uint64, string) (any, error) +type idEndpoint func(*gin.Context, *Service, uint64, string, uint64) (any, error) + +func withService(factory serviceFactory, endpoint endpoint) gin.HandlerFunc { + return func(c *gin.Context) { + service, actor, requestID, ok := requestScope(c, factory) + if !ok { + return + } + data, err := endpoint(c, service, actor, requestID) + respond(c, data, err) + } +} + +func withID(factory serviceFactory, endpoint idEndpoint) gin.HandlerFunc { + return func(c *gin.Context) { + service, actor, requestID, ok := requestScope(c, factory) + if !ok { + return + } + id, err := pathID(c, "id") + if err != nil { + respond(c, nil, err) + return + } + data, endpointErr := endpoint(c, service, actor, requestID, id) + respond(c, data, endpointErr) + } +} + +func withTwoIDs(factory serviceFactory, endpoint func(*gin.Context, *Service, uint64, string, uint64, uint64) (any, error)) gin.HandlerFunc { + return func(c *gin.Context) { + service, actor, requestID, ok := requestScope(c, factory) + if !ok { + return + } + id, err := pathID(c, "id") + if err == nil { + var version uint64 + version, err = pathID(c, "version") + if err == nil { + data, endpointErr := endpoint(c, service, actor, requestID, id, version) + respond(c, data, endpointErr) + return + } + } + respond(c, nil, err) + } +} + +func withBody[T any](factory serviceFactory, endpoint func(*gin.Context, *Service, uint64, string, T) (any, error)) gin.HandlerFunc { + return func(c *gin.Context) { + service, actor, requestID, ok := requestScope(c, factory) + if !ok { + return + } + var input T + if err := c.ShouldBindJSON(&input); err != nil { + respond(c, nil, FieldError{Field: "body", Message: "must be valid JSON"}) + return + } + data, endpointErr := endpoint(c, service, actor, requestID, input) + respond(c, data, endpointErr) + } +} + +func withIDBody[T any](factory serviceFactory, endpoint func(*gin.Context, *Service, uint64, string, uint64, T) (any, error)) gin.HandlerFunc { + return func(c *gin.Context) { + service, actor, requestID, ok := requestScope(c, factory) + if !ok { + return + } + id, err := pathID(c, "id") + if err != nil { + respond(c, nil, err) + return + } + var input T + if err := c.ShouldBindJSON(&input); err != nil { + respond(c, nil, FieldError{Field: "body", Message: "must be valid JSON"}) + return + } + data, endpointErr := endpoint(c, service, actor, requestID, id, input) + respond(c, data, endpointErr) + } +} + +func requestScope(c *gin.Context, factory serviceFactory) (*Service, uint64, string, bool) { + service, err := factory(c) + if err != nil { + respond(c, nil, err) + return nil, 0, "", false + } + actor, err := actorID(c) + if err != nil { + respond(c, nil, err) + return nil, 0, "", false + } + requestID := strings.TrimSpace(c.GetHeader(pkg.TrafficKey)) + if requestID == "" { + requestID = uuid.NewString() + } + return service, actor, requestID, true +} + +func actorID(c *gin.Context) (uint64, error) { + claims := jwt.ExtractClaims(c) + value, ok := claims[jwt.IdentityKey] + if !ok { + return 0, errors.New("administrator identity is missing") + } + switch value := value.(type) { + case float64: + if value > 0 && value == float64(uint64(value)) { + return uint64(value), nil + } + case string: + parsed, err := strconv.ParseUint(value, 10, 64) + if err == nil && parsed > 0 { + return parsed, nil + } + case uint64: + if value > 0 { + return value, nil + } + } + return 0, errors.New("administrator identity is invalid") +} + +func pathID(c *gin.Context, name string) (uint64, error) { + value, err := strconv.ParseUint(c.Param(name), 10, 64) + if err != nil || value == 0 { + return 0, FieldError{Field: name, Message: "must be a positive integer"} + } + return value, nil +} + +func respond(c *gin.Context, data any, err error) { + if err == nil { + c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "data": data, "msg": "ok"}) + return + } + status, code, message := http.StatusInternalServerError, "internal_error", "request could not be completed" + response := gin.H{"code": status, "error": code, "msg": message} + var field FieldError + var cooldown CooldownError + switch { + case errors.As(err, &field): + status, code, message = http.StatusUnprocessableEntity, "validation_failed", "request fields are invalid" + response["field"] = field.Field + case errors.Is(err, ErrNotFound): + status, code, message = http.StatusNotFound, "not_found", "resource was not found" + case errors.Is(err, ErrConflict): + status, code, message = http.StatusConflict, "version_conflict", "configuration has changed; refresh and retry" + case errors.As(err, &cooldown): + status, code, message = http.StatusTooManyRequests, "connectivity_cooldown", "connectivity check is cooling down" + response["retry_after_seconds"] = cooldown.RetryAfterSeconds + case errors.Is(err, ErrConnectivityDisabled): + status, code, message = http.StatusForbidden, "connectivity_not_authorized", "connectivity checks require explicit authorization" + } + response["code"], response["error"], response["msg"] = status, code, message + c.JSON(status, response) +} diff --git a/admin/app/chorus/router_test.go b/admin/app/chorus/router_test.go new file mode 100644 index 0000000..282b9c3 --- /dev/null +++ b/admin/app/chorus/router_test.go @@ -0,0 +1,53 @@ +package chorus + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" +) + +func TestChorusRoutesRequireAuthenticationAuthorizationAndHaveNoDeleteEndpoint(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + authentication := func(c *gin.Context) { + if c.GetHeader("Authorization") != "Bearer test" { + c.AbortWithStatus(http.StatusUnauthorized) + return + } + c.Next() + } + authorization := func(c *gin.Context) { + if c.GetHeader("X-Chorus-Role") != "operator" { + c.AbortWithStatus(http.StatusForbidden) + return + } + c.Next() + } + RegisterWithService(engine.Group("/api/v1"), authentication, authorization, nil) + + for _, test := range []struct { + name string + headers map[string]string + method string + path string + want int + }{ + {name: "unauthenticated", method: http.MethodGet, path: "/api/v1/chorus/providers", want: http.StatusUnauthorized}, + {name: "not authorized", method: http.MethodGet, path: "/api/v1/chorus/providers", headers: map[string]string{"Authorization": "Bearer test"}, want: http.StatusForbidden}, + {name: "delete is not exposed", method: http.MethodDelete, path: "/api/v1/chorus/providers/1", headers: map[string]string{"Authorization": "Bearer test", "X-Chorus-Role": "operator"}, want: http.StatusNotFound}, + } { + t.Run(test.name, func(t *testing.T) { + recorder := httptest.NewRecorder() + request := httptest.NewRequest(test.method, test.path, nil) + for key, value := range test.headers { + request.Header.Set(key, value) + } + engine.ServeHTTP(recorder, request) + if recorder.Code != test.want { + t.Fatalf("status = %d, want %d", recorder.Code, test.want) + } + }) + } +} diff --git a/admin/app/chorus/service.go b/admin/app/chorus/service.go new file mode 100644 index 0000000..3033e37 --- /dev/null +++ b/admin/app/chorus/service.go @@ -0,0 +1,1310 @@ +package chorus + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/url" + "slices" + "strings" + "time" + + corecrypto "git.ilapage.cn/OPC/chorus/internal/core/crypto" + "git.ilapage.cn/OPC/chorus/internal/core/model" + "git.ilapage.cn/OPC/chorus/internal/core/provider" + "github.com/google/uuid" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +const maxRouteMembers = 32 + +type ProbeConfiguration struct { + BaseURL string + AuthType provider.AuthType + APIKey string + ModelID string + APIType model.APIType + Kind model.GenerationKind + Capabilities []model.Capability + ExtraBody json.RawMessage + Timeout time.Duration + MaxResponseBytes int64 +} + +// ProbeRunner is deliberately injected. The production implementation is +// created with the same SSRF-safe HTTP client as workers; tests provide a +// mock and the default server setting refuses to call it. +type ProbeRunner interface { + Probe(context.Context, ProbeConfiguration) error +} + +type Config struct { + AllowConnectivityChecks bool + ConnectivityCooldown time.Duration + MaxResponseBytes int64 + Now func() time.Time + Probe ProbeRunner +} + +type Service struct { + db *gorm.DB + cipher corecrypto.KeyCipher + allowConnectivityChecks bool + connectivityCooldown time.Duration + maxResponseBytes int64 + now func() time.Time + probe ProbeRunner +} + +func NewService(db *gorm.DB, cipher corecrypto.KeyCipher, cfg Config) (*Service, error) { + if db == nil || cipher == nil { + return nil, errors.New("chorus admin service dependencies are required") + } + if cfg.Now == nil { + cfg.Now = time.Now + } + if cfg.AllowConnectivityChecks && (cfg.ConnectivityCooldown <= 0 || cfg.Probe == nil || cfg.MaxResponseBytes <= 0) { + return nil, errors.New("authorized connectivity checks require cooldown, probe, and response limit") + } + return &Service{ + db: db, cipher: cipher, allowConnectivityChecks: cfg.AllowConnectivityChecks, + connectivityCooldown: cfg.ConnectivityCooldown, maxResponseBytes: cfg.MaxResponseBytes, + now: cfg.Now, probe: cfg.Probe, + }, nil +} + +type providerRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + Slug string `gorm:"column:slug"` + Name string `gorm:"column:name"` + BaseURL string `gorm:"column:base_url"` + AuthType string `gorm:"column:auth_type"` + ActiveCredentialID *uint64 `gorm:"column:active_credential_id"` + Enabled bool `gorm:"column:enabled"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (providerRow) TableName() string { return "providers" } + +type credentialRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + ProviderID uint64 `gorm:"column:provider_id"` + CredentialVersion uint32 `gorm:"column:credential_version"` + APIKeyEnc json.RawMessage `gorm:"column:api_key_enc"` + KeyID string `gorm:"column:key_id"` + Status string `gorm:"column:status"` + CreatedBy uint64 `gorm:"column:created_by"` + UpdatedBy uint64 `gorm:"column:updated_by"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` + RetiredAt *time.Time `gorm:"column:retired_at"` +} + +func (credentialRow) TableName() string { return "provider_credentials" } + +type providerModelRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + ProviderID uint64 `gorm:"column:provider_id"` + Name string `gorm:"column:name"` + ModelID string `gorm:"column:model_id"` + APIType model.APIType `gorm:"column:api_type"` + Kind model.GenerationKind `gorm:"column:kind"` + ExtraBody json.RawMessage `gorm:"column:extra_body"` + TimeoutMS uint32 `gorm:"column:timeout_ms"` + Weight uint32 `gorm:"column:weight"` + Enabled bool `gorm:"column:enabled"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (providerModelRow) TableName() string { return "provider_models" } + +type modelCapabilityRow struct { + ProviderModelID uint64 `gorm:"column:provider_model_id;primaryKey"` + Capability model.Capability `gorm:"column:capability;primaryKey"` +} + +func (modelCapabilityRow) TableName() string { return "provider_model_capabilities" } + +type promptTemplateRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + TemplateKey string `gorm:"column:template_key"` + Kind model.GenerationKind `gorm:"column:kind"` + APIType model.APIType `gorm:"column:api_type"` + Capability model.Capability `gorm:"column:capability"` + Name string `gorm:"column:name"` + Version uint32 `gorm:"column:version"` + TemplateText string `gorm:"column:template_text"` + DefaultRoleRule string `gorm:"column:default_role_rule"` + Enabled bool `gorm:"column:enabled"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (promptTemplateRow) TableName() string { return "prompt_templates" } + +type routePoolRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + Slug string `gorm:"column:slug"` + Name string `gorm:"column:name"` + Capability model.Capability `gorm:"column:capability"` + PromptTemplateID uint64 `gorm:"column:prompt_template_id"` + MaxFailover uint32 `gorm:"column:max_failover"` + Version uint32 `gorm:"column:version"` + Enabled bool `gorm:"column:enabled"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (routePoolRow) TableName() string { return "route_pools" } + +type routeMemberRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + RoutePoolID uint64 `gorm:"column:route_pool_id"` + ProviderModelID uint64 `gorm:"column:provider_model_id"` + Weight uint32 `gorm:"column:weight"` + FailureThreshold uint32 `gorm:"column:failure_threshold"` + OpenSeconds uint32 `gorm:"column:open_seconds"` + HalfOpenMax uint32 `gorm:"column:half_open_max"` + Enabled bool `gorm:"column:enabled"` + Position uint32 `gorm:"column:position"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (routeMemberRow) TableName() string { return "route_pool_members" } + +type activeRouteRow struct { + Capability model.Capability `gorm:"column:capability;primaryKey"` + RoutePoolID uint64 `gorm:"column:route_pool_id"` + OperatorID uint64 `gorm:"column:operator_id"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (activeRouteRow) TableName() string { return "active_routes" } + +type connectivityRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + ProviderModelID uint64 `gorm:"column:provider_model_id"` + OperatorID uint64 `gorm:"column:operator_id"` + Status string `gorm:"column:status"` + ErrorCode *string `gorm:"column:error_code"` + ErrorMessage *string `gorm:"column:error_message"` + LatencyMS *uint32 `gorm:"column:latency_ms"` + StartedAt time.Time `gorm:"column:started_at"` + CompletedAt *time.Time `gorm:"column:completed_at"` + CooldownUntil time.Time `gorm:"column:cooldown_until"` + CreatedAt time.Time `gorm:"column:created_at"` +} + +func (connectivityRow) TableName() string { return "provider_connectivity_checks" } + +type auditRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + Operator uint64 `gorm:"column:operator_id"` + Action string `gorm:"column:action"` + Target string `gorm:"column:target_type"` + TargetID string `gorm:"column:target_id"` + Result string `gorm:"column:result"` + RequestID string `gorm:"column:request_id"` + Summary json.RawMessage `gorm:"column:summary"` + CreatedAt time.Time `gorm:"column:created_at"` +} + +func (auditRow) TableName() string { return "admin_audit_events" } + +type portalUserRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + Email string `gorm:"column:email"` + DisplayName string `gorm:"column:display_name"` + Status string `gorm:"column:status"` + CreatedAt time.Time `gorm:"column:created_at"` + UpdatedAt time.Time `gorm:"column:updated_at"` +} + +func (portalUserRow) TableName() string { return "users" } + +type generationRow struct { + ID uint64 `gorm:"column:id;primaryKey"` + UserID uint64 `gorm:"column:user_id"` + Status model.GenerationStatus `gorm:"column:status"` + Kind model.GenerationKind `gorm:"column:kind"` + RenderedPrompt string `gorm:"column:rendered_prompt"` + Attempts json.RawMessage `gorm:"column:attempts"` + ErrorCode *string `gorm:"column:error_code"` + ErrorMessage *string `gorm:"column:error_message"` + ProviderAttemptCount uint32 `gorm:"column:provider_attempt_count"` + CreatedAt time.Time `gorm:"column:created_at"` + CompletedAt *time.Time `gorm:"column:completed_at"` +} + +func (generationRow) TableName() string { return "generations" } + +func (s *Service) CreateProvider(ctx context.Context, actor uint64, requestID string, input ProviderInput) (ProviderView, error) { + if err := validateProvider(input, true); err != nil { + return ProviderView{}, err + } + var id uint64 + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + // MySQL validates the provider's active-credential foreign-key cycle per + // statement. Start credential-backed providers as auth_type=none, then + // switch both columns together after the encrypted credential exists. + initialAuthType := input.AuthType + if input.APIKey != nil { + initialAuthType = string(provider.AuthNone) + } + row := providerRow{Slug: strings.TrimSpace(input.Slug), Name: strings.TrimSpace(input.Name), BaseURL: strings.TrimSpace(input.BaseURL), AuthType: initialAuthType, Enabled: input.Enabled} + if err := tx.Create(&row).Error; err != nil { + return fmt.Errorf("create provider: %w", err) + } + if input.APIKey != nil { + credential, err := s.newCredential(ctx, row.ID, 1, actor, *input.APIKey) + if err != nil { + return err + } + if err := tx.Create(&credential).Error; err != nil { + return fmt.Errorf("create provider credential: %w", err) + } + if err := tx.Model(&providerRow{}).Where("id = ?", row.ID).Updates(map[string]any{"auth_type": input.AuthType, "active_credential_id": credential.ID}).Error; err != nil { + return fmt.Errorf("activate provider credential: %w", err) + } + row.AuthType = input.AuthType + } + if row.Enabled && input.AuthType != string(provider.AuthNone) && input.APIKey == nil { + return FieldError{Field: "api_key", Message: "is required before enabling this provider"} + } + if err := s.audit(tx, actor, requestID, "provider.create", "provider", row.ID, "succeeded", map[string]any{"enabled": row.Enabled, "auth_type": row.AuthType}); err != nil { + return err + } + id = row.ID + return nil + }) + if err != nil { + return ProviderView{}, err + } + return s.Provider(ctx, id) +} + +func (s *Service) UpdateProvider(ctx context.Context, actor uint64, requestID string, id uint64, input ProviderInput) (ProviderView, error) { + if id == 0 { + return ProviderView{}, FieldError{Field: "id", Message: "must be positive"} + } + if input.APIKey != nil { + return ProviderView{}, FieldError{Field: "api_key", Message: "use the credential rotation endpoint"} + } + if err := validateProvider(input, false); err != nil { + return ProviderView{}, err + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var row providerRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil { + return translateNotFound(err) + } + if input.AuthType == string(provider.AuthNone) && row.ActiveCredentialID != nil { + return FieldError{Field: "auth_type", Message: "cannot be none while a credential is active"} + } + if input.Enabled && input.AuthType != string(provider.AuthNone) && row.ActiveCredentialID == nil { + return FieldError{Field: "enabled", Message: "requires an active credential"} + } + updates := map[string]any{"slug": strings.TrimSpace(input.Slug), "name": strings.TrimSpace(input.Name), "base_url": strings.TrimSpace(input.BaseURL), "auth_type": input.AuthType, "enabled": input.Enabled} + if err := tx.Model(&providerRow{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return fmt.Errorf("update provider: %w", err) + } + return s.audit(tx, actor, requestID, "provider.update", "provider", id, "succeeded", map[string]any{"enabled": input.Enabled, "auth_type": input.AuthType}) + }) + if err != nil { + return ProviderView{}, err + } + return s.Provider(ctx, id) +} + +func (s *Service) RotateCredential(ctx context.Context, actor uint64, requestID string, providerID uint64, apiKey string) (ProviderView, error) { + if providerID == 0 { + return ProviderView{}, FieldError{Field: "provider_id", Message: "must be positive"} + } + if strings.TrimSpace(apiKey) == "" { + return ProviderView{}, FieldError{Field: "api_key", Message: "must not be empty"} + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var row providerRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, providerID).Error; err != nil { + return translateNotFound(err) + } + if row.AuthType == string(provider.AuthNone) { + return FieldError{Field: "auth_type", Message: "does not accept credentials"} + } + var latest uint32 + if err := tx.Model(&credentialRow{}).Where("provider_id = ?", row.ID).Select("COALESCE(MAX(credential_version), 0)").Scan(&latest).Error; err != nil { + return fmt.Errorf("read credential version: %w", err) + } + credential, err := s.newCredential(ctx, row.ID, latest+1, actor, apiKey) + if err != nil { + return err + } + now := s.nowUTC() + if err := tx.Model(&credentialRow{}).Where("provider_id = ? AND status = 'active'", row.ID).Updates(map[string]any{"status": "retired", "retired_at": now, "updated_by": actor}).Error; err != nil { + return fmt.Errorf("retire provider credential: %w", err) + } + if err := tx.Create(&credential).Error; err != nil { + return fmt.Errorf("create rotated credential: %w", err) + } + if err := tx.Model(&providerRow{}).Where("id = ?", row.ID).Update("active_credential_id", credential.ID).Error; err != nil { + return fmt.Errorf("activate rotated credential: %w", err) + } + return s.audit(tx, actor, requestID, "provider.credential.rotate", "provider", row.ID, "succeeded", map[string]any{"credential_version": credential.CredentialVersion, "key_id": credential.KeyID}) + }) + if err != nil { + return ProviderView{}, err + } + return s.Provider(ctx, providerID) +} + +func (s *Service) ActivateCredential(ctx context.Context, actor uint64, requestID string, providerID uint64, version uint32) (ProviderView, error) { + if providerID == 0 || version == 0 { + return ProviderView{}, FieldError{Field: "credential_version", Message: "must be positive"} + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var providerRecord providerRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&providerRecord, providerID).Error; err != nil { + return translateNotFound(err) + } + if providerRecord.AuthType == string(provider.AuthNone) { + return FieldError{Field: "auth_type", Message: "does not accept credentials"} + } + var selected credentialRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider_id = ? AND credential_version = ?", providerID, version).First(&selected).Error; err != nil { + return translateNotFound(err) + } + now := s.nowUTC() + if err := tx.Model(&credentialRow{}).Where("provider_id = ? AND status = 'active'", providerID).Updates(map[string]any{"status": "retired", "retired_at": now, "updated_by": actor}).Error; err != nil { + return fmt.Errorf("retire active credential: %w", err) + } + if err := tx.Model(&credentialRow{}).Where("id = ?", selected.ID).Updates(map[string]any{"status": "active", "retired_at": nil, "updated_by": actor}).Error; err != nil { + return fmt.Errorf("reactivate provider credential: %w", err) + } + if err := tx.Model(&providerRow{}).Where("id = ?", providerID).Update("active_credential_id", selected.ID).Error; err != nil { + return fmt.Errorf("set active credential: %w", err) + } + return s.audit(tx, actor, requestID, "provider.credential.activate", "provider", providerID, "succeeded", map[string]any{"credential_version": version, "key_id": selected.KeyID}) + }) + if err != nil { + return ProviderView{}, err + } + return s.Provider(ctx, providerID) +} + +func (s *Service) Providers(ctx context.Context) ([]ProviderView, error) { + var rows []providerRow + if err := s.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list providers: %w", err) + } + return s.providerViews(ctx, rows) +} + +func (s *Service) Provider(ctx context.Context, id uint64) (ProviderView, error) { + if id == 0 { + return ProviderView{}, ErrNotFound + } + var row providerRow + if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil { + return ProviderView{}, translateNotFound(err) + } + items, err := s.providerViews(ctx, []providerRow{row}) + if err != nil { + return ProviderView{}, err + } + return items[0], nil +} + +func (s *Service) CreateProviderModel(ctx context.Context, actor uint64, requestID string, input ProviderModelInput) (ProviderModelView, error) { + if err := validateProviderModel(input); err != nil { + return ProviderModelView{}, err + } + var id uint64 + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := ensureProvider(tx, input.ProviderID); err != nil { + return err + } + row := providerModelRow{ProviderID: input.ProviderID, Name: strings.TrimSpace(input.Name), ModelID: strings.TrimSpace(input.ModelID), APIType: input.APIType, Kind: input.Kind, ExtraBody: normalizedExtraBody(input.ExtraBody), TimeoutMS: input.TimeoutMS, Weight: input.Weight, Enabled: input.Enabled} + if err := tx.Create(&row).Error; err != nil { + return fmt.Errorf("create provider model: %w", err) + } + if err := replaceCapabilities(tx, row.ID, input.Capabilities); err != nil { + return err + } + if err := s.audit(tx, actor, requestID, "provider_model.create", "provider_model", row.ID, "succeeded", map[string]any{"enabled": row.Enabled, "api_type": row.APIType}); err != nil { + return err + } + id = row.ID + return nil + }) + if err != nil { + return ProviderModelView{}, err + } + return s.ProviderModel(ctx, id) +} + +func (s *Service) UpdateProviderModel(ctx context.Context, actor uint64, requestID string, id uint64, input ProviderModelInput) (ProviderModelView, error) { + if id == 0 { + return ProviderModelView{}, FieldError{Field: "id", Message: "must be positive"} + } + if err := validateProviderModel(input); err != nil { + return ProviderModelView{}, err + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var existing providerModelRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&existing, id).Error; err != nil { + return translateNotFound(err) + } + if err := ensureProvider(tx, input.ProviderID); err != nil { + return err + } + updates := map[string]any{"provider_id": input.ProviderID, "name": strings.TrimSpace(input.Name), "model_id": strings.TrimSpace(input.ModelID), "api_type": input.APIType, "kind": input.Kind, "extra_body": normalizedExtraBody(input.ExtraBody), "timeout_ms": input.TimeoutMS, "weight": input.Weight, "enabled": input.Enabled} + if err := tx.Model(&providerModelRow{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return fmt.Errorf("update provider model: %w", err) + } + if err := replaceCapabilities(tx, id, input.Capabilities); err != nil { + return err + } + return s.audit(tx, actor, requestID, "provider_model.update", "provider_model", id, "succeeded", map[string]any{"enabled": input.Enabled, "api_type": input.APIType}) + }) + if err != nil { + return ProviderModelView{}, err + } + return s.ProviderModel(ctx, id) +} + +func (s *Service) ProviderModels(ctx context.Context) ([]ProviderModelView, error) { + var rows []providerModelRow + if err := s.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list provider models: %w", err) + } + return s.providerModelViews(ctx, rows) +} + +func (s *Service) ProviderModel(ctx context.Context, id uint64) (ProviderModelView, error) { + var row providerModelRow + if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil { + return ProviderModelView{}, translateNotFound(err) + } + items, err := s.providerModelViews(ctx, []providerModelRow{row}) + if err != nil { + return ProviderModelView{}, err + } + return items[0], nil +} + +func (s *Service) CreatePromptTemplate(ctx context.Context, actor uint64, requestID string, input PromptTemplateInput) (PromptTemplateView, error) { + if err := validatePromptTemplate(input); err != nil { + return PromptTemplateView{}, err + } + var id uint64 + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + row := promptTemplateRow{TemplateKey: strings.TrimSpace(input.TemplateKey), Kind: input.Kind, APIType: input.APIType, Capability: input.Capability, Name: strings.TrimSpace(input.Name), Version: 1, TemplateText: input.TemplateText, DefaultRoleRule: input.DefaultRoleRule, Enabled: input.Enabled} + if err := tx.Create(&row).Error; err != nil { + return fmt.Errorf("create prompt template: %w", err) + } + if err := s.audit(tx, actor, requestID, "prompt_template.create", "prompt_template", row.ID, "succeeded", map[string]any{"enabled": row.Enabled, "capability": row.Capability, "version": row.Version}); err != nil { + return err + } + id = row.ID + return nil + }) + if err != nil { + return PromptTemplateView{}, err + } + return s.PromptTemplate(ctx, id) +} + +func (s *Service) UpdatePromptTemplate(ctx context.Context, actor uint64, requestID string, id uint64, input PromptTemplateInput) (PromptTemplateView, error) { + if id == 0 { + return PromptTemplateView{}, FieldError{Field: "id", Message: "must be positive"} + } + if err := validatePromptTemplate(input); err != nil { + return PromptTemplateView{}, err + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var existing promptTemplateRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&existing, id).Error; err != nil { + return translateNotFound(err) + } + updates := map[string]any{"template_key": strings.TrimSpace(input.TemplateKey), "kind": input.Kind, "api_type": input.APIType, "capability": input.Capability, "name": strings.TrimSpace(input.Name), "template_text": input.TemplateText, "default_role_rule": input.DefaultRoleRule, "enabled": input.Enabled, "version": existing.Version + 1} + if err := tx.Model(&promptTemplateRow{}).Where("id = ?", id).Updates(updates).Error; err != nil { + return fmt.Errorf("update prompt template: %w", err) + } + return s.audit(tx, actor, requestID, "prompt_template.update", "prompt_template", id, "succeeded", map[string]any{"enabled": input.Enabled, "capability": input.Capability, "version": existing.Version + 1}) + }) + if err != nil { + return PromptTemplateView{}, err + } + return s.PromptTemplate(ctx, id) +} + +func (s *Service) PromptTemplates(ctx context.Context) ([]PromptTemplateView, error) { + var rows []promptTemplateRow + if err := s.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list prompt templates: %w", err) + } + return promptViews(rows), nil +} + +func (s *Service) PromptTemplate(ctx context.Context, id uint64) (PromptTemplateView, error) { + var row promptTemplateRow + if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil { + return PromptTemplateView{}, translateNotFound(err) + } + return promptViews([]promptTemplateRow{row})[0], nil +} + +func (s *Service) CreateRoutePool(ctx context.Context, actor uint64, requestID string, input RoutePoolInput) (RoutePoolView, error) { + if input.Version != 0 { + return RoutePoolView{}, FieldError{Field: "version", Message: "must be omitted when creating a route pool"} + } + if err := validateRoutePoolInput(input, true); err != nil { + return RoutePoolView{}, err + } + var id uint64 + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := s.validateRoutePoolReferences(tx, input); err != nil { + return err + } + row := routePoolRow{Slug: strings.TrimSpace(input.Slug), Name: strings.TrimSpace(input.Name), Capability: input.Capability, PromptTemplateID: input.PromptTemplateID, MaxFailover: input.MaxFailover, Version: 1, Enabled: input.Enabled} + if err := tx.Create(&row).Error; err != nil { + return fmt.Errorf("create route pool: %w", err) + } + if err := replaceRouteMembers(tx, row.ID, input.Members); err != nil { + return err + } + if input.Publish { + if err := s.publishRoute(tx, actor, row, input); err != nil { + return err + } + } + if err := s.audit(tx, actor, requestID, "route_pool.create", "route_pool", row.ID, "succeeded", map[string]any{"capability": row.Capability, "version": row.Version, "published": input.Publish}); err != nil { + return err + } + id = row.ID + return nil + }) + if err != nil { + return RoutePoolView{}, err + } + return s.RoutePool(ctx, id) +} + +func (s *Service) UpdateRoutePool(ctx context.Context, actor uint64, requestID string, id uint64, input RoutePoolInput) (RoutePoolView, error) { + if id == 0 || input.Version == 0 { + return RoutePoolView{}, FieldError{Field: "version", Message: "is required for route pool updates"} + } + if err := validateRoutePoolInput(input, true); err != nil { + return RoutePoolView{}, err + } + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var existing routePoolRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&existing, id).Error; err != nil { + return translateNotFound(err) + } + if existing.Version != input.Version { + return ErrConflict + } + if err := s.validateRoutePoolReferences(tx, input); err != nil { + return err + } + updates := map[string]any{"slug": strings.TrimSpace(input.Slug), "name": strings.TrimSpace(input.Name), "capability": input.Capability, "prompt_template_id": input.PromptTemplateID, "max_failover": input.MaxFailover, "enabled": input.Enabled, "version": existing.Version + 1} + if err := tx.Model(&routePoolRow{}).Where("id = ? AND version = ?", id, existing.Version).Updates(updates).Error; err != nil { + return fmt.Errorf("update route pool: %w", err) + } + if err := replaceRouteMembers(tx, id, input.Members); err != nil { + return err + } + updated := existing + updated.Version++ + updated.Capability, updated.PromptTemplateID, updated.MaxFailover, updated.Enabled = input.Capability, input.PromptTemplateID, input.MaxFailover, input.Enabled + if input.Publish { + if err := s.publishRoute(tx, actor, updated, input); err != nil { + return err + } + } + return s.audit(tx, actor, requestID, "route_pool.update", "route_pool", id, "succeeded", map[string]any{"capability": input.Capability, "version": updated.Version, "published": input.Publish}) + }) + if err != nil { + return RoutePoolView{}, err + } + return s.RoutePool(ctx, id) +} + +func (s *Service) RoutePools(ctx context.Context) ([]RoutePoolView, error) { + var rows []routePoolRow + if err := s.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list route pools: %w", err) + } + items := make([]RoutePoolView, 0, len(rows)) + for _, row := range rows { + item, err := s.routePoolView(ctx, row) + if err != nil { + return nil, err + } + items = append(items, item) + } + return items, nil +} + +func (s *Service) RoutePool(ctx context.Context, id uint64) (RoutePoolView, error) { + var row routePoolRow + if err := s.db.WithContext(ctx).First(&row, id).Error; err != nil { + return RoutePoolView{}, translateNotFound(err) + } + return s.routePoolView(ctx, row) +} + +func (s *Service) Users(ctx context.Context) ([]UserView, error) { + var rows []portalUserRow + if err := s.db.WithContext(ctx).Order("id ASC").Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list users: %w", err) + } + items := make([]UserView, 0, len(rows)) + for _, row := range rows { + items = append(items, UserView{ID: row.ID, Email: row.Email, DisplayName: row.DisplayName, Status: row.Status, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}) + } + return items, nil +} + +func (s *Service) Generations(ctx context.Context) ([]GenerationView, error) { + var rows []generationRow + if err := s.db.WithContext(ctx).Order("created_at DESC, id DESC").Limit(100).Find(&rows).Error; err != nil { + return nil, fmt.Errorf("list generations: %w", err) + } + items := make([]GenerationView, 0, len(rows)) + for _, row := range rows { + item := GenerationView{ID: row.ID, UserID: row.UserID, Status: row.Status, Kind: row.Kind, RenderedPrompt: truncate(row.RenderedPrompt, 1024), Attempts: append(json.RawMessage(nil), row.Attempts...), ProviderAttemptCnt: row.ProviderAttemptCount, CreatedAt: row.CreatedAt, CompletedAt: row.CompletedAt} + if row.ErrorCode != nil { + item.ErrorCode = *row.ErrorCode + } + if row.ErrorMessage != nil { + item.ErrorMessage = truncate(*row.ErrorMessage, 256) + } + items = append(items, item) + } + return items, nil +} + +func (s *Service) StartConnectivityCheck(ctx context.Context, actor uint64, requestID string, providerModelID uint64) (ConnectivityCheckView, error) { + if providerModelID == 0 { + return ConnectivityCheckView{}, FieldError{Field: "provider_model_id", Message: "must be positive"} + } + if !s.allowConnectivityChecks { + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := ensureProviderModel(tx, providerModelID); err != nil { + return err + } + return s.audit(tx, actor, requestID, "provider_model.connectivity.blocked", "provider_model", providerModelID, "failed", map[string]any{"reason": "explicit authorization required"}) + }) + if err != nil { + return ConnectivityCheckView{}, err + } + return ConnectivityCheckView{}, ErrConnectivityDisabled + } + + var check connectivityRow + err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := ensureProviderModelLocked(tx, providerModelID); err != nil { + return err + } + var latest connectivityRow + result := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("provider_model_id = ?", providerModelID).Order("started_at DESC, id DESC").Limit(1).Find(&latest) + if result.Error != nil { + return fmt.Errorf("read latest connectivity check: %w", result.Error) + } + now := s.nowUTC() + if result.RowsAffected == 1 && latest.CooldownUntil.After(now) { + return CooldownError{RetryAfterSeconds: secondsUntil(latest.CooldownUntil, now)} + } + check = connectivityRow{ProviderModelID: providerModelID, OperatorID: actor, Status: "running", StartedAt: now, CooldownUntil: now.Add(s.connectivityCooldown)} + if err := tx.Create(&check).Error; err != nil { + return fmt.Errorf("reserve connectivity check: %w", err) + } + return s.audit(tx, actor, requestID, "provider_model.connectivity.start", "provider_model", providerModelID, "succeeded", map[string]any{"check_id": check.ID}) + }) + if err != nil { + return ConnectivityCheckView{}, err + } + + started := s.nowUTC() + configuration, configErr := s.probeConfiguration(ctx, providerModelID) + var probeErr error + if configErr != nil { + probeErr = configErr + } else { + probeErr = s.probe.Probe(ctx, configuration) + } + completed := s.nowUTC() + latencyMillis := completed.Sub(started).Milliseconds() + if latencyMillis < 0 { + latencyMillis = 0 + } + if uint64(latencyMillis) > uint64(^uint32(0)) { + latencyMillis = int64(^uint32(0)) + } + latency := uint32(latencyMillis) + check.Status, check.CompletedAt, check.LatencyMS = "succeeded", &completed, &latency + if probeErr != nil { + code, message := "connectivity_failed", "connectivity check failed" + check.Status, check.ErrorCode, check.ErrorMessage = "failed", &code, &message + } + if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + updates := map[string]any{"status": check.Status, "completed_at": completed, "latency_ms": latency, "error_code": check.ErrorCode, "error_message": check.ErrorMessage} + if err := tx.Model(&connectivityRow{}).Where("id = ? AND status = 'running'", check.ID).Updates(updates).Error; err != nil { + return fmt.Errorf("complete connectivity check: %w", err) + } + result := "succeeded" + if probeErr != nil { + result = "failed" + } + return s.audit(tx, actor, requestID+":complete", "provider_model.connectivity.complete", "provider_model", providerModelID, result, map[string]any{"check_id": check.ID, "latency_ms": latency, "error_code": check.ErrorCode}) + }); err != nil { + return ConnectivityCheckView{}, err + } + return connectivityView(check), nil +} + +func (s *Service) newCredential(ctx context.Context, providerID uint64, version uint32, actor uint64, apiKey string) (credentialRow, error) { + bytes := []byte(apiKey) + defer clear(bytes) + envelope, err := s.cipher.Encrypt(ctx, bytes) + if err != nil { + return credentialRow{}, errors.New("encrypt provider credential") + } + encoded, err := json.Marshal(envelope) + if err != nil { + return credentialRow{}, errors.New("encode provider credential") + } + return credentialRow{ProviderID: providerID, CredentialVersion: version, APIKeyEnc: encoded, KeyID: envelope.KeyID, Status: "active", CreatedBy: actor, UpdatedBy: actor}, nil +} + +func (s *Service) providerModelViews(ctx context.Context, rows []providerModelRow) ([]ProviderModelView, error) { + if len(rows) == 0 { + return []ProviderModelView{}, nil + } + ids := make([]uint64, 0, len(rows)) + providerIDs := make([]uint64, 0, len(rows)) + for _, row := range rows { + ids, providerIDs = append(ids, row.ID), append(providerIDs, row.ProviderID) + } + var capabilities []modelCapabilityRow + if err := s.db.WithContext(ctx).Where("provider_model_id IN ?", ids).Find(&capabilities).Error; err != nil { + return nil, fmt.Errorf("list model capabilities: %w", err) + } + var providers []providerRow + if err := s.db.WithContext(ctx).Where("id IN ?", providerIDs).Find(&providers).Error; err != nil { + return nil, fmt.Errorf("list model providers: %w", err) + } + capabilityByID := make(map[uint64][]model.Capability, len(rows)) + for _, capability := range capabilities { + capabilityByID[capability.ProviderModelID] = append(capabilityByID[capability.ProviderModelID], capability.Capability) + } + providerName := make(map[uint64]string, len(providers)) + for _, provider := range providers { + providerName[provider.ID] = provider.Name + } + items := make([]ProviderModelView, 0, len(rows)) + for _, row := range rows { + caps := capabilityByID[row.ID] + slices.Sort(caps) + items = append(items, ProviderModelView{ID: row.ID, ProviderID: row.ProviderID, ProviderName: providerName[row.ProviderID], Name: row.Name, ModelID: row.ModelID, APIType: row.APIType, Kind: row.Kind, Capabilities: caps, ExtraBody: append(json.RawMessage(nil), row.ExtraBody...), TimeoutMS: row.TimeoutMS, Weight: row.Weight, Enabled: row.Enabled, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}) + } + return items, nil +} + +func (s *Service) providerViews(ctx context.Context, rows []providerRow) ([]ProviderView, error) { + if len(rows) == 0 { + return []ProviderView{}, nil + } + credentialIDs := make([]uint64, 0, len(rows)) + for _, row := range rows { + if row.ActiveCredentialID != nil { + credentialIDs = append(credentialIDs, *row.ActiveCredentialID) + } + } + credentialsByID := make(map[uint64]credentialRow, len(credentialIDs)) + if len(credentialIDs) > 0 { + var credentials []credentialRow + if err := s.db.WithContext(ctx).Where("id IN ? AND status = 'active'", credentialIDs).Find(&credentials).Error; err != nil { + return nil, fmt.Errorf("list active provider credentials: %w", err) + } + for _, credential := range credentials { + credentialsByID[credential.ID] = credential + } + } + items := make([]ProviderView, 0, len(rows)) + for _, row := range rows { + var version *uint32 + var keyID *string + if row.ActiveCredentialID != nil { + if credential, ok := credentialsByID[*row.ActiveCredentialID]; ok { + version, keyID = &credential.CredentialVersion, &credential.KeyID + } + } + items = append(items, providerView(row, version, keyID)) + } + return items, nil +} + +func (s *Service) validateRoutePoolReferences(tx *gorm.DB, input RoutePoolInput) error { + var template promptTemplateRow + if err := tx.First(&template, input.PromptTemplateID).Error; err != nil { + return FieldError{Field: "prompt_template_id", Message: "does not exist"} + } + if template.Capability != input.Capability { + return FieldError{Field: "prompt_template_id", Message: "does not match route capability"} + } + if len(input.Members) == 0 { + return nil + } + modelIDs := make([]uint64, 0, len(input.Members)) + for _, member := range input.Members { + modelIDs = append(modelIDs, member.ProviderModelID) + } + var models []providerModelRow + if err := tx.Where("id IN ?", modelIDs).Find(&models).Error; err != nil { + return fmt.Errorf("load route models: %w", err) + } + if len(models) != len(modelIDs) { + return FieldError{Field: "members", Message: "contains an unknown provider model"} + } + var capabilities []modelCapabilityRow + if err := tx.Where("provider_model_id IN ? AND capability = ?", modelIDs, input.Capability).Find(&capabilities).Error; err != nil { + return fmt.Errorf("load route capabilities: %w", err) + } + if len(capabilities) != len(modelIDs) { + return FieldError{Field: "members", Message: "contains a model without the route capability"} + } + if input.Publish { + if !input.Enabled { + return FieldError{Field: "enabled", Message: "must be true when publishing"} + } + providerIDs := make([]uint64, 0, len(models)) + for _, row := range models { + providerIDs = append(providerIDs, row.ProviderID) + } + var providers []providerRow + if err := tx.Where("id IN ?", providerIDs).Find(&providers).Error; err != nil { + return fmt.Errorf("load route providers: %w", err) + } + providersByID := make(map[uint64]providerRow, len(providers)) + for _, row := range providers { + providersByID[row.ID] = row + } + hasEnabled := false + for _, member := range input.Members { + modelRecord := findModel(models, member.ProviderModelID) + providerRecord := providersByID[modelRecord.ProviderID] + if member.Enabled && modelRecord.Enabled && providerRecord.Enabled && (providerRecord.AuthType == string(provider.AuthNone) || providerRecord.ActiveCredentialID != nil) { + hasEnabled = true + } + } + if !hasEnabled { + return FieldError{Field: "members", Message: "publishing requires an enabled, credential-ready member"} + } + } + return nil +} + +func (s *Service) publishRoute(tx *gorm.DB, actor uint64, row routePoolRow, input RoutePoolInput) error { + if !input.Publish { + return nil + } + active := activeRouteRow{Capability: row.Capability, RoutePoolID: row.ID, OperatorID: actor} + if err := tx.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "capability"}}, DoUpdates: clause.Assignments(map[string]any{"route_pool_id": row.ID, "operator_id": actor, "updated_at": s.nowUTC()})}).Create(&active).Error; err != nil { + return fmt.Errorf("publish active route: %w", err) + } + return nil +} + +func (s *Service) routePoolView(ctx context.Context, row routePoolRow) (RoutePoolView, error) { + var members []routeMemberRow + if err := s.db.WithContext(ctx).Where("route_pool_id = ?", row.ID).Order("position ASC, id ASC").Find(&members).Error; err != nil { + return RoutePoolView{}, fmt.Errorf("list route members: %w", err) + } + var activeCount int64 + if err := s.db.WithContext(ctx).Model(&activeRouteRow{}).Where("capability = ? AND route_pool_id = ?", row.Capability, row.ID).Count(&activeCount).Error; err != nil { + return RoutePoolView{}, fmt.Errorf("read active route: %w", err) + } + items := make([]RouteMemberInput, 0, len(members)) + for _, member := range members { + items = append(items, RouteMemberInput{ProviderModelID: member.ProviderModelID, Weight: member.Weight, FailureThreshold: member.FailureThreshold, OpenSeconds: member.OpenSeconds, HalfOpenMax: member.HalfOpenMax, Enabled: member.Enabled, Position: member.Position}) + } + return RoutePoolView{ID: row.ID, Slug: row.Slug, Name: row.Name, Capability: row.Capability, PromptTemplateID: row.PromptTemplateID, MaxFailover: row.MaxFailover, Version: row.Version, Enabled: row.Enabled, Active: activeCount == 1, Members: items, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}, nil +} + +func (s *Service) probeConfiguration(ctx context.Context, providerModelID uint64) (ProbeConfiguration, error) { + type selected struct { + BaseURL string + AuthType string + APIKeyEnc json.RawMessage + ModelID string + APIType model.APIType + Kind model.GenerationKind + ExtraBody json.RawMessage + TimeoutMS uint32 + } + var row selected + result := s.db.WithContext(ctx).Raw(` + SELECT p.base_url, p.auth_type, credential.api_key_enc, pm.model_id, pm.api_type, pm.kind, pm.extra_body, pm.timeout_ms + FROM provider_models pm + JOIN providers p ON p.id = pm.provider_id + LEFT JOIN provider_credentials credential ON credential.id = p.active_credential_id AND credential.status = 'active' + WHERE pm.id = ? AND pm.enabled = TRUE AND p.enabled = TRUE + LIMIT 1`, providerModelID).Scan(&row) + if result.Error != nil { + return ProbeConfiguration{}, fmt.Errorf("load connectivity configuration: %w", result.Error) + } + if result.RowsAffected != 1 { + return ProbeConfiguration{}, errors.New("connectivity target is not enabled") + } + authType := provider.AuthType(row.AuthType) + if !authType.Valid() { + return ProbeConfiguration{}, errors.New("connectivity target authentication is invalid") + } + var apiKey string + if authType != provider.AuthNone { + var envelope corecrypto.Envelope + if json.Unmarshal(row.APIKeyEnc, &envelope) != nil { + return ProbeConfiguration{}, errors.New("connectivity credential is invalid") + } + plaintext, err := s.cipher.Decrypt(ctx, envelope) + if err != nil { + return ProbeConfiguration{}, errors.New("connectivity credential is unavailable") + } + apiKey = string(plaintext) + clear(plaintext) + } + var capabilityRows []modelCapabilityRow + if err := s.db.WithContext(ctx).Where("provider_model_id = ?", providerModelID).Find(&capabilityRows).Error; err != nil { + return ProbeConfiguration{}, fmt.Errorf("load connectivity capabilities: %w", err) + } + capabilities := make([]model.Capability, 0, len(capabilityRows)) + for _, capability := range capabilityRows { + capabilities = append(capabilities, capability.Capability) + } + return ProbeConfiguration{BaseURL: row.BaseURL, AuthType: authType, APIKey: apiKey, ModelID: row.ModelID, APIType: row.APIType, Kind: row.Kind, Capabilities: capabilities, ExtraBody: row.ExtraBody, Timeout: time.Duration(row.TimeoutMS) * time.Millisecond, MaxResponseBytes: s.maxResponseBytes}, nil +} + +func (s *Service) audit(tx *gorm.DB, actor uint64, requestID, action, target string, targetID uint64, result string, summary map[string]any) error { + if requestID == "" { + requestID = uuid.NewString() + } + if len(requestID) > 128 { + requestID = requestID[:128] + } + encoded, err := json.Marshal(summary) + if err != nil { + return errors.New("encode admin audit summary") + } + event := auditRow{Operator: actor, Action: action, Target: target, TargetID: fmt.Sprintf("%d", targetID), Result: result, RequestID: requestID, Summary: encoded} + if err := tx.Create(&event).Error; err != nil { + return fmt.Errorf("write admin audit event: %w", err) + } + return nil +} + +func (s *Service) nowUTC() time.Time { return s.now().UTC() } + +func validateProvider(input ProviderInput, creating bool) error { + if strings.TrimSpace(input.Slug) == "" || len(input.Slug) > 80 { + return FieldError{Field: "slug", Message: "must be 1 to 80 characters"} + } + if strings.TrimSpace(input.Name) == "" || len(input.Name) > 120 { + return FieldError{Field: "name", Message: "must be 1 to 120 characters"} + } + if err := validateBaseURL(input.BaseURL); err != nil { + return err + } + authType := provider.AuthType(input.AuthType) + if !authType.Valid() { + return FieldError{Field: "auth_type", Message: "must be none, bearer, or x-goog-api-key"} + } + if input.APIKey != nil && strings.TrimSpace(*input.APIKey) == "" { + return FieldError{Field: "api_key", Message: "must not be empty when supplied"} + } + if authType == provider.AuthNone && input.APIKey != nil { + return FieldError{Field: "api_key", Message: "is not allowed when auth_type is none"} + } + if creating && input.Enabled && authType != provider.AuthNone && input.APIKey == nil { + return FieldError{Field: "api_key", Message: "is required before enabling this provider"} + } + return nil +} + +func validateBaseURL(raw string) error { + parsed, err := url.Parse(strings.TrimSpace(raw)) + if err != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" || parsed.User != nil || parsed.RawQuery != "" || parsed.Fragment != "" { + return FieldError{Field: "base_url", Message: "must be an http or https origin or path without credentials, query, or fragment"} + } + return nil +} + +func validateProviderModel(input ProviderModelInput) error { + if input.ProviderID == 0 { + return FieldError{Field: "provider_id", Message: "must be positive"} + } + if strings.TrimSpace(input.Name) == "" || len(input.Name) > 120 || strings.TrimSpace(input.ModelID) == "" || len(input.ModelID) > 191 { + return FieldError{Field: "model_id", Message: "name and model_id are required within their limits"} + } + if input.TimeoutMS == 0 || input.Weight == 0 { + return FieldError{Field: "timeout_ms", Message: "timeout_ms and weight must be positive"} + } + if !input.Kind.Valid() || !validAPIType(input.APIType) { + return FieldError{Field: "api_type", Message: "is invalid"} + } + if err := validateCapabilities(input.APIType, input.Kind, input.Capabilities); err != nil { + return err + } + return validateExtraBody(input.ExtraBody) +} + +func validatePromptTemplate(input PromptTemplateInput) error { + if strings.TrimSpace(input.TemplateKey) == "" || len(input.TemplateKey) > 120 || strings.TrimSpace(input.Name) == "" || len(input.Name) > 120 { + return FieldError{Field: "template_key", Message: "template_key and name are required within their limits"} + } + if strings.TrimSpace(input.TemplateText) == "" || len(input.DefaultRoleRule) > 65535 || !input.Capability.Valid() || !input.Kind.Valid() || !validAPIType(input.APIType) { + return FieldError{Field: "template", Message: "template fields are invalid"} + } + if err := validateCapabilities(input.APIType, input.Kind, []model.Capability{input.Capability}); err != nil { + return FieldError{Field: "capability", Message: "does not match api_type and kind"} + } + return nil +} + +func validateRoutePoolInput(input RoutePoolInput, requireMembers bool) error { + if strings.TrimSpace(input.Slug) == "" || len(input.Slug) > 80 || strings.TrimSpace(input.Name) == "" || len(input.Name) > 120 { + return FieldError{Field: "slug", Message: "slug and name are required within their limits"} + } + if !input.Capability.Valid() || input.PromptTemplateID == 0 { + return FieldError{Field: "capability", Message: "capability and prompt_template_id are required"} + } + if len(input.Members) > maxRouteMembers || (requireMembers && input.Publish && len(input.Members) == 0) { + return FieldError{Field: "members", Message: "published route pools need 1 to 32 members"} + } + seenModels, seenPositions := map[uint64]bool{}, map[uint32]bool{} + for _, member := range input.Members { + if member.ProviderModelID == 0 || member.Weight == 0 || member.FailureThreshold == 0 || member.OpenSeconds == 0 || member.HalfOpenMax == 0 { + return FieldError{Field: "members", Message: "member model, weight, breaker threshold, open duration, and half-open limit must be positive"} + } + if seenModels[member.ProviderModelID] || seenPositions[member.Position] { + return FieldError{Field: "members", Message: "model and position must be unique"} + } + seenModels[member.ProviderModelID], seenPositions[member.Position] = true, true + } + for position := range input.Members { + if !seenPositions[uint32(position)] { + return FieldError{Field: "members", Message: "positions must be continuous from zero"} + } + } + return nil +} + +func validateCapabilities(apiType model.APIType, kind model.GenerationKind, capabilities []model.Capability) error { + if len(capabilities) == 0 || len(capabilities) > 3 { + return FieldError{Field: "capabilities", Message: "must contain one or more supported capabilities"} + } + seen := map[model.Capability]bool{} + for _, capability := range capabilities { + if !capability.Valid() || seen[capability] { + return FieldError{Field: "capabilities", Message: "contains an invalid or duplicate capability"} + } + seen[capability] = true + } + switch apiType { + case model.APIChat: + if kind != model.KindText || !seen[model.CapabilityText] || len(seen) != 1 { + return FieldError{Field: "capabilities", Message: "chat only supports text"} + } + case model.APIImages: + if kind != model.KindImage || !seen[model.CapabilityImageGenerate] || len(seen) != 1 { + return FieldError{Field: "capabilities", Message: "images only supports image_generate"} + } + case model.APIImagesEdits: + if kind != model.KindImage || !seen[model.CapabilityImageEdit] || len(seen) != 1 { + return FieldError{Field: "capabilities", Message: "images_edits only supports image_edit"} + } + case model.APIGemini: + if kind == model.KindText && (!seen[model.CapabilityText] || len(seen) != 1) { + return FieldError{Field: "capabilities", Message: "text Gemini models must declare text only"} + } + if kind == model.KindImage && seen[model.CapabilityText] { + return FieldError{Field: "capabilities", Message: "image Gemini models cannot declare text"} + } + default: + return FieldError{Field: "api_type", Message: "is invalid"} + } + return nil +} + +func validateExtraBody(raw json.RawMessage) error { + if len(raw) == 0 || string(raw) == "null" { + return nil + } + var fields map[string]any + if err := json.Unmarshal(raw, &fields); err != nil { + return FieldError{Field: "extra_body", Message: "must be a JSON object"} + } + allowed := map[string]bool{"temperature": true, "max_tokens": true, "size": true, "quality": true} + for key, value := range fields { + if !allowed[key] { + return FieldError{Field: "extra_body", Message: "contains a reserved or unsupported field"} + } + switch value.(type) { + case string, float64, bool: + default: + return FieldError{Field: "extra_body", Message: "values must be strings, numbers, or booleans"} + } + } + return nil +} + +func validAPIType(apiType model.APIType) bool { + return apiType == model.APIChat || apiType == model.APIImages || apiType == model.APIImagesEdits || apiType == model.APIGemini +} + +func replaceCapabilities(tx *gorm.DB, providerModelID uint64, capabilities []model.Capability) error { + if err := tx.Where("provider_model_id = ?", providerModelID).Delete(&modelCapabilityRow{}).Error; err != nil { + return fmt.Errorf("clear model capabilities: %w", err) + } + rows := make([]modelCapabilityRow, 0, len(capabilities)) + for _, capability := range capabilities { + rows = append(rows, modelCapabilityRow{ProviderModelID: providerModelID, Capability: capability}) + } + if err := tx.Create(&rows).Error; err != nil { + return fmt.Errorf("create model capabilities: %w", err) + } + return nil +} + +func replaceRouteMembers(tx *gorm.DB, routePoolID uint64, members []RouteMemberInput) error { + if err := tx.Where("route_pool_id = ?", routePoolID).Delete(&routeMemberRow{}).Error; err != nil { + return fmt.Errorf("replace route members: %w", err) + } + if len(members) == 0 { + return nil + } + rows := make([]routeMemberRow, 0, len(members)) + for _, member := range members { + rows = append(rows, routeMemberRow{RoutePoolID: routePoolID, ProviderModelID: member.ProviderModelID, Weight: member.Weight, FailureThreshold: member.FailureThreshold, OpenSeconds: member.OpenSeconds, HalfOpenMax: member.HalfOpenMax, Enabled: member.Enabled, Position: member.Position}) + } + if err := tx.Create(&rows).Error; err != nil { + return fmt.Errorf("create route members: %w", err) + } + return nil +} + +func ensureProvider(tx *gorm.DB, id uint64) error { + if id == 0 { + return FieldError{Field: "provider_id", Message: "must be positive"} + } + var count int64 + if err := tx.Model(&providerRow{}).Where("id = ?", id).Count(&count).Error; err != nil { + return fmt.Errorf("read provider: %w", err) + } + if count != 1 { + return FieldError{Field: "provider_id", Message: "does not exist"} + } + return nil +} + +func ensureProviderModel(tx *gorm.DB, id uint64) error { + var count int64 + if err := tx.Model(&providerModelRow{}).Where("id = ?", id).Count(&count).Error; err != nil { + return fmt.Errorf("read provider model: %w", err) + } + if count != 1 { + return ErrNotFound + } + return nil +} + +func ensureProviderModelLocked(tx *gorm.DB, id uint64) error { + var row providerModelRow + if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, id).Error; err != nil { + return translateNotFound(err) + } + return nil +} + +func providerView(row providerRow, version *uint32, keyID *string) ProviderView { + view := ProviderView{ID: row.ID, Slug: row.Slug, Name: row.Name, BaseURL: row.BaseURL, AuthType: row.AuthType, Enabled: row.Enabled, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt} + if row.ActiveCredentialID != nil && version != nil && keyID != nil { + view.Credential = CredentialInfo{HasCredential: true, Version: *version, KeyID: *keyID} + } + return view +} + +func promptViews(rows []promptTemplateRow) []PromptTemplateView { + items := make([]PromptTemplateView, 0, len(rows)) + for _, row := range rows { + items = append(items, PromptTemplateView{ID: row.ID, TemplateKey: row.TemplateKey, Kind: row.Kind, APIType: row.APIType, Capability: row.Capability, Name: row.Name, Version: row.Version, TemplateText: row.TemplateText, DefaultRoleRule: row.DefaultRoleRule, Enabled: row.Enabled, CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt}) + } + return items +} + +func connectivityView(row connectivityRow) ConnectivityCheckView { + view := ConnectivityCheckView{ID: row.ID, ProviderModel: row.ProviderModelID, Status: row.Status, LatencyMS: row.LatencyMS, StartedAt: row.StartedAt, CompletedAt: row.CompletedAt, CooldownUntil: row.CooldownUntil} + if row.ErrorCode != nil { + view.ErrorCode = *row.ErrorCode + } + if row.ErrorMessage != nil { + view.ErrorMessage = *row.ErrorMessage + } + return view +} + +func findModel(rows []providerModelRow, id uint64) providerModelRow { + for _, row := range rows { + if row.ID == id { + return row + } + } + return providerModelRow{} +} + +func translateNotFound(err error) error { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrNotFound + } + return err +} + +func normalizedExtraBody(raw json.RawMessage) json.RawMessage { + if len(raw) == 0 || string(raw) == "null" { + return json.RawMessage("{}") + } + return append(json.RawMessage(nil), raw...) +} + +func clear(value []byte) { + for index := range value { + value[index] = 0 + } +} + +func truncate(value string, limit int) string { + if len(value) <= limit { + return value + } + return value[:limit] +} + +func secondsUntil(until, now time.Time) uint32 { + seconds := math.Ceil(until.Sub(now).Seconds()) + if seconds < 1 { + return 1 + } + if seconds > float64(^uint32(0)) { + return ^uint32(0) + } + return uint32(seconds) +} diff --git a/admin/app/chorus/service_mysql_integration_test.go b/admin/app/chorus/service_mysql_integration_test.go new file mode 100644 index 0000000..fc51d94 --- /dev/null +++ b/admin/app/chorus/service_mysql_integration_test.go @@ -0,0 +1,329 @@ +package chorus + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "os" + "strings" + "sync/atomic" + "testing" + "time" + + "git.ilapage.cn/OPC/chorus/internal/core/model" + platformcrypto "git.ilapage.cn/OPC/chorus/internal/platform/crypto" + mysqldriver "github.com/go-sql-driver/mysql" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/logger" +) + +// These tests use only a locally designated disposable database. They are +// opt-in so routine package tests never touch a developer's MySQL instance. +func TestAdminServiceMySQL(t *testing.T) { + db := openAdminTestDB(t) + ctx := context.Background() + deleteAdminFixture(t, db, "admin-") + suffix := fmt.Sprintf("admin-%d", time.Now().UnixNano()) + registerActiveRouteRestore(t, db, model.CapabilityImageGenerate) + t.Cleanup(func() { deleteAdminFixture(t, db, suffix) }) + + service := newAdminTestService(t, db, Config{}) + secret := "synthetic-provider-secret-" + suffix + providerInput := ProviderInput{ + Slug: "provider-" + suffix, Name: "Provider " + suffix, + BaseURL: "https://provider.invalid/v1", AuthType: "bearer", Enabled: true, APIKey: &secret, + } + providerView, err := service.CreateProvider(ctx, 7, "create-"+suffix, providerInput) + if err != nil { + t.Fatalf("create provider: %v", err) + } + if !providerView.Credential.HasCredential || providerView.Credential.Version != 1 { + t.Fatalf("unexpected credential metadata: %#v", providerView.Credential) + } + response, err := json.Marshal(providerView) + if err != nil { + t.Fatal(err) + } + if bytes.Contains(response, []byte(secret)) || bytes.Contains(response, []byte("ciphertext")) { + t.Fatalf("provider response contains protected credential data: %s", response) + } + + var encrypted string + if err := db.Raw("SELECT CAST(api_key_enc AS CHAR) FROM provider_credentials WHERE provider_id = ? AND credential_version = 1", providerView.ID).Scan(&encrypted).Error; err != nil { + t.Fatalf("read stored credential: %v", err) + } + if encrypted == "" || strings.Contains(encrypted, secret) { + t.Fatal("provider credential was not encrypted at rest") + } + var summaries []string + if err := db.Raw("SELECT CAST(summary AS CHAR) FROM admin_audit_events WHERE target_type = 'provider' AND target_id = ?", fmt.Sprint(providerView.ID)).Scan(&summaries).Error; err != nil { + t.Fatalf("read credential audit events: %v", err) + } + for _, summary := range summaries { + if strings.Contains(summary, secret) || strings.Contains(summary, "ciphertext") || strings.Contains(summary, "nonce") { + t.Fatalf("credential audit event contains protected data: %s", summary) + } + } + + rotatedSecret := "synthetic-rotated-secret-" + suffix + rotated, err := service.RotateCredential(ctx, 7, "rotate-"+suffix, providerView.ID, rotatedSecret) + if err != nil { + t.Fatalf("rotate credential: %v", err) + } + if rotated.Credential.Version != 2 || !rotated.Credential.HasCredential { + t.Fatalf("rotation did not expose only current metadata: %#v", rotated.Credential) + } + // A normal update omits api_key and must preserve the selected version. + retained, err := service.UpdateProvider(ctx, 7, "update-"+suffix, providerView.ID, ProviderInput{ + Slug: providerInput.Slug, Name: providerInput.Name + " updated", BaseURL: providerInput.BaseURL, + AuthType: providerInput.AuthType, Enabled: true, + }) + if err != nil { + t.Fatalf("update provider without key: %v", err) + } + if retained.Credential.Version != 2 { + t.Fatalf("empty regular update changed credential version to %d", retained.Credential.Version) + } + rolledBack, err := service.ActivateCredential(ctx, 7, "rollback-"+suffix, providerView.ID, 1) + if err != nil { + t.Fatalf("activate prior credential: %v", err) + } + if rolledBack.Credential.Version != 1 { + t.Fatalf("rollback selected version %d, want 1", rolledBack.Credential.Version) + } + + modelView, err := service.CreateProviderModel(ctx, 7, "model-"+suffix, ProviderModelInput{ + ProviderID: providerView.ID, Name: "Model " + suffix, ModelID: "image-" + suffix, + APIType: model.APIImages, Kind: model.KindImage, Capabilities: []model.Capability{model.CapabilityImageGenerate}, + ExtraBody: json.RawMessage(`{"size":"1024x1024"}`), TimeoutMS: 1000, Weight: 100, Enabled: true, + }) + if err != nil { + t.Fatalf("create provider model: %v", err) + } + templateView, err := service.CreatePromptTemplate(ctx, 7, "template-"+suffix, PromptTemplateInput{ + TemplateKey: "template-" + suffix, Kind: model.KindImage, APIType: model.APIImages, + Capability: model.CapabilityImageGenerate, Name: "Template " + suffix, + TemplateText: "{{.UserPrompt}}", DefaultRoleRule: "", Enabled: true, + }) + if err != nil { + t.Fatalf("create template: %v", err) + } + poolInput := RoutePoolInput{ + Slug: "route-" + suffix, Name: "Route " + suffix, Capability: model.CapabilityImageGenerate, + PromptTemplateID: templateView.ID, MaxFailover: 1, Enabled: true, + Members: []RouteMemberInput{{ProviderModelID: modelView.ID, Weight: 100, FailureThreshold: 3, OpenSeconds: 60, HalfOpenMax: 1, Enabled: true, Position: 0}}, + } + poolView, err := service.CreateRoutePool(ctx, 7, "route-create-"+suffix, poolInput) + if err != nil { + t.Fatalf("create route pool: %v", err) + } + poolInput.Version, poolInput.Publish = poolView.Version, true + published, err := service.UpdateRoutePool(ctx, 7, "route-publish-"+suffix, poolView.ID, poolInput) + if err != nil { + t.Fatalf("publish route pool: %v", err) + } + if !published.Active || published.Version != poolView.Version+1 { + t.Fatalf("route pool publication state = active:%t version:%d", published.Active, published.Version) + } + poolInput.Version = poolView.Version + if _, err := service.UpdateRoutePool(ctx, 7, "route-stale-"+suffix, poolView.ID, poolInput); !errors.Is(err, ErrConflict) { + t.Fatalf("stale route update error = %v, want version conflict", err) + } +} + +func TestConnectivityChecksRequireAuthorizationAndReserveCooldownMySQL(t *testing.T) { + db := openAdminTestDB(t) + ctx := context.Background() + deleteAdminFixture(t, db, "probe-") + suffix := fmt.Sprintf("probe-%d", time.Now().UnixNano()) + t.Cleanup(func() { deleteAdminFixture(t, db, suffix) }) + + disabled := newAdminTestService(t, db, Config{}) + _, modelID := createEnabledProviderAndModel(t, disabled, suffix) + if _, err := disabled.StartConnectivityCheck(ctx, 9, "blocked-"+suffix, modelID); !errors.Is(err, ErrConnectivityDisabled) { + t.Fatalf("disabled connectivity error = %v, want explicit authorization error", err) + } + + probe := &blockingProbe{started: make(chan struct{}, 1), release: make(chan struct{})} + service := newAdminTestService(t, db, Config{ + AllowConnectivityChecks: true, ConnectivityCooldown: time.Minute, MaxResponseBytes: 4096, Probe: probe, + }) + result := make(chan error, 1) + go func() { + _, err := service.StartConnectivityCheck(ctx, 9, "probe-"+suffix, modelID) + result <- err + }() + select { + case <-probe.started: + case <-time.After(5 * time.Second): + t.Fatal("mock connectivity probe did not start") + } + if _, err := service.StartConnectivityCheck(ctx, 9, "probe-duplicate-"+suffix, modelID); !isCooldown(err) { + t.Fatalf("second connectivity reservation error = %v, want cooldown", err) + } + if got := probe.calls.Load(); got != 1 { + t.Fatalf("probe calls while first is reserved = %d, want 1", got) + } + close(probe.release) + if err := <-result; err != nil { + t.Fatalf("complete mock connectivity probe: %v", err) + } + var events []string + if err := db.Raw("SELECT CAST(summary AS CHAR) FROM admin_audit_events WHERE target_type = 'provider_model' AND target_id = ?", fmt.Sprint(modelID)).Scan(&events).Error; err != nil { + t.Fatalf("read connectivity audits: %v", err) + } + for _, event := range events { + if strings.Contains(event, "synthetic-provider-secret") || strings.Contains(event, "ciphertext") { + t.Fatalf("connectivity audit contains protected data: %s", event) + } + } +} + +type blockingProbe struct { + calls atomic.Int32 + started chan struct{} + release chan struct{} +} + +func (p *blockingProbe) Probe(context.Context, ProbeConfiguration) error { + p.calls.Add(1) + p.started <- struct{}{} + <-p.release + return nil +} + +func isCooldown(err error) bool { + var cooldown CooldownError + return errors.As(err, &cooldown) +} + +func openAdminTestDB(t *testing.T) *gorm.DB { + t.Helper() + if os.Getenv("CHORUS_RUN_ADMIN_TESTS") != "1" { + t.Skip("set CHORUS_RUN_ADMIN_TESTS=1 to run against the disposable MySQL test database") + } + dsn := strings.TrimSpace(os.Getenv("CHORUS_DSN")) + if dsn == "" { + t.Fatal("CHORUS_DSN is required for admin MySQL integration tests") + } + parsed, err := mysqldriver.ParseDSN(dsn) + if err != nil { + t.Fatalf("parse CHORUS_DSN: %v", err) + } + if parsed.DBName != "chorus_test" { + t.Fatalf("refusing to use non-disposable database %q", parsed.DBName) + } + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)}) + if err != nil { + t.Fatalf("connect disposable MySQL database: %v", err) + } + return db +} + +func newAdminTestService(t *testing.T, db *gorm.DB, cfg Config) *Service { + t.Helper() + ring, err := platformcrypto.NewKeyRing("test", map[string][]byte{"test": []byte("0123456789abcdef0123456789abcdef")}) + if err != nil { + t.Fatal(err) + } + service, err := NewService(db, ring, cfg) + if err != nil { + t.Fatal(err) + } + return service +} + +func createEnabledProviderAndModel(t *testing.T, service *Service, suffix string) (uint64, uint64) { + t.Helper() + secret := "synthetic-provider-secret-" + suffix + providerView, err := service.CreateProvider(context.Background(), 9, "fixture-provider-"+suffix, ProviderInput{ + Slug: "probe-provider-" + suffix, Name: "Probe Provider " + suffix, BaseURL: "https://provider.invalid/v1", + AuthType: "bearer", Enabled: true, APIKey: &secret, + }) + if err != nil { + t.Fatalf("create connectivity fixture provider: %v", err) + } + modelView, err := service.CreateProviderModel(context.Background(), 9, "fixture-model-"+suffix, ProviderModelInput{ + ProviderID: providerView.ID, Name: "Probe Model " + suffix, ModelID: "probe-" + suffix, + APIType: model.APIChat, Kind: model.KindText, Capabilities: []model.Capability{model.CapabilityText}, + TimeoutMS: 1000, Weight: 100, Enabled: true, + }) + if err != nil { + t.Fatalf("create connectivity fixture model: %v", err) + } + return providerView.ID, modelView.ID +} + +func deleteAdminFixture(t *testing.T, db *gorm.DB, suffix string) { + t.Helper() + var poolIDs []uint64 + if err := db.Raw("SELECT id FROM route_pools WHERE slug LIKE ?", "%"+suffix+"%").Scan(&poolIDs).Error; err != nil { + t.Errorf("find route pools for cleanup: %v", err) + return + } + if len(poolIDs) > 0 { + if err := db.Where("route_pool_id IN ?", poolIDs).Delete(&activeRouteRow{}).Error; err != nil { + t.Errorf("delete active routes: %v", err) + } + if err := db.Where("id IN ?", poolIDs).Delete(&routePoolRow{}).Error; err != nil { + t.Errorf("delete route pools: %v", err) + } + } + if err := db.Where("template_key LIKE ?", "%"+suffix+"%").Delete(&promptTemplateRow{}).Error; err != nil { + t.Errorf("delete templates: %v", err) + } + var providerIDs []uint64 + if err := db.Raw("SELECT id FROM providers WHERE slug LIKE ?", "%"+suffix+"%").Scan(&providerIDs).Error; err != nil { + t.Errorf("find providers for cleanup: %v", err) + return + } + if len(providerIDs) == 0 { + return + } + var modelIDs []uint64 + if err := db.Raw("SELECT id FROM provider_models WHERE provider_id IN ?", providerIDs).Scan(&modelIDs).Error; err != nil { + t.Errorf("find provider models for cleanup: %v", err) + return + } + if len(modelIDs) > 0 { + if err := db.Where("provider_model_id IN ?", modelIDs).Delete(&connectivityRow{}).Error; err != nil { + t.Errorf("delete connectivity checks: %v", err) + } + if err := db.Where("id IN ?", modelIDs).Delete(&providerModelRow{}).Error; err != nil { + t.Errorf("delete provider models: %v", err) + } + } + if err := db.Model(&providerRow{}).Where("id IN ?", providerIDs).Updates(map[string]any{"auth_type": "none", "active_credential_id": nil}).Error; err != nil { + t.Errorf("clear active credentials: %v", err) + } + if err := db.Where("provider_id IN ?", providerIDs).Delete(&credentialRow{}).Error; err != nil { + t.Errorf("delete provider credentials: %v", err) + } + if err := db.Where("id IN ?", providerIDs).Delete(&providerRow{}).Error; err != nil { + t.Errorf("delete providers: %v", err) + } + if err := db.Where("target_type IN ('provider', 'provider_model', 'route_pool') AND request_id LIKE ?", "%"+suffix+"%").Delete(&auditRow{}).Error; err != nil { + t.Errorf("delete audit events: %v", err) + } +} + +func registerActiveRouteRestore(t *testing.T, db *gorm.DB, capability model.Capability) { + t.Helper() + var previous activeRouteRow + err := db.Where("capability = ?", capability).First(&previous).Error + if errors.Is(err, gorm.ErrRecordNotFound) { + return + } + if err != nil { + t.Fatalf("read active route before test: %v", err) + } + t.Cleanup(func() { + if err := db.Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "capability"}}, DoUpdates: clause.AssignmentColumns([]string{"route_pool_id", "operator_id", "created_at", "updated_at"})}).Create(&previous).Error; err != nil { + t.Errorf("restore active route: %v", err) + } + }) +} diff --git a/admin/app/chorus/types.go b/admin/app/chorus/types.go new file mode 100644 index 0000000..6fd3531 --- /dev/null +++ b/admin/app/chorus/types.go @@ -0,0 +1,166 @@ +// Package chorus implements the MVP-1 business extension for the fixed +// go-admin backend. It deliberately keeps provider credentials out of all +// response DTOs and audit summaries. +package chorus + +import ( + "encoding/json" + "time" + + "git.ilapage.cn/OPC/chorus/internal/core/model" +) + +type ProviderInput struct { + Slug string `json:"slug"` + Name string `json:"name"` + BaseURL string `json:"base_url"` + AuthType string `json:"auth_type"` + Enabled bool `json:"enabled"` + APIKey *string `json:"api_key,omitempty"` +} + +type CredentialInfo struct { + HasCredential bool `json:"has_credential"` + Version uint32 `json:"version,omitempty"` + KeyID string `json:"key_id,omitempty"` +} + +type ProviderView struct { + ID uint64 `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + BaseURL string `json:"base_url"` + AuthType string `json:"auth_type"` + Enabled bool `json:"enabled"` + Credential CredentialInfo `json:"credential"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ProviderModelInput struct { + ProviderID uint64 `json:"provider_id"` + Name string `json:"name"` + ModelID string `json:"model_id"` + APIType model.APIType `json:"api_type"` + Kind model.GenerationKind `json:"kind"` + Capabilities []model.Capability `json:"capabilities"` + ExtraBody json.RawMessage `json:"extra_body"` + TimeoutMS uint32 `json:"timeout_ms"` + Weight uint32 `json:"weight"` + Enabled bool `json:"enabled"` +} + +type ProviderModelView struct { + ID uint64 `json:"id"` + ProviderID uint64 `json:"provider_id"` + ProviderName string `json:"provider_name"` + Name string `json:"name"` + ModelID string `json:"model_id"` + APIType model.APIType `json:"api_type"` + Kind model.GenerationKind `json:"kind"` + Capabilities []model.Capability `json:"capabilities"` + ExtraBody json.RawMessage `json:"extra_body"` + TimeoutMS uint32 `json:"timeout_ms"` + Weight uint32 `json:"weight"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type PromptTemplateInput struct { + TemplateKey string `json:"template_key"` + Kind model.GenerationKind `json:"kind"` + APIType model.APIType `json:"api_type"` + Capability model.Capability `json:"capability"` + Name string `json:"name"` + TemplateText string `json:"template_text"` + DefaultRoleRule string `json:"default_role_rule"` + Enabled bool `json:"enabled"` +} + +type PromptTemplateView struct { + ID uint64 `json:"id"` + TemplateKey string `json:"template_key"` + Kind model.GenerationKind `json:"kind"` + APIType model.APIType `json:"api_type"` + Capability model.Capability `json:"capability"` + Name string `json:"name"` + Version uint32 `json:"version"` + TemplateText string `json:"template_text"` + DefaultRoleRule string `json:"default_role_rule"` + Enabled bool `json:"enabled"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type RouteMemberInput struct { + ProviderModelID uint64 `json:"provider_model_id"` + Weight uint32 `json:"weight"` + FailureThreshold uint32 `json:"failure_threshold"` + OpenSeconds uint32 `json:"open_seconds"` + HalfOpenMax uint32 `json:"half_open_max"` + Enabled bool `json:"enabled"` + Position uint32 `json:"position"` +} + +type RoutePoolInput struct { + Slug string `json:"slug"` + Name string `json:"name"` + Capability model.Capability `json:"capability"` + PromptTemplateID uint64 `json:"prompt_template_id"` + MaxFailover uint32 `json:"max_failover"` + Enabled bool `json:"enabled"` + Publish bool `json:"publish"` + Version uint32 `json:"version,omitempty"` + Members []RouteMemberInput `json:"members"` +} + +type RoutePoolView struct { + ID uint64 `json:"id"` + Slug string `json:"slug"` + Name string `json:"name"` + Capability model.Capability `json:"capability"` + PromptTemplateID uint64 `json:"prompt_template_id"` + MaxFailover uint32 `json:"max_failover"` + Version uint32 `json:"version"` + Enabled bool `json:"enabled"` + Active bool `json:"active"` + Members []RouteMemberInput `json:"members"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type ConnectivityCheckView struct { + ID uint64 `json:"id"` + ProviderModel uint64 `json:"provider_model_id"` + Status string `json:"status"` + ErrorCode string `json:"error_code,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + LatencyMS *uint32 `json:"latency_ms,omitempty"` + StartedAt time.Time `json:"started_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` + CooldownUntil time.Time `json:"cooldown_until"` +} + +type UserView struct { + ID uint64 `json:"id"` + Email string `json:"email"` + DisplayName string `json:"display_name"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type GenerationView struct { + ID uint64 `json:"id"` + UserID uint64 `json:"user_id"` + Status model.GenerationStatus `json:"status"` + Kind model.GenerationKind `json:"kind"` + RenderedPrompt string `json:"rendered_prompt"` + Attempts json.RawMessage `json:"attempts"` + ErrorCode string `json:"error_code,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` + ProviderAttemptCnt uint32 `json:"provider_attempt_count"` + CreatedAt time.Time `json:"created_at"` + CompletedAt *time.Time `json:"completed_at,omitempty"` +} diff --git a/admin/cmd/server.go b/admin/cmd/server.go new file mode 100644 index 0000000..de79b9a --- /dev/null +++ b/admin/cmd/server.go @@ -0,0 +1,178 @@ +package cmd + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "git.ilapage.cn/OPC/chorus/admin/app/admin/router" + "git.ilapage.cn/OPC/chorus/admin/app/chorus" + "git.ilapage.cn/OPC/chorus/admin/common/database" + common "git.ilapage.cn/OPC/chorus/admin/common/middleware" + ext "git.ilapage.cn/OPC/chorus/admin/config" + corecrypto "git.ilapage.cn/OPC/chorus/internal/core/crypto" + platformcrypto "git.ilapage.cn/OPC/chorus/internal/platform/crypto" + safehttp "git.ilapage.cn/OPC/chorus/internal/platform/http" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/config/source/file" + "github.com/go-admin-team/go-admin-core/sdk" + coreapi "github.com/go-admin-team/go-admin-core/sdk/api" + coreconfig "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/spf13/cobra" + "gorm.io/gorm" +) + +var configPath string + +var rootCmd = &cobra.Command{ + Use: "chorus-admin", + SilenceUsage: true, +} + +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Start the Chorus go-admin backend", + PreRunE: func(cmd *cobra.Command, args []string) error { + return setup(configPath) + }, + RunE: func(cmd *cobra.Command, args []string) error { + return serve() + }, +} + +func init() { + serverCmd.Flags().StringVarP(&configPath, "config", "c", "", "go-admin settings file") + rootCmd.AddCommand(serverCmd) +} + +func Execute() error { + return rootCmd.Execute() +} + +func setup(path string) error { + if strings.TrimSpace(path) == "" { + return errors.New("--config is required; use config/settings.example.yml as a starting point") + } + coreconfig.ExtendConfig = &ext.ExtConfig + coreconfig.Setup(file.NewSource(file.WithPath(path)), database.Setup) + serviceConfig, err := serviceConfigFromEnvironment(os.LookupEnv) + if err != nil { + return err + } + chorus.Configure(func(db *gorm.DB) (*chorus.Service, error) { + return chorus.NewService(db, serviceConfig.cipher, serviceConfig.config) + }) + return nil +} + +type configuredService struct { + cipher corecrypto.KeyCipher + config chorus.Config +} + +func serviceConfigFromEnvironment(lookup func(string) (string, bool)) (configuredService, error) { + read := func(name string) string { + value, _ := lookup(name) + return strings.TrimSpace(value) + } + masterKey := []byte(read("CHORUS_MASTER_KEY")) + if len(masterKey) == 0 { + return configuredService{}, errors.New("CHORUS_MASTER_KEY is required for the admin backend") + } + ring, err := platformcrypto.NewKeyRing("primary", map[string][]byte{"primary": masterKey}) + for index := range masterKey { + masterKey[index] = 0 + } + if err != nil { + return configuredService{}, errors.New("CHORUS_MASTER_KEY has an unsupported length") + } + configured := configuredService{cipher: ring} + allowRaw := read("CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES") + if allowRaw == "" || allowRaw == "0" { + return configured, nil + } + if allowRaw != "1" { + return configuredService{}, errors.New("CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES must be 1 when set") + } + cooldown, err := positiveDuration(read("CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS"), time.Second) + if err != nil { + return configuredService{}, errors.New("CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS must be a positive integer") + } + timeout, err := positiveDuration(read("CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS"), time.Second) + if err != nil { + return configuredService{}, errors.New("CHORUS_PROVIDER_HTTP_TIMEOUT_SECONDS must be a positive integer") + } + maxResponse, err := positiveInt64(read("CHORUS_PROVIDER_MAX_RESPONSE_BYTES")) + if err != nil { + return configuredService{}, errors.New("CHORUS_PROVIDER_MAX_RESPONSE_BYTES must be a positive integer") + } + client, err := safehttp.New(safehttp.Config{Timeout: timeout, MaxRedirects: 3}) + if err != nil { + return configuredService{}, errors.New("configure SSRF-safe connectivity client") + } + probe, err := chorus.NewLiveProbe(safehttp.NewProviderClient(client)) + if err != nil { + return configuredService{}, errors.New("configure connectivity probe") + } + configured.config = chorus.Config{AllowConnectivityChecks: true, ConnectivityCooldown: cooldown, MaxResponseBytes: maxResponse, Probe: probe} + return configured, nil +} + +func positiveDuration(raw string, unit time.Duration) (time.Duration, error) { + value, err := strconv.ParseInt(raw, 10, 64) + if err != nil || value <= 0 { + return 0, errors.New("invalid positive duration") + } + return time.Duration(value) * unit, nil +} + +func positiveInt64(raw string) (int64, error) { + value, err := strconv.ParseInt(raw, 10, 64) + if err != nil || value <= 0 { + return 0, errors.New("invalid positive integer") + } + return value, nil +} + +func serve() error { + if coreconfig.ApplicationConfig.Mode == pkg.ModeProd.String() { + gin.SetMode(gin.ReleaseMode) + } + engine := gin.New() + sdk.Runtime.SetEngine(engine) + engine.Use(common.Sentinel()).Use(common.RequestId(pkg.TrafficKey)).Use(coreapi.SetRequestLogger) + common.InitMiddleware(engine) + router.InitRouter() + + server := &http.Server{ + Addr: fmt.Sprintf("%s:%d", coreconfig.ApplicationConfig.Host, coreconfig.ApplicationConfig.Port), + Handler: engine, + ReadHeaderTimeout: 5 * time.Second, + ReadTimeout: time.Duration(coreconfig.ApplicationConfig.ReadTimeout) * time.Second, + WriteTimeout: time.Duration(coreconfig.ApplicationConfig.WriterTimeout) * time.Second, + IdleTimeout: 60 * time.Second, + } + shutdown, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + result := make(chan error, 1) + go func() { result <- server.ListenAndServe() }() + select { + case <-shutdown.Done(): + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + return server.Shutdown(ctx) + case err := <-result: + if errors.Is(err, http.ErrServerClosed) { + return nil + } + return err + } +} diff --git a/admin/cmd/server_test.go b/admin/cmd/server_test.go new file mode 100644 index 0000000..3b42eaf --- /dev/null +++ b/admin/cmd/server_test.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "strings" + "testing" +) + +func TestServiceConfigRequiresMasterKey(t *testing.T) { + _, err := serviceConfigFromEnvironment(func(string) (string, bool) { return "", false }) + if err == nil || !strings.Contains(err.Error(), "CHORUS_MASTER_KEY") { + t.Fatalf("missing master key error = %v", err) + } +} + +func TestServiceConfigKeepsConnectivityDisabledWithoutExplicitAuthorization(t *testing.T) { + configured, err := serviceConfigFromEnvironment(testEnvironment(map[string]string{ + "CHORUS_MASTER_KEY": "0123456789abcdef0123456789abcdef", + })) + if err != nil { + t.Fatalf("configure disabled connectivity: %v", err) + } + if configured.config.AllowConnectivityChecks || configured.config.Probe != nil { + t.Fatal("connectivity probe must not be created without explicit authorization") + } +} + +func TestServiceConfigRequiresAllExplicitConnectivityLimits(t *testing.T) { + _, err := serviceConfigFromEnvironment(testEnvironment(map[string]string{ + "CHORUS_MASTER_KEY": "0123456789abcdef0123456789abcdef", + "CHORUS_ADMIN_ALLOW_CONNECTIVITY_PROBES": "1", + })) + if err == nil || !strings.Contains(err.Error(), "CHORUS_ADMIN_CONNECTIVITY_COOLDOWN_SECONDS") { + t.Fatalf("missing cooldown error = %v", err) + } +} + +func testEnvironment(values map[string]string) func(string) (string, bool) { + return func(key string) (string, bool) { + value, ok := values[key] + return value, ok + } +} diff --git a/admin/common/actions/create.go b/admin/common/actions/create.go new file mode 100644 index 0000000..e025bd0 --- /dev/null +++ b/admin/common/actions/create.go @@ -0,0 +1,49 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// CreateAction 通用新增动作 +func CreateAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + //新增操作 + req := control.Generate() + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, err.Error()) + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + object.SetCreateBy(user.GetUserId(c)) + err = db.WithContext(c).Create(object).Error + if err != nil { + log.Errorf("Create error: %s", err) + response.Error(c, 500, err, "创建失败") + return + } + response.OK(c, object.GetId(), "创建成功") + c.Next() + } +} diff --git a/admin/common/actions/delete.go b/admin/common/actions/delete.go new file mode 100644 index 0000000..0ad175a --- /dev/null +++ b/admin/common/actions/delete.go @@ -0,0 +1,61 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// DeleteAction 通用删除动作 +func DeleteAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + //删除操作 + req := control.Generate() + err = req.Bind(c) + if err != nil { + log.Errorf("MsgID[%s] Bind error: %s", msgID, err) + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + + object.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := GetPermissionFromContext(c) + + db = db.WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).Delete(object) + if err = db.Error; err != nil { + log.Errorf("MsgID[%s] Delete error: %s", msgID, err) + response.Error(c, 500, err, "删除失败") + return + } + if db.RowsAffected == 0 { + response.Error(c, http.StatusForbidden, nil, "无权删除该数据") + return + } + response.OK(c, object.GetId(), "删除成功") + c.Next() + } +} diff --git a/admin/common/actions/index.go b/admin/common/actions/index.go new file mode 100644 index 0000000..01763ff --- /dev/null +++ b/admin/common/actions/index.go @@ -0,0 +1,58 @@ +package actions + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// IndexAction 通用查询动作 +func IndexAction(m models.ActiveRecord, d dto.Index, f func() interface{}) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + list := f() + object := m.Generate() + req := d.Generate() + var count int64 + + //查询列表 + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + + //数据权限检查 + p := GetPermissionFromContext(c) + + err = db.WithContext(c).Model(object). + Scopes( + dto.MakeCondition(req.GetNeedSearch()), + dto.Paginate(req.GetPageSize(), req.GetPageIndex()), + Permission(object.TableName(), p), + ). + Find(list).Limit(-1).Offset(-1). + Count(&count).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + log.Errorf("MsgID[%s] Index error: %s", msgID, err) + response.Error(c, 500, err, "查询失败") + return + } + response.PageOK(c, list, int(count), req.GetPageIndex(), req.GetPageSize(), "查询成功") + c.Next() + } +} diff --git a/admin/common/actions/permission.go b/admin/common/actions/permission.go new file mode 100644 index 0000000..82532c4 --- /dev/null +++ b/admin/common/actions/permission.go @@ -0,0 +1,96 @@ +package actions + +import ( + "errors" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" +) + +type DataPermission struct { + DataScope string + UserId int + DeptId int + RoleId int +} + +func PermissionAction() gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + var p = new(DataPermission) + if userId := user.GetUserIdStr(c); userId != "" { + p, err = newDataPermission(db, userId) + if err != nil { + log.Errorf("MsgID[%s] PermissionAction error: %s", msgID, err) + response.Error(c, 500, err, "权限范围鉴定错误") + c.Abort() + return + } + } + c.Set(PermissionKey, p) + c.Next() + } +} + +func newDataPermission(tx *gorm.DB, userId interface{}) (*DataPermission, error) { + var err error + p := &DataPermission{} + + err = tx.Table("sys_user"). + Select("sys_user.user_id", "sys_role.role_id", "sys_user.dept_id", "sys_role.data_scope"). + Joins("left join sys_role on sys_role.role_id = sys_user.role_id"). + Where("sys_user.user_id = ?", userId). + Scan(p).Error + if err != nil { + err = errors.New("获取用户数据出错 msg:" + err.Error()) + return nil, err + } + return p, nil +} + +func Permission(tableName string, p *DataPermission) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if !config.ApplicationConfig.EnableDP { + return db + } + switch p.DataScope { + case "2": + return db.Where(tableName+".create_by in (select sys_user.user_id from sys_role_dept left join sys_user on sys_user.dept_id=sys_role_dept.dept_id where sys_role_dept.role_id = ?)", p.RoleId) + case "3": + return db.Where(tableName+".create_by in (SELECT user_id from sys_user where dept_id = ? )", p.DeptId) + case "4": + return db.Where(tableName+".create_by in (SELECT user_id from sys_user where sys_user.dept_id in(select dept_id from sys_dept where dept_path like ? ))", "%/"+pkg.IntToString(p.DeptId)+"/%") + case "5": + return db.Where(tableName+".create_by = ?", p.UserId) + default: + return db + } + } +} + +func getPermissionFromContext(c *gin.Context) *DataPermission { + p := new(DataPermission) + if pm, ok := c.Get(PermissionKey); ok { + switch pm.(type) { + case *DataPermission: + p = pm.(*DataPermission) + } + } + return p +} + +// GetPermissionFromContext 提供非action写法数据范围约束 +func GetPermissionFromContext(c *gin.Context) *DataPermission { + return getPermissionFromContext(c) +} diff --git a/admin/common/actions/type.go b/admin/common/actions/type.go new file mode 100644 index 0000000..69beb8c --- /dev/null +++ b/admin/common/actions/type.go @@ -0,0 +1,5 @@ +package actions + +const ( + PermissionKey = "dataPermission" +) diff --git a/admin/common/actions/update.go b/admin/common/actions/update.go new file mode 100644 index 0000000..b424c02 --- /dev/null +++ b/admin/common/actions/update.go @@ -0,0 +1,59 @@ +package actions + +import ( + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// UpdateAction 通用更新动作 +func UpdateAction(control dto.Control) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + req := control.Generate() + //更新操作 + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + object.SetUpdateBy(user.GetUserId(c)) + + //数据权限检查 + p := GetPermissionFromContext(c) + + db = db.WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).Updates(object) + if err = db.Error; err != nil { + log.Errorf("MsgID[%s] Update error: %s", msgID, err) + response.Error(c, 500, err, "更新失败") + return + } + if db.RowsAffected == 0 { + response.Error(c, http.StatusForbidden, nil, "无权更新该数据") + return + } + response.OK(c, object.GetId(), "更新成功") + c.Next() + } +} diff --git a/admin/common/actions/view.go b/admin/common/actions/view.go new file mode 100644 index 0000000..4bce62d --- /dev/null +++ b/admin/common/actions/view.go @@ -0,0 +1,67 @@ +package actions + +import ( + "errors" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "net/http" + + "github.com/gin-gonic/gin" + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/common/dto" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +// ViewAction 通用详情动作 +func ViewAction(control dto.Control, f func() interface{}) gin.HandlerFunc { + return func(c *gin.Context) { + db, err := pkg.GetOrm(c) + if err != nil { + log.Error(err) + return + } + + msgID := pkg.GenerateMsgIDFromContext(c) + //查看详情 + req := control.Generate() + err = req.Bind(c) + if err != nil { + response.Error(c, http.StatusUnprocessableEntity, err, "参数验证失败") + return + } + var object models.ActiveRecord + object, err = req.GenerateM() + if err != nil { + response.Error(c, 500, err, "模型生成失败") + return + } + + var rsp interface{} + if f != nil { + rsp = f() + } else { + rsp, _ = req.GenerateM() + } + + //数据权限检查 + p := GetPermissionFromContext(c) + + err = db.Model(object).WithContext(c).Scopes( + Permission(object.TableName(), p), + ).Where(req.GetId()).First(rsp).Error + + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + response.Error(c, http.StatusNotFound, nil, "查看对象不存在或无权查看") + return + } + if err != nil { + log.Errorf("MsgID[%s] View error: %s", msgID, err) + response.Error(c, 500, err, "查看失败") + return + } + response.OK(c, rsp, "查询成功") + c.Next() + } +} diff --git a/admin/common/apis/api.go b/admin/common/apis/api.go new file mode 100644 index 0000000..d3226c7 --- /dev/null +++ b/admin/common/apis/api.go @@ -0,0 +1,135 @@ +package apis + +import ( + "errors" + "fmt" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "gorm.io/gorm" + + "git.ilapage.cn/OPC/chorus/admin/common/service" +) + +type Api struct { + Context *gin.Context + Logger *logger.Helper + Orm *gorm.DB + Errors error +} + +func (e *Api) AddError(err error) { + if e.Errors == nil { + e.Errors = err + } else if err != nil { + e.Logger.Error(err) + e.Errors = fmt.Errorf("%v; %w", e.Errors, err) + } +} + +// MakeContext 设置http上下文 +func (e *Api) MakeContext(c *gin.Context) *Api { + e.Context = c + e.Logger = api.GetRequestLogger(c) + return e +} + +// GetLogger 获取上下文提供的日志 +func (e *Api) GetLogger() *logger.Helper { + return api.GetRequestLogger(e.Context) +} + +func (e *Api) Bind(d interface{}, bindings ...binding.Binding) *Api { + var err error + if len(bindings) == 0 { + bindings = append(bindings, binding.JSON, nil) + } + for i := range bindings { + switch bindings[i] { + case binding.JSON: + err = e.Context.ShouldBindWith(d, binding.JSON) + case binding.XML: + err = e.Context.ShouldBindWith(d, binding.XML) + case binding.Form: + err = e.Context.ShouldBindWith(d, binding.Form) + case binding.Query: + err = e.Context.ShouldBindWith(d, binding.Query) + case binding.FormPost: + err = e.Context.ShouldBindWith(d, binding.FormPost) + case binding.FormMultipart: + err = e.Context.ShouldBindWith(d, binding.FormMultipart) + case binding.ProtoBuf: + err = e.Context.ShouldBindWith(d, binding.ProtoBuf) + case binding.MsgPack: + err = e.Context.ShouldBindWith(d, binding.MsgPack) + case binding.YAML: + err = e.Context.ShouldBindWith(d, binding.YAML) + case binding.Header: + err = e.Context.ShouldBindWith(d, binding.Header) + default: + err = e.Context.ShouldBindUri(d) + } + if err != nil { + e.AddError(err) + } + } + return e +} + +// GetOrm 获取Orm DB +func (e *Api) GetOrm() (*gorm.DB, error) { + db, err := pkg.GetOrm(e.Context) + if err != nil { + e.Error(500, err, "数据库连接获取失败") + return nil, err + } + return db, nil +} + +// MakeOrm 设置Orm DB +func (e *Api) MakeOrm() *Api { + var err error + if e.Logger == nil { + err = errors.New("at MakeOrm logger is nil") + //e.Logger.Error(500, err, "at MakeOrm logger is nil") + e.AddError(err) + return e + } + db, err := pkg.GetOrm(e.Context) + if err != nil { + e.Logger.Error(500, err, "数据库连接获取失败") + e.AddError(err) + } + e.Orm = db + return e +} + +func (e *Api) MakeService(c *service.Service) *Api { + c.Log = e.Logger + c.Orm = e.Orm + return e +} + +// Error 通常错误数据处理 +func (e *Api) Error(code int, err error, msg string) { + response.Error(e.Context, code, err, msg) +} + +// OK 通常成功数据处理 +func (e *Api) OK(data interface{}, msg string) { + response.OK(e.Context, data, msg) +} + +// PageOK 分页数据处理 +func (e *Api) PageOK(result interface{}, count int, pageIndex int, pageSize int, msg string) { + response.PageOK(e.Context, result, count, pageIndex, pageSize, msg) +} + +// Custom 兼容函数 +func (e *Api) Custom(data gin.H) { + response.Custum(e.Context, data) +} diff --git a/admin/common/database/initialize.go b/admin/common/database/initialize.go new file mode 100644 index 0000000..f86bdcb --- /dev/null +++ b/admin/common/database/initialize.go @@ -0,0 +1,66 @@ +package database + +import ( + "time" + + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk" + toolsConfig "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + mycasbin "github.com/go-admin-team/go-admin-core/sdk/pkg/casbin" + toolsDB "github.com/go-admin-team/go-admin-core/tools/database" + . "github.com/go-admin-team/go-admin-core/tools/gorm/logger" + "gorm.io/gorm" + "gorm.io/gorm/logger" + "gorm.io/gorm/schema" + + "git.ilapage.cn/OPC/chorus/admin/common/global" +) + +// Setup 配置数据库 +func Setup() { + for k := range toolsConfig.DatabasesConfig { + setupSimpleDatabase(k, toolsConfig.DatabasesConfig[k]) + } +} + +func setupSimpleDatabase(host string, c *toolsConfig.Database) { + if global.Driver == "" { + global.Driver = c.Driver + } + // Database sources can contain passwords; record only the configured logical name. + log.Infof("database connection configured: %s", host) + registers := make([]toolsDB.ResolverConfigure, len(c.Registers)) + for i := range c.Registers { + registers[i] = toolsDB.NewResolverConfigure( + c.Registers[i].Sources, + c.Registers[i].Replicas, + c.Registers[i].Policy, + c.Registers[i].Tables) + } + resolverConfig := toolsDB.NewConfigure(c.Source, c.MaxIdleConns, c.MaxOpenConns, c.ConnMaxIdleTime, c.ConnMaxLifeTime, registers) + db, err := resolverConfig.Init(&gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + SingularTable: true, + }, + Logger: New( + logger.Config{ + SlowThreshold: time.Second, + Colorful: true, + LogLevel: logger.LogLevel( + log.DefaultLogger.Options().Level.LevelForGorm()), + }, + ), + }, opens[c.Driver]) + + if err != nil { + log.Fatal(pkg.Red(c.Driver+" connect error :"), err) + } else { + log.Info(pkg.Green(c.Driver + " connect success !")) + } + + e := mycasbin.Setup(db, "") + + sdk.Runtime.SetDb(host, db) + sdk.Runtime.SetCasbin(host, e) +} diff --git a/admin/common/database/open.go b/admin/common/database/open.go new file mode 100644 index 0000000..de02868 --- /dev/null +++ b/admin/common/database/open.go @@ -0,0 +1,16 @@ +//go:build !sqlite3 + +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +var opens = map[string]func(string) gorm.Dialector{ + "mysql": mysql.Open, + "postgres": postgres.Open, + "sqlserver": sqlserver.Open, +} diff --git a/admin/common/database/open_sqlite3.go b/admin/common/database/open_sqlite3.go new file mode 100644 index 0000000..aaebe94 --- /dev/null +++ b/admin/common/database/open_sqlite3.go @@ -0,0 +1,19 @@ +//go:build sqlite3 +// +build sqlite3 + +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/driver/sqlite" + "gorm.io/driver/sqlserver" + "gorm.io/gorm" +) + +var opens = map[string]func(string) gorm.Dialector{ + "mysql": mysql.Open, + "postgres": postgres.Open, + "sqlite3": sqlite.Open, + "sqlserver": sqlserver.Open, +} diff --git a/admin/common/dto/auto_form.go b/admin/common/dto/auto_form.go new file mode 100644 index 0000000..c99976a --- /dev/null +++ b/admin/common/dto/auto_form.go @@ -0,0 +1,74 @@ +package dto + +type AutoForm struct { + Fields []Field `json:"fields"` + FormRef string `json:"formRef"` + FormModel string `json:"formModel"` + Size string `json:"size"` + LabelPosition string `json:"labelPosition"` + LabelWidth int `json:"labelWidth"` + FormRules string `json:"formRules"` + Gutter int `json:"gutter"` + Disabled bool `json:"disabled"` + Span int `json:"span"` + FormBtns bool `json:"formBtns"` +} + +type Config struct { + Label string `json:"label"` + LabelWidth interface{} `json:"labelWidth"` + ShowLabel bool `json:"showLabel"` + ChangeTag bool `json:"changeTag"` + Tag string `json:"tag"` + TagIcon string `json:"tagIcon"` + Required bool `json:"required"` + Layout string `json:"layout"` + Span int `json:"span"` + Document string `json:"document"` + RegList []interface{} `json:"regList"` + FormId int `json:"formId"` + RenderKey int64 `json:"renderKey"` + DefaultValue interface{} `json:"defaultValue"` + ShowTip bool `json:"showTip,omitempty"` + ButtonText string `json:"buttonText,omitempty"` + FileSize int `json:"fileSize,omitempty"` + SizeUnit string `json:"sizeUnit,omitempty"` +} + +type Option struct { + Label string `json:"label"` + Value string `json:"value"` +} + +type Slot struct { + Prepend string `json:"prepend,omitempty"` + Append string `json:"append,omitempty"` + ListType bool `json:"list-type,omitempty"` + Options []Option `json:"options,omitempty"` +} + +type Field struct { + Config Config `json:"__config__"` + Slot Slot `json:"__slot__"` + Placeholder string `json:"placeholder,omitempty"` + Style Style `json:"style,omitempty"` + Clearable bool `json:"clearable,omitempty"` + PrefixIcon string `json:"prefix-icon,omitempty"` + SuffixIcon string `json:"suffix-icon,omitempty"` + Maxlength interface{} `json:"maxlength"` + ShowWordLimit bool `json:"show-word-limit,omitempty"` + Readonly bool `json:"readonly,omitempty"` + Disabled bool `json:"disabled"` + VModel string `json:"__vModel__"` + Action string `json:"action,omitempty"` + Accept string `json:"accept,omitempty"` + Name string `json:"name,omitempty"` + AutoUpload bool `json:"auto-upload,omitempty"` + ListType string `json:"list-type,omitempty"` + Multiple bool `json:"multiple,omitempty"` + Filterable bool `json:"filterable,omitempty"` +} + +type Style struct { + Width string `json:"width"` +} diff --git a/admin/common/dto/generate.go b/admin/common/dto/generate.go new file mode 100644 index 0000000..313ae6a --- /dev/null +++ b/admin/common/dto/generate.go @@ -0,0 +1,106 @@ +package dto + +import ( + vd "github.com/bytedance/go-tagexpr/v2/validator" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/api" +) + +type ObjectById struct { + Id int `uri:"id"` + Ids []int `json:"ids"` +} + +func (s *ObjectById) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBindUri(s) + if err != nil { + log.Warnf("ShouldBindUri error: %s", err.Error()) + return err + } + if ctx.Request.Method == http.MethodDelete { + err = ctx.ShouldBind(&s) + if err != nil { + log.Warnf("ShouldBind error: %s", err.Error()) + return err + } + if len(s.Ids) > 0 { + return nil + } + if s.Ids == nil { + s.Ids = make([]int, 0) + } + if s.Id != 0 { + s.Ids = append(s.Ids, s.Id) + } + } + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectById) GetId() interface{} { + if len(s.Ids) > 0 { + s.Ids = append(s.Ids, s.Id) + return s.Ids + } + return s.Id +} + +type ObjectGetReq struct { + Id int `uri:"id"` +} + +func (s *ObjectGetReq) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBindUri(s) + if err != nil { + log.Warnf("ShouldBindUri error: %s", err.Error()) + return err + } + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectGetReq) GetId() interface{} { + return s.Id +} + +type ObjectDeleteReq struct { + Ids []int `json:"ids"` +} + +func (s *ObjectDeleteReq) Bind(ctx *gin.Context) error { + var err error + log := api.GetRequestLogger(ctx) + err = ctx.ShouldBind(&s) + if err != nil { + log.Warnf("ShouldBind error: %s", err.Error()) + return err + } + if len(s.Ids) > 0 { + return nil + } + if s.Ids == nil { + s.Ids = make([]int, 0) + } + + if err = vd.Validate(s); err != nil { + log.Errorf("Validate error: %s", err.Error()) + return err + } + return err +} + +func (s *ObjectDeleteReq) GetId() interface{} { + return s.Ids +} diff --git a/admin/common/dto/order.go b/admin/common/dto/order.go new file mode 100644 index 0000000..25455fb --- /dev/null +++ b/admin/common/dto/order.go @@ -0,0 +1,12 @@ +package dto + +import ( + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +func OrderDest(sort string, bl bool) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Order(clause.OrderByColumn{Column: clause.Column{Name: sort}, Desc: bl}) + } +} diff --git a/admin/common/dto/pagination.go b/admin/common/dto/pagination.go new file mode 100644 index 0000000..ee438e5 --- /dev/null +++ b/admin/common/dto/pagination.go @@ -0,0 +1,20 @@ +package dto + +type Pagination struct { + PageIndex int `form:"pageIndex"` + PageSize int `form:"pageSize"` +} + +func (m *Pagination) GetPageIndex() int { + if m.PageIndex <= 0 { + m.PageIndex = 1 + } + return m.PageIndex +} + +func (m *Pagination) GetPageSize() int { + if m.PageSize <= 0 { + m.PageSize = 10 + } + return m.PageSize +} diff --git a/admin/common/dto/search.go b/admin/common/dto/search.go new file mode 100644 index 0000000..6cd61ee --- /dev/null +++ b/admin/common/dto/search.go @@ -0,0 +1,84 @@ +package dto + +import ( + "github.com/go-admin-team/go-admin-core/tools/search" + "git.ilapage.cn/OPC/chorus/admin/common/global" + "gorm.io/gorm" +) + +type GeneralDelDto struct { + Id int `uri:"id" json:"id" validate:"required"` + Ids []int `json:"ids"` +} + +func (g GeneralDelDto) GetIds() []int { + ids := make([]int, 0) + if g.Id != 0 { + ids = append(ids, g.Id) + } + if len(g.Ids) > 0 { + for _, id := range g.Ids { + if id > 0 { + ids = append(ids, id) + } + } + } else { + if g.Id > 0 { + ids = append(ids, g.Id) + } + } + if len(ids) <= 0 { + //方式全部删除 + ids = append(ids, 0) + } + return ids +} + +type GeneralGetDto struct { + Id int `uri:"id" json:"id" validate:"required"` +} + +func MakeCondition(q interface{}) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + condition := &search.GormCondition{ + GormPublic: search.GormPublic{}, + Join: make([]*search.GormJoin, 0), + } + search.ResolveSearchQuery(global.Driver, q, condition) + for _, join := range condition.Join { + if join == nil { + continue + } + db = db.Joins(join.JoinOn) + for k, v := range join.Where { + db = db.Where(k, v...) + } + for k, v := range join.Or { + db = db.Or(k, v...) + } + for _, o := range join.Order { + db = db.Order(o) + } + } + for k, v := range condition.Where { + db = db.Where(k, v...) + } + for k, v := range condition.Or { + db = db.Or(k, v...) + } + for _, o := range condition.Order { + db = db.Order(o) + } + return db + } +} + +func Paginate(pageSize, pageIndex int) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + offset := (pageIndex - 1) * pageSize + if offset < 0 { + offset = 0 + } + return db.Offset(offset).Limit(pageSize) + } +} diff --git a/admin/common/dto/type.go b/admin/common/dto/type.go new file mode 100644 index 0000000..80957ac --- /dev/null +++ b/admin/common/dto/type.go @@ -0,0 +1,21 @@ +package dto + +import ( + "github.com/gin-gonic/gin" + "git.ilapage.cn/OPC/chorus/admin/common/models" +) + +type Index interface { + Generate() Index + Bind(ctx *gin.Context) error + GetPageIndex() int + GetPageSize() int + GetNeedSearch() interface{} +} + +type Control interface { + Generate() Control + Bind(ctx *gin.Context) error + GenerateM() (models.ActiveRecord, error) + GetId() interface{} +} diff --git a/admin/common/file_store/initialize.go b/admin/common/file_store/initialize.go new file mode 100644 index 0000000..fabce9f --- /dev/null +++ b/admin/common/file_store/initialize.go @@ -0,0 +1,45 @@ +package file_store + +import "fmt" + +type OXS struct { + // Endpoint 访问域名 + Endpoint string + // AccessKeyID AK + AccessKeyID string + // AccessKeySecret AKS + AccessKeySecret string + // BucketName 桶名称 + BucketName string +} + +// Setup 配置文件存储driver +func (e *OXS) Setup(driver DriverType, options ...ClientOption) FileStoreType { + fileStoreType := driver + var fileStore FileStoreType + switch fileStoreType { + case AliYunOSS: + fileStore = new(ALiYunOSS) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + case HuaweiOBS: + fileStore = new(HuaWeiOBS) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + case QiNiuKodo: + fileStore = new(QiNiuKODO) + err := fileStore.Setup(e.Endpoint, e.AccessKeyID, e.AccessKeySecret, e.BucketName) + if err != nil { + fmt.Println(err) + } + return fileStore + } + + return nil +} diff --git a/admin/common/file_store/interface.go b/admin/common/file_store/interface.go new file mode 100644 index 0000000..acecbe9 --- /dev/null +++ b/admin/common/file_store/interface.go @@ -0,0 +1,27 @@ +package file_store + +// DriverType 驱动类型 +type DriverType string + +const ( + // HuaweiOBS 华为云OBS + HuaweiOBS DriverType = "HuaweiOBS" + // AliYunOSS 阿里云OSS + AliYunOSS DriverType = "AliYunOSS" + // QiNiuKodo 七牛云kodo + QiNiuKodo DriverType = "QiNiuKodo" +) + +type ClientOption map[string]interface{} + +// TODO: FileStoreType名称待定 + +// FileStoreType OXS +type FileStoreType interface { + // Setup 装载 endpoint sss + Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error + // UpLoad 上传 + UpLoad(yourObjectName string, localFile interface{}) error + // GetTempToken 获取临时Token + GetTempToken() (string, error) +} diff --git a/admin/common/file_store/kodo.go b/admin/common/file_store/kodo.go new file mode 100644 index 0000000..db896e2 --- /dev/null +++ b/admin/common/file_store/kodo.go @@ -0,0 +1,111 @@ +package file_store + +import ( + "context" + "fmt" + "github.com/qiniu/go-sdk/v7/auth/qbox" + "github.com/qiniu/go-sdk/v7/storage" +) + +type Zone string + +const ( + // HuaDong 华东 + HuaDong Zone = "HuaDong" + // HuaBei 华北 + HuaBei Zone = "HuaBei" + // HuaNan 华南 + HuaNan Zone = "HuaNan" + // BeiMei 北美 + BeiMei Zone = "BeiMei" + // XinJiaPo 新加坡 + XinJiaPo Zone = "XinJiaPo" +) + +type QiNiuKODO struct { + Client interface{} + BucketName string + cfg storage.Config + options []ClientOption +} + +func (e *QiNiuKODO) getToken() string { + putPolicy := storage.PutPolicy{ + Scope: e.BucketName, + } + if len(e.options) > 0 && e.options[0]["Expires"] != nil { + putPolicy.Expires = e.options[0]["Expires"].(uint64) + } + upToken := putPolicy.UploadToken(e.Client.(*qbox.Mac)) + return upToken +} + +//Setup 装载 +//endpoint sss +func (e *QiNiuKODO) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + + mac := qbox.NewMac(accessKeyID, accessKeySecret) + // 获取存储空间。 + cfg := storage.Config{} + // 空间对应的机房 + e.setZoneORDefault(cfg, options...) + // 是否使用https域名 + cfg.UseHTTPS = true + // 上传是否使用CDN上传加速 + cfg.UseCdnDomains = false + + e.Client = mac + e.BucketName = BucketName + e.cfg = cfg + e.options = options + return nil +} + +// setZoneORDefault 设置Zone或者默认华东 +func (e *QiNiuKODO) setZoneORDefault(cfg storage.Config, options ...ClientOption) { + if len(options) > 0 && options[0]["Zone"] != nil { + if _, ok := options[0]["Zone"].(Zone); !ok { + cfg.Zone = &storage.ZoneHuadong + } + switch options[0]["Zone"].(Zone) { + case HuaDong: + cfg.Zone = &storage.ZoneHuadong + case HuaBei: + cfg.Zone = &storage.ZoneHuabei + case HuaNan: + cfg.Zone = &storage.ZoneHuanan + case BeiMei: + cfg.Zone = &storage.ZoneBeimei + case XinJiaPo: + cfg.Zone = &storage.ZoneXinjiapo + default: + cfg.Zone = &storage.ZoneHuadong + } + } +} + +// UpLoad 文件上传 +func (e *QiNiuKODO) UpLoad(yourObjectName string, localFile interface{}) error { + + // 构建表单上传的对象 + formUploader := storage.NewFormUploader(&e.cfg) + ret := storage.PutRet{} + // 可选配置 + putExtra := storage.PutExtra{ + Params: map[string]string{ + "x:name": "github logo", + }, + } + err := formUploader.PutFile(context.Background(), &ret, e.getToken(), yourObjectName, localFile.(string), &putExtra) + if err != nil { + fmt.Println(err) + return err + } + fmt.Println(ret.Key, ret.Hash) + return nil +} + +func (e *QiNiuKODO) GetTempToken() (string, error) { + token := e.getToken() + return token, nil +} diff --git a/admin/common/file_store/obs.go b/admin/common/file_store/obs.go new file mode 100644 index 0000000..e937729 --- /dev/null +++ b/admin/common/file_store/obs.go @@ -0,0 +1,52 @@ +package file_store + +import ( + "fmt" + "github.com/huaweicloud/huaweicloud-sdk-go-obs/obs" + "log" +) + +type HuaWeiOBS struct { + Client interface{} + BucketName string +} + +func (e *HuaWeiOBS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + // 创建ObsClient结构体 + client, err := obs.New(accessKeyID, accessKeySecret, endpoint) + if err != nil { + log.Println("Error:", err) + return err + } + e.Client = client + e.BucketName = BucketName + return nil +} + +// UpLoad 文件上传 +// yourObjectName 文件路径名称,与objectKey是同一概念,表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg +func (e *HuaWeiOBS) UpLoad(yourObjectName string, localFile interface{}) error { + // 获取存储空间。 + input := &obs.PutFileInput{} + input.Bucket = e.BucketName + input.Key = yourObjectName + input.SourceFile = localFile.(string) + output, err := e.Client.(*obs.ObsClient).PutFile(input) + + if err == nil { + fmt.Printf("RequestId:%s\n", output.RequestId) + fmt.Printf("ETag:%s, StorageClass:%s\n", output.ETag, output.StorageClass) + } else { + if obsError, ok := err.(obs.ObsError); ok { + fmt.Println(obsError.Code) + fmt.Println(obsError.Message) + } else { + fmt.Println(err) + } + } + return nil +} + +func (e *HuaWeiOBS) GetTempToken() (string, error) { + return "", nil +} diff --git a/admin/common/file_store/oss.go b/admin/common/file_store/oss.go new file mode 100644 index 0000000..c35627b --- /dev/null +++ b/admin/common/file_store/oss.go @@ -0,0 +1,48 @@ +package file_store + +import ( + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "log" +) + +type ALiYunOSS struct { + Client interface{} + BucketName string +} + +//Setup 装载 +//endpoint sss +func (e *ALiYunOSS) Setup(endpoint, accessKeyID, accessKeySecret, BucketName string, options ...ClientOption) error { + client, err := oss.New(endpoint, accessKeyID, accessKeySecret) + if err != nil { + log.Println("Error:", err) + return err + } + e.Client = client + e.BucketName = BucketName + + return nil +} + +// UpLoad 文件上传 +func (e *ALiYunOSS) UpLoad(yourObjectName string, localFile interface{}) error { + // 获取存储空间。 + bucket, err := e.Client.(*oss.Client).Bucket(e.BucketName) + if err != nil { + log.Println("Error:", err) + return err + } + // 设置分片大小为100 KB,指定分片上传并发数为3,并开启断点续传上传。 + // 其中与objectKey是同一概念,表示断点续传上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg。 + // "LocalFile"为filePath,100*1024为partSize。 + err = bucket.UploadFile(yourObjectName, localFile.(string), 100*1024, oss.Routines(3), oss.Checkpoint(true, "")) + if err != nil { + log.Println("Error:", err) + return err + } + return nil +} + +func (e *ALiYunOSS) GetTempToken() (string, error) { + return "", nil +} diff --git a/admin/common/global/adm.go b/admin/common/global/adm.go new file mode 100644 index 0000000..6b19d7c --- /dev/null +++ b/admin/common/global/adm.go @@ -0,0 +1,11 @@ +package global + +const ( + // Version go-admin version info + Version = "2.2.0" +) + +var ( + // Driver 数据库驱动 + Driver string +) diff --git a/admin/common/global/casbin.go b/admin/common/global/casbin.go new file mode 100644 index 0000000..72b7e6c --- /dev/null +++ b/admin/common/global/casbin.go @@ -0,0 +1,18 @@ +package global + +import ( + "github.com/casbin/casbin/v2" + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" +) + +func LoadPolicy(c *gin.Context) (*casbin.SyncedEnforcer, error) { + log := api.GetRequestLogger(c) + if err := sdk.Runtime.GetCasbinKey(c.Request.Host).LoadPolicy(); err == nil { + return sdk.Runtime.GetCasbinKey(c.Request.Host), err + } else { + log.Errorf("casbin rbac_model or policy init error, %s ", err.Error()) + return nil, err + } +} diff --git a/admin/common/global/logo.go b/admin/common/global/logo.go new file mode 100644 index 0000000..e4ed471 --- /dev/null +++ b/admin/common/global/logo.go @@ -0,0 +1,4 @@ +package global + +// LogoContent go-admin ascii显示,减少静态文件依赖 +var LogoContent = []byte{10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 95, 95, 95, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 32, 32, 32, 32, 32, 32, 32, 32, 44, 39, 32, 32, 44, 32, 96, 46, 32, 32, 44, 45, 45, 44, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 44, 45, 43, 45, 44, 46, 39, 32, 95, 32, 124, 44, 45, 45, 46, 39, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 44, 45, 45, 45, 44, 10, 32, 32, 44, 45, 45, 45, 45, 46, 95, 44, 46, 32, 32, 39, 32, 32, 32, 44, 39, 92, 32, 32, 32, 44, 39, 32, 32, 46, 39, 32, 124, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 58, 32, 32, 44, 45, 43, 45, 46, 32, 59, 32, 32, 32, 44, 32, 124, 124, 124, 32, 32, 124, 44, 32, 32, 32, 32, 32, 32, 44, 45, 43, 45, 46, 32, 47, 32, 32, 124, 10, 32, 47, 32, 32, 32, 47, 32, 32, 39, 32, 47, 32, 47, 32, 32, 32, 47, 32, 32, 32, 124, 44, 45, 45, 45, 46, 39, 32, 32, 32, 44, 32, 44, 45, 45, 46, 45, 45, 46, 32, 32, 32, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 32, 32, 124, 124, 96, 45, 45, 39, 95, 32, 32, 32, 32, 32, 44, 45, 45, 46, 39, 124, 39, 32, 32, 32, 124, 10, 124, 32, 32, 32, 58, 32, 32, 32, 32, 32, 124, 46, 32, 32, 32, 59, 32, 44, 46, 32, 58, 124, 32, 32, 32, 124, 32, 32, 32, 32, 124, 47, 32, 32, 32, 32, 32, 32, 32, 92, 32, 32, 32, 44, 45, 45, 46, 95, 95, 124, 32, 124, 124, 32, 32, 32, 124, 32, 32, 44, 39, 44, 32, 124, 32, 32, 124, 44, 44, 39, 32, 44, 39, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 32, 44, 34, 39, 32, 124, 10, 124, 32, 32, 32, 124, 32, 46, 92, 32, 32, 46, 39, 32, 32, 32, 124, 32, 124, 58, 32, 58, 58, 32, 32, 32, 58, 32, 32, 46, 39, 46, 45, 45, 46, 32, 32, 46, 45, 46, 32, 124, 32, 47, 32, 32, 32, 44, 39, 32, 32, 32, 124, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 45, 45, 39, 32, 39, 32, 32, 124, 32, 124, 32, 32, 32, 124, 32, 32, 32, 124, 32, 47, 32, 32, 124, 32, 124, 10, 46, 32, 32, 32, 59, 32, 39, 59, 32, 32, 124, 39, 32, 32, 32, 124, 32, 46, 59, 32, 58, 58, 32, 32, 32, 124, 46, 39, 32, 32, 32, 92, 95, 95, 92, 47, 58, 32, 46, 32, 46, 46, 32, 32, 32, 39, 32, 32, 47, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 32, 44, 32, 32, 32, 32, 124, 32, 32, 124, 32, 58, 32, 32, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 32, 124, 10, 39, 32, 32, 32, 46, 32, 32, 32, 46, 32, 124, 124, 32, 32, 32, 58, 32, 32, 32, 32, 124, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 44, 34, 32, 46, 45, 45, 46, 59, 32, 124, 39, 32, 32, 32, 59, 32, 124, 58, 32, 32, 124, 124, 32, 32, 32, 58, 32, 124, 32, 32, 124, 47, 32, 32, 32, 32, 32, 39, 32, 32, 58, 32, 124, 95, 95, 32, 124, 32, 32, 32, 124, 32, 124, 32, 32, 124, 47, 10, 32, 96, 45, 45, 45, 96, 45, 39, 124, 32, 124, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 47, 32, 32, 47, 32, 32, 44, 46, 32, 32, 124, 124, 32, 32, 32, 124, 32, 39, 47, 32, 32, 39, 124, 32, 32, 32, 124, 32, 124, 96, 45, 39, 32, 32, 32, 32, 32, 32, 124, 32, 32, 124, 32, 39, 46, 39, 124, 124, 32, 32, 32, 124, 32, 124, 45, 45, 39, 10, 32, 46, 39, 95, 95, 47, 92, 95, 58, 32, 124, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 46, 39, 32, 32, 32, 92, 32, 32, 32, 58, 32, 32, 32, 32, 58, 124, 124, 32, 32, 32, 59, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 59, 32, 32, 58, 32, 32, 32, 32, 59, 124, 32, 32, 32, 124, 47, 10, 32, 124, 32, 32, 32, 58, 32, 32, 32, 32, 58, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 32, 32, 46, 45, 46, 47, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 39, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 124, 32, 32, 44, 32, 32, 32, 47, 32, 39, 45, 45, 45, 39, 10, 32, 32, 92, 32, 32, 32, 92, 32, 32, 47, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 96, 45, 45, 96, 45, 45, 45, 39, 32, 32, 32, 32, 32, 96, 45, 45, 45, 45, 39, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 45, 45, 45, 96, 45, 39, 10, 32, 32, 32, 96, 45, 45, 96, 45, 39, 10} diff --git a/admin/common/global/topic.go b/admin/common/global/topic.go new file mode 100644 index 0000000..60e1d34 --- /dev/null +++ b/admin/common/global/topic.go @@ -0,0 +1,7 @@ +package global + +const ( + LoginLog = "login_log_queue" + OperateLog = "operate_log_queue" + ApiCheck = "api_check_queue" +) diff --git a/admin/common/ip.go b/admin/common/ip.go new file mode 100644 index 0000000..ec95a8b --- /dev/null +++ b/admin/common/ip.go @@ -0,0 +1,28 @@ +package common + +import ( + "github.com/gin-gonic/gin" + "strings" +) + +func GetClientIP(c *gin.Context) string { + // 优先从 X-Forwarded-For 获取 IP + ip := c.Request.Header.Get("X-Forwarded-For") + if ip == "" || strings.Contains(ip, "127.0.0.1") { + // 如果为空或为本地地址,则尝试从 X-Real-IP 获取 + ip = c.Request.Header.Get("X-real-ip") + } + if ip == "" { + // 如果仍为空,则使用 RemoteIP + ip = c.RemoteIP() + } + if ip == "" || ip == "127.0.0.1" { + // 如果仍为空或为本地地址,则使用 ClientIP + ip = c.ClientIP() + } + if ip == "" { + // 最后兜底为本地地址 + ip = "127.0.0.1" + } + return ip +} diff --git a/admin/common/middleware/auth.go b/admin/common/middleware/auth.go new file mode 100644 index 0000000..f4dcd9f --- /dev/null +++ b/admin/common/middleware/auth.go @@ -0,0 +1,38 @@ +package middleware + +import ( + "time" + + "git.ilapage.cn/OPC/chorus/admin/common/middleware/handler" + "github.com/go-admin-team/go-admin-core/sdk/config" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" +) + +// AuthInit jwt验证new +func AuthInit() (*jwt.GinJWTMiddleware, error) { + timeout := time.Hour + if config.ApplicationConfig.Mode == "dev" { + timeout = time.Duration(876010) * time.Hour + } else { + if config.JwtConfig.Timeout != 0 { + timeout = time.Duration(config.JwtConfig.Timeout) * time.Second + } + } + return jwt.New(&jwt.GinJWTMiddleware{ + Realm: "test zone", + Key: []byte(config.JwtConfig.Secret), + Timeout: timeout, + MaxRefresh: time.Hour, + PayloadFunc: handler.PayloadFunc, + IdentityHandler: handler.IdentityHandler, + Authenticator: handler.Authenticator, + Authorizator: handler.Authorizator, + Unauthorized: handler.Unauthorized, + // Management APIs use bearer headers only. Query and cookie tokens leak + // into access logs or turn state-changing requests into CSRF targets. + TokenLookup: "header: Authorization", + TokenHeadName: "Bearer", + TimeFunc: time.Now, + }) + +} diff --git a/admin/common/middleware/auth_test.go b/admin/common/middleware/auth_test.go new file mode 100644 index 0000000..2303c55 --- /dev/null +++ b/admin/common/middleware/auth_test.go @@ -0,0 +1,13 @@ +package middleware + +import "testing" + +func TestAuthUsesAuthorizationHeaderOnly(t *testing.T) { + auth, err := AuthInit() + if err != nil { + t.Fatalf("initialize JWT middleware: %v", err) + } + if auth.TokenLookup != "header: Authorization" { + t.Fatalf("token lookup = %q, want authorization header only", auth.TokenLookup) + } +} diff --git a/admin/common/middleware/customerror.go b/admin/common/middleware/customerror.go new file mode 100644 index 0000000..1f92628 --- /dev/null +++ b/admin/common/middleware/customerror.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "fmt" + "net/http" + "runtime" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func CustomError(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + + if c.IsAborted() { + c.Status(200) + } + switch errStr := err.(type) { + case string: + p := strings.Split(errStr, "#") + if len(p) == 3 && p[0] == "CustomError" { + statusCode, e := strconv.Atoi(p[1]) + if e != nil { + break + } + c.Status(statusCode) + fmt.Println( + time.Now().Format("2006-01-02 15:04:05"), + "[ERROR]", + c.Request.Method, + c.Request.URL, + statusCode, + c.Request.RequestURI, + c.ClientIP(), + p[2], + ) + c.JSON(http.StatusOK, gin.H{ + "code": statusCode, + "msg": p[2], + }) + } else { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": errStr, + }) + } + case runtime.Error: + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": errStr.Error(), + }) + default: + panic(err) + } + } + }() + c.Next() +} diff --git a/admin/common/middleware/db.go b/admin/common/middleware/db.go new file mode 100644 index 0000000..0e73a2d --- /dev/null +++ b/admin/common/middleware/db.go @@ -0,0 +1,11 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" +) + +func WithContextDb(c *gin.Context) { + c.Set("db", sdk.Runtime.GetDbByKey(c.Request.Host).WithContext(c)) + c.Next() +} diff --git a/admin/common/middleware/demo.go b/admin/common/middleware/demo.go new file mode 100644 index 0000000..f13f28f --- /dev/null +++ b/admin/common/middleware/demo.go @@ -0,0 +1,29 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk/config" + "net/http" +) + +func DemoEvn() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + if config.ApplicationConfig.Mode == "demo" { + if method == "GET" || + method == "OPTIONS" || + c.Request.RequestURI == "/api/v1/login" || + c.Request.RequestURI == "/api/v1/logout" { + c.Next() + } else { + c.JSON(http.StatusOK, gin.H{ + "code": 500, + "msg": "谢谢您的参与,但为了大家更好的体验,所以本次提交就算了吧!\U0001F600\U0001F600\U0001F600", + }) + c.Abort() + return + } + } + c.Next() + } +} diff --git a/admin/common/middleware/handler/auth.go b/admin/common/middleware/handler/auth.go new file mode 100644 index 0000000..6f831cc --- /dev/null +++ b/admin/common/middleware/handler/auth.go @@ -0,0 +1,182 @@ +package handler + +import ( + "git.ilapage.cn/OPC/chorus/admin/app/admin/models" + "git.ilapage.cn/OPC/chorus/admin/common" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" + "github.com/mssola/user_agent" + "git.ilapage.cn/OPC/chorus/admin/common/global" +) + +func PayloadFunc(data interface{}) jwt.MapClaims { + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(SysUser) + r, _ := v["role"].(SysRole) + return jwt.MapClaims{ + jwt.IdentityKey: u.UserId, + jwt.RoleIdKey: r.RoleId, + jwt.RoleKey: r.RoleKey, + jwt.NiceKey: u.Username, + jwt.DataScopeKey: r.DataScope, + jwt.RoleNameKey: r.RoleName, + } + } + return jwt.MapClaims{} +} + +func IdentityHandler(c *gin.Context) interface{} { + claims := jwt.ExtractClaims(c) + return map[string]interface{}{ + "IdentityKey": claims["identity"], + "UserName": claims["nice"], + "RoleKey": claims["rolekey"], + "UserId": claims["identity"], + "RoleIds": claims["roleid"], + "DataScope": claims["datascope"], + } +} + +// Authenticator 获取token +// @Summary 登陆 +// @Description 获取token +// @Description LoginHandler can be used by clients to get a jwt token. +// @Description Payload needs to be json in the form of {"username": "USERNAME", "password": "PASSWORD"}. +// @Description Reply will be of the form {"token": "TOKEN"}. +// @Description dev mode:It should be noted that all fields cannot be empty, and a value of 0 can be passed in addition to the account password +// @Description 注意:开发模式:需要注意全部字段不能为空,账号密码外可以传入0值 +// @Tags 登陆 +// @Accept application/json +// @Product application/json +// @Param account body Login true "account" +// @Success 200 {string} string "{"code": 200, "expire": "2019-08-07T12:45:48+08:00", "token": ".eyJleHAiOjE1NjUxNTMxNDgsImlkIjoiYWRtaW4iLCJvcmlnX2lhdCI6MTU2NTE0OTU0OH0.-zvzHvbg0A" }" +// @Router /api/v1/login [post] +func Authenticator(c *gin.Context) (interface{}, error) { + log := api.GetRequestLogger(c) + db, err := pkg.GetOrm(c) + if err != nil { + log.Errorf("get db error, %s", err.Error()) + response.Error(c, 500, err, "数据库连接获取失败") + return nil, jwt.ErrFailedAuthentication + } + + var loginVals Login + var status = "2" + var msg = "登录成功" + var username = "" + defer func() { + LoginLogToDB(c, status, msg, username) + }() + + if err = c.ShouldBind(&loginVals); err != nil { + username = loginVals.Username + msg = "数据解析失败" + status = "1" + + return nil, jwt.ErrMissingLoginValues + } + if config.ApplicationConfig.Mode != "dev" { + if !captcha.Verify(loginVals.UUID, loginVals.Code, true) { + username = loginVals.Username + msg = "验证码错误" + status = "1" + + return nil, jwt.ErrInvalidVerificationode + } + } + sysUser, role, e := loginVals.GetUser(db) + if e == nil { + username = loginVals.Username + + return map[string]interface{}{"user": sysUser, "role": role}, nil + } else { + msg = "登录失败" + status = "1" + log.Warnf("%s login failed!", loginVals.Username) + } + return nil, jwt.ErrFailedAuthentication +} + +// LoginLogToDB Write log to database +func LoginLogToDB(c *gin.Context, status string, msg string, username string) { + if !config.LoggerConfig.EnabledDB { + return + } + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + + ua := user_agent.New(c.Request.UserAgent()) + l["ipaddr"] = common.GetClientIP(c) + l["loginLocation"] = "" // pkg.GetLocation(common.GetClientIP(c),gaConfig.ExtConfig.AMap.Key) + l["loginTime"] = pkg.GetCurrentTime() + l["status"] = status + l["remark"] = c.Request.UserAgent() + browserName, browserVersion := ua.Browser() + l["browser"] = browserName + " " + browserVersion + l["os"] = ua.OS() + l["platform"] = ua.Platform() + l["username"] = username + l["msg"] = msg + + q := sdk.Runtime.GetMemoryQueue(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.LoginLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + //日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +} + +// LogOut +// @Summary 退出登录 +// @Description 获取token +// LoginHandler can be used by clients to get a jwt token. +// Reply will be of the form {"token": "TOKEN"}. +// @Accept application/json +// @Product application/json +// @Success 200 {string} string "{"code": 200, "msg": "成功退出系统" }" +// @Router /logout [post] +// @Security Bearer +func LogOut(c *gin.Context) { + LoginLogToDB(c, "2", "退出成功", user.GetUserName(c)) + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "msg": "退出成功", + }) + +} + +func Authorizator(data interface{}, c *gin.Context) bool { + + if v, ok := data.(map[string]interface{}); ok { + u, _ := v["user"].(models.SysUser) + r, _ := v["role"].(models.SysRole) + c.Set("role", r.RoleName) + c.Set("roleIds", r.RoleId) + c.Set("userId", u.UserId) + c.Set("userName", u.Username) + c.Set("dataScope", r.DataScope) + return true + } + return false +} + +func Unauthorized(c *gin.Context, code int, message string) { + c.JSON(http.StatusOK, gin.H{ + "code": code, + "msg": message, + }) +} diff --git a/admin/common/middleware/handler/httpshandler.go b/admin/common/middleware/handler/httpshandler.go new file mode 100644 index 0000000..e127439 --- /dev/null +++ b/admin/common/middleware/handler/httpshandler.go @@ -0,0 +1,22 @@ +package handler + +import ( + "github.com/gin-gonic/gin" + "github.com/unrolled/secure" + + "github.com/go-admin-team/go-admin-core/sdk/config" +) + +func TlsHandler() gin.HandlerFunc { + return func(c *gin.Context) { + secureMiddleware := secure.New(secure.Options{ + SSLRedirect: true, + SSLHost: config.SslConfig.Domain, + }) + err := secureMiddleware.Process(c.Writer, c.Request) + if err != nil { + return + } + c.Next() + } +} diff --git a/admin/common/middleware/handler/login.go b/admin/common/middleware/handler/login.go new file mode 100644 index 0000000..d17626c --- /dev/null +++ b/admin/common/middleware/handler/login.go @@ -0,0 +1,33 @@ +package handler + +import ( + log "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "gorm.io/gorm" +) + +type Login struct { + Username string `form:"UserName" json:"username" binding:"required"` + Password string `form:"Password" json:"password" binding:"required"` + Code string `form:"Code" json:"code" binding:"required"` + UUID string `form:"UUID" json:"uuid" binding:"required"` +} + +func (u *Login) GetUser(tx *gorm.DB) (user SysUser, role SysRole, err error) { + err = tx.Table("sys_user").Where("username = ? and status = '2'", u.Username).First(&user).Error + if err != nil { + log.Errorf("get user error, %s", err.Error()) + return + } + _, err = pkg.CompareHashAndPassword(user.Password, u.Password) + if err != nil { + log.Errorf("user login error, %s", err.Error()) + return + } + err = tx.Table("sys_role").Where("role_id = ? ", user.RoleId).First(&role).Error + if err != nil { + log.Errorf("get role error, %s", err.Error()) + return + } + return +} diff --git a/admin/common/middleware/handler/ping.go b/admin/common/middleware/handler/ping.go new file mode 100644 index 0000000..ab4d645 --- /dev/null +++ b/admin/common/middleware/handler/ping.go @@ -0,0 +1,11 @@ +package handler + +import ( + "github.com/gin-gonic/gin" +) + +func Ping(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "ok", + }) +} diff --git a/admin/common/middleware/handler/role.go b/admin/common/middleware/handler/role.go new file mode 100644 index 0000000..119e9a7 --- /dev/null +++ b/admin/common/middleware/handler/role.go @@ -0,0 +1,24 @@ +package handler + +import "git.ilapage.cn/OPC/chorus/admin/common/models" + +type SysRole struct { + RoleId int `json:"roleId" gorm:"primaryKey;autoIncrement"` // 角色编码 + RoleName string `json:"roleName" gorm:"size:128;"` // 角色名称 + Status string `json:"status" gorm:"size:4;"` // + RoleKey string `json:"roleKey" gorm:"size:128;"` //角色代码 + RoleSort int `json:"roleSort" gorm:""` //角色排序 + Flag string `json:"flag" gorm:"size:128;"` // + Remark string `json:"remark" gorm:"size:255;"` //备注 + Admin bool `json:"admin" gorm:"size:4;"` + DataScope string `json:"dataScope" gorm:"size:128;"` + Params string `json:"params" gorm:"-"` + MenuIds []int `json:"menuIds" gorm:"-"` + DeptIds []int `json:"deptIds" gorm:"-"` + models.ControlBy + models.ModelTime +} + +func (SysRole) TableName() string { + return "sys_role" +} diff --git a/admin/common/middleware/handler/user.go b/admin/common/middleware/handler/user.go new file mode 100644 index 0000000..311a9a6 --- /dev/null +++ b/admin/common/middleware/handler/user.go @@ -0,0 +1,40 @@ +package handler + +import ( + "git.ilapage.cn/OPC/chorus/admin/common/models" + "gorm.io/gorm" +) + +type SysUser struct { + UserId int `gorm:"primaryKey;autoIncrement;comment:编码" json:"userId"` + Username string `json:"username" gorm:"size:64;comment:用户名"` + Password string `json:"-" gorm:"size:128;comment:密码"` + NickName string `json:"nickName" gorm:"size:128;comment:昵称"` + Phone string `json:"phone" gorm:"size:11;comment:手机号"` + RoleId int `json:"roleId" gorm:"size:20;comment:角色ID"` + Salt string `json:"-" gorm:"size:255;comment:加盐"` + Avatar string `json:"avatar" gorm:"size:255;comment:头像"` + Sex string `json:"sex" gorm:"size:255;comment:性别"` + Email string `json:"email" gorm:"size:128;comment:邮箱"` + DeptId int `json:"deptId" gorm:"size:20;comment:部门"` + PostId int `json:"postId" gorm:"size:20;comment:岗位"` + Remark string `json:"remark" gorm:"size:255;comment:备注"` + Status string `json:"status" gorm:"size:4;comment:状态"` + DeptIds []int `json:"deptIds" gorm:"-"` + PostIds []int `json:"postIds" gorm:"-"` + RoleIds []int `json:"roleIds" gorm:"-"` + //Dept *SysDept `json:"dept"` + models.ControlBy + models.ModelTime +} + +func (*SysUser) TableName() string { + return "sys_user" +} + +func (e *SysUser) AfterFind(_ *gorm.DB) error { + e.DeptIds = []int{e.DeptId} + e.PostIds = []int{e.PostId} + e.RoleIds = []int{e.RoleId} + return nil +} diff --git a/admin/common/middleware/header.go b/admin/common/middleware/header.go new file mode 100644 index 0000000..b1411e5 --- /dev/null +++ b/admin/common/middleware/header.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +// NoCache is a middleware function that appends headers +// to prevent the client from caching the HTTP response. +func NoCache(c *gin.Context) { + c.Header("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate, value") + c.Header("Expires", "Thu, 01 Jan 1970 00:00:00 GMT") + c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) + c.Next() +} + +// Options is a middleware function that appends headers +// for options requests and aborts then exits the middleware +// chain and ends the request. +func Options(c *gin.Context) { + if c.Request.Method != "OPTIONS" { + c.Next() + } else { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Access-Control-Allow-Headers", "authorization, origin, content-type, accept") + c.Header("Allow", "HEAD,GET,POST,PUT,PATCH,DELETE,OPTIONS") + c.Header("Content-Type", "application/json") + c.AbortWithStatus(200) + } +} + +// Secure is a middleware function that appends security +// and resource access headers. +func Secure(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + //c.Header("X-Frame-Options", "DENY") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("X-XSS-Protection", "1; mode=block") + if c.Request.TLS != nil { + c.Header("Strict-Transport-Security", "max-age=31536000") + } + + // Also consider adding Content-Security-Policy headers + // c.Header("Content-Security-Policy", "script-src 'self' https://cdnjs.cloudflare.com") +} diff --git a/admin/common/middleware/init.go b/admin/common/middleware/init.go new file mode 100644 index 0000000..acd9d38 --- /dev/null +++ b/admin/common/middleware/init.go @@ -0,0 +1,35 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + jwt "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "git.ilapage.cn/OPC/chorus/admin/common/actions" +) + +const ( + JwtTokenCheck string = "JwtToken" + RoleCheck string = "AuthCheckRole" + PermissionCheck string = "PermissionAction" +) + +func InitMiddleware(r *gin.Engine) { + r.Use(DemoEvn()) + // 数据库链接 + r.Use(WithContextDb) + // 日志处理 + r.Use(LoggerToFile()) + // 自定义错误处理 + r.Use(CustomError) + // NoCache is a middleware function that appends headers + r.Use(NoCache) + // 跨域处理 + r.Use(Options) + // Secure is a middleware function that appends security + r.Use(Secure) + // 链路追踪 + //r.Use(middleware.Trace()) + sdk.Runtime.SetMiddleware(JwtTokenCheck, (*jwt.GinJWTMiddleware).MiddlewareFunc) + sdk.Runtime.SetMiddleware(RoleCheck, AuthCheckRole()) + sdk.Runtime.SetMiddleware(PermissionCheck, actions.PermissionAction()) +} diff --git a/admin/common/middleware/logger.go b/admin/common/middleware/logger.go new file mode 100644 index 0000000..525b6cc --- /dev/null +++ b/admin/common/middleware/logger.go @@ -0,0 +1,136 @@ +package middleware + +import ( + "bytes" + "encoding/json" + "git.ilapage.cn/OPC/chorus/admin/app/admin/service/dto" + "git.ilapage.cn/OPC/chorus/admin/common" + "io" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth/user" + + "git.ilapage.cn/OPC/chorus/admin/common/global" +) + +// LoggerToFile 日志记录到文件 +func LoggerToFile() gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + // 开始时间 + startTime := time.Now() + // 处理请求 + var body string + if config.LoggerConfig.EnabledDB && !isChorusManagementPath(c.Request.URL.Path) { + payload, err := io.ReadAll(c.Request.Body) + if err != nil { + log.Warnf("read body for operation log: %s", err.Error()) + } else { + c.Request.Body = io.NopCloser(bytes.NewReader(payload)) + body = string(payload) + } + } + + c.Next() + url := c.Request.RequestURI + if strings.Index(url, "logout") > -1 || + strings.Index(url, "login") > -1 { + return + } + // 结束时间 + endTime := time.Now() + if c.Request.Method == http.MethodOptions { + return + } + + rt, bl := c.Get("result") + var result = "" + if bl { + rb, err := json.Marshal(rt) + if err != nil { + log.Warnf("json Marshal result error, %s", err.Error()) + } else { + result = string(rb) + } + } + + st, bl := c.Get("status") + var statusBus = 0 + if bl { + statusBus = st.(int) + } + + // 请求方式 + reqMethod := c.Request.Method + // 请求路由 + reqUri := c.Request.RequestURI + // 状态码 + statusCode := c.Writer.Status() + // 请求IP + clientIP := common.GetClientIP(c) + // 执行时间 + latencyTime := endTime.Sub(startTime) + // 日志格式 + logData := map[string]interface{}{ + "statusCode": statusCode, + "latencyTime": latencyTime, + "clientIP": clientIP, + "method": reqMethod, + "uri": reqUri, + } + log.WithFields(logData).Info() + defer func() { + log.Fields(map[string]interface{}{}) + }() + if c.Request.Method != "OPTIONS" && config.LoggerConfig.EnabledDB && statusCode != 404 && !isChorusManagementPath(c.Request.URL.Path) { + SetDBOperLog(c, clientIP, statusCode, reqUri, reqMethod, latencyTime, body, result, statusBus) + } + } +} + +func isChorusManagementPath(path string) bool { + return strings.HasPrefix(path, "/api/v1/chorus/") || path == "/api/v1/chorus" +} + +// SetDBOperLog 写入操作日志表 fixme 该方法后续即将弃用 +func SetDBOperLog(c *gin.Context, clientIP string, statusCode int, reqUri string, reqMethod string, latencyTime time.Duration, body string, result string, status int) { + + log := api.GetRequestLogger(c) + l := make(map[string]interface{}) + l["_fullPath"] = c.FullPath() + l["operUrl"] = reqUri + l["operIp"] = clientIP + l["operLocation"] = "" // pkg.GetLocation(clientIP, gaConfig.ExtConfig.AMap.Key) + l["operName"] = user.GetUserName(c) + l["requestMethod"] = reqMethod + l["operParam"] = body + l["operTime"] = time.Now() + l["jsonResult"] = result + l["latencyTime"] = latencyTime.String() + l["statusCode"] = statusCode + l["userAgent"] = c.Request.UserAgent() + l["createBy"] = user.GetUserId(c) + l["updateBy"] = user.GetUserId(c) + if status == http.StatusOK { + l["status"] = dto.OperaStatusEnabel + } else { + l["status"] = dto.OperaStatusDisable + } + q := sdk.Runtime.GetMemoryQueue(c.Request.Host) + message, err := sdk.Runtime.GetStreamMessage("", global.OperateLog, l) + if err != nil { + log.Errorf("GetStreamMessage error, %s", err.Error()) + // 日志报错错误,不中断请求 + } else { + err = q.Append(message) + if err != nil { + log.Errorf("Append message error, %s", err.Error()) + } + } +} diff --git a/admin/common/middleware/permission.go b/admin/common/middleware/permission.go new file mode 100644 index 0000000..00d1b1f --- /dev/null +++ b/admin/common/middleware/permission.go @@ -0,0 +1,61 @@ +package middleware + +import ( + "github.com/casbin/casbin/v2/util" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/api" + "github.com/go-admin-team/go-admin-core/sdk/pkg/jwtauth" + "github.com/go-admin-team/go-admin-core/sdk/pkg/response" +) + +// AuthCheckRole 权限检查中间件 +func AuthCheckRole() gin.HandlerFunc { + return func(c *gin.Context) { + log := api.GetRequestLogger(c) + data, _ := c.Get(jwtauth.JwtPayloadKey) + v := data.(jwtauth.MapClaims) + e := sdk.Runtime.GetCasbinKey(c.Request.Host) + var res, casbinExclude bool + var err error + //检查权限 + if v["rolekey"] == "admin" { + res = true + c.Next() + return + } + for _, i := range CasbinExclude { + if util.KeyMatch2(c.Request.URL.Path, i.Url) && c.Request.Method == i.Method { + casbinExclude = true + break + } + } + if casbinExclude { + log.Infof("Casbin exclusion, no validation method:%s path:%s", c.Request.Method, c.Request.URL.Path) + c.Next() + return + } + res, err = e.Enforce(v["rolekey"], c.Request.URL.Path, c.Request.Method) + if err != nil { + log.Errorf("AuthCheckRole error:%s method:%s path:%s", err, c.Request.Method, c.Request.URL.Path) + response.Error(c, 500, err, "") + return + } + + if res { + log.Infof("isTrue: %v role: %s method: %s path: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path) + c.Next() + } else { + log.Warnf("isTrue: %v role: %s method: %s path: %s message: %s", res, v["rolekey"], c.Request.Method, c.Request.URL.Path, "当前request无权限,请管理员确认!") + c.JSON(http.StatusOK, gin.H{ + "code": 403, + "msg": "对不起,您没有该接口访问权限,请联系管理员", + }) + c.Abort() + return + } + + } +} diff --git a/admin/common/middleware/request_id.go b/admin/common/middleware/request_id.go new file mode 100644 index 0000000..8dedd83 --- /dev/null +++ b/admin/common/middleware/request_id.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "github.com/go-admin-team/go-admin-core/logger" + "github.com/go-admin-team/go-admin-core/sdk/pkg" + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +// RequestId 自动增加requestId +func RequestId(trafficKey string) gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.Method == http.MethodOptions { + c.Next() + return + } + requestId := c.GetHeader(trafficKey) + if requestId == "" { + requestId = c.GetHeader(strings.ToLower(trafficKey)) + } + if requestId == "" { + requestId = uuid.New().String() + } + c.Request.Header.Set(trafficKey, requestId) + c.Set(trafficKey, requestId) + c.Set(pkg.LoggerKey, + logger.NewHelper(logger.DefaultLogger). + WithFields(map[string]interface{}{ + trafficKey: requestId, + })) + c.Next() + } +} diff --git a/admin/common/middleware/security_test.go b/admin/common/middleware/security_test.go new file mode 100644 index 0000000..9b9d660 --- /dev/null +++ b/admin/common/middleware/security_test.go @@ -0,0 +1,14 @@ +package middleware + +import "testing" + +func TestChorusManagementPathIsExcludedFromGenericOperationLogs(t *testing.T) { + for _, path := range []string{"/api/v1/chorus", "/api/v1/chorus/providers", "/api/v1/chorus/providers/1/credential"} { + if !isChorusManagementPath(path) { + t.Fatalf("management path %q must be excluded", path) + } + } + if isChorusManagementPath("/api/v1/sys_user") { + t.Fatal("unrelated admin path must not match") + } +} diff --git a/admin/common/middleware/sentinel.go b/admin/common/middleware/sentinel.go new file mode 100644 index 0000000..603559a --- /dev/null +++ b/admin/common/middleware/sentinel.go @@ -0,0 +1,30 @@ +package middleware + +import ( + "github.com/alibaba/sentinel-golang/core/system" + sentinel "github.com/alibaba/sentinel-golang/pkg/adapters/gin" + "github.com/gin-gonic/gin" + + log "github.com/go-admin-team/go-admin-core/logger" +) + +// Sentinel 限流 +func Sentinel() gin.HandlerFunc { + if _, err := system.LoadRules([]*system.Rule{ + { + MetricType: system.InboundQPS, + TriggerCount: 200, + Strategy: system.BBR, + }, + }); err != nil { + log.Fatalf("Unexpected error: %+v", err) + } + return sentinel.SentinelMiddleware( + sentinel.WithBlockFallback(func(ctx *gin.Context) { + ctx.AbortWithStatusJSON(200, map[string]interface{}{ + "msg": "too many request; the quota used up!", + "code": 500, + }) + }), + ) +} diff --git a/admin/common/middleware/settings.go b/admin/common/middleware/settings.go new file mode 100644 index 0000000..daf8d30 --- /dev/null +++ b/admin/common/middleware/settings.go @@ -0,0 +1,43 @@ +package middleware + +type UrlInfo struct { + Url string + Method string +} + +// CasbinExclude casbin 排除的路由列表 +var CasbinExclude = []UrlInfo{ + {Url: "/api/v1/dict/type-option-select", Method: "GET"}, + {Url: "/api/v1/dict-data/option-select", Method: "GET"}, + {Url: "/api/v1/deptTree", Method: "GET"}, + {Url: "/api/v1/db/tables/page", Method: "GET"}, + {Url: "/api/v1/db/columns/page", Method: "GET"}, + {Url: "/api/v1/gen/toproject/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/todb/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/tabletree", Method: "GET"}, + {Url: "/api/v1/gen/preview/:tableId", Method: "GET"}, + {Url: "/api/v1/gen/apitofile/:tableId", Method: "GET"}, + {Url: "/api/v1/getCaptcha", Method: "GET"}, + {Url: "/api/v1/getinfo", Method: "GET"}, + {Url: "/api/v1/menuTreeselect", Method: "GET"}, + {Url: "/api/v1/menurole", Method: "GET"}, + {Url: "/api/v1/menuids", Method: "GET"}, + {Url: "/api/v1/roleMenuTreeselect/:roleId", Method: "GET"}, + {Url: "/api/v1/roleDeptTreeselect/:roleId", Method: "GET"}, + {Url: "/api/v1/refresh_token", Method: "GET"}, + {Url: "/api/v1/configKey/:configKey", Method: "GET"}, + {Url: "/api/v1/app-config", Method: "GET"}, + {Url: "/api/v1/user/profile", Method: "GET"}, + {Url: "/info", Method: "GET"}, + {Url: "/api/v1/login", Method: "POST"}, + {Url: "/api/v1/logout", Method: "POST"}, + {Url: "/api/v1/user/avatar", Method: "POST"}, + {Url: "/api/v1/user/pwd", Method: "PUT"}, + {Url: "/api/v1/metrics", Method: "GET"}, + {Url: "/api/v1/health", Method: "GET"}, + {Url: "/", Method: "GET"}, + {Url: "/api/v1/server-monitor", Method: "GET"}, + {Url: "/api/v1/public/uploadFile", Method: "POST"}, + {Url: "/api/v1/user/pwd/set", Method: "PUT"}, + {Url: "/api/v1/sys-user", Method: "PUT"}, +} diff --git a/admin/common/middleware/trace.go b/admin/common/middleware/trace.go new file mode 100644 index 0000000..6cce4a3 --- /dev/null +++ b/admin/common/middleware/trace.go @@ -0,0 +1,27 @@ +package middleware + +import ( + "github.com/gin-gonic/gin" + "github.com/opentracing/opentracing-go" +) + +// Trace 链路追踪 +func Trace() gin.HandlerFunc { + return func(ctx *gin.Context) { + var sp opentracing.Span + opName := ctx.Request.URL.Path + // Attempt to join a trace by getting trace context from the headers. + wireContext, err := opentracing.GlobalTracer().Extract( + opentracing.TextMap, + opentracing.HTTPHeadersCarrier(ctx.Request.Header)) + if err != nil { + // If for whatever reason we can't join, go ahead and start a new root span. + sp = opentracing.StartSpan(opName) + } else { + sp = opentracing.StartSpan(opName, opentracing.ChildOf(wireContext)) + } + ctx.Set("traceSpan", sp) + ctx.Next() + sp.Finish() + } +} diff --git a/admin/common/models/by.go b/admin/common/models/by.go new file mode 100644 index 0000000..181c7ed --- /dev/null +++ b/admin/common/models/by.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type ControlBy struct { + CreateBy int `json:"createBy" gorm:"index;comment:创建者"` + UpdateBy int `json:"updateBy" gorm:"index;comment:更新者"` +} + +// SetCreateBy 设置创建人id +func (e *ControlBy) SetCreateBy(createBy int) { + e.CreateBy = createBy +} + +// SetUpdateBy 设置修改人id +func (e *ControlBy) SetUpdateBy(updateBy int) { + e.UpdateBy = updateBy +} + +type Model struct { + Id int `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"` +} + +type ModelTime struct { + CreatedAt time.Time `json:"createdAt" gorm:"comment:创建时间"` + UpdatedAt time.Time `json:"updatedAt" gorm:"comment:最后更新时间"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index;comment:删除时间"` +} \ No newline at end of file diff --git a/admin/common/models/menu.go b/admin/common/models/menu.go new file mode 100644 index 0000000..e3b0c92 --- /dev/null +++ b/admin/common/models/menu.go @@ -0,0 +1,11 @@ +package models + +// Menu 菜单中的类型枚举值 +const ( + // Directory 目录 + Directory string = "M" + // Menu 菜单 + Menu string = "C" + // Button 按钮 + Button string = "F" +) diff --git a/admin/common/models/migrate.go b/admin/common/models/migrate.go new file mode 100644 index 0000000..5573cb2 --- /dev/null +++ b/admin/common/models/migrate.go @@ -0,0 +1,12 @@ +package models + +import "time" + +type Migration struct { + Version string `gorm:"primaryKey"` + ApplyTime time.Time `gorm:"autoCreateTime"` +} + +func (Migration) TableName() string { + return "sys_migration" +} diff --git a/admin/common/models/response.go b/admin/common/models/response.go new file mode 100644 index 0000000..8ee0625 --- /dev/null +++ b/admin/common/models/response.go @@ -0,0 +1,30 @@ +package models + +type Response struct { + // 代码 + Code int `json:"code" example:"200"` + // 数据集 + Data interface{} `json:"data"` + // 消息 + Msg string `json:"msg"` + RequestId string `json:"requestId"` +} + +type Page struct { + List interface{} `json:"list"` + Count int `json:"count"` + PageIndex int `json:"pageIndex"` + PageSize int `json:"pageSize"` +} + +// ReturnOK 正常返回 +func (res *Response) ReturnOK() *Response { + res.Code = 200 + return res +} + +// ReturnError 错误返回 +func (res *Response) ReturnError(code int) *Response { + res.Code = code + return res +} diff --git a/admin/common/models/type.go b/admin/common/models/type.go new file mode 100644 index 0000000..2087d55 --- /dev/null +++ b/admin/common/models/type.go @@ -0,0 +1,11 @@ +package models + +import "gorm.io/gorm/schema" + +type ActiveRecord interface { + schema.Tabler + SetCreateBy(createBy int) + SetUpdateBy(updateBy int) + Generate() ActiveRecord + GetId() interface{} +} diff --git a/admin/common/models/user.go b/admin/common/models/user.go new file mode 100644 index 0000000..6053bb3 --- /dev/null +++ b/admin/common/models/user.go @@ -0,0 +1,42 @@ +package models + +import ( + "gorm.io/gorm" + + "github.com/go-admin-team/go-admin-core/sdk/pkg" +) + +// BaseUser 密码登录基础用户 +type BaseUser struct { + Username string `json:"username" gorm:"type:varchar(100);comment:用户名"` + Salt string `json:"-" gorm:"type:varchar(255);comment:加盐;<-"` + PasswordHash string `json:"-" gorm:"type:varchar(128);comment:密码hash;<-"` + Password string `json:"password" gorm:"-"` +} + +// SetPassword 设置密码 +func (u *BaseUser) SetPassword(value string) { + u.Password = value + u.generateSalt() + u.PasswordHash = u.GetPasswordHash() +} + +// GetPasswordHash 获取密码hash +func (u *BaseUser) GetPasswordHash() string { + passwordHash, err := pkg.SetPassword(u.Password, u.Salt) + if err != nil { + return "" + } + return passwordHash +} + +// generateSalt 生成加盐值 +func (u *BaseUser) generateSalt() { + u.Salt = pkg.GenerateRandomKey16() +} + +// Verify 验证密码 +func (u *BaseUser) Verify(db *gorm.DB, tableName string) bool { + db.Table(tableName).Where("username = ?", u.Username).First(u) + return u.GetPasswordHash() == u.PasswordHash +} diff --git a/admin/common/response/binding.go b/admin/common/response/binding.go new file mode 100644 index 0000000..6cf279a --- /dev/null +++ b/admin/common/response/binding.go @@ -0,0 +1,105 @@ +package response + +import ( + "fmt" + "github.com/gin-gonic/gin/binding" + "reflect" + "strings" + "sync" +) + +const ( + _ uint8 = iota + json + xml + yaml + form + query +) + +//var constructor = &bindConstructor{} + +type bindConstructor struct { + cache map[string][]uint8 + mux sync.Mutex +} + +func (e *bindConstructor) GetBindingForGin(d interface{}) []binding.Binding { + bs := e.getBinding(reflect.TypeOf(d).String()) + if bs == nil { + //重新构建 + bs = e.resolve(d) + } + gbs := make([]binding.Binding, len(bs)) + for i, b := range bs { + switch b { + case json: + gbs[i] = binding.JSON + case xml: + gbs[i] = binding.XML + case yaml: + gbs[i] = binding.YAML + case form: + gbs[i] = binding.Form + case query: + gbs[i] = binding.Query + default: + gbs[i] = nil + } + } + return gbs +} + +func (e *bindConstructor) resolve(d interface{}) []uint8 { + bs := make([]uint8, 0) + qType := reflect.TypeOf(d).Elem() + var tag reflect.StructTag + var ok bool + fmt.Println(qType.Kind()) + for i := 0; i < qType.NumField(); i++ { + tag = qType.Field(i).Tag + if _, ok = tag.Lookup("json"); ok { + bs = append(bs, json) + } + if _, ok = tag.Lookup("xml"); ok { + bs = append(bs, xml) + } + if _, ok = tag.Lookup("yaml"); ok { + bs = append(bs, yaml) + } + if _, ok = tag.Lookup("form"); ok { + bs = append(bs, form) + } + if _, ok = tag.Lookup("query"); ok { + bs = append(bs, query) + } + if _, ok = tag.Lookup("uri"); ok { + bs = append(bs, 0) + } + if t, ok := tag.Lookup("binding"); ok && strings.Index(t, "dive") > -1 { + qValue := reflect.ValueOf(d) + bs = append(bs, e.resolve(qValue.Field(i))...) + continue + } + if t, ok := tag.Lookup("validate"); ok && strings.Index(t, "dive") > -1 { + qValue := reflect.ValueOf(d) + bs = append(bs, e.resolve(qValue.Field(i))...) + } + } + return bs +} + +func (e *bindConstructor) getBinding(name string) []uint8 { + e.mux.Lock() + defer e.mux.Unlock() + return e.cache[name] +} + +func (e *bindConstructor) setBinding(name string, bs []uint8) { + e.mux.Lock() + defer e.mux.Unlock() + if e.cache == nil { + e.cache = make(map[string][]uint8) + } + e.cache[name] = bs +} diff --git a/admin/common/service/service.go b/admin/common/service/service.go new file mode 100644 index 0000000..0deb4a9 --- /dev/null +++ b/admin/common/service/service.go @@ -0,0 +1,25 @@ +package service + +import ( + "fmt" + + "github.com/go-admin-team/go-admin-core/logger" + "gorm.io/gorm" +) + +type Service struct { + Orm *gorm.DB + Msg string + MsgID string + Log *logger.Helper + Error error +} + +func (db *Service) AddError(err error) error { + if db.Error == nil { + db.Error = err + } else if err != nil { + db.Error = fmt.Errorf("%v; %w", db.Error, err) + } + return db.Error +} diff --git a/admin/common/storage/initialize.go b/admin/common/storage/initialize.go new file mode 100644 index 0000000..f1e6933 --- /dev/null +++ b/admin/common/storage/initialize.go @@ -0,0 +1,50 @@ +/* + * @Author: zhangwenjian + * @Date: 2025/04/13 22:03 + * @Last Modified by: zhangwenjian + * @Last Modified time: 2025/04/13 22:03 + */ + +package storage + +import ( + "log" + + "github.com/go-admin-team/go-admin-core/sdk" + "github.com/go-admin-team/go-admin-core/sdk/config" + "github.com/go-admin-team/go-admin-core/sdk/pkg/captcha" +) + +// Setup 配置storage组件 +func Setup() { + setupCache() + setupCaptcha() + setupQueue() +} + +func setupCache() { + cacheAdapter, err := config.CacheConfig.Setup() + if err != nil { + log.Fatalf("cache setup error, %s\n", err.Error()) + } + sdk.Runtime.SetCacheAdapter(cacheAdapter) +} + +func setupCaptcha() { + captcha.SetStore(captcha.NewCacheStore(sdk.Runtime.GetCacheAdapter(), 600)) +} + +func setupQueue() { + if config.QueueConfig.Empty() { + return + } + if q := sdk.Runtime.GetQueueAdapter(); q != nil { + q.Shutdown() + } + queueAdapter, err := config.QueueConfig.Setup() + if err != nil { + log.Fatalf("queue setup error, %s\n", err.Error()) + } + sdk.Runtime.SetQueueAdapter(queueAdapter) + go queueAdapter.Run() +} diff --git a/admin/config/extend.go b/admin/config/extend.go new file mode 100644 index 0000000..bd06e03 --- /dev/null +++ b/admin/config/extend.go @@ -0,0 +1,16 @@ +package config + +var ExtConfig Extend + +// Extend 扩展配置 +// extend: +// demo: +// name: demo-name +// 使用方法: config.ExtConfig......即可!! +type Extend struct { + AMap AMap // 这里配置对应配置文件的结构即可 +} + +type AMap struct { + Key string +} diff --git a/admin/config/settings.example.yml b/admin/config/settings.example.yml new file mode 100644 index 0000000..9641866 --- /dev/null +++ b/admin/config/settings.example.yml @@ -0,0 +1,23 @@ +settings: + application: + mode: prod + host: 127.0.0.1 + port: 8090 + readtimeout: 30 + writertimeout: 30 + logger: + path: var/logs/admin + stdout: default + level: info + enableddb: false + jwt: + # Set a unique secret in the protected operator configuration. + secret: replace-with-a-protected-jwt-secret + timeout: 3600 + database: + driver: mysql + # Supply the isolated Chorus database connection here. Do not put real + # credentials in this tracked example file. + source: user:password@tcp(127.0.0.1:3308)/chorus?charset=utf8mb4&parseTime=True&loc=Local + cache: + memory: '' diff --git a/admin/go.mod b/admin/go.mod new file mode 100644 index 0000000..af70a03 --- /dev/null +++ b/admin/go.mod @@ -0,0 +1,136 @@ +module git.ilapage.cn/OPC/chorus/admin + +go 1.26.5 + +require ( + git.ilapage.cn/OPC/chorus v0.0.0 + github.com/alibaba/sentinel-golang v1.0.4 + github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666 + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/bitly/go-simplejson v0.5.1 + github.com/bytedance/go-tagexpr/v2 v2.9.11 + github.com/casbin/casbin/v2 v2.135.0 + github.com/gin-gonic/gin v1.12.0 + github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 + github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 + github.com/google/uuid v1.6.0 + github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible + github.com/mssola/user_agent v0.6.0 + github.com/opentracing/opentracing-go v1.2.0 + github.com/pkg/errors v0.9.1 + github.com/qiniu/go-sdk/v7 v7.27.0 + github.com/spf13/cobra v1.10.2 + github.com/unrolled/secure v1.17.0 + golang.org/x/crypto v0.55.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.2 + gorm.io/driver/sqlite v1.6.0 + gorm.io/driver/sqlserver v1.6.4 + gorm.io/gorm v1.31.2 +) + +replace git.ilapage.cn/OPC/chorus => .. + +require ( + dario.cat/mergo v1.0.1 // indirect + filippo.io/edwards25519 v1.2.0 // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect + github.com/andeya/ameda v1.5.3 // indirect + github.com/andeya/goutil v1.1.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/bytedance/gopkg v0.1.4 // indirect + github.com/bytedance/sonic v1.15.2 // indirect + github.com/bytedance/sonic/loader v0.5.2 // indirect + github.com/casbin/gorm-adapter/v3 v3.32.0 // indirect + github.com/casbin/govaluate v1.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 // indirect + github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd // indirect + github.com/cloudwego/base64x v0.1.7 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.15 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/gin-contrib/sse v1.1.1 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/glebarez/sqlite v1.11.0 // indirect + github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.3 // indirect + github.com/go-sql-driver/mysql v1.10.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.10.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/leodido/go-urn v1.5.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/mattn/go-sqlite3 v1.14.49 // indirect + github.com/microsoft/go-mssqldb v1.10.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mojocn/base64Captcha v1.3.8 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nyaruka/phonenumbers v1.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.4.3 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/prometheus/client_golang v1.24.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.70.1 // indirect + github.com/prometheus/procfs v0.21.1 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.61.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/robfig/cron/v3 v3.0.1 // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect + github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.2 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/arch v0.30.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/image v0.41.0 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.41.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/plugin/dbresolver v1.6.2 // indirect + modernc.org/fileutil v1.3.0 // indirect + modernc.org/libc v1.62.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.9.1 // indirect + modernc.org/sqlite v1.37.0 // indirect +) + +//replace ( +// github.com/go-admin-team/go-admin-core v1.5.2-0.20231103105356-84418ed9252c => ../go-admin-core +// github.com/go-admin-team/go-admin-core/sdk v1.5.2-0.20231103105356-84418ed9252c => ../go-admin-core/sdk +//) diff --git a/admin/go.sum b/admin/go.sum new file mode 100644 index 0000000..3ab7223 --- /dev/null +++ b/admin/go.sum @@ -0,0 +1,817 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= +filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 h1:E4MgwLBGeVB5f2MdcIVD3ELVAWpr+WD6MUe1i+tM/PA= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0/go.mod h1:Y2b/1clN4zsAoUd/pgNAQHjLDnTis/6ROkUfyob6psM= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 h1:nCYfgcSyHZXJI8J0IWE5MsCGlb2xp9fJiXyxWgmOFg4= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0/go.mod h1:ucUjca2JtSZboY8IoUqyQyuuXvwbMBVwFOm0vdQPNhA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= +github.com/alibaba/sentinel-golang v1.0.2/go.mod h1:QsB99f/z35D2AiMrAWwgWE85kDTkBUIkcmPrRt+61NI= +github.com/alibaba/sentinel-golang v1.0.4 h1:i0wtMvNVdy7vM4DdzYrlC4r/Mpk1OKUUBurKKkWhEo8= +github.com/alibaba/sentinel-golang v1.0.4/go.mod h1:Lag5rIYyJiPOylK8Kku2P+a23gdKMMqzQS7wTnjWEpk= +github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666 h1:nLA94XbUtqArHLPtBZuPuIlOOS1rWnWs8ANnMj+TW4Y= +github.com/alibaba/sentinel-golang/pkg/adapters/gin v0.0.0-20241224061304-f4c2c5964666/go.mod h1:RWwrQy9bLKVZyr47l34uImkq7LQEc2ngkKn2O2LV1gE= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/andeya/ameda v1.5.3 h1:SvqnhQPZwwabS8HQTRGfJwWPl2w9ZIPInHAw9aE1Wlk= +github.com/andeya/ameda v1.5.3/go.mod h1:FQDHRe1I995v6GG+8aJ7UIUToEmbdTJn/U26NCPIgXQ= +github.com/andeya/goutil v1.0.1/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= +github.com/andeya/goutil v1.1.2 h1:RiFWFkL/9yXh2SjQkNWOHqErU1x+RauHmeR23eNUzSg= +github.com/andeya/goutil v1.1.2/go.mod h1:jEG5/QnnhG7yGxwFUX6Q+JGMif7sjdHmmNVjn7nhJDo= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow= +github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bytedance/go-tagexpr/v2 v2.9.11 h1:jJgmoDKPKacGl0llPYbYL/+/2N+Ng0vV0ipbnVssXHY= +github.com/bytedance/go-tagexpr/v2 v2.9.11/go.mod h1:UAyKh4ZRLBPGsyTRFZoPqTni1TlojMdOJXQnEIPCX84= +github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM= +github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4= +github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= +github.com/bytedance/sonic/loader v0.5.2 h1:0QtP1gevc1OZ6/H8Lb9BRZiCXd1Ftjd3OKuj1T1lBIo= +github.com/bytedance/sonic/loader v0.5.2/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/casbin/casbin/v2 v2.135.0 h1:6BLkMQiGotYyS5yYeWgW19vxqugUlvHFkFiLnLR/bxk= +github.com/casbin/casbin/v2 v2.135.0/go.mod h1:FmcfntdXLTcYXv/hxgNntcRPqAbwOG9xsism0yXT+18= +github.com/casbin/gorm-adapter/v3 v3.32.0 h1:Au+IOILBIE9clox5BJhI2nA3p9t7Ep1ePlupdGbGfus= +github.com/casbin/gorm-adapter/v3 v3.32.0/go.mod h1:Zre/H8p17mpv5U3EaWgPoxLILLdXO3gHW5aoQQpUDZI= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/casbin/govaluate v1.10.0 h1:ffGw51/hYH3w3rZcxO/KcaUIDOLP84w7nsidMVgaDG0= +github.com/casbin/govaluate v1.10.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99 h1:K62Lb6bsgLOB++z/VAvRvtiEBdNCuMfmQGTGGWMdPpM= +github.com/chanxuehong/rand v0.0.0-20211009035549-2f07823e8e99/go.mod h1:9+sJ9zvvkXC5sPjPEZM3Jpb9n2Q2VtcrGZly0UHYF5I= +github.com/chanxuehong/util v0.0.0-20200304121633-ca8141845b13/go.mod h1:XEYt99iTxMqkv+gW85JX/DdUINHUe43Sbe5AtqSaDAQ= +github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd h1:v3JNsFZmplLO/Cmiyr/rGvR7lW1ld9lB+d5h4yR0MTI= +github.com/chanxuehong/wechat v0.0.0-20230222024006-36f0325263cd/go.mod h1:mysjrtCs9MmN8hqDf4/mc4eQ26Rt9s1p5oO+fhJlLB4= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI= +github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= +github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko= +github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= +github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 h1:UI9ppj+iWl/0Y6kp9w06dwGUdLsdxWRC3p0J0gLHApI= +github.com/go-admin-team/go-admin-core v1.5.3-rc.3.0.20250408121721-2763de5dcdf4/go.mod h1:uWX7fPisJ6DluUP9vR3m3818RkDpZb/4dnwbZdmZN6Q= +github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2 h1:cPTLzpvvyh8kyB24jblB+2W0QZBugP+8VYN3R34Pb4s= +github.com/go-admin-team/go-admin-core/plugins/logger/zap v1.5.2/go.mod h1:ejtJ3aohd6EznZ9Q+KZVA3NwPU/2qIm0gayIGM3tIXw= +github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4 h1:gU2OBSCsfSrqWId2monoEQhPKXZqhZ8W9ol9f7A4DAE= +github.com/go-admin-team/go-admin-core/sdk v1.5.3-rc.3.0.20250408121721-2763de5dcdf4/go.mod h1:va1lNEXHGnV161Avr0lzi5gnT8OazJ/wmN9xnsY9N/s= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +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= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.10.0 h1:Q+1LV8DkHJvSYAdR83XzuhDaTykuDx0l6fkXxoWCWfw= +github.com/go-sql-driver/mysql v1.10.0/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/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.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible h1:lX3m9hvP5tSnJ8bFg/TdT2BYHj1nSBulealy5VN9mPU= +github.com/huaweicloud/huaweicloud-sdk-go-obs v3.26.6+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.5.0 h1:pLqT2kq1zpHW/1D18QMjMpdtX7cekxqtJJjg5ANyWw0= +github.com/leodido/go-urn v1.5.0/go.mod h1:9BORnCDhdPBJNDEX+w1bJisa8yOKYi116VeO96s4ifE= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.49 h1:B8jBHC3xhxZgxztrgruTuLucebnULQnx4W7cF7SAE9w= +github.com/mattn/go-sqlite3 v1.14.49/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY= +github.com/microsoft/go-mssqldb v1.10.0/go.mod h1:mnG7lGa9iYJbzJqGCXyuQCegStKMr3kogDLD6+bmggg= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mojocn/base64Captcha v1.3.8 h1:rrN9BhCwXKS8ht1e21kvR3iTaMgf4qPC9sRoV52bqEg= +github.com/mojocn/base64Captcha v1.3.8/go.mod h1:QFZy927L8HVP3+VV5z2b1EAEiv1KxVJKZbAucVgLUy4= +github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4= +github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nyaruka/phonenumbers v1.0.55/go.mod h1:sDaTZ/KPX5f8qyV9qN+hIm+4ZBARJrupC6LuhshJq1U= +github.com/nyaruka/phonenumbers v1.2.2 h1:OwVjf7Y4uHoK9VJUrA8ebR0ha2yc6sEYbfrwkq0asCY= +github.com/nyaruka/phonenumbers v1.2.2/go.mod h1:wzk2qq7qwsaBKrfbkWKdgHYOOH+QFTesSpIq53ELw8M= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.9.0/go.mod h1:FqZLKOZnGdFAhOK4nqGHa7D66IdsO+O441Eve7ptJDU= +github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= +github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s= +github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY= +github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= +github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/qiniu/go-sdk/v7 v7.27.0 h1:n+2U0S5fhbmG/lN/agO8KcYJaQDqROUVShtnp56Mkw8= +github.com/qiniu/go-sdk/v7 v7.27.0/go.mod h1:pTwVR1B+8SXcPLhDzBUasiKFTD9F7jRglRDR553BW3k= +github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4TzM7MFjy0= +github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.61.0 h1:ui88A53s8MSVYLC56en0KQ17HARk+9986Dn0SBfKNvA= +github.com/quic-go/quic-go v0.61.0/go.mod h1:9So2anK4Tp22URSQq00k+Vo2PNkle96ycDPDHL4s9vs= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shirou/gopsutil v3.20.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil/v3 v3.21.6/go.mod h1:JfVbDpIBLVzT8oKbvMg9P3wEIMDDpVn+LwHTKj0ST88= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY= +github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY= +github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +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 v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.3.2 h1:zkEASHHyEClGeURfgNT9PJZVfAbs9oEX9QXggwWNJbc= +github.com/ugorji/go/codec v1.3.2/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +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/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8= +go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= +golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= +golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo= +golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +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.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +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/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +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.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +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= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= +gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= +gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= +gorm.io/driver/sqlserver v1.6.4 h1:kGA9Z0D7dnIz7yVvWp18qLBSPFpUQWGqMA4rnxkScdQ= +gorm.io/driver/sqlserver v1.6.4/go.mod h1:oRtXDKFRYj8MqyMq+JFEdaA+StSQKC4zupU6blIdB0s= +gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= +gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +gorm.io/plugin/dbresolver v1.6.2 h1:F4b85TenghUeITqe3+epPSUtHH7RIk3fXr5l83DF8Pc= +gorm.io/plugin/dbresolver v1.6.2/go.mod h1:tctw63jdrOezFR9HmrKnPkmig3m5Edem9fdxk9bQSzM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/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= +modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic= +modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU= +modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s= +modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g= +modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.37.0 h1:s1TMe7T3Q3ovQiK2Ouz4Jwh7dw4ZDqbebSDTlSJdfjI= +modernc.org/sqlite v1.37.0/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/admin/main.go b/admin/main.go new file mode 100644 index 0000000..2ef199d --- /dev/null +++ b/admin/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + + "git.ilapage.cn/OPC/chorus/admin/cmd" +) + +//go:generate swag init --parseDependency --parseDepth=6 --instanceName admin -o ./docs/admin + +// @title go-admin API +// @version 2.0.0 +// @description 基于Gin + Vue + Element UI的前后端分离权限管理系统的接口文档 +// @description 添加qq群: 521386980 进入技术交流群 请先star,谢谢! +// @license.name MIT +// @license.url https://github.com/go-admin-team/go-admin/blob/master/LICENSE.md + +// @securityDefinitions.apikey Bearer +// @in header +// @name Authorization +func main() { + if err := cmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +}