// Admin.NET 项目的版æƒã€å•†æ ‡ã€ä¸“利和其他相关æƒåˆ©å‡å—ç›¸åº”æ³•å¾‹æ³•è§„çš„ä¿æŠ¤ã€‚ä½¿ç”¨æœ¬é¡¹ç›®åº”éµå®ˆç›¸å…³æ³•律法规和许å¯è¯çš„è¦æ±‚。 // // 本项目主è¦éµå¾ª MIT 许å¯è¯å’Œ Apache 许å¯è¯ï¼ˆç‰ˆæœ¬ 2.0)进行分å‘和使用。许å¯è¯ä½äºŽæºä»£ç æ ‘æ ¹ç›®å½•ä¸çš„ LICENSE-MIT å’Œ LICENSE-APACHE 文件。 // // ä¸å¾—利用本项目从事å±å®³å›½å®¶å®‰å…¨ã€æ‰°ä¹±ç¤¾ä¼šç§©åºã€ä¾µçŠ¯ä»–äººåˆæ³•æƒç›Šç‰æ³•å¾‹æ³•è§„ç¦æ¢çš„æ´»åЍï¼ä»»ä½•基于本项目二次开å‘è€Œäº§ç”Ÿçš„ä¸€åˆ‡æ³•å¾‹çº çº·å’Œè´£ä»»ï¼Œæˆ‘ä»¬ä¸æ‰¿æ‹…ä»»ä½•è´£ä»»ï¼ namespace Admin.NET.Core.Service; /// <summary> /// ç³»ç»Ÿç§Ÿæˆ·ç®¡ç†æœåŠ¡ 🧩 /// </summary> [ApiDescriptionSettings(Order = 390)] public class SysTenantService : IDynamicApiController, ITransient { private readonly SqlSugarRepository<SysTenant> _sysTenantRep; private readonly SqlSugarRepository<SysOrg> _sysOrgRep; private readonly SqlSugarRepository<SysRole> _sysRoleRep; private readonly SqlSugarRepository<SysPos> _sysPosRep; private readonly SqlSugarRepository<SysUser> _sysUserRep; private readonly SqlSugarRepository<SysUserExtOrg> _sysUserExtOrgRep; private readonly SqlSugarRepository<SysRoleMenu> _sysRoleMenuRep; private readonly SqlSugarRepository<SysUserRole> _userRoleRep; private readonly SysUserRoleService _sysUserRoleService; private readonly SysRoleMenuService _sysRoleMenuService; private readonly SysConfigService _sysConfigService; private readonly SysCacheService _sysCacheService; public SysTenantService(SqlSugarRepository<SysTenant> sysTenantRep, SqlSugarRepository<SysOrg> sysOrgRep, SqlSugarRepository<SysRole> sysRoleRep, SqlSugarRepository<SysPos> sysPosRep, SqlSugarRepository<SysUser> sysUserRep, SqlSugarRepository<SysUserExtOrg> sysUserExtOrgRep, SqlSugarRepository<SysRoleMenu> sysRoleMenuRep, SqlSugarRepository<SysUserRole> userRoleRep, SysUserRoleService sysUserRoleService, SysRoleMenuService sysRoleMenuService, SysConfigService sysConfigService, SysCacheService sysCacheService) { _sysTenantRep = sysTenantRep; _sysOrgRep = sysOrgRep; _sysRoleRep = sysRoleRep; _sysPosRep = sysPosRep; _sysUserRep = sysUserRep; _sysUserExtOrgRep = sysUserExtOrgRep; _sysRoleMenuRep = sysRoleMenuRep; _userRoleRep = userRoleRep; _sysUserRoleService = sysUserRoleService; _sysRoleMenuService = sysRoleMenuService; _sysConfigService = sysConfigService; _sysCacheService = sysCacheService; } /// <summary> /// 获å–租户分页列表 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [DisplayName("获å–租户分页列表")] public async Task<SqlSugarPagedList<TenantOutput>> Page(PageTenantInput input) { return await _sysTenantRep.AsQueryable() .LeftJoin<SysUser>((u, a) => u.UserId == a.Id) .LeftJoin<SysOrg>((u, a, b) => u.OrgId == b.Id) .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), (u, a) => a.Phone.Contains(input.Phone.Trim())) .WhereIF(!string.IsNullOrWhiteSpace(input.Name), (u, a, b) => b.Name.Contains(input.Name.Trim())) //.Where(u => u.Id.ToString() != SqlSugarConst.MainConfigId) // 排除默认主库/主租户 .OrderBy(u => new { u.OrderNo, u.Id }) .Select((u, a, b) => new TenantOutput { Id = u.Id, OrgId = b.Id, Name = b.Name, UserId = a.Id, AdminAccount = a.Account, Phone = a.Phone, Email = a.Email, TenantType = u.TenantType, DbType = u.DbType, Connection = u.Connection, ConfigId = u.ConfigId, OrderNo = u.OrderNo, Remark = u.Remark, Status = u.Status, CreateTime = u.CreateTime, CreateUserName = u.CreateUserName, UpdateTime = u.UpdateTime, UpdateUserName = u.UpdateUserName, }) .ToPagedListAsync(input.Page, input.PageSize); } /// <summary> /// 获å–库隔离的租户列表 /// </summary> /// <returns></returns> [NonAction] public async Task<List<SysTenant>> GetTenantDbList() { return await _sysTenantRep.GetListAsync(u => u.TenantType == TenantTypeEnum.Db && u.Status == StatusEnum.Enable); } /// <summary> /// å¢žåŠ ç§Ÿæˆ· 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [UnitOfWork] [ApiDescriptionSettings(Name = "Add"), HttpPost] [DisplayName("å¢žåŠ ç§Ÿæˆ·")] public async Task AddTenant(AddTenantInput input) { var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1300); isExist = await _sysUserRep.AsQueryable().ClearFilter().AnyAsync(u => u.Account == input.AdminAccount); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1301); // 从库é…ç½®åˆ¤æ– if (!string.IsNullOrWhiteSpace(input.SlaveConnections) && !JSON.IsValid(input.SlaveConnections)) throw Oops.Oh(ErrorCodeEnum.D1302); switch (input.TenantType) { // Id隔离时设置与主库一致 case TenantTypeEnum.Id: var config = _sysTenantRep.AsSugarClient().CurrentConnectionConfig; input.DbType = config.DbType; input.Connection = config.ConnectionString; break; case TenantTypeEnum.Db: if (string.IsNullOrWhiteSpace(input.Connection)) throw Oops.Oh(ErrorCodeEnum.Z1004); break; default: throw Oops.Oh(ErrorCodeEnum.D3004); } var tenant = input.Adapt<TenantOutput>(); await _sysTenantRep.InsertAsync(tenant); await InitNewTenant(tenant); await CacheTenant(); } /// <summary> /// è®¾ç½®ç§Ÿæˆ·çŠ¶æ€ ðŸ”– /// </summary> /// <param name="input"></param> /// <returns></returns> [DisplayName("设置租户状æ€")] public async Task<int> SetStatus(TenantInput input) { var tenant = await _sysTenantRep.GetFirstAsync(u => u.Id == input.Id); if (tenant == null || tenant.ConfigId == SqlSugarConst.MainConfigId) throw Oops.Oh(ErrorCodeEnum.Z1001); if (!Enum.IsDefined(typeof(StatusEnum), input.Status)) throw Oops.Oh(ErrorCodeEnum.D3005); tenant.Status = input.Status; return await _sysTenantRep.AsUpdateable(tenant).UpdateColumns(u => new { u.Status }).ExecuteCommandAsync(); } /// <summary> /// 新增租户åˆå§‹åŒ– /// </summary> /// <param name="tenant"></param> private async Task InitNewTenant(TenantOutput tenant) { var tenantId = tenant.Id; var tenantName = tenant.Name; // åˆå§‹åŒ–机构 var newOrg = new SysOrg { TenantId = tenantId, Pid = 0, Name = tenantName, Code = tenantName, Remark = tenantName, }; await _sysOrgRep.InsertAsync(newOrg); // åˆå§‹åŒ–角色 var newRole = new SysRole { TenantId = tenantId, Name = "租管-" + tenantName, Code = CommonConst.SysAdminRole, DataScope = DataScopeEnum.All, Remark = tenantName }; await _sysRoleRep.InsertAsync(newRole); // åˆå§‹åŒ–èŒä½ var newPos = new SysPos { TenantId = tenantId, Name = "租管-" + tenantName, Code = tenantName, Remark = tenantName, }; await _sysPosRep.InsertAsync(newPos); // åˆå§‹åŒ–ç³»ç»Ÿè´¦å· var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword); var newUser = new SysUser { TenantId = tenantId, Account = tenant.AdminAccount, Password = CryptogramUtil.Encrypt(password), NickName = "租管", Email = tenant.Email, Phone = tenant.Phone, AccountType = AccountTypeEnum.SysAdmin, OrgId = newOrg.Id, PosId = newPos.Id, Birthday = DateTime.Parse("2000-01-01"), RealName = "租管", Remark = "租管" + tenantName, }; await _sysUserRep.InsertAsync(newUser); // å…³è”用户åŠè§’色 var newUserRole = new SysUserRole { RoleId = newRole.Id, UserId = newUser.Id }; await _userRoleRep.InsertAsync(newUserRole); // å…³è”租户组织机构和管ç†å‘˜ç”¨æˆ· await _sysTenantRep.UpdateAsync(u => new SysTenant() { UserId = newUser.Id, OrgId = newOrg.Id }, u => u.Id == tenantId); // 默认租户管ç†å‘˜è§’色èœå•é›†åˆ var menuIdList = new List<long> { 1300000000111,1300000000121, // å·¥ä½œå° 1310000000111,1310000000112,1310000000113,1310000000114,1310000000115,1310000000116,1310000000117,1310000000118,1310000000119,1310000000120, // è´¦å· 1310000000131,1310000000132,1310000000133,1310000000134,1310000000135,1310000000136,1310000000137,1310000000138, // 角色 1310000000141,1310000000142,1310000000143,1310000000144,1310000000145, // 机构 1310000000151,1310000000152,1310000000153,1310000000154,1310000000155, // èŒä½ 1310000000161,1310000000162,1310000000163,1310000000164, // 个人ä¸å¿ƒ 1310000000171,1310000000172,1310000000173,1310000000174,1310000000175,1310000000176,1310000000177 // 通知公告 }; await _sysRoleMenuService.GrantRoleMenu(new RoleMenuInput() { Id = newRole.Id, MenuIdList = menuIdList }); } /// <summary> /// åˆ é™¤ç§Ÿæˆ· 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [ApiDescriptionSettings(Name = "Delete"), HttpPost] [DisplayName("åˆ é™¤ç§Ÿæˆ·")] public async Task DeleteTenant(DeleteTenantInput input) { // ç¦æ¢åˆ 除默认租户 if (input.Id.ToString() == SqlSugarConst.MainConfigId) throw Oops.Oh(ErrorCodeEnum.D1023); // 若账å·ä¸ºå¼€æ”¾æŽ¥å£ç»‘å®šç§Ÿæˆ·åˆ™ç¦æ¢åˆ 除 var isOpenAccessTenant = await _sysTenantRep.ChangeRepository<SqlSugarRepository<SysOpenAccess>>().IsAnyAsync(u => u.BindTenantId == input.Id); if (isOpenAccessTenant) throw Oops.Oh(ErrorCodeEnum.D1031); await _sysTenantRep.DeleteAsync(u => u.Id == input.Id); await CacheTenant(input.Id); // åˆ é™¤ä¸Žç§Ÿæˆ·ç›¸å…³çš„è¡¨æ•°æ® var users = await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.Id).ToListAsync(); var userIds = users.Select(u => u.Id).ToList(); await _sysUserRep.AsDeleteable().Where(u => userIds.Contains(u.Id)).ExecuteCommandAsync(); await _userRoleRep.AsDeleteable().Where(u => userIds.Contains(u.UserId)).ExecuteCommandAsync(); await _sysUserExtOrgRep.AsDeleteable().Where(u => userIds.Contains(u.UserId)).ExecuteCommandAsync(); await _sysRoleRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); var roleIds = await _sysRoleRep.AsQueryable().ClearFilter() .Where(u => u.TenantId == input.Id).Select(u => u.Id).ToListAsync(); await _sysRoleMenuRep.AsDeleteable().Where(u => roleIds.Contains(u.RoleId)).ExecuteCommandAsync(); await _sysOrgRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); await _sysPosRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync(); } /// <summary> /// 更新租户 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [ApiDescriptionSettings(Name = "Update"), HttpPost] [DisplayName("更新租户")] public async Task UpdateTenant(UpdateTenantInput input) { var isExist = await _sysOrgRep.IsAnyAsync(u => u.Name == input.Name && u.Id != input.OrgId); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1300); isExist = await _sysUserRep.IsAnyAsync(u => u.Account == input.AdminAccount && u.Id != input.UserId); if (isExist) throw Oops.Oh(ErrorCodeEnum.D1301); // Id隔离时设置与主库一致 switch (input.TenantType) { case TenantTypeEnum.Id: var config = _sysTenantRep.AsSugarClient().CurrentConnectionConfig; input.DbType = config.DbType; input.Connection = config.ConnectionString; break; case TenantTypeEnum.Db: if (string.IsNullOrWhiteSpace(input.Connection)) throw Oops.Oh(ErrorCodeEnum.Z1004); break; default: throw Oops.Oh(ErrorCodeEnum.D3004); } // 从库é…ç½®åˆ¤æ– if (!string.IsNullOrWhiteSpace(input.SlaveConnections) && !JSON.IsValid(input.SlaveConnections)) throw Oops.Oh(ErrorCodeEnum.D1302); await _sysTenantRep.AsUpdateable(input.Adapt<TenantOutput>()).IgnoreColumns(true).ExecuteCommandAsync(); // 更新系统机构 await _sysOrgRep.UpdateAsync(u => new SysOrg() { Name = input.Name }, u => u.Id == input.OrgId); // 更新系统用户 await _sysUserRep.UpdateAsync(u => new SysUser() { Account = input.AdminAccount, Phone = input.Phone, Email = input.Email }, u => u.Id == input.UserId); await CacheTenant(input.Id); } /// <summary> /// 授æƒç§Ÿæˆ·ç®¡ç†å‘˜è§’色èœå• 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [UnitOfWork] [DisplayName("授æƒç§Ÿæˆ·ç®¡ç†å‘˜è§’色èœå•")] public async Task GrantMenu(RoleMenuInput input) { // 获å–租户管ç†å‘˜è§’色ã€sys_admin】 var adminRole = await _sysRoleRep.AsQueryable().ClearFilter() .FirstAsync(u => u.Code == CommonConst.SysAdminRole && u.TenantId == input.Id && u.IsDelete == false); if (adminRole == null) return; input.Id = adminRole.Id; // é‡ç½®ç§Ÿæˆ·ç®¡ç†å‘˜è§’色Id await _sysRoleMenuService.GrantRoleMenu(input); } /// <summary> /// 获å–租户管ç†å‘˜è§’色拥有èœå•Idé›†åˆ ðŸ”– /// </summary> /// <param name="input"></param> /// <returns></returns> [DisplayName("获å–租户管ç†å‘˜è§’色拥有èœå•Id集åˆ")] public async Task<List<long>> GetOwnMenuList([FromQuery] TenantUserInput input) { var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.UserId); return await _sysRoleMenuService.GetRoleMenuIdList(new List<long> { roleIds[0] }); } /// <summary> /// é‡ç½®ç§Ÿæˆ·ç®¡ç†å‘˜å¯†ç 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [DisplayName("é‡ç½®ç§Ÿæˆ·ç®¡ç†å‘˜å¯†ç ")] public async Task<string> ResetPwd(TenantUserInput input) { var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword); var encryptPassword = CryptogramUtil.Encrypt(password); await _sysUserRep.UpdateAsync(u => new SysUser() { Password = encryptPassword }, u => u.Id == input.UserId); return password; } /// <summary> /// ç¼“å˜æ‰€æœ‰ç§Ÿæˆ· /// </summary> /// <param name="tenantId"></param> /// <returns></returns> [NonAction] public async Task CacheTenant(long tenantId = 0) { // 移除 ISqlSugarClient ä¸çš„库连接并排除默认主库 if (tenantId > 0 && tenantId.ToString() != SqlSugarConst.MainConfigId) _sysTenantRep.AsTenant().RemoveConnection(tenantId); var tenantList = await _sysTenantRep.GetListAsync(); // 对租户库连接进行SM2åŠ å¯† foreach (var tenant in tenantList) { if (!string.IsNullOrWhiteSpace(tenant.Connection)) tenant.Connection = CryptogramUtil.SM2Encrypt(tenant.Connection); } _sysCacheService.Set(CacheConst.KeyTenant, tenantList); } /// <summary> /// 创建租户数æ®åº“ 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [ApiDescriptionSettings(Name = "CreateDb"), HttpPost] [DisplayName("创建租户数æ®åº“")] public async Task CreateDb(TenantInput input) { var tenant = await _sysTenantRep.GetSingleAsync(u => u.Id == input.Id); if (tenant == null) return; if (tenant.DbType == SqlSugar.DbType.Oracle) throw Oops.Oh(ErrorCodeEnum.Z1002); if (string.IsNullOrWhiteSpace(tenant.Connection) || tenant.Connection.Length < 10) throw Oops.Oh(ErrorCodeEnum.Z1004); // 默认数æ®åº“é…ç½® var defaultConfig = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(); var config = new DbConnectionConfig { ConfigId = tenant.Id.ToString(), DbType = tenant.DbType, ConnectionString = tenant.Connection, DbSettings = new DbSettings() { EnableInitDb = true, EnableDiffLog = false, EnableUnderLine = defaultConfig.DbSettings.EnableUnderLine, } }; SqlSugarSetup.InitTenantDatabase(App.GetRequiredService<ISqlSugarClient>().AsTenant(), config); } /// <summary> /// 获å–租户下的用户列表 🔖 /// </summary> /// <param name="input"></param> /// <returns></returns> [DisplayName("获å–租户下的用户列表")] public async Task<List<SysUser>> UserList(TenantIdInput input) { return await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.TenantId).ToListAsync(); } /// <summary> /// 获å–租户数æ®åº“连接 /// </summary> /// <returns></returns> [NonAction] public SqlSugarScopeProvider GetTenantDbConnectionScope(long tenantId) { var iTenant = _sysTenantRep.AsTenant(); // 若已å˜åœ¨ç§Ÿæˆ·åº“连接,则直接返回 if (iTenant.IsAnyConnection(tenantId.ToString())) return iTenant.GetConnectionScope(tenantId.ToString()); lock (iTenant) { // 从缓å˜é‡Œé¢èŽ·å–ç§Ÿæˆ·ä¿¡æ¯ var tenant = _sysCacheService.Get<List<SysTenant>>(CacheConst.KeyTenant)?.FirstOrDefault(u => u.Id == tenantId); if (tenant == null || tenant.TenantType == TenantTypeEnum.Id) return null; // 获å–默认库连接é…ç½® var dbOptions = App.GetOptions<DbConnectionOptions>(); var mainConnConfig = dbOptions.ConnectionConfigs.First(u => u.ConfigId.ToString() == SqlSugarConst.MainConfigId); // 设置租户库连接é…ç½® var tenantConnConfig = new DbConnectionConfig { ConfigId = tenant.Id.ToString(), DbType = tenant.DbType, IsAutoCloseConnection = true, ConnectionString = CryptogramUtil.SM2Decrypt(tenant.Connection), // 对租户库连接进行SM2解密 DbSettings = new DbSettings() { EnableUnderLine = mainConnConfig.DbSettings.EnableUnderLine, }, SlaveConnectionConfigs = JSON.IsValid(tenant.SlaveConnections) ? JSON.Deserialize<List<SlaveConnectionConfig>>(tenant.SlaveConnections) : null // 从库连接é…ç½® }; iTenant.AddConnection(tenantConnConfig); var sqlSugarScopeProvider = iTenant.GetConnectionScope(tenantId.ToString()); SqlSugarSetup.SetDbConfig(tenantConnConfig); SqlSugarSetup.SetDbAop(sqlSugarScopeProvider, dbOptions.EnableConsoleSql); return sqlSugarScopeProvider; } } }