sync: 同步 v1.0.4 并脱敏配置与镜像
This commit is contained in:
@@ -42,11 +42,17 @@ func init() {
|
||||
}
|
||||
|
||||
func (s *sAgentDept) DeptList(ctx context.Context, in *model.AgentDeptListParam) (list []*model.AgentEnterpriseCustomerModel, total int, err error) {
|
||||
if in == nil {
|
||||
in = &model.AgentDeptListParam{}
|
||||
}
|
||||
in.NormalizePagination()
|
||||
list = make([]*model.AgentEnterpriseCustomerModel, 0)
|
||||
currentDeptId := contexts.GetDeptId(ctx)
|
||||
if currentDeptId == 0 {
|
||||
return nil, 0, gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
m := s.deptModel(ctx).
|
||||
Where(dao.SysDept.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(dao.SysDept.Columns().ParentId, currentDeptId).
|
||||
Where(dao.SysDept.Columns().DeptType, 1)
|
||||
if in.DeptName != "" {
|
||||
@@ -56,14 +62,15 @@ func (s *sAgentDept) DeptList(ctx context.Context, in *model.AgentDeptListParam)
|
||||
m = m.Where(dao.SysDept.Columns().Status, in.Status)
|
||||
}
|
||||
err = m.Order(dao.SysDept.Columns().DeptId, "ASC").
|
||||
Scan(&list)
|
||||
Page(in.Page, in.PageSize).
|
||||
ScanAndCount(&list, &total, false)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err = s.fillEnterpriseDefaultUsers(ctx, list); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return list, len(list), nil
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (s *sAgentDept) DeptView(ctx context.Context, deptId int64) (res *model.AgentEnterpriseCustomerModel, err error) {
|
||||
@@ -124,63 +131,25 @@ func (s *sAgentDept) DeptSave(ctx context.Context, in *model.AgentDeptSaveParam)
|
||||
return deptId, nil
|
||||
}
|
||||
|
||||
// EnsureCurrentDeptDefaultRole upgrades legacy child enterprises that did not
|
||||
// receive an inherited administrator role when they were created.
|
||||
func (s *sAgentDept) EnsureCurrentDeptDefaultRole(ctx context.Context) error {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
if contexts.GetUser(ctx) == nil {
|
||||
return gerror.New("登录已过期")
|
||||
}
|
||||
deptId := contexts.GetDeptId(ctx)
|
||||
if deptId == 0 {
|
||||
deptID := contexts.GetDeptId(ctx)
|
||||
if deptID <= 0 {
|
||||
return gerror.New("当前用户未绑定组织")
|
||||
}
|
||||
|
||||
var dept *entity.SysDept
|
||||
if err := s.deptModel(ctx).
|
||||
Where(dao.SysDept.Columns().DeptId, deptId).
|
||||
Scan(&dept); err != nil {
|
||||
if err := s.deptModel(ctx).Where(dao.SysDept.Columns().DeptId, deptID).Scan(&dept); err != nil {
|
||||
return err
|
||||
}
|
||||
if dept == nil || dept.DeptId == 0 {
|
||||
if dept == nil {
|
||||
return gerror.New("组织不存在")
|
||||
}
|
||||
if dept.DeptType != 1 || dept.DeptRole != 0 {
|
||||
return nil
|
||||
if dept.DeptType == 1 && dept.DeptRole <= 0 {
|
||||
return gerror.Newf("机构%d未关联默认角色,请联系平台管理员核对机构角色绑定", deptID)
|
||||
}
|
||||
|
||||
sourceRole, menuIds, err := s.passRoleByDept(ctx, dept.ParentId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
var current *entity.SysDept
|
||||
if err := tx.Ctx(ctx).Model(dao.SysDept.Table()).
|
||||
Where(dao.SysDept.Columns().DeptId, deptId).
|
||||
Scan(¤t); err != nil {
|
||||
return err
|
||||
}
|
||||
if current == nil || current.DeptId == 0 {
|
||||
return gerror.New("组织不存在")
|
||||
}
|
||||
if current.DeptRole != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
roleId, err := s.createEnterpriseRole(ctx, tx, user, deptId, sourceRole, menuIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.updateDeptRole(ctx, tx, deptId, roleId, user.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
// Keep the legacy role for the active session. After re-login, this role
|
||||
// becomes the department's selected default administrator role.
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysUserRole.Table()).
|
||||
Data(do.SysUserRole{UserId: user.ID, RoleId: roleId}).
|
||||
OmitNil().Insert()
|
||||
return err
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sAgentDept) validateEnterpriseSave(ctx context.Context, tenantId string, in *model.AgentDeptSaveParam) error {
|
||||
@@ -498,7 +467,10 @@ func (s *sAgentDept) passRoleByDept(ctx context.Context, deptId int64) (*entity.
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if template != nil && contexts.GetTenantId(ctx) == template.TenantId {
|
||||
if template != nil {
|
||||
if contexts.GetTenantId(ctx) != template.TenantId {
|
||||
return nil, nil, gerror.New("当前租户不属于平台代理模板授权范围")
|
||||
}
|
||||
// 配置了平台模板后,下发来源不再依赖 is_pass=1:
|
||||
// 一级代理由平台模板推出,其默认角色是复制来的 is_pass=0 角色;
|
||||
// 各级代理再创建下级时,必须能从本机构默认角色(sys_dept.dept_role)继续传递,
|
||||
@@ -523,7 +495,7 @@ func (s *sAgentDept) passRoleByDept(ctx context.Context, deptId int64) (*entity.
|
||||
}
|
||||
}
|
||||
var menus []*entity.SysRoleMenu
|
||||
if err := dao.SysRoleMenu.Ctx(ctx).Where("role_id", source.RoleId).Scan(&menus); err != nil {
|
||||
if err := dao.SysRoleMenu.Ctx(ctx).Master().Where("tenant_id", template.TenantId).Where("role_id", source.RoleId).Scan(&menus); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
ids := make([]int64, 0, len(menus))
|
||||
@@ -690,7 +662,11 @@ func (s *sAgentDept) childDeptAncestors(ctx context.Context, parentId int64) (st
|
||||
if ancestors == "" {
|
||||
ancestors = "0"
|
||||
}
|
||||
return fmt.Sprintf("%s,%d", ancestors, parent.DeptId), nil
|
||||
// ancestors 必须与平台约定格式一致:以本机构结尾并带尾逗号(如 "0,1,172,")。
|
||||
// 早期实现返回 "0,1,172"(缺尾逗号),而 GetDeptAndSub 按 '%,id,%' 展开
|
||||
// 「本机构及以下」数据权限,缺尾逗号时匹配不到,表现为上级机构看不到下级
|
||||
// (例:一级代理在卡片划拨里选不到下级客户)。
|
||||
return fmt.Sprintf("%s,%d,", ancestors, parent.DeptId), nil
|
||||
}
|
||||
|
||||
func (s *sAgentDept) nextCustomerDeptKey(ctx context.Context) (string, error) {
|
||||
@@ -744,11 +720,68 @@ func (s *sAgentDept) ensureUserNameUnique(ctx context.Context, tenantId, userNam
|
||||
}
|
||||
|
||||
func (s *sAgentDept) fillEnterpriseDefaultUsers(ctx context.Context, list []*model.AgentEnterpriseCustomerModel) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
deptIDs = make([]int64, 0, len(list))
|
||||
roleIDs = make([]int64, 0, len(list))
|
||||
bindings []struct {
|
||||
RoleId int64
|
||||
UserId int64
|
||||
}
|
||||
fallbacks []struct {
|
||||
DeptId int64
|
||||
UserId int64
|
||||
}
|
||||
)
|
||||
for _, dept := range list {
|
||||
user, err := s.defaultUserByDept(ctx, dept.DeptId, dept.DeptRole)
|
||||
if err != nil {
|
||||
deptIDs = append(deptIDs, dept.DeptId)
|
||||
if dept.DeptRole > 0 {
|
||||
roleIDs = append(roleIDs, dept.DeptRole)
|
||||
}
|
||||
}
|
||||
tenantID := contexts.GetTenantId(ctx)
|
||||
if len(roleIDs) > 0 {
|
||||
if err := dao.SysUserRole.Ctx(ctx).Where("tenant_id", tenantID).
|
||||
WhereIn("role_id", roleIDs).Fields("role_id", "MIN(user_id) AS user_id").Group("role_id").Scan(&bindings); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := s.userModel(ctx).Where("tenant_id", tenantID).WhereIn("dept_id", deptIDs).
|
||||
Fields("dept_id", "MIN(user_id) AS user_id").Group("dept_id").Scan(&fallbacks); err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
roleUsers = make(map[int64]int64, len(bindings))
|
||||
deptUsers = make(map[int64]int64, len(fallbacks))
|
||||
userIDs = make([]int64, 0, len(bindings)+len(fallbacks))
|
||||
users []*entity.SysUser
|
||||
)
|
||||
for _, binding := range bindings {
|
||||
roleUsers[binding.RoleId] = binding.UserId
|
||||
userIDs = append(userIDs, binding.UserId)
|
||||
}
|
||||
for _, fallback := range fallbacks {
|
||||
deptUsers[fallback.DeptId] = fallback.UserId
|
||||
userIDs = append(userIDs, fallback.UserId)
|
||||
}
|
||||
if len(userIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := s.userModel(ctx).Where("tenant_id", tenantID).WhereIn("dept_id", deptIDs).
|
||||
WhereIn("user_id", userIDs).Fields("user_id", "dept_id", "user_name", "nick_name").Scan(&users); err != nil {
|
||||
return err
|
||||
}
|
||||
usersByID := make(map[int64]*entity.SysUser, len(users))
|
||||
for _, user := range users {
|
||||
usersByID[user.UserId] = user
|
||||
}
|
||||
for _, dept := range list {
|
||||
user := usersByID[roleUsers[dept.DeptRole]]
|
||||
if user == nil || user.DeptId != dept.DeptId {
|
||||
user = usersByID[deptUsers[dept.DeptId]]
|
||||
}
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type paginationDeptService struct{ service.ISysDept }
|
||||
|
||||
func (paginationDeptService) Model(ctx context.Context, _ ...*handler.Option) *gdb.Model {
|
||||
return g.DB().Model("sys_dept").Ctx(ctx).Handler(handler.FilterTenant)
|
||||
}
|
||||
|
||||
type paginationUserService struct{ service.ISysUser }
|
||||
|
||||
func (paginationUserService) Model(ctx context.Context, _ ...*handler.Option) *gdb.Model {
|
||||
return g.DB().Model("sys_user").Ctx(ctx).Handler(handler.FilterTenant)
|
||||
}
|
||||
|
||||
func TestEnterpriseCustomerDatabasePaginationAndDefaultUsers(t *testing.T) {
|
||||
setupAgentRoleSyncTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{TenantId: "000000", DeptId: 1}}})
|
||||
service.RegisterSysDept(paginationDeptService{})
|
||||
service.RegisterSysUser(paginationUserService{})
|
||||
t.Cleanup(func() {
|
||||
service.RegisterSysDept(nil)
|
||||
service.RegisterSysUser(nil)
|
||||
})
|
||||
statements := []string{
|
||||
"ALTER TABLE sys_dept ADD COLUMN dept_name TEXT",
|
||||
"CREATE TABLE sys_user (user_id INTEGER PRIMARY KEY, tenant_id TEXT, dept_id INTEGER, user_name TEXT, nick_name TEXT, deleted_at DATETIME)",
|
||||
"INSERT INTO sys_dept (dept_id,tenant_id,parent_id,dept_type,dept_role,dept_name) VALUES (2,'000000',1,1,12,'企业A'),(3,'000000',1,1,13,'企业B'),(4,'000000',1,1,14,'企业C'),(5,'000000',2,1,15,'孙级企业'),(6,'other',1,1,16,'其他租户'),(7,'000000',1,0,17,'内部部门'),(8,'000000',1,1,18,'已删除企业'),(9,'000000',1,1,0,'无账号企业')",
|
||||
"UPDATE sys_dept SET deleted_at=CURRENT_TIMESTAMP WHERE dept_id=8",
|
||||
"INSERT INTO sys_user VALUES (20,'000000',2,'fallback-a','回退A',NULL),(21,'000000',2,'admin-a','管理员A',NULL),(30,'000000',3,'deleted-admin','已删除',CURRENT_TIMESTAMP),(31,'000000',3,'fallback-b','回退B',NULL),(40,'000000',4,'fallback-c','回退C',NULL),(50,'other',4,'other-user','跨租户',NULL)",
|
||||
"INSERT INTO sys_user_role (tenant_id,user_id,role_id) VALUES ('000000',21,12),('000000',30,13),('other',50,14)",
|
||||
}
|
||||
for _, statement := range statements {
|
||||
mustExecAgentRoleSyncTest(t, ctx, statement)
|
||||
}
|
||||
subject := NewAgentDept()
|
||||
params := &model.AgentDeptListParam{Page: 1, PageSize: 2}
|
||||
list, total, err := subject.DeptList(ctx, params)
|
||||
if err != nil || total != 4 || len(list) != 2 {
|
||||
t.Fatalf("page 1: total=%d list=%+v err=%v", total, list, err)
|
||||
}
|
||||
if list[0].DeptId != 2 || list[0].UserName != "admin-a" || list[1].DeptId != 3 || list[1].UserName != "fallback-b" {
|
||||
t.Fatalf("unexpected account selection: %+v %+v", list[0], list[1])
|
||||
}
|
||||
params.Page = 2
|
||||
list, total, err = subject.DeptList(ctx, params)
|
||||
if err != nil || total != 4 || len(list) != 2 || list[0].DeptId != 4 || list[0].UserName != "fallback-c" || list[1].DeptId != 9 || list[1].UserName != "" {
|
||||
t.Fatalf("page 2: total=%d list=%+v err=%v", total, list, err)
|
||||
}
|
||||
params.Page = 3
|
||||
list, total, err = subject.DeptList(ctx, params)
|
||||
if err != nil || total != 4 || len(list) != 0 || list == nil {
|
||||
t.Fatalf("out of range: total=%d list=%+v err=%v", total, list, err)
|
||||
}
|
||||
list, total, err = subject.DeptList(ctx, &model.AgentDeptListParam{DeptName: "企业A", Status: "0"})
|
||||
if err != nil || total != 1 || len(list) != 1 || list[0].DeptId != 2 {
|
||||
t.Fatalf("filtered list: total=%d list=%+v err=%v", total, list, err)
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,10 @@ import (
|
||||
"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/service"
|
||||
|
||||
_ "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/logic/system"
|
||||
)
|
||||
|
||||
// 复现生产形态:模板角色已配置(role 18 @ dept 100,is_pass=1),
|
||||
@@ -56,6 +57,8 @@ func TestPassRoleByDeptWorksForChildAgentWithoutTemplateFlag(t *testing.T) {
|
||||
|
||||
func setupPassRoleTestDB(t *testing.T) {
|
||||
t.Helper()
|
||||
service.RegisterSysDept(system.NewSysDept())
|
||||
service.RegisterSysRole(system.NewSysRole())
|
||||
if err := gdb.SetConfig(gdb.Config{
|
||||
"default": gdb.ConfigGroup{
|
||||
{Type: "sqlite", Link: "sqlite::@file(:memory:)", Debug: false},
|
||||
@@ -69,7 +72,7 @@ func setupPassRoleTestDB(t *testing.T) {
|
||||
}
|
||||
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_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_name TEXT, 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,
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/exportcenter"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// agentExportColumns 只允许列表字段,列标题和映射由服务端确定。
|
||||
func agentExportColumns(raw string, defaults []model.ExportColumn) ([]model.ExportColumn, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil, gerror.New("请选择导出列")
|
||||
}
|
||||
var requested []model.ExportColumn
|
||||
if err := json.Unmarshal([]byte(raw), &requested); err != nil {
|
||||
return nil, gerror.New("导出列格式错误")
|
||||
}
|
||||
allowed := make(map[string]model.ExportColumn, len(defaults))
|
||||
for _, col := range defaults {
|
||||
allowed[col.Field] = col
|
||||
}
|
||||
result := make([]model.ExportColumn, 0, len(requested))
|
||||
seen := make(map[string]bool)
|
||||
for _, col := range requested {
|
||||
safe, ok := allowed[col.Field]
|
||||
if !ok {
|
||||
return nil, gerror.New("包含不允许导出的字段")
|
||||
}
|
||||
if !seen[col.Field] {
|
||||
result = append(result, safe)
|
||||
seen[col.Field] = true
|
||||
}
|
||||
}
|
||||
if len(result) == 0 {
|
||||
return nil, gerror.New("请选择导出列")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *sAgentUser) UserExport(ctx context.Context, in *model.AgentUserListParam) error {
|
||||
columns, err := agentExportColumns(in.ExportColumnsJson, []model.ExportColumn{
|
||||
{Field: "userName", Label: "用户名称"}, {Field: "nickName", Label: "用户昵称"},
|
||||
{Field: "roles", Label: "角色"}, {Field: "phonenumber", Label: "联系电话"},
|
||||
{Field: "email", Label: "邮箱"}, {Field: "status", Label: "状态"}, {Field: "createdAt", Label: "创建时间"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||||
BizKey: "agentUser", BizName: "用户管理", PermCode: "cpm:agent:user:export", QueryParam: in, Columns: columns,
|
||||
RowFields: agentExportFields(columns),
|
||||
ToRow: func(value interface{}) []interface{} {
|
||||
row := value.(map[string]interface{})
|
||||
values := make([]interface{}, 0, len(columns))
|
||||
for _, column := range columns {
|
||||
values = append(values, row[column.Field])
|
||||
}
|
||||
return values
|
||||
},
|
||||
Tags: func() ([]string, error) {
|
||||
labels := make([]string, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
labels = append(labels, col.Label)
|
||||
}
|
||||
return labels, nil
|
||||
},
|
||||
Fetch: func(ctx context.Context, page, pageSize int) ([]interface{}, int, error) {
|
||||
query := *in
|
||||
query.Page = page
|
||||
query.PageSize = pageSize
|
||||
list, total, err := s.UserList(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows := make([]interface{}, 0, len(list))
|
||||
for _, item := range list {
|
||||
roles := make([]string, 0, len(item.Roles))
|
||||
for _, role := range item.Roles {
|
||||
if role != nil && role.RoleName != "" {
|
||||
roles = append(roles, role.RoleName)
|
||||
}
|
||||
}
|
||||
roleNames := strings.Join(roles, "、")
|
||||
if roleNames == "" {
|
||||
roleNames = "-"
|
||||
}
|
||||
createdAt := ""
|
||||
if item.CreatedAt != nil {
|
||||
createdAt = item.CreatedAt.Format("Y-m-d H:i:s")
|
||||
}
|
||||
rows = append(rows, map[string]interface{}{"userName": item.UserName, "nickName": item.NickName, "roles": roleNames, "phonenumber": item.Phonenumber, "email": item.Email, "status": agentExportStatus(item.Status), "createdAt": createdAt})
|
||||
}
|
||||
return rows, total, nil
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sAgentDept) DeptExport(ctx context.Context, in *model.AgentDeptListParam) error {
|
||||
columns, err := agentExportColumns(in.ExportColumnsJson, []model.ExportColumn{
|
||||
{Field: "deptName", Label: "企业名称"}, {Field: "userName", Label: "登录账号"}, {Field: "nickName", Label: "负责人"},
|
||||
{Field: "phone", Label: "联系电话"}, {Field: "email", Label: "联系人邮箱"}, {Field: "areaCode", Label: "组织所在地"},
|
||||
{Field: "address", Label: "详细地址"}, {Field: "createdAt", Label: "创建时间"}, {Field: "status", Label: "状态"},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||||
BizKey: "agentDept", BizName: "企业客户", PermCode: "cpm:agent:dept:export", QueryParam: in, Columns: columns,
|
||||
RowFields: agentExportFields(columns),
|
||||
ToRow: func(value interface{}) []interface{} {
|
||||
row := value.(map[string]interface{})
|
||||
values := make([]interface{}, 0, len(columns))
|
||||
for _, column := range columns {
|
||||
values = append(values, row[column.Field])
|
||||
}
|
||||
return values
|
||||
},
|
||||
Tags: func() ([]string, error) {
|
||||
labels := make([]string, 0, len(columns))
|
||||
for _, col := range columns {
|
||||
labels = append(labels, col.Label)
|
||||
}
|
||||
return labels, nil
|
||||
},
|
||||
Fetch: func(ctx context.Context, page, pageSize int) ([]interface{}, int, error) {
|
||||
query := *in
|
||||
query.Page = page
|
||||
query.PageSize = pageSize
|
||||
list, total, err := s.DeptList(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows := make([]interface{}, 0, len(list))
|
||||
for _, item := range list {
|
||||
createdAt := ""
|
||||
if item.CreatedAt != nil {
|
||||
createdAt = item.CreatedAt.Format("Y-m-d H:i:s")
|
||||
}
|
||||
rows = append(rows, map[string]interface{}{"deptName": item.DeptName, "userName": item.UserName, "nickName": item.NickName, "phone": item.Phone, "email": item.Email, "areaCode": item.AreaCode, "address": item.Address, "createdAt": createdAt, "status": agentExportStatus(item.Status)})
|
||||
}
|
||||
return rows, total, nil
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func agentExportFields(columns []model.ExportColumn) []string {
|
||||
fields := make([]string, 0, len(columns))
|
||||
for _, column := range columns {
|
||||
fields = append(fields, column.Field)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func agentExportStatus(status string) string {
|
||||
if status == "0" {
|
||||
return "正常"
|
||||
}
|
||||
if status == "1" {
|
||||
return "停用"
|
||||
}
|
||||
return "未知"
|
||||
}
|
||||
@@ -46,17 +46,22 @@ func (s *sAgentMenu) AllowedMenuIdsByDeptRole(ctx context.Context, deptId int64)
|
||||
return []int64{}, nil
|
||||
}
|
||||
if dept.DeptRole == consts.SuperAdminRoleId {
|
||||
return s.allMenuIds(ctx)
|
||||
menuIds, err = s.allMenuIds(ctx)
|
||||
} else {
|
||||
var roleMenus []*entity.SysRoleMenu
|
||||
roleMenus, err = service.SysRole().GetRoleListMenu(ctx, []int64{dept.DeptRole})
|
||||
for _, item := range roleMenus {
|
||||
menuIds = append(menuIds, item.MenuId)
|
||||
}
|
||||
}
|
||||
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)
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
return rolepolicy.RestrictAssignableMenus(ctx, dept.TenantId, dept.DeptId, contexts.GetUserId(ctx), menuIds)
|
||||
}
|
||||
return rolepolicy.RestrictMenus(ctx, dept.TenantId, dept.DeptId, menuIds)
|
||||
}
|
||||
|
||||
@@ -83,32 +88,6 @@ func (s *sAgentMenu) getRoleById(ctx context.Context, roleId int64) (role *entit
|
||||
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)
|
||||
@@ -118,13 +97,13 @@ func (s *sAgentMenu) AllowedMenuTree(ctx context.Context, deptId int64) (tree []
|
||||
if len(menuIds) == 0 && !contexts.IsSuperAdmin(ctx) {
|
||||
return []*model.SysMenuTreeModel{}, []int64{}, nil
|
||||
}
|
||||
menuIds, err = s.withMenuAncestors(ctx, menuIds)
|
||||
visibleMenuIds, err := rolepolicy.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)
|
||||
mod = mod.WhereIn(dao.SysMenu.Columns().MenuId, visibleMenuIds)
|
||||
}
|
||||
var menus []*entity.SysMenu
|
||||
err = mod.Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
@@ -134,43 +113,18 @@ func (s *sAgentMenu) AllowedMenuTree(ctx context.Context, deptId int64) (tree []
|
||||
return nil, nil, err
|
||||
}
|
||||
tree = s.buildMenuList(menus)
|
||||
activeMenuIds := make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
activeMenuIds = append(activeMenuIds, menu.MenuId)
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
menuIds = activeMenuIds
|
||||
} else {
|
||||
menuIds = rolepolicy.Intersect(menuIds, activeMenuIds)
|
||||
}
|
||||
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{
|
||||
|
||||
@@ -2,8 +2,6 @@ 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"
|
||||
@@ -112,7 +110,7 @@ func (s *sAgentRole) RoleView(ctx context.Context, roleId int64) (res *model.Age
|
||||
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)
|
||||
roleId, saveErr = s.saveTemplateRole(rolesync.DeferRecording(ctx), in)
|
||||
if saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
@@ -153,24 +151,17 @@ func (s *sAgentRole) saveTemplateRole(ctx context.Context, in *model.AgentRoleSa
|
||||
in.DataScope = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
allowed, err := s.filterMenuIdsByAllowed(ctx, in.DeptId, in.MenuIds)
|
||||
allowed, err := rolepolicy.NormalizeAssignableMenus(ctx, user.TenantId, in.DeptId, user.ID, in.MenuIds)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for _, id := range in.MenuIds {
|
||||
if !slices.Contains(allowed, id) {
|
||||
return 0, gerror.New("角色包含超出本机构权限范围的菜单或按钮")
|
||||
}
|
||||
}
|
||||
in.MenuIds = allowed
|
||||
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
|
||||
@@ -181,7 +172,6 @@ func (s *sAgentRole) saveTemplateRole(ctx context.Context, in *model.AgentRoleSa
|
||||
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()
|
||||
@@ -399,188 +389,3 @@ func (s *sAgentRole) inheritedRoleModel(ctx context.Context) *gdb.Model {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"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/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
func TestEnsureRoleNotInheritedAllowsSuperAdmin(t *testing.T) {
|
||||
ctx := contexts.Set(context.Background(), &model.Context{
|
||||
User: &model.Identity{BaseClaims: model.BaseClaims{
|
||||
TenantId: "000000",
|
||||
Roles: []model.Role{{RoleId: consts.SuperAdminRoleId}},
|
||||
}},
|
||||
})
|
||||
|
||||
if err := NewAgentRole().ensureRoleNotInherited(ctx, 100); err != nil {
|
||||
t.Fatalf("super admin should edit inherited roles, got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentSuperAdminChecksLiveRoleAssignment(t *testing.T) {
|
||||
setupAgentRoleSyncTestDB(t)
|
||||
ctx := context.Background()
|
||||
mustExecAgentRoleSyncTest(t, ctx, "INSERT INTO sys_role (role_id,tenant_id,dept_id,status) VALUES (1,'000000',1,'0')")
|
||||
mustExecAgentRoleSyncTest(t, ctx, "INSERT INTO sys_user_role (tenant_id,user_id,role_id) VALUES ('000000',99,1)")
|
||||
isAdmin, err := rolepolicy.CurrentSuperAdmin(ctx, 99)
|
||||
if err != nil || !isAdmin {
|
||||
t.Fatalf("expected active administrator: %v %v", isAdmin, err)
|
||||
}
|
||||
mustExecAgentRoleSyncTest(t, ctx, "DELETE FROM sys_user_role WHERE user_id=99")
|
||||
isAdmin, err = rolepolicy.CurrentSuperAdmin(ctx, 99)
|
||||
if err != nil || isAdmin {
|
||||
t.Fatalf("revoked administrator must lose access: %v %v", isAdmin, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoActiveRolesCannotReadFilteredData(t *testing.T) {
|
||||
setupAgentRoleSyncTestDB(t)
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{ID: 99, TenantId: "000000", DeptId: 2}}})
|
||||
mustExecAgentRoleSyncTest(t, ctx, "INSERT INTO sys_role (role_id,tenant_id,dept_id,status) VALUES (21,'000000',2,'0')")
|
||||
count, err := g.DB().Model("sys_role").Ctx(ctx).Handler(handler.FilterAuth).Count()
|
||||
if err != nil || count != 0 {
|
||||
t.Fatalf("user without roles must not read filtered data: %d %v", count, err)
|
||||
}
|
||||
}
|
||||
|
||||
func setupAgentRoleSyncTestDB(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)
|
||||
}
|
||||
ctx := context.Background()
|
||||
t.Cleanup(func() { _ = g.DB().Close(ctx) })
|
||||
mustExecAgentRoleSyncTest(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)`)
|
||||
mustExecAgentRoleSyncTest(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)`)
|
||||
mustExecAgentRoleSyncTest(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)`)
|
||||
mustExecAgentRoleSyncTest(t, ctx, `CREATE TABLE sys_role_menu (tenant_id TEXT, role_id INTEGER, menu_id INTEGER, data_scope TEXT)`)
|
||||
mustExecAgentRoleSyncTest(t, ctx, `CREATE TABLE sys_role_dept (tenant_id TEXT, role_id INTEGER, dept_id INTEGER)`)
|
||||
mustExecAgentRoleSyncTest(t, ctx, `CREATE TABLE sys_user_role (tenant_id TEXT DEFAULT '000000', user_id INTEGER, role_id INTEGER)`)
|
||||
}
|
||||
|
||||
func mustExecAgentRoleSyncTest(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", err)
|
||||
}
|
||||
}
|
||||
|
||||
func assertAgentRoleSyncCount(t *testing.T, ctx context.Context, sql string, expected int) {
|
||||
t.Helper()
|
||||
count, err := g.DB().GetValue(ctx, sql)
|
||||
if err != nil {
|
||||
t.Fatalf("query count failed: %v", err)
|
||||
}
|
||||
if count.Int() != expected {
|
||||
t.Fatalf("count = %d, want %d, sql=%s", count.Int(), expected, sql)
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
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,64 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"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"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
)
|
||||
|
||||
type templateRoleService struct{ service.ISysRole }
|
||||
|
||||
func (templateRoleService) ModelQuery(ctx context.Context, _ ...*handler.Option) *gdb.Model {
|
||||
return g.DB().Model("sys_role").Ctx(ctx).Handler(handler.FilterTenant)
|
||||
}
|
||||
|
||||
func TestConfiguredTemplateUsesInheritedDefaultAtEveryLevel(t *testing.T) {
|
||||
setupAgentRoleSyncTestDB(t)
|
||||
service.RegisterSysDept(paginationDeptService{})
|
||||
service.RegisterSysRole(templateRoleService{})
|
||||
t.Cleanup(func() {
|
||||
service.RegisterSysDept(nil)
|
||||
service.RegisterSysRole(nil)
|
||||
})
|
||||
ctx := contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{ID: 99, TenantId: "000000", DeptId: 2}}})
|
||||
for _, statement := range []string{
|
||||
"ALTER TABLE sys_dept ADD COLUMN dept_name TEXT",
|
||||
"INSERT INTO sys_dept(dept_id,tenant_id,parent_id,dept_type,dept_role) VALUES(1,'000000',0,1,0),(2,'000000',1,1,21),(3,'000000',2,1,31)",
|
||||
"INSERT INTO sys_role(role_id,tenant_id,dept_id,status,is_pass,created_dept,data_scope) VALUES(10,'000000',1,'0',0,1,'4'),(21,'000000',2,'0',0,1,'4'),(31,'000000',3,'0',0,2,'4')",
|
||||
"INSERT INTO sys_config(tenant_id,config_key,config_value) VALUES('000000','agent.role.template.role_id','10')",
|
||||
"INSERT INTO sys_role_menu(tenant_id,role_id,menu_id,data_scope) VALUES('000000',10,100,'0'),('000000',10,200,'0'),('000000',21,100,'0'),('000000',21,300,'0'),('000000',31,100,'0')",
|
||||
} {
|
||||
mustExecAgentRoleSyncTest(t, ctx, statement)
|
||||
}
|
||||
for _, deptID := range []int64{1, 2, 3} {
|
||||
source, menus, err := NewAgentDept().passRoleByDept(ctx, deptID)
|
||||
if err != nil || source == nil {
|
||||
t.Fatalf("source for department %d: %+v %v", deptID, source, err)
|
||||
}
|
||||
expected := map[int64][]int64{1: {100, 200}, 2: {100, 300}, 3: {100}}[deptID]
|
||||
got := make([]int64, 0, len(menus))
|
||||
for _, menu := range menus {
|
||||
got = append(got, menu.MenuId)
|
||||
}
|
||||
slices.Sort(got)
|
||||
if !slices.Equal(got, expected) {
|
||||
t.Fatalf("department %d must copy its current default role including platform expansion: %v, want %v", deptID, got, expected)
|
||||
}
|
||||
}
|
||||
mustExecAgentRoleSyncTest(t, ctx, "UPDATE sys_dept SET dept_role=0 WHERE dept_id=2")
|
||||
if _, _, err := NewAgentDept().passRoleByDept(ctx, 2); err == nil {
|
||||
t.Fatal("missing default role must not fall back to full platform permissions")
|
||||
}
|
||||
if err := NewAgentDept().EnsureCurrentDeptDefaultRole(ctx); err == nil {
|
||||
t.Fatal("read request must report missing default role")
|
||||
}
|
||||
assertAgentRoleSyncCount(t, ctx, "SELECT COUNT(*) FROM sys_role", 3)
|
||||
assertAgentRoleSyncCount(t, ctx, "SELECT COUNT(*) FROM sys_user_role", 0)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package agent
|
||||
|
||||
@@ -1,29 +1,26 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @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/exportcenter"
|
||||
"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{}
|
||||
@@ -86,31 +83,32 @@ func (s *sAgentTApi) List(ctx context.Context, in *model.TApiListParam) (list []
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出API集成
|
||||
// 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
|
||||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||||
BizKey: "tApi",
|
||||
BizName: "API集成",
|
||||
PermCode: "cpm:agent:tApi:export",
|
||||
QueryParam: in,
|
||||
Tags: func() ([]string, error) {
|
||||
return convert.GetEntityDescTags(model.TApiListModel{})
|
||||
},
|
||||
Fetch: func(ctx context.Context, page, pageSize int) (rows []interface{}, total int, err error) {
|
||||
query := *in
|
||||
query.Page = page
|
||||
query.PageSize = pageSize
|
||||
list, count, err := s.List(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows = make([]interface{}, 0, len(list))
|
||||
for _, item := range list {
|
||||
rows = append(rows, item)
|
||||
}
|
||||
return rows, count, nil
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Edit 修改/新增API集成
|
||||
|
||||
@@ -2,21 +2,19 @@ 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/exportcenter"
|
||||
"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"
|
||||
)
|
||||
@@ -71,31 +69,32 @@ func (s *sAgentTApiGroup) List(ctx context.Context, in *model.TApiGroupListParam
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出开放接口权限组列表。
|
||||
// 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)
|
||||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||||
BizKey: "tApiGroup",
|
||||
BizName: "开放接口权限组",
|
||||
PermCode: "cpm:agent:tApiGroup:export",
|
||||
QueryParam: in,
|
||||
Tags: func() ([]string, error) {
|
||||
return convert.GetEntityDescTags(model.TApiGroupListModel{})
|
||||
},
|
||||
Fetch: func(ctx context.Context, page, pageSize int) (rows []interface{}, total int, err error) {
|
||||
query := *in
|
||||
query.Page = page
|
||||
query.PageSize = pageSize
|
||||
list, count, err := s.List(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows = make([]interface{}, 0, len(list))
|
||||
for _, item := range list {
|
||||
rows = append(rows, item)
|
||||
}
|
||||
return rows, count, nil
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Edit 新增或修改开放接口权限组,并同步维护权限组与接口资源的关联。
|
||||
|
||||
@@ -2,22 +2,20 @@ 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/exportcenter"
|
||||
"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"
|
||||
@@ -120,30 +118,32 @@ func (s *sAgentTApiResource) List(ctx context.Context, in *model.TApiResourceLis
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出开放接口资源列表。
|
||||
// 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)
|
||||
_, err = exportcenter.Submit(ctx, &exportcenter.Request{
|
||||
BizKey: "tApiResource",
|
||||
BizName: "开放接口资源",
|
||||
PermCode: "cpm:agent:tApiResource:export",
|
||||
QueryParam: in,
|
||||
Tags: func() ([]string, error) {
|
||||
return convert.GetEntityDescTags(model.TApiResourceListModel{})
|
||||
},
|
||||
Fetch: func(ctx context.Context, page, pageSize int) (rows []interface{}, total int, err error) {
|
||||
query := *in
|
||||
query.Page = page
|
||||
query.PageSize = pageSize
|
||||
list, count, err := s.List(ctx, &query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
rows = make([]interface{}, 0, len(list))
|
||||
for _, item := range list {
|
||||
rows = append(rows, item)
|
||||
}
|
||||
return rows, count, nil
|
||||
},
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Edit 新增或修改开放接口资源。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package iot
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @Author Lxj <xxx@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package agent
|
||||
|
||||
Reference in New Issue
Block a user