// package system // @Link https://github.com/xiujiecn/xiu-admin // @Copyright Copyright (c) 2025 LiXiujie // @Author Lxj // @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE package system import ( "context" "database/sql" "errors" "fmt" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "git.5iot.com/iotlcm-public/iotlcm-app/utility" "slices" "strings" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" ) type sSysTenant struct { } func NewSysTenant() *sSysTenant { return &sSysTenant{} } func init() { s := NewSysTenant() service.RegisterSysTenant(s) } func (l *sSysTenant) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model { if len(option) == 0 { option = append(option, &handler.Option{ FilterTenant: false, }) } return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysTenant.Table()), option...) } // 获取租户信息 func (l *sSysTenant) View(ctx context.Context, param *model.SysTenantViewParam) (data *model.SysTenantViewModel, err error) { db := l.ModelQuery(ctx) if param.Id != 0 { db = db.Where(dao.SysTenant.Columns().Id, param.Id) } else if param.TenantId != "" { db = db.Where(dao.SysTenant.Columns().TenantId, param.TenantId) } else { return nil, errors.New("参数错误") } err = db.Scan(&data) return } // 获取租户列表 func (l *sSysTenant) List(ctx context.Context, param *model.SysTenantListParam) (data []*model.SysTenantListModel, total int, err error) { // db := dao.SysTenant.Ctx(ctx) // 内存数据库优化 db := l.ModelQuery(ctx) if param.TenantId != "" { db = db.Where(dao.SysTenant.Columns().TenantId, param.TenantId) } if param.ContactUserName != "" { db = db.WhereLike(dao.SysTenant.Columns().ContactUserName, "%"+param.ContactUserName+"%") } if param.ContactPhone != "" { db = db.WhereLike(dao.SysTenant.Columns().ContactPhone, "%"+param.ContactPhone+"%") } if param.CompanyName != "" { db = db.WhereLike(dao.SysTenant.Columns().CompanyName, "%"+param.CompanyName+"%") } if param.LicenseNumber != "" { db = db.WhereLike(dao.SysTenant.Columns().LicenseNumber, "%"+param.LicenseNumber+"%") } if param.Status != "" { db = db.Where(dao.SysTenant.Columns().Status, param.Status) } total, err = db.Count() if err != nil { return nil, 0, err } err = db.Page(param.Page, param.PageSize).OrderAsc(dao.SysTenant.Columns().PackageId).Scan(&data) if err != nil { return nil, 0, err } return data, total, nil } func (l *sSysTenant) Add(ctx context.Context, param *model.SysTenantAddParam) (output *model.SysTenantAddModel, err error) { data := &do.SysTenant{} gconv.Struct(param, &data) data.CreatedAt = gtime.Now() data.CreatedBy = contexts.GetUserId(ctx) data.CreatedDept = contexts.GetDeptId(ctx) // 查询租户套餐 tenantPackage, err := service.SysTenantPackage().View(ctx, &model.SysTenantPackageViewParam{ PackageId: param.PackageId, }) if err != nil || tenantPackage == nil { g.Log().Errorf(ctx, "sSysTenant.Add SysTenantPackage().View err: %v, packageId: %d, param: %+v", err, param.PackageId, param) err = errors.New("租户套餐不存在") return nil, err } if tenantPackage.MenuIds == "" { err = errors.New("租户套餐菜单为空") return nil, err } menuIds := strings.Split(tenantPackage.MenuIds, ",") tenantId := "" deptId := int64(0) roleId := int64(0) userId := int64(0) sysConfigIds := make([]int64, 0) err = g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error { data.TenantId = "100000" id, err := dao.SysTenant.Ctx(ctx).TX(tx).Data(data).OmitNil().InsertAndGetId() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysTenant.Table().InsertAndGetId err: %v, data: %+v", err, data) return err } tenantId = fmt.Sprintf("%06d", 100000+id) // 创建部门 dataDeptInsert := do.SysDept{} dataDeptInsert.TenantId = tenantId dataDeptInsert.DeptName = param.CompanyName dataDeptInsert.ParentId = 0 dataDeptInsert.OrderNum = 0 dataDeptInsert.DeptType = 1 // 0:部门 1是公司 dataDeptInsert.Status = consts.SysDeptStatusNormal dataDeptInsert.CreatedBy = contexts.GetUserId(ctx) dataDeptInsert.CreatedAt = gtime.Now() dataDeptInsert.UpdatedBy = contexts.GetUserId(ctx) dataDeptInsert.UpdatedAt = gtime.Now() deptId, err = dao.SysDept.Ctx(ctx).TX(tx).Data(dataDeptInsert).OmitNil().InsertAndGetId() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysDept.Table().InsertAndGetId err: %v, data: %+v", err, dataDeptInsert) return err } // 创建角色 dataRoleInsert := do.SysRole{} dataRoleInsert.TenantId = tenantId dataRoleInsert.RoleName = "租户管理员" dataRoleInsert.RoleKey = "tenantAdmin" dataRoleInsert.RoleSort = 1 dataRoleInsert.Status = "0" dataRoleInsert.DataScope = consts.SysRoleDataScopeAll dataRoleInsert.MenuCheckStrictly = 0 dataRoleInsert.DeptCheckStrictly = 0 dataRoleInsert.CreatedBy = contexts.GetUserId(ctx) dataRoleInsert.CreatedAt = gtime.Now() dataRoleInsert.UpdatedBy = contexts.GetUserId(ctx) dataRoleInsert.UpdatedAt = gtime.Now() roleId, err = dao.SysRole.Ctx(ctx).TX(tx).Data(dataRoleInsert).OmitNil().InsertAndGetId() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysRole.Table().InsertAndGetId err: %v, data: %+v", err, dataRoleInsert) return err } // 角色关联菜单 dataRoleMenuInserts := []*do.SysRoleMenu{} for _, menuId := range menuIds { dataRoleMenuInserts = append(dataRoleMenuInserts, &do.SysRoleMenu{ RoleId: roleId, MenuId: menuId, }) } _, err = dao.SysRoleMenu.Ctx(ctx).TX(tx).Data(dataRoleMenuInserts).OmitNil().Insert() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysRoleMenu.Table().Insert err: %v, data: %+v", err, dataRoleMenuInserts) return err } // 创建系统管理员 salt := utility.RandomString(5) password := utility.PasswordEncrypt(param.Password, salt) dataUserInsert := do.SysUser{} dataUserInsert.TenantId = tenantId dataUserInsert.DeptId = deptId dataUserInsert.NickName = param.ContactUserName dataUserInsert.UserName = param.Username dataUserInsert.Password = password dataUserInsert.UserType = consts.SysUserTypeSys dataUserInsert.Salt = salt dataUserInsert.Phonenumber = param.ContactPhone dataUserInsert.Email = "" dataUserInsert.Sex = consts.SysUserSexUnknown dataUserInsert.Status = consts.SysUserStatusNormal dataUserInsert.CreatedDept = contexts.GetDeptId(ctx) dataUserInsert.CreatedBy = contexts.GetUserId(ctx) dataUserInsert.CreatedAt = gtime.Now() dataUserInsert.UpdatedBy = contexts.GetUserId(ctx) dataUserInsert.UpdatedAt = gtime.Now() userId, err = dao.SysUser.Ctx(ctx).TX(tx).Data(dataUserInsert).OmitNil().InsertAndGetId() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysUser.Table().InsertAndGetId err: %v, data: %+v", err, dataUserInsert) return err } // 修改租户信息 tenantUpdate := do.SysTenant{ TenantId: tenantId, AdminRoleId: roleId, AdminDeptId: deptId, AdminUserId: userId, } _, err = dao.SysTenant.Ctx(ctx).TX(tx).Data(tenantUpdate). Where(dao.SysTenant.Columns().Id, id).Update() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysTenant.Table().Update err: %v, data: %+v", err, data) return err } // 修改部门负责人 dataDeptUpdate := do.SysDept{} dataDeptUpdate.DeptId = deptId dataDeptUpdate.Leader = userId dataDeptUpdate.UpdatedBy = contexts.GetUserId(ctx) dataDeptUpdate.UpdatedAt = gtime.Now() _, err = dao.SysDept.Ctx(ctx).TX(tx).Data(dataDeptUpdate).OmitNil().Where(dao.SysDept.Columns().DeptId, deptId).Update() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysDept.Table().Update err: %v, data: %+v", err, dataDeptUpdate) return err } output = &model.SysTenantAddModel{ Id: id, } // 角色关联用户 dataUserRoleInserts := []*do.SysUserRole{} dataUserRoleInserts = append(dataUserRoleInserts, &do.SysUserRole{ UserId: userId, RoleId: roleId, }) _, err = dao.SysUserRole.Ctx(ctx).TX(tx).Data(dataUserRoleInserts).OmitNil().Insert() if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add SysUserRole.Table().Insert err: %v, data: %+v", err, dataUserRoleInserts) return err } // 插入系统参数 sysConfigIds, err = l.BatchInsertSysParam(ctx, tx, tenantId, userId, deptId) if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add batchInsertSysParam err: %v, tenantId: %s, createdBy: %d, createdDept: %d", err, tenantId, contexts.GetUserId(ctx), contexts.GetDeptId(ctx)) return err } return nil }) if err != nil { g.Log().Errorf(ctx, "sSysTenant.Add err: %v", err) return nil, err } // 更新缓存 event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptCreate, deptId) event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleCreate, roleId) event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantCreate, output.Id) event.EventsInstance().Emit(ctx, consts.EventKeyUserCreate, userId) for _, sysConfigId := range sysConfigIds { event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigCreate, sysConfigId) } return } func (l *sSysTenant) Edit(ctx context.Context, param *model.SysTenantEditParam) (output *model.SysTenantEditModel, err error) { if param.Id == 0 { return nil, errors.New("参数错误") } data := &do.SysTenant{} gconv.Struct(param, &data) data.UpdatedAt = gtime.Now() data.UpdatedBy = contexts.GetUserId(ctx) db := dao.SysTenant.Ctx(ctx) db = db.Where(dao.SysTenant.Columns().Id, param.Id) _, err = db.Data(data).OmitNil().Update() if err != nil { return nil, err } output = &model.SysTenantEditModel{ Id: param.Id, } event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantUpdate, param.Id) return } func (l *sSysTenant) Delete(ctx context.Context, param *model.SysTenantDeleteParam) (output *model.SysTenantDeleteModel, err error) { if len(param.Ids) == 0 { return nil, errors.New("参数错误") } if slices.Contains(param.Ids, 1) { return nil, errors.New("不能删除默认租户") } data := &do.SysTenant{} gconv.Struct(param, &data) data.DeletedAt = gtime.Now() data.DeletedBy = contexts.GetUserId(ctx) db := dao.SysTenant.Ctx(ctx) db = db.WhereIn(dao.SysTenant.Columns().Id, param.Ids) _, err = db.Data(data).OmitNil().Update() if err != nil { return nil, err } output = &model.SysTenantDeleteModel{ Ids: param.Ids, } event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantDelete, param.Ids) return } func (l *sSysTenant) Status(ctx context.Context, param *model.SysTenantStatusParam) (output *model.SysTenantStatusModel, err error) { if param.Id == 0 { return nil, errors.New("参数错误") } data := &do.SysTenant{} gconv.Struct(param, &data) data.UpdatedAt = gtime.Now() data.UpdatedBy = contexts.GetUserId(ctx) db := dao.SysTenant.Ctx(ctx) db = db.Where(dao.SysTenant.Columns().Id, param.Id) _, err = db.Data(data).OmitNil().Update() if err != nil { return nil, err } output = &model.SysTenantStatusModel{ Id: param.Id, } event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantUpdate, param.Id) return } // 新建租户插入系统参数表数据 func (l *sSysTenant) BatchInsertSysParam(ctx context.Context, tx gdb.TX, tenantId string, createdBy int64, createdDept int64) (ids []int64, err error) { now := gtime.Now() configs := []do.SysConfig{ { TenantId: tenantId, ConfigName: "主框架页-默认皮肤样式名称", ConfigKey: "sys.index.skinName", ConfigValue: "skin-blue", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow", }, { TenantId: tenantId, ConfigName: "用户管理-账号初始密码", ConfigKey: "sys.user.initPassword", ConfigValue: "123456", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "初始化密码 123456", }, { TenantId: tenantId, ConfigName: "主框架页-侧边栏主题", ConfigKey: "sys.index.sideTheme", ConfigValue: "theme-dark", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "深色主题theme-dark,浅色主题theme-light", }, { TenantId: tenantId, ConfigName: "账号自助-是否开启用户注册功能", ConfigKey: "sys.account.registerUser", ConfigValue: "false", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "是否开启注册用户功能(true开启,false关闭)", }, { TenantId: tenantId, ConfigName: "OSS预览列表资源开关", ConfigKey: "sys.oss.previewListResource", ConfigValue: "true", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "true:开启, false:关闭", }, { TenantId: tenantId, ConfigName: "上传文件类型", ConfigKey: "sys.oss.fileType", ConfigValue: "txt,md,doc,docx,pdf,xls,xlsx,ppt,pptx,txt,jpg,jpeg,png,gif,mp4,avi,mov,mp3,wav,m4a,csv,json,application/json,bin,hex,fw,zip", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "上传图片类型", ConfigKey: "sys.oss.imgType", ConfigValue: "jpg,jpeg,png,gif,webp", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "上传文件大小", ConfigKey: "sys.oss.fileSize", ConfigValue: "20M", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "上传图片大小", ConfigKey: "sys.oss.imgSize", ConfigValue: "5M", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "上传文件Url路径", ConfigKey: "sys.oss.urlPath", ConfigValue: "/data/app/iotlcmadmin-web-svc/upload|http://iotlcm.example.com:8008/upload", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "\"http://ab.com/\" 或 \"/\" 或 \"resource/upload|http://ab.com/upload\"", }, { TenantId: tenantId, ConfigName: "是否启动在线用户强制退出", ConfigKey: "sys.online.forceLogout", ConfigValue: "true", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, Remark: "true开启,false关闭", }, { TenantId: tenantId, ConfigName: "系统启用的监控通道", ConfigKey: "iot.default.channel.gb28181", ConfigValue: "0", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "报警资源匹配时间", ConfigKey: "iot.alarm.image.timesec", ConfigValue: "10", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "自主注册组织ID", ConfigKey: "iot.register.dept", ConfigValue: "0", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "自主注册角色ID", ConfigKey: "iot.register.role", ConfigValue: "0", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "系统默认MQTT渠道", ConfigKey: "iot.default.channel.mqtt", ConfigValue: "0", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "系统默认TCP渠道", ConfigKey: "iot.default.channel.tcp", ConfigValue: "0", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "系统默认UDP渠道", ConfigKey: "iot.default.channel.udp", ConfigValue: "0", ConfigType: "N", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, { TenantId: tenantId, ConfigName: "项目API默认地址", ConfigKey: "iot.project.api.default.addr", ConfigValue: "http://127.0.0.1:8510", ConfigType: "Y", CreatedDept: createdDept, CreatedBy: createdBy, CreatedAt: now, }, } ids = make([]int64, 0, len(configs)) configDao := dao.SysConfig.Ctx(ctx).TX(tx) for _, config := range configs { id, insertErr := configDao.Data(config).InsertAndGetId() if insertErr != nil { return nil, insertErr } ids = append(ids, id) } return ids, nil } // 同步租户菜单 func (l *sSysTenant) SyncTenantMenu(ctx context.Context, tenantId string) (err error) { tenantInfo, err := l.View(ctx, &model.SysTenantViewParam{ TenantId: tenantId, }) if err != nil { g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysTenant().View err: %v, tenantId: %s", err, tenantId) return err } tenantPackage, err := service.SysTenantPackage().View(ctx, &model.SysTenantPackageViewParam{ PackageId: tenantInfo.PackageId, }) if err != nil { g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysTenantPackage().View err: %v, tenantId: %s, packageId: %d", err, tenantId, tenantInfo.PackageId) return err } if tenantPackage == nil { return errors.New("租户套餐不存在") } menuIds := strings.Split(tenantPackage.MenuIds, ",") // 获取角色对应菜单 roleMenuList, err := service.SysRole().GetRoleMenu(ctx, tenantInfo.AdminRoleId) if err != nil { g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRole().GetRoleMenu err: %v, tenantId: %s, roleId: %d", err, tenantId, tenantInfo.AdminRoleId) return err } insertMenuIds := make([]do.SysRoleMenu, 0) for _, menuId := range menuIds { found := false for _, roleMenu := range roleMenuList { if roleMenu.MenuId == gconv.Int64(menuId) { found = true break } } if !found { insertMenuIds = append(insertMenuIds, do.SysRoleMenu{ RoleId: tenantInfo.AdminRoleId, MenuId: gconv.Int64(menuId), }) } } delMenuIds := make([]int64, 0) for _, roleMenu := range roleMenuList { if !slices.Contains(menuIds, gconv.String(roleMenu.MenuId)) { delMenuIds = append(delMenuIds, roleMenu.MenuId) continue } } if len(delMenuIds) > 0 { _, err = dao.SysRoleMenu.Ctx(ctx).WhereIn(dao.SysRoleMenu.Columns().RoleId, tenantInfo.AdminRoleId). WhereIn(dao.SysRoleMenu.Columns().MenuId, delMenuIds).Delete() if err != nil { g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRoleMenu.Ctx(ctx).Delete err: %v, tenantId: %s, roleId: %d, delMenuIds: %v", err, tenantId, tenantInfo.AdminRoleId, delMenuIds) return err } } if len(insertMenuIds) > 0 { _, err = dao.SysRoleMenu.Ctx(ctx).Data(insertMenuIds).Insert() if err != nil { g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRoleMenu.Ctx(ctx).Insert err: %v, tenantId: %s, roleId: %d, insertMenuIds: %v", err, tenantId, tenantInfo.AdminRoleId, insertMenuIds) return err } } return nil } // 查询用户ID是否是租户管理员 func (l *sSysTenant) IsTenantAdmin(ctx context.Context, userIds []int64) (isAdminUserIds []int64, err error) { entityList := make([]*entity.SysTenant, 0) err = dao.SysTenant.Ctx(ctx).WhereIn(dao.SysTenant.Columns().AdminUserId, userIds).Scan(&entityList) if err != nil && err != sql.ErrNoRows { return nil, err } isAdminUserIds = make([]int64, 0) for _, entity := range entityList { isAdminUserIds = append(isAdminUserIds, entity.AdminUserId) } return isAdminUserIds, nil }