chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type agentBase struct{}
|
||||
|
||||
func newAgentBase() *agentBase {
|
||||
return &agentBase{}
|
||||
}
|
||||
|
||||
func (s *agentBase) deptModel(ctx context.Context) *gdb.Model {
|
||||
return service.SysDept().Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false})
|
||||
}
|
||||
|
||||
func (s *agentBase) roleModel(ctx context.Context) *gdb.Model {
|
||||
return service.SysRole().ModelQuery(ctx, &handler.Option{FilterTenant: true, FilterAuth: false})
|
||||
}
|
||||
|
||||
func (s *agentBase) userModel(ctx context.Context) *gdb.Model {
|
||||
return service.SysUser().Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false})
|
||||
}
|
||||
|
||||
// scopeDeptIds 获取当前登录组织本级及下一级组织的部门ID列表
|
||||
func (s *agentBase) scopeDeptIds(ctx context.Context) (deptIds []int64, err error) {
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return nil, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
deptIds = []int64{currentDeptId}
|
||||
var children []*entity.SysDept
|
||||
err = service.SysDept().Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
Where(dao.SysDept.Columns().ParentId, currentDeptId).
|
||||
Order(dao.SysDept.Columns().OrderNum, "ASC").
|
||||
Scan(&children)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, dept := range children {
|
||||
deptIds = append(deptIds, dept.DeptId)
|
||||
}
|
||||
slices.Sort(deptIds)
|
||||
return slices.Compact(deptIds), nil
|
||||
}
|
||||
|
||||
// ensureDeptInScope 确保部门在当前机构的权限范围内
|
||||
func (s *agentBase) ensureDeptInScope(ctx context.Context, deptId int64) error {
|
||||
deptIds, err := s.scopeDeptIds(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !slices.Contains(deptIds, deptId) {
|
||||
return gerror.New("只能操作本级及下一级组织")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureRoleInScope 确保角色在当前机构的权限范围内
|
||||
func (s *agentBase) ensureRoleInScope(ctx context.Context, roleId int64) error {
|
||||
if roleId == 0 {
|
||||
return nil
|
||||
}
|
||||
deptIds, err := s.scopeDeptIds(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
count, err := s.roleModel(ctx).
|
||||
Where(dao.SysRole.Columns().RoleId, roleId).
|
||||
WhereIn(dao.SysRole.Columns().DeptId, deptIds).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return gerror.New("只能绑定本级及下一级组织的角色")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureDeptRoleUnchangedForSelf 确保当前机构的权限未被修改
|
||||
func (s *agentBase) ensureDeptRoleUnchangedForSelf(ctx context.Context, deptId, deptRole int64) error {
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if deptId == 0 || deptId != currentDeptId {
|
||||
return nil
|
||||
}
|
||||
var dept *entity.SysDept
|
||||
err := s.deptModel(ctx).
|
||||
Where(dao.SysDept.Columns().DeptId, currentDeptId).
|
||||
Scan(&dept)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dept == nil || dept.DeptId == 0 {
|
||||
return gerror.New("组织不存在")
|
||||
}
|
||||
if dept.DeptRole != deptRole {
|
||||
return gerror.New("不能修改本级组织的权限")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// currentDeptDefaultRoleId 获取当前机构的默认角色ID
|
||||
func (s *agentBase) currentDeptDefaultRoleId(ctx context.Context) (int64, error) {
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return 0, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
var dept *entity.SysDept
|
||||
err := s.deptModel(ctx).
|
||||
Where(dao.SysDept.Columns().DeptId, currentDeptId).
|
||||
Scan(&dept)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dept == nil || dept.DeptId == 0 {
|
||||
return 0, gerror.New("组织不存在")
|
||||
}
|
||||
return dept.DeptRole, nil
|
||||
}
|
||||
|
||||
// ensureRoleNotCurrentDeptDefault 确保角色不是当前机构的默认角色
|
||||
func (s *agentBase) ensureRoleNotCurrentDeptDefault(ctx context.Context, roleId int64) error {
|
||||
if roleId == 0 {
|
||||
return nil
|
||||
}
|
||||
defaultRoleId, err := s.currentDeptDefaultRoleId(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if defaultRoleId != 0 && roleId == defaultRoleId {
|
||||
return gerror.New("不能修改本级机构绑定的默认角色权限")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,185 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/memorydb"
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/system"
|
||||
)
|
||||
|
||||
// 复现生产形态:模板角色已配置(role 18 @ dept 100,is_pass=1),
|
||||
// 一级代理的默认角色是创建代理时复制下来的(is_pass=0)。
|
||||
// 二级代理(dept 1862, parent 128)要再创建下级时,必须能从本机构默认角色拿到权限。
|
||||
func TestPassRoleByDeptWorksForChildAgentWithoutTemplateFlag(t *testing.T) {
|
||||
setupPassRoleTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1,
|
||||
TenantId: "000000",
|
||||
DeptId: 128, // 一级代理,要继续创建二级代理
|
||||
}}})
|
||||
|
||||
// 三级结构:平台 100(模板 18,is_pass=1)→ 一级代理 128(默认角色 2321,is_pass=0)→ 二级代理 1862(默认角色 1779,is_pass=0)
|
||||
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES
|
||||
(100,'000000',0,1,'0',1),
|
||||
(128,'000000',100,1,'0',2321),
|
||||
(1862,'000000',128,1,'0',1779)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
|
||||
(18,'000000',100,'代理商','dl','0','4',1,100),
|
||||
(2321,'000000',128,'代理商','dl','0','4',0,128),
|
||||
(1779,'000000',1862,'代理商','dl','0','4',0,100)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES
|
||||
('000000',18,100,'0'),('000000',2321,100,'0'),('000000',1779,100,'0')`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
|
||||
|
||||
service := NewAgentDept()
|
||||
role, menus, err := service.currentDeptPassRole(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("一级代理创建下级时应能取到本机构默认角色权限,实际报错: %v", err)
|
||||
}
|
||||
if role == nil || role.RoleId != 2321 {
|
||||
t.Fatalf("应以本机构默认角色 2321 作为下发来源,实际: %+v", role)
|
||||
}
|
||||
if len(menus) != 1 || menus[0].MenuId != 100 {
|
||||
t.Fatalf("应复制本机构默认角色的菜单,实际: %+v", menus)
|
||||
}
|
||||
}
|
||||
|
||||
func setupPassRoleTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
if err := gdb.SetConfig(gdb.Config{
|
||||
"default": gdb.ConfigGroup{
|
||||
{Type: "sqlite", Link: "sqlite::@file(:memory:)", Debug: false},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("configure sqlite failed: %v", err)
|
||||
}
|
||||
// 走真实部署形态:内存库指向主库(config.yaml 里 link 为空时回落主库)
|
||||
if err := g.Cfg().GetAdapter().(*gcfg.AdapterFile).Set("database.memorydb.link", ""); err != nil {
|
||||
t.Fatalf("override memorydb link failed: %v", err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
t.Cleanup(func() { _ = g.DB().Close(ctx) })
|
||||
mustExec(t, ctx, `CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_type INTEGER, status TEXT DEFAULT '0', dept_role INTEGER, deleted_at DATETIME)`)
|
||||
mustExec(t, ctx, `CREATE TABLE sys_config (config_id INTEGER PRIMARY KEY, tenant_id TEXT, config_name TEXT, config_key TEXT, config_value TEXT, config_type TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, remark TEXT)`)
|
||||
mustExec(t, ctx, `CREATE TABLE sys_role (
|
||||
role_id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id TEXT, dept_id INTEGER, role_name TEXT, role_key TEXT, role_sort INTEGER, data_scope TEXT,
|
||||
menu_check_strictly INTEGER, dept_check_strictly INTEGER, status TEXT, is_pass INTEGER, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, deleted_by INTEGER, deleted_at DATETIME, remark TEXT)`)
|
||||
mustExec(t, ctx, `CREATE TABLE sys_role_menu (tenant_id TEXT, role_id INTEGER, menu_id INTEGER, data_scope TEXT)`)
|
||||
mustExec(t, ctx, `CREATE TABLE sys_role_dept (tenant_id TEXT, role_id INTEGER, dept_id INTEGER)`)
|
||||
mustExec(t, ctx, `CREATE TABLE sys_user_role (tenant_id TEXT DEFAULT '000000', user_id INTEGER, role_id INTEGER)`)
|
||||
_ = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
|
||||
func mustExec(t *testing.T, ctx context.Context, sql string) {
|
||||
t.Helper()
|
||||
if _, err := g.DB().Exec(ctx, sql); err != nil {
|
||||
t.Fatalf("execute sql failed: %v\nsql=%s", err, sql)
|
||||
}
|
||||
}
|
||||
|
||||
// 平台自身(dept 100 = 模板所属机构)仍以模板角色为下发来源。
|
||||
func TestPassRoleByDeptTemplateOwnerUsesTemplateRole(t *testing.T) {
|
||||
setupPassRoleTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1, TenantId: "000000", DeptId: 100,
|
||||
}}})
|
||||
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (100,'000000',0,1,'0',1)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
|
||||
(1,'000000',100,'超级管理员','admin','0','1',0,100),
|
||||
(18,'000000',100,'代理商','dl','0','4',1,100)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',18,100,'0')`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
|
||||
|
||||
role, menus, err := NewAgentDept().currentDeptPassRole(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("平台创建一级代理应成功,实际: %v", err)
|
||||
}
|
||||
if role.RoleId != 18 {
|
||||
t.Fatalf("平台下发来源应为模板角色 18,实际: %d", role.RoleId)
|
||||
}
|
||||
if len(menus) != 1 || menus[0].MenuId != 100 {
|
||||
t.Fatalf("应复制模板菜单,实际: %+v", menus)
|
||||
}
|
||||
}
|
||||
|
||||
// 默认角色缺失/停用/归属不符时必须明确报错,不能静默 0 权限。
|
||||
func TestPassRoleByDeptRejectsBrokenDefaultRole(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
sql []string
|
||||
}{
|
||||
{
|
||||
name: "默认角色不存在",
|
||||
sql: []string{
|
||||
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',9999)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "默认角色已停用",
|
||||
sql: []string{
|
||||
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`,
|
||||
`INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',128,'代理商','dl','1','4',0,128)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "默认角色归属其它机构",
|
||||
sql: []string{
|
||||
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`,
|
||||
`INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',129,'代理商','dl','0','4',0,100)`,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "本机构未绑定默认角色",
|
||||
sql: []string{
|
||||
`INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',0)`,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
setupPassRoleTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1, TenantId: "000000", DeptId: 128,
|
||||
}}})
|
||||
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (100,'000000',0,1,'0',1)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (18,'000000',100,'代理商','dl','0','4',1,100)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',18,100,'0')`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
|
||||
for _, sql := range tc.sql {
|
||||
mustExec(t, ctx, sql)
|
||||
}
|
||||
if _, _, err := NewAgentDept().currentDeptPassRole(ctx); err == nil {
|
||||
t.Fatal("默认角色异常时必须报错,不能静默下发空权限")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 未配置平台模板时,保留原 is_pass=1 唯一角色的下发流程。
|
||||
func TestPassRoleByDeptKeepsLegacyFlowWithoutTemplate(t *testing.T) {
|
||||
setupPassRoleTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1, TenantId: "000000", DeptId: 128,
|
||||
}}})
|
||||
mustExec(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (128,'000000',100,1,'0',2321)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES (2321,'000000',128,'代理商','dl','0','4',1,128)`)
|
||||
mustExec(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',2321,100,'0')`)
|
||||
|
||||
role, menus, err := NewAgentDept().currentDeptPassRole(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("未配模板时应走原有 is_pass 流程,实际: %v", err)
|
||||
}
|
||||
if role.RoleId != 2321 || len(menus) != 1 {
|
||||
t.Fatalf("旧流程结果不符: role=%+v menus=%+v", role, menus)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentMenu struct {
|
||||
*agentBase
|
||||
}
|
||||
|
||||
func NewAgentMenu() *sAgentMenu {
|
||||
return &sAgentMenu{
|
||||
agentBase: newAgentBase(),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentMenu(NewAgentMenu())
|
||||
}
|
||||
|
||||
// AllowedMenuIdsByDeptRole 获取当前机构允许的菜单ID
|
||||
func (s *sAgentMenu) AllowedMenuIdsByDeptRole(ctx context.Context, deptId int64) (menuIds []int64, err error) {
|
||||
if err = s.ensureDeptInScope(ctx, deptId); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var dept *entity.SysDept
|
||||
err = service.SysDept().Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
Where(dao.SysDept.Columns().DeptId, deptId).
|
||||
Scan(&dept)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dept == nil || dept.DeptRole == 0 {
|
||||
return []int64{}, nil
|
||||
}
|
||||
if dept.DeptRole == consts.SuperAdminRoleId {
|
||||
return s.allMenuIds(ctx)
|
||||
}
|
||||
roleMenus, err := service.SysRole().GetRoleListMenu(ctx, []int64{dept.DeptRole})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range roleMenus {
|
||||
menuIds = append(menuIds, item.MenuId)
|
||||
}
|
||||
slices.Sort(menuIds)
|
||||
menuIds = slices.Compact(menuIds)
|
||||
return rolepolicy.RestrictMenus(ctx, dept.TenantId, dept.DeptId, menuIds)
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) allMenuIds(ctx context.Context) (menuIds []int64, err error) {
|
||||
var menus []*entity.SysMenu
|
||||
err = service.SysMenu().Model(ctx).
|
||||
Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum, "ASC").
|
||||
Scan(&menus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range menus {
|
||||
menuIds = append(menuIds, item.MenuId)
|
||||
}
|
||||
slices.Sort(menuIds)
|
||||
return slices.Compact(menuIds), nil
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) getRoleById(ctx context.Context, roleId int64) (role *entity.SysRole, err error) {
|
||||
err = service.SysRole().Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
Where(dao.SysRole.Columns().RoleId, roleId).
|
||||
Scan(&role)
|
||||
return role, err
|
||||
}
|
||||
|
||||
// filterMenuIdsByAllowed 筛选出当前机构允许的菜单ID
|
||||
func (s *sAgentMenu) filterMenuIdsByAllowed(ctx context.Context, deptId int64, menuIds []int64) (res []int64, err error) {
|
||||
if deptId == 0 {
|
||||
deptId = contexts.GetDeptId(ctx)
|
||||
}
|
||||
allowed, err := s.AllowedMenuIdsByDeptRole(ctx, deptId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowed, err = s.withMenuAncestors(ctx, allowed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allowedMap := make(map[int64]struct{}, len(allowed))
|
||||
for _, menuId := range allowed {
|
||||
allowedMap[menuId] = struct{}{}
|
||||
}
|
||||
for _, menuId := range menuIds {
|
||||
if _, ok := allowedMap[menuId]; ok {
|
||||
res = append(res, menuId)
|
||||
}
|
||||
}
|
||||
slices.Sort(res)
|
||||
return slices.Compact(res), nil
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) AllowedMenuTree(ctx context.Context, deptId int64) (tree []*model.SysMenuTreeModel, menuIds []int64, err error) {
|
||||
deptId = contexts.GetDeptId(ctx)
|
||||
menuIds, err = s.AllowedMenuIdsByDeptRole(ctx, deptId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(menuIds) == 0 && !contexts.IsSuperAdmin(ctx) {
|
||||
return []*model.SysMenuTreeModel{}, []int64{}, nil
|
||||
}
|
||||
menuIds, err = s.withMenuAncestors(ctx, menuIds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
mod := service.SysMenu().Model(ctx)
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
mod = mod.WhereIn(dao.SysMenu.Columns().MenuId, menuIds)
|
||||
}
|
||||
var menus []*entity.SysMenu
|
||||
err = mod.Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum, "ASC").
|
||||
Scan(&menus)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tree = s.buildMenuList(menus)
|
||||
return tree, menuIds, nil
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) withMenuAncestors(ctx context.Context, menuIds []int64) ([]int64, error) {
|
||||
seen := make(map[int64]struct{}, len(menuIds))
|
||||
queue := append([]int64{}, menuIds...)
|
||||
for _, menuId := range menuIds {
|
||||
seen[menuId] = struct{}{}
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
var menus []*entity.SysMenu
|
||||
err := service.SysMenu().Model(ctx).
|
||||
WhereIn(dao.SysMenu.Columns().MenuId, queue).
|
||||
Scan(&menus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queue = queue[:0]
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[menu.ParentId]; ok {
|
||||
continue
|
||||
}
|
||||
seen[menu.ParentId] = struct{}{}
|
||||
queue = append(queue, menu.ParentId)
|
||||
}
|
||||
}
|
||||
res := make([]int64, 0, len(seen))
|
||||
for menuId := range seen {
|
||||
res = append(res, menuId)
|
||||
}
|
||||
slices.Sort(res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) buildMenuList(menus []*entity.SysMenu) (list []*model.SysMenuTreeModel) {
|
||||
for _, menu := range menus {
|
||||
list = append(list, &model.SysMenuTreeModel{
|
||||
Title: menu.MenuName,
|
||||
Key: gconv.String(menu.MenuId),
|
||||
ParentId: menu.ParentId,
|
||||
MenuId: menu.MenuId,
|
||||
MenuName: menu.MenuName,
|
||||
MenuType: menu.MenuType,
|
||||
Perms: menu.Perms,
|
||||
})
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (s *sAgentMenu) buildMenuTree(parentId int64, menus []*entity.SysMenu) (tree []*model.SysMenuTreeModel) {
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId != parentId {
|
||||
continue
|
||||
}
|
||||
item := &model.SysMenuTreeModel{
|
||||
Title: menu.MenuName,
|
||||
Key: gconv.String(menu.MenuId),
|
||||
ParentId: menu.ParentId,
|
||||
MenuId: menu.MenuId,
|
||||
MenuName: menu.MenuName,
|
||||
MenuType: menu.MenuType,
|
||||
Perms: menu.Perms,
|
||||
}
|
||||
item.Children = s.buildMenuTree(menu.MenuId, menus)
|
||||
tree = append(tree, item)
|
||||
}
|
||||
if parentId == 0 && len(tree) == 0 {
|
||||
for _, menu := range menus {
|
||||
item := &model.SysMenuTreeModel{
|
||||
Title: menu.MenuName,
|
||||
Key: gconv.String(menu.MenuId),
|
||||
ParentId: menu.ParentId,
|
||||
MenuId: menu.MenuId,
|
||||
MenuName: menu.MenuName,
|
||||
MenuType: menu.MenuType,
|
||||
Perms: menu.Perms,
|
||||
}
|
||||
item.Children = s.buildMenuTree(menu.MenuId, menus)
|
||||
tree = append(tree, item)
|
||||
}
|
||||
}
|
||||
return tree
|
||||
}
|
||||
@@ -0,0 +1,586 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolesync"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sAgentRole struct {
|
||||
*sAgentMenu
|
||||
}
|
||||
|
||||
// NewAgentRole 创建代理商角色管理服务实例。
|
||||
func NewAgentRole() *sAgentRole {
|
||||
return &sAgentRole{
|
||||
sAgentMenu: &sAgentMenu{
|
||||
agentBase: newAgentBase(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentRole(NewAgentRole())
|
||||
}
|
||||
|
||||
// RoleList 查询当前登录组织本级角色列表。
|
||||
func (s *sAgentRole) RoleList(ctx context.Context, in *model.AgentRoleListParam) (list []*model.AgentRoleListModel, total int, err error) {
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return nil, 0, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
if err = service.AgentDept().EnsureCurrentDeptDefaultRole(ctx); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
m := s.roleModel(ctx).Where(dao.SysRole.Columns().DeptId, currentDeptId)
|
||||
if in.RoleName != "" {
|
||||
m = m.WhereLike(dao.SysRole.Columns().RoleName, "%"+in.RoleName+"%")
|
||||
}
|
||||
if in.RoleKey != "" {
|
||||
m = m.WhereLike(dao.SysRole.Columns().RoleKey, "%"+in.RoleKey+"%")
|
||||
}
|
||||
if in.Status != "" {
|
||||
m = m.Where(dao.SysRole.Columns().Status, in.Status)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
begin, end := gtime.NewFromStr(in.CreatedAt[0]), gtime.NewFromStr(in.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysRole.Columns().CreatedAt, begin, end.EndOfDay())
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Order(dao.SysRole.Columns().RoleId, "ASC").
|
||||
Scan(&list)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
defaultRoleId, err := s.currentDeptDefaultRoleId(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, role := range list {
|
||||
role.IsDefaultRole = defaultRoleId != 0 && role.RoleId == defaultRoleId
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// RoleView 查询角色详情,并返回当前登录组织可分配的菜单树。
|
||||
func (s *sAgentRole) RoleView(ctx context.Context, roleId int64) (res *model.AgentRoleViewModel, err error) {
|
||||
role, err := service.SysRole().View(ctx, &model.SysRoleViewParam{RoleId: roleId})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if role == nil || role.RoleId == 0 {
|
||||
return nil, gerror.New("角色不存在")
|
||||
}
|
||||
if role.DeptId != contexts.GetDeptId(ctx) {
|
||||
return nil, gerror.New("只能操作本级角色")
|
||||
}
|
||||
tree, allowed, err := s.AllowedMenuTree(ctx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isDefaultRole, err := s.isCurrentDeptDefaultRole(ctx, role.RoleId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.AgentRoleViewModel{
|
||||
SysRoleViewModel: *role,
|
||||
AllowedMenuIds: allowed,
|
||||
MenuTree: tree,
|
||||
IsDefaultRole: isDefaultRole,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RoleSave 保存代理商角色,按角色ID区分新增和修改流程。
|
||||
func (s *sAgentRole) RoleSave(ctx context.Context, in *model.AgentRoleSaveParam) (roleId int64, err error) {
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
var saveErr error
|
||||
roleId, saveErr = s.saveTemplateRole(ctx, in)
|
||||
if saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := rolesync.EnqueueRole(ctx, roleId, contexts.GetUserId(ctx)); err != nil {
|
||||
return roleId, err
|
||||
}
|
||||
return roleId, nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) saveTemplateRole(ctx context.Context, in *model.AgentRoleSaveParam) (roleId int64, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return 0, gerror.New("登录已过期")
|
||||
}
|
||||
if in.DeptId == 0 {
|
||||
in.DeptId = user.DeptId
|
||||
}
|
||||
if in.DeptId != user.DeptId {
|
||||
return 0, gerror.New("只能维护本级角色")
|
||||
}
|
||||
if err = s.ensureRoleNotCurrentDeptDefault(ctx, in.RoleId); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if in.RoleId > 0 {
|
||||
if err = s.ensureRoleNotInherited(ctx, in.RoleId); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if in.Status == "" {
|
||||
in.Status = "0"
|
||||
}
|
||||
if in.DataScope == "" {
|
||||
in.DataScope = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
allowed, err := s.filterMenuIdsByAllowed(ctx, in.DeptId, in.MenuIds)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, id := range in.MenuIds {
|
||||
if !slices.Contains(allowed, id) {
|
||||
return 0, gerror.New("角色包含超出本机构权限范围的菜单或按钮")
|
||||
}
|
||||
}
|
||||
switch in.DataScope {
|
||||
case consts.SysRoleDataScopeDept, consts.SysRoleDataScopeDeptAndBelow, consts.SysRoleDataScopePersonal, consts.SysRoleDataScopeDeptAndBelowOrPersonal, consts.SysRoleDataScopeOrgAndNextLevel, consts.SysRoleDataScopeOrgNextLevel:
|
||||
default:
|
||||
return 0, gerror.New("代理角色数据权限不能超出本机构及下级范围")
|
||||
}
|
||||
}
|
||||
// 只有本次保存的目标角色是平台代理模板时才走模板语义;
|
||||
// 普通角色(如「运营人员」)不能被强制改写成 is_pass=1,
|
||||
// 否则任何已配置模板的机构都无法再保存第二个角色。
|
||||
template, templateErr := rolepolicy.TemplateForRoles(ctx, in.RoleId)
|
||||
if templateErr != nil {
|
||||
return 0, templateErr
|
||||
}
|
||||
if in.IsPass != 0 && in.IsPass != 1 {
|
||||
return 0, gerror.New("传递标记无效")
|
||||
}
|
||||
if template != nil {
|
||||
in.DataScope = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
// 未配置平台模板时,保留原有「本级唯一 is_pass=1 角色」下发流程。
|
||||
legacyPass := in.IsPass == 1
|
||||
if legacyPass {
|
||||
configured, err := dao.SysConfig.Ctx(ctx).Master().Where("tenant_id", consts.DefaultSystemTenantCode).Where("config_key", rolepolicy.TemplateConfigKey).Count()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
legacyPass = configured == 0
|
||||
}
|
||||
if legacyPass {
|
||||
in.DataScope = consts.SysRoleDataScopeDeptAndBelow
|
||||
var dept *entity.SysDept
|
||||
deptModel := s.deptModel(ctx).Where(dao.SysDept.Columns().DeptId, in.DeptId)
|
||||
if dao.SysDept.DB().GetConfig().Type != "sqlite" {
|
||||
deptModel = deptModel.LockUpdate()
|
||||
}
|
||||
if err := deptModel.Scan(&dept); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if dept == nil {
|
||||
return 0, gerror.New("当前机构不存在")
|
||||
}
|
||||
count, err := s.roleModel(ctx).
|
||||
Where(dao.SysRole.Columns().DeptId, in.DeptId).
|
||||
Where("is_pass", 1).
|
||||
WhereNot(dao.SysRole.Columns().RoleId, in.RoleId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if count > 0 {
|
||||
return 0, gerror.New("每个机构只能配置一个下级管理员角色")
|
||||
}
|
||||
}
|
||||
|
||||
if in.RoleId == 0 {
|
||||
return s.addRole(ctx, in)
|
||||
}
|
||||
return s.editRole(ctx, in)
|
||||
}
|
||||
|
||||
// addRole 新增代理商角色,并同步角色的数据权限配置。
|
||||
func (s *sAgentRole) addRole(ctx context.Context, in *model.AgentRoleSaveParam) (int64, error) {
|
||||
deptId := in.DeptId
|
||||
role, err := service.SysRole().Add(ctx, &model.SysRoleAddParam{
|
||||
RoleName: in.RoleName,
|
||||
RoleKey: in.RoleKey,
|
||||
RoleSort: in.RoleSort,
|
||||
DataScope: in.DataScope,
|
||||
MenuCheckStrictly: in.MenuCheckStrictly,
|
||||
Status: in.Status,
|
||||
Remark: in.Remark,
|
||||
MenuIds: in.MenuIds,
|
||||
DeptId: &deptId,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if role == nil {
|
||||
return 0, nil
|
||||
}
|
||||
if _, err = dao.SysRole.Ctx(ctx).
|
||||
Where(dao.SysRole.Columns().RoleId, role.RoleId).
|
||||
Data(do.SysRole{IsPass: in.IsPass}).Update(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = s.updateRoleDataScope(ctx, role.RoleId, in); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return role.RoleId, nil
|
||||
}
|
||||
|
||||
// editRole 修改代理商角色,并禁止在代理商页面移动角色所属组织。
|
||||
func (s *sAgentRole) editRole(ctx context.Context, in *model.AgentRoleSaveParam) (int64, error) {
|
||||
current, err := s.RoleView(ctx, in.RoleId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if current.DeptId != in.DeptId {
|
||||
return 0, gerror.New("不能在代理商页面移动角色所属组织")
|
||||
}
|
||||
deptId := in.DeptId
|
||||
roleName := in.RoleName
|
||||
roleKey := in.RoleKey
|
||||
roleSort := in.RoleSort
|
||||
menuCheckStrictly := in.MenuCheckStrictly
|
||||
status := in.Status
|
||||
remark := in.Remark
|
||||
role, err := service.SysRole().Edit(ctx, &model.SysRoleEditParam{
|
||||
RoleId: in.RoleId,
|
||||
RoleName: &roleName,
|
||||
RoleKey: &roleKey,
|
||||
RoleSort: &roleSort,
|
||||
MenuCheckStrictly: &menuCheckStrictly,
|
||||
Status: &status,
|
||||
Remark: &remark,
|
||||
MenuIds: in.MenuIds,
|
||||
DeptId: &deptId,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err = s.updateRoleDataScope(ctx, in.RoleId, in); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err = dao.SysRole.Ctx(ctx).
|
||||
Where(dao.SysRole.Columns().RoleId, in.RoleId).
|
||||
Data(do.SysRole{IsPass: in.IsPass}).Update(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if role == nil {
|
||||
return in.RoleId, nil
|
||||
}
|
||||
return role.RoleId, nil
|
||||
}
|
||||
|
||||
// updateRoleDataScope 更新角色数据权限和组织树勾选配置。
|
||||
func (s *sAgentRole) updateRoleDataScope(ctx context.Context, roleId int64, in *model.AgentRoleSaveParam) error {
|
||||
deptCheckStrictly := in.DeptCheckStrictly
|
||||
return service.SysRole().EditRoleDataScope(ctx, &model.SysRoleDataScopeEditParam{
|
||||
RoleId: roleId,
|
||||
DataScope: in.DataScope,
|
||||
DeptCheckStrictly: &deptCheckStrictly,
|
||||
DeptIds: []int64{in.DeptId},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *sAgentRole) isCurrentDeptDefaultRole(ctx context.Context, roleId int64) (bool, error) {
|
||||
defaultRoleId, err := s.currentDeptDefaultRoleId(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return defaultRoleId != 0 && roleId == defaultRoleId, nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) ensureRoleInCurrentDept(ctx context.Context, roleId int64) error {
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
count, err := s.roleModel(ctx).
|
||||
Where(dao.SysRole.Columns().RoleId, roleId).
|
||||
Where(dao.SysRole.Columns().DeptId, currentDeptId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count == 0 {
|
||||
return gerror.New("只能操作本级角色")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) ensureRoleNotInherited(ctx context.Context, roleId int64) error {
|
||||
if roleId <= 0 {
|
||||
return nil
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
return nil
|
||||
}
|
||||
count, err := s.inheritedRoleModel(ctx).
|
||||
Where("r.role_id", roleId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("上级下发角色由上级角色统一维护,当前组织只能查看")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) ensureRoleNoUsers(ctx context.Context, roleId int64) error {
|
||||
count, err := dao.SysUserRole.Ctx(ctx).
|
||||
Where(dao.SysUserRole.Columns().RoleId, roleId).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("该角色已绑定用户,不能删除")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RoleDelete 删除代理商角色,删除前校验本级机构默认角色不可被删除。
|
||||
func (s *sAgentRole) RoleDelete(ctx context.Context, roleIds []int64) (err error) {
|
||||
for _, roleId := range roleIds {
|
||||
if err = s.ensureRoleInCurrentDept(ctx, roleId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.ensureRoleNotCurrentDeptDefault(ctx, roleId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.ensureRoleNotInherited(ctx, roleId); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = s.ensureRoleNoUsers(ctx, roleId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err = service.SysRole().Delete(ctx, &model.SysRoleDeleteParam{RoleIds: roleIds})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sAgentRole) inheritedRoleModel(ctx context.Context) *gdb.Model {
|
||||
m := g.Model(dao.SysRole.Table()+" r").
|
||||
LeftJoin(dao.SysDept.Table()+" d", "d.dept_id = r.dept_id AND d.tenant_id = r.tenant_id").
|
||||
Where("r.deleted_at IS NULL").
|
||||
Where("d.deleted_at IS NULL").
|
||||
Where("r.created_dept > 0").
|
||||
Where("r.dept_id <> r.created_dept").
|
||||
Where("d.parent_id = r.created_dept")
|
||||
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
|
||||
m = m.Where("r.tenant_id", tenantId)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (s *sAgentRole) SyncInheritedRole(ctx context.Context, message *model.AgentRoleSyncMessage) error {
|
||||
if message == nil || message.SourceRoleId <= 0 {
|
||||
return gerror.New("invalid agent role sync message")
|
||||
}
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return templateErr
|
||||
}
|
||||
if template != nil && message.SourceDeptId == template.DeptId && (message.TenantId == template.TenantId || message.TenantId == "") {
|
||||
return syncTemplateRoles(ctx, template, message.ActorId)
|
||||
}
|
||||
var source *entity.SysRole
|
||||
sourceModel := dao.SysRole.Ctx(ctx).Where(dao.SysRole.Columns().RoleId, message.SourceRoleId)
|
||||
if message.TenantId != "" {
|
||||
sourceModel = sourceModel.Where(dao.SysRole.Columns().TenantId, message.TenantId)
|
||||
}
|
||||
if err := sourceModel.Scan(&source); err != nil {
|
||||
return err
|
||||
}
|
||||
if source == nil || source.IsPass != 1 || source.DeletedAt != nil {
|
||||
return nil
|
||||
}
|
||||
if message.SourceDeptId > 0 && message.SourceDeptId != source.DeptId {
|
||||
return gerror.New("role sync source department changed")
|
||||
}
|
||||
var children []*entity.SysDept
|
||||
if err := dao.SysDept.Ctx(ctx).
|
||||
Where(dao.SysDept.Columns().TenantId, source.TenantId).
|
||||
Where(dao.SysDept.Columns().ParentId, source.DeptId).
|
||||
Where(dao.SysDept.Columns().DeptType, 1).
|
||||
Scan(&children); err != nil {
|
||||
return err
|
||||
}
|
||||
failed := 0
|
||||
var firstErr error
|
||||
for _, child := range children {
|
||||
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
return s.syncInheritedRoleChildrenTx(ctx, tx, message.SourceRoleId, source.TenantId, source.DeptId, message.ActorId, child.DeptId)
|
||||
})
|
||||
if err != nil {
|
||||
failed++
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
g.Log().Warningf(ctx, "agent role sync child failed, sourceRoleId:%d, childDeptId:%d, err:%v", source.RoleId, child.DeptId, err)
|
||||
}
|
||||
}
|
||||
if firstErr != nil {
|
||||
return gerror.Wrapf(firstErr, "agent role sync failed for %d of %d children", failed, len(children))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) syncInheritedRoleChildrenTx(ctx context.Context, tx gdb.TX, sourceRoleID int64, tenantId string, sourceDeptId, actorId, childDeptId int64) error {
|
||||
if sourceRoleID == 0 {
|
||||
return nil
|
||||
}
|
||||
var source *entity.SysRole
|
||||
sourceModel := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where(dao.SysRole.Columns().RoleId, sourceRoleID)
|
||||
if tenantId != "" {
|
||||
sourceModel = sourceModel.Where(dao.SysRole.Columns().TenantId, tenantId)
|
||||
}
|
||||
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
|
||||
sourceModel = sourceModel.LockUpdate()
|
||||
}
|
||||
if err := sourceModel.Scan(&source); err != nil {
|
||||
return err
|
||||
}
|
||||
if source == nil || source.RoleId == 0 || source.DeletedAt != nil || source.IsPass != 1 {
|
||||
return nil
|
||||
}
|
||||
if sourceDeptId > 0 && source.DeptId != sourceDeptId {
|
||||
return gerror.New("role sync source department changed")
|
||||
}
|
||||
var childDepts []*entity.SysDept
|
||||
childDeptModel := tx.Ctx(ctx).Model(dao.SysDept.Table()).Where(dao.SysDept.Columns().TenantId, source.TenantId).Where(dao.SysDept.Columns().ParentId, source.DeptId).Where(dao.SysDept.Columns().DeptType, 1).Where(dao.SysDept.Columns().DeptId, childDeptId)
|
||||
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
|
||||
childDeptModel = childDeptModel.LockUpdate()
|
||||
}
|
||||
if err := childDeptModel.Scan(&childDepts); err != nil {
|
||||
return err
|
||||
}
|
||||
var menus []*entity.SysRoleMenu
|
||||
if err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where(dao.SysRoleMenu.Columns().RoleId, sourceRoleID).OrderAsc(dao.SysRoleMenu.Columns().MenuId).Scan(&menus); err != nil {
|
||||
return err
|
||||
}
|
||||
menuIDs := make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
menuIDs = append(menuIDs, menu.MenuId)
|
||||
}
|
||||
allowed, err := rolepolicy.RestrictMenus(ctx, source.TenantId, source.DeptId, menuIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return templateErr
|
||||
}
|
||||
filtered := make([]*entity.SysRoleMenu, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
if slices.Contains(allowed, menu.MenuId) {
|
||||
copy := *menu
|
||||
if template != nil {
|
||||
copy.DataScope = consts.SysRoleMenuDataScopeRole
|
||||
}
|
||||
filtered = append(filtered, ©)
|
||||
}
|
||||
}
|
||||
menus = filtered
|
||||
for _, childDept := range childDepts {
|
||||
var children []*entity.SysRole
|
||||
if err := tx.Ctx(ctx).Model(dao.SysRole.Table()+" r").
|
||||
Fields("r.*").
|
||||
Where("r.tenant_id", source.TenantId).
|
||||
Where("r.dept_id", childDept.DeptId).
|
||||
Where("r.created_dept", source.DeptId).
|
||||
Where("r.role_id", childDept.DeptRole).
|
||||
Where("r.deleted_at IS NULL").
|
||||
Scan(&children); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(children) > 1 {
|
||||
return gerror.Newf("multiple inherited roles in department %d for role %d", childDept.DeptId, source.RoleId)
|
||||
}
|
||||
if len(children) == 0 {
|
||||
var legacy *entity.SysRole
|
||||
if childDept.DeptRole > 0 {
|
||||
if err := tx.Ctx(ctx).Model(dao.SysRole.Table()).
|
||||
Where(dao.SysRole.Columns().RoleId, childDept.DeptRole).
|
||||
Where(dao.SysRole.Columns().DeptId, childDept.DeptId).
|
||||
Where(dao.SysRole.Columns().TenantId, source.TenantId).
|
||||
Scan(&legacy); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if legacy != nil && legacy.RoleId > 0 {
|
||||
if legacy.CreatedDept != source.DeptId || legacy.RoleKey != source.RoleKey {
|
||||
return gerror.Newf("cannot identify inherited role for department %d", childDept.DeptId)
|
||||
}
|
||||
children = []*entity.SysRole{legacy}
|
||||
} else {
|
||||
if childDept.DeptRole > 0 {
|
||||
return gerror.Newf("missing default role for department %d", childDept.DeptId)
|
||||
}
|
||||
roleId, err := s.createInheritedRoleTx(ctx, tx, source, childDept.DeptId, menus, actorId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
children = []*entity.SysRole{{RoleId: roleId}}
|
||||
}
|
||||
}
|
||||
for _, child := range children {
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where(dao.SysRole.Columns().RoleId, child.RoleId).Data(do.SysRole{RoleName: strings.TrimSpace(source.RoleName), RoleKey: source.RoleKey, RoleSort: source.RoleSort, DataScope: source.DataScope, MenuCheckStrictly: source.MenuCheckStrictly, DeptCheckStrictly: source.DeptCheckStrictly, Status: source.Status, Remark: source.Remark, UpdatedBy: actorId, UpdatedAt: gtime.Now()}).OmitNil().Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where(dao.SysRoleMenu.Columns().RoleId, child.RoleId).Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, menu := range menus {
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(do.SysRoleMenu{TenantId: source.TenantId, RoleId: child.RoleId, MenuId: menu.MenuId, DataScope: menu.DataScope}).OmitNil().Insert(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAgentRole) createInheritedRoleTx(ctx context.Context, tx gdb.TX, source *entity.SysRole, deptId int64, menus []*entity.SysRoleMenu, actorId int64) (int64, error) {
|
||||
if actorId == 0 {
|
||||
actorId = source.UpdatedBy
|
||||
}
|
||||
roleId, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Data(do.SysRole{TenantId: source.TenantId, DeptId: deptId, RoleName: strings.TrimSpace(source.RoleName), RoleKey: source.RoleKey, RoleSort: source.RoleSort, DataScope: source.DataScope, MenuCheckStrictly: source.MenuCheckStrictly, DeptCheckStrictly: source.DeptCheckStrictly, IsPass: 0, Status: source.Status, CreatedDept: source.DeptId, CreatedBy: actorId, CreatedAt: gtime.Now(), UpdatedBy: actorId, UpdatedAt: gtime.Now(), Remark: source.Remark}).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, menu := range menus {
|
||||
if _, err = tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(do.SysRoleMenu{TenantId: source.TenantId, RoleId: roleId, MenuId: menu.MenuId, DataScope: menu.DataScope}).OmitNil().Insert(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysRoleDept.Table()).Data(do.SysRoleDept{RoleId: roleId, DeptId: deptId}).OmitNil().Insert()
|
||||
return roleId, err
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/cmd/inithttp"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rabbitmq"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
localqueue "git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/queue"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
type agentRoleSyncQueue struct{}
|
||||
|
||||
func init() {
|
||||
inithttp.RegisterHttpInitFunc("agentRoleSyncQueue", func(ctx context.Context) error {
|
||||
if rabbitmq.Enabled(ctx) {
|
||||
if err := rabbitmq.DeclareQueue(ctx, consts.QueueAgentRoleSyncDeadLetter); err != nil {
|
||||
return err
|
||||
}
|
||||
return rabbitmq.ConsumeJSON[model.AgentRoleSyncMessage](ctx, consts.QueueAgentRoleSync, "iotlcm-agent-role-sync", handleAgentRoleSyncMessage)
|
||||
}
|
||||
if err := localqueue.NewConsumer(gctx.New(), &agentRoleSyncQueue{}, &localqueue.Config{Workers: 2}); err != nil {
|
||||
return err
|
||||
}
|
||||
return localqueue.NewProducer(gctx.New(), localqueue.QueueTypeMemory, consts.QueueAgentRoleSync, &localqueue.Config{})
|
||||
})
|
||||
}
|
||||
|
||||
func publishAgentRoleSync(ctx context.Context, message *model.AgentRoleSyncMessage) error {
|
||||
if rabbitmq.Enabled(ctx) {
|
||||
return rabbitmq.Publish(ctx, consts.QueueAgentRoleSync, message)
|
||||
}
|
||||
return localqueue.Push(ctx, consts.QueueAgentRoleSync, message)
|
||||
}
|
||||
|
||||
func handleAgentRoleSyncMessage(ctx context.Context, message model.AgentRoleSyncMessage) error {
|
||||
if err := service.AgentRole().SyncInheritedRole(ctx, &message); err != nil {
|
||||
g.Log().Errorf(ctx, "agent role sync failed, sourceRoleId:%d, retry:%d, err:%v", message.SourceRoleId, message.RetryCount, err)
|
||||
if message.RetryCount < 3 {
|
||||
message.RetryCount++
|
||||
return rabbitmq.PublishDelayed(ctx, consts.QueueAgentRoleSyncRetry, consts.QueueAgentRoleSync, time.Duration(message.RetryCount)*time.Second, message)
|
||||
}
|
||||
deadLetter := model.AgentRoleSyncDeadLetterMessage{Message: message, Error: err.Error(), FailedAt: time.Now().Format(time.RFC3339)}
|
||||
if publishErr := rabbitmq.Publish(ctx, consts.QueueAgentRoleSyncDeadLetter, deadLetter); publishErr != nil {
|
||||
return publishErr
|
||||
}
|
||||
g.Log().Errorf(ctx, "agent role sync moved to dead-letter queue, sourceRoleId:%d, err:%v", message.SourceRoleId, err)
|
||||
return nil
|
||||
}
|
||||
g.Log().Infof(ctx, "agent role sync completed, sourceRoleId:%d", message.SourceRoleId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*agentRoleSyncQueue) GetTopic() string { return consts.QueueAgentRoleSync }
|
||||
|
||||
func (*agentRoleSyncQueue) GetType() localqueue.QueueType { return localqueue.QueueTypeMemory }
|
||||
|
||||
func (*agentRoleSyncQueue) Handle(ctx context.Context, payload localqueue.Payload) error {
|
||||
var message model.AgentRoleSyncMessage
|
||||
if err := json.Unmarshal(payload.Data, &message); err != nil {
|
||||
return err
|
||||
}
|
||||
var err error
|
||||
for attempt := 0; attempt <= 3; attempt++ {
|
||||
err = service.AgentRole().SyncInheritedRole(ctx, &message)
|
||||
if err == nil {
|
||||
g.Log().Infof(ctx, "agent role sync completed, sourceRoleId:%d", message.SourceRoleId)
|
||||
return nil
|
||||
}
|
||||
if attempt < 3 {
|
||||
time.Sleep(time.Duration(attempt+1) * time.Second)
|
||||
}
|
||||
}
|
||||
g.Log().Errorf(ctx, "agent role sync failed after retries, sourceRoleId:%d, err:%v", message.SourceRoleId, err)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/memorydb"
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/system"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
)
|
||||
|
||||
// 复现生产形态:dept 100 同时存在模板角色 18(is_pass=1)和普通角色 79(运营人员,is_pass=0)。
|
||||
// 给 79 勾权限保存时,旧逻辑强制把 in.IsPass 置 1 再查重,必然报
|
||||
// 「每个机构只能配置一个下级管理员角色」,导致普通角色永远改不了。
|
||||
func TestSaveTemplateRoleAllowsOrdinaryRoleWithTemplateConfigured(t *testing.T) {
|
||||
setupRoleSaveTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1,
|
||||
TenantId: "000000",
|
||||
DeptId: 100,
|
||||
Roles: []model.Role{{RoleId: 1}},
|
||||
}}})
|
||||
// 模板已配置为 18,机构 100 已有 is_pass=1 的 18
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (100,'000000',0,1,'0',1)`)
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
|
||||
(1,'000000',100,'超级管理员','admin','0','1',0,100),
|
||||
(18,'000000',100,'代理商','dl','0','4',1,100),
|
||||
(79,'000000',100,'运营人员','yunying','0','4',0,100)`)
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_role_menu (tenant_id, role_id, menu_id, data_scope) VALUES ('000000',18,100,'0')`)
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_config (config_id, tenant_id, config_key, config_value) VALUES (1,'000000','agent.role.template.role_id','18')`)
|
||||
|
||||
// 编辑普通角色 79:勾一个菜单
|
||||
_, err := NewAgentRole().saveTemplateRole(ctx, &model.AgentRoleSaveParam{
|
||||
RoleId: 79,
|
||||
DeptId: 100,
|
||||
RoleName: "运营人员",
|
||||
RoleKey: "yunying",
|
||||
Status: "0",
|
||||
DataScope: "4",
|
||||
MenuIds: []int64{100},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("已配置模板时普通角色必须能保存,实际报错: %v", err)
|
||||
}
|
||||
|
||||
// 关键断言:79 不能被悄悄改成 is_pass=1
|
||||
var isPass int
|
||||
v, qErr := g.DB().GetValue(ctx, `SELECT is_pass FROM sys_role WHERE role_id = 79`)
|
||||
if qErr != nil {
|
||||
t.Fatalf("query is_pass failed: %v", qErr)
|
||||
}
|
||||
isPass = v.Int()
|
||||
if isPass != 0 {
|
||||
t.Fatalf("普通角色 79 的 is_pass 被改成了 %d,应保持 0", isPass)
|
||||
}
|
||||
// 模板 18 的 is_pass 也不能被动到
|
||||
v18, _ := g.DB().GetValue(ctx, `SELECT is_pass FROM sys_role WHERE role_id = 18`)
|
||||
if v18.Int() != 1 {
|
||||
t.Fatalf("模板角色 18 的 is_pass 变成了 %d,应保持 1", v18.Int())
|
||||
}
|
||||
}
|
||||
|
||||
// 未配置平台模板时,原有「本级唯一 is_pass=1 角色」限制必须保留。
|
||||
func TestSaveTemplateRoleKeepsLegacyUniquenessWithoutTemplate(t *testing.T) {
|
||||
setupRoleSaveTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
ID: 1, TenantId: "000000", DeptId: 200,
|
||||
}}})
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_dept (dept_id, tenant_id, parent_id, dept_type, status, dept_role) VALUES (200,'000000',0,1,'0',1)`)
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_role (role_id, tenant_id, dept_id, role_name, role_key, status, data_scope, is_pass, created_dept) VALUES
|
||||
(1,'000000',200,'超级管理员','admin','0','1',0,200),
|
||||
(90,'000000',200,'已有的下发角色','dl','0','4',1,200)`)
|
||||
// 没有 sys_config 模板参数 → 走 legacy 分支
|
||||
_, err := NewAgentRole().saveTemplateRole(ctx, &model.AgentRoleSaveParam{
|
||||
RoleId: 0,
|
||||
DeptId: 200,
|
||||
RoleName: "第二个下发角色",
|
||||
RoleKey: "dl2",
|
||||
Status: "0",
|
||||
DataScope: "4",
|
||||
MenuIds: []int64{},
|
||||
IsPass: 1,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("未配置模板时,第二个 is_pass=1 角色必须被拒绝")
|
||||
}
|
||||
if err.Error() != "每个机构只能配置一个下级管理员角色" {
|
||||
t.Fatalf("错误信息不符,实际: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupRoleSaveTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
if err := gdb.SetConfig(gdb.Config{
|
||||
"default": gdb.ConfigGroup{
|
||||
{Type: "sqlite", Link: "sqlite::@file(:memory:)", Debug: false},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("configure sqlite failed: %v", err)
|
||||
}
|
||||
_ = g.Cfg().GetAdapter().(*gcfg.AdapterFile).Set("database.memorydb.link", "")
|
||||
ctx := context.Background()
|
||||
t.Cleanup(func() { _ = g.DB().Close(ctx) })
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_type INTEGER, status TEXT DEFAULT '0', dept_role INTEGER, order_num INTEGER DEFAULT 0, ancestors TEXT, dept_name TEXT, deleted_at DATETIME)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_config (config_id INTEGER PRIMARY KEY, tenant_id TEXT, config_name TEXT, config_key TEXT, config_value TEXT, config_type TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, remark TEXT)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_role (
|
||||
role_id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id TEXT, dept_id INTEGER, role_name TEXT, role_key TEXT, role_sort INTEGER, data_scope TEXT,
|
||||
menu_check_strictly INTEGER, dept_check_strictly INTEGER, status TEXT, is_pass INTEGER, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, deleted_by INTEGER, deleted_at DATETIME, remark TEXT)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_role_menu (tenant_id TEXT, role_id INTEGER, menu_id INTEGER, data_scope TEXT)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_role_dept (tenant_id TEXT, role_id INTEGER, dept_id INTEGER)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_user_role (tenant_id TEXT DEFAULT '000000', user_id INTEGER, role_id INTEGER)`)
|
||||
mustExecRole(t, ctx, `CREATE TABLE sys_menu (menu_id INTEGER PRIMARY KEY, menu_name TEXT, parent_id INTEGER, level INTEGER, tree TEXT, order_num INTEGER DEFAULT 0, path TEXT, component TEXT, query_param TEXT, is_frame INTEGER, is_cache INTEGER, menu_type TEXT, visible TEXT, status TEXT DEFAULT '0', perms TEXT, icon TEXT, created_dept INTEGER, created_by INTEGER, created_at DATETIME, updated_by INTEGER, updated_at DATETIME, remark TEXT)`)
|
||||
mustExecRole(t, ctx, `INSERT INTO sys_menu (menu_id, menu_name, parent_id, status) VALUES (100,'卡片信息',0,'0')`)
|
||||
}
|
||||
|
||||
func mustExecRole(t *testing.T, ctx context.Context, sql string) {
|
||||
t.Helper()
|
||||
if _, err := g.DB().Exec(ctx, sql); err != nil {
|
||||
t.Fatalf("execute sql failed: %v\nsql=%s", err, sql)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
)
|
||||
|
||||
type sAgentScope struct {
|
||||
*agentBase
|
||||
}
|
||||
|
||||
func NewAgentScope() *sAgentScope {
|
||||
return &sAgentScope{
|
||||
agentBase: newAgentBase(),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentScope(NewAgentScope())
|
||||
}
|
||||
|
||||
func (s *sAgentScope) ScopeDeptIds(ctx context.Context) (deptIds []int64, err error) {
|
||||
return s.scopeDeptIds(ctx)
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func syncTemplateRoles(ctx context.Context, template *entity.SysRole, actorID int64) error {
|
||||
queue := []int64{template.DeptId}
|
||||
seen := make(map[int64]bool)
|
||||
failed := 0
|
||||
var firstErr error
|
||||
for len(queue) > 0 {
|
||||
parentID := queue[0]
|
||||
queue = queue[1:]
|
||||
if seen[parentID] {
|
||||
return gerror.New("机构上下级存在循环")
|
||||
}
|
||||
seen[parentID] = true
|
||||
var children []*entity.SysDept
|
||||
if err := dao.SysDept.Ctx(ctx).Where("tenant_id", template.TenantId).Where("parent_id", parentID).Where("dept_type", 1).Scan(&children); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, child := range children {
|
||||
if child.DeptRole <= 0 {
|
||||
g.Log().Warningf(ctx, "agent template sync skipped missing default role, deptId:%d", child.DeptId)
|
||||
continue
|
||||
}
|
||||
err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
lock := tx.Ctx(ctx).Model(dao.SysDept.Table()).Where("dept_id", child.DeptId).Where("tenant_id", template.TenantId)
|
||||
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
|
||||
lock = lock.LockUpdate()
|
||||
}
|
||||
var locked *entity.SysDept
|
||||
if err := lock.Scan(&locked); err != nil {
|
||||
return err
|
||||
}
|
||||
if locked == nil || locked.DeptRole != child.DeptRole {
|
||||
return gerror.New("下级机构角色绑定已变化")
|
||||
}
|
||||
var current *entity.SysRole
|
||||
if err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", child.DeptRole).Where("tenant_id", template.TenantId).Where("dept_id", child.DeptId).Scan(¤t); err != nil {
|
||||
return err
|
||||
}
|
||||
if current == nil {
|
||||
g.Log().Warningf(ctx, "agent template sync skipped missing or deleted role, deptId:%d, roleId:%d", child.DeptId, child.DeptRole)
|
||||
return nil
|
||||
}
|
||||
configLock := tx.Ctx(ctx).Model(dao.SysConfig.Table()).Where("tenant_id", template.TenantId).Where("config_key", rolepolicy.TemplateConfigKey)
|
||||
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
|
||||
configLock = configLock.LockUpdate()
|
||||
}
|
||||
var config *entity.SysConfig
|
||||
if err := configLock.Scan(&config); err != nil {
|
||||
return err
|
||||
}
|
||||
if config == nil || strings.TrimSpace(config.ConfigValue) != strconv.FormatInt(template.RoleId, 10) {
|
||||
return gerror.New("模板已切换,请重新同步")
|
||||
}
|
||||
templateLock := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", template.RoleId)
|
||||
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
|
||||
templateLock = templateLock.LockUpdate()
|
||||
}
|
||||
var latest *entity.SysRole
|
||||
if err := templateLock.Scan(&latest); err != nil {
|
||||
return err
|
||||
}
|
||||
if latest == nil || latest.Status != "0" {
|
||||
return gerror.New("模板不可用")
|
||||
}
|
||||
var menus []*entity.SysRoleMenu
|
||||
if err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where("tenant_id", template.TenantId).Where("role_id", template.RoleId).Scan(&menus); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", current.RoleId).Data(do.SysRole{CreatedDept: template.DeptId, DataScope: consts.SysRoleDataScopeDeptAndBelow, UpdatedBy: actorID}).Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where("role_id", current.RoleId).Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, menu := range menus {
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(do.SysRoleMenu{TenantId: template.TenantId, RoleId: current.RoleId, MenuId: menu.MenuId, DataScope: consts.SysRoleMenuDataScopeRole}).Insert(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
failed++
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
g.Log().Errorf(ctx, "agent template sync child failed, deptId:%d, err:%v", child.DeptId, err)
|
||||
continue
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleUpdate, child.DeptRole)
|
||||
}
|
||||
}
|
||||
if firstErr != nil {
|
||||
return gerror.Wrapf(firstErr, "代理模板同步失败企业数:%d", failed)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentUser struct {
|
||||
*agentBase
|
||||
}
|
||||
|
||||
func NewAgentUser() *sAgentUser {
|
||||
return &sAgentUser{
|
||||
agentBase: newAgentBase(),
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentUser(NewAgentUser())
|
||||
}
|
||||
|
||||
// UserList 查询用户列表
|
||||
func (s *sAgentUser) UserList(ctx context.Context, in *model.AgentUserListParam) (list []*model.AgentUserListModel, total int, err error) {
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
if deptId == 0 {
|
||||
return nil, 0, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
if in.Page <= 0 {
|
||||
in.Page = 1
|
||||
}
|
||||
if in.PageSize <= 0 {
|
||||
in.PageSize = 20
|
||||
}
|
||||
m := s.userModel(ctx).Where(dao.SysUser.Columns().DeptId, deptId)
|
||||
if in.UserName != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().UserName, "%"+in.UserName+"%")
|
||||
}
|
||||
if in.NickName != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().NickName, "%"+in.NickName+"%")
|
||||
}
|
||||
if in.Email != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().Email, "%"+in.Email+"%")
|
||||
}
|
||||
if in.Phonenumber != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().Phonenumber, "%"+in.Phonenumber+"%")
|
||||
}
|
||||
if in.Status != "" {
|
||||
m = m.Where(dao.SysUser.Columns().Status, in.Status)
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
begin, end := gtime.NewFromStr(in.CreatedAt[0]), gtime.NewFromStr(in.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysUser.Columns().CreatedAt, begin, end.EndOfDay())
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
var sysList []*model.SysUserListModel
|
||||
err = m.Order(dao.SysUser.Columns().UserId, "ASC").WithAll().Page(in.Page, in.PageSize).Scan(&sysList)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err = s.fillRolesAndDefaultFlag(ctx, sysList); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err = gconv.Structs(sysList, &list); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
// UserView 查看用户详情
|
||||
func (s *sAgentUser) UserView(ctx context.Context, userId int64) (res *model.AgentUserViewModel, err error) {
|
||||
sysUser, err := service.SysUser().GetUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysUser == nil || sysUser.UserId == 0 {
|
||||
return nil, gerror.New("用户不存在")
|
||||
}
|
||||
if sysUser.DeptId != contexts.GetDeptId(ctx) {
|
||||
return nil, gerror.New("只能操作本级用户")
|
||||
}
|
||||
res = &model.AgentUserViewModel{}
|
||||
if err = gconv.Struct(sysUser, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.IsDefaultUser, err = s.isDefaultUser(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// filterRoleIdsByDept 过滤角色ID,只保留本级及下一级角色
|
||||
func (s *sAgentUser) filterRoleIdsByDept(ctx context.Context, deptId int64, roleIds []int64) (res []int64, err error) {
|
||||
if len(roleIds) == 0 {
|
||||
return roleIds, nil
|
||||
}
|
||||
var roles []*entity.SysRole
|
||||
err = s.roleModel(ctx).
|
||||
WhereIn(dao.SysRole.Columns().RoleId, roleIds).
|
||||
Where(dao.SysRole.Columns().DeptId, deptId).
|
||||
Where("is_pass", 0).
|
||||
Scan(&roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, role := range roles {
|
||||
res = append(res, role.RoleId)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// fillRolesAndDefaultFlag 填充用户角色和默认标志
|
||||
func (s *sAgentUser) fillRolesAndDefaultFlag(ctx context.Context, list []*model.SysUserListModel) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
userIds := make([]int64, 0, len(list))
|
||||
deptIds := make([]int64, 0, len(list))
|
||||
userById := make(map[int64]*model.SysUserListModel, len(list))
|
||||
for _, user := range list {
|
||||
userIds = append(userIds, user.UserId)
|
||||
deptIds = append(deptIds, user.DeptId)
|
||||
userById[user.UserId] = user
|
||||
}
|
||||
|
||||
var userRoles []*entity.SysUserRole
|
||||
err := dao.SysUserRole.Ctx(ctx).
|
||||
WhereIn(dao.SysUserRole.Columns().UserId, userIds).
|
||||
Scan(&userRoles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
roleIds := make([]int64, 0, len(userRoles))
|
||||
userRoleMap := make(map[int64][]int64, len(userRoles))
|
||||
for _, userRole := range userRoles {
|
||||
roleIds = append(roleIds, userRole.RoleId)
|
||||
userRoleMap[userRole.UserId] = append(userRoleMap[userRole.UserId], userRole.RoleId)
|
||||
}
|
||||
if len(roleIds) > 0 {
|
||||
var roles []*entity.SysRole
|
||||
if err = s.roleModel(ctx).
|
||||
WhereIn(dao.SysRole.Columns().RoleId, roleIds).
|
||||
Scan(&roles); err != nil {
|
||||
return err
|
||||
}
|
||||
roleMap := make(map[int64]*entity.SysRole, len(roles))
|
||||
for _, role := range roles {
|
||||
roleMap[role.RoleId] = role
|
||||
}
|
||||
for _, userRole := range userRoles {
|
||||
role := roleMap[userRole.RoleId]
|
||||
user := userById[userRole.UserId]
|
||||
if role == nil || user == nil {
|
||||
continue
|
||||
}
|
||||
user.Roles = append(user.Roles, &model.SysRoleMiniModel{
|
||||
RoleId: role.RoleId,
|
||||
RoleName: role.RoleName,
|
||||
DataScope: role.DataScope,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var depts []*entity.SysDept
|
||||
if err = s.deptModel(ctx).
|
||||
WhereIn(dao.SysDept.Columns().DeptId, deptIds).
|
||||
Scan(&depts); err != nil {
|
||||
return err
|
||||
}
|
||||
deptRoleMap := make(map[int64]int64, len(depts))
|
||||
for _, dept := range depts {
|
||||
if dept.DeptRole > 0 {
|
||||
deptRoleMap[dept.DeptId] = dept.DeptRole
|
||||
}
|
||||
}
|
||||
for _, user := range list {
|
||||
deptRoleId := deptRoleMap[user.DeptId]
|
||||
if deptRoleId == 0 {
|
||||
continue
|
||||
}
|
||||
for _, roleId := range userRoleMap[user.UserId] {
|
||||
if roleId == deptRoleId {
|
||||
user.IsDefaultUser = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDefaultUser 判断用户是否为默认用户
|
||||
func (s *sAgentUser) isDefaultUser(ctx context.Context, userId int64) (bool, error) {
|
||||
var user *entity.SysUser
|
||||
if err := s.userModel(ctx).
|
||||
Where(dao.SysUser.Columns().UserId, userId).
|
||||
Scan(&user); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if user == nil || user.UserId == 0 {
|
||||
return false, gerror.New("用户不存在")
|
||||
}
|
||||
var dept *entity.SysDept
|
||||
if err := s.deptModel(ctx).
|
||||
Where(dao.SysDept.Columns().DeptId, user.DeptId).
|
||||
Scan(&dept); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if dept == nil || dept.DeptRole == 0 {
|
||||
return false, nil
|
||||
}
|
||||
count, err := dao.SysUserRole.Ctx(ctx).
|
||||
Where(dao.SysUserRole.Columns().UserId, userId).
|
||||
Where(dao.SysUserRole.Columns().RoleId, dept.DeptRole).
|
||||
Count()
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// UserSave 新增/修改用户
|
||||
func (s *sAgentUser) UserSave(ctx context.Context, in *model.SysUserAddModel) (res *model.AgentUserViewModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.New("登录已过期")
|
||||
}
|
||||
in.TenantId = user.TenantId
|
||||
in.DeptId = user.DeptId
|
||||
in.UserType = consts.SysUserTypeSys
|
||||
if in.DeptId == 0 {
|
||||
return nil, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
if len(in.RoleIds) == 0 {
|
||||
return nil, gerror.New("请选择角色")
|
||||
}
|
||||
in.RoleIds, err = s.filterRoleIdsByDept(ctx, in.DeptId, in.RoleIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(in.RoleIds) == 0 {
|
||||
return nil, gerror.New("请选择本级角色")
|
||||
}
|
||||
// 验证角色的DeptId和用户的deptId一致
|
||||
if err = s.ensureRoleDeptInScope(ctx, in.DeptId, in.RoleIds); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sysUser, err := service.SysUser().AddUser(ctx, in)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = &model.AgentUserViewModel{}
|
||||
if err = gconv.Struct(sysUser, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.IsDefaultUser, err = s.isDefaultUser(ctx, res.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// UserUpdate 更新用户
|
||||
func (s *sAgentUser) UserUpdate(ctx context.Context, in *model.SysUserUpdateModel) (err error) {
|
||||
current, err := s.UserView(ctx, in.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
if current.DeptId != currentDeptId {
|
||||
return gerror.New("只能修改本级用户")
|
||||
}
|
||||
in.DeptId = ¤t.DeptId
|
||||
if len(in.RoleIds) == 0 {
|
||||
return gerror.New("请选择角色")
|
||||
}
|
||||
in.RoleIds, err = s.filterRoleIdsByDept(ctx, *in.DeptId, in.RoleIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(in.RoleIds) == 0 {
|
||||
return gerror.New("请选择本级角色")
|
||||
}
|
||||
userType := consts.SysUserTypeSys
|
||||
in.UserType = &userType
|
||||
// 验证角色的DeptId和用户的deptId一致
|
||||
if err = s.ensureRoleDeptInScope(ctx, *in.DeptId, in.RoleIds); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.SysUser().UpdateUser(ctx, in)
|
||||
}
|
||||
|
||||
// UserDelete 删除用户
|
||||
func (s *sAgentUser) UserDelete(ctx context.Context, userIds []int64) (err error) {
|
||||
for _, userId := range userIds {
|
||||
if _, err = s.UserView(ctx, userId); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return service.SysUser().DeleteUser(ctx, userIds)
|
||||
}
|
||||
|
||||
// UserResetPassword 重置用户密码
|
||||
func (s *sAgentUser) UserResetPassword(ctx context.Context, userId int64, password string) (err error) {
|
||||
if _, err = s.UserView(ctx, userId); err != nil {
|
||||
return err
|
||||
}
|
||||
return service.SysUser().ResetPassword(ctx, userId, password)
|
||||
}
|
||||
|
||||
// 验证角色部门和用户部门是否一致
|
||||
func (s *sAgentUser) ensureRoleDeptInScope(ctx context.Context, deptId int64, roleIds []int64) (err error) {
|
||||
// 验证角色的DeptId和用户的deptId一致
|
||||
var roles []*entity.SysRole
|
||||
err = s.roleModel(ctx).
|
||||
WhereIn(dao.SysRole.Columns().RoleId, roleIds).
|
||||
Scan(&roles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, role := range roles {
|
||||
if role.DeptId != deptId {
|
||||
return gerror.New("角色部门与用户部门不一致")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentEnterprise struct{}
|
||||
|
||||
func NewAgentEnterprise() *sAgentEnterprise {
|
||||
return &sAgentEnterprise{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentEnterprise(NewAgentEnterprise())
|
||||
}
|
||||
|
||||
// Model 企业客户单位信息ORM模型
|
||||
func (s *sAgentEnterprise) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TAgentEnterprise.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取企业客户单位信息列表
|
||||
func (s *sAgentEnterprise) List(ctx context.Context, in *model.TAgentEnterpriseListParam) (list []*model.TAgentEnterpriseListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
// 字段过滤
|
||||
mod = mod.Fields(model.TAgentEnterpriseListModel{})
|
||||
|
||||
// 查询主键ID
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.TAgentEnterprise.Columns().Id, in.Id)
|
||||
}
|
||||
|
||||
// 查询创建时间
|
||||
if len(in.CreatedAt) == 2 {
|
||||
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(dao.TAgentEnterprise.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
// 分页
|
||||
mod = mod.Page(in.Page, in.PageSize)
|
||||
|
||||
// 排序
|
||||
mod = mod.OrderDesc(dao.TAgentEnterprise.Columns().Id)
|
||||
|
||||
// 操作人摘要信息
|
||||
mod = mod.Hook(hook.MemberSummary)
|
||||
|
||||
// 查询数据
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取企业客户单位信息列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出企业客户单位信息
|
||||
func (s *sAgentEnterprise) Export(ctx context.Context, in *model.TAgentEnterpriseListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.TAgentEnterpriseExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出企业客户单位信息-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
|
||||
exports []model.TAgentEnterpriseExportModel
|
||||
)
|
||||
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增企业客户单位信息
|
||||
func (s *sAgentEnterprise) Edit(ctx context.Context, in *model.TAgentEnterpriseEditParam) (err error) {
|
||||
// 验证'LegalPersonIdCard'唯一
|
||||
if err = xgorm.IsUnique(ctx, &dao.TAgentEnterprise, g.Map{dao.TAgentEnterprise.Columns().LegalPersonIdCard: in.LegalPersonIdCard}, "法人身份证号已存在", in.Id); err != nil {
|
||||
return
|
||||
}
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
in.UpdatedBy = &userId
|
||||
in.UpdatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TAgentEnterpriseUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改企业客户单位信息失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &deptId
|
||||
userId := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &userId
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.TAgentEnterpriseInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增企业客户单位信息失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除企业客户单位信息
|
||||
func (s *sAgentEnterprise) Delete(ctx context.Context, in *model.TAgentEnterpriseDeleteParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).Where(dao.TAgentEnterprise.Columns().DeptId, in.DeptId).Unscoped().Delete(); err != nil {
|
||||
err = gerror.Wrap(err, "删除企业客户单位信息失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取企业客户单位信息指定信息
|
||||
func (s *sAgentEnterprise) View(ctx context.Context, in *model.TAgentEnterpriseViewParam) (res *model.TAgentEnterpriseViewModel, err error) {
|
||||
if err = s.Model(ctx).Where(dao.TAgentEnterprise.Columns().DeptId, in.DeptId).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取企业客户单位信息信息,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentTApi struct{}
|
||||
|
||||
func NewAgentTApi() *sAgentTApi {
|
||||
return &sAgentTApi{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentTApi(NewAgentTApi())
|
||||
}
|
||||
|
||||
// Model API集成ORM模型
|
||||
func (s *sAgentTApi) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TApi.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取API集成列表
|
||||
func (s *sAgentTApi) List(ctx context.Context, in *model.TApiListParam) (list []*model.TApiListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx)
|
||||
tApiTable := dao.TApi.Table()
|
||||
sysDeptTable := dao.SysDept.Table()
|
||||
|
||||
// 字段过滤
|
||||
mod = mod.FieldsPrefix(tApiTable, model.TApiListModel{})
|
||||
mod = mod.Fields(
|
||||
sysDeptTable+"."+dao.SysDept.Columns().DeptKey+" AS deptKey",
|
||||
sysDeptTable+"."+dao.SysDept.Columns().DeptName+" AS deptName",
|
||||
)
|
||||
mod = mod.LeftJoin(
|
||||
sysDeptTable,
|
||||
sysDeptTable+"."+dao.SysDept.Columns().DeptId+"="+tApiTable+"."+dao.TApi.Columns().DeptId,
|
||||
)
|
||||
|
||||
// 查询APPKey
|
||||
if in.AppKey != "" {
|
||||
mod = mod.Where(dao.TApi.Columns().AppKey+" like ?", "%"+in.AppKey+"%")
|
||||
}
|
||||
|
||||
// 分页
|
||||
mod = mod.Page(in.Page, in.PageSize)
|
||||
|
||||
// 排序
|
||||
mod = mod.OrderDesc(dao.TApi.Columns().Id)
|
||||
|
||||
// 操作人摘要信息
|
||||
mod = mod.Hook(hook.MemberSummary)
|
||||
|
||||
// 查询数据
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取API集成列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出API集成
|
||||
func (s *sAgentTApi) Export(ctx context.Context, in *model.TApiListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.TApiExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出API集成-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
|
||||
exports []model.TApiExportModel
|
||||
)
|
||||
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增API集成
|
||||
func (s *sAgentTApi) Edit(ctx context.Context, in *model.TApiEditParam) (err error) {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
in.UpdatedBy = &userId
|
||||
in.UpdatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TApiUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改API集成失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &deptId
|
||||
userId := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &userId
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.TApiInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增API集成失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除API集成
|
||||
func (s *sAgentTApi) Delete(ctx context.Context, in *model.TApiDeleteParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
err = gerror.Wrap(err, "删除API集成失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取API集成指定信息
|
||||
func (s *sAgentTApi) View(ctx context.Context, in *model.TApiViewParam) (res *model.TApiViewModel, err error) {
|
||||
if err = s.Model(ctx).Where(dao.TApi.Columns().AppKey, in.AppKey).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取API集成信息,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentTApiGroup struct{}
|
||||
|
||||
// NewAgentTApiGroup 创建开放接口权限组后台服务实例。
|
||||
func NewAgentTApiGroup() *sAgentTApiGroup {
|
||||
return &sAgentTApiGroup{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentTApiGroup(NewAgentTApiGroup())
|
||||
}
|
||||
|
||||
// Model 返回开放接口权限组 ORM 模型,默认启用租户和数据权限过滤。
|
||||
func (s *sAgentTApiGroup) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TApiGroup.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 分页查询开放接口权限组列表。
|
||||
func (s *sAgentTApiGroup) List(ctx context.Context, in *model.TApiGroupListParam) (list []*model.TApiGroupListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx).Fields(model.TApiGroupListModel{})
|
||||
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.TApiGroup.Columns().Id, in.Id)
|
||||
}
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where(dao.TApiGroup.Columns().Status, in.Status)
|
||||
}
|
||||
if in.GroupName != "" {
|
||||
mod = mod.Where(dao.TApiGroup.Columns().GroupName+" LIKE ?", "%"+in.GroupName+"%")
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
|
||||
mod = mod.WhereBetween(dao.TApiGroup.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
mod = mod.Page(in.Page, in.PageSize).
|
||||
OrderDesc(dao.TApiGroup.Columns().Id).
|
||||
Hook(hook.MemberSummary)
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "获取开放接口权限组列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出开放接口权限组列表。
|
||||
func (s *sAgentTApiGroup) Export(ctx context.Context, in *model.TApiGroupListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := convert.GetEntityDescTags(model.TApiGroupExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fileName := "导出开放接口权限组-" + gctx.CtxId(ctx)
|
||||
sheetName := fmt.Sprintf(
|
||||
"搜索条件共%v条 共%v页 当前导出第%v页 本页共%v条",
|
||||
totalCount,
|
||||
(totalCount+in.PageSize-1)/in.PageSize,
|
||||
in.Page,
|
||||
len(list),
|
||||
)
|
||||
var exports []model.TApiGroupExportModel
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
return excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
}
|
||||
|
||||
// Edit 新增或修改开放接口权限组,并同步维护权限组与接口资源的关联。
|
||||
func (s *sAgentTApiGroup) Edit(ctx context.Context, in *model.TApiGroupEditParam) (err error) {
|
||||
if in.GroupCode != nil {
|
||||
if err = xgorm.IsUnique(ctx, &dao.TApiGroup, g.Map{dao.TApiGroup.Columns().GroupCode: *in.GroupCode}, "权限组编码已存在", in.Id); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
groupId := in.Id
|
||||
|
||||
if in.Id > 0 {
|
||||
updatedBy := contexts.GetUserId(ctx)
|
||||
in.UpdatedBy = &updatedBy
|
||||
in.UpdatedAt = gtime.Now()
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TApiGroupUpdateFields{}).
|
||||
WherePri(in.Id).
|
||||
Data(in).
|
||||
Update(); err != nil {
|
||||
return gerror.Wrap(err, "修改开放接口权限组失败,请稍后重试!")
|
||||
}
|
||||
return s.replaceGroupResources(ctx, tx, groupId, in.ApiIds)
|
||||
}
|
||||
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
createdDept := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &createdDept
|
||||
createdBy := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &createdBy
|
||||
in.CreatedAt = gtime.Now()
|
||||
groupId, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.TApiGroupInsertFields{}).
|
||||
Data(in).
|
||||
InsertAndGetId()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "新增开放接口权限组失败,请稍后重试!")
|
||||
}
|
||||
return s.replaceGroupResources(ctx, tx, groupId, in.ApiIds)
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除开放接口权限组,并删除该权限组下的资源关联。
|
||||
func (s *sAgentTApiGroup) Delete(ctx context.Context, in *model.TApiGroupDeleteParam) (err error) {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
groupIds := gconv.Int64s(in.Id)
|
||||
if len(groupIds) > 0 {
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TApiGroupResource.Table()).
|
||||
WhereIn(dao.TApiGroupResource.Columns().GroupId, groupIds).
|
||||
Delete(); err != nil {
|
||||
return gerror.Wrap(err, "删除开放接口权限组资源关联失败,请稍后重试!")
|
||||
}
|
||||
}
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
return gerror.Wrap(err, "删除开放接口权限组失败,请稍后重试!")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// View returns the API group detail with bound resource IDs and resource list.
|
||||
func (s *sAgentTApiGroup) View(ctx context.Context, in *model.TApiGroupViewParam) (res *model.TApiGroupViewModel, err error) {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "get open api group failed")
|
||||
}
|
||||
if res == nil {
|
||||
return
|
||||
}
|
||||
apiIds, err := dao.TApiGroupResource.Ctx(ctx).
|
||||
Fields(dao.TApiGroupResource.Columns().ApiId).
|
||||
Where(dao.TApiGroupResource.Columns().GroupId, in.Id).
|
||||
Array()
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "get open api group resource relations failed")
|
||||
}
|
||||
res.ApiIds = gconv.Int64s(apiIds)
|
||||
res.Apis = []*model.TApiResourceListModel{}
|
||||
if len(res.ApiIds) > 0 {
|
||||
if err = handler.Model(dao.TApiResource.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
Fields(model.TApiResourceListModel{}).
|
||||
WhereIn(dao.TApiResource.Columns().Id, res.ApiIds).
|
||||
OrderAsc(dao.TApiResource.Columns().Id).
|
||||
Hook(hook.MemberSummary).
|
||||
Scan(&res.Apis); err != nil {
|
||||
return nil, gerror.Wrap(err, "get open api group resources failed")
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新开放接口权限组启停状态。
|
||||
func (s *sAgentTApiGroup) Status(ctx context.Context, in *model.TApiGroupStatusParam) (err error) {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.TApiGroup.Columns().Status: in.Status,
|
||||
dao.TApiGroup.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.TApiGroup.Columns().UpdatedAt: gtime.Now(),
|
||||
}).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新开放接口权限组状态失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// replaceGroupResources 替换权限组绑定的接口资源,先清空旧关联再批量写入新关联。
|
||||
func (s *sAgentTApiGroup) replaceGroupResources(ctx context.Context, tx gdb.TX, groupId int64, apiIds []int64) (err error) {
|
||||
if groupId <= 0 {
|
||||
return gerror.New("权限组ID不能为空")
|
||||
}
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TApiGroupResource.Table()).
|
||||
Where(dao.TApiGroupResource.Columns().GroupId, groupId).
|
||||
Delete(); err != nil {
|
||||
return gerror.Wrap(err, "删除开放接口权限组资源关联失败,请稍后重试!")
|
||||
}
|
||||
|
||||
seen := make(map[int64]struct{}, len(apiIds))
|
||||
data := g.List{}
|
||||
for _, apiId := range apiIds {
|
||||
if apiId <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[apiId]; ok {
|
||||
continue
|
||||
}
|
||||
seen[apiId] = struct{}{}
|
||||
data = append(data, g.Map{
|
||||
dao.TApiGroupResource.Columns().GroupId: groupId,
|
||||
dao.TApiGroupResource.Columns().ApiId: apiId,
|
||||
})
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TApiGroupResource.Table()).Data(data).Insert(); err != nil {
|
||||
return gerror.Wrap(err, "保存开放接口权限组资源关联失败,请稍后重试!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentTApiResource struct{}
|
||||
|
||||
// NewAgentTApiResource 创建开放接口资源后台服务实例。
|
||||
func NewAgentTApiResource() *sAgentTApiResource {
|
||||
return &sAgentTApiResource{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentTApiResource(NewAgentTApiResource())
|
||||
}
|
||||
|
||||
// Model 返回开放接口资源 ORM 模型,默认启用租户和数据权限过滤。
|
||||
func (s *sAgentTApiResource) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TApiResource.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 分页查询开放接口资源列表。
|
||||
func (s *sAgentTApiResource) List(ctx context.Context, in *model.TApiResourceListParam) (list []*model.TApiResourceListModel, totalCount int, err error) {
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
if deptId <= 0 {
|
||||
return []*model.TApiResourceListModel{}, 0, nil
|
||||
}
|
||||
|
||||
mod := s.Model(ctx, &handler.Option{FilterTenant: true, FilterAuth: false}).
|
||||
Fields(model.TApiResourceListModel{})
|
||||
|
||||
isTop, dept, err := service.AgentDept().IsTopDept(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if dept != nil && (dept.DeptId == 100 || dept.ParentId == 100) {
|
||||
isTop = true
|
||||
}
|
||||
|
||||
if !isTop {
|
||||
var apiInfo struct {
|
||||
GroupId int64 `orm:"group_id"`
|
||||
}
|
||||
if err = dao.TApi.Ctx(ctx).
|
||||
Fields(dao.TApi.Columns().GroupId).
|
||||
Where(dao.TApi.Columns().DeptId, deptId).
|
||||
Where(dao.TApi.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
OrderDesc(dao.TApi.Columns().Id).
|
||||
Scan(&apiInfo); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "get current dept open api group failed")
|
||||
}
|
||||
if apiInfo.GroupId <= 0 {
|
||||
return []*model.TApiResourceListModel{}, 0, nil
|
||||
}
|
||||
|
||||
apiIds, err := dao.TApiGroupResource.Ctx(ctx).
|
||||
Fields(dao.TApiGroupResource.Columns().ApiId).
|
||||
Where(dao.TApiGroupResource.Columns().GroupId, apiInfo.GroupId).
|
||||
Array()
|
||||
if err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "get open api group resources failed")
|
||||
}
|
||||
if len(apiIds) == 0 {
|
||||
return []*model.TApiResourceListModel{}, 0, nil
|
||||
}
|
||||
mod = mod.WhereIn(dao.TApiResource.Columns().Id, gconv.Int64s(apiIds))
|
||||
}
|
||||
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.TApiResource.Columns().Id, in.Id)
|
||||
}
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where(dao.TApiResource.Columns().Status, in.Status)
|
||||
}
|
||||
if in.ApiName != "" {
|
||||
mod = mod.Where(dao.TApiResource.Columns().ApiName+" LIKE ?", "%"+in.ApiName+"%")
|
||||
}
|
||||
if in.Version != "" {
|
||||
mod = mod.Where(dao.TApiResource.Columns().Version+" LIKE ?", "%"+in.Version+"%")
|
||||
}
|
||||
|
||||
if len(in.CreatedAt) == 2 {
|
||||
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
|
||||
mod = mod.WhereBetween(dao.TApiResource.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
mod = mod.Page(in.Page, in.PageSize).
|
||||
OrderDesc(dao.TApiResource.Columns().Id).
|
||||
Hook(hook.MemberSummary)
|
||||
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
return nil, 0, gerror.Wrap(err, "获取开放接口资源列表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出开放接口资源列表。
|
||||
func (s *sAgentTApiResource) Export(ctx context.Context, in *model.TApiResourceListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
tags, err := convert.GetEntityDescTags(model.TApiResourceExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fileName := "export-open-api-resource-" + gctx.CtxId(ctx)
|
||||
totalPage := 0
|
||||
if in.PageSize > 0 {
|
||||
totalPage = (totalCount + in.PageSize - 1) / in.PageSize
|
||||
}
|
||||
sheetName := fmt.Sprintf("total %v pages %v current %v count %v", totalCount, totalPage, in.Page, len(list))
|
||||
|
||||
var exports []*model.TApiResourceExportModel
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
return excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
}
|
||||
|
||||
// Edit 新增或修改开放接口资源。
|
||||
func (s *sAgentTApiResource) Edit(ctx context.Context, in *model.TApiResourceEditParam) (err error) {
|
||||
if err = in.Filter(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
if in.Status == nil {
|
||||
status := 0
|
||||
in.Status = &status
|
||||
}
|
||||
if in.Method != nil {
|
||||
method := *in.Method
|
||||
in.Method = &method
|
||||
}
|
||||
|
||||
if in.ApiCode != nil && gstr.Trim(*in.ApiCode) != "" {
|
||||
if err = xgorm.IsUnique(ctx, &dao.TApiResource, g.Map{dao.TApiResource.Columns().ApiCode: gstr.Trim(*in.ApiCode)}, "接口权限标识已存在", in.Id); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
if in.Id > 0 {
|
||||
updatedBy := contexts.GetUserId(ctx)
|
||||
in.UpdatedBy = &updatedBy
|
||||
in.UpdatedAt = gtime.Now()
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TApiResourceUpdateFields{}).
|
||||
WherePri(in.Id).
|
||||
Data(in).
|
||||
Update(); err != nil {
|
||||
return gerror.Wrap(err, "修改开放接口资源失败,请稍后重试!")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
createdDept := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &createdDept
|
||||
createdBy := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &createdBy
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.TApiResourceInsertFields{}).
|
||||
Data(in).
|
||||
Insert(); err != nil {
|
||||
return gerror.Wrap(err, "新增开放接口资源失败,请稍后重试!")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除开放接口资源,并清理权限组资源关联。
|
||||
func (s *sAgentTApiResource) Delete(ctx context.Context, in *model.TApiResourceDeleteParam) (err error) {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
apiIds := gconv.Int64s(in.Id)
|
||||
if len(apiIds) > 0 {
|
||||
if _, err = tx.Ctx(ctx).Model(dao.TApiGroupResource.Table()).
|
||||
WhereIn(dao.TApiGroupResource.Columns().ApiId, apiIds).
|
||||
Delete(); err != nil {
|
||||
return gerror.Wrap(err, "删除开放接口权限组资源关联失败,请稍后重试!")
|
||||
}
|
||||
}
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
return gerror.Wrap(err, "删除开放接口资源失败,请稍后重试!")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// View 查询开放接口资源详情。
|
||||
func (s *sAgentTApiResource) View(ctx context.Context, in *model.TApiResourceViewParam) (res *model.TApiResourceViewModel, err error) {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
return nil, gerror.Wrap(err, "获取开放接口资源信息失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新开放接口资源启停状态。
|
||||
func (s *sAgentTApiResource) Status(ctx context.Context, in *model.TApiResourceStatusParam) (err error) {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.TApiResource.Columns().Status: in.Status,
|
||||
dao.TApiResource.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.TApiResource.Columns().UpdatedAt: gtime.Now(),
|
||||
}).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新开放接口资源状态失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sAgentTCustWithdrawAccount struct{}
|
||||
|
||||
func NewAgentTCustWithdrawAccount() *sAgentTCustWithdrawAccount {
|
||||
return &sAgentTCustWithdrawAccount{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterAgentTCustWithdrawAccount(NewAgentTCustWithdrawAccount())
|
||||
}
|
||||
|
||||
// Model 客户提现账户表ORM模型
|
||||
func (s *sAgentTCustWithdrawAccount) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TCustWithdrawAccount.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取客户提现账户表列表
|
||||
func (s *sAgentTCustWithdrawAccount) List(ctx context.Context, in *model.TCustWithdrawAccountListParam) (list []*model.TCustWithdrawAccountListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
// 字段过滤
|
||||
mod = mod.Fields(model.TCustWithdrawAccountListModel{})
|
||||
|
||||
// 查询主键ID
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.TCustWithdrawAccount.Columns().Id, in.Id)
|
||||
}
|
||||
|
||||
// 查询状态:0-禁用 1-正常
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where(dao.TCustWithdrawAccount.Columns().Status, in.Status)
|
||||
}
|
||||
|
||||
// 查询创建时间
|
||||
if len(in.CreatedAt) == 2 {
|
||||
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(dao.TCustWithdrawAccount.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
// 分页
|
||||
mod = mod.Page(in.Page, in.PageSize)
|
||||
|
||||
// 排序
|
||||
mod = mod.OrderDesc(dao.TCustWithdrawAccount.Columns().Id)
|
||||
|
||||
// 操作人摘要信息
|
||||
mod = mod.Hook(hook.MemberSummary)
|
||||
|
||||
// 查询数据
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取客户提现账户表列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出客户提现账户表
|
||||
func (s *sAgentTCustWithdrawAccount) Export(ctx context.Context, in *model.TCustWithdrawAccountListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.TCustWithdrawAccountExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出客户提现账户表-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
|
||||
exports []model.TCustWithdrawAccountExportModel
|
||||
)
|
||||
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增客户提现账户表
|
||||
func (s *sAgentTCustWithdrawAccount) Edit(ctx context.Context, in *model.TCustWithdrawAccountEditParam) (err error) {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
in.UpdatedBy = &userId
|
||||
in.UpdatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TCustWithdrawAccountUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改客户提现账户表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &deptId
|
||||
userId := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &userId
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.TCustWithdrawAccountInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增客户提现账户表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除客户提现账户表
|
||||
func (s *sAgentTCustWithdrawAccount) Delete(ctx context.Context, in *model.TCustWithdrawAccountDeleteParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
err = gerror.Wrap(err, "删除客户提现账户表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取客户提现账户表指定信息
|
||||
func (s *sAgentTCustWithdrawAccount) View(ctx context.Context, in *model.TCustWithdrawAccountViewParam) (res *model.TCustWithdrawAccountViewModel, err error) {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取客户提现账户表信息,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新客户提现账户表状态
|
||||
func (s *sAgentTCustWithdrawAccount) Status(ctx context.Context, in *model.TCustWithdrawAccountStatusParam) (err error) {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.TCustWithdrawAccount.Columns().Status: in.Status,
|
||||
dao.TCustWithdrawAccount.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
}).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新客户提现账户表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user