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)
+ }
+}