DigitalFactory/Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs

458 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// 大名科技(天津)有限公司版权所有 电话18020030720 QQ515096995
//
// 此源代码遵循位于源代码树根目录中的 LICENSE 文件的许可证
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()))
.OrderBy(u => u.OrderNo)
.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,
})
.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);
// ID隔离时设置与主库一致
if (input.TenantType == TenantTypeEnum.Id)
{
var config = _sysTenantRep.AsSugarClient().CurrentConnectionConfig;
input.DbType = config.DbType;
input.Connection = config.ConnectionString;
}
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>(CommonConst.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);
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);
// 从库配置判断
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)
{
var tenantAdminUser = await _sysUserRep.GetFirstAsync(u => u.TenantId == input.Id && u.AccountType == AccountTypeEnum.SysAdmin);
if (tenantAdminUser == null) return;
var roleIds = await _sysUserRoleService.GetUserRoleIdList(tenantAdminUser.Id);
input.Id = roleIds[0]; // 重置租户管理员角色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>(CommonConst.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)
{
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)?.First(u => u.Id == tenantId);
if (tenant == null) 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;
}
}
}