添加项目文件。
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System.Services.Auth;
|
||||
|
||||
/// <inheritdoc cref="IAuthService"/>
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ISysUserService _userService;
|
||||
private readonly ISysOrgService _sysOrgService;
|
||||
|
||||
public AuthService(ISimpleCacheService simpleCacheService, IEventPublisher eventPublisher, IConfigService configService,
|
||||
ISysUserService userService, ISysOrgService sysOrgService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_eventPublisher = eventPublisher;
|
||||
_configService = configService;
|
||||
_userService = userService;
|
||||
_sysOrgService = sysOrgService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<PicValidCodeOutPut> GetCaptchaInfo()
|
||||
{
|
||||
var config = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_CAPTCHA_TYPE);
|
||||
var captchaType = (CaptchaType)Enum.Parse(typeof(CaptchaType), config.ConfigValue);
|
||||
//生成验证码
|
||||
var captchaInfo = CaptchaUtil.CreateCaptcha(captchaType, 4, 100, 38);
|
||||
//生成请求号,并将验证码放入缓存
|
||||
var reqNo = AddValidCodeToRedis(captchaInfo.Code);
|
||||
//返回验证码和请求号
|
||||
return new PicValidCodeOutPut
|
||||
{
|
||||
ValidCodeBase64 = captchaInfo.Base64Str,
|
||||
ValidCodeReqNo = reqNo
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<string> GetPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await ValidPhoneValidCode(input, loginClientType);//校验手机号验证码
|
||||
var phoneValidCode = RandomHelper.CreateNum(6);//生产随机数字;
|
||||
|
||||
|
||||
#region 发送短信和记录数据库等操作
|
||||
|
||||
//这里为什么不封装阿里云或者腾讯云短信接口,是因为短信发送是一个通用功能,一般每个公司都有自己封装好的短信组件来适配各种项目,这里再封装一次显得多余了。
|
||||
//这里不建议在该系统中封装短信发送接口,当然后续如果确实有需要也会增加
|
||||
//这里执行发送短信验证码代码,这里先默认就是年月日
|
||||
phoneValidCode = DateTime.Now.ToString("yyMMdd");
|
||||
|
||||
#endregion 发送短信和记录数据库等操作
|
||||
|
||||
//生成请求号,并将验证码放入缓存
|
||||
var reqNo = AddValidCodeToRedis(phoneValidCode);
|
||||
return reqNo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginOutPut> Login(LoginInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await CheckCaptcha(input);//检查验证码
|
||||
await CheckWebOpen(input);//检查网站是否开启
|
||||
var password = CryptogramUtil.Sm2Decrypt(input.Password);//SM2解密
|
||||
//获取多租户配置
|
||||
var isTenant = await _configService.IsTenant();
|
||||
//获取登录策略
|
||||
var loginPolicy = await _configService.GetConfigsByCategory(CateGoryConst.CONFIG_LOGIN_POLICY);
|
||||
await BeforeLogin(loginPolicy, input, isTenant);//登录前校验
|
||||
// 根据账号获取用户信息,根据B端或C端判断
|
||||
if (loginClientType == LoginClientTypeEnum.B)//如果是B端
|
||||
{
|
||||
var userInfo = await _userService.GetUserByAccount(input.Account, input.TenantId);//获取用户信息
|
||||
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
|
||||
if (userInfo.Password != password)
|
||||
{
|
||||
LoginError(loginPolicy, input, isTenant);//登录错误操作
|
||||
throw Oops.Bah("账号密码错误");//账号密码错误
|
||||
}
|
||||
var result = await ExecLoginB(userInfo, input.Device, loginClientType, input.TenantId);// 执行B端登录
|
||||
return result;
|
||||
}
|
||||
//执行c端登录
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginOutPut> LoginByPhone(LoginByPhoneInput input, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
await ValidPhoneValidCode(input, loginClientType, false);//校验手机号和验证码,这里不删除Redis,防止输入错误又要重新输入验证码
|
||||
// 根据手机号获取用户信息,根据B端或C端判断
|
||||
if (loginClientType == LoginClientTypeEnum.B)//如果是B端
|
||||
{
|
||||
var userInfo = await _userService.GetUserByPhone(input.Phone);//获取信息
|
||||
if (userInfo == null) throw Oops.Bah("用户不存在");//用户不存在
|
||||
var result = await ExecLoginB(userInfo, input.Device, loginClientType, input.TenantId);// 执行B端登录
|
||||
RemoveValidCodeFromRedis(input.ValidCodeReqNo);//删除验证码
|
||||
return result;
|
||||
}
|
||||
//执行c端登录
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task LoginOut(string token, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取用户信息
|
||||
var userinfo = await _userService.GetUserByAccount(UserManager.UserAccount, UserManager.TenantId);
|
||||
if (userinfo != null)
|
||||
{
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
|
||||
SysUser = userinfo,
|
||||
Token = token
|
||||
};
|
||||
RemoveTokenFromRedis(loginEvent, loginClientType);//移除token
|
||||
//发布登出事件总线
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.LOGIN_OUT_B, loginEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<LoginUserOutput> GetLoginUser()
|
||||
{
|
||||
var userInfo = await _userService.GetUserByAccount(UserManager.UserAccount, UserManager.TenantId);//根据账号获取用户信息
|
||||
if (userInfo != null)
|
||||
{
|
||||
userInfo.Avatar = await _userService.GetUserAvatar(userInfo.Id);//获取头像
|
||||
return userInfo.Adapt<LoginUserOutput>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查验证码
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="AppFriendlyException"></exception>
|
||||
public async Task CheckCaptcha(LoginInput input)
|
||||
{
|
||||
//判断是否有验证码
|
||||
var sysBase = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_CAPTCHA_OPEN);
|
||||
if (sysBase != null)//如果有这个配置项
|
||||
{
|
||||
if (sysBase.ConfigValue.ToBoolean())//如果需要验证码
|
||||
{
|
||||
//如果没填验证码,提示验证码不能为空
|
||||
if (string.IsNullOrEmpty(input.ValidCode) || string.IsNullOrEmpty(input.ValidCodeReqNo))
|
||||
throw Oops.Bah("验证码不能为空").StatusCode(410);
|
||||
ValidValidCode(input.ValidCode, input.ValidCodeReqNo);//校验验证码
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查网站是否开启
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <exception cref="AppFriendlyException"></exception>
|
||||
public async Task CheckWebOpen(LoginInput input)
|
||||
{
|
||||
//判断是否开启web访问
|
||||
var webStatus = await _configService.GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_WEB_STATUS);
|
||||
if (webStatus != null && webStatus.ConfigValue == CommonStatusConst.DISABLED
|
||||
&& input.Account.ToLower() != SysRoleConst.SUPER_ADMIN.ToLower())//如果禁用了网站并且不是超级管理员
|
||||
{
|
||||
var closePrompt = await _configService.GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_WEB_CLOSE_PROMPT);
|
||||
throw Oops.Bah(closePrompt.ConfigValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录之前执行的方法
|
||||
/// </summary>
|
||||
/// <param name="loginPolicy"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="isTenant"></param>
|
||||
public async Task BeforeLogin(List<SysConfig> loginPolicy, LoginInput input, bool isTenant)
|
||||
{
|
||||
var lockTime = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_LOCK).ConfigValue.ToInt();//获取锁定时间
|
||||
var errorCount = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_COUNT).ConfigValue.ToInt();//获取错误次数
|
||||
var key = SystemConst.CACHE_LOGIN_ERROR_COUNT;//获取登录错误次数Key值
|
||||
//如果是关闭多租户则直接用账号
|
||||
if (!isTenant)
|
||||
key += input.Account;
|
||||
else
|
||||
{
|
||||
//如果租户ID为空表示用域名登录
|
||||
if (input.TenantId == null)
|
||||
{
|
||||
//获取域名
|
||||
var origin = App.HttpContext.Request.Headers["Origin"].ToString();
|
||||
// 如果Origin头不存在,可以尝试使用Referer头作为备选
|
||||
if (string.IsNullOrEmpty(origin))
|
||||
origin = App.HttpContext.Request.Headers["Referer"].ToString();
|
||||
//根据域名获取二级域名
|
||||
var domain = origin.Split("//")[1].Split(".")[0];
|
||||
//根据二级域名获取租户
|
||||
var tenantList = await _sysOrgService.GetTenantList();
|
||||
var tenant = tenantList.FirstOrDefault(x => x.Code.ToLower() == domain);//获取租户默认是机构编码
|
||||
if (tenant != null)
|
||||
input.TenantId = tenant.Id;
|
||||
else
|
||||
throw Oops.Bah("租户不存在");
|
||||
}
|
||||
//如果是手动选择多租户则用账号+租户
|
||||
key += $"{input.TenantId}:{input.Account}";
|
||||
}
|
||||
var errorCountCache = _simpleCacheService.Get<int>(key);//获取登录错误次数
|
||||
if (errorCountCache >= errorCount)
|
||||
{
|
||||
_simpleCacheService.SetExpire(key, TimeSpan.FromMinutes(lockTime));//设置缓存
|
||||
throw Oops.Bah($"密码错误次数过多,请{lockTime}分钟后再试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录错误操作
|
||||
/// </summary>
|
||||
/// <param name="loginPolicy"></param>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="isTenant"></param>
|
||||
public void LoginError(List<SysConfig> loginPolicy, LoginInput input, bool isTenant)
|
||||
{
|
||||
var resetTime = loginPolicy.First(x => x.ConfigKey == SysConfigConst.LOGIN_ERROR_RESET_TIME).ConfigValue.ToInt();//获取重置时间
|
||||
var key = SystemConst.CACHE_LOGIN_ERROR_COUNT;//获取登录错误次数Key值
|
||||
//如果是关闭多租户则直接用账号
|
||||
if (!isTenant)
|
||||
key += input.Account;
|
||||
else
|
||||
{
|
||||
//如果是手动选择多租户则用账号+租户
|
||||
key += $"{input.TenantId}:{input.Account}";
|
||||
}
|
||||
_simpleCacheService.Increment(key, 1);// 登录错误次数+1
|
||||
_simpleCacheService.SetExpire(key, TimeSpan.FromMinutes(resetTime));//设置过期时间
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验验证码方法
|
||||
/// </summary>
|
||||
/// <param name="validCode">验证码</param>
|
||||
/// <param name="validCodeReqNo">请求号</param>
|
||||
/// <param name="isDelete">是否从Redis删除</param>
|
||||
public void ValidValidCode(string validCode, string validCodeReqNo, bool isDelete = true)
|
||||
{
|
||||
var key = SystemConst.CACHE_CAPTCHA + validCodeReqNo;//获取验证码Key值
|
||||
var code = _simpleCacheService.Get<string>(key);//从缓存拿数据
|
||||
if (isDelete) RemoveValidCodeFromRedis(validCodeReqNo);//如果需要删除验证码
|
||||
if (code != null && validCode != null)//如果有
|
||||
{
|
||||
//验证码如果不匹配直接抛错误,这里忽略大小写
|
||||
if (validCode.ToLower() != code.ToLower()) throw Oops.Bah("验证码错误");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah("验证码不能为空");//抛出验证码不能为空
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Redis中删除验证码
|
||||
/// </summary>
|
||||
/// <param name="validCodeReqNo"></param>
|
||||
public void RemoveValidCodeFromRedis(string validCodeReqNo)
|
||||
{
|
||||
var key = SystemConst.CACHE_CAPTCHA + validCodeReqNo;//获取验证码Key值
|
||||
_simpleCacheService.Remove(key);//删除验证码
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 校验手机验证码
|
||||
/// </summary>
|
||||
/// <param name="input">输入参数</param>
|
||||
/// <param name="loginClientType">登录端类型</param>
|
||||
/// <param name="isDelete">是否删除</param>
|
||||
/// <returns></returns>
|
||||
public async Task ValidPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType, bool isDelete = true)
|
||||
{
|
||||
ValidValidCode(input.ValidCode, input.ValidCodeReqNo, isDelete);//校验验证码
|
||||
if (loginClientType == LoginClientTypeEnum.B)//B端登录
|
||||
{
|
||||
var userId = await _userService.GetIdByPhone(input.Phone);//获取用户名
|
||||
if (userId == 0)
|
||||
{
|
||||
throw Oops.Bah("手机号不存在");//手机号不存在
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//校验C段用户手机号
|
||||
throw Oops.Bah("手机号不存在");//手机号不存在
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加验证码到缓存
|
||||
/// </summary>
|
||||
/// <param name="code">验证码</param>
|
||||
/// <param name="expire">过期时间</param>
|
||||
/// <returns>验证码请求号</returns>
|
||||
public string AddValidCodeToRedis(string code, int expire = 5)
|
||||
{
|
||||
//生成请求号
|
||||
var reqNo = CommonUtils.GetSingleId().ToString();
|
||||
//插入缓存
|
||||
_simpleCacheService.Set(SystemConst.CACHE_CAPTCHA + reqNo, code, TimeSpan.FromMinutes(expire));
|
||||
return reqNo;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行B端登录
|
||||
/// </summary>
|
||||
/// <param name="sysUser">用户信息</param>
|
||||
/// <param name="device">登录设备</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <param name="tenantId">租户id</param>
|
||||
/// <returns></returns>
|
||||
public async Task<LoginOutPut> ExecLoginB(SysUser sysUser, AuthDeviceTypeEnum device, LoginClientTypeEnum loginClientType,
|
||||
long? tenantId)
|
||||
{
|
||||
if (sysUser.Status == CommonStatusConst.DISABLED)
|
||||
throw Oops.Bah("账号已停用");//账号冻结
|
||||
if (sysUser.ModuleList.Count == 0) throw Oops.Bah("该账号未分配模块,请联系管理员");//没有分配菜单权限
|
||||
var org = await _sysOrgService.GetSysOrgById(sysUser.OrgId);//获取机构
|
||||
if (org.Status == CommonStatusConst.DISABLED) throw Oops.Bah("所属公司/部门已停用,请联系管理员");//机构冻结
|
||||
//生成Token
|
||||
var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{
|
||||
ClaimConst.USER_ID, sysUser.Id
|
||||
},
|
||||
{
|
||||
ClaimConst.ACCOUNT, sysUser.Account
|
||||
},
|
||||
{
|
||||
ClaimConst.NAME, sysUser.Name
|
||||
},
|
||||
{
|
||||
ClaimConst.IS_SUPER_ADMIN, sysUser.RoleCodeList.Contains(SysRoleConst.SUPER_ADMIN)
|
||||
},
|
||||
{
|
||||
ClaimConst.ORG_ID, sysUser.OrgId
|
||||
},
|
||||
{
|
||||
ClaimConst.TENANT_ID, tenantId
|
||||
}
|
||||
});
|
||||
var expire = App.GetConfig<int>("JWTSettings:ExpiredTime");//获取过期时间(分钟)
|
||||
// 生成刷新Token令牌
|
||||
var refreshToken = JWTEncryption.GenerateRefreshToken(accessToken, expire * 2);
|
||||
// 设置Swagger自动登录
|
||||
App.HttpContext.SigninToSwagger(accessToken);
|
||||
// 设置响应报文头
|
||||
App.HttpContext.SetTokensOfResponseHeaders(accessToken, refreshToken);
|
||||
//登录事件参数
|
||||
var loginEvent = new LoginEvent
|
||||
{
|
||||
Ip = App.HttpContext.GetRemoteIpAddressToIPv4(),
|
||||
Device = device,
|
||||
Expire = expire,
|
||||
SysUser = sysUser,
|
||||
Token = accessToken
|
||||
};
|
||||
await WriteTokenToRedis(loginEvent, loginClientType);//写入token到缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.LOGIN_B, loginEvent);//发布登录事件总线
|
||||
//返回结果
|
||||
return new LoginOutPut
|
||||
{
|
||||
Token = accessToken,
|
||||
Account = sysUser.Account,
|
||||
Name = sysUser.Name,
|
||||
DefaultModule = sysUser.DefaultModule,
|
||||
ModuleList = sysUser.ModuleList
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 写入用户token到缓存
|
||||
/// </summary>
|
||||
/// <param name="loginEvent">登录事件参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
private async Task WriteTokenToRedis(LoginEvent loginEvent, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取token列表
|
||||
var tokenInfos = GetTokenInfos(loginEvent.SysUser.Id);
|
||||
var tokenTimeout = loginEvent.DateTime.AddMinutes(loginEvent.Expire);
|
||||
//生成token信息
|
||||
var tokenInfo = new TokenInfo
|
||||
{
|
||||
Device = loginEvent.Device.ToString(),
|
||||
Expire = loginEvent.Expire,
|
||||
TokenTimeout = tokenTimeout,
|
||||
LoginClientType = loginClientType,
|
||||
Token = loginEvent.Token
|
||||
};
|
||||
//如果缓存有数据
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
var isSingle = false;//默认不开启单用户登录
|
||||
var singleConfig = await _configService.GetByConfigKey(CateGoryConst.CONFIG_LOGIN_POLICY, SysConfigConst.LOGIN_SINGLE_OPEN);//获取系统单用户登录选项
|
||||
if (singleConfig != null)
|
||||
isSingle = singleConfig.ConfigValue.ToBoolean();//如果配置不为空则设置单用户登录选项为系统配置的值
|
||||
//判断是否单用户登录
|
||||
if (isSingle)
|
||||
{
|
||||
await SingleLogin(loginEvent.SysUser.Id.ToString(), tokenInfos.Where(it => it.LoginClientType == loginClientType).ToList());//单用户登录方法
|
||||
tokenInfos = tokenInfos.Where(it => it.LoginClientType != loginClientType).ToList();//去掉当前登录类型的token
|
||||
tokenInfos.Add(tokenInfo);//添加到列表
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenInfos.Add(tokenInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenInfos = new List<TokenInfo>
|
||||
{
|
||||
tokenInfo
|
||||
};//直接就一个
|
||||
}
|
||||
|
||||
//添加到token列表
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString(), tokenInfos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存删除用户token
|
||||
/// </summary>
|
||||
/// <param name="loginEvent">登录事件参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
private void RemoveTokenFromRedis(LoginEvent loginEvent, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
//获取token列表
|
||||
var tokenInfos = GetTokenInfos(loginEvent.SysUser.Id);
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
//获取当前用户的token
|
||||
var token = tokenInfos.Where(it => it.Token == loginEvent.Token && it.LoginClientType == loginClientType).FirstOrDefault();
|
||||
if (token != null)
|
||||
tokenInfos.Remove(token);
|
||||
if (tokenInfos.Count > 0)
|
||||
{
|
||||
//更新token列表
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString(), tokenInfos);
|
||||
}
|
||||
else
|
||||
{
|
||||
//从列表中删除
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, loginEvent.SysUser.Id.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户token列表
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>token列表</returns>
|
||||
private List<TokenInfo> GetTokenInfos(long userId)
|
||||
{
|
||||
//缓存获取用户token列表
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId.ToString());
|
||||
if (tokenInfos != null)
|
||||
{
|
||||
tokenInfos = tokenInfos.Where(it => it.TokenTimeout > DateTime.Now).ToList();//去掉登录超时的
|
||||
}
|
||||
return tokenInfos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单用户登录通知用户下线
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <param name="tokenInfos">Token列表</param>
|
||||
private async Task SingleLogin(string userId, List<TokenInfo> tokenInfos)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.USER_LOGIN_OUT, new UserLoginOutEvent
|
||||
{
|
||||
Message = "您的账号已在别处登录!",
|
||||
TokenInfos = tokenInfos,
|
||||
UserId = userId
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 权限认证输入
|
||||
/// </summary>
|
||||
public class AuthInput
|
||||
{
|
||||
}
|
||||
|
||||
public class ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证码
|
||||
/// </summary>
|
||||
public string ValidCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 请求号
|
||||
/// </summary>
|
||||
public string ValidCodeReqNo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录输入参数
|
||||
/// </summary>
|
||||
public class LoginInput : ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
/// <example>superAdmin</example>
|
||||
[Required(ErrorMessage = "账号不能为空")]
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
///</summary>
|
||||
///<example>04fc514b346f14b23d7cf5e6f64663b030512aa380a9e7d311288ed1e8be7b863ae5ee0bb570df2405fc9daff2b2d1ac627a0fbbd49ef2c6b8fac4fd5e4b9a1b7120999bdc0a8e425aa37abab3aec6f9f3570775ff901f2845e957b0c2d0542e21fbf1bbb65c04</example>
|
||||
[Required(ErrorMessage = "密码不能为空")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备类型,默认PC
|
||||
/// </summary>
|
||||
/// <example>0</example>
|
||||
public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC;
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public long? TenantId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登出输入参数
|
||||
/// </summary>
|
||||
public class LoginOutInput
|
||||
{
|
||||
/// <summary>
|
||||
/// token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取短信验证码输入
|
||||
/// </summary>
|
||||
public class GetPhoneValidCodeInput : ValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 手机号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "手机号不能为空")]
|
||||
[Phone(ErrorMessage = "手机号格式不对")]
|
||||
public string Phone { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手机号登录输入
|
||||
/// </summary>
|
||||
public class LoginByPhoneInput : GetPhoneValidCodeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 设备类型,默认PC
|
||||
/// </summary>
|
||||
/// <example>0</example>
|
||||
public AuthDeviceTypeEnum Device { get; set; } = AuthDeviceTypeEnum.PC;
|
||||
|
||||
/// <summary>
|
||||
/// 租户ID
|
||||
/// </summary>
|
||||
public long? TenantId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class AuthOutPut
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证码返回
|
||||
/// </summary>
|
||||
public class PicValidCodeOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证码图片,Base64
|
||||
/// </summary>
|
||||
public string ValidCodeBase64 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 验证码请求号
|
||||
/// </summary>
|
||||
public string ValidCodeReqNo { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录返回参数
|
||||
/// </summary>
|
||||
public class LoginOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 令牌Token
|
||||
/// </summary>
|
||||
public string Token { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 默认模块
|
||||
/// </summary>
|
||||
public long? DefaultModule { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块列表
|
||||
/// </summary>
|
||||
public List<SysResource> ModuleList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 登录用互信息
|
||||
/// </summary>
|
||||
public class LoginUserOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 头像
|
||||
///</summary>
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签名
|
||||
///</summary>
|
||||
public string Signature { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
///</summary>
|
||||
public string Nickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号
|
||||
/// </summary>
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生日
|
||||
/// </summary>
|
||||
public string Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮箱
|
||||
/// </summary>
|
||||
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
///</summary>
|
||||
public string Gender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭住址
|
||||
///</summary>
|
||||
public string HomeAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构信息
|
||||
/// </summary>
|
||||
public string OrgName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构信息全称
|
||||
/// </summary>
|
||||
public string OrgNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位信息
|
||||
/// </summary>
|
||||
public string PositionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色码集合
|
||||
/// </summary>
|
||||
public List<string> RoleCodeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按钮码集合
|
||||
/// </summary>
|
||||
public List<string> ButtonCodeList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构及以下机构ID集合
|
||||
/// </summary>
|
||||
public List<SysResource> ModuleList { get; set; } = new List<SysResource>();
|
||||
|
||||
/// <summary>
|
||||
/// 默认模块
|
||||
///</summary>
|
||||
public long? DefaultModule { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 权限校验服务
|
||||
/// </summary>
|
||||
public interface IAuthService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取图形验证码
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<PicValidCodeOutPut> GetCaptchaInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户信息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<LoginUserOutput> GetLoginUser();
|
||||
|
||||
/// <summary>
|
||||
/// 获取手机短信验证码
|
||||
/// </summary>
|
||||
/// <param name="input">验证码参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <returns></returns>
|
||||
Task<string> GetPhoneValidCode(GetPhoneValidCodeInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 登录
|
||||
/// </summary>
|
||||
/// <param name="input">登录参数</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
/// <returns>Token信息</returns>
|
||||
Task<LoginOutPut> Login(LoginInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 手机号登录
|
||||
/// </summary>
|
||||
/// <param name="input">登录参数</param>
|
||||
/// <param name="loginClientType">登录端类型</param>
|
||||
/// <returns>Token信息</returns>
|
||||
Task<LoginOutPut> LoginByPhone(LoginByPhoneInput input, LoginClientTypeEnum loginClientType);
|
||||
|
||||
/// <summary>
|
||||
/// 退出登录
|
||||
/// </summary>
|
||||
/// <param name="token">token</param>
|
||||
/// <param name="loginClientType">登出类型</param>
|
||||
/// <returns></returns>
|
||||
Task LoginOut(string token, LoginClientTypeEnum loginClientType);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话分页查询
|
||||
/// </summary>
|
||||
public class SessionPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public virtual string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录ip
|
||||
///</summary>
|
||||
public string LatestLoginIp { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Token退出参数
|
||||
/// </summary>
|
||||
public class ExitTokenInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// token
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Tokens不能为空")]
|
||||
public List<string> Tokens { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话输出
|
||||
/// </summary>
|
||||
public class SessionOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 主键Id
|
||||
/// </summary>
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 头像
|
||||
///</summary>
|
||||
public string Avatar { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
public virtual string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线状态
|
||||
/// </summary>
|
||||
public string OnlineStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录ip
|
||||
///</summary>
|
||||
public string LatestLoginIp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录地点
|
||||
///</summary>
|
||||
public string LatestLoginAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最新登录时间
|
||||
///</summary>
|
||||
public DateTime? LatestLoginTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 令牌数量
|
||||
/// </summary>
|
||||
public int TokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 令牌信息集合
|
||||
/// </summary>
|
||||
public List<TokenInfo> TokenSignList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 会话统计结果
|
||||
/// </summary>
|
||||
public class SessionAnalysisOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 当前会话总数量
|
||||
/// </summary>
|
||||
public int CurrentSessionTotalCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大签发令牌数
|
||||
/// </summary>
|
||||
public int MaxTokenCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 在线用户数
|
||||
/// </summary>
|
||||
public int OnLineCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// BC端会话比例
|
||||
/// </summary>
|
||||
public string ProportionOfBAndC { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 会话管理服务
|
||||
/// </summary>
|
||||
public interface ISessionService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 会话统计
|
||||
/// </summary>
|
||||
/// <returns>统计结果</returns>
|
||||
SessionAnalysisOutPut Analysis();
|
||||
|
||||
/// <summary>
|
||||
/// 强退会话
|
||||
/// </summary>
|
||||
/// <param name="input">用户ID</param>
|
||||
Task ExitSession(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 强退token
|
||||
/// </summary>
|
||||
/// <param name="input">token列表</param>
|
||||
Task ExitToken(ExitTokenInput input);
|
||||
|
||||
/// <summary>
|
||||
/// B端会话分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>B端会话列表</returns>
|
||||
Task<SqlSugarPagedList<SessionOutput>> PageB(SessionPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// C端会话分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>C端会话列表</returns>
|
||||
Task<SqlSugarPagedList<SessionOutput>> PageC(SessionPageInput input);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using Masuit.Tools.DateTimeExt;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ISessionService"/>
|
||||
/// </summary>
|
||||
public class SessionService : DbRepository<SysUser>, ISessionService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SessionService(ISimpleCacheService simpleCacheService, IEventPublisher eventPublisher)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SessionOutput>> PageB(SessionPageInput input)
|
||||
{
|
||||
//获取b端token列表
|
||||
var bTokenInfoDic = GetTokenDicFromRedis();
|
||||
|
||||
//获取用户ID列表
|
||||
var userIds = bTokenInfoDic.Keys.Select(it => it.ToLong()).ToList();
|
||||
var query = Context.Queryable<SysUser>().Where(it => userIds.Contains(it.Id))//根据ID查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), it => it.Name.Contains(input.Name))//根据姓名查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Account), it => it.Account.Contains(input.Account))//根据账号查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.LatestLoginIp), it => it.LatestLoginIp.Contains(input.LatestLoginIp))//根据IP查询
|
||||
.OrderBy(it => it.LatestLoginTime, OrderByType.Desc).Select<SessionOutput>().Mapper(it =>
|
||||
{
|
||||
var tokenInfos = bTokenInfoDic[it.Id.ToString()];//获取用户token信息
|
||||
GetTokenInfos(ref tokenInfos, LoginClientTypeEnum.B);//获取剩余时间
|
||||
it.TokenCount = tokenInfos.Count;//令牌数量
|
||||
it.TokenSignList = tokenInfos;//令牌列表
|
||||
//如果有mqtt客户端ID就是在线
|
||||
it.OnlineStatus = tokenInfos.Any(it => it.ClientIds.Count > 0)
|
||||
? SysDictConst.ONLINE_STATUS_ONLINE
|
||||
: SysDictConst.ONLINE_STATUS_OFFLINE;
|
||||
});
|
||||
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
pageInfo.List.OrderByDescending(it => it.TokenCount);
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SessionOutput>> PageC(SessionPageInput input)
|
||||
{
|
||||
return new SqlSugarPagedList<SessionOutput> { PageNum = 1, PageSize = 20, Total = 0, Pages = 1, HasNextPages = false };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SessionAnalysisOutPut Analysis()
|
||||
{
|
||||
var tokenDic = GetTokenDicFromRedis();//redisToken会话字典信息
|
||||
var tokenInfosList = tokenDic.Values.ToList();//端token列表
|
||||
var dicB = new Dictionary<string, List<TokenInfo>>();
|
||||
var dicC = new Dictionary<string, List<TokenInfo>>();
|
||||
var onLineCount = 0;
|
||||
foreach (var token in tokenDic)
|
||||
{
|
||||
var b = token.Value.Where(it => it.LoginClientType == LoginClientTypeEnum.B).ToList();//获取该用户B端token
|
||||
var c = token.Value.Where(it => it.LoginClientType == LoginClientTypeEnum.C).ToList();//获取该用户C端token
|
||||
if (b.Count > 0)
|
||||
dicB.Add(token.Key, b);
|
||||
if (c.Count > 0)
|
||||
dicC.Add(token.Key, c);
|
||||
var count = token.Value.Count(it => it.ClientIds.Count > 0);//计算在线用户
|
||||
onLineCount += count;
|
||||
}
|
||||
var tokenB = dicB.Values.ToList();//b端token列表
|
||||
var tokenC = dicC.Values.ToList();//c端token列表
|
||||
int maxCountB = 0, maxCountC = 0;
|
||||
if (tokenB.Count > 0)
|
||||
maxCountB = tokenB.OrderByDescending(it => it.Count).Take(1).First().Count;//b端最大会话数
|
||||
|
||||
if (tokenC.Count > 0)
|
||||
maxCountC = tokenC.OrderByDescending(it => it.Count).Take(1).First().Count;//C端最大会话数
|
||||
return new SessionAnalysisOutPut
|
||||
{
|
||||
OnLineCount = onLineCount,
|
||||
CurrentSessionTotalCount = tokenB.Count + tokenC.Count,
|
||||
MaxTokenCount = maxCountB > maxCountC ? maxCountB : maxCountC,
|
||||
ProportionOfBAndC = $"{tokenB.Count}/{tokenC.Count}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExitSession(BaseIdInput input)
|
||||
{
|
||||
var userId = input.Id.ToString();
|
||||
//token列表
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
//从列表中删除
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
await NoticeUserLoginOut(userId, tokenInfos);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExitToken(ExitTokenInput input)
|
||||
{
|
||||
var userId = input.Id.ToString();
|
||||
//获取该用户的token信息
|
||||
var tokenInfos = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);
|
||||
//当前需要踢掉用户的token
|
||||
var deleteTokens = tokenInfos.Where(it => input.Tokens.Contains(it.Token)).ToList();
|
||||
//踢掉包含token列表的token信息
|
||||
tokenInfos = tokenInfos.Where(it => !input.Tokens.Contains(it.Token)).ToList();
|
||||
if (tokenInfos.Count > 0)
|
||||
_simpleCacheService.HashAdd(CacheConst.CACHE_USER_TOKEN, userId, tokenInfos);//如果还有token则更新token
|
||||
else
|
||||
_simpleCacheService.HashDel<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, userId);//否则直接删除key
|
||||
await NoticeUserLoginOut(userId, deleteTokens);
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取redis中token信息列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, List<TokenInfo>> GetTokenDicFromRedis()
|
||||
{
|
||||
var clockSkew = App.GetConfig<int>("JWTSettings:ClockSkew");//获取过期时间容错值(秒)
|
||||
//redis获取token信息hash集合,并转成字典
|
||||
var bTokenDic = _simpleCacheService.HashGetAll<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN).ToDictionary(u => u.Key, u => u.Value);
|
||||
if (bTokenDic != null)
|
||||
{
|
||||
bTokenDic.ForEach(it =>
|
||||
{
|
||||
var tokens = it.Value.Where(it => it.TokenTimeout.AddSeconds(clockSkew) > DateTime.Now).ToList();//去掉登录超时的
|
||||
if (tokens.Count == 0)
|
||||
{
|
||||
//表示都过期了
|
||||
bTokenDic.Remove(it.Key);
|
||||
}
|
||||
else
|
||||
{
|
||||
bTokenDic[it.Key] = tokens;//重新赋值token
|
||||
}
|
||||
});
|
||||
if (bTokenDic.Count > 0)
|
||||
{
|
||||
_simpleCacheService.HashSet(CacheConst.CACHE_USER_TOKEN, bTokenDic);//如果还有token则更新token
|
||||
}
|
||||
else
|
||||
{
|
||||
_simpleCacheService.Remove(CacheConst.CACHE_USER_TOKEN);//否则直接删除key
|
||||
}
|
||||
return bTokenDic;
|
||||
}
|
||||
return new Dictionary<string, List<TokenInfo>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取token剩余时间信息
|
||||
/// </summary>
|
||||
/// <param name="tokenInfos">token列表</param>
|
||||
/// <param name="loginClientType">登录类型</param>
|
||||
public void GetTokenInfos(ref List<TokenInfo> tokenInfos, LoginClientTypeEnum loginClientType)
|
||||
{
|
||||
tokenInfos = tokenInfos.Where(it => it.LoginClientType == loginClientType).ToList();
|
||||
tokenInfos.ForEach(it =>
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
it.TokenRemain = now.GetDiffTime(it.TokenTimeout);//获取时间差
|
||||
var tokenSecond = it.TokenTimeout.AddMinutes(-it.Expire).ConvertDateTimeToLong();//颁发时间转为时间戳
|
||||
var timeoutSecond = it.TokenTimeout.ConvertDateTimeToLong();//过期时间转为时间戳
|
||||
var tokenRemainPercent =
|
||||
1 - (now.ConvertDateTimeToLong() - tokenSecond) * 1.0 / (timeoutSecond - tokenSecond);//求百分比,用现在时间-token颁布时间除以超时时间-token颁布时间
|
||||
it.TokenRemainPercent = tokenRemainPercent;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知用户下线
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task NoticeUserLoginOut(string userId, List<TokenInfo> tokenInfos)
|
||||
{
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.USER_LOGIN_OUT, new UserLoginOutEvent
|
||||
{
|
||||
Message = "您已被强制下线!",
|
||||
TokenInfos = tokenInfos,
|
||||
UserId = userId
|
||||
});//通知用户下线
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IBatchEditService"/>
|
||||
/// </summary>
|
||||
public class BatchEditService : DbRepository<BatchEdit>, IBatchEditService
|
||||
{
|
||||
private readonly ILogger<BatchEditService> _logger;
|
||||
|
||||
public BatchEditService(ILogger<BatchEditService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<BatchEdit>> Page(BatchEditPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<BatchEdit>()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.ConfigId), it => it.ConfigId.Contains(input.ConfigId.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), it => it.EntityName.Contains(input.EntityName.Trim()))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.TableName), it => it.TableName.Contains(input.TableName.Trim()))
|
||||
//.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}");
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(BatchEditAddInput input)
|
||||
{
|
||||
var entity = input.Adapt<BatchEdit>();//实体转换
|
||||
await CheckInput(entity);//检查参数
|
||||
var tableColumns = SqlSugarUtils.GetTableColumns(input.ConfigId, input.TableName);//获取表的字段信息
|
||||
var batchEdiConfig = new List<BatchEditConfig>();//代码生成配置字段集合
|
||||
|
||||
//遍历字段
|
||||
tableColumns.ForEach(it =>
|
||||
{
|
||||
//判断是否是主键或者通用字段
|
||||
var isPkOrCommon = it.IsPrimaryKey || SqlSugarUtils.IsCommonColumn(it.ColumnName);
|
||||
if (!isPkOrCommon)
|
||||
{
|
||||
//添加到字段集合
|
||||
batchEdiConfig.Add(GetUpdateBatchConfig(it));
|
||||
}
|
||||
});
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
entity = await InsertReturnEntityAsync(entity);//输入参数转实体并插入
|
||||
batchEdiConfig.ForEach(it => { it.UId = entity.Id; });//遍历字段赋值基础Id
|
||||
await Context.Insertable(batchEdiConfig).ExecuteCommandAsync();
|
||||
});
|
||||
if (!result.IsSuccess)//如果失败了
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Config(List<BatchEditConfigInput> input)
|
||||
{
|
||||
var updateBatch = input.Adapt<List<BatchEditConfig>>();//实体转换
|
||||
var configRep = ChangeRepository<DbRepository<BatchEditConfig>>();
|
||||
var ids = input.Select(it => it.Id).ToList();//获取当前配置Id
|
||||
if (ids.Any())
|
||||
{
|
||||
await configRep.DeleteAsync(it => !ids.Contains(it.Id) && it.UId == input.First().UId);//删除没有的
|
||||
await Context.Updateable(updateBatch).ExecuteCommandAsync();//更新数据
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除数据
|
||||
await Context.Deleteable<BatchEditConfig>().Where(it => ids.Contains(it.UId)).ExecuteCommandAsync();
|
||||
});
|
||||
if (!result.IsSuccess)//如果失败了
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SyncColumns(BaseIdInput input)
|
||||
{
|
||||
var config = await GetFirstAsync(it => it.Id == input.Id);
|
||||
if (config != null)
|
||||
{
|
||||
var newColumns = new List<BatchEditConfig>();
|
||||
//获取表的字段信息
|
||||
var tableColumns = SqlSugarUtils.GetTableColumns(config.ConfigId, config.TableName);
|
||||
//找到当前配置字段列表
|
||||
var batchEdiConfig = await Context.Queryable<BatchEditConfig>().Where(it => it.UId == config.Id).ToListAsync();
|
||||
foreach (var tableColumn in tableColumns)
|
||||
{
|
||||
//判断是否是主键或者通用字段
|
||||
var isPkOrCommon = tableColumn.IsPrimaryKey || SqlSugarUtils.IsCommonColumn(tableColumn.ColumnName);
|
||||
if (!isPkOrCommon)
|
||||
{
|
||||
//如果当前配置没有
|
||||
if (!batchEdiConfig.Any(it => it.ColumnName == tableColumn.ColumnName))
|
||||
{
|
||||
var netType = SqlSugarUtils.ConvertDataType(tableColumn.DataType);
|
||||
//添加到字段集合
|
||||
newColumns.Add(GetUpdateBatchConfig(tableColumn));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newColumns.Count > 0)
|
||||
{
|
||||
newColumns.ForEach(it => it.UId = config.Id);
|
||||
await Context.Insertable(newColumns).ExecuteCommandAsync();//插入新的字段数据
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<BatchEditConfig>> Columns(string code)
|
||||
{
|
||||
var batchEdiConfig = new List<BatchEditConfig>();
|
||||
var updateBatch = await GetFirstAsync(it => it.Code == code);//根据code获取配置
|
||||
if (updateBatch != null)
|
||||
{
|
||||
//找到对应字段
|
||||
batchEdiConfig = await Context.Queryable<BatchEditConfig>().Where(it => it.UId == updateBatch.Id && it.Status == CommonStatusConst.ENABLE)
|
||||
.ToListAsync();
|
||||
}
|
||||
return batchEdiConfig;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<SqlSugarTableInfo> GetTables()
|
||||
{
|
||||
return SqlSugarUtils.GetTablesByAttribute<BatchEditAttribute>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<BatchEditConfig>> ConfigList(BaseIdInput input)
|
||||
{
|
||||
return await Context.Queryable<BatchEditConfig>().Where(u => u.UId == input.Id).OrderByDescending(it => it.Status).ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Dictionary<string, object>> GetUpdateBatchConfigDict(string code, List<BatchEditColumn> columns)
|
||||
{
|
||||
var dic = new Dictionary<string, object>();
|
||||
var configs = await Columns(code);
|
||||
foreach (var item in columns)
|
||||
{
|
||||
var config = configs.Where(it => it.ColumnName == item.TableColumn).FirstOrDefault();
|
||||
if (config == null) throw Oops.Bah("不存在的列");
|
||||
dic.Add(item.TableColumn, item.ColumnValue.ToString());
|
||||
}
|
||||
return dic;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置
|
||||
/// </summary>
|
||||
/// <param name="columnInfo"></param>
|
||||
private BatchEditConfig GetUpdateBatchConfig(SqlSugarColumnInfo columnInfo)
|
||||
{
|
||||
var netType = SqlSugarUtils.ConvertDataType(columnInfo.DataType);
|
||||
return new BatchEditConfig
|
||||
{
|
||||
ColumnName = columnInfo.ColumnName,
|
||||
ColumnComment = string.IsNullOrWhiteSpace(columnInfo.ColumnDescription) ? columnInfo.ColumnName : columnInfo.ColumnDescription,
|
||||
NetType = netType,
|
||||
DataType = SqlSugarUtils.DataTypeToEff(netType),
|
||||
Status = CommonStatusConst.DISABLED
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="updateBatch"></param>
|
||||
private async Task CheckInput(BatchEdit updateBatch)
|
||||
{
|
||||
if (updateBatch.Id == 0)
|
||||
{
|
||||
var isExist = await IsAnyAsync(it => it.Code == updateBatch.Code);
|
||||
if (isExist) throw Oops.Bah("唯一编码不能重复");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 批量分页查询参数
|
||||
/// </summary>
|
||||
public class BatchEditPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一编码
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属库
|
||||
/// </summary>
|
||||
public string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体名
|
||||
/// </summary>
|
||||
public string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表名
|
||||
/// </summary>
|
||||
public string TableName { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加批量参数
|
||||
/// </summary>
|
||||
public class BatchEditAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 唯一编码
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code必填")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属库
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ConfigId必填")]
|
||||
public string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "EntityName必填")]
|
||||
public string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "TableName必填")]
|
||||
public string TableName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表描述
|
||||
/// </summary>
|
||||
public string TableDescription { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改批量参数
|
||||
/// </summary>
|
||||
public class BatchEditConfigInput : BatchEditConfig, IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (Status == CommonStatusConst.ENABLE)
|
||||
{
|
||||
//如果是api请求并且必填参数有空的
|
||||
if (DataType.Contains("api") && (string.IsNullOrEmpty(RequestUrl) || string.IsNullOrEmpty(RequestType)
|
||||
|| string.IsNullOrEmpty(RequestLabel) || string.IsNullOrEmpty(RequestValue)))
|
||||
{
|
||||
yield return new ValidationResult($"字段{ColumnName}接口信息必填", new[] { nameof(DataType) });
|
||||
}
|
||||
//如果是字典数据并且字典值为空
|
||||
if (DataType.Contains("dict") && string.IsNullOrEmpty(DictTypeCode))
|
||||
{
|
||||
yield return new ValidationResult($"字段{ColumnName}字典值必填", new[] { nameof(DictTypeCode) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量修改输入
|
||||
/// </summary>
|
||||
public class BatchEditInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量编辑Code
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空")]
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Id列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Ids不能为空")]
|
||||
public List<long>? Ids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Columns不能为空")]
|
||||
public List<BatchEditColumn>? Columns { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量修改DTO
|
||||
/// </summary>
|
||||
public class BatchEditColumn
|
||||
{
|
||||
[Required(ErrorMessage = "字段名必填")]
|
||||
public string TableColumn { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "字段值必填")]
|
||||
public object ColumnValue { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 批量服务
|
||||
/// </summary>
|
||||
public interface IBatchEditService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页结果</returns>
|
||||
Task<SqlSugarPagedList<BatchEdit>> Page(BatchEditPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 添加批量
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(BatchEditAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除批量
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取需要批量修改的表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
List<SqlSugarTableInfo> GetTables();
|
||||
|
||||
/// <summary>
|
||||
/// 获取批量修改配置
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<BatchEditConfig>> ConfigList(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 配置字段
|
||||
/// </summary>
|
||||
/// <param name="input">字段信息</param>
|
||||
/// <returns></returns>
|
||||
Task Config(List<BatchEditConfigInput> input);
|
||||
|
||||
/// <summary>
|
||||
/// 批量配置字段列表
|
||||
/// </summary>
|
||||
/// <param name="code">唯一编码</param>
|
||||
/// <returns>列表</returns>
|
||||
Task<List<BatchEditConfig>> Columns(string code);
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典配置
|
||||
/// </summary>
|
||||
/// <param name="code">唯一编码</param>
|
||||
/// <param name="columns">字段信息</param>
|
||||
/// <returns>SqlSugar对应字典</returns>
|
||||
Task<Dictionary<string, object>> GetUpdateBatchConfigDict(string code, List<BatchEditColumn> columns);
|
||||
|
||||
/// <summary>
|
||||
/// 同步字段
|
||||
/// </summary>
|
||||
/// <param name="input">id</param>
|
||||
/// <returns></returns>
|
||||
Task SyncColumns(BaseIdInput input);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 文件分页输出
|
||||
/// </summary>
|
||||
public class FilePageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件引擎
|
||||
/// </summary>
|
||||
public string Engine { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IFileService"/>
|
||||
/// </summary>
|
||||
public class FileService : DbRepository<SysFile>, IFileService
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public FileService(IConfigService configService)
|
||||
{
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysFile>> Page(FilePageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysFile>().WhereIF(!string.IsNullOrEmpty(input.Engine), it => it.Engine == input.Engine)//根据关键字查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.Id);
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<long> UploadFile(string engine, IFormFile file)
|
||||
{
|
||||
return await StorageFile(engine, file);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
var ids = input.Ids;//获取ID
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//根据ID删除数据库
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileStreamResult GetFileStreamResult(string path, string fileName, bool isPathFolder = false)
|
||||
{
|
||||
if (isPathFolder) path = path.CombinePath(fileName);
|
||||
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));//文件名转utf8不然前端下载会乱码
|
||||
//文件转流
|
||||
var result = new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream")
|
||||
{
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<FileStreamResult> GetFileStreamResultFromMinio(string objectName, string fileName)
|
||||
{
|
||||
var minioService = App.GetService<MinioUtils>();
|
||||
var stream = await minioService.DownloadFileAsync(objectName);
|
||||
|
||||
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));//文件名转utf8不然前端下载会乱码
|
||||
//文件转流
|
||||
var result = new FileStreamResult(stream, "application/octet-stream")
|
||||
{
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileStreamResult GetFileStreamResult(byte[] byteArray, string fileName)
|
||||
{
|
||||
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));//文件名转utf8不然前端下载会乱码
|
||||
//文件转流
|
||||
var result = new FileStreamResult(new MemoryStream(byteArray), "application/octet-stream")
|
||||
{
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileStreamResult> Download(BaseIdInput input)
|
||||
{
|
||||
var devFile = await GetByIdAsync(input.Id);
|
||||
if (devFile != null)
|
||||
{
|
||||
if (devFile.Engine == SysDictConst.FILE_ENGINE_LOCAL)
|
||||
return GetFileStreamResult(devFile.StoragePath, devFile.Name);
|
||||
if (devFile.Engine == SysDictConst.FILE_ENGINE_MINIO)
|
||||
return await GetFileStreamResultFromMinio(devFile.ObjName, devFile.Name);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 存储文件
|
||||
/// </summary>
|
||||
/// <param name="engine"></param>
|
||||
/// <param name="file"></param>
|
||||
private async Task<long> StorageFile(string engine, IFormFile file)
|
||||
{
|
||||
var bucketName = string.Empty;// 存储桶名称
|
||||
var storageUrl = string.Empty;// 定义存储的url,本地文件返回文件实际路径,其他引擎返回网络地址
|
||||
var objName = string.Empty;// 定义存储的url,本地文件返回文件实际路径,其他引擎返回网络地址
|
||||
|
||||
var objectId = CommonUtils.GetSingleId();//生成id
|
||||
|
||||
switch (engine)
|
||||
{
|
||||
//存储本地
|
||||
case SysDictConst.FILE_ENGINE_LOCAL:
|
||||
bucketName = "defaultBucketName";// 存储桶名称
|
||||
storageUrl = await StorageLocal(objectId, file);
|
||||
break;
|
||||
//存储本地
|
||||
case SysDictConst.FILE_ENGINE_MINIO:
|
||||
var config = await _configService.GetByConfigKey(CateGoryConst.CONFIG_FILE_MINIO, SysConfigConst.FILE_MINIO_DEFAULT_BUCKET_NAME);
|
||||
if (config != null)
|
||||
{
|
||||
bucketName = config.ConfigValue;// 存储桶名称
|
||||
(objName, storageUrl) = await StorageMinio(objectId, file);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
throw Oops.Bah("不支持的文件引擎");
|
||||
}
|
||||
var fileSizeKb = (long)(file.Length / 1024.0);// 文件大小KB
|
||||
var fileSuffix = Path.GetExtension(file.FileName).ToLower();// 文件后缀
|
||||
var devFile = new SysFile
|
||||
{
|
||||
Id = objectId,
|
||||
Engine = engine,
|
||||
Bucket = bucketName,
|
||||
Name = file.FileName,
|
||||
Suffix = fileSuffix.Split(".")[1],
|
||||
ObjName = objName,
|
||||
SizeKb = fileSizeKb,
|
||||
SizeInfo = GetSizeInfo(fileSizeKb),
|
||||
StoragePath = storageUrl
|
||||
};
|
||||
if (engine != CateGoryConst.CONFIG_FILE_LOCAL)//如果不是本地,设置下载地址
|
||||
{
|
||||
devFile.DownloadPath = storageUrl;
|
||||
}
|
||||
//如果是图片,生成缩略图
|
||||
if (IsPic(fileSuffix))
|
||||
{
|
||||
//$"data:image/png;base64," + imgByte;
|
||||
await using var fileStream = file.OpenReadStream();//获取文件流
|
||||
var image = SKImage.FromEncodedData(fileStream);//获取图片
|
||||
var bmp = SKBitmap.FromImage(image);
|
||||
var thubnail = bmp.GetPicThumbnail(100, 100);//压缩图片
|
||||
var thubnailBase64 = ImageUtil.ImgToBase64String(thubnail);//转base64
|
||||
devFile.Thumbnail = "data:image/png;base64," + thubnailBase64;
|
||||
}
|
||||
await InsertAsync(devFile);
|
||||
return objectId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储本地文件
|
||||
/// </summary>
|
||||
/// <param name="fileId"></param>
|
||||
/// <param name="file"></param>
|
||||
private async Task<string> StorageLocal(long fileId, IFormFile file)
|
||||
{
|
||||
string uploadFileFolder;
|
||||
var configKey = string.Empty;
|
||||
//判断是windows还是linux
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
configKey = SysConfigConst.FILE_LOCAL_FOLDER_FOR_UNIX;//Linux
|
||||
}
|
||||
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
configKey = SysConfigConst.FILE_LOCAL_FOLDER_FOR_WINDOWS;//Windows
|
||||
}
|
||||
//获取路径配置
|
||||
var config = await _configService.GetByConfigKey(CateGoryConst.CONFIG_FILE_LOCAL, configKey);
|
||||
if (config != null)
|
||||
{
|
||||
uploadFileFolder = config.ConfigValue;//赋值路径
|
||||
var now = DateTime.Now.ToString("d");
|
||||
var filePath = Path.Combine(uploadFileFolder, now);
|
||||
if (!Directory.Exists(filePath))//如果不存在就创建文件夹
|
||||
Directory.CreateDirectory(filePath);
|
||||
var fileSuffix = Path.GetExtension(file.FileName).ToLower();// 文件后缀
|
||||
var fileObjectName = $"{fileId}{fileSuffix}";//存储后的文件名
|
||||
var fileName = Path.Combine(filePath, fileObjectName);//获取文件全路局
|
||||
fileName = fileName.Replace("\\", "/");//格式化一系
|
||||
//存储文件
|
||||
using (var stream = File.Create(Path.Combine(filePath, fileObjectName)))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
throw Oops.Oh("文件存储路径未配置");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 存储到minio
|
||||
/// </summary>
|
||||
/// <param name="fileId"></param>
|
||||
/// <param name="file"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<(string objName, string downloadUrl)> StorageMinio(long fileId, IFormFile file)
|
||||
{
|
||||
var minioService = App.GetService<MinioUtils>();
|
||||
var now = DateTime.Now.ToString("d");
|
||||
var fileSuffix = Path.GetExtension(file.FileName).ToLower();// 文件后缀
|
||||
var fileObjectName = $"{now}/{fileId}{fileSuffix}";//存储后的文件名
|
||||
var downloadUrl = await minioService.PutObjectAsync(fileObjectName, file);
|
||||
return (fileObjectName, downloadUrl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件大小
|
||||
/// </summary>
|
||||
/// <param name="fileSizeKb"></param>
|
||||
/// <returns></returns>
|
||||
private string GetSizeInfo(long fileSizeKb)
|
||||
{
|
||||
var b = fileSizeKb * 1024;
|
||||
const int mb = 1024 * 1024;
|
||||
const int kb = 1024;
|
||||
if (b / mb >= 1)
|
||||
{
|
||||
return Math.Round(b / (float)mb, 2) + "MB";
|
||||
}
|
||||
|
||||
if (b / kb >= 1)
|
||||
{
|
||||
return Math.Round(b / (float)kb, 2) + "KB";
|
||||
}
|
||||
if (b == 0)
|
||||
{
|
||||
return "0B";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否是图片
|
||||
/// </summary>
|
||||
/// <param name="suffix">后缀名</param>
|
||||
/// <returns></returns>
|
||||
private bool IsPic(string suffix)
|
||||
{
|
||||
//图片后缀名列表
|
||||
var pics = new[]
|
||||
{
|
||||
".png", ".bmp", ".gif", ".jpg", ".jpeg", ".psd"
|
||||
};
|
||||
if (pics.Contains(suffix))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 文件管理服务
|
||||
/// </summary>
|
||||
public interface IFileService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <param name="input">ID列表</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 文件下载
|
||||
/// </summary>
|
||||
/// <param name="input">文件iD</param>
|
||||
/// <returns>文件流</returns>
|
||||
Task<FileStreamResult> Download(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取FileStreamResult文件流
|
||||
/// </summary>
|
||||
/// <param name="path">路径</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="isPathFolder">路径是否是文件夹</param>
|
||||
/// <returns></returns>
|
||||
FileStreamResult GetFileStreamResult(string path, string fileName, bool isPathFolder = false);
|
||||
|
||||
/// <summary>
|
||||
/// 获取FileStreamResult文件流
|
||||
/// </summary>
|
||||
/// <param name="byteArray">文件数组</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <returns></returns>
|
||||
FileStreamResult GetFileStreamResult(byte[] byteArray, string fileName);
|
||||
|
||||
/// <summary>
|
||||
/// 文件分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>文件列表</returns>
|
||||
Task<SqlSugarPagedList<SysFile>> Page(FilePageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 上传文件到本地返回下载url
|
||||
/// </summary>
|
||||
/// <param name="engine">文件引擎</param>
|
||||
/// <param name="file">文件</param>
|
||||
/// <returns></returns>
|
||||
Task<long> UploadFile(string engine, IFormFile file);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class MessagePageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 站内信分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送参数
|
||||
/// </summary>
|
||||
public class MessageSendInput : SysMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// 主题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Subject不能为空")]
|
||||
public override string Subject { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Category不能为空")]
|
||||
public override string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收人Id
|
||||
/// </summary>
|
||||
public List<long> ReceiverIdList { get; set; }
|
||||
|
||||
public override string Status { get; set; } = SysDictConst.MESSAGE_STATUS_READY;
|
||||
}
|
||||
|
||||
public class MessageSendUpdateInput : MessageSendInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息详情输入参数
|
||||
/// </summary>
|
||||
public class MessageDetailInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否显示接收信息
|
||||
/// </summary>
|
||||
public bool ShowReceiveInfo { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已读
|
||||
/// </summary>
|
||||
public bool Read { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 已读输入参数
|
||||
/// </summary>
|
||||
public class MessageReadInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息Id
|
||||
/// </summary>
|
||||
public List<long> Ids { get; set; } = new List<long>();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 消息详情
|
||||
/// </summary>
|
||||
public class MessageDetailOutPut : SysMessage
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未读消息数量输出
|
||||
/// </summary>
|
||||
public class MessageUnReadOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// 未读消息数量
|
||||
/// </summary>
|
||||
public int UnReadCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 站内信服务
|
||||
/// </summary>
|
||||
public interface IMessageService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 删除消息
|
||||
/// </summary>
|
||||
/// <param name="input">id列表</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除我的消息
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteMyMessage(BaseIdInput input, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息详情
|
||||
/// </summary>
|
||||
/// <param name="input">输入参数</param>
|
||||
/// <returns>消息详情</returns>
|
||||
Task<SysMessage> Detail(MessageDetailInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 我的消息列表
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysMessage>> MyMessagePage(MessagePageInput input, long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysMessage>> Page(MessagePageInput input);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取未读消息数
|
||||
/// </summary>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns></returns>
|
||||
Task<List<MessageUnReadOutPut>> UnReadCount(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 最新未读
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysMessage>> NewUnRead(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 标记已读
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> SetRead(MessageReadInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除消息
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> SetDelete(MessageReadInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 新增
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task Add(MessageSendInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task Edit(MessageSendUpdateInput input);
|
||||
}
|
||||
@@ -0,0 +1,339 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using Masuit.Tools.DateTimeExt;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IMessageService"/>
|
||||
/// </summary>
|
||||
public class MessageService : DbRepository<SysMessage>, IMessageService
|
||||
{
|
||||
private readonly ILogger<MessageService> _logger;
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public MessageService(ILogger<MessageService> logger, ISimpleCacheService simpleCacheService, IRelationService relationService,
|
||||
IEventPublisher eventPublisher)
|
||||
{
|
||||
_logger = logger;
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_relationService = relationService;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysMessage>> Page(MessagePageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysMessage>().WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey),
|
||||
it => it.Subject.Contains(input.SearchKey) || it.Content.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.CreateTime, OrderByType.Desc);
|
||||
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysMessage>> MyMessagePage(MessagePageInput input, long userId)
|
||||
{
|
||||
var query = Context.Queryable<SysMessageUser>().LeftJoin<SysMessage>((u, m) => u.MessageId == m.Id)
|
||||
.Where((u, m) => u.IsDelete == false && u.UserId == userId && u.Status == SysDictConst.MESSAGE_STATUS_ALREADY)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), (u, m) => m.Category == input.Category)//根据分类查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey),
|
||||
(u, m) => m.Subject.Contains(input.SearchKey) || m.Content.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderBy((u, m) => u.Read).OrderBy((u, m) => u.CreateTime, OrderByType.Desc).Select((u, m) => new SysMessage
|
||||
{
|
||||
Id = m.Id.SelectAll(),
|
||||
Read = u.Read
|
||||
});
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(MessageSendInput input)
|
||||
{
|
||||
CheckInput(input);
|
||||
var sysMessage = input.Adapt<SysMessage>();//实体转换
|
||||
sysMessage.Id = CommonUtils.GetSingleId();
|
||||
//如果是立即发送的直接修改状态
|
||||
if (sysMessage.SendWay == SysDictConst.SEND_WAY_NOW)
|
||||
sysMessage.Status = SysDictConst.MESSAGE_STATUS_ALREADY;//已发送
|
||||
var messageUsers = new List<SysMessageUser>();
|
||||
var userIds = new List<long>();
|
||||
//根据接收人获取需要发送的用户id
|
||||
switch (sysMessage.ReceiverType)
|
||||
{
|
||||
case SysDictConst.RECEIVER_TYPE_ALL:
|
||||
userIds = await Context.Queryable<SysUser>().Select(it => it.Id).ToListAsync();
|
||||
break;
|
||||
case SysDictConst.RECEIVER_TYPE_ROLE:
|
||||
var roleIds = sysMessage.ReceiverInfo.Select(it => it.Id.ToString()).ToList();//获取角色ID列表
|
||||
var roleUsers =
|
||||
await _relationService.GetRelationListByTargetIdListAndCategory(roleIds, CateGoryConst.RELATION_SYS_USER_HAS_ROLE);//获取角色用户列表
|
||||
userIds = roleUsers.Select(it => it.ObjectId).ToList();//获取用户ID列表
|
||||
break;
|
||||
case SysDictConst.RECEIVER_TYPE_APPOINT:
|
||||
userIds = sysMessage.ReceiverInfo.Select(it => it.Id).ToList();
|
||||
break;
|
||||
}
|
||||
//去掉自己
|
||||
userIds = userIds.Where(it => it != UserManager.UserId).ToList();
|
||||
|
||||
//遍历用户ID
|
||||
userIds.ForEach(userId =>
|
||||
{
|
||||
messageUsers.Add(new SysMessageUser
|
||||
{
|
||||
MessageId = sysMessage.Id,
|
||||
UserId = userId,
|
||||
Read = false,
|
||||
IsDelete = false,
|
||||
Status = sysMessage.SendWay == SysDictConst.SEND_WAY_NOW ? SysDictConst.MESSAGE_STATUS_ALREADY : SysDictConst.MESSAGE_STATUS_READY
|
||||
});
|
||||
});
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await InsertAsync(sysMessage);//添加消息
|
||||
await Context.Insertable(messageUsers).ExecuteCommandAsync();
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.NEW_MESSAGE, new NewMessageEvent
|
||||
{
|
||||
Id = sysMessage.Id,
|
||||
SendWay = sysMessage.SendWay,
|
||||
SendTime = sysMessage.SendTime
|
||||
});//通知用户有新的消息
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(MessageSendUpdateInput input)
|
||||
{
|
||||
CheckInput(input);
|
||||
var sysMessage = input.Adapt<SysMessage>();//实体转换
|
||||
await UpdateAsync(sysMessage);//更新数据
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysMessage> Detail(MessageDetailInput input)
|
||||
{
|
||||
//获取消息
|
||||
var message = await GetFirstAsync(it => it.Id == input.Id);
|
||||
if (message != null && message.SendWay == SysDictConst.SEND_WAY_DELAY)
|
||||
{
|
||||
//delayTime等于发送时间减掉创建时间
|
||||
message.DelayTime = (int)(message.SendTime.GetTotalSeconds() - message.CreateTime.Value.GetTotalSeconds());
|
||||
}
|
||||
if (message != null && input.ShowReceiveInfo)
|
||||
{
|
||||
var messageUserRep = ChangeRepository<DbRepository<SysMessageUser>>();//切换仓储
|
||||
var messageUsers = await messageUserRep.GetListAsync(it => it.MessageId == message.Id);
|
||||
|
||||
var userIds = messageUsers.Select(it => it.UserId).ToList();//获取用户ID列表
|
||||
var userInfos = await Context.Queryable<SysUser>().Where(it => userIds.Contains(it.Id)).Select(it => new { it.Id, it.Name })
|
||||
.ToListAsync();//获取用户姓名信息列表
|
||||
//遍历关系
|
||||
messageUsers.ForEach(messageUser =>
|
||||
{
|
||||
var user = userInfos.Where(u => u.Id == messageUser.UserId).FirstOrDefault();//获取用户信息
|
||||
if (user != null)
|
||||
{
|
||||
//添加到已读列表
|
||||
message.ReceiverDetail.Add(new ReceiverDetail
|
||||
{
|
||||
Id = user.Id,
|
||||
Name = user.Name,
|
||||
Read = messageUser.Read
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
//设置已读
|
||||
if (input.Read)
|
||||
{
|
||||
await Context.Updateable<SysMessageUser>()
|
||||
.SetColumns(it => it.Read == true)
|
||||
.SetColumns(it => it.UpdateTime == DateTime.Now)
|
||||
.Where(it => it.MessageId == input.Id && it.Read == false && it.UserId == UserManager.UserId).ExecuteCommandAsync();
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteAsync(it => ids.Contains(it.Id));
|
||||
await Context.Deleteable<SysMessageUser>().Where(it => ids.Contains(it.MessageId)).ExecuteCommandAsync();
|
||||
});
|
||||
if (!result.IsSuccess)//如果失败了
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteMyMessage(BaseIdInput input, long userId)
|
||||
{
|
||||
var messageUserRep = ChangeRepository<DbRepository<SysMessageUser>>();//切换仓储
|
||||
await Context.Deleteable<SysMessageUser>().Where(it => it.UserId == userId && it.MessageId == input.Id).IsLogic().ExecuteCommandAsync();//逻辑删除
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<MessageUnReadOutPut>> UnReadCount(long userId)
|
||||
{
|
||||
// 连表查询未读
|
||||
var unReadList = await Context.Queryable<SysMessage>().LeftJoin<SysMessageUser>((m, u) => u.MessageId == m.Id)
|
||||
.Where((m, u) => u.UserId == userId && u.Read == false && u.IsDelete == false)
|
||||
.Select<SysMessage>().ToListAsync();
|
||||
//根据消息分类分组
|
||||
var groupList = unReadList.GroupBy(it => it.Category).Select(it => new MessageUnReadOutPut
|
||||
{
|
||||
Category = it.Key,
|
||||
UnReadCount = it.Count()
|
||||
}).ToList();
|
||||
return groupList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysMessage>> NewUnRead(long userId)
|
||||
{
|
||||
//根据消息分类分组,获取每组未读前五条
|
||||
var result = await Context.Queryable<SysMessage>().LeftJoin<SysMessageUser>((m, u) => u.MessageId == m.Id)
|
||||
.Where((m, u) => u.UserId == userId && u.IsDelete == false && u.Status == SysDictConst.MESSAGE_STATUS_ALREADY)
|
||||
.Select((m, u) => new SysMessage
|
||||
{
|
||||
Index2 = SqlFunc.RowNumber($"{m.Id} desc",
|
||||
m.Category),//order by id partition by name,参考sqlsugar分组查询https://www.donet5.com/Doc/1/2243
|
||||
Subject = m.Subject,
|
||||
CreateTime = m.CreateTime,
|
||||
SendTime = m.SendTime,
|
||||
Category = m.Category,
|
||||
Read = u.Read
|
||||
})
|
||||
.MergeTable()//将结果合并成一个表
|
||||
.Where(it => it.Index2 <= 5)//取第五条
|
||||
.Mapper(it =>
|
||||
{
|
||||
it.SendTimeFormat = TimeHelper.FormatTimeAgo(it.SendTime);
|
||||
})
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> SetRead(MessageReadInput input)
|
||||
{
|
||||
//如果ID列表为空,就是全部已读
|
||||
if (input.Ids.Count == 0 && input.Category != null)
|
||||
{
|
||||
var result = await Context.Updateable<SysMessageUser>()
|
||||
.SetColumns(it => it.Read == true)
|
||||
.SetColumns(it => it.UpdateTime == DateTime.Now)
|
||||
.Where(it => it.UserId == UserManager.UserId && it.Read == false)
|
||||
.Where(it => SqlFunc.Subqueryable<SysMessage>().Where(s => s.Id == it.MessageId && s.Category == input.Category).Any())
|
||||
.ExecuteCommandAsync();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return await Context.Updateable<SysMessageUser>()
|
||||
.SetColumns(it => it.Read == true)
|
||||
.SetColumns(it => it.UpdateTime == DateTime.Now)
|
||||
.Where(it => it.UserId == UserManager.UserId && input.Ids.Contains(it.MessageId))
|
||||
.Where(it => it.Read == false)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> SetDelete(MessageReadInput input)
|
||||
{
|
||||
//如果ID列表为空,就是全部删除
|
||||
if (input.Ids.Count == 0 && input.Category != null)
|
||||
{
|
||||
var result = await Context.Updateable<SysMessageUser>()
|
||||
.SetColumns(it => it.IsDelete == true)
|
||||
.SetColumns(it => it.UpdateTime == DateTime.Now)
|
||||
.Where(it => it.UserId == UserManager.UserId && it.IsDelete == false)
|
||||
.Where(it => SqlFunc.Subqueryable<SysMessage>().Where(s => s.Id == it.MessageId && s.Category == input.Category).Any())
|
||||
.ExecuteCommandAsync();
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return await Context.Updateable<SysMessageUser>()
|
||||
.SetColumns(it => it.IsDelete == true)
|
||||
.SetColumns(it => it.UpdateTime == DateTime.Now)
|
||||
.Where(it => it.UserId == UserManager.UserId && input.Ids.Contains(it.MessageId))
|
||||
.Where(it => it.IsDelete == false)
|
||||
.ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
private void CheckInput(MessageSendInput input)
|
||||
{
|
||||
if (input.Status == SysDictConst.MESSAGE_STATUS_ALREADY)
|
||||
throw Oops.Oh("已发送的消息不能修改");
|
||||
switch (input.SendWay)
|
||||
{
|
||||
case SysDictConst.SEND_WAY_NOW:
|
||||
input.SendTime = DateTime.Now;
|
||||
break;
|
||||
case SysDictConst.SEND_WAY_DELAY:
|
||||
if (input.CreateTime != null)
|
||||
input.SendTime = input.CreateTime.Value.AddSeconds(input.DelayTime);
|
||||
else
|
||||
input.SendTime = DateTime.Now.AddSeconds(input.DelayTime);
|
||||
break;
|
||||
case SysDictConst.SEND_WAY_SCHEDULE:
|
||||
if (input.SendTime < DateTime.Now)
|
||||
throw Oops.Oh("发送时间不能小于当前时间");
|
||||
input.SendTime = input.SendTime;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成基础添加参数
|
||||
/// </summary>
|
||||
public class GenBasicAddInput : GenBasic, IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 所属库名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ConfigId不能为空")]
|
||||
public override string ConfigId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主表名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "BbTable不能为空")]
|
||||
public override string DbTable { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "FuncList不能为空")]
|
||||
public override List<string>? FuncList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成模版
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ModuleType不能为空")]
|
||||
public override string ModuleType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据权限
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "DataPermission不能为空")]
|
||||
public override string DataPermission { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 实体名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "EntityName不能为空")]
|
||||
public override string EntityName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表前缀移除
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "TablePrefix不能为空")]
|
||||
public override string TablePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生成方式
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "GenerateType不能为空")]
|
||||
public override string GenerateType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属模块
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Module不能为空")]
|
||||
public override long Module { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路由名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "RouteName不能为空")]
|
||||
public override string RouteName { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Icon不能为空")]
|
||||
public override string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "FunctionName不能为空")]
|
||||
public override string FunctionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "BusName不能为空")]
|
||||
public override string BusName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 类名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ClassName不能为空")]
|
||||
public override string ClassName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 表单布局
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "FormLayout不能为空")]
|
||||
public override string FormLayout { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 使用栅格
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "GridWhether不能为空")]
|
||||
public override string GridWhether { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 左侧树
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "LeftTree不能为空")]
|
||||
public override string LeftTree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 前端路径
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "FrontedPath不能为空")]
|
||||
public override string FrontedPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务层
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ServicePosition不能为空")]
|
||||
public override string ServicePosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 控制器层
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ControllerPosition不能为空")]
|
||||
public override string ControllerPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "SortCode不能为空")]
|
||||
public override int SortCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 作者名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "AuthorName不能为空")]
|
||||
public override string AuthorName { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
//如果是树形结构
|
||||
if (ModuleType.Contains("tree"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(TreeId) || string.IsNullOrEmpty(TreeName) || string.IsNullOrEmpty(TreePid))
|
||||
yield return new ValidationResult($"必须配置树表相关信息", new[] { nameof(TreeId), nameof(TreeName), nameof(TreePid) });
|
||||
}
|
||||
//如果是树形结构
|
||||
if (ModuleType.Contains("master"))
|
||||
{
|
||||
if (string.IsNullOrEmpty(ChildFk) || string.IsNullOrEmpty(ChildTable))
|
||||
yield return new ValidationResult($"必须配置主子表相关信息", new[] { nameof(ChildTable), nameof(ChildFk) });
|
||||
}
|
||||
if (!FuncList.Contains("curd"))
|
||||
{
|
||||
yield return new ValidationResult($"必须包含基础的增删改查功能", new[] { nameof(FuncList) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成基础编辑参数
|
||||
/// </summary>
|
||||
public class GenBasicEditInput : GenBasicAddInput
|
||||
{
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行代码生成参数
|
||||
/// </summary>
|
||||
public class ExecGenInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成类型
|
||||
/// </summary>
|
||||
public string ExecType { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 表字段信息输出
|
||||
/// </summary>
|
||||
public class GenBasicColumnOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 字段名称
|
||||
/// </summary>
|
||||
public string ColumnName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否主键
|
||||
/// </summary>
|
||||
public bool IsPrimarykey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段类型
|
||||
/// </summary>
|
||||
public string TypeName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字段注释
|
||||
/// </summary>
|
||||
public string ColumnRemark { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 预览代码生成结果
|
||||
/// </summary>
|
||||
public class GenBasePreviewOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// SQL代码结果集
|
||||
/// </summary>
|
||||
public List<GenBaseCodeResult> SqlResults { get; set; } = new List<GenBaseCodeResult>();
|
||||
|
||||
/// <summary>
|
||||
/// 前端代码结果集
|
||||
/// </summary>
|
||||
public List<GenBaseCodeResult> CodeFrontendResults { get; set; } = new List<GenBaseCodeResult>();
|
||||
|
||||
/// <summary>
|
||||
/// 后端代码结果集
|
||||
/// </summary>
|
||||
|
||||
public List<GenBaseCodeResult> CodeBackendResults { get; set; } = new List<GenBaseCodeResult>();
|
||||
|
||||
public class GenBaseCodeResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 代码文件名称
|
||||
/// </summary>
|
||||
public string CodeFileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 代码文件内容
|
||||
/// </summary>
|
||||
public string CodeFileContent { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
using StringHelper = SimpleTool.StringHelper;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成绑定视图
|
||||
/// </summary>
|
||||
public class GenViewModel : GenBasic
|
||||
{
|
||||
#region 基础
|
||||
|
||||
/// <summary>
|
||||
/// 生成时间
|
||||
/// </summary>
|
||||
public string GenTime { get; set; } = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/// <summary>
|
||||
/// 表字段
|
||||
/// </summary>
|
||||
public List<GenConfig> TableFields { get; set; }
|
||||
|
||||
#endregion 基础
|
||||
|
||||
#region 菜单
|
||||
|
||||
/// <summary>
|
||||
/// 菜单ID
|
||||
/// </summary>
|
||||
public long MenuId { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
/// <summary>
|
||||
/// 菜单编码
|
||||
/// </summary>
|
||||
public string MenuCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// /菜单路径
|
||||
/// </summary>
|
||||
public string MenuPath
|
||||
{
|
||||
get { return $"/{RouteName}/{BusName}"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// /菜单路径
|
||||
/// </summary>
|
||||
public string MenuComponent
|
||||
{
|
||||
get { return $"{RouteName}/{BusName}/index"; }
|
||||
}
|
||||
|
||||
#endregion 菜单
|
||||
|
||||
#region 按钮
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮ID
|
||||
/// </summary>
|
||||
public long AddButtonId { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
/// <summary>
|
||||
/// 批量删除按钮ID
|
||||
/// </summary>
|
||||
public long BatchDeleteButtonId { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
/// <summary>
|
||||
/// 编辑按钮ID
|
||||
/// </summary>
|
||||
public long EditButtonId { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
/// <summary>
|
||||
/// 删除按钮ID
|
||||
/// </summary>
|
||||
public long DeleteButtonId { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
#endregion 按钮
|
||||
|
||||
|
||||
#region 后端
|
||||
|
||||
/// <summary>
|
||||
/// 类名首字母小写
|
||||
/// </summary>
|
||||
public string ClassNameFirstLower
|
||||
{
|
||||
get { return StringHelper.FirstCharToLower(ClassName); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// swagger分组名称
|
||||
/// </summary>
|
||||
public string ApiGroup
|
||||
{
|
||||
get
|
||||
{
|
||||
return ServicePosition.Split(".").Last();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务名
|
||||
/// </summary>
|
||||
public string ServiceName
|
||||
{
|
||||
get { return ClassName + "Service"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务名开头首字母小写
|
||||
/// </summary>
|
||||
public string ServiceFirstLower
|
||||
{
|
||||
get { return ClassNameFirstLower + "Service"; }
|
||||
}
|
||||
|
||||
#endregion 后端
|
||||
|
||||
#region 注释描述
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
public string DescriptionPage
|
||||
{
|
||||
get { return FunctionName + "分页查询"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 列表查询
|
||||
/// </summary>
|
||||
public string DescriptionList
|
||||
{
|
||||
get { return FunctionName + "列表查询"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加
|
||||
/// </summary>
|
||||
public string DescriptionAdd
|
||||
{
|
||||
get { return "添加" + FunctionName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改
|
||||
/// </summary>
|
||||
public string DescriptionEdit
|
||||
{
|
||||
get { return "修改" + FunctionName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改
|
||||
/// </summary>
|
||||
public string DescriptionDelete
|
||||
{
|
||||
get { return "删除" + FunctionName; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 详情
|
||||
/// </summary>
|
||||
public string DescriptionDetail
|
||||
{
|
||||
get { return FunctionName + "详情"; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 导入
|
||||
/// </summary>
|
||||
public string DescriptionImport
|
||||
{
|
||||
get { return FunctionName + "导入"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入模板下载
|
||||
/// </summary>
|
||||
public string DescriptionTemplate
|
||||
{
|
||||
get { return FunctionName + "导入模板下载"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入预览
|
||||
/// </summary>
|
||||
public string DescriptionPreview
|
||||
{
|
||||
get { return FunctionName + "导入预览"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 导入
|
||||
/// </summary>
|
||||
public string DescriptionExport
|
||||
{
|
||||
get { return FunctionName + "导出"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量编辑
|
||||
/// </summary>
|
||||
public string DescriptionEdits
|
||||
{
|
||||
get { return FunctionName + "批量编辑"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 树查询
|
||||
/// </summary>
|
||||
public string DescriptionTree
|
||||
{
|
||||
get { return FunctionName + "树"; }
|
||||
}
|
||||
|
||||
#endregion 注释描述
|
||||
|
||||
#region 参数
|
||||
|
||||
/// <summary>
|
||||
/// 分页参数
|
||||
/// </summary>
|
||||
public string PageInput
|
||||
{
|
||||
get { return ClassName + "PageInput"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加参数
|
||||
/// </summary>
|
||||
public string AddInput
|
||||
{
|
||||
get { return ClassName + "AddInput"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑参数
|
||||
/// </summary>
|
||||
public string EditInput
|
||||
{
|
||||
get { return ClassName + "EditInput"; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 树查询参数
|
||||
/// </summary>
|
||||
public string TreeInput
|
||||
{
|
||||
get { return ClassName + "TreeInput"; }
|
||||
}
|
||||
|
||||
#endregion 参数
|
||||
}
|
||||
@@ -0,0 +1,808 @@
|
||||
using StringHelper = SimpleTool.StringHelper;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IGenbasicService"/>
|
||||
/// </summary>
|
||||
public class GenBasicService : DbRepository<GenBasic>, IGenbasicService
|
||||
{
|
||||
private readonly ILogger<GenBasicService> _logger;
|
||||
private readonly IViewEngine _viewEngine;
|
||||
private readonly IGenConfigService _genConfigService;
|
||||
private readonly IMenuService _menuService;
|
||||
private readonly IButtonService _buttonService;
|
||||
private readonly ISysRoleService _roleService;
|
||||
private readonly string _sqlDir = "Sql";
|
||||
private readonly string _backendDir = "Backend";
|
||||
private readonly string _frontDir = "Frontend";
|
||||
private const string _interfaceDir = "interface";
|
||||
private const string _modulesDir = "modules";
|
||||
private const string _indexTs = "index.ts";
|
||||
|
||||
public GenBasicService(ILogger<GenBasicService> logger,
|
||||
IViewEngine viewEngine,
|
||||
IGenConfigService genConfigService,
|
||||
IMenuService menuService,
|
||||
IButtonService buttonService, ISysRoleService roleService)
|
||||
{
|
||||
_logger = logger;
|
||||
_viewEngine = viewEngine;
|
||||
_genConfigService = genConfigService;
|
||||
_menuService = menuService;
|
||||
_buttonService = buttonService;
|
||||
_roleService = roleService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<GenBasic>> Page(BasePageInput input)
|
||||
{
|
||||
var query = Context.Queryable<GenBasic>()
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.SortCode, OrderByType.Desc)//默认排序
|
||||
.OrderBy(it => it.CreateTime, OrderByType.Desc)//默认排序
|
||||
.Mapper(it =>
|
||||
{
|
||||
it.FuncList = it.Functions.Split(",", StringSplitOptions.RemoveEmptyEntries).ToList();//功能集合转为列表
|
||||
});
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<SqlSugarTableInfo> GetTables(bool isAll = false)
|
||||
{
|
||||
if (isAll)
|
||||
return SqlSugarUtils.GetTablesByAttribute<SugarTable>();
|
||||
return SqlSugarUtils.GetTablesByAttribute<CodeGenAttribute>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<string> GetAssemblies()
|
||||
{
|
||||
var names = new List<string>();//结果集
|
||||
var excludeList = new List<string>
|
||||
{
|
||||
"Web.Entry", "Core", "Cache", "SqlSugar", "Plugin.Core", "Plugin.Aop"
|
||||
};//排除的程序集
|
||||
//获取所有程序集名称
|
||||
var assemblies = App.Assemblies
|
||||
.Where(it => !it.FullName.StartsWith("MoYu"))
|
||||
.Select(it => it.FullName).ToList();
|
||||
assemblies.ForEach(it =>
|
||||
{
|
||||
var name = it.Split(",")[0];//根据逗号分割取第一个
|
||||
//根据.分割获取最后两个
|
||||
|
||||
|
||||
var projectName = string.Join(".", name.Split(".").Skip(1).ToList());
|
||||
if (!excludeList.Contains(projectName))//去掉排除的程序集
|
||||
{
|
||||
names.Add(name);
|
||||
}
|
||||
});
|
||||
return names;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GenBasic> Add(GenBasicAddInput input)
|
||||
{
|
||||
var entity = input.Adapt<GenBasic>();//输入参数转实体
|
||||
entity.Functions = string.Join(",", input.FuncList);//功能集合
|
||||
var tableColumns = SqlSugarUtils.GetTableColumns(input.ConfigId, input.DbTable);//获取表的字段信息
|
||||
var genConfigs = new List<GenConfig>();//代码生成配置字段集合
|
||||
//遍历字段
|
||||
var sortCode = 0;
|
||||
//遍历字段
|
||||
tableColumns.ForEach(it =>
|
||||
{
|
||||
#region 判断是否想显示
|
||||
|
||||
var yesOrNo = GenConst.Yes;
|
||||
if (it.IsPrimaryKey || SqlSugarUtils.IsCommonColumn(it.ColumnName))//如果是主键或者是公共字段则不显示
|
||||
yesOrNo = GenConst.No;
|
||||
else
|
||||
yesOrNo = GenConst.Yes;
|
||||
|
||||
#endregion 判断是否想显示
|
||||
|
||||
//添加到字段集合
|
||||
genConfigs.Add(new GenConfig
|
||||
{
|
||||
IsPrimaryKey = it.IsPrimaryKey ? GenConst.Yes : GenConst.No,
|
||||
FieldName = it.ColumnName,
|
||||
FieldType = it.DataType,
|
||||
FieldNetType = SqlSugarUtils.ConvertDataType(it.DataType),
|
||||
FieldRemark = it.ColumnDescription ?? it.ColumnName,
|
||||
EffectType = EffTypeConst.INPUT,
|
||||
Width = 100,
|
||||
WhetherTable = yesOrNo,
|
||||
WhetherAddUpdate = yesOrNo,
|
||||
WhetherImportExport = yesOrNo,
|
||||
WhetherResizable = GenConst.No,
|
||||
WhetherRequired = GenConst.No,
|
||||
WhetherRetract = GenConst.No,
|
||||
QueryWhether = GenConst.No,
|
||||
SortCode = yesOrNo == GenConst.No ? 99 : sortCode//如果是公共字段就排最后
|
||||
});
|
||||
sortCode++;
|
||||
});
|
||||
var index = 0;
|
||||
genConfigs = genConfigs.OrderBy(it => it.SortCode).ToList();//排序
|
||||
genConfigs.ForEach(it =>
|
||||
{
|
||||
it.FieldIndex = index;
|
||||
index++;
|
||||
});
|
||||
if (!genConfigs.Any(it => it.IsPrimaryKey == GenConst.Yes))//判断是否有主键
|
||||
{
|
||||
throw Oops.Bah("要生成的表必须有主键");
|
||||
}
|
||||
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
entity = await InsertReturnEntityAsync(entity);//输入参数转实体并插入
|
||||
genConfigs.ForEach(it => { it.BasicId = entity.Id; });//遍历字段赋值基础Id
|
||||
await Context.Insertable(genConfigs).ExecuteCommandAsync();
|
||||
});
|
||||
if (!result.IsSuccess)//如果失败了
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids.ToList();
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除基础表
|
||||
await Context.Deleteable<GenConfig>().Where(it => ids.Contains(it.BasicId))
|
||||
.ExecuteCommandAsync();//删除配置表
|
||||
});
|
||||
if (!result.IsSuccess)//如果失败了
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GenBasic> Edit(GenBasicEditInput input)
|
||||
{
|
||||
var entity = input.Adapt<GenBasic>();//输入参数转实体
|
||||
entity.Functions = string.Join(",", input.FuncList);//功能集合
|
||||
await UpdateAsync(entity);
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<GenBasePreviewOutput> PreviewGen(BaseIdInput input)
|
||||
{
|
||||
var genBasic = await GetGenBasic(input.Id);//获取代码生成基础
|
||||
return await PreviewGen(genBasic);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task ExecGenPro(ExecGenInput input)
|
||||
{
|
||||
var genBasic = await GetGenBasic(input.Id);//获取代码生成基础
|
||||
if (genBasic.GenerateType != GenConst.Pro) throw Oops.Bah("当前配置生成方式为:项目中");
|
||||
var backendPath =
|
||||
Path.Combine(new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName);//获取主工程目录
|
||||
if (await CreateMenuButtonAndRelation(genBasic))
|
||||
{
|
||||
var previewCode = await PreviewGen(genBasic);//获取代码生成预览
|
||||
if (input.ExecType != GenConst.ExecAll)//如果不是全部执行
|
||||
{
|
||||
if (input.ExecType == GenConst.ExecBackend)
|
||||
ExecBackend(previewCode.CodeBackendResults);//执行后端代码生成
|
||||
else if (input.ExecType == GenConst.ExecFrontend)
|
||||
ExecFronted(previewCode.CodeFrontendResults);//执行前端代码生成
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecBackend(previewCode.CodeBackendResults);//执行后端代码生成
|
||||
|
||||
ExecFronted(previewCode.CodeFrontendResults);//执行前端代码生成
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah("代码生成失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileStreamResult> ExecGenZip(ExecGenInput input)
|
||||
{
|
||||
var genBasic = await GetGenBasic(input.Id);//获取代码生成基础
|
||||
// if (genBasic.GenerateType != GenConst.Zip) throw Oops.Bah("当前配置生成方式为:压缩包");
|
||||
var temDir = Path.GetTempPath().CombinePath(genBasic.ClassName);//获取临时目录并用类名做存放代码文件文件夹
|
||||
File.Delete(temDir + ".zip");// 先删除压缩包
|
||||
var previewCode = await PreviewGen(genBasic);//获取代码生成预览
|
||||
if (input.ExecType != GenConst.ExecAll)//如果不是全部执行
|
||||
{
|
||||
if (input.ExecType == GenConst.ExecBackend)
|
||||
{
|
||||
ExecBackend(previewCode.CodeBackendResults, temDir.CombinePath(_backendDir), true);//执行后端代码生成
|
||||
ExecSql(previewCode.SqlResults, temDir);//执行sql生成
|
||||
}
|
||||
else if (input.ExecType == GenConst.ExecFrontend)
|
||||
ExecFronted(previewCode.CodeFrontendResults, temDir.CombinePath(_frontDir));//执行前端生成
|
||||
}
|
||||
else
|
||||
{
|
||||
ExecBackend(previewCode.CodeBackendResults, temDir.CombinePath(_backendDir), true);//执行后端代码生成
|
||||
ExecFronted(previewCode.CodeFrontendResults, temDir.CombinePath(_frontDir));//执行前端生成
|
||||
ExecSql(previewCode.SqlResults, temDir);//执行sql生成
|
||||
}
|
||||
var zipPath = ZipUtils.CompressDirectory(temDir, true);//压缩文件夹
|
||||
var result = new FileStreamResult(new FileStream(zipPath, FileMode.Open), "application/octet-stream")
|
||||
{ FileDownloadName = $"{genBasic.ClassName}.zip" };
|
||||
return result;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 生成后端代码文件
|
||||
/// </summary>
|
||||
/// <param name="baseCodeResults">后端代码模板</param>
|
||||
/// <param name="genBasic">代码基础</param>
|
||||
/// <param name="backendPath">后端生成路径</param>
|
||||
/// <param name="isZip">是否是zip方式</param>
|
||||
// public void ExecBackend(List<GenBasePreviewOutput.GenBaseCodeResult> baseCodeResults, GenBasic genBasic,
|
||||
// string backendPath, bool isZip = false)
|
||||
// {
|
||||
// var serviceDir = "Services";//服务代码文件夹
|
||||
// var controllerDir = "Controllers";//服务代码文件夹
|
||||
// var tempDicName = "";//临时文件夹名称
|
||||
// var dirList = new HashSet<string> { };//文件夹列表
|
||||
// var postion = "";//文件夹位置
|
||||
// var parentDir = _backendDir;//父文件文件夹名称
|
||||
// var serviceName = genBasic.ServicePosition.Split(".").Last();//取服务层最后一个单词命名为接口层控制器下的文件夹
|
||||
// baseCodeResults.ForEach(it =>
|
||||
// {
|
||||
// var fileInfo = new FileInfo(it.FilePath);//获取文件信息
|
||||
// var dirName = fileInfo.Directory.Name;//获取文件文件夹名称
|
||||
// var parentName = fileInfo.Directory.Parent.Name;//获取父文件文件夹名称
|
||||
// if (dirName == controllerDir)//如果是控制器
|
||||
// {
|
||||
// dirList.Add(dirName);//添加到当前文件夹到文件夹列表
|
||||
// dirList.Add(serviceName);//添加服务层目录名到文件夹列表
|
||||
// postion = genBasic.ControllerPosition;//设置文件夹创建位置为Api层
|
||||
// }
|
||||
// else if (dirName == serviceDir)//如果是服务
|
||||
// {
|
||||
// dirList.Remove(controllerDir);//删除接口文件夹
|
||||
// dirList.Remove(serviceName);//删除服务层目录
|
||||
// dirList.Add(genBasic.ClassName);//添加ClassName到文件夹列表
|
||||
// dirList.Add(dirName);//添加到当前文件夹到文件夹列表
|
||||
// postion = genBasic.ServicePosition;//设置文件夹创建位置为服务层
|
||||
// }
|
||||
//
|
||||
// if (parentDir != parentName)//如果当前文件的父文件夹不等于partDir表示层级有变动
|
||||
// {
|
||||
// dirList.Add(dirName);//添加到文件夹列表
|
||||
// parentDir = parentName;//重新赋值父文件夹
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (tempDicName != dirName)//如果临时文件夹不是当前文件夹表示同级目录下的另一个文件夹
|
||||
// {
|
||||
// dirList.Remove(tempDicName);//删除上一个文件夹
|
||||
// dirList.Add(dirName);//添加当前文件夹到文件夹列表
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// tempDicName = dirName;//给临时文件夹赋值
|
||||
// var path = backendPath;//后端文件目录
|
||||
// if (!isZip) path = backendPath.CombinePath(postion);//如果不是zip方式就加上命名空间
|
||||
// path = path.CombinePath(dirList.ToArray());
|
||||
// if (!Directory.Exists(path))//如果文件夹不存在就创建文件夹
|
||||
// Directory.CreateDirectory(path);
|
||||
// //var fileName = genBasic.ClassName + it.CodeFileName;//文件名等于类名加代码文件名
|
||||
// //if (it.CodeFileName.StartsWith("IService"))
|
||||
// // fileName = $"I{genBasic.ClassName}Service.cs";//对IService接口要特殊处理
|
||||
// //path = path.CombinePath(fileName);//最终生成文件地址
|
||||
// File.WriteAllText(path.CombinePath(it.CodeFileName), it.CodeFileContent, Encoding.UTF8);//写入文件
|
||||
// });
|
||||
// }
|
||||
public void ExecBackend(List<GenBasePreviewOutput.GenBaseCodeResult> baseCodeResults, string backendPath = null, bool isZip = false)
|
||||
{
|
||||
//遍历baseCodeResults
|
||||
baseCodeResults.ForEach(it =>
|
||||
{
|
||||
var path = it.FilePath;
|
||||
//判断path所属的文件夹
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
//如果backendPath不为空,则将path设置为backendPath.CombinePath($"{it.CodeFileName}")
|
||||
if (backendPath != null)
|
||||
{
|
||||
path = backendPath.CombinePath($"{it.CodeFileName}");
|
||||
//重新获取path所属的文件夹
|
||||
dir = Path.GetDirectoryName(path);
|
||||
}
|
||||
//如果不存在就创建文件夹
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
File.WriteAllText(path, it.CodeFileContent, Encoding.UTF8);//写入文件
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成前端代码文件
|
||||
/// </summary>
|
||||
/// <param name="baseCodeResults">前端代码模板</param>
|
||||
/// <param name="frontedPath">前端生成路径</param>
|
||||
public void ExecFronted(List<GenBasePreviewOutput.GenBaseCodeResult> baseCodeResults, string frontedPath = null)
|
||||
{
|
||||
baseCodeResults.ForEach(it =>
|
||||
{
|
||||
var path = it.FilePath;
|
||||
//判断path所属的文件夹
|
||||
var dir = Path.GetDirectoryName(path);
|
||||
if (frontedPath != null)
|
||||
{
|
||||
path = frontedPath.CombinePath($"{it.CodeFileName}");
|
||||
dir = Path.GetDirectoryName(path);
|
||||
}
|
||||
|
||||
//如果不存在就创建文件夹
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
File.WriteAllText(path, it.CodeFileContent, Encoding.UTF8);//写入文件
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成sql代码文件
|
||||
/// </summary>
|
||||
/// <param name="baseCodeResults">后端代码模板</param>
|
||||
/// <param name="execPath">生成路径</param>
|
||||
public void ExecSql(List<GenBasePreviewOutput.GenBaseCodeResult> baseCodeResults, string execPath)
|
||||
{
|
||||
execPath = execPath.CombinePath(_sqlDir);//写在sql目录里面
|
||||
if (!Directory.Exists(execPath))//如果文件夹不存在就创建文件夹
|
||||
Directory.CreateDirectory(execPath);
|
||||
baseCodeResults.ForEach(it =>
|
||||
{
|
||||
File.WriteAllText(execPath.CombinePath(it.CodeFileName), it.CodeFileContent, Encoding.UTF8);//写入文件
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取sql代码预览结果
|
||||
/// </summary>
|
||||
/// <param name="genViewModel">视图</param>
|
||||
/// <param name="templatePath">模板路径</param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<GenBasePreviewOutput.GenBaseCodeResult>> GetSqlCodeResult(GenViewModel genViewModel,
|
||||
string templatePath)
|
||||
{
|
||||
var sqlCodeResults = new List<GenBasePreviewOutput.GenBaseCodeResult>();//结果集
|
||||
var sqlTemplatePath = Path.Combine(templatePath, _sqlDir);//获取sql模板文件路径
|
||||
|
||||
var files = GetAllFileInfo(sqlTemplatePath);
|
||||
foreach (var fileInfo in files)
|
||||
{
|
||||
var fileName = fileInfo.Name;//文件名
|
||||
var fileNoPrefix = fileName.Split(fileInfo.Extension)[0];//不带模板后缀的文件名
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
sqlCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = fileNoPrefix,
|
||||
FilePath = fileInfo.FullName
|
||||
});
|
||||
}
|
||||
|
||||
return sqlCodeResults;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取后端代码预览结果
|
||||
/// </summary>
|
||||
/// <param name="genViewModel">视图</param>
|
||||
/// <param name="templatePath">模板路径</param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<GenBasePreviewOutput.GenBaseCodeResult>> GetBackendCodeResult(GenViewModel genViewModel,
|
||||
string templatePath)
|
||||
{
|
||||
var backendCodeResults = new List<GenBasePreviewOutput.GenBaseCodeResult>();//结果集
|
||||
var backendTemplatePath = Path.Combine(templatePath, _backendDir);//获取后端模板文件路径
|
||||
var serviceDir = new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName.CombinePath(genViewModel.ServicePosition);//服务文件夹
|
||||
var serviceDicList = genViewModel.ServiceDictionary.Split("/").Where(it => !string.IsNullOrWhiteSpace(it)).ToList();//获取文件夹
|
||||
foreach (var item in serviceDicList)
|
||||
{
|
||||
serviceDir = serviceDir.CombinePath(item);
|
||||
}
|
||||
serviceDir = serviceDir.CombinePath(genViewModel.ClassName);
|
||||
//获取控制器文件夹和后面的路径
|
||||
var controllerDir = new DirectoryInfo(App.WebHostEnvironment.ContentRootPath).Parent.FullName.CombinePath(genViewModel.ControllerPosition);
|
||||
var controllerDicList = genViewModel.ControllerDictionary.Split("/").Where(it => !string.IsNullOrWhiteSpace(it)).ToList();
|
||||
foreach (var item in controllerDicList)
|
||||
{
|
||||
controllerDir = controllerDir.CombinePath(item);
|
||||
}
|
||||
controllerDir = controllerDir.CombinePath(genViewModel.ClassName);
|
||||
var files = GetAllFileInfo(backendTemplatePath);
|
||||
// files = files.Where(it => it.Name == "Service.cs.vm").ToArray();//测试用
|
||||
var serviceFiles = files.Where(it => it.FullName.Contains("Service.cs")).ToList();
|
||||
var controllerFiles = files.Where(it => it.FullName.Contains("Controller.cs")).ToList();
|
||||
var dtoDir = files.Where(it => it.FullName.Contains("Dto")).ToList();
|
||||
foreach (var fileInfo in serviceFiles)
|
||||
{
|
||||
var nameWithPrefix = fileInfo.Name;//文件名
|
||||
var fileNoPrefix = nameWithPrefix.Split(fileInfo.Extension)[0];//不带模板后缀的文件名
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var fileName = genViewModel.ClassName + fileNoPrefix;//文件名等于类名加代码文件名
|
||||
if (fileNoPrefix.StartsWith("IService"))
|
||||
fileName = $"I{genViewModel.ClassName}Service.cs";//对IService接口要特殊处理
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
backendCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = $"{genViewModel.ClassName}{fileNoPrefix}",
|
||||
FilePath = serviceDir.CombinePath(fileName)
|
||||
});
|
||||
}
|
||||
foreach (var fileInfo in controllerFiles)
|
||||
{
|
||||
var nameWithPrefix = fileInfo.Name;//文件名
|
||||
var fileNoPrefix = nameWithPrefix.Split(fileInfo.Extension)[0];//不带模板后缀的文件名
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var fileName = genViewModel.ClassName + fileNoPrefix;//文件名等于类名加代码文件名
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
backendCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = $"{genViewModel.ClassName}{fileNoPrefix}",
|
||||
FilePath = controllerDir.CombinePath(fileName)
|
||||
});
|
||||
}
|
||||
foreach (var fileInfo in dtoDir)
|
||||
{
|
||||
var nameWithPrefix = fileInfo.Name;//文件名
|
||||
var fileNoPrefix = nameWithPrefix.Split(fileInfo.Extension)[0];//不带模板后缀的文件名
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var fileName = genViewModel.ClassName + fileNoPrefix;//文件名等于类名加代码文件名
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
backendCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = $"{genViewModel.ClassName}{fileNoPrefix}",
|
||||
FilePath = serviceDir.CombinePath("Dto").CombinePath(fileName)
|
||||
});
|
||||
}
|
||||
return backendCodeResults;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取前端代码预览结果
|
||||
/// </summary>
|
||||
/// <param name="genViewModel">视图</param>
|
||||
/// <param name="templatePath">模板路径</param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<GenBasePreviewOutput.GenBaseCodeResult>> GetForntCodeResult(GenViewModel genViewModel,
|
||||
string templatePath)
|
||||
{
|
||||
var frontCodeResults = new List<GenBasePreviewOutput.GenBaseCodeResult>();//结果集
|
||||
var frontTemplatePath = Path.Combine(templatePath, _frontDir);//获取前端模板文件路径
|
||||
var files = GetAllFileInfo(frontTemplatePath);
|
||||
var srcDir = genViewModel.FrontedPath.CombinePath("src");//src文件夹
|
||||
var templateVm = "template.vm";//模板文件
|
||||
var view = "views";//视图文件夹
|
||||
var api = "api";//接口文件夹
|
||||
var componentsDir = "components";//组件文件夹
|
||||
var apiDir = srcDir.CombinePath(api);
|
||||
var viewsDir = srcDir.CombinePath(view);
|
||||
var template = files.Where(it => it.Name == templateVm).FirstOrDefault();
|
||||
if (templatePath == null) throw Oops.Bah($"{templateVm}文件不存在");
|
||||
var dirs = genViewModel.RouteName.Split("/").Where(it => !string.IsNullOrWhiteSpace(it)).ToList();// 根据路/分隔,获取文件夹名字
|
||||
//读取模板文件内容,utf-8编码
|
||||
var templateContent = File.ReadAllText(template.FullName, Encoding.UTF8);
|
||||
var apiFiles = files.Where(it => it.FullName.Contains(api)).ToList();
|
||||
var viewFiles = files.Where(it => it.FullName.Contains(view)).ToList();
|
||||
foreach (var fileInfo in apiFiles)
|
||||
{
|
||||
var fileName = fileInfo.Name;//文件名
|
||||
//获取文件所在文件夹名字
|
||||
var dirName = fileInfo.Directory.Name;
|
||||
var fileWhich = fileName.Split(".")[0];//获取是接口还是实现
|
||||
if (fileName == templateVm) continue;
|
||||
var fileNoPrefix = $"{fileWhich}{genViewModel.RouteName}/{genViewModel.BusName}.ts";//不带模板后缀的文件名
|
||||
var filePath = apiDir.CombinePath(fileWhich);//生成文件路径
|
||||
//以["biz","ops"]为例,先去找api/interface文件夹下的index.ts文件
|
||||
//如果有就在index.ts文件中添加export * from "./biz",然后interfacePath路径就变成了api/interface/biz
|
||||
//如果不存在这个文件夹,就表示需要创建这个文件夹,然后在这个文件夹下创建index.ts文件,导入下一层的文件也就是ops
|
||||
for (var i = 0; i < dirs.Count; i++)
|
||||
{
|
||||
var it = dirs[i];//获取文件夹名
|
||||
var indexPath = filePath.CombinePath(_indexTs);
|
||||
// 判断interfacePath路径下是否有index.ts文件,这里因为是typescript,所以需要引入
|
||||
if (File.Exists(indexPath))
|
||||
{
|
||||
//读取文件内容
|
||||
var indexContent = File.ReadAllText(indexPath);
|
||||
var content = $"export * from \"./{it}\";";//需要添加的内容
|
||||
//判断content是否存在
|
||||
if (!indexContent.Contains(content))
|
||||
{
|
||||
indexContent += content;
|
||||
}
|
||||
var pathSplit = indexPath.Split($"{fileWhich}")[1];
|
||||
//先通过\分隔再用/合并字符串
|
||||
var codeFileName = $"{fileWhich}{pathSplit}".Split("\\").Aggregate((a, b) => a + "/" + b);
|
||||
//加到结果集
|
||||
frontCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = indexContent,
|
||||
CodeFileName = codeFileName,
|
||||
FilePath = indexPath
|
||||
});
|
||||
}
|
||||
filePath = filePath.CombinePath(it);//拼接文件夹路径
|
||||
|
||||
if (!Directory.Exists(filePath))//如果文件夹不存在
|
||||
{
|
||||
var index = filePath.CombinePath(_indexTs);
|
||||
var pathSplit = index.Split($"{fileWhich}")[1];
|
||||
var indexContent = i + 1 == dirs.Count ? genViewModel.BusName : dirs[i + 1];//如果是最后一个就是busName
|
||||
// 在当前内容下添加内容
|
||||
var content = templateContent + $"\n\nexport * from \"./{indexContent}\";";
|
||||
//加到结果集
|
||||
frontCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = content,
|
||||
CodeFileName = $"{fileWhich}{pathSplit}",
|
||||
FilePath = index
|
||||
});
|
||||
}
|
||||
}
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
frontCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = fileNoPrefix,
|
||||
FilePath = filePath.CombinePath($"{genViewModel.BusName}.ts")
|
||||
});
|
||||
}
|
||||
//遍历视图文件
|
||||
foreach (var fileInfo in viewFiles)
|
||||
{
|
||||
var fileName = fileInfo.Name;//文件名
|
||||
//获取文件所在文件夹名字
|
||||
var dirName = fileInfo.Directory.Name;
|
||||
var name = fileName.Split(".vm")[0];//不带模板后缀的文件名
|
||||
var fileNoPrefix = $"{dirName}/{name}";//不带模板后缀的文件名
|
||||
var filePath = viewsDir;
|
||||
foreach (var dir in dirs)
|
||||
{
|
||||
filePath = filePath.CombinePath(dir);
|
||||
}
|
||||
filePath = filePath.CombinePath(genViewModel.BusName);
|
||||
if (dirName == componentsDir)//如果是组件文件夹
|
||||
{
|
||||
fileNoPrefix = $"{view}/{dirName}/{name}";//不带模板后缀的文件名
|
||||
filePath = filePath.CombinePath(componentsDir).CombinePath(name);//生成文件路径
|
||||
}
|
||||
else
|
||||
filePath = filePath.CombinePath(name);//生成文件路径
|
||||
var tContent = File.ReadAllText(fileInfo.FullName);//读取文件
|
||||
var tResult = await GetViewEngine(tContent, genViewModel);//渲染
|
||||
//将渲染结果添加到结果集
|
||||
frontCodeResults.Add(new GenBasePreviewOutput.GenBaseCodeResult
|
||||
{
|
||||
CodeFileContent = tResult,
|
||||
CodeFileName = fileNoPrefix,
|
||||
FilePath = filePath
|
||||
});
|
||||
}
|
||||
return frontCodeResults;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成视图
|
||||
/// </summary>
|
||||
/// <param name="genBasic"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<GenViewModel> GetGenViewModel(GenBasic genBasic)
|
||||
{
|
||||
//实体转视图
|
||||
var genViewEngine = genBasic.Adapt<GenViewModel>();
|
||||
//获取字段信息
|
||||
var tableFieldList = await _genConfigService.List(genBasic.Id);
|
||||
tableFieldList.ForEach(it =>
|
||||
{
|
||||
it.FieldNameFirstLower = StringHelper.FirstCharToLower(it.FieldName);//首字母小写
|
||||
it.FieldNameFirstUpper = StringHelper.FirstCharToUpper(it.FieldName);//首字母大写
|
||||
});
|
||||
genViewEngine.TableFields = tableFieldList;//赋值表字段信息
|
||||
|
||||
return genViewEngine;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 视图渲染
|
||||
/// </summary>
|
||||
/// <param name="tContent">模板内容</param>
|
||||
/// <param name="genViewModel">参数</param>
|
||||
/// <returns></returns>
|
||||
public async Task<string> GetViewEngine(string tContent, GenViewModel genViewModel)
|
||||
{
|
||||
//视图引擎渲染
|
||||
var tResult = await _viewEngine.RunCompileFromCachedAsync(tContent, genViewModel, builderAction: builder =>
|
||||
{
|
||||
builder.AddAssemblyReference(typeof(GenBasic));//添加程序集
|
||||
builder.AddAssemblyReferenceByName("System.Collections");//添加程序集
|
||||
builder.AddAssemblyReferenceByName("SimpleTool");//添加程序集
|
||||
});
|
||||
return tResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获得文件夹下所有的文件
|
||||
/// </summary>
|
||||
/// <param name="path">文件夹路径</param>
|
||||
/// <returns></returns>
|
||||
private FileInfo[] GetAllFileInfo(string path)
|
||||
{
|
||||
var dir = new DirectoryInfo(path);
|
||||
return dir.GetFiles(".", SearchOption.AllDirectories);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成预览
|
||||
/// </summary>
|
||||
/// <param name="genBasic">基础配置</param>
|
||||
/// <returns></returns>
|
||||
public async Task<GenBasePreviewOutput> PreviewGen(GenBasic genBasic)
|
||||
{
|
||||
var basePath = AppContext.BaseDirectory;//获取项目目录
|
||||
var templatePath = basePath.CombinePath("ViewEngine").CombinePath("CodeGen");//获取文件路径
|
||||
templatePath = "D:\\胡国东\\SimpleAdmin.Net\\SimpleAdmin\\SimpleAdmin.System\\ViewEngine\\CodeGen";//测试用
|
||||
var genViewModel = await GetGenViewModel(genBasic);
|
||||
var frontendResult = await GetForntCodeResult(genViewModel, templatePath);
|
||||
var backendResult = await GetBackendCodeResult(genViewModel, templatePath);
|
||||
var sqlResult = await GetSqlCodeResult(genViewModel, templatePath);
|
||||
return new GenBasePreviewOutput
|
||||
{
|
||||
CodeBackendResults = backendResult,
|
||||
SqlResults = sqlResult,
|
||||
CodeFrontendResults = frontendResult
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成基础
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<GenBasic> GetGenBasic(long id)
|
||||
{
|
||||
var genBasic = await GetByIdAsync(id);
|
||||
if (genBasic == null)
|
||||
throw Oops.Bah("代码生成配置不存在");
|
||||
genBasic.FuncList = genBasic.Functions.Split(',').ToList();
|
||||
return genBasic;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建代码生成相关的菜单按钮和授权关系
|
||||
/// </summary>
|
||||
/// <param name="genBasic"></param>
|
||||
/// <returns></returns>
|
||||
private async Task<bool> CreateMenuButtonAndRelation(GenBasic genBasic)
|
||||
{
|
||||
try
|
||||
{
|
||||
#region 菜单
|
||||
|
||||
var title = genBasic.FunctionName + genBasic.FunctionNameSuffix;
|
||||
var menuRep = ChangeRepository<DbRepository<SysResource>>();//切换仓储
|
||||
//获取已经存在的旧菜单
|
||||
var oldMenu = await menuRep.GetFirstAsync(it => it.Title == title
|
||||
&& it.Category == CateGoryConst.RESOURCE_MENU
|
||||
&& it.MenuType == SysResourceConst.MENU &&
|
||||
it.Code != SysResourceConst.SYSTEM);
|
||||
if (oldMenu != null)//如果存在就直接和删除(同时删除其下面的菜单、按钮,清除对应的角色与资源信息)
|
||||
await _menuService.Delete(new BaseIdListInput()
|
||||
{
|
||||
Ids = new List<long>()
|
||||
{
|
||||
oldMenu.Id
|
||||
}
|
||||
});
|
||||
//添加菜单参数
|
||||
var menu = new MenuAddInput
|
||||
{
|
||||
Id = CommonUtils.GetSingleId(),
|
||||
ParentId = genBasic.MenuPid,
|
||||
Title = title,
|
||||
Category = CateGoryConst.RESOURCE_MENU,
|
||||
Module = genBasic.Module,
|
||||
Icon = genBasic.Icon,
|
||||
Name = genBasic.BusName,
|
||||
Code = RandomHelper.CreateRandomString(10),
|
||||
Path = $"{genBasic.RouteName}/{genBasic.BusName}",
|
||||
Component = $"{genBasic.RouteName}/{genBasic.BusName}/index".TrimStart('/'),
|
||||
SortCode = 99,
|
||||
Status = CommonStatusConst.ENABLE,
|
||||
IsKeepAlive = true
|
||||
};
|
||||
await _menuService.Add(menu);//添加菜单
|
||||
|
||||
#endregion 菜单
|
||||
|
||||
#region 按钮
|
||||
|
||||
//添加按钮参数
|
||||
var button = new ButtonAddInput
|
||||
{
|
||||
Title = genBasic.FunctionName,
|
||||
ParentId = menu.Id,
|
||||
Code = StringHelper.FirstCharToLower(genBasic.ClassName),
|
||||
Status = CommonStatusConst.ENABLE
|
||||
};
|
||||
var buttonIds = await _buttonService.AddBatch(button);//添加按钮
|
||||
|
||||
#endregion 按钮
|
||||
|
||||
#region 角色授权
|
||||
|
||||
var roleRep = ChangeRepository<DbRepository<SysRole>>();//切换仓储
|
||||
var superAdmin = await roleRep.GetFirstAsync(it =>
|
||||
it.Code == SysRoleConst.SUPER_ADMIN && it.Category == CateGoryConst.ROLE_GLOBAL);//获取超级管理员
|
||||
//授权菜单参数
|
||||
var grantResource = new GrantResourceInput
|
||||
{
|
||||
Id = superAdmin.Id,
|
||||
GrantInfoList = new List<RelationRoleResource>
|
||||
{ new RelationRoleResource { MenuId = menu.Id, ButtonInfo = buttonIds } },
|
||||
IsCodeGen = true
|
||||
};
|
||||
await _roleService.GrantResource(grantResource);//授权菜单
|
||||
|
||||
#endregion 角色授权
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError($"代码生成创建菜单和授权报错{ex.Message}", ex.InnerException);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成基础服务
|
||||
/// </summary>
|
||||
public interface IGenbasicService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加代码生成器基础
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<GenBasic> Add(GenBasicAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除代码生成配置
|
||||
/// </summary>
|
||||
/// <param name="input">ID列表</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑代码生成基础配置
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns>代码生产基础</returns>
|
||||
Task<GenBasic> Edit(GenBasicEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 执行代码生成:本地
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task ExecGenPro(ExecGenInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 执行代码生成:压缩包
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<FileStreamResult> ExecGenZip(ExecGenInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取项目所有程序集
|
||||
/// </summary>
|
||||
/// <returns>程序集列表</returns>
|
||||
List<string> GetAssemblies();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取表信息
|
||||
/// </summary>
|
||||
/// <param name="isAll">是否所有表</param>
|
||||
/// <returns>实体表列表</returns>
|
||||
List<SqlSugarTableInfo> GetTables(bool isAll = false);
|
||||
|
||||
/// <summary>
|
||||
/// 获取代码生成基础分页
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>代码生成基础分页列表</returns>
|
||||
Task<SqlSugarPagedList<GenBasic>> Page(BasePageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成预览
|
||||
/// </summary>
|
||||
/// <param name="input">代码生成基础Id</param>
|
||||
/// <returns>预览结果</returns>
|
||||
Task<GenBasePreviewOutput> PreviewGen(BaseIdInput input);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class GenConfigInput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IGenConfigService"/>
|
||||
/// </summary>
|
||||
public class GenConfigService : DbRepository<GenConfig>, IGenConfigService
|
||||
{
|
||||
private readonly ILogger<GenConfigService> logger;
|
||||
|
||||
public GenConfigService(ILogger<GenConfigService> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<GenConfig>> List(long basicId)
|
||||
{
|
||||
var configs = await GetListAsync(it => it.BasicId == basicId);//获取配置表相关配置
|
||||
configs = configs.OrderBy(it => it.FieldIndex).ToList();//排序一下
|
||||
return configs;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task EditBatch(List<GenConfig> configs)
|
||||
{
|
||||
await UpdateRangeAsync(configs);//批量更新
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 代码生成详细配置服务
|
||||
/// </summary>
|
||||
public interface IGenConfigService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 批量更新
|
||||
/// </summary>
|
||||
/// <param name="configs"></param>
|
||||
/// <returns></returns>
|
||||
Task EditBatch(List<GenConfig> configs);
|
||||
|
||||
/// <summary>
|
||||
/// 查询代码生成详细配置列表
|
||||
/// </summary>
|
||||
/// <param name="basicId"></param>
|
||||
/// <returns>配置列表</returns>
|
||||
Task<List<GenConfig>> List(long basicId);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 导入预览输入
|
||||
/// </summary>
|
||||
public class ImportPreviewInput : BaseFileInput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 文件导入通用输出
|
||||
/// </summary>
|
||||
public class ImportPreviewOutput<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否有错误
|
||||
/// </summary>
|
||||
public bool HasError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态表头
|
||||
/// </summary>
|
||||
public List<TableColumns> TableColumns { get; set; } = new List<TableColumns>();
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public List<T> Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态表头
|
||||
/// </summary>
|
||||
public class TableColumns
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据源
|
||||
/// </summary>
|
||||
public string DataIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 宽度
|
||||
/// </summary>
|
||||
public int Width { get; set; } = 120;
|
||||
|
||||
/// <summary>
|
||||
/// 超过宽度将自动省略
|
||||
/// </summary>
|
||||
public bool Ellipsis { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是日期格式
|
||||
/// </summary>
|
||||
public bool Date { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 批量导入结果输入
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ImportResultInput<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public List<T> Data { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 导入结果输出
|
||||
/// </summary>
|
||||
public class ImportResultOutPut<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
|
||||
public bool Success { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 总数
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 导入数量
|
||||
/// </summary>
|
||||
public int ImportCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误数
|
||||
/// </summary>
|
||||
public int FailCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
|
||||
public List<T> Data { get; set; } = new List<T>();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 导入基础输入
|
||||
/// </summary>
|
||||
[ExcelImporter(IsLabelingError = true)]
|
||||
public class ImportTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
public long Id { get; set; } = CommonUtils.GetSingleId();
|
||||
|
||||
/// <summary>
|
||||
/// 是否有错误
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
public bool HasError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误详情
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
public IDictionary<string, string> ErrorInfo { get; set; } = new Dictionary<string, string>();
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 导入服务
|
||||
/// </summary>
|
||||
public interface IImportExportService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 导出数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <returns>文件流</returns>
|
||||
Task<FileStreamResult> Export<T>(List<T> data, string fileName) where T : class, new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地模板
|
||||
/// </summary>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <param name="templateFolder">模板文件夹路径:默认wwwroot下的Template</param>
|
||||
/// <returns>文件流</returns>
|
||||
FileStreamResult GenerateLocalTemplate(string fileName, string templateFolder = "Template");
|
||||
|
||||
/// <summary>
|
||||
/// 生成模板
|
||||
/// </summary>
|
||||
/// <typeparam name="T">实体类</typeparam>
|
||||
/// <param name="fileName">文件名</param>
|
||||
/// <returns>文件流</returns>
|
||||
Task<FileStreamResult> GenerateTemplate<T>(string fileName) where T : class, new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取导入预览
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="file">文件</param>
|
||||
/// <returns>导入预览结果</returns>
|
||||
Task<ImportPreviewOutput<T>> GetImportPreview<T>(IFormFile file) where T : ImportTemplateInput, new();
|
||||
|
||||
/// <summary>
|
||||
/// 获取预计导入结果
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="importData">成功导入数据</param>
|
||||
/// <returns>预计导入结果</returns>
|
||||
ImportResultOutPut<T> GetImportResultPreview<T>(List<T> data, out List<T> importData) where T : ImportTemplateInput;
|
||||
|
||||
/// <summary>
|
||||
/// 导入文件验证
|
||||
/// </summary>
|
||||
/// <param name="file">文件</param>
|
||||
/// <param name="maxSize">文件最大体积(M)</param>
|
||||
/// <param name="allowTypes">允许的格式</param>
|
||||
void ImportVerification(IFormFile file, int maxSize = 30, string[] allowTypes = null);
|
||||
|
||||
/// <summary>
|
||||
/// 模板数据验证
|
||||
/// </summary>
|
||||
/// <typeparam name="T">模板类实体</typeparam>
|
||||
/// <param name="importResult">结果</param>
|
||||
/// <returns>导入预览数据</returns>
|
||||
ImportPreviewOutput<T> TemplateDataVerification<T>(ImportResult<T> importResult) where T : ImportTemplateInput;
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IImportExportService"/>
|
||||
/// </summary>
|
||||
public class ImportExportService : IImportExportService
|
||||
{
|
||||
#region 导入
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ImportVerification(IFormFile file, int maxSize = 30, string[] allowTypes = null)
|
||||
{
|
||||
if (file == null) throw Oops.Bah("文件不能为空");
|
||||
if (file.Length > maxSize * 1024 * 1024) throw Oops.Bah($"文件大小不允许超过{maxSize}M");
|
||||
var fileSuffix = Path.GetExtension(file.FileName).ToLower().Split(".")[1];// 文件后缀
|
||||
var allowTypeS = allowTypes == null ? new[] { "xlsx" } : allowTypes;//允许上传的文件类型
|
||||
if (!allowTypeS.Contains(fileSuffix)) throw Oops.Bah(errorMessage: "文件格式错误");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImportPreviewOutput<T> TemplateDataVerification<T>(ImportResult<T> importResult) where T : ImportTemplateInput
|
||||
{
|
||||
if (importResult.Exception != null) throw Oops.Bah("导入异常,请检查文件格式!");
|
||||
////遍历模板错误
|
||||
importResult.TemplateErrors.ForEach(error =>
|
||||
{
|
||||
if (error.Message.Contains("not found")) throw Oops.Bah($"列[{error.RequireColumnName}]未找到");
|
||||
throw Oops.Bah($"列[{error.RequireColumnName}]:{error.Message}");
|
||||
});
|
||||
if (importResult.Data == null)
|
||||
throw Oops.Bah("文件数据格式有误,请重新导入!");
|
||||
|
||||
//导入结果输出
|
||||
var importPreview = new ImportPreviewOutput<T> { HasError = importResult.HasError };
|
||||
var headerMap = new Dictionary<string, string>();
|
||||
//遍历导入的表头列表信息
|
||||
importResult.ImporterHeaderInfos.ForEach(it =>
|
||||
{
|
||||
headerMap.Add(it.Header.Name, it.PropertyName);
|
||||
var tableColumns = new TableColumns
|
||||
{ Title = it.Header.Name.Split("(")[0], DataIndex = it.PropertyName.FirstCharToLower() };//定义表头,部分表头有说明用(分组去掉说明
|
||||
var antTableAttribute = it.PropertyInfo.GetCustomAttribute<AntTableAttribute>();//获取表格特性
|
||||
if (antTableAttribute != null)
|
||||
{
|
||||
tableColumns.Date = antTableAttribute.IsDate;
|
||||
tableColumns.Ellipsis = antTableAttribute.Ellipsis;
|
||||
tableColumns.Width = antTableAttribute.Width;
|
||||
}
|
||||
importPreview.TableColumns.Add(tableColumns);//添加到表头
|
||||
});
|
||||
|
||||
//导入的数据转集合
|
||||
var data = importResult.Data.ToList();
|
||||
var systemError = new string[] { };//系统错误提示
|
||||
//遍历错误列,将错误字典中的中文改成英文跟实体对应
|
||||
importResult.RowErrors.ForEach(row =>
|
||||
{
|
||||
IDictionary<string, string> fieldErrors = new Dictionary<string, string>();//定义字典
|
||||
//遍历错误列,赋值给新的字典
|
||||
row.FieldErrors.ForEach(it =>
|
||||
{
|
||||
var errrVaule = it.Value;
|
||||
//value xx Invalid, please fill in the correct integer value!
|
||||
//value xx Invalid, please fill in the correct date and time format!
|
||||
if (it.Value.Contains("Invalid"))//如果错误信息有Invalid就提示格式错误
|
||||
errrVaule = $"{it.Key}格式错误";
|
||||
fieldErrors.Add(headerMap[it.Key], errrVaule);
|
||||
});
|
||||
row.FieldErrors = fieldErrors;//替换新的字典
|
||||
row.RowIndex -= 2;//下表与列表中的下标一致
|
||||
data[row.RowIndex].HasError = true;//错误的行HasError = true
|
||||
data[row.RowIndex].ErrorInfo = fieldErrors;//替换新的字典
|
||||
});
|
||||
data = data.OrderByDescending(it => it.HasError).ToList();//排序
|
||||
importPreview.Data = data;//重新赋值data
|
||||
return importPreview;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileStreamResult> GenerateTemplate<T>(string fileName) where T : class, new()
|
||||
{
|
||||
IImporter importer = new ExcelImporter();
|
||||
var byteArray = await importer.GenerateTemplateBytes<T>();
|
||||
var result = GetFileStreamResult(byteArray, fileName);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public FileStreamResult GenerateLocalTemplate(string fileName, string templateFolder = "Template")
|
||||
{
|
||||
var folder = App.WebHostEnvironment.WebRootPath.CombinePath(templateFolder);
|
||||
return GetFileStreamResult(folder, fileName, true);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ImportPreviewOutput<T>> GetImportPreview<T>(IFormFile file) where T : ImportTemplateInput, new()
|
||||
{
|
||||
ImportVerification(file);//验证文件
|
||||
IImporter importer = new ExcelImporter();
|
||||
using var fileStream = file.OpenReadStream();//获取文件流
|
||||
var import = await importer.Import<T>(fileStream);//导入的文件转化为带入结果
|
||||
var importPreview = TemplateDataVerification(import);//验证数据完整度
|
||||
return importPreview;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ImportResultOutPut<T> GetImportResultPreview<T>(List<T> data, out List<T> importData) where T : ImportTemplateInput
|
||||
{
|
||||
//定义结果
|
||||
var result = new ImportResultOutPut<T> { Total = data.Count };
|
||||
//可以导入的数据
|
||||
importData = data.Where(it => it.HasError == false).ToList();
|
||||
//如果有错误
|
||||
if (importData.Count != data.Count)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Data = data.Where(it => it.HasError).ToList();
|
||||
result.FailCount = data.Count - importData.Count;
|
||||
}
|
||||
result.ImportCount = importData.Count;
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion 导入
|
||||
|
||||
#region 导出
|
||||
|
||||
public async Task<FileStreamResult> Export<T>(List<T> data, string fileName) where T : class, new()
|
||||
{
|
||||
IExporter exporter = new ExcelExporter();
|
||||
var byteArray = await exporter.ExportAsByteArray(data);
|
||||
var result = GetFileStreamResult(byteArray, fileName);
|
||||
return result;
|
||||
}
|
||||
|
||||
#endregion 导出
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件流
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <param name="isPathFolder"></param>
|
||||
/// <returns></returns>
|
||||
public FileStreamResult GetFileStreamResult(string path, string fileName, bool isPathFolder = false)
|
||||
{
|
||||
fileName = GetFileName(fileName);
|
||||
if (isPathFolder) path = path.CombinePath(fileName);
|
||||
//文件转流
|
||||
var result = new FileStreamResult(new FileStream(path, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName };
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件流
|
||||
/// </summary>
|
||||
/// <param name="byteArray"></param>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public FileStreamResult GetFileStreamResult(byte[] byteArray, string fileName)
|
||||
{
|
||||
fileName = GetFileName(fileName);
|
||||
//文件转流
|
||||
var result = new FileStreamResult(new MemoryStream(byteArray), "application/octet-stream") { FileDownloadName = fileName };
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件名
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public string GetFileName(string fileName)
|
||||
{
|
||||
if (!fileName.Contains("."))
|
||||
fileName = fileName + ".xlsx";
|
||||
fileName = HttpUtility.UrlEncode(fileName, Encoding.GetEncoding("UTF-8"));//文件名转utf8不然前端下载会乱码
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取本地模板路径
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public string GetTemplateFolder()
|
||||
{
|
||||
var folder = App.WebHostEnvironment.WebRootPath.CombinePath("Template");
|
||||
return folder;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 日程列表查询参数
|
||||
/// </summary>
|
||||
public class ScheduleListInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 日程日期
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ScheduleDate不能为空")]
|
||||
public string ScheduleDate { get; set; }
|
||||
}
|
||||
|
||||
public class ScheduleAddInput : RelationUserSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// 日程日期
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "scheduleDate不能为空")]
|
||||
public override string ScheduleDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日程内容
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ScheduleContent不能为空")]
|
||||
public override string ScheduleContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 日程时间
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ScheduleTime 不能为空")]
|
||||
public override string ScheduleTime { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class ScheduleListOutput : RelationUserSchedule
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 首页服务
|
||||
/// </summary>
|
||||
public interface IIndexService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加日程
|
||||
/// </summary>
|
||||
/// <param name="input">日程参数</param>
|
||||
/// <returns></returns>
|
||||
Task AddSchedule(ScheduleAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除日程
|
||||
/// </summary>
|
||||
/// <param name="input">id列表</param>
|
||||
/// <returns></returns>
|
||||
Task DeleteSchedule(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取日程列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>日程列表</returns>
|
||||
Task<List<ScheduleListOutput>> ScheduleList(ScheduleListInput input);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IIndexService"/>
|
||||
/// </summary>
|
||||
public class IndexService : DbRepository<SysRelation>, IIndexService
|
||||
{
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public IndexService(IRelationService relationService)
|
||||
{
|
||||
_relationService = relationService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<ScheduleListOutput>> ScheduleList(ScheduleListInput input)
|
||||
{
|
||||
var relations = await GetListAsync(
|
||||
it => it.Category == CateGoryConst.RELATION_SYS_USER_SCHEDULE_DATA && it.ObjectId == UserManager.UserId
|
||||
&& it.TargetId == input.ScheduleDate,
|
||||
it => new SysRelation { ExtJson = it.ExtJson, Id = it.Id });//获取当前用户的日程列表
|
||||
var userSchedules = new List<ScheduleListOutput>();//结果集
|
||||
relations.ForEach(it =>
|
||||
{
|
||||
var extJson = it.ExtJson.ToJsonEntity<RelationUserSchedule>();//转成实体
|
||||
var userSchedule = extJson.Adapt<ScheduleListOutput>();//格式化
|
||||
userSchedule.Id = it.Id;//赋值ID
|
||||
userSchedules.Add(userSchedule);//添加到结果集
|
||||
});
|
||||
return userSchedules;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task AddSchedule(ScheduleAddInput input)
|
||||
{
|
||||
input.ScheduleUserId = UserManager.UserId;
|
||||
input.ScheduleUserName = UserManager.Name;
|
||||
//添加日程
|
||||
await _relationService.SaveRelation(CateGoryConst.RELATION_SYS_USER_SCHEDULE_DATA, UserManager.UserId, input.ScheduleDate, input.ToJson(),
|
||||
false, false);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task DeleteSchedule(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
await DeleteAsync(it => ids.Contains(it.Id) && it.ObjectId == UserManager.UserId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IButtonService"/>
|
||||
/// </summary>
|
||||
public class ButtonService : DbRepository<SysResource>, IButtonService
|
||||
{
|
||||
private readonly ILogger<ButtonService> _logger;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public ButtonService(ILogger<ButtonService> logger, IResourceService resourceService, IRelationService relationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_resourceService = resourceService;
|
||||
_relationService = relationService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysResource>> Page(ButtonPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysResource>().Where(it => it.ParentId == input.ParentId && it.Category == CateGoryConst.RESOURCE_BUTTON)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Path.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}").OrderBy(it => it.SortCode);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(ButtonAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
if (await InsertAsync(sysResource))//插入数据
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_BUTTON);//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<long>> AddBatch(ButtonAddInput input)
|
||||
{
|
||||
var sysResources = new List<SysResource>();//按钮列表
|
||||
var codeList = new List<string> { "Add", "Edit", "Delete", "BatchDelete", "Import", "Export", "BatchEdit" };//code后缀
|
||||
var titleList = new List<string> { "新增", "编辑", "删除", "批量删除", "导入", "导出", "批量编辑" };//title前缀
|
||||
var idList = new List<long>();//Id列表
|
||||
for (var i = 0; i < codeList.Count; i++)
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();
|
||||
sysResources.Add(new SysResource
|
||||
{
|
||||
Id = id,
|
||||
Title = titleList[i] + input.Title,//标题等于前缀输入的值
|
||||
Code = input.Code + codeList[i],//code等于输入的值加后缀
|
||||
ParentId = input.ParentId,
|
||||
SortCode = i + 1
|
||||
});
|
||||
idList.Add(id);
|
||||
}
|
||||
//遍历列表
|
||||
foreach (var sysResource in sysResources)
|
||||
{
|
||||
await CheckInput(sysResource);//检查按钮参数
|
||||
}
|
||||
//添加到数据库
|
||||
if (await InsertRangeAsync(sysResources))//插入数据
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_BUTTON);//刷新缓存
|
||||
return sysResources.Select(it => it.Id).ToList();
|
||||
}
|
||||
return new List<long>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(ButtonEditInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await UpdateAsync(sysResource);//更新按钮
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_BUTTON);//资源表按钮刷新缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
//获取所有按钮集合
|
||||
var buttonList = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_BUTTON);
|
||||
|
||||
#region 处理关系表角色资源信息
|
||||
|
||||
//获取所有菜单集合
|
||||
var menuList = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MENU);
|
||||
//获取按钮的父菜单id集合
|
||||
var parentIds = buttonList.Where(it => ids.Contains(it.Id)).Select(it => it.ParentId.Value.ToString()).ToList();
|
||||
//获取关系表分类为SYS_ROLE_HAS_RESOURCE数据
|
||||
var roleResources = await _relationService.GetRelationByCategory(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);
|
||||
//获取相关关系表数据
|
||||
var relationList = roleResources.Where(it => parentIds.Contains(it.TargetId))//目标ID是父ID中
|
||||
.Where(it => it.ExtJson != null).ToList();//扩展信息不为空
|
||||
//遍历关系表
|
||||
relationList.ForEach(it =>
|
||||
{
|
||||
var relationRoleResuorce = it.ExtJson.ToJsonEntity<RelationRoleResource>();//拓展信息转实体
|
||||
var buttonInfo = relationRoleResuorce.ButtonInfo;//获取按钮信息
|
||||
if (buttonInfo.Count > 0)
|
||||
{
|
||||
// 使用 LINQ 查询找出交集的补集(即不同元素)
|
||||
var diffArr = buttonInfo.Except(ids).Union(ids.Except(buttonInfo)).ToList();
|
||||
relationRoleResuorce.ButtonInfo = diffArr;//重新赋值按钮信息
|
||||
it.ExtJson = relationRoleResuorce.ToJson();//重新赋值拓展信息
|
||||
}
|
||||
});
|
||||
|
||||
#endregion 处理关系表角色资源信息
|
||||
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮
|
||||
if (relationList.Count > 0)
|
||||
{
|
||||
await Context.Updateable(relationList).UpdateColumns(it => it.ExtJson).ExecuteCommandAsync();//修改拓展信息
|
||||
}
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_BUTTON);//资源表按钮刷新缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);//关系表刷新角色资源缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysResource> Detail(BaseIdInput input)
|
||||
{
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_BUTTON);
|
||||
var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysResource"></param>
|
||||
private async Task CheckInput(SysResource sysResource)
|
||||
{
|
||||
//获取所有按钮和菜单
|
||||
var buttonList = await _resourceService.GetListAsync(new List<string> { CateGoryConst.RESOURCE_BUTTON, CateGoryConst.RESOURCE_MENU });
|
||||
//判断code是否重复
|
||||
if (buttonList.Any(it => it.Code == sysResource.Code && it.Id != sysResource.Id))
|
||||
throw Oops.Bah($"存在重复的按钮编码:{sysResource.Code}");
|
||||
//判断菜单是否存在
|
||||
if (!buttonList.Any(it => it.Id == sysResource.ParentId))
|
||||
throw Oops.Bah($"不存在的父级菜单:{sysResource.ParentId}");
|
||||
sysResource.Category = CateGoryConst.RESOURCE_BUTTON;//设置分类为按钮
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
public class ButtonPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ParentId不能为空")]
|
||||
public long? ParentId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮参数
|
||||
/// </summary>
|
||||
public class ButtonAddInput : SysResource
|
||||
{
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ParentId不能为空")]
|
||||
public override long? ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Title不能为空")]
|
||||
public override string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编码
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空")]
|
||||
public override string Code { get; set; }
|
||||
}
|
||||
|
||||
public class ButtonEditInput : ButtonAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 权限按钮服务
|
||||
/// </summary>
|
||||
public interface IButtonService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加按钮
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(ButtonAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<long>> AddBatch(ButtonAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除按钮
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑按钮
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(ButtonEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 按钮分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询条件</param>
|
||||
/// <returns>按钮分页列表</returns>
|
||||
Task<SqlSugarPagedList<SysResource>> Page(ButtonPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 按钮详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysResource> Detail(BaseIdInput input);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单树查询参数
|
||||
/// </summary>
|
||||
public class MenuTreeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块
|
||||
/// </summary>
|
||||
public long? Module { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键字
|
||||
/// </summary>
|
||||
public string SearchKey { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加菜单参数
|
||||
/// </summary>
|
||||
public class MenuAddInput : SysResource, IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "ParentId不能为空")]
|
||||
public override long? ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Title不能为空")]
|
||||
public override string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单类型
|
||||
/// </summary>
|
||||
public override string MenuType { get; set; } = SysResourceConst.MENU;
|
||||
|
||||
/// <summary>
|
||||
/// 模块
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Module不能为空")]
|
||||
public override long? Module { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Path不能为空")]
|
||||
public override string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Icon不能为空")]
|
||||
public override string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 特殊验证
|
||||
/// </summary>
|
||||
/// <param name="validationContext"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
//如果菜单类型是菜单
|
||||
if (MenuType is SysResourceConst.MENU or SysResourceConst.SUBSET or SysResourceConst.LINK)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Name))
|
||||
yield return new ValidationResult("Name不能为空", new[] { nameof(Name) });
|
||||
if (string.IsNullOrEmpty(Component))
|
||||
yield return new ValidationResult("Component不能为空", new[] { nameof(Name) });
|
||||
if (MenuType is SysResourceConst.SUBSET)//如果是子集
|
||||
{
|
||||
if (string.IsNullOrEmpty(ActiveMenu))
|
||||
yield return new ValidationResult("ActiveMenu不能为空", new[] { nameof(Name) });
|
||||
IsHome = false;
|
||||
IsHide = true;
|
||||
IsFull = false;
|
||||
IsAffix = false;
|
||||
IsKeepAlive = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = null;//设置name为空
|
||||
Component = null;//设置组件为空
|
||||
}
|
||||
//设置分类为菜单
|
||||
Category = CateGoryConst.RESOURCE_MENU;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑菜单输入参数
|
||||
/// </summary>
|
||||
public class MenuEditInput : MenuAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 改变模块输入参数
|
||||
/// </summary>
|
||||
public class MenuChangeModuleInput : BaseIdInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块ID
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Module不能为空")]
|
||||
public long? Module { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 菜单服务
|
||||
/// </summary>
|
||||
public interface IMenuService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加菜单
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(MenuAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 详情
|
||||
/// </summary>
|
||||
/// <param name="input">id</param>
|
||||
/// <returns>详细信息</returns>
|
||||
Task<SysResource> Detail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 构建菜单树形结构
|
||||
/// </summary>
|
||||
/// <param name="resourceList">菜单列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <returns>菜单形结构</returns>
|
||||
List<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long? parentId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// 获取菜单树
|
||||
/// </summary>
|
||||
/// <param name="input">菜单树查询参数</param>
|
||||
/// <param name="showDisabled">是否显示禁用的</param>
|
||||
/// <returns>菜单树列表</returns>
|
||||
Task<List<SysResource>> Tree(MenuTreeInput input, bool showDisabled = true);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑菜单
|
||||
/// </summary>
|
||||
/// <param name="input">菜单编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(MenuEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除菜单
|
||||
/// </summary>
|
||||
/// <param name="input">删除菜单参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 改变菜单模块
|
||||
/// </summary>
|
||||
/// <param name="input">改变菜单模块参数</param>
|
||||
/// <returns></returns>
|
||||
Task ChangeModule(MenuChangeModuleInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 快捷方式菜单树
|
||||
/// </summary>
|
||||
/// <param name="sysResources">资源列表</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> ShortcutTree(List<SysResource> sysResources = null);
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IMenuService"/>
|
||||
/// </summary>
|
||||
public class MenuService : DbRepository<SysResource>, IMenuService
|
||||
{
|
||||
private readonly ILogger<MenuService> _logger;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public MenuService(ILogger<MenuService> logger, IResourceService resourceService, IRelationService relationService)
|
||||
{
|
||||
_logger = logger;
|
||||
_resourceService = resourceService;
|
||||
_relationService = relationService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<SysResource> ConstructMenuTrees(List<SysResource> resourceList, long? parentId = 0)
|
||||
{
|
||||
//找下级资源ID列表
|
||||
var resources = resourceList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
|
||||
if (resources.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysResource>();
|
||||
foreach (var item in resources)//遍历资源
|
||||
{
|
||||
item.Children = ConstructMenuTrees(resourceList, item.Id);//添加子节点
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysResource>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> Tree(MenuTreeInput input, bool showDisabled = true)
|
||||
{
|
||||
//获取所有菜单
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MENU);
|
||||
sysResources = sysResources.WhereIF(input.Module != null, it => it.Module.Value == input.Module.Value)//根据模块查找
|
||||
.WhereIF(!showDisabled, it => it.Status == CommonStatusConst.ENABLE)//是否显示禁用的
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title == input.SearchKey)//根据关键字查找
|
||||
.ToList();
|
||||
//构建菜单树
|
||||
var tree = ConstructMenuTrees(sysResources);
|
||||
return tree;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> ShortcutTree(List<SysResource> sysResources = null)
|
||||
{
|
||||
if (sysResources == null)
|
||||
//获取所有菜单
|
||||
sysResources = (await _resourceService.GetAllModuleAndMenuAndSpaList())
|
||||
.Where(it => it.Status == CommonStatusConst.ENABLE).ToList();
|
||||
// //获取所有单页
|
||||
// var sysSpas = (await _resourceService.GetConfigsByCategory(CateGoryConst.RESOURCE_SPA))
|
||||
// .Where(it => it.Status == CommonStatusConst.ENABLE).ToList();
|
||||
sysResources.ForEach(it =>
|
||||
{
|
||||
if (it.MenuType == SysResourceConst.CATALOG)
|
||||
it.ParentId = it.Module.Value;//目录的父级ID设置为模块ID
|
||||
});
|
||||
|
||||
//构建菜单树
|
||||
var tree = ConstructMenuTrees(sysResources, null);
|
||||
//将单页的排前面根据排序码排序
|
||||
tree = tree.OrderByDescending(it => it.Category == CateGoryConst.RESOURCE_SPA).ThenBy(it => it.SortCode).ToList();
|
||||
return tree;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(MenuAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
|
||||
if (await InsertAsync(sysResource))//插入数据
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MENU);//刷新菜单缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(MenuEditInput input)
|
||||
{
|
||||
var resource = await CheckInput(input, true);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
var updatePath = resource.Path != input.Path;//是否更新路径
|
||||
var permissions = new List<SysRelation>();
|
||||
if (updatePath)
|
||||
{
|
||||
//获取所有角色和用户的权限关系
|
||||
var rolePermissions = await _relationService.GetRelationByCategory(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);
|
||||
var userPermissions = await _relationService.GetRelationByCategory(CateGoryConst.RELATION_SYS_USER_HAS_PERMISSION);
|
||||
//找到所有匹配的权限
|
||||
rolePermissions = rolePermissions.Where(it => it.TargetId.Contains(resource.Path)).ToList();
|
||||
userPermissions = userPermissions.Where(it => it.TargetId.Contains(resource.Path)).ToList();
|
||||
//更新路径
|
||||
rolePermissions.ForEach(it => it.TargetId = it.TargetId.Replace(resource.Path, input.Path));
|
||||
userPermissions.ForEach(it => it.TargetId = it.TargetId.Replace(resource.Path, input.Path));
|
||||
//添加到权限列表
|
||||
permissions.AddRange(rolePermissions);
|
||||
permissions.AddRange(userPermissions);
|
||||
}
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await UpdateAsync(sysResource);//更新数据
|
||||
if (permissions.Count > 0)//如果权限列表大于0就更新
|
||||
{
|
||||
await Context.Updateable(permissions)
|
||||
.ExecuteCommandAsync();//更新关系表
|
||||
}
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MENU);//刷新菜单缓存
|
||||
//刷新关系表缓存
|
||||
if (updatePath)
|
||||
{
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_USER_HAS_PERMISSION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//获取所有菜单和按钮
|
||||
var resourceList = await _resourceService.GetListAsync(new List<string> { CateGoryConst.RESOURCE_MENU, CateGoryConst.RESOURCE_BUTTON });
|
||||
//找到要删除的菜单
|
||||
var sysResources = resourceList.Where(it => ids.Contains(it.Id)).ToList();
|
||||
//查找内置菜单
|
||||
var system = sysResources.Where(it => it.Code == SysResourceConst.SYSTEM).FirstOrDefault();
|
||||
if (system != null)
|
||||
throw Oops.Bah($"不可删除系统菜单:{system.Title}");
|
||||
//需要删除的资源ID列表
|
||||
var resourceIds = new List<long>();
|
||||
//遍历菜单列表
|
||||
sysResources.ForEach(it =>
|
||||
{
|
||||
//获取菜单所有子节点
|
||||
var child = _resourceService.GetChildListById(resourceList, it.Id, false);
|
||||
//将子节点ID添加到删除资源ID列表
|
||||
resourceIds.AddRange(child.Select(it => it.Id).ToList());
|
||||
resourceIds.Add(it.Id);//添加到删除资源ID列表
|
||||
});
|
||||
ids.AddRange(resourceIds);//添加到删除ID列表
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除菜单和按钮
|
||||
await Context.Deleteable<SysRelation>()//关系表删除对应SYS_ROLE_HAS_RESOURCE
|
||||
.Where(it => it.Category == CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE && resourceIds.Contains(SqlFunc.ToInt64(it.TargetId)))
|
||||
.ExecuteCommandAsync();
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MENU);//资源表菜单刷新缓存
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_BUTTON);//资源表按钮刷新缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);//关系表刷新缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysResource> Detail(BaseIdInput input)
|
||||
{
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MENU);
|
||||
var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ChangeModule(MenuChangeModuleInput input)
|
||||
{
|
||||
var sysResource = await Detail(new BaseIdInput { Id = input.Id });
|
||||
if (sysResource != null)
|
||||
{
|
||||
if (sysResource.Module == input.Module)//如果模块ID没变直接返回
|
||||
return;
|
||||
if (sysResource.ParentId != 0)
|
||||
throw Oops.Bah("非顶级菜单不可修改所属模块");
|
||||
//获取所有菜单和模块
|
||||
var resourceList = await _resourceService.GetListAsync(new List<string> { CateGoryConst.RESOURCE_MENU, CateGoryConst.RESOURCE_MODULE });
|
||||
if (!resourceList.Any(it => it.Category == CateGoryConst.RESOURCE_MODULE && it.Id == input.Module.Value))
|
||||
throw Oops.Bah("不存在的模块");
|
||||
//获取所有菜单
|
||||
var menuList = resourceList.Where(it => it.Category == CateGoryConst.RESOURCE_MENU).ToList();
|
||||
//获取需要改变模块菜单的所有子菜单
|
||||
var childList = _resourceService.GetChildListById(menuList, input.Id);
|
||||
childList.ForEach(it => it.Module = input.Module);
|
||||
//更新数据
|
||||
await UpdateRangeAsync(childList);
|
||||
//刷新菜单缓存
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MENU);
|
||||
}
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysResource"></param>
|
||||
/// <param name="update"></param>
|
||||
private async Task<SysResource> CheckInput(SysResource sysResource, bool update = false)
|
||||
{
|
||||
//获取所有菜单列表
|
||||
var menList = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MENU);
|
||||
//判断是否有同级且同名的菜单
|
||||
if (menList.Any(it => it.ParentId == sysResource.ParentId && it.Title == sysResource.Title && it.Id != sysResource.Id))
|
||||
throw Oops.Bah($"存在重复的菜单名称:{sysResource.Title}");
|
||||
if (sysResource.ParentId != 0)
|
||||
{
|
||||
//获取父级,判断父级ID正不正确
|
||||
var parent = menList.Where(it => it.Id == sysResource.ParentId).FirstOrDefault();
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent.Module != sysResource.Module)//如果父级的模块和当前模块不一样
|
||||
throw Oops.Bah("模块与上级菜单不一致");
|
||||
if (parent.Id == sysResource.Id)
|
||||
throw Oops.Bah("上级菜单不能选择自己");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"上级菜单不存在:{sysResource.ParentId}");
|
||||
}
|
||||
}
|
||||
//如果ID大于0表示编辑
|
||||
if (sysResource.Id > 0 && update)
|
||||
{
|
||||
var resource = menList.Where(it => it.Id == sysResource.Id).FirstOrDefault();
|
||||
if (resource == null)
|
||||
throw Oops.Bah($"菜单不存在:{sysResource.Id}");
|
||||
return resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 模块分页输入
|
||||
/// </summary>
|
||||
public class ModulePageInput : BasePageInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加模块输入
|
||||
/// </summary>
|
||||
public class ModuleAddInput : SysResource
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑模块输入
|
||||
/// </summary>
|
||||
public class ModuleEditInput : ModuleAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 模块管理服务
|
||||
/// </summary>
|
||||
public interface IModuleService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加模块
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(ModuleAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除模块
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑模块
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(ModuleEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">分页参数</param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysResource>> Page(ModulePageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 模块详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysResource> Detail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 模块列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> List();
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IModuleService"/>
|
||||
/// </summary>
|
||||
public class ModuleService : DbRepository<SysResource>, IModuleService
|
||||
{
|
||||
private readonly ILogger<ModuleService> _logger;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
private readonly ISysUserService _sysUserService;
|
||||
|
||||
public ModuleService(ILogger<ModuleService> logger, IResourceService resourceService, IRelationService relationService,
|
||||
IEventPublisher eventPublisher, ISysUserService sysUserService)
|
||||
{
|
||||
_logger = logger;
|
||||
_resourceService = resourceService;
|
||||
_relationService = relationService;
|
||||
_eventPublisher = eventPublisher;
|
||||
_sysUserService = sysUserService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysResource>> Page(ModulePageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysResource>().Where(it => it.Category == CateGoryConst.RESOURCE_MODULE)//模块
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}").OrderBy(it => it.SortCode);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(ModuleAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
if (await InsertAsync(sysResource))//插入数据
|
||||
{
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MODULE);//刷新缓存
|
||||
_sysUserService.DeleteUserFromRedis(UserManager.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(ModuleEditInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
if (await UpdateAsync(sysResource))//更新数据
|
||||
{
|
||||
if (sysResource.Status == CommonStatusConst.DISABLED)//如果禁用
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_MODULE_TOKEN, sysResource.Id);//清除角色下用户缓存
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_MODULE);//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysResource> Detail(BaseIdInput input)
|
||||
{
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MODULE);
|
||||
var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//获取所有
|
||||
var resourceList = await _resourceService.GetListAsync();
|
||||
//找到要删除的模块
|
||||
var sysResources = resourceList.Where(it => ids.Contains(it.Id)).ToList();
|
||||
//查找内置模块
|
||||
var system = sysResources.Where(it => it.Code == SysResourceConst.SYSTEM).FirstOrDefault();
|
||||
if (system != null)
|
||||
throw Oops.Bah($"不可删除系统内置模块:{system.Title}");
|
||||
if (sysResources.Any(it => it.Status == CommonStatusConst.ENABLE))//如果有启用的模块
|
||||
throw Oops.Bah($"请先禁用模块再删除");
|
||||
//获取模块下的所有菜单Id列表
|
||||
var menuIds = resourceList.Where(it => ids.Contains(it.Module.ToLong()) && it.ParentId.ToLong() == SimpleAdminConst.ZERO)
|
||||
.Select(it => it.Id).ToList();
|
||||
//需要删除的资源ID列表
|
||||
var resourceIds = new List<long>();
|
||||
//遍历列表
|
||||
menuIds.ForEach(it =>
|
||||
{
|
||||
//获取菜单所有子节点
|
||||
var child = _resourceService.GetChildListById(resourceList, it, false);
|
||||
//将子节点ID添加到删除ID列表
|
||||
resourceIds.AddRange(child.Select(it => it.Id).ToList());
|
||||
resourceIds.Add(it);//添加到删除ID列表
|
||||
});
|
||||
ids.AddRange(resourceIds);
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除菜单和按钮
|
||||
await Context.Deleteable<SysRelation>()//关系表删除对应SYS_ROLE_HAS_RESOURCE
|
||||
.Where(it => it.Category == CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE && resourceIds.Contains(SqlFunc.ToInt64(it.TargetId)))
|
||||
.ExecuteCommandAsync();
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _resourceService.RefreshCache();//资源表刷新缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);//关系表刷新缓存
|
||||
_sysUserService.DeleteUserFromRedis(UserManager.UserId);
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> List()
|
||||
{
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MODULE);
|
||||
return sysResources;
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysResource"></param>
|
||||
private async Task CheckInput(SysResource sysResource)
|
||||
{
|
||||
var sysResourceList = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_MODULE);
|
||||
//判断是否从存在重复模块
|
||||
var hasSameName = sysResourceList.Any(it => it.Title == sysResource.Title && it.Id != sysResource.Id);
|
||||
if (hasSameName)
|
||||
{
|
||||
throw Oops.Bah($"存在重复的模块:{sysResource.Title}");
|
||||
}
|
||||
if (sysResource.Status == CommonStatusConst.DISABLED)//如果禁用
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_MODULE_TOKEN, sysResource.Id);//清除角色下用户缓存
|
||||
//设置为模块
|
||||
sysResource.Category = CateGoryConst.RESOURCE_MODULE;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 角色查询参数
|
||||
/// </summary>
|
||||
public class RolePageInput : PositionPageInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色添加参数
|
||||
/// </summary>
|
||||
public class RoleAddInput : SysRole
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Name不能为空")]
|
||||
public override string Name { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色编辑参数
|
||||
/// </summary>
|
||||
public class RoleEditInput : RoleAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权资源参数
|
||||
/// </summary>
|
||||
public class GrantResourceInput : RoleOwnResourceOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权资源信息
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "GrantInfoList不能为空")]
|
||||
public override List<RelationRoleResource> GrantInfoList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否代码生成
|
||||
/// </summary>
|
||||
public bool IsCodeGen { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权资源参数
|
||||
/// </summary>
|
||||
public class GrantPermissionInput : RoleOwnPermissionOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权权限信息
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "GrantInfoList不能为空")]
|
||||
public override List<RelationRolePermission> GrantInfoList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权用户参数
|
||||
/// </summary>
|
||||
public class GrantUserInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权权限信息
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "GrantInfoList不能为空")]
|
||||
public List<long> GrantInfoList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色选择器参数
|
||||
/// </summary>
|
||||
public class RoleSelectorInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构ID列表
|
||||
/// </summary>
|
||||
public List<long> OrgIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
}
|
||||
|
||||
public class RoleTreeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 机构ID列表
|
||||
/// </summary>
|
||||
public List<long> OrgIds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 角色拥有的资源输出
|
||||
/// </summary>
|
||||
public class RoleOwnResourceOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已授权资源信息
|
||||
/// </summary>
|
||||
public virtual List<RelationRoleResource> GrantInfoList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色拥有权限输出
|
||||
/// </summary>
|
||||
public class RoleOwnPermissionOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色Id
|
||||
/// </summary>
|
||||
public virtual long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 已授权资源信息
|
||||
/// </summary>
|
||||
public virtual List<RelationRolePermission> GrantInfoList { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色树输出参数
|
||||
/// </summary>
|
||||
public class RoleTreeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 职位Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是职位
|
||||
/// </summary>
|
||||
public bool IsRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子项
|
||||
/// </summary>
|
||||
public List<RoleTreeOutput> Children { get; set; } = new List<RoleTreeOutput>();
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 角色服务
|
||||
/// </summary>
|
||||
public interface ISysRoleService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有角色
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRole>> GetListAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取用户角色集合
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRole>> GetRoleListByUserId(long userId);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色拥有权限
|
||||
/// </summary>
|
||||
/// <param name="input">角色ID</param>
|
||||
/// <returns></returns>
|
||||
Task<RoleOwnPermissionOutput> OwnPermission(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 角色拥有资源
|
||||
/// </summary>
|
||||
/// <param name="input">角色id</param>
|
||||
/// <param name="category">资源类型</param>
|
||||
/// <returns>角色拥有资源信息</returns>
|
||||
Task<RoleOwnResourceOutput> OwnResource(BaseIdInput input, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色下的用户
|
||||
/// </summary>
|
||||
/// <param name="input">角色ID</param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserSelectorOutPut>> OwnUser(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询角色
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysRole>> Page(RolePageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色授权权限选择器
|
||||
/// </summary>
|
||||
/// <param name="input">角色ID</param>
|
||||
/// <returns></returns>
|
||||
Task<List<string>> RolePermissionTreeSelector(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 角色选择器
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<RoleSelectorOutPut>> RoleSelector(RoleSelectorInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据id集合获取角色集合
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRole>> GetRoleListByIdList(IdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取角色树
|
||||
/// </summary>
|
||||
/// <param name="input">角色树</param>
|
||||
/// <returns></returns>
|
||||
Task<List<RoleTreeOutput>> Tree(RoleTreeInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情
|
||||
/// </summary>
|
||||
/// <param name="input">角色Id</param>
|
||||
/// <returns></returns>
|
||||
Task<SysRole> Detail(BaseIdInput input);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <summary>
|
||||
/// 添加角色
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(RoleAddInput input);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 编辑角色
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(RoleEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给角色授权权限
|
||||
/// </summary>
|
||||
/// <param name="input">授权信息</param>
|
||||
/// <returns></returns>
|
||||
Task GrantPermission(GrantPermissionInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给角色授权
|
||||
/// </summary>
|
||||
/// <param name="input">授权参数</param>
|
||||
/// <returns></returns>
|
||||
Task GrantResource(GrantResourceInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给角色授权用户
|
||||
/// </summary>
|
||||
/// <param name="input">授权信息</param>
|
||||
/// <returns></returns>
|
||||
Task GrantUser(GrantUserInput input);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RefreshCache();
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="ISysRoleService"/>
|
||||
//[Injection(Proxy = typeof(GlobalDispatchProxy))]
|
||||
public class SysRoleService : DbRepository<SysRole>, ISysRoleService
|
||||
{
|
||||
private readonly ILogger<SysRoleService> _logger;
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly ISysOrgService _sysOrgService;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SysRoleService(ILogger<SysRoleService> logger, ISimpleCacheService simpleCacheService, IRelationService relationService,
|
||||
ISysOrgService sysOrgService, IResourceService resourceService, IEventPublisher eventPublisher)
|
||||
{
|
||||
_logger = logger;
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_relationService = relationService;
|
||||
_sysOrgService = sysOrgService;
|
||||
_resourceService = resourceService;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override async Task<List<SysRole>> GetListAsync()
|
||||
{
|
||||
//先从Redis拿
|
||||
var sysRoles = _simpleCacheService.Get<List<SysRole>>(SystemConst.CACHE_SYS_ROLE);
|
||||
if (sysRoles == null)
|
||||
{
|
||||
//redis没有就去数据库拿
|
||||
sysRoles = await base.GetListAsync();
|
||||
if (sysRoles.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(SystemConst.CACHE_SYS_ROLE, sysRoles);
|
||||
}
|
||||
}
|
||||
return sysRoles;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRole>> GetRoleListByUserId(long userId)
|
||||
{
|
||||
var cods = new List<SysRole>();//角色代码集合
|
||||
var roleList = await _relationService.GetRelationListByObjectIdAndCategory(userId, CateGoryConst.RELATION_SYS_USER_HAS_ROLE);//根据用户ID获取角色ID
|
||||
var roleIdList = roleList.Select(x => x.TargetId.ToLong()).ToList();//角色ID列表
|
||||
if (roleIdList.Count > 0)
|
||||
{
|
||||
cods = await GetListAsync(it => roleIdList.Contains(it.Id));
|
||||
}
|
||||
return cods;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysRole>> Page(RolePageInput input)
|
||||
{
|
||||
var orgIds = await _sysOrgService.GetOrgChildIds(input.OrgId);//获取下级机构
|
||||
var query = Context.Queryable<SysRole>()
|
||||
.WhereIF(input.OrgId > 0, it => orgIds.Contains(it.OrgId.Value))//根据组织
|
||||
.WhereIF(input.OrgIds != null, it => input.OrgIds.Contains(it.OrgId.Value))//数据权限
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Status), it => it.Status == input.Status)//根据分类
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}").OrderBy(it => it.SortCode);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<RoleTreeOutput>> Tree(RoleTreeInput input)
|
||||
{
|
||||
var result = new List<RoleTreeOutput>();//返回结果
|
||||
var sysOrgList = await _sysOrgService.GetListAsync(false);//获取所有机构
|
||||
var sysRoles = await GetListAsync();//获取所有角色
|
||||
sysRoles = sysRoles.Where(it => it.Code != SysRoleConst.SUPER_ADMIN).ToList();//排除超管
|
||||
if (input.OrgIds != null)//根据数据范围查
|
||||
{
|
||||
sysOrgList = sysOrgList.Where(it => input.OrgIds.Contains(it.Id)).ToList();//在指定组织列表查询
|
||||
sysRoles = sysRoles.Where(it => input.OrgIds.Contains(it.OrgId.GetValueOrDefault())).ToList();//在指定职位列表查询
|
||||
}
|
||||
var topOrgList = sysOrgList.Where(it => it.ParentId == 0).ToList();//获取顶级机构
|
||||
var globalRole = sysRoles.Where(it => it.Category == CateGoryConst.ROLE_GLOBAL).ToList();//获取全局角色
|
||||
if (globalRole.Count > 0)
|
||||
{
|
||||
result.Add(new RoleTreeOutput()
|
||||
{
|
||||
Id = CommonUtils.GetSingleId(),
|
||||
Name = "全局角色",
|
||||
Children = globalRole.Select(it => new RoleTreeOutput
|
||||
{
|
||||
Id = it.Id,
|
||||
Name = it.Name,
|
||||
IsRole = true
|
||||
}).ToList()
|
||||
});//添加全局角色
|
||||
}
|
||||
//遍历顶级机构
|
||||
foreach (var org in topOrgList)
|
||||
{
|
||||
var childIds = await _sysOrgService.GetOrgChildIds(org.Id, true, sysOrgList);//获取机构下的所有子级ID
|
||||
var childRoles = sysRoles.Where(it => it.OrgId != null && childIds.Contains(it.OrgId.Value)).ToList();//获取机构下的所有角色
|
||||
if (childRoles.Count == 0)
|
||||
continue;
|
||||
var roleTreeOutput = new RoleTreeOutput
|
||||
{
|
||||
Id = org.Id,
|
||||
Name = org.Name,
|
||||
IsRole = false
|
||||
};//实例化角色树
|
||||
childRoles.ForEach(it =>
|
||||
{
|
||||
roleTreeOutput.Children.Add(new RoleTreeOutput()
|
||||
{
|
||||
Id = it.Id,
|
||||
Name = it.Name,
|
||||
IsRole = true
|
||||
});
|
||||
});
|
||||
result.Add(roleTreeOutput);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleOwnResourceOutput> OwnResource(BaseIdInput input, string category)
|
||||
{
|
||||
var roleOwnResource = new RoleOwnResourceOutput { Id = input.Id };//定义结果集
|
||||
var grantInfoList = new List<RelationRoleResource>();//已授权信息集合
|
||||
//获取关系列表
|
||||
var relations = await _relationService.GetRelationListByObjectIdAndCategory(input.Id, category);
|
||||
//遍历关系表
|
||||
relations.ForEach(it =>
|
||||
{
|
||||
//将扩展信息转为实体
|
||||
var relationRole = it.ExtJson.ToJsonEntity<RelationRoleResource>();
|
||||
grantInfoList.Add(relationRole);//添加到已授权信息
|
||||
});
|
||||
roleOwnResource.GrantInfoList = grantInfoList;//赋值已授权信息
|
||||
return roleOwnResource;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleOwnPermissionOutput> OwnPermission(BaseIdInput input)
|
||||
{
|
||||
var roleOwnPermission = new RoleOwnPermissionOutput { Id = input.Id };//定义结果集
|
||||
var grantInfoList = new List<RelationRolePermission>();//已授权信息集合
|
||||
//获取关系列表
|
||||
var relations = await _relationService.GetRelationListByObjectIdAndCategory(input.Id, CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);
|
||||
//遍历关系表
|
||||
relations.ForEach(it =>
|
||||
{
|
||||
//将扩展信息转为实体
|
||||
var relationPermission = it.ExtJson.ToJsonEntity<RelationRolePermission>();
|
||||
grantInfoList.Add(relationPermission);//添加到已授权信息
|
||||
});
|
||||
roleOwnPermission.GrantInfoList = grantInfoList;//赋值已授权信息
|
||||
return roleOwnPermission;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<UserSelectorOutPut>> OwnUser(BaseIdInput input)
|
||||
{
|
||||
//获取关系列表
|
||||
var relations = await _relationService.GetRelationListByTargetIdAndCategory(input.Id.ToString(), CateGoryConst.RELATION_SYS_USER_HAS_ROLE);
|
||||
var userIds = relations.Select(it => it.ObjectId).ToList();
|
||||
var userList = await Context.Queryable<SysUser>().Where(it => userIds.Contains(it.Id)).Select<UserSelectorOutPut>().ToListAsync();
|
||||
return userList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<RoleSelectorOutPut>> RoleSelector(RoleSelectorInput input)
|
||||
{
|
||||
var orgIds = await _sysOrgService.GetOrgChildIds(input.OrgId);//获取下级组织
|
||||
//如果机构ID列表不为空
|
||||
if (input.OrgIds != null)
|
||||
{
|
||||
orgIds = orgIds.Where(it => input.OrgIds.Contains(it)).ToList();//包含在机构ID列表中的组织ID
|
||||
}
|
||||
var result = await Context.Queryable<SysRole>()
|
||||
.Where(it => it.Code != SysRoleConst.SUPER_ADMIN)
|
||||
.WhereIF(input.OrgId == 0, it => it.Category == CateGoryConst.ROLE_GLOBAL)//不传机构ID就是全局角色
|
||||
.WhereIF(orgIds.Count > 0, it => orgIds.Contains(it.OrgId.Value))//组织ID
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), it => it.Name.Contains(input.Name))//根据关键字查询
|
||||
.Select<RoleSelectorOutPut>()
|
||||
.ToPagedListAsync(input.PageNum, input.PageSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<string>> RolePermissionTreeSelector(BaseIdInput input)
|
||||
{
|
||||
var permissionTreeSelectors = new List<string>();//授权树结果集
|
||||
//获取单页信息
|
||||
var spa = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_SPA);
|
||||
var spaIds = spa.Select(it => it.Id).ToList();
|
||||
if (spaIds.Any())
|
||||
{
|
||||
//获取权限授权树
|
||||
var permissions = _resourceService.PermissionTreeSelector(spa.Select(it => it.Path).ToList());
|
||||
if (permissions.Count > 0)
|
||||
permissionTreeSelectors.AddRange(permissions.Select(it => it.PermissionName).ToList());//返回授权树权限名称列表
|
||||
}
|
||||
//获取角色资源关系
|
||||
var relationsRes = await _relationService.GetRelationByCategory(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);
|
||||
var menuIds = relationsRes.Where(it => it.ObjectId == input.Id).Select(it => it.TargetId.ToLong()).ToList();
|
||||
if (menuIds.Any())
|
||||
{
|
||||
//获取菜单信息
|
||||
var menus = await _resourceService.GetResourcesByIds(menuIds, CateGoryConst.RESOURCE_MENU);
|
||||
//获取权限授权树
|
||||
var permissions = _resourceService.PermissionTreeSelector(menus.Select(it => it.Path).ToList());
|
||||
if (permissions.Count > 0)
|
||||
permissionTreeSelectors.AddRange(permissions.Select(it => it.PermissionName).ToList());//返回授权树权限名称列表
|
||||
}
|
||||
return permissionTreeSelectors;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysRole>> GetRoleListByIdList(IdListInput input)
|
||||
{
|
||||
var roles = await GetListAsync();
|
||||
var roleList = roles.Where(it => input.IdList.Contains(it.Id)).ToList();// 获取指定ID的岗位列表
|
||||
return roleList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysRole> Detail(BaseIdInput input)
|
||||
{
|
||||
var roles = await GetListAsync();
|
||||
var role = roles.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
return role;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(RoleAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysRole = input.Adapt<SysRole>();//实体转换
|
||||
if (await InsertAsync(sysRole))//插入数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(RoleEditInput input)
|
||||
{
|
||||
//判断是否超管
|
||||
if (input.Code == SysRoleConst.SUPER_ADMIN)
|
||||
throw Oops.Bah("不可编辑超管角色");
|
||||
await CheckInput(input);//检查参数
|
||||
var role = await GetFirstAsync(it => it.Id == input.Id);//获取角色
|
||||
if (role != null)
|
||||
{
|
||||
var permissions = new List<SysRelation>();
|
||||
//如果改了默认数据权限
|
||||
if (role.DefaultDataScope.ScopeCategory != input.DefaultDataScope.ScopeCategory)
|
||||
{
|
||||
//获取角色授权列表
|
||||
var relations = await _relationService.GetRelationByCategory(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);
|
||||
//找到当前角色的
|
||||
permissions = relations.Where(it => it.ObjectId == input.Id).ToList();
|
||||
permissions.ForEach(it =>
|
||||
{
|
||||
var rolePermission = it.ExtJson.ToJsonEntity<RelationRolePermission>();//扩展信息转实体
|
||||
//如果表里的数据范围是默认数据范围才更新,已经自定义了的数据范围不更新
|
||||
if (rolePermission.ScopeCategory == role.DefaultDataScope.ScopeCategory
|
||||
&& Enumerable.SequenceEqual(rolePermission.ScopeDefineOrgIdList, role.DefaultDataScope.ScopeDefineOrgIdList))
|
||||
{
|
||||
//重新赋值数据范围
|
||||
rolePermission.ScopeCategory = input.DefaultDataScope.ScopeCategory;
|
||||
rolePermission.ScopeDefineOrgIdList = input.DefaultDataScope.ScopeDefineOrgIdList;
|
||||
it.ExtJson = rolePermission.ToJson();
|
||||
}
|
||||
});
|
||||
}
|
||||
var sysRole = input.Adapt<SysRole>();//实体转换
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await UpdateAsync(sysRole);//更新角色
|
||||
if (permissions.Any())//如果有授权权限就更新
|
||||
await Context.Updateable(permissions).ExecuteCommandAsync();
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await RefreshCache();//刷新缓存
|
||||
if (permissions.Any())//如果有授权
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);//关系表刷新SYS_ROLE_HAS_PERMISSION缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_USER_CACHE, new List<long> { input.Id });//清除角色下用户缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task GrantResource(GrantResourceInput input)
|
||||
{
|
||||
var allMenus = await _resourceService.GetListByCategory(SysResourceConst.MENU);//获取所有菜单
|
||||
var spas = await _resourceService.GetListByCategory(SysResourceConst.SPA);
|
||||
var menuIds = input.GrantInfoList.Select(it => it.MenuId).ToList();//菜单ID
|
||||
var extJsons = input.GrantInfoList.Select(it => it.ToJson()).ToList();//拓展信息
|
||||
var relationRoles = new List<SysRelation>();//要添加的角色资源和授权关系表
|
||||
var sysRole = (await GetListAsync()).Where(it => it.Id == input.Id).FirstOrDefault();//获取角色
|
||||
if (sysRole != null)
|
||||
{
|
||||
#region 角色模块处理
|
||||
|
||||
var menusList = allMenus.Where(it => menuIds.Contains(it.Id)).ToList();//获取菜单信息
|
||||
//获取我的模块信息Id列表
|
||||
var moduleIds = menusList.Select(it => it.Module.Value).Distinct().ToList();
|
||||
moduleIds.ForEach(it =>
|
||||
{
|
||||
//将角色资源添加到列表
|
||||
relationRoles.Add(new SysRelation
|
||||
{
|
||||
ObjectId = sysRole.Id,
|
||||
TargetId = it.ToString(),
|
||||
Category = CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE
|
||||
});
|
||||
});
|
||||
|
||||
#endregion 角色模块处理
|
||||
|
||||
#region 角色资源处理
|
||||
|
||||
//遍历菜单列表
|
||||
for (var i = 0; i < menuIds.Count; i++)
|
||||
{
|
||||
//将角色资源添加到列表
|
||||
relationRoles.Add(new SysRelation
|
||||
{
|
||||
ObjectId = sysRole.Id,
|
||||
TargetId = menuIds[i].ToString(),
|
||||
Category = CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE,
|
||||
ExtJson = extJsons?[i]
|
||||
});
|
||||
}
|
||||
|
||||
#endregion 角色资源处理
|
||||
|
||||
#region 角色权限处理.
|
||||
|
||||
var relationRolePer = new List<SysRelation>();//要添加的角色有哪些权限列表
|
||||
var defaultDataScope = sysRole.DefaultDataScope;//获取默认数据范围
|
||||
|
||||
//获取菜单信息
|
||||
var menus = allMenus.Where(it => menuIds.Contains(it.Id)).ToList();
|
||||
menus.AddRange(spas);//单页id添加到列表
|
||||
if (menus.Count > 0)
|
||||
{
|
||||
//获取权限授权树
|
||||
var permissions = _resourceService.PermissionTreeSelector(menus.Select(it => it.Path).ToList());
|
||||
permissions.ForEach(it =>
|
||||
{
|
||||
//新建角色权限关系
|
||||
relationRolePer.Add(new SysRelation
|
||||
{
|
||||
ObjectId = sysRole.Id,
|
||||
TargetId = it.ApiRoute,
|
||||
Category = CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION,
|
||||
ExtJson = new RelationRolePermission
|
||||
{
|
||||
ApiUrl = it.ApiRoute,
|
||||
ScopeCategory = defaultDataScope.ScopeCategory,
|
||||
ScopeDefineOrgIdList = defaultDataScope.ScopeDefineOrgIdList
|
||||
}.ToJson()
|
||||
});
|
||||
});
|
||||
}
|
||||
relationRoles.AddRange(relationRolePer);//合并列表
|
||||
|
||||
#endregion 角色权限处理.
|
||||
|
||||
#region 保存数据库
|
||||
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
var relationRep = ChangeRepository<DbRepository<SysRelation>>();//切换仓储
|
||||
//如果不是代码生成,就删除老的
|
||||
if (!input.IsCodeGen)
|
||||
await relationRep.DeleteAsync(it => it.ObjectId == sysRole.Id && (it.Category == CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION
|
||||
|| it.Category == CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE
|
||||
|| it.Category == CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE));
|
||||
await relationRep.InsertRangeAsync(relationRoles);//添加新的
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);//刷新关系缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);//刷新关系缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE);//刷新关系缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_USER_CACHE, new List<long> { input.Id });//发送事件清除角色下用户缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
|
||||
#endregion 保存数据库
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task GrantPermission(GrantPermissionInput input)
|
||||
{
|
||||
var sysRole = (await GetListAsync()).Where(it => it.Id == input.Id).FirstOrDefault();//获取角色
|
||||
if (sysRole != null)
|
||||
{
|
||||
var apiUrls = input.GrantInfoList.Select(it => it.ApiUrl).ToList();//apiUrl列表
|
||||
var extJsons = input.GrantInfoList.Select(it => it.ToJson()).ToList();//拓展信息
|
||||
await _relationService.SaveRelationBatch(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION, input.Id, apiUrls, extJsons,
|
||||
true);//添加到数据库
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_USER_CACHE, new List<long> { input.Id });//清除角色下用户缓存
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task GrantUser(GrantUserInput input)
|
||||
{
|
||||
var sysRelations = new List<SysRelation>();//关系列表
|
||||
//遍历用户ID
|
||||
input.GrantInfoList.ForEach(it =>
|
||||
{
|
||||
sysRelations.Add(new SysRelation
|
||||
{
|
||||
ObjectId = it,
|
||||
TargetId = input.Id.ToString(),
|
||||
Category = CateGoryConst.RELATION_SYS_USER_HAS_ROLE
|
||||
});
|
||||
});
|
||||
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
var relationRep = ChangeRepository<DbRepository<SysRelation>>();//切换仓储
|
||||
var targetId = input.Id.ToString();//目标ID转string
|
||||
await relationRep.DeleteAsync(it => it.TargetId == targetId && it.Category == CateGoryConst.RELATION_SYS_USER_HAS_ROLE);//删除老的
|
||||
await relationRep.InsertRangeAsync(sysRelations);//添加新的
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_USER_HAS_ROLE);//刷新关系表SYS_USER_HAS_ROLE缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_USER_CACHE, new List<long> { input.Id });//清除角色下用户缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
var sysRoles = await GetListAsync();//获取所有角色
|
||||
var hasSuperAdmin = sysRoles.Any(it => it.Code == SysRoleConst.SUPER_ADMIN && ids.Contains(it.Id));//判断是否有超级管理员
|
||||
if (hasSuperAdmin) throw Oops.Bah("不可删除系统内置超管角色");
|
||||
|
||||
//数据库是string所以这里转下
|
||||
var targetIds = ids.Select(it => it.ToString()).ToList();
|
||||
//定义删除的关系
|
||||
var delRelations = new List<string>
|
||||
{
|
||||
CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE,
|
||||
CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION,
|
||||
CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE
|
||||
};
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await DeleteByIdsAsync(ids.Cast<object>().ToArray());//删除按钮
|
||||
var relationRep = ChangeRepository<DbRepository<SysRelation>>();//切换仓储
|
||||
//删除关系表角色与资源关系,角色与权限关系
|
||||
await relationRep.DeleteAsync(it => ids.Contains(it.ObjectId) && delRelations.Contains(it.Category));
|
||||
//删除关系表角色与用户关系
|
||||
await relationRep.DeleteAsync(it => targetIds.Contains(it.TargetId) && it.Category == CateGoryConst.RELATION_SYS_USER_HAS_ROLE);
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await RefreshCache();//刷新缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_USER_HAS_ROLE);//关系表刷新SYS_USER_HAS_ROLE缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);//关系表刷新Relation_SYS_ROLE_HAS_RESOURCE缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_PERMISSION);//关系表刷新Relation_SYS_ROLE_HAS_PERMISSION缓存
|
||||
await _relationService.RefreshCache(CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE);//关系表刷新RELATION_SYS_ROLE_HAS_MODULE缓存
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_USER_CACHE, ids);//清除角色下用户缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0002);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RefreshCache()
|
||||
{
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_ROLE);//删除KEY
|
||||
await GetListAsync();//重新缓存
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysRole"></param>
|
||||
private async Task CheckInput(SysRole sysRole)
|
||||
{
|
||||
//判断分类
|
||||
if (sysRole.Category != CateGoryConst.ROLE_GLOBAL && sysRole.Category != CateGoryConst.ROLE_ORG)
|
||||
throw Oops.Bah($"角色所属分类错误:{sysRole.Category}");
|
||||
//如果是机构角色orgId不能为空
|
||||
if (sysRole is { Category: CateGoryConst.ROLE_ORG, OrgId: null })
|
||||
throw Oops.Bah("orgId不能为空");
|
||||
if (sysRole.Category == CateGoryConst.ROLE_GLOBAL)//如果是全局
|
||||
sysRole.OrgId = null;//机构id设null
|
||||
|
||||
var sysRoles = await GetListAsync();//获取所有
|
||||
var repeatName = sysRoles.Any(it => it.OrgId == sysRole.OrgId && it.Name == sysRole.Name && it.Id != sysRole.Id);//是否有重复角色名称
|
||||
if (repeatName)//如果有
|
||||
{
|
||||
if (sysRole.OrgId == null)
|
||||
throw Oops.Bah($"存在重复的全局角色:{sysRole.Name}");
|
||||
throw Oops.Bah($"同组织下存在重复的角色:{sysRole.Name}");
|
||||
}
|
||||
//如果code没填
|
||||
if (string.IsNullOrEmpty(sysRole.Code))
|
||||
{
|
||||
sysRole.Code = RandomHelper.CreateRandomString(10);//赋值Code
|
||||
}
|
||||
else
|
||||
{
|
||||
//判断是否有相同的Code
|
||||
if (sysRoles.Any(it => it.Code == sysRole.Code && it.Id != sysRole.Id))
|
||||
throw Oops.Bah($"存在重复的编码:{sysRole.Code}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 单页输入参数
|
||||
/// </summary>
|
||||
public class SpaPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单类型
|
||||
/// </summary>
|
||||
public string MenuType { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单页输入参数
|
||||
/// </summary>
|
||||
public class SpaAddInput : SysResource
|
||||
{
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Title不能为空")]
|
||||
public override string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单类型
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Title不能为空")]
|
||||
public override string MenuType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Path不能为空")]
|
||||
public override string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Icon不能为空")]
|
||||
public override string Icon { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单页修改参数
|
||||
/// </summary>
|
||||
public class SpaEditInput : SpaAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 单页服务
|
||||
/// </summary>
|
||||
public interface ISpaService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加单页
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(SpaAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除单页
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑单页
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(SpaEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysResource>> Page(SpaPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysResource> Detail(BaseIdInput input);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ISpaService"/>
|
||||
/// </summary>
|
||||
public class SpaService : DbRepository<SysResource>, ISpaService
|
||||
{
|
||||
private readonly IResourceService _resourceService;
|
||||
|
||||
public SpaService(IResourceService resourceService)
|
||||
{
|
||||
_resourceService = resourceService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysResource>> Page(SpaPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysResource>().Where(it => it.Category == CateGoryConst.RESOURCE_SPA)//单页
|
||||
.WhereIF(!string.IsNullOrEmpty(input.MenuType), it => it.MenuType == input.MenuType)//根据菜单类型查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Title.Contains(input.SearchKey) || it.Path.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}").OrderBy(it => it.SortCode);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(SpaAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
input.Code = RandomHelper.CreateRandomString(10);//code取随机值
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
if (await InsertAsync(sysResource))//插入数据
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_SPA);//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(SpaEditInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var sysResource = input.Adapt<SysResource>();//实体转换
|
||||
if (await UpdateAsync(sysResource))//更新数据
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_SPA);//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//获取所有
|
||||
var resourceList = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_SPA);
|
||||
//找到要删除的
|
||||
var sysresources = resourceList.Where(it => ids.Contains(it.Id)).ToList();
|
||||
//查找内置单页面
|
||||
var system = sysresources.Where(it => it.Code == SysResourceConst.SYSTEM).FirstOrDefault();
|
||||
if (system != null)
|
||||
throw Oops.Bah($"不可删除系统内置单页面:{system.Title}");
|
||||
//删除菜单
|
||||
await DeleteAsync(sysresources);
|
||||
await _resourceService.RefreshCache(CateGoryConst.RESOURCE_SPA);//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysResource> Detail(BaseIdInput input)
|
||||
{
|
||||
var sysResources = await _resourceService.GetListByCategory(CateGoryConst.RESOURCE_SPA);
|
||||
var resource = sysResources.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
return resource;
|
||||
}
|
||||
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysResource"></param>
|
||||
private async Task CheckInput(SysResource sysResource)
|
||||
{
|
||||
//判断菜单类型
|
||||
if (sysResource.MenuType == SysResourceConst.MENU)//如果是菜单
|
||||
{
|
||||
if (string.IsNullOrEmpty(sysResource.Name))
|
||||
{
|
||||
throw Oops.Bah("单页名称不能为空");
|
||||
}
|
||||
if (string.IsNullOrEmpty(sysResource.Component))
|
||||
{
|
||||
throw Oops.Bah("组件地址不能为空");
|
||||
}
|
||||
}
|
||||
else if (sysResource.MenuType == SysResourceConst.IFRAME || sysResource.MenuType == SysResourceConst.LINK)//如果是内链或者外链
|
||||
{
|
||||
// sysResource.Name = RandomHelper.CreateNum(10);//设置name为随机数
|
||||
sysResource.Name = null;//设置name为标题
|
||||
sysResource.Component = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"单页类型错误:{sysResource.MenuType}");//都不是
|
||||
}
|
||||
if (sysResource.IsHome)
|
||||
{
|
||||
var spas = await _resourceService.GetListByCategory(SysResourceConst.SPA);//获取所有单页
|
||||
if (spas.Any(it => it.IsHome && it.Id != sysResource.Id))//如果有多个主页
|
||||
{
|
||||
throw Oops.Bah("已存在首页,请取消其他主页后再试");
|
||||
}
|
||||
sysResource.IsHide = false;//如果是主页,则不隐藏
|
||||
sysResource.IsAffix = true;//如果是主页,则固定
|
||||
}
|
||||
//设置为单页
|
||||
sysResource.Category = CateGoryConst.RESOURCE_SPA;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志分页输入
|
||||
/// </summary>
|
||||
public class OperateLogPageInput : VisitLogPageInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志日志删除输入
|
||||
/// </summary>
|
||||
public class OperateLogDeleteInput : VisitLogDeleteInput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志周统计输出
|
||||
/// </summary>
|
||||
public class OperateLogDayStatisticsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 日期
|
||||
/// </summary>
|
||||
public string Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数量
|
||||
/// </summary>
|
||||
public int Count { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志统计
|
||||
/// </summary>
|
||||
public class OperateLogTotalCountOutput : VisitLogTotalCountOutput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志服务
|
||||
/// </summary>
|
||||
public interface IOperateLogService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据分类删除操作日志
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 日志详情
|
||||
/// </summary>
|
||||
/// <param name="input">id</param>
|
||||
/// <returns>日志详情</returns>
|
||||
Task<SysLogOperate> Detail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页列表</returns>
|
||||
Task<SqlSugarPagedList<SysLogOperate>> Page(OperateLogPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志周统计
|
||||
/// </summary>
|
||||
/// <param name="day">天数</param>
|
||||
/// <returns>统计结果</returns>
|
||||
Task<List<OperateLogDayStatisticsOutput>> StatisticsByDay(int day);
|
||||
|
||||
/// <summary>
|
||||
/// 操作日志总览
|
||||
/// </summary>
|
||||
/// <returns>异常日志和操作日志的数量</returns>
|
||||
Task<List<OperateLogTotalCountOutput>> TotalCount();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IOperateLogService"/>
|
||||
/// </summary>
|
||||
public class OperateLogService : DbRepository<SysLogOperate>, IOperateLogService
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作日志中文名称
|
||||
/// </summary>
|
||||
private readonly string _nameOperate = "操作日志";
|
||||
|
||||
/// <summary>
|
||||
/// 异常日志中文名称
|
||||
/// </summary>
|
||||
private readonly string _nameException = "异常日志";
|
||||
|
||||
/// <summary>
|
||||
/// 分表最多查近多少年的数据
|
||||
/// </summary>
|
||||
private readonly int _maxTabs = 100;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysLogOperate>> Page(OperateLogPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysLogOperate>().WhereIF(!string.IsNullOrEmpty(input.Account), it => it.OpAccount == input.Account)//根据账号查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey) || it.OpIp.Contains(input.SearchKey))//根据关键字查询
|
||||
.IgnoreColumns(it => new { it.ParamJson, it.ResultJson }).SplitTable(tabs => tabs.Take(_maxTabs))
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.CreateTime, OrderByType.Desc);
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<OperateLogDayStatisticsOutput>> StatisticsByDay(int day)
|
||||
{
|
||||
//取最近七天
|
||||
var dayArray = Enumerable.Range(0, day).Select(it => DateTime.Now.Date.AddDays(it * -1)).ToList();
|
||||
//生成时间表
|
||||
var queryableLeft = Context.Reportable(dayArray).ToQueryable<DateTime>();
|
||||
//ReportableDateType.MonthsInLast1yea 表式近一年月份 并且queryable之后还能在where过滤
|
||||
var queryableRight = Context.Queryable<SysLogOperate>().SplitTable(tabs => tabs.Take(_maxTabs));//声名表
|
||||
//报表查询
|
||||
var list = await Context
|
||||
.Queryable(queryableLeft, queryableRight, JoinType.Left,
|
||||
(x1, x2) => x2.CreateTime.Value.ToString("yyyy-MM-dd") == x1.ColumnName.ToString("yyyy-MM-dd"))
|
||||
.GroupBy((x1, x2) => x1.ColumnName)//根据时间分组
|
||||
.OrderBy((x1, x2) => x1.ColumnName)//根据时间升序排序
|
||||
.Select((x1, x2) => new
|
||||
{
|
||||
OperateCount = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Category == CateGoryConst.LOG_OPERATE, 1, 0)),//null的数据要为0所以不能用count
|
||||
ExceptionCount = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Category == CateGoryConst.LOG_EXCEPTION, 1, 0)),//null的数据要为0所以不能用count
|
||||
Date = x1.ColumnName.ToString("yyyy-MM-dd")
|
||||
}).ToListAsync();
|
||||
//定义返回结果
|
||||
var result = new List<OperateLogDayStatisticsOutput>();
|
||||
//遍历结果
|
||||
list.ForEach(it =>
|
||||
{
|
||||
result.Add(new OperateLogDayStatisticsOutput { Date = it.Date, Name = _nameOperate, Count = it.OperateCount });//添加访问日志
|
||||
result.Add(new OperateLogDayStatisticsOutput { Date = it.Date, Name = _nameException, Count = it.ExceptionCount });//添加异常日志
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<OperateLogTotalCountOutput>> TotalCount()
|
||||
{
|
||||
var data = await Context.Queryable<SysLogOperate>().SplitTable(tabs => tabs.Take(_maxTabs)).GroupBy(it => it.Category)//根据分类分组
|
||||
.Select(it => new
|
||||
{
|
||||
it.Category,//分类
|
||||
Count = SqlFunc.AggregateCount(it.Category)//数量
|
||||
}).ToListAsync();
|
||||
//定义结果数组
|
||||
var operateLogTotalCounts = new List<OperateLogTotalCountOutput>
|
||||
{
|
||||
//添加操作日志数据
|
||||
new OperateLogTotalCountOutput
|
||||
{
|
||||
Type = _nameOperate,
|
||||
Value = data.Where(it => it.Category == CateGoryConst.LOG_OPERATE).Select(it => it.Count).FirstOrDefault()
|
||||
},
|
||||
//添加异常日志数据
|
||||
new OperateLogTotalCountOutput
|
||||
{
|
||||
Type = _nameException,
|
||||
Value = data.Where(it => it.Category == CateGoryConst.LOG_EXCEPTION).Select(it => it.Count).FirstOrDefault()
|
||||
}
|
||||
};
|
||||
return operateLogTotalCounts;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(string category)
|
||||
{
|
||||
await Context.Deleteable<SysLogOperate>().Where(it => it.Category == category).SplitTable(tabs => tabs.Take(_maxTabs))
|
||||
.ExecuteCommandAsync();//删除对应分类日志
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysLogOperate> Detail(BaseIdInput input)
|
||||
{
|
||||
return await GetFirstSplitTableAsync(it => it.Id == input.Id, tabs => tabs.Take(_maxTabs));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 访问日志分页输入
|
||||
/// </summary>
|
||||
public class VisitLogPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string Account { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 访问日志删除输入
|
||||
/// </summary>
|
||||
public class VisitLogDeleteInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 周统计输出
|
||||
/// </summary>
|
||||
public class VisitLogDayStatisticsOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 日期
|
||||
/// </summary>
|
||||
public string Date { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登录次数
|
||||
/// </summary>
|
||||
public int LoginCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 登出次数
|
||||
/// </summary>
|
||||
public int LogoutCount { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 访问日志总数输出
|
||||
/// </summary>
|
||||
public class VisitLogTotalCountOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 类型
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数量
|
||||
/// </summary>
|
||||
public int Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 访问日志服务
|
||||
/// </summary>
|
||||
public interface IVisitLogService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据分类删除
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 访问日志分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>日志列表</returns>
|
||||
Task<SqlSugarPagedList<SysLogVisit>> Page(VisitLogPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根统计N天来登录和登出数量
|
||||
/// </summary>
|
||||
/// <param name="day">天使</param>
|
||||
/// <returns>统计信息</returns>
|
||||
Task<List<VisitLogDayStatisticsOutput>> StatisticsByDay(int day);
|
||||
|
||||
/// <summary>
|
||||
/// 统计登录登出总览
|
||||
/// </summary>
|
||||
/// <returns>登录和登出的数量</returns>
|
||||
Task<List<VisitLogTotalCountOutput>> TotalCount();
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IVisitLogService"/>
|
||||
/// </summary>
|
||||
public class VisitLogService : DbRepository<SysLogVisit>, IVisitLogService
|
||||
{
|
||||
/// <summary>
|
||||
/// 分表最多查近多少年的数据
|
||||
/// </summary>
|
||||
private readonly int _maxTabs = 100;
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysLogVisit>> Page(VisitLogPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysLogVisit>().WhereIF(!string.IsNullOrEmpty(input.Account), it => it.OpAccount == input.Account)//根据账号查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey) || it.OpIp.Contains(input.SearchKey))//根据关键字查询
|
||||
.SplitTable(tabs => tabs.Take(_maxTabs)).OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.CreateTime, OrderByType.Desc);
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<VisitLogDayStatisticsOutput>> StatisticsByDay(int day)
|
||||
{
|
||||
//取最近七天
|
||||
var dayArray = Enumerable.Range(0, day).Select(it => DateTime.Now.Date.AddDays(it * -1)).ToList();
|
||||
//生成时间表
|
||||
var queryableLeft = Context.Reportable(dayArray).ToQueryable<DateTime>();
|
||||
//ReportableDateType.MonthsInLast1yea 表式近一年月份 并且queryable之后还能在where过滤
|
||||
var queryableRight = Context.Queryable<SysLogVisit>().SplitTable(tabs => tabs.Take(_maxTabs));//声名表
|
||||
//报表查询
|
||||
var list = await Context
|
||||
.Queryable(queryableLeft, queryableRight, JoinType.Left,
|
||||
(x1, x2) => x2.CreateTime.Value.ToString("yyyy-MM-dd") == x1.ColumnName.ToString("yyyy-MM-dd"))
|
||||
.GroupBy((x1, x2) => x1.ColumnName)//根据时间分组
|
||||
.OrderBy((x1, x2) => x1.ColumnName)//根据时间升序排序
|
||||
.Select((x1, x2) => new VisitLogDayStatisticsOutput
|
||||
{
|
||||
LoginCount = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Category == CateGoryConst.LOG_LOGIN, 1, 0)),//null的数据要为0所以不能用count
|
||||
LogoutCount = SqlFunc.AggregateSum(SqlFunc.IIF(x2.Category == CateGoryConst.LOG_LOGOUT, 1, 0)),//null的数据要为0所以不能用count
|
||||
Date = x1.ColumnName.ToString("yyyy-MM-dd")
|
||||
}).ToListAsync();
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<VisitLogTotalCountOutput>> TotalCount()
|
||||
{
|
||||
var data = await Context.Queryable<SysLogVisit>().SplitTable(tabs => tabs.Take(_maxTabs)).GroupBy(it => it.Category)//根据分类分组
|
||||
.Select(it => new
|
||||
{
|
||||
it.Category,//分类
|
||||
Count = SqlFunc.AggregateCount(it.Category)//数量
|
||||
}).ToListAsync();
|
||||
//定义结果数组
|
||||
var visitLogTotalCounts = new List<VisitLogTotalCountOutput>
|
||||
{
|
||||
//添加登录数据
|
||||
new VisitLogTotalCountOutput
|
||||
{
|
||||
Type = EventSubscriberConst.LOGIN_B,
|
||||
Value = data.Where(it => it.Category == CateGoryConst.LOG_LOGIN).Select(it => it.Count).FirstOrDefault()
|
||||
},
|
||||
//添加登出数据
|
||||
new VisitLogTotalCountOutput
|
||||
{
|
||||
Type = EventSubscriberConst.LOGIN_OUT_B,
|
||||
Value = data.Where(it => it.Category == CateGoryConst.LOG_LOGOUT).Select(it => it.Count).FirstOrDefault()
|
||||
}
|
||||
};
|
||||
return visitLogTotalCounts;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(string category)
|
||||
{
|
||||
await Context.Deleteable<SysLogVisit>().Where(it => it.Category == category).SplitTable(tabs => tabs.Take(_maxTabs))
|
||||
.ExecuteCommandAsync();//删除对应分类日志
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// mqtt认证参数
|
||||
/// </summary>
|
||||
public class MqttAuthInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户ID
|
||||
/// </summary>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户端ID
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// mqtt登录参数输出
|
||||
/// </summary>
|
||||
public class MqttParameterOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 地址
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户名
|
||||
/// </summary>
|
||||
public string UserName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 客户端ID
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主题列表
|
||||
/// </summary>
|
||||
public List<string> Topics { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// mqtt认证输出
|
||||
/// </summary>
|
||||
public class MqttAuthOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 结果 "allow" | "deny" | "ignore", // Default `"ignore"`
|
||||
/// </summary>
|
||||
public string Result { get; set; } = "deny";
|
||||
|
||||
/// <summary>
|
||||
/// 是否超级管理员
|
||||
/// </summary>
|
||||
public bool Is_superuser { get; set; } = false;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
///Mqtt服务
|
||||
/// </summary>
|
||||
public interface IMqttService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// mqtt登录http认证
|
||||
/// </summary>
|
||||
/// <param name="input">认证参数</param>
|
||||
/// <param name="userId">用户Id</param>
|
||||
/// <returns>认证结果</returns>
|
||||
Task<MqttAuthOutput> Auth(MqttAuthInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取mqtt登录web端参数
|
||||
/// </summary>
|
||||
/// <returns>登录参数</returns>
|
||||
Task<MqttParameterOutput> GetWebLoginParameter();
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IMqttService"/>
|
||||
/// </summary>
|
||||
public class MqttService : IMqttService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly ISysUserService _sysUserService;
|
||||
private readonly IConfigService _configService;
|
||||
|
||||
public MqttService(ISimpleCacheService simpleCacheService, ISysUserService sysUserService, IConfigService configService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_sysUserService = sysUserService;
|
||||
_configService = configService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<MqttParameterOutput> GetWebLoginParameter()
|
||||
{
|
||||
var user = await _sysUserService.GetUserById(UserManager.UserId);//获取用户信息
|
||||
var token = JWTEncryption.GetJwtBearerToken((DefaultHttpContext)App.HttpContext);// 获取当前token
|
||||
//获取mqtt配置
|
||||
var mqttconfig = await _configService.GetConfigsByCategory(CateGoryConst.CONFIG_MQTT_BASE);
|
||||
//地址
|
||||
var url = mqttconfig.Where(it => it.ConfigKey == SysConfigConst.MQTT_PARAM_URL).Select(it => it.ConfigValue).FirstOrDefault();
|
||||
//用户名
|
||||
var userName = mqttconfig.Where(it => it.ConfigKey == SysConfigConst.MQTT_PARAM_USERNAME).Select(it => it.ConfigValue).FirstOrDefault();
|
||||
//密码
|
||||
var password = mqttconfig.Where(it => it.ConfigKey == SysConfigConst.MQTT_PARAM_PASSWORD).Select(it => it.ConfigValue).FirstOrDefault();
|
||||
|
||||
#region 用户名特殊处理
|
||||
|
||||
if (userName.ToLower() == "$username")
|
||||
userName = user.Account;
|
||||
else if (userName.ToLower() == "$userid")
|
||||
userName = user.Id.ToString();
|
||||
|
||||
#endregion 用户名特殊处理
|
||||
|
||||
#region 密码特殊处理
|
||||
|
||||
if (password.ToLower() == "$username")
|
||||
password = token;// 当前token作为mqtt密码
|
||||
|
||||
#endregion 密码特殊处理
|
||||
|
||||
var clientId = $"{user.Id}_{RandomHelper.CreateLetterAndNumber(5)}";//客户端ID
|
||||
_simpleCacheService.Set(MqttConst.CACHE_MQTT_CLIENT_USER + clientId, token,
|
||||
TimeSpan.FromMinutes(1));//将该客户端ID对应的token插入redis后面可以根据这个判断是哪个token登录的
|
||||
return new MqttParameterOutput
|
||||
{
|
||||
ClientId = clientId,
|
||||
Password = password,
|
||||
Url = url,
|
||||
UserName = userName,
|
||||
Topics = new List<string> { MqttConst.MQTT_TOPIC_PREFIX + user.Id }//默认监听自己
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<MqttAuthOutput> Auth(MqttAuthInput input)
|
||||
{
|
||||
var user = await _sysUserService.GetUserByAccount(input.Username);
|
||||
var mqttAuthOutput = new MqttAuthOutput { Is_superuser = false, Result = "deny" };
|
||||
//获取用户token
|
||||
var tokens = _simpleCacheService.HashGetOne<List<TokenInfo>>(CacheConst.CACHE_USER_TOKEN, user.Id.ToString());
|
||||
if (tokens != null)
|
||||
{
|
||||
if (tokens.Any(it => it.Token == input.Password))//判断是否有token
|
||||
mqttAuthOutput.Result = "allow";//允许登录
|
||||
}
|
||||
return mqttAuthOutput;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="IConfigService"/>
|
||||
public class ConfigService : DbRepository<SysConfig>, IConfigService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
|
||||
public ConfigService(ISimpleCacheService simpleCacheService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysConfig>> GetConfigsByCategory(string category)
|
||||
{
|
||||
var key = SystemConst.CACHE_DEV_CONFIG + category;//系统配置key
|
||||
//先从redis拿配置
|
||||
var configList = _simpleCacheService.HashGetAll<SysConfig>(key).Values.ToList();
|
||||
if (configList.Count == 0)
|
||||
{
|
||||
//redis没有再去数据可拿
|
||||
var configs = await GetListAsync(it => it.Category == category);//获取系统配置列表
|
||||
if (configs.Count > 0)
|
||||
{
|
||||
configList = configs;
|
||||
_simpleCacheService.HashSet(key, configList.ToDictionary(it => it.ConfigKey));//如果不为空,插入redis
|
||||
}
|
||||
}
|
||||
return configList.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysConfig>> GetSysConfigList()
|
||||
{
|
||||
//这里不返回系统配置
|
||||
return await GetListAsync(it => it.Category != CateGoryConst.CONFIG_BIZ_DEFINE);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SysConfig> GetByConfigKey(string category, string configKey)
|
||||
{
|
||||
var key = SystemConst.CACHE_DEV_CONFIG + category;//系统配置key
|
||||
//先从redis拿配置
|
||||
var config = _simpleCacheService.HashGetOne<SysConfig>(key, configKey);
|
||||
if (config == null)
|
||||
{
|
||||
//redis没有再去数据可拿
|
||||
config = await GetFirstAsync(it => it.Category == category && it.ConfigKey == configKey);//获取系统配置
|
||||
if (config != null)
|
||||
{
|
||||
await GetConfigsByCategory(category);//获取全部字典,并插入redis
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysConfig>> Page(ConfigPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysConfig>().Where(it => it.Category == CateGoryConst.CONFIG_BIZ_DEFINE)//自定义配置
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey),
|
||||
it => it.ConfigKey.Contains(input.SearchKey) || it.ConfigValue.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")//排序
|
||||
.OrderBy(it => it.SortCode)
|
||||
.OrderBy(it => it.CreateTime);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<FileStreamResult> GetIco()
|
||||
{
|
||||
var config = await GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_ICO);//获取系统配置 ico
|
||||
var icoBase64 = config.ConfigValue.Split(",")[1];//获取ico的base64
|
||||
//转为byte数组
|
||||
var bytes = Convert.FromBase64String(icoBase64);
|
||||
//将byte数组转为流
|
||||
var stream = new MemoryStream(bytes);
|
||||
return new FileStreamResult(stream, "image/x-icon");
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<bool> IsTenant()
|
||||
{
|
||||
var tenantOption = await GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_TENANT_OPTIONS);
|
||||
var isTenant = tenantOption.ConfigValue != SysDictConst.TENANT_OPTIONS_CLOSE;//是否开启多租户
|
||||
return isTenant;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 添加
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Add(ConfigAddInput input)
|
||||
{
|
||||
await CheckInput(input);
|
||||
var devConfig = input.Adapt<SysConfig>();//实体转换
|
||||
if (await InsertAsync(devConfig))//插入数据)
|
||||
await RefreshCache(CateGoryConst.CONFIG_BIZ_DEFINE);//刷新缓存
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Edit(ConfigEditInput input)
|
||||
{
|
||||
await CheckInput(input);
|
||||
var devConfig = input.Adapt<SysConfig>();//实体转换
|
||||
if (await UpdateAsync(devConfig))//更新数据
|
||||
await RefreshCache(CateGoryConst.CONFIG_BIZ_DEFINE);//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task EditBatch(List<SysConfig> devConfigs)
|
||||
{
|
||||
if (devConfigs.Count > 0)
|
||||
{
|
||||
//根据分类分组
|
||||
var configGroups = devConfigs.GroupBy(it => it.Category).ToList();
|
||||
//遍历分组
|
||||
foreach (var item in configGroups)
|
||||
{
|
||||
var configList = await GetConfigsByCategory(item.Key);//获取系统配置
|
||||
var configs = item.ToList();//获取分组完的配置列表
|
||||
var keys = configs.Select(it => it.ConfigKey).ToList();//获取key列表
|
||||
configList = configList.Where(it => keys.Contains(it.ConfigKey)).ToList();//获取要修改的列表
|
||||
//遍历配置列表
|
||||
configList.ForEach(it =>
|
||||
{
|
||||
//赋值ConfigValue
|
||||
it.ConfigValue = configs.Where(c => c.ConfigKey == it.ConfigKey).First().ConfigValue;
|
||||
});
|
||||
//更新数据
|
||||
if (await UpdateRangeAsync(configList))
|
||||
await RefreshCache(item.Key);//刷新缓存
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task Delete(ConfigDeleteInput input)
|
||||
{
|
||||
//获取所有业务配置
|
||||
var configs = await GetConfigsByCategory(CateGoryConst.CONFIG_BIZ_DEFINE);
|
||||
if (configs.Any(it => it.Id == input.Id))//如果有当前配置
|
||||
{
|
||||
if (await DeleteByIdAsync(input.Id))//删除配置
|
||||
await RefreshCache(CateGoryConst.CONFIG_BIZ_DEFINE);//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
private async Task RefreshCache(string category)
|
||||
{
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_DEV_CONFIG + category);//redis删除
|
||||
await GetConfigsByCategory(category);//重新获取
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysConfig"></param>
|
||||
private async Task CheckInput(SysConfig sysConfig)
|
||||
{
|
||||
var configs = await GetConfigsByCategory(CateGoryConst.CONFIG_BIZ_DEFINE);//获取全部字典
|
||||
//判断是否从存在重复字典名
|
||||
var hasSameKey = configs.Any(it => it.ConfigKey == sysConfig.ConfigKey && it.Id != sysConfig.Id);
|
||||
if (hasSameKey)
|
||||
{
|
||||
throw Oops.Bah($"存在重复的配置键:{sysConfig.ConfigKey}");
|
||||
}
|
||||
//设置分类为业务
|
||||
sysConfig.Category = CateGoryConst.CONFIG_BIZ_DEFINE;
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 配置分页参数
|
||||
/// </summary>
|
||||
public class ConfigPageInput : BasePageInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加配置参数
|
||||
/// </summary>
|
||||
public class ConfigAddInput : SysConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 配置键
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "configKey不能为空")]
|
||||
public override string ConfigKey { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 配置值
|
||||
/// </summary>
|
||||
|
||||
[Required(ErrorMessage = "ConfigValue不能为空")]
|
||||
public override string ConfigValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑配置参数
|
||||
/// </summary>
|
||||
public class ConfigEditInput : ConfigAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除配置参数
|
||||
/// </summary>
|
||||
public class ConfigDeleteInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量修改输入参数
|
||||
/// </summary>
|
||||
public class ConfigEditBatchInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string CateGory { get; set; }
|
||||
|
||||
public List<SysConfig> DevConfigs { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 系统配置服务
|
||||
/// </summary>
|
||||
public interface IConfigService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 根据分类和配置键获配置
|
||||
/// </summary>
|
||||
/// <param name="category">分类</param>
|
||||
/// <param name="configKey">配置键</param>
|
||||
/// <returns>配置信息</returns>
|
||||
Task<SysConfig> GetByConfigKey(string category, string configKey);
|
||||
|
||||
/// <summary>
|
||||
/// 根据分类获取配置列表
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns>配置列表</returns>
|
||||
Task<List<SysConfig>> GetConfigsByCategory(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询其他配置
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>其他配置列表</returns>
|
||||
Task<SqlSugarPagedList<SysConfig>> Page(ConfigPageInput input);
|
||||
|
||||
Task<FileStreamResult> GetIco();
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统配置列表,不包含业务的
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysConfig>> GetSysConfigList();
|
||||
|
||||
/// <summary>
|
||||
/// 是否是多租户模式
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<bool> IsTenant();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <summary>
|
||||
/// 新增配置
|
||||
/// </summary>
|
||||
/// <param name="input">新增参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(ConfigAddInput input);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 修改配置
|
||||
/// </summary>
|
||||
/// <param name="input">修改参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(ConfigEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 批量编辑
|
||||
/// </summary>
|
||||
/// <param name="devConfigs">配置列表</param>
|
||||
/// <returns></returns>
|
||||
Task EditBatch(List<SysConfig> devConfigs);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <summary>
|
||||
/// 删除配置
|
||||
/// </summary>
|
||||
/// <param name="input">删除</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(ConfigDeleteInput input);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="IDictService"/>
|
||||
/// </summary>
|
||||
public class DictService : DbRepository<SysDict>, IDictService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
|
||||
public DictService(ISimpleCacheService simpleCacheService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysDict>> Page(DictPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysDict>().Where(it => it.Category == input.Category)//根据分类查询
|
||||
.Where(it => it.ParentId == input.ParentId)//根据父ID查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey),
|
||||
it => it.DictLabel.Contains(input.SearchKey) || it.DictValue.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")
|
||||
.OrderBy(it => it.SortCode)//排序
|
||||
.OrderBy(it => it.CreateTime);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(DictAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var devDict = input.Adapt<SysDict>();//实体转换
|
||||
if (await InsertAsync(devDict))//插入数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(DictAddInput input)
|
||||
{
|
||||
await CheckInput(input);//检查参数
|
||||
var devDict = input.Adapt<SysDict>();//实体转换
|
||||
if (await UpdateAsync(devDict))//更新数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(DictDeleteInput input)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//获取所有字典
|
||||
var dictList = await GetListAsync();
|
||||
//判断是否有系统字典
|
||||
var frm = dictList.Any(it => ids.Contains(it.Id) && it.Category == CateGoryConst.DICT_FRM);
|
||||
|
||||
//如果是系统字典提示不可删除
|
||||
if (frm) throw Oops.Bah("不可删除系统内置字典");
|
||||
var deleteIds = new List<long>();//要删除的id列表
|
||||
deleteIds.AddRange(ids);//
|
||||
ids.ForEach(it =>
|
||||
{
|
||||
//获取子节点
|
||||
var children = GetDevDictChildren(dictList, it);
|
||||
//提取ID
|
||||
var childrenIds = children.Select(c => c.Id).ToList();
|
||||
deleteIds.AddRange(childrenIds);
|
||||
});
|
||||
//删除数据
|
||||
if (await DeleteByIdsAsync(deleteIds.Cast<object>().ToArray()))
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全部
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override async Task<List<SysDict>> GetListAsync()
|
||||
{
|
||||
//先从redis拿
|
||||
var sysDictList = _simpleCacheService.Get<List<SysDict>>(SystemConst.CACHE_DEV_DICT);
|
||||
if (sysDictList == null)
|
||||
{
|
||||
sysDictList = await base.GetListAsync();//去数据库拿
|
||||
if (sysDictList.Count > 0)
|
||||
{
|
||||
_simpleCacheService.Set(SystemConst.CACHE_DEV_DICT, sysDictList);//如果数据库有数,更新redis
|
||||
return sysDictList;
|
||||
}
|
||||
}
|
||||
return sysDictList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysDict> GetDict(string dictValue)
|
||||
{
|
||||
var sysDictList = await GetListAsync();
|
||||
var devDict = sysDictList.Where(it => it.DictValue == dictValue).FirstOrDefault();
|
||||
return devDict;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysDict>> GetChildrenByDictValue(string dictValue)
|
||||
{
|
||||
var sysDictList = await GetListAsync();
|
||||
var devDict = sysDictList.Where(it => it.DictValue == dictValue).FirstOrDefault();
|
||||
if (devDict != null)
|
||||
{
|
||||
var children = GetDevDictChildren(sysDictList, devDict.Id);
|
||||
return children;
|
||||
}
|
||||
return new List<SysDict>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<string>> GetValuesByDictValue(string dictValue, List<SysDict> devDictList = null)
|
||||
{
|
||||
var sysDictList = devDictList == null ? await GetListAsync() : devDictList;//获取全部
|
||||
var id = sysDictList.Where(it => it.DictValue == dictValue).Select(it => it.Id).FirstOrDefault();//根据value找到父节点
|
||||
if (id > 0)
|
||||
return sysDictList.Where(it => it.ParentId == id).Select(it => it.DictValue).ToList();//拿到字典值
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Dictionary<string, List<string>>> GetValuesByDictValue(string[] dictValues)
|
||||
{
|
||||
var result = new Dictionary<string, List<string>>();
|
||||
var sysDictList = await GetListAsync();//获取全部
|
||||
foreach (var dictValue in dictValues)
|
||||
{
|
||||
var data = await GetValuesByDictValue(dictValue, sysDictList);
|
||||
result.Add(dictValue, data);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysDict>> Tree(DictTreeInput input)
|
||||
{
|
||||
var devList= await Context.Queryable<SysDict>()
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)
|
||||
.WhereIF(!string.IsNullOrEmpty(input.DictValue), it => it.DictValue == input.DictValue)
|
||||
.OrderBy(it => it.SortCode)
|
||||
.ToListAsync();
|
||||
return ConstructResourceTrees(devList);
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public List<SysDict> ConstructResourceTrees(List<SysDict> dictList, long parentId = 0)
|
||||
{
|
||||
//找下级字典ID列表
|
||||
var resources = dictList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
|
||||
if (resources.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysDict>();
|
||||
foreach (var item in resources)//遍历字典
|
||||
{
|
||||
item.Children = ConstructResourceTrees(dictList, item.Id);//添加子节点
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysDict>();
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
private async Task RefreshCache()
|
||||
{
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_DEV_DICT);
|
||||
await GetListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysDict"></param>
|
||||
private async Task CheckInput(SysDict sysDict)
|
||||
{
|
||||
var dictList = await GetListAsync();//获取全部字典
|
||||
//判断是否从存在重复字典名
|
||||
var hasSameLabel = dictList.Any(it =>
|
||||
it.ParentId == sysDict.ParentId && it.Category == sysDict.Category && it.DictLabel == sysDict.DictLabel && it.Id != sysDict.Id);
|
||||
if (hasSameLabel)
|
||||
{
|
||||
throw Oops.Bah($"存在重复的字典名称:{sysDict.DictLabel}");
|
||||
}
|
||||
//判断是否存在重复字典值
|
||||
var hasSameValue = dictList.Any(it => it.ParentId == sysDict.ParentId && it.DictValue == sysDict.DictValue && it.Id != sysDict.Id);
|
||||
if (hasSameValue)
|
||||
{
|
||||
throw Oops.Bah($"存在重复的字典值:{sysDict.DictValue}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典所有下级
|
||||
/// </summary>
|
||||
/// <param name="dictList">字典列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <returns></returns>
|
||||
public List<SysDict> GetDevDictChildren(List<SysDict> dictList, long parentId)
|
||||
{
|
||||
//找下级ID列表
|
||||
var dicts = dictList.Where(it => it.ParentId == parentId).ToList();
|
||||
if (dicts.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysDict>();
|
||||
foreach (var item in dicts)//遍历机构
|
||||
{
|
||||
var devDictChildren = GetDevDictChildren(dictList, item.Id);
|
||||
data.AddRange(devDictChildren);//添加子节点);
|
||||
data.Add(item);////添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysDict>();
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 字典查询参数
|
||||
/// </summary>
|
||||
public class DictInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字典树参数
|
||||
/// </summary>
|
||||
public class DictTreeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 字典分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
public string DictValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 字典查询参数
|
||||
/// </summary>
|
||||
public class DictPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 父id
|
||||
///</summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
///</summary>
|
||||
public string Category { get; set; } = SysDictConst.DICT_CATEGORY_FRM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加字典参数
|
||||
/// </summary>
|
||||
public class DictAddInput : SysDict
|
||||
{
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
public override long ParentId { get; set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// 字典名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "DictLabel不能为空")]
|
||||
public override string DictLabel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 字典值
|
||||
/// </summary>
|
||||
|
||||
[Required(ErrorMessage = "DictValue不能为空")]
|
||||
public override string DictValue { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑字典参数
|
||||
/// </summary>
|
||||
public class DictEditInput : DictAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除字典参数
|
||||
/// </summary>
|
||||
public class DictDeleteInput : BaseIdListInput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 字典服务
|
||||
/// </summary>
|
||||
public interface IDictService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加字典
|
||||
/// </summary>
|
||||
/// <param name="input">输入参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(DictAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 构建字典树形结构
|
||||
/// </summary>
|
||||
/// <param name="dictList">字典列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <returns>字典树形结构</returns>
|
||||
List<SysDict> ConstructResourceTrees(List<SysDict> dictList, long parentId = 0);
|
||||
|
||||
/// <summary>
|
||||
/// 删除字典
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(DictDeleteInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑字典
|
||||
/// </summary>
|
||||
/// <param name="input">输入参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(DictAddInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典
|
||||
/// </summary>
|
||||
/// <param name="dictValue">字典</param>
|
||||
/// <returns></returns>
|
||||
Task<SysDict> GetDict(string dictValue);
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有
|
||||
/// </summary>
|
||||
/// <returns>字典列表</returns>
|
||||
Task<List<SysDict>> GetListAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 根据字典DictValue获取字典值列表
|
||||
/// </summary>
|
||||
/// <param name="dictValue">字典值</param>
|
||||
/// <param name="devDictList">字典列表</param>
|
||||
/// <returns>字典值列表</returns>
|
||||
Task<List<string>> GetValuesByDictValue(string dictValue, List<SysDict> devDictList = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据字典DictValue列表获取对应字典值列表
|
||||
/// </summary>
|
||||
/// <param name="dictValues">字典值列表</param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, List<string>>> GetValuesByDictValue(string[] dictValues);
|
||||
|
||||
/// <summary>
|
||||
/// 字典分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>字典分页列表</returns>
|
||||
Task<SqlSugarPagedList<SysDict>> Page(DictPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取字典树形结构
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>字典树形结构</returns>
|
||||
Task<List<SysDict>> Tree(DictTreeInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据字典值获取子级字典
|
||||
/// </summary>
|
||||
/// <param name="dictValue">字典值</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysDict>> GetChildrenByDictValue(string dictValue);
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 组织分页查询参数
|
||||
/// </summary>
|
||||
public class SysOrgPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 编码
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织添加参数
|
||||
/// </summary>
|
||||
public class SysOrgAddInput : SysOrg
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织修改参数
|
||||
/// </summary>
|
||||
public class SysOrgEditInput : SysOrgAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织复制参数
|
||||
/// </summary>
|
||||
public class SysOrgCopyInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标ID
|
||||
/// </summary>
|
||||
public long TargetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组织Id列表
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Ids列表不能为空")]
|
||||
public List<long>? Ids { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含下级
|
||||
/// </summary>
|
||||
public bool ContainsChild { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含职位
|
||||
/// </summary>
|
||||
public bool ContainsPosition { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织导入
|
||||
/// </summary>
|
||||
public class SysOrgImportInput : ImportTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "名称")]
|
||||
[Required(ErrorMessage = "名称不能为空")]
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 上级组织
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "上级组织")]
|
||||
[Required(ErrorMessage = "上级组织不能为空")]
|
||||
public string Names { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "分类")]
|
||||
[Required(ErrorMessage = "分类不能为空")]
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序码
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "排序码")]
|
||||
public int SortCode { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 主管账号
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "主管账号")]
|
||||
[Required(ErrorMessage = "主管账号不能为空")]
|
||||
public string Director { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织树查询参数
|
||||
/// 懒加载用
|
||||
/// </summary>
|
||||
public class SysOrgTreeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 父Id
|
||||
/// </summary>
|
||||
public long? ParentId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 组织架构服务
|
||||
/// </summary>
|
||||
public interface ISysOrgService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 检查组织是否存在
|
||||
/// </summary>
|
||||
/// <param name="sysOrgList">组织列表</param>
|
||||
/// <param name="orgName">组织名称</param>
|
||||
/// <param name="parentId">父Id</param>
|
||||
/// <param name="orgId">组织Id</param>
|
||||
/// <returns>是否存在,存在返回组织ID</returns>
|
||||
bool IsExistOrgByName(List<SysOrg> sysOrgList, string orgName, long parentId,
|
||||
out long orgId);
|
||||
|
||||
/// <summary>
|
||||
/// 组织详情
|
||||
/// </summary>
|
||||
/// <param name="input">id参数</param>
|
||||
/// <returns>组织信息</returns>
|
||||
Task<SysOrg> Detail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据组织ID获取 下级
|
||||
/// </summary>
|
||||
/// <param name="orgId">组织ID</param>
|
||||
/// <param name="isContainOneself">是否包含自己</param>
|
||||
/// <param name="sysOrgList">组织列表</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysOrg>> GetChildListById(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有组织
|
||||
/// </summary>
|
||||
/// <param name="showDisabled">是否显示禁用的</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysOrg>> GetListAsync(bool showDisabled = true);
|
||||
|
||||
/// <summary>
|
||||
/// 获取机构及下级ID列表
|
||||
/// </summary>
|
||||
/// <param name="orgId"></param>
|
||||
/// <param name="isContainOneself"></param>
|
||||
/// <param name="sysOrgList">组织列表</param>
|
||||
/// <returns></returns>
|
||||
Task<List<long>> GetOrgChildIds(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据组织Id递归获取上级
|
||||
/// </summary>
|
||||
/// <param name="allOrgList">组织列表</param>
|
||||
/// <param name="orgId">组织Id</param>
|
||||
/// <param name="includeSelf">是否包含自己</param>
|
||||
/// <returns></returns>
|
||||
List<SysOrg> GetOrgParents(List<SysOrg> allOrgList, long orgId, bool includeSelf = true);
|
||||
|
||||
/// <summary>
|
||||
/// 获取组织信息
|
||||
/// </summary>
|
||||
/// <param name="id">组织id</param>
|
||||
/// <returns>组织信息</returns>
|
||||
Task<SysOrg> GetSysOrgById(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 组织分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页信息</returns>
|
||||
Task<SqlSugarPagedList<SysOrg>> Page(SysOrgPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID列表获取组织列表
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysOrg>> GetOrgListByIdList(IdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取租户列表
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysOrg>> GetTenantList();
|
||||
|
||||
/// <summary>
|
||||
/// 根据组织ID获取租户ID
|
||||
/// </summary>
|
||||
/// <param name="orgId">组织id</param>
|
||||
/// <param name="sysOrgList">租户id</param>
|
||||
/// <returns></returns>
|
||||
Task<long?> GetTenantIdByOrgId(long orgId, List<SysOrg> sysOrgList = null);
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <summary>
|
||||
/// 添加组织
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Add(SysOrgAddInput input, string name = SystemConst.SYS_ORG);
|
||||
|
||||
/// <summary>
|
||||
/// 复制组织
|
||||
/// </summary>
|
||||
/// <param name="input">机构复制参数</param>
|
||||
/// <returns></returns>
|
||||
Task Copy(SysOrgCopyInput input);
|
||||
|
||||
#endregion 新增
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 编辑组织
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(SysOrgEditInput input, string name = SystemConst.SYS_ORG);
|
||||
|
||||
#endregion 编辑
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <summary>
|
||||
/// 删除组织
|
||||
/// </summary>
|
||||
/// <param name="input">删除参数</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input, string name = SystemConst.SYS_ORG);
|
||||
|
||||
#endregion 删除
|
||||
|
||||
#region 其他
|
||||
|
||||
/// <summary>
|
||||
/// 构建组织树形结构
|
||||
/// </summary>
|
||||
/// <param name="orgList">组织列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <returns>树型结构</returns>
|
||||
List<SysOrg> ConstructOrgTrees(List<SysOrg> orgList, long parentId = SimpleAdminConst.ZERO);
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RefreshCache();
|
||||
|
||||
/// <summary>
|
||||
/// 获取组织树型结构
|
||||
/// </summary>
|
||||
/// <param name="orgIds">机构ID列表</param>
|
||||
/// <param name="treeInput">组织选择器(懒加载用)</param>
|
||||
/// <returns>组织树列表</returns>
|
||||
Task<List<SysOrg>> Tree(List<long> orgIds = null, SysOrgTreeInput treeInput = null);
|
||||
|
||||
#endregion 其他
|
||||
}
|
||||
@@ -0,0 +1,643 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="ISysOrgService"/>
|
||||
public class SysOrgService : DbRepository<SysOrg>, ISysOrgService
|
||||
{
|
||||
private readonly ILogger<SysOrgService> _logger;
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IEventPublisher _eventPublisher;
|
||||
|
||||
public SysOrgService(ILogger<SysOrgService> logger, ISimpleCacheService simpleCacheService, IEventPublisher eventPublisher)
|
||||
{
|
||||
_logger = logger;
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysOrg>> GetListAsync(bool showDisabled = true)
|
||||
{
|
||||
var sysOrgList = _simpleCacheService.Get<List<SysOrg>>(SystemConst.CACHE_SYS_ORG);
|
||||
if (sysOrgList == null)
|
||||
{
|
||||
//redis没有就去数据库拿
|
||||
sysOrgList = await base.GetListAsync();
|
||||
if (sysOrgList.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(SystemConst.CACHE_SYS_ORG, sysOrgList);
|
||||
}
|
||||
}
|
||||
if (!showDisabled)
|
||||
{
|
||||
sysOrgList = sysOrgList.Where(it => it.Status == CommonStatusConst.ENABLE).ToList();
|
||||
}
|
||||
return sysOrgList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysOrg> GetSysOrgById(long id)
|
||||
{
|
||||
var sysOrg = await GetListAsync();
|
||||
var result = sysOrg.Where(it => it.Id == id).FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysOrg>> GetChildListById(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null)
|
||||
{
|
||||
//获取所有组织
|
||||
sysOrgList ??= await GetListAsync();
|
||||
//查找下级
|
||||
var childList = GetSysOrgChildren(sysOrgList, orgId);
|
||||
if (isContainOneself)//如果包含自己
|
||||
{
|
||||
//获取自己的组织信息
|
||||
var self = sysOrgList.Where(it => it.Id == orgId).FirstOrDefault();
|
||||
if (self != null) childList.Insert(0, self);//如果组织不为空就插到第一个
|
||||
}
|
||||
return childList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<long>> GetOrgChildIds(long orgId, bool isContainOneself = true, List<SysOrg> sysOrgList = null)
|
||||
{
|
||||
var orgIds = new List<long>();//组织列表
|
||||
if (orgId > 0)//如果orgId有值
|
||||
{
|
||||
//获取所有子集
|
||||
var childList = await GetChildListById(orgId, isContainOneself, sysOrgList);
|
||||
orgIds = childList.Select(x => x.Id).ToList();//提取ID列表
|
||||
}
|
||||
return orgIds;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysOrg>> Page(SysOrgPageInput input)
|
||||
{
|
||||
var query = Context.Queryable<SysOrg>()
|
||||
.WhereIF(input.ParentId > 0,
|
||||
it => it.ParentId == input.ParentId || it.Id == input.ParentId || SqlFunc.JsonLike(it.ParentIdList, input.ParentId.ToString()))//父级
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Name), it => it.Name.Contains(input.Name))//根据名称查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Code), it => it.Code.Contains(input.Code))//根据编码查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Status), it => it.Status == input.Status)//根据状态查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}").OrderBy(it => it.SortCode)
|
||||
.OrderBy(it => it.CreateTime);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysOrg>> Tree(List<long> orgIds = null, SysOrgTreeInput treeInput = null)
|
||||
{
|
||||
long parentId = SimpleAdminConst.ZERO;//父级ID
|
||||
//获取所有组织
|
||||
var sysOrgList = await GetListAsync(false);
|
||||
if (orgIds != null)
|
||||
sysOrgList = GetParentListByIds(sysOrgList, orgIds);//如果组织ID不为空则获取组织ID列表的所有父节点
|
||||
//如果选择器ID不为空则表示是懒加载,只加载子节点
|
||||
if (treeInput != null && treeInput.ParentId != null)
|
||||
{
|
||||
parentId = treeInput.ParentId.Value;
|
||||
sysOrgList = GetSysOrgChildrenLazy(sysOrgList, treeInput.ParentId.Value);//获取懒加载下级
|
||||
}
|
||||
sysOrgList = sysOrgList.OrderBy(it => it.SortCode).ToList();//排序
|
||||
//构建组织树
|
||||
var result = ConstructOrgTrees(sysOrgList, parentId);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysOrg> Detail(BaseIdInput input)
|
||||
{
|
||||
var sysOrgList = await GetListAsync();
|
||||
var orgDetail = sysOrgList.Where(it => it.Id == input.Id).FirstOrDefault();
|
||||
if (orgDetail.DirectorId != null)
|
||||
{
|
||||
//获取主管信息
|
||||
orgDetail.DirectorInfo = await Tenant.QueryableWithAttr<SysUser>().Where(it => it.Id == orgDetail.DirectorId).Select(it =>
|
||||
new UserSelectorOutPut
|
||||
{
|
||||
Id = it.Id,
|
||||
Name = it.Name,
|
||||
Account = it.Account
|
||||
}).FirstAsync();
|
||||
}
|
||||
return orgDetail;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<SysOrg> GetOrgParents(List<SysOrg> allOrgList, long orgId, bool includeSelf = true)
|
||||
{
|
||||
//找到组织
|
||||
var sysOrgList = allOrgList.Where(it => it.Id == orgId).FirstOrDefault();
|
||||
if (sysOrgList != null)//如果组织不为空
|
||||
{
|
||||
var data = new List<SysOrg>();
|
||||
var parents = GetOrgParents(allOrgList, sysOrgList.ParentId, includeSelf);//递归获取父节点
|
||||
data.AddRange(parents);//添加父节点;
|
||||
if (includeSelf)
|
||||
data.Add(sysOrgList);//添加到列表
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysOrg>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsExistOrgByName(List<SysOrg> sysOrgList, string orgName, long parentId,
|
||||
out long orgId)
|
||||
{
|
||||
orgId = 0;
|
||||
var sysOrg = sysOrgList.Where(it => it.ParentId == parentId && it.Name == orgName).FirstOrDefault();
|
||||
if (sysOrg != null)
|
||||
{
|
||||
orgId = sysOrg.Id;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysOrg>> GetOrgListByIdList(IdListInput input)
|
||||
{
|
||||
var sysOrgList = await GetListAsync();
|
||||
var orgList = sysOrgList.Where(it => input.IdList.Contains(it.Id)).ToList();// 获取指定ID的组织列表
|
||||
return orgList;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysOrg>> GetTenantList()
|
||||
{
|
||||
var tenantList = _simpleCacheService.Get<List<SysOrg>>(SystemConst.CACHE_SYS_TENANT);
|
||||
if (tenantList == null)
|
||||
{
|
||||
var orgList = await GetListAsync(false);
|
||||
tenantList = orgList.Where(it => it.Category == CateGoryConst.ORG_COMPANY).ToList();
|
||||
if (tenantList.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(SystemConst.CACHE_SYS_TENANT, tenantList);
|
||||
}
|
||||
}
|
||||
return tenantList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<long?> GetTenantIdByOrgId(long orgId, List<SysOrg> sysOrgList = null)
|
||||
{
|
||||
//先从缓存拿租户Id
|
||||
var tenantId = _simpleCacheService.HashGetOne<long?>(SystemConst.CACHE_SYS_ORG_TENANT, orgId.ToString());
|
||||
if (tenantId == null)
|
||||
{
|
||||
//获取所有组织
|
||||
sysOrgList ??= await GetListAsync();
|
||||
var userOrg = sysOrgList.FirstOrDefault(it => it.Id == orgId);
|
||||
if (userOrg != null)
|
||||
{
|
||||
//如果是公司直接返回
|
||||
if (userOrg.Category == CateGoryConst.ORG_COMPANY)
|
||||
{
|
||||
tenantId = userOrg.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
var parentIds = userOrg.ParentIdList;//获取父级ID列表
|
||||
//从最后一个往前遍历,取第一个公司ID为租户ID
|
||||
for (var i = parentIds.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var parentId = parentIds[i];
|
||||
var org = sysOrgList.FirstOrDefault(it => it.Id == parentId);
|
||||
if (org.Category == CateGoryConst.ORG_COMPANY)
|
||||
{
|
||||
tenantId = org.Id;//租户ID
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tenantId != null)
|
||||
_simpleCacheService.HashAdd(SystemConst.CACHE_SYS_ORG_TENANT, orgId.ToString(), tenantId);//插入缓存
|
||||
}
|
||||
}
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(SysOrgAddInput input, string name = SystemConst.SYS_ORG)
|
||||
{
|
||||
await CheckInput(input, name);//检查参数
|
||||
var sysOrg = input.Adapt<SysOrg>();//实体转换
|
||||
if (await InsertAsync(sysOrg))//插入数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Copy(SysOrgCopyInput input)
|
||||
{
|
||||
var orgList = await GetListAsync();//获取所有
|
||||
var positionList = await Context.Queryable<SysPosition>().ToListAsync();//获取所有职位
|
||||
var ids = new HashSet<long>();//定义不重复Id集合
|
||||
var addOrgList = new List<SysOrg>();//添加机构列表
|
||||
var addPositionList = new List<SysPosition>();//添加职位列表
|
||||
var alreadyIds = new HashSet<long>();//定义已经复制过得组织Id
|
||||
ids.AddRange(input.Ids);//加到集合
|
||||
if (ids.Contains(input.TargetId))
|
||||
throw Oops.Bah("不能包含自己");
|
||||
//获取目标组织
|
||||
var target = orgList.Where(it => it.Id == input.TargetId).FirstOrDefault();
|
||||
if (target != null)
|
||||
{
|
||||
//需要复制的组织名称列表
|
||||
var orgNames = orgList.Where(it => ids.Contains(it.Id)).Select(it => it.Name).ToList();
|
||||
//目标组织的一级子组织名称列表
|
||||
var targetChildNames = orgList.Where(it => it.ParentId == input.TargetId).Select(it => it.Name).ToList();
|
||||
orgNames.ForEach(it =>
|
||||
{
|
||||
if (targetChildNames.Contains(it)) throw Oops.Bah($"已存在{it}");
|
||||
});
|
||||
foreach (var id in input.Ids)
|
||||
{
|
||||
var org = orgList.Where(o => o.Id == id).FirstOrDefault();//获取组织
|
||||
if (org != null && !alreadyIds.Contains(id))
|
||||
{
|
||||
alreadyIds.Add(id);//添加到已复制列表
|
||||
RedirectOrg(org);//生成新的实体
|
||||
org.ParentId = input.TargetId;//父id为目标Id
|
||||
addOrgList.Add(org);
|
||||
//是否包含职位
|
||||
if (input.ContainsPosition)
|
||||
{
|
||||
var positions = positionList.Where(p => p.OrgId == id).ToList();//获取机构下的职位
|
||||
positions.ForEach(p =>
|
||||
{
|
||||
p.OrgId = org.Id;//赋值新的机构ID
|
||||
p.Id = CommonUtils.GetSingleId();//生成新的ID
|
||||
p.Code = RandomHelper.CreateRandomString(10);//生成新的Code
|
||||
addPositionList.Add(p);//添加到职位列表
|
||||
});
|
||||
}
|
||||
//是否包含下级
|
||||
if (input.ContainsChild)
|
||||
{
|
||||
var childIds = await GetOrgChildIds(id, false);//获取下级id列表
|
||||
alreadyIds.AddRange(childIds);//添加到已复制id
|
||||
var childList = orgList.Where(c => childIds.Contains(c.Id)).ToList();//获取下级
|
||||
var sysOrgChildren = CopySysOrgChildren(childList, id, org.Id, input.ContainsPosition,
|
||||
positionList);//赋值下级组织
|
||||
addOrgList.AddRange(sysOrgChildren.Item1);//添加到组织列表
|
||||
addPositionList.AddRange(sysOrgChildren.Item2);//添加到职位列表
|
||||
}
|
||||
}
|
||||
}
|
||||
orgList.AddRange(addOrgList);//要添加的组织添加到组织列表
|
||||
//遍历机构重新赋值全称和父Id列表
|
||||
addOrgList.ForEach(it =>
|
||||
{
|
||||
it.Names = it.Name;
|
||||
if (it.ParentId != SimpleAdminConst.ZERO)
|
||||
{
|
||||
GetNames(orgList, it.ParentId, it.Name, out var names,
|
||||
out var parentIdList);
|
||||
it.Names = names;
|
||||
it.ParentIdList = parentIdList;
|
||||
}
|
||||
});
|
||||
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
await InsertRangeAsync(addOrgList);//插入组织
|
||||
if (addPositionList.Count > 0)
|
||||
{
|
||||
await Context.Insertable(addPositionList).ExecuteCommandAsync();
|
||||
}
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await RefreshCache();//刷新缓存
|
||||
if (addPositionList.Count > 0) _simpleCacheService.Remove(SystemConst.CACHE_SYS_POSITION);//删除机构缓存
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogError(result.ErrorException, result.ErrorMessage);
|
||||
throw Oops.Oh($"复制失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 新增
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(SysOrgEditInput input, string name = SystemConst.SYS_ORG)
|
||||
{
|
||||
await CheckInput(input, name);//检查参数
|
||||
var sysOrg = input.Adapt<SysOrg>();//实体转换
|
||||
if (await UpdateAsync(sysOrg))//更新数据
|
||||
{
|
||||
//如果状态为禁用
|
||||
if (sysOrg.Status == CommonStatusConst.DISABLED)
|
||||
{
|
||||
var orgIds = await GetOrgChildIds(sysOrg.Id, true);//获取所有下级
|
||||
await _eventPublisher.PublishAsync(EventSubscriberConst.CLEAR_ORG_TOKEN, orgIds);//清除角色下用户缓存
|
||||
}
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 编辑
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input, string name = SystemConst.SYS_ORG)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
var sysOrgList = await GetListAsync();//获取所有组织
|
||||
var sysDeleteOrgList = new List<long>();//需要删除的组织ID集合
|
||||
ids.ForEach(it =>
|
||||
{
|
||||
var children = GetSysOrgChildren(sysOrgList, it);//查找下级组织
|
||||
sysDeleteOrgList.AddRange(children.Select(it => it.Id).ToList());
|
||||
sysDeleteOrgList.Add(it);
|
||||
});
|
||||
//如果组织下有用户则不能删除
|
||||
if (await Context.Queryable<SysUser>().AnyAsync(it => sysDeleteOrgList.Contains(it.OrgId)))
|
||||
{
|
||||
throw Oops.Bah($"请先删除{name}下的用户");
|
||||
}
|
||||
//判断组织下是否有角色
|
||||
var hasRole = await Context.Queryable<SysRole>().Where(it => sysDeleteOrgList.Contains(it.OrgId.Value)).CountAsync() > 0;
|
||||
if (hasRole) throw Oops.Bah($"请先删除{name}下的角色");
|
||||
// 判断组织下是否有职位
|
||||
var hasPosition = await Context.Queryable<SysPosition>().Where(it => sysDeleteOrgList.Contains(it.OrgId)).CountAsync() > 0;
|
||||
if (hasPosition) throw Oops.Bah($"请先删除{name}下的职位");
|
||||
//删除组织
|
||||
if (await DeleteByIdsAsync(sysDeleteOrgList.Cast<object>().ToArray()))
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
#endregion 删除
|
||||
|
||||
#region 其他
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RefreshCache()
|
||||
{
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_ORG);//从redis删除
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_TENANT);//从redis删除
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_USER);//清空redis所有的用户信息
|
||||
await GetListAsync();//刷新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<SysOrg> ConstructOrgTrees(List<SysOrg> orgList, long parentId = SimpleAdminConst.ZERO)
|
||||
{
|
||||
//找下级ID列表
|
||||
var orgInfos = orgList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
|
||||
if (orgInfos.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysOrg>();
|
||||
foreach (var item in orgInfos)//遍历组织
|
||||
{
|
||||
item.Children = ConstructOrgTrees(orgList, item.Id);//添加子节点
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysOrg>();
|
||||
}
|
||||
|
||||
#endregion 其他
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysOrg"></param>
|
||||
/// <param name="name"></param>
|
||||
private async Task CheckInput(SysOrg sysOrg, string name)
|
||||
{
|
||||
//判断分类是否正确
|
||||
if (sysOrg.Category != CateGoryConst.ORG_COMPANY && sysOrg.Category != CateGoryConst.ORG_DEPT)
|
||||
throw Oops.Bah($"{name}所属分类错误:{sysOrg.Category}");
|
||||
|
||||
var sysOrgList = await GetListAsync();//获取全部
|
||||
if (sysOrgList.Any(it => it.ParentId == sysOrg.ParentId && it.Name == sysOrg.Name && it.Id != sysOrg.Id))//判断同级是否有名称重复的
|
||||
throw Oops.Bah($"存在重复的同级{name}:{sysOrg.Name}");
|
||||
sysOrg.Names = sysOrg.Name;//全称默认自己
|
||||
if (sysOrg.ParentId != 0)
|
||||
{
|
||||
//获取父级,判断父级ID正不正确
|
||||
var parent = sysOrgList.Where(it => it.Id == sysOrg.ParentId).FirstOrDefault();
|
||||
if (parent != null)
|
||||
{
|
||||
if (parent.Id == sysOrg.Id)
|
||||
throw Oops.Bah($"上级{name}不能选择自己");
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"上级{name}不存在:{sysOrg.ParentId}");
|
||||
}
|
||||
GetNames(sysOrgList, sysOrg.ParentId, sysOrg.Name, out var names,
|
||||
out var parentIdList);
|
||||
sysOrg.Names = names;
|
||||
sysOrg.ParentIdList = parentIdList;
|
||||
}
|
||||
//如果code没填
|
||||
if (string.IsNullOrEmpty(sysOrg.Code))
|
||||
{
|
||||
sysOrg.Code = RandomHelper.CreateRandomString(10);//赋值Code
|
||||
}
|
||||
else
|
||||
{
|
||||
//判断是否有相同的Code
|
||||
if (sysOrgList.Any(it => it.Code == sysOrg.Code && it.Id != sysOrg.Id))
|
||||
throw Oops.Bah($"存在重复的编码:{sysOrg.Code}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据组织Id列表获取所有父级组织
|
||||
/// </summary>
|
||||
/// <param name="allOrgList"></param>
|
||||
/// <param name="orgIds"></param>
|
||||
/// <returns></returns>
|
||||
public List<SysOrg> GetParentListByIds(List<SysOrg> allOrgList, List<long> orgIds)
|
||||
{
|
||||
var sysOrgList = new HashSet<SysOrg>();//结果列表
|
||||
//遍历组织ID
|
||||
orgIds.ForEach(it =>
|
||||
{
|
||||
//获取该组织ID的所有父级
|
||||
var parents = GetOrgParents(allOrgList, it);
|
||||
sysOrgList.AddRange(parents);//添加到结果
|
||||
});
|
||||
return sysOrgList.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取组织所有下级
|
||||
/// </summary>
|
||||
/// <param name="orgList"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
public List<SysOrg> GetSysOrgChildren(List<SysOrg> orgList, long parentId)
|
||||
{
|
||||
//找下级组织ID列表
|
||||
var orgInfos = orgList.Where(it => it.ParentId == parentId).ToList();
|
||||
if (orgInfos.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysOrg>();
|
||||
foreach (var item in orgInfos)//遍历组织
|
||||
{
|
||||
var children = GetSysOrgChildren(orgList, item.Id);//获取子节点
|
||||
data.AddRange(children);//添加子节点);
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysOrg>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取组织下级(懒加载)
|
||||
/// </summary>
|
||||
/// <param name="orgList"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
public List<SysOrg> GetSysOrgChildrenLazy(List<SysOrg> orgList, long parentId)
|
||||
{
|
||||
//找下级组织ID列表
|
||||
var orgInfos = orgList.Where(it => it.ParentId == parentId).ToList();
|
||||
if (orgInfos.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysOrg>();
|
||||
foreach (var item in orgInfos)//遍历组织
|
||||
{
|
||||
var children = orgList.Where(it => it.ParentId == item.Id).ToList();//获取子节点
|
||||
//遍历子节点
|
||||
children.ForEach(it =>
|
||||
{
|
||||
if (!orgList.Any(org => org.ParentId == it.Id))
|
||||
it.Leaf = true;//如果没有下级,则设置为叶子节点
|
||||
});
|
||||
data.AddRange(children);//添加子节点);
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysOrg>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 赋值组织的所有下级
|
||||
/// </summary>
|
||||
/// <param name="orgList">组织列表</param>
|
||||
/// <param name="parentId">父Id</param>
|
||||
/// <param name="newParentId">新父Id</param>
|
||||
/// <param name="isCopyPosition"></param>
|
||||
/// <param name="positions"></param>
|
||||
/// <returns></returns>
|
||||
public Tuple<List<SysOrg>, List<SysPosition>> CopySysOrgChildren(List<SysOrg> orgList, long parentId, long newParentId,
|
||||
bool isCopyPosition,
|
||||
List<SysPosition> positions)
|
||||
{
|
||||
//找下级组织列表
|
||||
var orgInfos = orgList.Where(it => it.ParentId == parentId).ToList();
|
||||
if (orgInfos.Count > 0)//如果数量大于0
|
||||
{
|
||||
var result = new Tuple<List<SysOrg>, List<SysPosition>>(
|
||||
new List<SysOrg>(), new List<SysPosition>()
|
||||
);
|
||||
foreach (var item in orgInfos)//遍历组织
|
||||
{
|
||||
var oldId = item.Id;//获取旧Id
|
||||
RedirectOrg(item);//实体重新赋值
|
||||
var children = CopySysOrgChildren(orgList, oldId, item.Id, isCopyPosition,
|
||||
positions);//获取子节点
|
||||
item.ParentId = newParentId;//赋值新的父Id
|
||||
result.Item1.AddRange(children.Item1);//添加下级组织;
|
||||
if (isCopyPosition)//如果包含职位
|
||||
{
|
||||
var positionList = positions.Where(it => it.OrgId == oldId).ToList();//获取职位列表
|
||||
positionList.ForEach(it =>
|
||||
{
|
||||
it.OrgId = item.Id;//赋值新的机构ID
|
||||
it.Id = CommonUtils.GetSingleId();//生成新的ID
|
||||
it.Code = RandomHelper.CreateRandomString(10);//生成新的Code
|
||||
});
|
||||
result.Item2.AddRange(positionList);//添加职位列表
|
||||
}
|
||||
}
|
||||
return result;//返回结果
|
||||
}
|
||||
return new Tuple<List<SysOrg>, List<SysPosition>>(
|
||||
new List<SysOrg>(), new List<SysPosition>()
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新生成组织实体
|
||||
/// </summary>
|
||||
/// <param name="org"></param>
|
||||
private void RedirectOrg(SysOrg org)
|
||||
{
|
||||
//重新生成ID并赋值
|
||||
var newId = CommonUtils.GetSingleId();
|
||||
org.Id = newId;
|
||||
org.Code = RandomHelper.CreateRandomString(10);
|
||||
org.CreateTime = DateTime.Now;
|
||||
org.CreateUser = UserManager.UserAccount;
|
||||
org.CreateUserId = UserManager.UserId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取全称
|
||||
/// </summary>
|
||||
/// <param name="sysOrgList">组织列表</param>
|
||||
/// <param name="parentId">父Id</param>
|
||||
/// <param name="orgName">组织名称</param>
|
||||
/// <param name="names">组织全称</param>
|
||||
/// <param name="parentIdList">组织父Id列表</param>
|
||||
public void GetNames(List<SysOrg> sysOrgList, long parentId, string orgName,
|
||||
out string names, out List<long> parentIdList)
|
||||
{
|
||||
names = "";
|
||||
//获取父级菜单
|
||||
var parents = GetOrgParents(sysOrgList, parentId);
|
||||
foreach (var item in parents)
|
||||
{
|
||||
names += $"{item.Name}/";
|
||||
}
|
||||
names += orgName;//赋值全称
|
||||
parentIdList = parents.Select(it => it.Id).ToList();//赋值父Id列表
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 职位分页查询
|
||||
/// </summary>
|
||||
public class PositionPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位列表
|
||||
/// </summary>
|
||||
public List<long> OrgIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
public string Category { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态
|
||||
/// </summary>
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 职位新增参数
|
||||
/// </summary>
|
||||
public class PositionAddInput : SysPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "OrgId不能为空")]
|
||||
public override long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Name不能为空")]
|
||||
public override string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 分类
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Category不能为空")]
|
||||
public override string Category { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 机构编辑参数
|
||||
/// </summary>
|
||||
public class PositionEditInput : PositionAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 机构选择器参数
|
||||
/// </summary>
|
||||
public class PositionSelectorInput : UserSelectorInput
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 机构树形选择器参数
|
||||
/// </summary>
|
||||
public class PositionTreeInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 机构ID列表
|
||||
/// </summary>
|
||||
public List<long> OrgIds { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 组织树输出参数
|
||||
/// </summary>
|
||||
public class PositionTreeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 职位Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否是职位
|
||||
/// </summary>
|
||||
public bool IsPosition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子项
|
||||
/// </summary>
|
||||
public List<PositionTreeOutput> Children { get; set; } = new List<PositionTreeOutput>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 组织选择器输出参数
|
||||
/// </summary>
|
||||
public class PositionSelectorOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织Id或者职位Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子项
|
||||
/// </summary>
|
||||
public List<PositionSelectorOutput> Children { get; set; } = new List<PositionSelectorOutput>();
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 职位服务
|
||||
/// </summary>
|
||||
public interface ISysPositionService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 获取职位列表
|
||||
/// </summary>
|
||||
/// <returns>职位列表</returns>
|
||||
Task<List<SysPosition>> GetListAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取职位信息
|
||||
/// </summary>
|
||||
/// <param name="id">职位ID</param>
|
||||
/// <returns>职位信息</returns>
|
||||
Task<SysPosition> GetSysPositionById(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 职位分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>分页列表</returns>
|
||||
Task<SqlSugarPagedList<SysPosition>> Page(PositionPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 职位选择器
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns></returns>
|
||||
Task<List<PositionSelectorOutput>> Selector(PositionSelectorInput input);
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 根据id集合获取职位集合
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysPosition>> GetPositionListByIdList(IdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 职位树形结构
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<PositionTreeOutput>> Tree(PositionTreeInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 职位详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysPosition> Detail(BaseIdInput input);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 编辑职位
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(PositionEditInput input, string name = SystemConst.SYS_POS);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task RefreshCache();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <summary>
|
||||
/// 添加职位
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Add(PositionAddInput input, string name = SystemConst.SYS_POS);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <summary>
|
||||
/// 删除职位
|
||||
/// </summary>
|
||||
/// <param name="input">id列表</param>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input, string name = SystemConst.SYS_POS);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc cref="ISysPositionService"/>
|
||||
/// </summary>
|
||||
public class SysPositionService : DbRepository<SysPosition>, ISysPositionService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly ISysOrgService _sysOrgService;
|
||||
private readonly IDictService _dictService;
|
||||
|
||||
public SysPositionService(ISimpleCacheService simpleCacheService, ISysOrgService sysOrgService, IDictService dictService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_sysOrgService = sysOrgService;
|
||||
_dictService = dictService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 获取全部
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override async Task<List<SysPosition>> GetListAsync()
|
||||
{
|
||||
//先从Redis拿
|
||||
var sysPositions = _simpleCacheService.Get<List<SysPosition>>(SystemConst.CACHE_SYS_POSITION);
|
||||
if (sysPositions == null)
|
||||
{
|
||||
//redis没有就去数据库拿
|
||||
sysPositions = await base.GetListAsync();
|
||||
if (sysPositions.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(SystemConst.CACHE_SYS_POSITION, sysPositions);
|
||||
}
|
||||
}
|
||||
return sysPositions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysPosition>> GetPositionListByIdList(IdListInput input)
|
||||
{
|
||||
var positions = await GetListAsync();
|
||||
var positionList = positions.Where(it => input.IdList.Contains(it.Id)).ToList();// 获取指定ID的岗位列表
|
||||
return positionList;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<PositionSelectorOutput>> Selector(PositionSelectorInput input)
|
||||
{
|
||||
var sysOrgList = await _sysOrgService.GetListAsync(false);//获取所有组织
|
||||
var sysPositions = await GetListAsync();//获取所有职位
|
||||
if (input.OrgIds != null)//根据数据范围查
|
||||
{
|
||||
sysOrgList = sysOrgList.Where(it => input.OrgIds.Contains(it.Id)).ToList();//在指定组织列表查询
|
||||
sysPositions = sysPositions.Where(it => input.OrgIds.Contains(it.OrgId)).ToList();//在指定职位列表查询
|
||||
}
|
||||
var result = await ConstructPositionSelector(sysOrgList, sysPositions);//构造树
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysPosition> GetSysPositionById(long id)
|
||||
{
|
||||
var sysPositions = await GetListAsync();
|
||||
var result = sysPositions.Where(it => it.Id == id).FirstOrDefault();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SqlSugarPagedList<SysPosition>> Page(PositionPageInput input)
|
||||
{
|
||||
var orgIds = await _sysOrgService.GetOrgChildIds(input.OrgId);//获取下级组织
|
||||
var query = Context.Queryable<SysPosition>().WhereIF(input.OrgId > 0, it => orgIds.Contains(it.OrgId))//根据组织ID查询
|
||||
.WhereIF(input.OrgIds != null, it => input.OrgIds.Contains(it.OrgId))//在指定组织列表查询
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Category), it => it.Category == input.Category)//根据分类
|
||||
.WhereIF(!string.IsNullOrEmpty(input.Status), it => it.Status == input.Status)//根据状态
|
||||
.WhereIF(!string.IsNullOrEmpty(input.SearchKey), it => it.Name.Contains(input.SearchKey))//根据关键字查询
|
||||
.OrderByIF(!string.IsNullOrEmpty(input.SortField), $"{input.SortField} {input.SortOrder}")
|
||||
.OrderBy(it => it.SortCode)//排序
|
||||
.OrderBy(it => it.CreateTime);//排序
|
||||
var pageInfo = await query.ToPagedListAsync(input.PageNum, input.PageSize);//分页
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<PositionTreeOutput>> Tree(PositionTreeInput input)
|
||||
{
|
||||
var result = new List<PositionTreeOutput>();//返回结果
|
||||
var sysOrgList = await _sysOrgService.GetListAsync(false);//获取所有组织
|
||||
var sysPositions = await GetListAsync();//获取所有职位
|
||||
if (input.OrgIds != null)//根据数据范围查
|
||||
{
|
||||
sysOrgList = sysOrgList.Where(it => input.OrgIds.Contains(it.Id)).ToList();//在指定组织列表查询
|
||||
sysPositions = sysPositions.Where(it => input.OrgIds.Contains(it.OrgId)).ToList();//在指定职位列表查询
|
||||
}
|
||||
var posCategory = await _dictService.GetChildrenByDictValue(SysDictConst.POSITION_CATEGORY);//获取职位分类
|
||||
var topOrgList = sysOrgList.Where(it => it.ParentId == 0).ToList();//获取顶级组织
|
||||
//遍历顶级组织
|
||||
foreach (var org in topOrgList)
|
||||
{
|
||||
var childIds = await _sysOrgService.GetOrgChildIds(org.Id, true, sysOrgList);//获取组织下的所有子级ID
|
||||
var orgPositions = sysPositions.Where(it => childIds.Contains(it.OrgId)).ToList();//获取组织下的职位
|
||||
if (orgPositions.Count == 0) continue;
|
||||
var positionTreeOutput = new PositionTreeOutput
|
||||
{
|
||||
Id = org.Id,
|
||||
Name = org.Name,
|
||||
IsPosition = false
|
||||
};//实例化组织树
|
||||
//获取组织下的职位职位分类
|
||||
foreach (var category in posCategory)
|
||||
{
|
||||
var id = CommonUtils.GetSingleId();//生成唯一ID临时用,因为前端需要ID
|
||||
var categoryTreeOutput = new PositionTreeOutput
|
||||
{
|
||||
Id = id,
|
||||
Name = category.DictLabel,
|
||||
IsPosition = false
|
||||
};//实例化职位分类树
|
||||
var positions = orgPositions.Where(it => it.Category == category.DictValue).ToList();//获取职位分类下的职位
|
||||
//遍历职位,实例化职位树
|
||||
positions.ForEach(it =>
|
||||
{
|
||||
categoryTreeOutput.Children.Add(new PositionTreeOutput()
|
||||
{
|
||||
Id = it.Id,
|
||||
Name = it.Name,
|
||||
IsPosition = true
|
||||
});//添加职位
|
||||
});
|
||||
positionTreeOutput.Children.Add(categoryTreeOutput);
|
||||
}
|
||||
result.Add(positionTreeOutput);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysPosition> Detail(BaseIdInput input)
|
||||
{
|
||||
return await GetSysPositionById(input.Id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Edit(PositionEditInput input, string name = SystemConst.SYS_POS)
|
||||
{
|
||||
await CheckInput(input, name);//检查参数
|
||||
var sysPosition = input.Adapt<SysPosition>();//实体转换
|
||||
if (await UpdateAsync(sysPosition))//更新数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RefreshCache()
|
||||
{
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_POSITION);//删除缓存
|
||||
await GetListAsync();//重新写入缓存
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Add(PositionAddInput input, string name = SystemConst.SYS_POS)
|
||||
{
|
||||
await CheckInput(input, name);//检查参数
|
||||
var sysPosition = input.Adapt<SysPosition>();//实体转换
|
||||
if (await InsertAsync(sysPosition))//插入数据
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Delete(BaseIdListInput input, string name = SystemConst.SYS_POS)
|
||||
{
|
||||
//获取所有ID
|
||||
var ids = input.Ids;
|
||||
if (ids.Count > 0)
|
||||
{
|
||||
//如果组织下有用户则不能删除
|
||||
if (await Context.Queryable<SysUser>().AnyAsync(it => ids.Contains(it.PositionId)))
|
||||
{
|
||||
throw Oops.Bah($"请先删除{name}下的用户");
|
||||
}
|
||||
//删除职位
|
||||
if (await DeleteByIdsAsync(ids.Cast<object>().ToArray()))
|
||||
await RefreshCache();//刷新缓存
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 检查输入参数
|
||||
/// </summary>
|
||||
/// <param name="sysPosition"></param>
|
||||
/// <param name="name">名称</param>
|
||||
private async Task CheckInput(SysPosition sysPosition, string name)
|
||||
{
|
||||
//所有分类放一个列表
|
||||
var posCategoryList = new List<string>
|
||||
{
|
||||
CateGoryConst.POSITION_HIGH, CateGoryConst.POSITION_LOW, CateGoryConst.POSITION_MIDDLE
|
||||
};
|
||||
if (!posCategoryList.Contains(sysPosition.Category))
|
||||
throw Oops.Bah($"{name}所属分类错误:{sysPosition.Category}");
|
||||
var sysPositions = await GetListAsync();//获取全部
|
||||
if (sysPositions.Any(it => it.OrgId == sysPosition.OrgId && it.Name == sysPosition.Name && it.Id != sysPosition.Id))//判断同级是否有名称重复的
|
||||
throw Oops.Bah($"存在重复的{name}:{sysPosition.Name}");
|
||||
if (sysPosition.Id > 0)//如果ID大于0表示编辑
|
||||
{
|
||||
var position = sysPositions.Where(it => it.Id == sysPosition.Id).FirstOrDefault();//获取当前职位
|
||||
if (position == null)
|
||||
throw Oops.Bah($"{name}不存在");
|
||||
}
|
||||
//如果code没填
|
||||
if (string.IsNullOrEmpty(sysPosition.Code))
|
||||
{
|
||||
sysPosition.Code = RandomHelper.CreateRandomString(10);//赋值Code
|
||||
}
|
||||
else
|
||||
{
|
||||
//判断是否有相同的Code
|
||||
if (sysPositions.Any(it => it.Code == sysPosition.Code && it.Id != sysPosition.Id))
|
||||
throw Oops.Bah($"存在重复的编码:{sysPosition.Code}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建职位选择器
|
||||
/// </summary>
|
||||
/// <param name="orgList">组织列表</param>
|
||||
/// <param name="sysPositions">职位列表</param>
|
||||
/// <param name="parentId">父Id</param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<PositionSelectorOutput>> ConstructPositionSelector(List<SysOrg> orgList, List<SysPosition> sysPositions,
|
||||
long parentId = SimpleAdminConst.ZERO)
|
||||
{
|
||||
//找下级组织列表
|
||||
var orgInfos = orgList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
|
||||
var data = new List<PositionSelectorOutput>();
|
||||
if (orgInfos.Count > 0)//如果数量大于0
|
||||
{
|
||||
foreach (var item in orgInfos)//遍历组织
|
||||
{
|
||||
var childIds = await _sysOrgService.GetOrgChildIds(item.Id, true, orgList);//获取组织下的所有子级ID
|
||||
var orgPositions = sysPositions.Where(it => childIds.Contains(it.OrgId)).ToList();//获取组织下的职位
|
||||
if (orgPositions.Count > 0)//如果组织和组织下级有职位
|
||||
{
|
||||
var positionSelectorOutput = new PositionSelectorOutput
|
||||
{
|
||||
Id = item.Id,
|
||||
Name = item.Name,
|
||||
Children = await ConstructPositionSelector(orgList, sysPositions, item.Id)//递归
|
||||
};//实例化职位树
|
||||
var positions = orgPositions.Where(it => it.OrgId == item.Id).ToList();//获取组织下的职位
|
||||
if (positions.Count > 0)//如果数量大于0
|
||||
{
|
||||
foreach (var position in positions)
|
||||
{
|
||||
positionSelectorOutput.Children.Add(new PositionSelectorOutput
|
||||
{
|
||||
Id = position.Id,
|
||||
Name = position.Name
|
||||
});//添加职位
|
||||
}
|
||||
}
|
||||
data.Add(positionSelectorOutput);//添加到列表
|
||||
}
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<PositionSelectorOutput>();
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 用户选择器参数
|
||||
/// </summary>
|
||||
public class UserSelectorInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构ID列表
|
||||
/// </summary>
|
||||
public List<long> OrgIds { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 机构ID
|
||||
/// </summary>
|
||||
public long PositionId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色ID
|
||||
/// </summary>
|
||||
public long RoleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键字
|
||||
/// </summary>
|
||||
public string Account { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户分页查询参数
|
||||
/// </summary>
|
||||
public class UserPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 所属组织
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 动态查询条件
|
||||
/// </summary>
|
||||
public Expressionable<SysUser> Expression { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户状态
|
||||
/// </summary>
|
||||
|
||||
public string Status { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加用户参数
|
||||
/// </summary>
|
||||
public class UserAddInput : SysUser
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Account不能为空")]
|
||||
public override string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Name不能为空")]
|
||||
public override string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组织id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "OrgId不能为空")]
|
||||
public override long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "PositionId不能为空")]
|
||||
public override long PositionId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑用户参数
|
||||
/// </summary>
|
||||
public class UserEditInput : UserAddInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public override long Id { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户授权角色参数
|
||||
/// </summary>
|
||||
public class UserGrantRoleInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
[IdNotNull(ErrorMessage = "Id不能为空")]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权权限信息
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "RoleIdList不能为空")]
|
||||
public List<long> RoleIdList { get; set; }
|
||||
}
|
||||
|
||||
public class UserGrantResourceInput : GrantResourceInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 默认数据权限
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "DefaultDataScope不能为空")]
|
||||
public DefaultDataScope DefaultDataScope { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户导入
|
||||
/// </summary>
|
||||
public class SysUserImportInput : ImportTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
[ImporterHeader(Name = "账号")]
|
||||
[Required(ErrorMessage = "账号不能为空")]
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "姓名")]
|
||||
[Required(ErrorMessage = "姓名不能为空")]
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "性别")]
|
||||
[Required(ErrorMessage = "性别不能为空")]
|
||||
public string Gender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "昵称")]
|
||||
public string Nickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机
|
||||
/// 这里使用了SM4自动加密解密
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "手机号")]
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮箱
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "邮箱")]
|
||||
[EmailAddress(ErrorMessage = "邮箱格式错误")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属机构
|
||||
/// </summary>
|
||||
[ImporterHeader(Name = "所属机构(子机构用/隔开)")]
|
||||
[Required(ErrorMessage = "所属部门不能为空")]
|
||||
[AntTable(Width = 200)]
|
||||
public string OrgName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位
|
||||
/// </summary>
|
||||
[ImporterHeader(Name = "职位")]
|
||||
[Required(ErrorMessage = "职位不能为空")]
|
||||
public string PositionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出生日期
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "出生日期")]
|
||||
[AntTable(IsDate = true)]
|
||||
public DateTime? Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 民族
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "民族")]
|
||||
public string Nation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 籍贯
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "籍贯")]
|
||||
public string NativePlace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭住址
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "家庭住址")]
|
||||
public string HomeAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通信地址
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "通信地址")]
|
||||
public string MailingAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证件类型
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "证件类型")]
|
||||
public string IdCardType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证件号码
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "证件号码")]
|
||||
public string IdCardNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文化程度
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "文化程度")]
|
||||
public string CultureLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 政治面貌
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "政治面貌")]
|
||||
public string PoliticalOutlook { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 毕业院校
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "毕业院校")]
|
||||
public string College { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学历
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "学历")]
|
||||
public string Education { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学制
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "学制")]
|
||||
public string EduLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学位
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "学位")]
|
||||
public string Degree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭电话
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "家庭电话")]
|
||||
public string HomeTel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 办公电话
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "办公电话")]
|
||||
public string OfficeTel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "紧急联系人")]
|
||||
public string EmergencyContact { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人电话
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "紧急联系人电话")]
|
||||
[Phone(ErrorMessage = "电话号码格式错误")]
|
||||
public string EmergencyPhone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人地址
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "紧急联系人地址")]
|
||||
public string EmergencyAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 员工编号
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "员工编号")]
|
||||
public string EmpNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入职日期
|
||||
///</summary>
|
||||
[ImporterHeader(Name = "入职日期")]
|
||||
[AntTable(IsDate = true)]
|
||||
public DateTime? EntryDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 部门Id
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位Id
|
||||
/// </summary>
|
||||
[ImporterHeader(IsIgnore = true)]
|
||||
public long PositionId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 选择用户输出参数
|
||||
/// </summary>
|
||||
public class UserSelectorOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 账号
|
||||
/// </summary>
|
||||
public string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
///</summary>
|
||||
public string Gender { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息输出
|
||||
/// </summary>
|
||||
public class UserInfoOutPut : SysUser
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
[ExcelExporter(Name = "用户信息", TableStyle = TableStyles.Light10, AutoFitAllColumn = true)]
|
||||
public class SysUserExportOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "账号")]
|
||||
public virtual string Account { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "姓名")]
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "昵称")]
|
||||
public string Nickname { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 性别
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "性别")]
|
||||
public string Gender { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机
|
||||
/// 这里使用了SM4自动加密解密
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "手机号")]
|
||||
public string Phone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 邮箱
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "邮箱")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所属机构
|
||||
/// </summary>
|
||||
[ExporterHeader(DisplayName = "所属机构(子机构用/隔开)")]
|
||||
public string OrgNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职位
|
||||
/// </summary>
|
||||
[ExporterHeader(DisplayName = "职位")]
|
||||
public string PositionName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 出生日期
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "出生日期")]
|
||||
public string Birthday { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 民族
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "民族")]
|
||||
public string Nation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 籍贯
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "籍贯")]
|
||||
public string NativePlace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭住址
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "家庭住址")]
|
||||
public string HomeAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通信地址
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "通信地址")]
|
||||
public string MailingAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证件类型
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "证件类型")]
|
||||
public string IdCardType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 证件号码
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "证件号码")]
|
||||
public string IdCardNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文化程度
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "文化程度")]
|
||||
public string CultureLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 政治面貌
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "政治面貌")]
|
||||
public string PoliticalOutlook { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 毕业院校
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "毕业院校")]
|
||||
public string College { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学历
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "学历")]
|
||||
public string Education { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学制
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "学制")]
|
||||
public string EduLength { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 学位
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "学位")]
|
||||
public string Degree { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 家庭电话
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "家庭电话")]
|
||||
public string HomeTel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 办公电话
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "办公电话")]
|
||||
public string OfficeTel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "紧急联系人")]
|
||||
public string EmergencyContact { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人电话
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "紧急联系人电话")]
|
||||
public string EmergencyPhone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 紧急联系人地址
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "紧急联系人地址")]
|
||||
public string EmergencyAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 员工编号
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "员工编号")]
|
||||
public string EmpNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入职日期
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "入职日期")]
|
||||
public string EntryDate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 职级
|
||||
///</summary>
|
||||
[ExporterHeader(DisplayName = "职级")]
|
||||
public string PositionLevel { get; set; }
|
||||
}
|
||||
|
||||
public class RoleSelectorOutPut
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 组织ID
|
||||
/// </summary>
|
||||
public long OrgId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码
|
||||
/// </summary>
|
||||
public string Code { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 用户服务
|
||||
/// </summary>
|
||||
public interface ISysUserService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID获取按钮ID集合
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<string>> GetButtonCodeList(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 根据账号获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="account">用户名</param>
|
||||
/// <param name="tenantId">租户ID</param>
|
||||
/// <returns>用户信息</returns>
|
||||
Task<SysUser> GetUserByAccount(string account, long? tenantId = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户ID和机构ID获取角色权限
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <param name="orgId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<DataScope>> GetPermissionListByUserId(long userId, long orgId);
|
||||
|
||||
/// <summary>
|
||||
/// 根据手机号获取用户账号
|
||||
/// </summary>
|
||||
/// <param name="phone">手机号</param>
|
||||
/// <param name="tenantId">租户ID</param>
|
||||
/// <returns>用户账号名称</returns>
|
||||
Task<long> GetIdByPhone(string phone, long? tenantId = null);
|
||||
|
||||
/// <summary>
|
||||
/// 用户选择器
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<UserSelectorOutPut>> Selector(UserSelectorInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 用户分页查询
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>用户分页列表</returns>
|
||||
Task<SqlSugarPagedList<SysUser>> Page(UserPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 用户列表
|
||||
/// </summary>
|
||||
/// <param name="input">查询</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysUser>> List(UserPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户Id获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>用户信息</returns>
|
||||
Task<SysUser> GetUserById(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户Id获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <typeparam name="T">转换的实体</typeparam>
|
||||
/// <returns></returns>
|
||||
Task<T> GetUserById<T>(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户账号获取用户ID
|
||||
/// </summary>
|
||||
/// <param name="account">用户账号</param>
|
||||
/// <param name="tenantId">租户id</param>
|
||||
/// <returns></returns>
|
||||
Task<long> GetIdByAccount(string account, long? tenantId = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据用户手机获取用户信息
|
||||
/// </summary>
|
||||
/// <param name="phone">手机号</param>
|
||||
/// <param name="tenantId">租户Id</param>
|
||||
/// <returns>用户信息</returns>
|
||||
Task<SysUser> GetUserByPhone(string phone, long? tenantId = null);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户拥有角色
|
||||
/// </summary>
|
||||
/// <param name="input">用户ID</param>
|
||||
/// <returns></returns>
|
||||
Task<List<RoleSelectorOutPut>> OwnRole(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户拥有的资源
|
||||
/// </summary>
|
||||
/// <param name="input">用户id</param>
|
||||
/// <returns>资源列表</returns>
|
||||
Task<RoleOwnResourceOutput> OwnResource(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户拥有的权限
|
||||
/// </summary>
|
||||
/// <param name="input">用户id</param>
|
||||
/// <returns>权限列表</returns>
|
||||
Task<RoleOwnPermissionOutput> OwnPermission(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 用户权限树选择
|
||||
/// </summary>
|
||||
/// <param name="input">用户id</param>
|
||||
/// <returns>权限列表</returns>
|
||||
Task<List<string>> UserPermissionTreeSelector(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 根据id集合获取用户集合
|
||||
/// </summary>
|
||||
/// <param name="input">Id集合</param>
|
||||
/// <returns></returns>
|
||||
Task<List<UserSelectorOutPut>> GetUserListByIdList(IdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 用户详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysUser> Detail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户头像
|
||||
/// </summary>
|
||||
/// <param name="userId">用户id</param>
|
||||
/// <returns>base64头像</returns>
|
||||
Task<string> GetUserAvatar(long userId);
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 数据范围相关
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前API用户的数据范围
|
||||
/// null:代表拥有全部数据权限
|
||||
/// [xx,xx]:代表拥有部分机构的权限
|
||||
/// []:代表仅自己权限
|
||||
/// </summary>
|
||||
/// <returns>机构列表</returns>
|
||||
Task<List<long>?> GetLoginUserApiDataScope();
|
||||
|
||||
/// <summary>
|
||||
/// 检查用户是否有机构的数据权限
|
||||
/// </summary>
|
||||
/// <param name="orgId">机构id</param>
|
||||
/// <param name="createUerId">创建者id</param>
|
||||
/// <param name="errMsg">错误提示:不为空则直接抛出异常</param>
|
||||
/// <returns>是否有权限</returns>
|
||||
Task<bool> CheckApiDataScope(long? orgId, long? createUerId, string errMsg = "");
|
||||
|
||||
/// <summary>
|
||||
/// 检查用户是否有机构的数据权限
|
||||
/// </summary>
|
||||
/// <param name="orgIds">机构id列表</param>
|
||||
/// <param name="createUerIds">创建者id列表</param>
|
||||
/// <param name="errMsg">错误提示:不为空则直接抛出异常</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> CheckApiDataScope(List<long> orgIds, List<long> createUerIds, string errMsg = "");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 新增
|
||||
|
||||
/// <summary>
|
||||
/// 添加用户
|
||||
/// </summary>
|
||||
/// <param name="input">添加参数</param>
|
||||
/// <returns></returns>
|
||||
Task Add(UserAddInput input);
|
||||
|
||||
#endregion 新增
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 编辑
|
||||
/// </summary>
|
||||
/// <param name="input">编辑参数</param>
|
||||
/// <returns></returns>
|
||||
Task Edit(UserEditInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 启用用户
|
||||
/// </summary>
|
||||
/// <param name="input">用户Id</param>
|
||||
/// <returns></returns>
|
||||
Task EnableUser(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用用户
|
||||
/// </summary>
|
||||
/// <param name="input">用户Id</param>
|
||||
/// <returns></returns>
|
||||
Task DisableUser(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 重置密码
|
||||
/// </summary>
|
||||
/// <param name="input">用户Id</param>
|
||||
/// <returns></returns>
|
||||
Task ResetPassword(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给用户授权角色
|
||||
/// </summary>
|
||||
/// <param name="input">授权参数</param>
|
||||
/// <returns></returns>
|
||||
Task GrantRole(UserGrantRoleInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给用户授权资源
|
||||
/// </summary>
|
||||
/// <param name="input">授权参数</param>
|
||||
/// <returns></returns>
|
||||
Task GrantResource(UserGrantResourceInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 给用户授权权限
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task GrantPermission(GrantPermissionInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 设置用户默认值
|
||||
/// </summary>
|
||||
/// <param name="sysUsers"></param>
|
||||
/// <returns></returns>
|
||||
Task SetUserDefault(List<SysUser> sysUsers);
|
||||
|
||||
/// <summary>
|
||||
/// 批量编辑
|
||||
/// </summary>
|
||||
/// <param name="input">批量编辑信息</param>
|
||||
/// <returns></returns>
|
||||
Task Edits(BatchEditInput input);
|
||||
|
||||
#endregion 编辑
|
||||
|
||||
#region 删除
|
||||
|
||||
/// <summary>
|
||||
/// 删除用户
|
||||
/// </summary>
|
||||
/// <param name="input">Id列表</param>
|
||||
/// <returns></returns>
|
||||
Task Delete(BaseIdListInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 从redis中删除用户信息
|
||||
/// </summary>
|
||||
/// <param name="ids">用户ID列表</param>
|
||||
void DeleteUserFromRedis(List<long> ids);
|
||||
|
||||
/// <summary>
|
||||
/// 从redis中删除用户信息
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
void DeleteUserFromRedis(long userId);
|
||||
|
||||
#endregion 删除
|
||||
|
||||
#region 导入导出
|
||||
|
||||
/// <summary>
|
||||
/// 导出用户
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<FileStreamResult> Export(UserPageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 导入模板下载
|
||||
/// </summary>
|
||||
/// <returns>模板</returns>
|
||||
Task<FileStreamResult> Template();
|
||||
|
||||
/// <summary>
|
||||
/// 导入预览
|
||||
/// </summary>
|
||||
/// <param name="input">预览参数</param>
|
||||
/// <returns>预览结果</returns>
|
||||
Task<ImportPreviewOutput<SysUserImportInput>> Preview(ImportPreviewInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 用户导入
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<ImportResultOutPut<SysUserImportInput>> Import(ImportResultInput<SysUserImportInput> input);
|
||||
|
||||
/// <summary>
|
||||
/// 检查导入数据
|
||||
/// </summary>
|
||||
/// <param name="data">数据</param>
|
||||
/// <param name="clearError">是否初始化错误</param>
|
||||
/// <returns></returns>
|
||||
Task<List<T>> CheckImport<T>(List<T> data, bool clearError = false) where T : SysUserImportInput;
|
||||
|
||||
#endregion 导入导出
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System.Services.System.Relation.Dto;
|
||||
|
||||
public class RelationOutput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 关系服务
|
||||
/// </summary>
|
||||
public interface IRelationService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据分类获取关系表信息
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns>关系表</returns>
|
||||
Task<List<SysRelation>> GetRelationByCategory(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 通过对象ID和分类获取关系列表
|
||||
/// </summary>
|
||||
/// <param name="objectId">对象ID</param>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRelation>> GetRelationListByObjectIdAndCategory(long objectId, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 通过对象ID列表和分类获取关系列表
|
||||
/// </summary>
|
||||
/// <param name="objectIds">对象ID</param>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRelation>> GetRelationListByObjectIdListAndCategory(List<long> objectIds, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 通过目标ID和分类获取关系列表
|
||||
/// </summary>
|
||||
/// <param name="targetId">目标ID</param>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRelation>> GetRelationListByTargetIdAndCategory(string targetId, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 通过目标ID列表和分类获取关系列表
|
||||
/// </summary>
|
||||
/// <param name="targetIds"></param>
|
||||
/// <param name="category"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysRelation>> GetRelationListByTargetIdListAndCategory(List<string> targetIds, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 获取关系表用户工作台
|
||||
/// </summary>
|
||||
/// <param name="userId">用户ID</param>
|
||||
/// <returns>关系表数据</returns>
|
||||
Task<SysRelation> GetWorkbench(long userId);
|
||||
|
||||
/// <summary>
|
||||
/// 更新缓存
|
||||
/// </summary>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
Task RefreshCache(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 保存关系
|
||||
/// </summary>
|
||||
/// <param name="category">分类</param>
|
||||
/// <param name="objectId">对象ID</param>
|
||||
/// <param name="targetId">目标ID</param>
|
||||
/// <param name="extJson">拓展信息</param>
|
||||
/// <param name="clear">是否清除老的数据</param>
|
||||
/// <param name="refreshCache">是否刷新缓存</param>
|
||||
/// <returns></returns>
|
||||
Task SaveRelation(string category, long objectId, string targetId,
|
||||
string extJson, bool clear, bool refreshCache = true);
|
||||
|
||||
/// <summary>
|
||||
/// 批量保存关系
|
||||
/// </summary>
|
||||
/// <param name="category">分类</param>
|
||||
/// <param name="objectId">对象ID</param>
|
||||
/// <param name="targetIds">目标ID列表</param>
|
||||
/// <param name="extJsons">拓展信息列表</param>
|
||||
/// <param name="clear">是否清除老的数据</param>
|
||||
/// <returns></returns>
|
||||
Task SaveRelationBatch(string category, long objectId, List<string> targetIds,
|
||||
List<string> extJsons, bool clear);
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户模块ID
|
||||
/// </summary>
|
||||
/// <param name="roleIdList"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<long>> GetUserModuleId(List<long> roleIdList, long userId);
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="IRelationService"/>
|
||||
public class RelationService : DbRepository<SysRelation>, IRelationService
|
||||
{
|
||||
private readonly ILogger<RelationService> _logger;
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
|
||||
public RelationService(ILogger<RelationService> logger, ISimpleCacheService simpleCacheService)
|
||||
{
|
||||
_logger = logger;
|
||||
_simpleCacheService = simpleCacheService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRelation>> GetRelationByCategory(string category)
|
||||
{
|
||||
var key = SystemConst.CACHE_SYS_RELATION + category;
|
||||
//先从Redis拿
|
||||
var sysRelations = _simpleCacheService.Get<List<SysRelation>>(key);
|
||||
if (sysRelations == null)
|
||||
{
|
||||
//redis没有就去数据库拿
|
||||
sysRelations = await base.GetListAsync(it => it.Category == category);
|
||||
if (sysRelations.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(key, sysRelations);
|
||||
}
|
||||
}
|
||||
return sysRelations;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRelation>> GetRelationListByObjectIdAndCategory(long objectId, string category)
|
||||
{
|
||||
var sysRelations = await GetRelationByCategory(category);
|
||||
var result = sysRelations.Where(it => it.ObjectId == objectId).ToList();//获取关系集合
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<SysRelation> GetWorkbench(long userId)
|
||||
{
|
||||
var sysRelations = await GetRelationByCategory(CateGoryConst.RELATION_SYS_USER_WORKBENCH_DATA);
|
||||
var result = sysRelations.Where(it => it.ObjectId == userId).FirstOrDefault();//获取个人工作台
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRelation>> GetRelationListByObjectIdListAndCategory(List<long> objectIds, string category)
|
||||
{
|
||||
var sysRelations = await GetRelationByCategory(category);
|
||||
var result = sysRelations.Where(it => objectIds.Contains(it.ObjectId)).ToList();//获取关系集合
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRelation>> GetRelationListByTargetIdAndCategory(string targetId, string category)
|
||||
{
|
||||
var sysRelations = await GetRelationByCategory(category);
|
||||
var result = sysRelations.Where(it => it.TargetId == targetId).ToList();//获取关系集合
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysRelation>> GetRelationListByTargetIdListAndCategory(List<string> targetIds, string category)
|
||||
{
|
||||
var sysRelations = await GetRelationByCategory(category);
|
||||
var result = sysRelations.Where(it => targetIds.Contains(it.TargetId)).ToList();//获取关系集合
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task RefreshCache(string category)
|
||||
{
|
||||
var key = SystemConst.CACHE_SYS_RELATION + category;//key
|
||||
_simpleCacheService.Remove(key);//删除redis
|
||||
await GetRelationByCategory(category);//更新缓存
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveRelationBatch(string category, long objectId, List<string> targetIds,
|
||||
List<string> extJsons, bool clear)
|
||||
{
|
||||
var sysRelations = new List<SysRelation>();//要添加的列表
|
||||
for (var i = 0; i < targetIds.Count; i++)
|
||||
{
|
||||
sysRelations.Add(new SysRelation
|
||||
{
|
||||
ObjectId = objectId,
|
||||
TargetId = targetIds[i],
|
||||
Category = category,
|
||||
ExtJson = extJsons == null ? null : extJsons[i]
|
||||
});
|
||||
}
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
if (clear)
|
||||
await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的
|
||||
await InsertRangeAsync(sysRelations);//添加新的
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
await RefreshCache(category);
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task SaveRelation(string category, long objectId, string targetId,
|
||||
string extJson, bool clear, bool refreshCache = true)
|
||||
{
|
||||
var sysRelation = new SysRelation
|
||||
{
|
||||
ObjectId = objectId,
|
||||
TargetId = targetId,
|
||||
Category = category,
|
||||
ExtJson = extJson
|
||||
};
|
||||
//事务
|
||||
var result = await Tenant.UseTranAsync(async () =>
|
||||
{
|
||||
if (clear)
|
||||
await DeleteAsync(it => it.ObjectId == objectId && it.Category == category);//删除老的
|
||||
await InsertAsync(sysRelation);//添加新的
|
||||
});
|
||||
if (result.IsSuccess)//如果成功了
|
||||
{
|
||||
if (refreshCache)
|
||||
await RefreshCache(category);
|
||||
}
|
||||
else
|
||||
{
|
||||
//写日志
|
||||
_logger.LogError(result.ErrorMessage, result.ErrorException);
|
||||
throw Oops.Oh(ErrorCodeEnum.A0003);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<long>> GetUserModuleId(List<long> roleIdList, long userId)
|
||||
{
|
||||
var moduleIds = new List<long>();
|
||||
var roleRelation = await GetRelationByCategory(CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE);//获取角色模块关系集合
|
||||
if (roleRelation != null && roleRelation.Count > 0)
|
||||
{
|
||||
moduleIds = roleRelation.Where(it => roleIdList.Contains(it.ObjectId)).Select(it => it.TargetId.ToLong()).ToList();
|
||||
}
|
||||
var userRelation = await GetRelationByCategory(CateGoryConst.RELATION_SYS_USER_HAS_MODULE);//获取用户模块关系集合
|
||||
if (userRelation != null && userRelation.Count > 0)
|
||||
{
|
||||
var userModuleIds = userRelation.Where(it => it.ObjectId == userId).Select(it => it.TargetId.ToLong()).ToList();
|
||||
moduleIds.AddRange(userModuleIds);
|
||||
}
|
||||
return moduleIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 资源输入
|
||||
/// </summary>
|
||||
public class ResTreeSelectorInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 资源分类
|
||||
/// </summary>
|
||||
public string ShowSystem { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含按钮
|
||||
/// </summary>
|
||||
public bool IsContainButton { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权资源树输出
|
||||
/// </summary>
|
||||
public class ResTreeSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块名称
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块图标
|
||||
/// </summary>
|
||||
public string Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块下菜单集合
|
||||
/// </summary>
|
||||
public List<RoleGrantResourceMenu> Menu { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 授权菜单类
|
||||
/// </summary>
|
||||
public class RoleGrantResourceMenu
|
||||
{
|
||||
/// <summary>
|
||||
/// 菜单id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父id
|
||||
/// </summary>
|
||||
public long ParentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父名称
|
||||
/// </summary>
|
||||
public string ParentName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块名称
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模块id
|
||||
/// </summary>
|
||||
public long Module { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 菜单下按钮集合
|
||||
/// </summary>
|
||||
public List<RoleGrantResourceButton> Button { get; set; } = new List<RoleGrantResourceButton>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权资源按钮信息
|
||||
/// </summary>
|
||||
public class RoleGrantResourceButton
|
||||
{
|
||||
/// <summary>
|
||||
/// 按钮id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 标题
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 权限树选择器输出
|
||||
/// </summary>
|
||||
public class PermissionTreeSelector
|
||||
{
|
||||
/// <summary>
|
||||
/// 接口描述
|
||||
/// </summary>
|
||||
public string ApiName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 路由名称
|
||||
/// </summary>
|
||||
public string ApiRoute { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限名称
|
||||
/// </summary>
|
||||
public string PermissionName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 资源服务
|
||||
/// </summary>
|
||||
public interface IResourceService : ITransient
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有的菜单和模块以及单页面列表,并按分类和排序码排序
|
||||
/// </summary>
|
||||
/// <returns>所有的菜单和模块以及单页面列表</returns>
|
||||
Task<List<SysResource>> GetAllModuleAndMenuAndSpaList();
|
||||
|
||||
/// <summary>
|
||||
/// 根据模块ID获取菜单和单页面列表
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> GetMenuAndSpaListByModuleId(long id);
|
||||
|
||||
/// <summary>
|
||||
/// 根据资源ID获取所有下级资源
|
||||
/// </summary>
|
||||
/// <param name="resId">资源ID</param>
|
||||
/// <param name="isContainOneself">是否包含自己</param>
|
||||
/// <returns>资源列表</returns>
|
||||
Task<List<SysResource>> GetChildListById(long resId, bool isContainOneself = true);
|
||||
|
||||
/// <summary>
|
||||
/// 根据资源ID获取所有下级资源
|
||||
/// </summary>
|
||||
/// <param name="sysResources">资源列表</param>
|
||||
/// <param name="resId">资源ID</param>
|
||||
/// <param name="isContainOneself">是否包含自己</param>
|
||||
/// <returns>资源列表</returns>
|
||||
List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true);
|
||||
|
||||
/// <summary>
|
||||
/// 获取ID获取Code列表
|
||||
/// </summary>
|
||||
/// <param name="ids">id列表</param>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns>Code列表</returns>
|
||||
Task<List<string>> GetCodeByIds(List<long> ids, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源列表
|
||||
/// </summary>
|
||||
/// <param name="categoryList">资源分类列表</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> GetListAsync(List<string>? categoryList = null);
|
||||
|
||||
/// <summary>
|
||||
/// 根据分类获取资源列表
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns>资源列表</returns>
|
||||
Task<List<SysResource>> GetListByCategory(string category);
|
||||
|
||||
/// <summary>
|
||||
/// 根据菜单ID获取菜单
|
||||
/// </summary>
|
||||
/// <param name="ids">id列表</param>
|
||||
/// <param name="category">分类</param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> GetResourcesByIds(List<long> ids, string category);
|
||||
|
||||
/// <summary>
|
||||
/// 获取权限授权树
|
||||
/// </summary>
|
||||
/// <param name="routes">路由列表</param>
|
||||
/// <returns></returns>
|
||||
List<PermissionTreeSelector> PermissionTreeSelector(List<string> routes);
|
||||
|
||||
/// <summary>
|
||||
/// 刷新缓存
|
||||
/// </summary>
|
||||
/// <param name="category">分类名称</param>
|
||||
/// <returns></returns>
|
||||
Task RefreshCache(string category = null);
|
||||
|
||||
/// <summary>
|
||||
/// 角色授权资源树
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<ResTreeSelector>> ResourceTreeSelector();
|
||||
|
||||
/// <summary>
|
||||
/// 获取上级
|
||||
/// </summary>
|
||||
/// <param name="resourceList"></param>
|
||||
/// <param name="parentId"></param>
|
||||
/// <returns></returns>
|
||||
List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId);
|
||||
|
||||
/// <summary>
|
||||
/// 获取授权菜单类菜单名称
|
||||
/// </summary>
|
||||
/// <param name="menuList">菜单列表</param>
|
||||
/// <param name="menu">当前菜单</param>
|
||||
/// <returns></returns>
|
||||
string GetRoleGrantResourceMenuTitle(List<SysResource> menuList, SysResource menu);
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using System.ComponentModel;
|
||||
using StringHelper = SimpleTool.StringHelper;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="IResourceService"/>
|
||||
public class ResourceService : DbRepository<SysResource>, IResourceService
|
||||
{
|
||||
private readonly ISimpleCacheService _simpleCacheService;
|
||||
private readonly IRelationService _relationService;
|
||||
|
||||
public ResourceService(ISimpleCacheService simpleCacheService, IRelationService relationService)
|
||||
{
|
||||
_simpleCacheService = simpleCacheService;
|
||||
_relationService = relationService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<string>> GetCodeByIds(List<long> ids, string category)
|
||||
{
|
||||
//根据分类获取所有
|
||||
var sysResources = await GetListByCategory(category);
|
||||
//条件查询
|
||||
var result = sysResources.Where(it => ids.Contains(it.Id)).Select(it => it.Code).ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysResource>> GetListAsync(List<string>? categoryList = null)
|
||||
{
|
||||
//定义结果
|
||||
var sysResources = new List<SysResource>();
|
||||
|
||||
//定义资源分类列表,如果是空的则获取全部资源
|
||||
categoryList = categoryList != null
|
||||
? categoryList
|
||||
: new List<string>
|
||||
{
|
||||
CateGoryConst.RESOURCE_MENU, CateGoryConst.RESOURCE_BUTTON,
|
||||
CateGoryConst.RESOURCE_SPA, CateGoryConst.RESOURCE_MODULE
|
||||
};
|
||||
//遍历列表
|
||||
foreach (var category in categoryList)
|
||||
{
|
||||
//根据分类获取到资源列表
|
||||
var data = await GetListByCategory(category);
|
||||
//添加到结果集
|
||||
sysResources.AddRange(data);
|
||||
}
|
||||
return sysResources;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysResource>> GetAllModuleAndMenuAndSpaList()
|
||||
{
|
||||
//获取所有的菜单和模块以及单页面列表,
|
||||
var sysResources = await GetListAsync(new List<string>
|
||||
{
|
||||
CateGoryConst.RESOURCE_MODULE, CateGoryConst.RESOURCE_MENU, CateGoryConst.RESOURCE_SPA
|
||||
});
|
||||
if (sysResources != null)
|
||||
{
|
||||
//并按分类和排序码排序
|
||||
sysResources = sysResources.OrderBy(it => it.Category).ThenBy(it => it.SortCode).ToList();
|
||||
}
|
||||
return sysResources;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<List<SysResource>> GetMenuAndSpaListByModuleId(long id)
|
||||
{
|
||||
//获取所有的菜单和模块以及单页面列表,
|
||||
var sysResources = await GetListAsync(new List<string> { CateGoryConst.RESOURCE_MENU, CateGoryConst.RESOURCE_SPA });
|
||||
if (sysResources != null)
|
||||
{
|
||||
//并按分类和排序码排序
|
||||
sysResources = sysResources.Where(it => it.Category == CateGoryConst.RESOURCE_SPA || it.Module == id)//根据模块ID获取菜单
|
||||
.OrderBy(it => it.Category).ThenBy(it => it.SortCode).ToList();//排序
|
||||
}
|
||||
return sysResources;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task RefreshCache(string category = null)
|
||||
{
|
||||
//如果分类是空的
|
||||
if (category == null)
|
||||
{
|
||||
//删除全部key
|
||||
_simpleCacheService.DelByPattern(SystemConst.CACHE_SYS_RESOURCE);
|
||||
await GetListAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
//否则只删除一个Key
|
||||
_simpleCacheService.Remove(SystemConst.CACHE_SYS_RESOURCE + category);
|
||||
await GetListByCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> GetChildListById(long resId, bool isContainOneself = true)
|
||||
{
|
||||
//获取所有机构
|
||||
var sysResources = await GetListAsync();
|
||||
//查找下级
|
||||
var childList = GetResourceChildren(sysResources, resId);
|
||||
if (isContainOneself)//如果包含自己
|
||||
{
|
||||
//获取自己的机构信息
|
||||
var self = sysResources.Where(it => it.Id == resId).FirstOrDefault();
|
||||
if (self != null) childList.Insert(0, self);//如果机构不为空就插到第一个
|
||||
}
|
||||
return childList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<SysResource> GetChildListById(List<SysResource> sysResources, long resId, bool isContainOneself = true)
|
||||
{
|
||||
//查找下级
|
||||
var childList = GetResourceChildren(sysResources, resId);
|
||||
if (isContainOneself)//如果包含自己
|
||||
{
|
||||
//获取自己的机构信息
|
||||
var self = sysResources.Where(it => it.Id == resId).FirstOrDefault();
|
||||
if (self != null) childList.Insert(0, self);//如果机构不为空就插到第一个
|
||||
}
|
||||
return childList;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> GetListByCategory(string category)
|
||||
{
|
||||
//先从Redis拿
|
||||
var sysResources = _simpleCacheService.Get<List<SysResource>>(SystemConst.CACHE_SYS_RESOURCE + category);
|
||||
if (sysResources == null)
|
||||
{
|
||||
//redis没有就去数据库拿
|
||||
sysResources = await base.GetListAsync(it => it.Category == category);
|
||||
if (sysResources.Count > 0)
|
||||
{
|
||||
//插入Redis
|
||||
_simpleCacheService.Set(SystemConst.CACHE_SYS_RESOURCE + category, sysResources);
|
||||
}
|
||||
}
|
||||
return sysResources;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<ResTreeSelector>> ResourceTreeSelector()
|
||||
{
|
||||
var resourceTreeSelectors = new List<ResTreeSelector>();//定义结果
|
||||
//获取模块列表
|
||||
var moduleList = await GetListByCategory(CateGoryConst.RESOURCE_MODULE);
|
||||
//遍历模块
|
||||
foreach (var module in moduleList)
|
||||
{
|
||||
//将实体转换为ResourceTreeSelectorOutPut获取基本信息
|
||||
var resourceTreeSelector = module.Adapt<ResTreeSelector>();
|
||||
resourceTreeSelector.Menu = await GetRoleGrantResourceMenus(module.Id);
|
||||
resourceTreeSelectors.Add(resourceTreeSelector);
|
||||
}
|
||||
return resourceTreeSelectors;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<PermissionTreeSelector> PermissionTreeSelector(List<string> routes)
|
||||
{
|
||||
var permissions = new List<PermissionTreeSelector>();//权限列表
|
||||
var controllerTypes =
|
||||
App.EffectiveTypes.Where(u =>
|
||||
!u.IsInterface && !u.IsAbstract && u.IsDefined(typeof(RolePermissionAttribute), false));
|
||||
foreach (var controller in controllerTypes)
|
||||
{
|
||||
var apiAttribute = controller.GetCustomAttribute<ApiDescriptionSettingsAttribute>();//获取接口描述特性
|
||||
var tag = apiAttribute.Tag;//获取标签
|
||||
//获取数据权限特性
|
||||
var routeAttributes = controller.GetCustomAttributes<RouteAttribute>().ToList();
|
||||
if (routeAttributes == null)
|
||||
continue;
|
||||
var route = routeAttributes[0];//取第一个值
|
||||
var routeName = GetRouteName(controller.Name, route.Template);//赋值路由名称
|
||||
//如果路由包含在路由列表中
|
||||
if (routes.Contains(routeName))
|
||||
{
|
||||
//获取所有方法
|
||||
var methods = controller.GetMethods();
|
||||
//遍历方法
|
||||
foreach (var method in methods)
|
||||
{
|
||||
//获取忽略数据权限特性
|
||||
var ignoreRolePermission = method.GetCustomAttribute<IgnoreRolePermissionAttribute>();
|
||||
if (ignoreRolePermission == null)//如果是空的代表需要数据权限
|
||||
{
|
||||
//获取接口描述
|
||||
var displayName = method.GetCustomAttribute<DisplayNameAttribute>();
|
||||
if (displayName != null)
|
||||
{
|
||||
//默认路由名称
|
||||
var apiRoute = StringHelper.FirstCharToLower(method.Name);
|
||||
//获取get特性
|
||||
var requestGet = method.GetCustomAttribute<HttpGetAttribute>();
|
||||
if (requestGet != null)//如果是get方法
|
||||
apiRoute = requestGet.Template;
|
||||
else
|
||||
{
|
||||
//获取post特性
|
||||
var requestPost = method.GetCustomAttribute<HttpPostAttribute>();
|
||||
if (requestPost != null)//如果是post方法
|
||||
apiRoute = requestPost.Template;
|
||||
}
|
||||
apiRoute = route.Template + $"/{apiRoute}";
|
||||
if (!apiRoute.StartsWith("/"))
|
||||
apiRoute = "/" + apiRoute;//如果路由地址不是/开头则加上/防止控制器没写
|
||||
var apiName = displayName.DisplayName;//如果描述不为空则接口名称用描述的名称
|
||||
//合并
|
||||
var permissionName = apiRoute + $"[{tag}-{apiName}]";
|
||||
//添加到权限列表
|
||||
permissions.Add(new PermissionTreeSelector
|
||||
{
|
||||
ApiName = apiName,
|
||||
ApiRoute = apiRoute,
|
||||
PermissionName = permissionName
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> GetResourcesByIds(List<long> ids, string category)
|
||||
{
|
||||
//获取所有菜单
|
||||
var menus = await GetListByCategory(category);
|
||||
if (!UserManager.SuperAdmin)//超级管理员获取所有
|
||||
{
|
||||
//获取菜单信息
|
||||
menus = menus.Where(it => ids.Contains(it.Id)).ToList();
|
||||
}
|
||||
return menus;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public List<SysResource> GetResourceParent(List<SysResource> resourceList, long parentId)
|
||||
{
|
||||
//找上级资源ID列表
|
||||
var resources = resourceList.Where(it => it.Id == parentId).FirstOrDefault();
|
||||
if (resources != null)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysResource>();
|
||||
var parents = GetResourceParent(resourceList, resources.ParentId.Value);
|
||||
data.AddRange(parents);//添加子节点;
|
||||
data.Add(resources);//添加到列表
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysResource>();
|
||||
}
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取路由地址名称
|
||||
/// </summary>
|
||||
/// <param name="controllerName">控制器地址</param>
|
||||
/// <param name="template">路由名称</param>
|
||||
/// <returns></returns>
|
||||
private string GetRouteName(string controllerName, string template)
|
||||
{
|
||||
if (!template.StartsWith("/"))
|
||||
template = "/" + template;//如果路由名称不是/开头则加上/防止控制器没写
|
||||
if (template.Contains("[controller]"))
|
||||
{
|
||||
controllerName = controllerName.Replace("Controller", "");//去掉Controller
|
||||
controllerName = StringHelper.FirstCharToLower(controllerName);//转首字母小写写
|
||||
template = template.Replace("[controller]", controllerName);//替换[controller]
|
||||
}
|
||||
|
||||
return template;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源所有下级
|
||||
/// </summary>
|
||||
/// <param name="resourceList">资源列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <returns></returns>
|
||||
public List<SysResource> GetResourceChildren(List<SysResource> resourceList, long parentId)
|
||||
{
|
||||
//找下级资源ID列表
|
||||
var resources = resourceList.Where(it => it.ParentId == parentId).ToList();
|
||||
if (resources.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<SysResource>();
|
||||
foreach (var item in resources)//遍历资源
|
||||
{
|
||||
var res = GetResourceChildren(resourceList, item.Id);
|
||||
data.AddRange(res);//添加子节点;
|
||||
data.Add(item);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<SysResource>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取授权菜单
|
||||
/// </summary>
|
||||
/// <param name="moduleId">模块ID</param>
|
||||
/// <returns></returns>
|
||||
public async Task<List<ResTreeSelector.RoleGrantResourceMenu>> GetRoleGrantResourceMenus(long moduleId)
|
||||
{
|
||||
var roleGrantResourceMenus = new List<ResTreeSelector.RoleGrantResourceMenu>();//定义结果
|
||||
var allMenuList = (await GetListByCategory(CateGoryConst.RESOURCE_MENU)).Where(it => it.Module == moduleId).ToList();//获取所有菜单列表
|
||||
var allButtonList = await GetListByCategory(CateGoryConst.RESOURCE_BUTTON);//获取所有按钮列表
|
||||
var parentMenuList = allMenuList.Where(it => it.ParentId == SimpleAdminConst.ZERO).ToList();//获取一级目录
|
||||
|
||||
//遍历一级目录
|
||||
foreach (var parent in parentMenuList)
|
||||
{
|
||||
//如果是目录则去遍历下级
|
||||
if (parent.MenuType == SysResourceConst.CATALOG)
|
||||
{
|
||||
//获取所有下级菜单
|
||||
var menuList = GetChildListById(allMenuList, parent.Id, false);
|
||||
if (menuList.Count > 0)//如果有菜单
|
||||
{
|
||||
//遍历下级菜单
|
||||
foreach (var menu in menuList)
|
||||
{
|
||||
//如果菜单类型是菜单
|
||||
if (menu.MenuType is SysResourceConst.MENU or SysResourceConst.SUBSET)
|
||||
{
|
||||
//获取菜单下按钮集合并转换成对应实体
|
||||
var buttonList = allButtonList.Where(it => it.ParentId == menu.Id).ToList();
|
||||
var buttons = buttonList.Adapt<List<ResTreeSelector.RoleGrantResourceButton>>();
|
||||
roleGrantResourceMenus.Add(new ResTreeSelector.RoleGrantResourceMenu
|
||||
{
|
||||
Id = menu.Id,
|
||||
ParentId = parent.Id,
|
||||
ParentName = parent.Title,
|
||||
Module = moduleId,
|
||||
Title = GetRoleGrantResourceMenuTitle(menuList, menu),//菜单名称需要特殊处理因为有二级菜单
|
||||
Button = buttons
|
||||
});
|
||||
}
|
||||
else if (menu.MenuType == SysResourceConst.LINK || menu.MenuType == SysResourceConst.IFRAME)//如果是内链或者外链
|
||||
{
|
||||
//直接加到资源列表
|
||||
roleGrantResourceMenus.Add(new ResTreeSelector.RoleGrantResourceMenu
|
||||
{
|
||||
Id = menu.Id,
|
||||
ParentId = parent.Id,
|
||||
ParentName = parent.Title,
|
||||
Module = moduleId,
|
||||
Title = menu.Title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//否则就将自己加到一级目录里面
|
||||
roleGrantResourceMenus.Add(new ResTreeSelector.RoleGrantResourceMenu
|
||||
{
|
||||
Id = parent.Id,
|
||||
ParentId = parent.Id,
|
||||
ParentName = parent.Title,
|
||||
Module = moduleId
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//就将自己加到一级目录里面
|
||||
var roleGrantResourcesButtons = new ResTreeSelector.RoleGrantResourceMenu
|
||||
{
|
||||
Id = parent.Id,
|
||||
ParentId = parent.Id,
|
||||
ParentName = parent.Title,
|
||||
Module = moduleId,
|
||||
Title = parent.Title
|
||||
};
|
||||
//如果菜单类型是菜单
|
||||
if (parent.MenuType == SysResourceConst.MENU)
|
||||
{
|
||||
//获取菜单下按钮集合并转换成对应实体
|
||||
var buttonList = allButtonList.Where(it => it.ParentId == parent.Id).ToList();
|
||||
roleGrantResourcesButtons.Button = buttonList.Adapt<List<ResTreeSelector.RoleGrantResourceButton>>();
|
||||
}
|
||||
roleGrantResourceMenus.Add(roleGrantResourcesButtons);
|
||||
}
|
||||
}
|
||||
return roleGrantResourceMenus;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetRoleGrantResourceMenuTitle(List<SysResource> menuList, SysResource menu)
|
||||
{
|
||||
//查找菜单上级
|
||||
var parentList = GetResourceParent(menuList, menu.ParentId.Value);
|
||||
//如果有父级菜单
|
||||
if (parentList.Count > 0)
|
||||
{
|
||||
var titles = parentList.Select(it => it.Title).ToList();//提取出父级的name
|
||||
var title = string.Join("- ", titles) + $"-{menu.Title}";//根据-分割,转换成字符串并在最后加上菜单的title
|
||||
return title;
|
||||
}
|
||||
return menu.Title;//原路返回
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 编辑个人信息参数
|
||||
/// </summary>
|
||||
public class UpdateInfoInput : SysUser
|
||||
{
|
||||
/// <summary>
|
||||
/// 姓名
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Name不能为空")]
|
||||
public override string Name { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateSignatureInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 签名图片
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Signature签名图片不能为空")]
|
||||
public string Signature { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新个人工作台
|
||||
/// </summary>
|
||||
public class UpdateWorkbenchInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 工作台数据
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "WorkbenchData不能为空")]
|
||||
public string WorkbenchData { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新个人密码
|
||||
/// </summary>
|
||||
public class UpdatePasswordInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 密码
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Password不能为空")]
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新密码
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "NewPassword不能为空")]
|
||||
public string NewPassword { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认模块输入
|
||||
/// </summary>
|
||||
public class SetDefaultModuleInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模块Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Id不能为空")]
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否默认
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "IfDefault不能为空")]
|
||||
public bool IfDefault { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 我的机构数样式
|
||||
/// </summary>
|
||||
public class LoginOrgTreeOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Label { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 父ID
|
||||
/// </summary>
|
||||
public long Pid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 子节点
|
||||
/// </summary>
|
||||
public List<LoginOrgTreeOutput> Children { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 样式
|
||||
/// </summary>
|
||||
public MyStyle? Style { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 我的机构样式
|
||||
/// </summary>
|
||||
public class MyStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// 背景色
|
||||
/// </summary>
|
||||
public string Background { get; set; } = "var(--primary-color)";
|
||||
|
||||
/// <summary>
|
||||
/// 字体颜色
|
||||
/// </summary>
|
||||
public string Color { get; set; } = "#FFF";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <summary>
|
||||
/// 个人信息中心服务
|
||||
/// </summary>
|
||||
public interface IUserCenterService : ITransient
|
||||
{
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户菜单
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> GetLoginMenu(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取个人工作台
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<string> GetLoginWorkbench();
|
||||
|
||||
/// <summary>
|
||||
/// 获取组织架构
|
||||
/// </summary>
|
||||
/// <returns>组织架构</returns>
|
||||
Task<List<LoginOrgTreeOutput>> LoginOrgTree();
|
||||
|
||||
/// <summary>
|
||||
/// 获取快捷菜单树
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysResource>> ShortcutTree();
|
||||
|
||||
/// <summary>
|
||||
/// 读取登录用户站内信详情
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SysMessage> LoginMessageDetail(MessageDetailInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户的站内信分页
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<SqlSugarPagedList<SysMessage>> LoginMessagePage(MessagePageInput input);
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <summary>
|
||||
/// 更新个人信息
|
||||
/// </summary>
|
||||
/// <param name="input">信息参数</param>
|
||||
/// <returns></returns>
|
||||
Task UpdateUserInfo(UpdateInfoInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 更新签名
|
||||
/// </summary>
|
||||
/// <param name="input">签名图片</param>
|
||||
/// <returns></returns>
|
||||
Task UpdateSignature(UpdateSignatureInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 编辑个人工作台
|
||||
/// </summary>
|
||||
/// <param name="input">工作台字符串</param>
|
||||
/// <returns></returns>
|
||||
Task UpdateWorkbench(UpdateWorkbenchInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除我的消息
|
||||
/// </summary>
|
||||
/// <param name="input">消息Id</param>
|
||||
/// <returns></returns>
|
||||
Task DeleteMyMessage(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 修改个人密码
|
||||
/// </summary>
|
||||
/// <param name="input">密码信息</param>
|
||||
/// <returns></returns>
|
||||
Task UpdatePassword(UpdatePasswordInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 修改头像
|
||||
/// </summary>
|
||||
/// <param name="input">头像文件</param>
|
||||
/// <returns></returns>
|
||||
Task<string> UpdateAvatar(BaseFileInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 修改默认模块
|
||||
/// </summary>
|
||||
/// <param name="input">默认模块输入参数</param>
|
||||
/// <returns></returns>
|
||||
Task SetDefaultModule(SetDefaultModuleInput input);
|
||||
|
||||
#endregion 编辑
|
||||
|
||||
#region 我的消息
|
||||
|
||||
/// <summary>
|
||||
/// 获取登录用户的站内信分页
|
||||
/// </summary>
|
||||
/// <param name="input">查询参数</param>
|
||||
/// <returns>站内信列表</returns>
|
||||
Task<SqlSugarPagedList<SysMessage>> MyMessagePage(MessagePageInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 读取登录用户站内信详情
|
||||
/// </summary>
|
||||
/// <param name="input">消息ID</param>
|
||||
/// <returns>消息详情</returns>
|
||||
Task<SysMessage> MyMessageDetail(BaseIdInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 获取未读消息数量
|
||||
/// </summary>
|
||||
/// <returns>未读消息数量</returns>
|
||||
Task<List<MessageUnReadOutPut>> UnReadCount();
|
||||
|
||||
/// <summary>
|
||||
/// 最新未读消息
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SysMessage>> NewUnRead();
|
||||
|
||||
/// <summary>
|
||||
/// 设置已读
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> SetRead(MessageReadInput input);
|
||||
|
||||
/// <summary>
|
||||
/// 删除我的消息
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
Task<int> SetDelete(MessageReadInput input);
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
using NewLife;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SimpleAdmin.System;
|
||||
|
||||
/// <inheritdoc cref="IUserCenterService"/>
|
||||
public class UserCenterService : DbRepository<SysUser>, IUserCenterService
|
||||
{
|
||||
private readonly ISysUserService _userService;
|
||||
private readonly IRelationService _relationService;
|
||||
private readonly IResourceService _resourceService;
|
||||
private readonly IMenuService _menuService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ISysOrgService _sysOrgService;
|
||||
private readonly IMessageService _messageService;
|
||||
|
||||
public UserCenterService(ISysUserService userService, IRelationService relationService, IResourceService resourceService,
|
||||
IMenuService menuService, IConfigService configService, ISysOrgService sysOrgService,
|
||||
IMessageService messageService)
|
||||
{
|
||||
_userService = userService;
|
||||
_relationService = relationService;
|
||||
_resourceService = resourceService;
|
||||
_menuService = menuService;
|
||||
_configService = configService;
|
||||
_sysOrgService = sysOrgService;
|
||||
_messageService = messageService;
|
||||
}
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> GetLoginMenu(BaseIdInput input)
|
||||
{
|
||||
var result = new List<SysResource>();
|
||||
//获取用户信息
|
||||
var userInfo = await _userService.GetUserByAccount(UserManager.UserAccount, UserManager.TenantId);
|
||||
if (userInfo != null)
|
||||
{
|
||||
//定义菜单ID列表
|
||||
var menuIdList = new HashSet<long>();//获取所有的菜单和模块以及单页面列表,并按分类和排序码排序
|
||||
var allModuleAndMenuAndSpaList = await _resourceService.GetMenuAndSpaListByModuleId(input.Id);
|
||||
//通过 App.GetOptions<TOptions> 获取选项
|
||||
var settings = App.GetOptions<SystemSettingsOptions>();
|
||||
//如果设置了超级管理员可以看到全部
|
||||
if (settings.SuperAdminViewAllData && UserManager.SuperAdmin)
|
||||
{
|
||||
menuIdList = allModuleAndMenuAndSpaList.Select(it => it.Id).ToHashSet();
|
||||
}
|
||||
else
|
||||
{
|
||||
//获取用户所拥有的资源集合
|
||||
var resourceList =
|
||||
await _relationService.GetRelationListByObjectIdAndCategory(userInfo.Id, CateGoryConst.RELATION_SYS_USER_HAS_RESOURCE);
|
||||
if (resourceList.Count == 0)//如果没有就获取角色的
|
||||
//获取角色所拥有的资源集合
|
||||
resourceList = await _relationService.GetRelationListByObjectIdListAndCategory(userInfo.RoleIdList,
|
||||
CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE);
|
||||
//获取菜单Id集合
|
||||
menuIdList.AddRange(resourceList.Select(r => r.TargetId.ToLong()).ToList());
|
||||
}
|
||||
var allMenuList = new List<SysResource>();//菜单列表
|
||||
var allSpaList = new List<SysResource>();//单页列表
|
||||
//遍历菜单集合
|
||||
allModuleAndMenuAndSpaList.ForEach(it =>
|
||||
{
|
||||
switch (it.Category)
|
||||
{
|
||||
case CateGoryConst.RESOURCE_MENU://菜单
|
||||
allMenuList.Add(it);//添加到菜单列表
|
||||
break;
|
||||
|
||||
case CateGoryConst.RESOURCE_SPA://单页
|
||||
allSpaList.Add(it);//添加到单页列表
|
||||
break;
|
||||
}
|
||||
});
|
||||
//获取我的菜单列表,只显示启用的
|
||||
var myMenus = allMenuList.Where(it => menuIdList.Contains(it.Id) && it.Status == CommonStatusConst.ENABLE).ToList();
|
||||
// 对获取到的角色对应的菜单列表进行处理,获取父列表
|
||||
var parentList = GetMyParentMenus(allMenuList, myMenus);
|
||||
myMenus.AddRange(parentList);//合并列表
|
||||
// 遍历单页列表
|
||||
allSpaList.ForEach(it =>
|
||||
{
|
||||
it.ParentId = SimpleAdminConst.ZERO;
|
||||
});
|
||||
myMenus.AddRange(allSpaList);//单页添加到菜单
|
||||
//构建meta
|
||||
ConstructMeta(myMenus);
|
||||
//构建菜单树
|
||||
result = _menuService.ConstructMenuTrees(myMenus);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> GetLoginWorkbench()
|
||||
{
|
||||
//获取个人工作台信息
|
||||
var sysRelation = await _relationService.GetWorkbench(UserManager.UserId);
|
||||
if (sysRelation != null)
|
||||
{
|
||||
//如果有数据直接返回个人工作台
|
||||
return sysRelation.ExtJson.ToLower();
|
||||
}
|
||||
//如果没数据去系统配置里取默认的工作台
|
||||
var devConfig = await _configService.GetByConfigKey(CateGoryConst.CONFIG_SYS_BASE, SysConfigConst.SYS_DEFAULT_WORKBENCH_DATA);
|
||||
if (devConfig != null)
|
||||
{
|
||||
return devConfig.ConfigValue.ToLower();//返回工作台信息
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<LoginOrgTreeOutput>> LoginOrgTree()
|
||||
{
|
||||
var orgList = await _sysOrgService.GetListAsync();//获取全部机构
|
||||
var parentOrgList = _sysOrgService.GetOrgParents(orgList, UserManager.OrgId);//获取父节点列表
|
||||
var topOrg = parentOrgList.Where(it => it.ParentId == SimpleAdminConst.ZERO).FirstOrDefault();//获取顶级节点
|
||||
if (topOrg != null)
|
||||
{
|
||||
var orgChildList = await _sysOrgService.GetChildListById(topOrg.Id);//获取下级
|
||||
var orgTree = ConstrucOrgTrees(orgChildList, 0, UserManager.OrgId);//获取组织架构
|
||||
return orgTree;
|
||||
}
|
||||
return new List<LoginOrgTreeOutput>();
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysResource>> ShortcutTree()
|
||||
{
|
||||
var sysResourceList = await _resourceService.GetAllModuleAndMenuAndSpaList();//获取模块和菜单和单页列表
|
||||
var userInfo = await _userService.GetUserById(UserManager.UserId);
|
||||
var hasResourcesList = new HashSet<long>();//拥有的资源列表
|
||||
var hasModuleList = new HashSet<long>();//拥有的资源列表
|
||||
var userResourceList =
|
||||
(await _relationService.GetRelationListByObjectIdAndCategory(UserManager.UserId, CateGoryConst.RELATION_SYS_USER_HAS_RESOURCE))
|
||||
.Select(it => it.TargetId.ToLong()).ToList();//获取用户资源id列表
|
||||
var userModuleList =
|
||||
(await _relationService.GetRelationListByObjectIdAndCategory(UserManager.UserId, CateGoryConst.RELATION_SYS_USER_HAS_MODULE))
|
||||
.Select(it => it.TargetId.ToLong()).ToList();//获取用户模块id列表
|
||||
if (userResourceList.Count > 0)//如果用户资源列表大于0就以用户为主
|
||||
{
|
||||
hasResourcesList.AddRange(userResourceList);
|
||||
hasResourcesList.AddRange(userModuleList);
|
||||
}
|
||||
else
|
||||
{
|
||||
var roleIds = userInfo.RoleIdList;//获取角色ID列表
|
||||
var roleResourceList =
|
||||
(await _relationService.GetRelationListByObjectIdListAndCategory(roleIds, CateGoryConst.RELATION_SYS_ROLE_HAS_RESOURCE))
|
||||
.Select(it => it.TargetId.ToLong()).ToList();//获取角色资源id列表
|
||||
var roleModuleList =
|
||||
(await _relationService.GetRelationListByObjectIdListAndCategory(roleIds, CateGoryConst.RELATION_SYS_ROLE_HAS_MODULE))
|
||||
.Select(it => it.TargetId.ToLong()).ToList();//获取角色模块id列表
|
||||
hasResourcesList.AddRange(roleResourceList);
|
||||
hasResourcesList.AddRange(roleModuleList);
|
||||
}
|
||||
|
||||
// 获取目录
|
||||
var catalogList = sysResourceList.Where(it => it.MenuType == SysResourceConst.CATALOG).ToList();
|
||||
//过滤出拥有的资源列表
|
||||
sysResourceList =
|
||||
sysResourceList.Where(it =>
|
||||
hasResourcesList.Contains(it.Id) || it.Category == CateGoryConst.RESOURCE_SPA)
|
||||
.ToList();//获取拥有的资源列表
|
||||
var parentIds = sysResourceList.Select(it => it.ParentId).Distinct().ToList();//获取父ID列表
|
||||
var parentList = catalogList.Where(it => parentIds.Contains(it.Id)).ToList();//获取所拥有的父级目录
|
||||
sysResourceList.AddRange(parentList);//添加到列表
|
||||
return await _menuService.ShortcutTree(sysResourceList);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysMessage>> LoginMessagePage(MessagePageInput input)
|
||||
{
|
||||
var messages = await _messageService.MyMessagePage(input, UserManager.UserId);//分页查询
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysMessage> LoginMessageDetail(MessageDetailInput input)
|
||||
{
|
||||
return await _messageService.Detail(input);//返回详情,不带用户列表
|
||||
}
|
||||
|
||||
#endregion 查询
|
||||
|
||||
#region 编辑
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateUserInfo(UpdateInfoInput input)
|
||||
{
|
||||
//如果手机号不是空
|
||||
if (!string.IsNullOrEmpty(input.Phone))
|
||||
{
|
||||
if (!input.Phone.MatchPhoneNumber())//判断是否是手机号格式
|
||||
throw Oops.Bah("手机号码格式错误");
|
||||
input.Phone = CryptogramUtil.Sm4Encrypt(input.Phone);
|
||||
var any = await IsAnyAsync(it => it.Phone == input.Phone && it.Id != UserManager.UserId);//判断是否有重复的
|
||||
if (any)
|
||||
throw Oops.Bah("系统已存在该手机号");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(input.Email))
|
||||
{
|
||||
var match = input.Email.MatchEmail();
|
||||
if (!match.isMatch)
|
||||
throw Oops.Bah("邮箱格式错误");
|
||||
}
|
||||
|
||||
//更新指定字段
|
||||
var result = await UpdateSetColumnsTrueAsync(it => new SysUser
|
||||
{
|
||||
Name = input.Name,
|
||||
Email = input.Email,
|
||||
Phone = input.Phone,
|
||||
Nickname = input.Nickname,
|
||||
Gender = input.Gender,
|
||||
Birthday = input.Birthday
|
||||
}, it => it.Id == UserManager.UserId);
|
||||
if (result)
|
||||
_userService.DeleteUserFromRedis(UserManager.UserId);//redis删除用户数据
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateSignature(UpdateSignatureInput input)
|
||||
{
|
||||
var signatureArray = input.Signature.Split(",");//分割
|
||||
// var base64String = signatureArray[1];//根据逗号分割取到base64字符串
|
||||
// var image = base64String.GetSkBitmapFromBase64();//转成图片
|
||||
// var resizeImage = image.ResizeImage(100, 50);//重新裁剪
|
||||
// var newBase64String = resizeImage.ImgToBase64String();//重新转为base64
|
||||
// var newSignature = signatureArray[0] + "," + newBase64String;//赋值新的签名
|
||||
|
||||
//更新签名
|
||||
var result = await UpdateSetColumnsTrueAsync(it => new SysUser
|
||||
{
|
||||
Signature = input.Signature
|
||||
}, it => it.Id == UserManager.UserId);
|
||||
if (result)
|
||||
_userService.DeleteUserFromRedis(UserManager.UserId);//redis删除用户数据
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdateWorkbench(UpdateWorkbenchInput input)
|
||||
{
|
||||
//关系表保存个人工作台
|
||||
await _relationService.SaveRelation(CateGoryConst.RELATION_SYS_USER_WORKBENCH_DATA, UserManager.UserId, null, input.WorkbenchData,
|
||||
true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task DeleteMyMessage(BaseIdInput input)
|
||||
{
|
||||
await _messageService.DeleteMyMessage(input, UserManager.UserId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task UpdatePassword(UpdatePasswordInput input)
|
||||
{
|
||||
//获取用户信息
|
||||
var userInfo = await _userService.GetUserById(UserManager.UserId);
|
||||
var password = CryptogramUtil.Sm2Decrypt(input.Password);//SM2解密
|
||||
if (userInfo.Password != password) throw Oops.Bah("原密码错误");
|
||||
var newPassword = CryptogramUtil.Sm2Decrypt(input.NewPassword);//sm2解密
|
||||
var loginPolicy = await _configService.GetConfigsByCategory(CateGoryConst.CONFIG_PWD_POLICY);//获取密码策略
|
||||
var containNumber = loginPolicy.First(it => it.ConfigKey == SysConfigConst.PWD_CONTAIN_NUM).ConfigValue.ToBoolean();//是否包含数字
|
||||
var containLower = loginPolicy.First(it => it.ConfigKey == SysConfigConst.PWD_CONTAIN_LOWER).ConfigValue.ToBoolean();//是否包含小写
|
||||
var containUpper = loginPolicy.First(it => it.ConfigKey == SysConfigConst.PWD_CONTAIN_UPPER).ConfigValue.ToBoolean();//是否包含大写
|
||||
var containChar = loginPolicy.First(it => it.ConfigKey == SysConfigConst.PWD_CONTAIN_CHARACTER).ConfigValue.ToBoolean();//是否包含特殊字符
|
||||
var minLength = loginPolicy.First(it => it.ConfigKey == SysConfigConst.PWD_MIN_LENGTH).ConfigValue.ToInt();//最小长度
|
||||
if (minLength > newPassword.Length)
|
||||
throw Oops.Bah($"密码长度不能小于{minLength}");
|
||||
if (containNumber && !Regex.IsMatch(newPassword, "[0-9]"))
|
||||
throw Oops.Bah("密码必须包含数字");
|
||||
if (containLower && !Regex.IsMatch(newPassword, "[a-z]"))
|
||||
throw Oops.Bah("密码必须包含小写字母");
|
||||
if (containUpper && !Regex.IsMatch(newPassword, "[A-Z]"))
|
||||
throw Oops.Bah("密码必须包含大写字母");
|
||||
if (containChar && !Regex.IsMatch(newPassword, "[~!@#$%^&*()_+`\\-={}|\\[\\]:\";'<>?,./]"))
|
||||
throw Oops.Bah("密码必须包含特殊字符");
|
||||
// var similarity = PwdUtil.Similarity(password, newPassword);
|
||||
// if (similarity > 80)
|
||||
// throw Oops.Bah($"新密码请勿与旧密码过于相似");
|
||||
newPassword = CryptogramUtil.Sm4Encrypt(newPassword);//SM4加密
|
||||
userInfo.Password = newPassword;
|
||||
await UpdateSetColumnsTrueAsync(it => new SysUser { Password = newPassword }, it => it.Id == userInfo.Id);//更新密码
|
||||
_userService.DeleteUserFromRedis(UserManager.UserId);//redis删除用户数据
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<string> UpdateAvatar(BaseFileInput input)
|
||||
{
|
||||
var userInfo = await _userService.GetUserById(UserManager.UserId);
|
||||
|
||||
var file = input.File;
|
||||
using var fileStream = file.OpenReadStream();//获取文件流
|
||||
var bytes = new byte[fileStream.Length];
|
||||
fileStream.Read(bytes, 0, bytes.Length);
|
||||
fileStream.Close();
|
||||
var base64String = Convert.ToBase64String(bytes);//转base64
|
||||
var avatar = base64String.ToImageBase64();//转图片
|
||||
await UpdateSetColumnsTrueAsync(it => new SysUser { Avatar = avatar }, it => it.Id == userInfo.Id);// 更新头像
|
||||
_userService.DeleteUserFromRedis(UserManager.UserId);//redis删除用户数据
|
||||
return avatar;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task SetDefaultModule(SetDefaultModuleInput input)
|
||||
{
|
||||
//获取用户信息
|
||||
var userInfo = await _userService.GetUserById(UserManager.UserId);
|
||||
//如果是默认模块
|
||||
if (input.IfDefault)
|
||||
userInfo.DefaultModule = input.Id;
|
||||
else
|
||||
userInfo.DefaultModule = null;
|
||||
await Context.Updateable(userInfo).UpdateColumns(it => new { it.DefaultModule }).ExecuteCommandAsync();//修改默认模块
|
||||
_userService.DeleteUserFromRedis(UserManager.UserId);//redis删除用户数据
|
||||
}
|
||||
|
||||
#endregion 编辑
|
||||
|
||||
#region 我的消息
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SqlSugarPagedList<SysMessage>> MyMessagePage(MessagePageInput input)
|
||||
{
|
||||
var messages = await _messageService.MyMessagePage(input, UserManager.UserId);//分页查询
|
||||
return messages;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<SysMessage> MyMessageDetail(BaseIdInput input)
|
||||
{
|
||||
return await _messageService.Detail(new MessageDetailInput { Id = input.Id, Read = true });//返回详情,不带用户列表
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<MessageUnReadOutPut>> UnReadCount()
|
||||
{
|
||||
return await _messageService.UnReadCount(UserManager.UserId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<SysMessage>> NewUnRead()
|
||||
{
|
||||
return await _messageService.NewUnRead(UserManager.UserId);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> SetRead(MessageReadInput input)
|
||||
{
|
||||
return await _messageService.SetRead(input);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> SetDelete(MessageReadInput input)
|
||||
{
|
||||
return await _messageService.SetDelete(input);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取父菜单集合
|
||||
/// </summary>
|
||||
/// <param name="allMenuList">所有菜单列表</param>
|
||||
/// <param name="myMenus">我的菜单列表</param>
|
||||
/// <returns></returns>
|
||||
private List<SysResource> GetMyParentMenus(List<SysResource> allMenuList, List<SysResource> myMenus)
|
||||
{
|
||||
var parentList = new List<SysResource>();
|
||||
myMenus.ForEach(it =>
|
||||
{
|
||||
var parents = _resourceService.GetResourceParent(allMenuList, it.ParentId.Value);//获取父级
|
||||
parents.ForEach(parent =>
|
||||
{
|
||||
// 如果父级菜单存在,并且父级菜单状态为启用,并不在父级列表中,则添加到父级列表
|
||||
if (parent != null && parent.Status == CommonStatusConst.ENABLE && !parentList.Contains(parent)
|
||||
&& !myMenus.Contains(parent))
|
||||
{
|
||||
parentList.Add(parent);//添加到父列表
|
||||
}
|
||||
});
|
||||
});
|
||||
return parentList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建Meta
|
||||
/// </summary>
|
||||
/// <param name="myMenus">我的菜单集合</param>
|
||||
private void ConstructMeta(List<SysResource> myMenus)
|
||||
{
|
||||
myMenus.ForEach(it =>
|
||||
{
|
||||
//定义meta
|
||||
var meta = new Meta
|
||||
{
|
||||
Icon = it.Icon,
|
||||
Title = it.Title,
|
||||
IsAffix = it.IsAffix,
|
||||
IsHide = it.IsHide,
|
||||
IsKeepAlive = it.IsKeepAlive,
|
||||
IsFull = it.IsFull,
|
||||
ActiveMenu = it.ActiveMenu,
|
||||
IsLink = it.IsLink
|
||||
};
|
||||
it.Meta = meta;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建机构树
|
||||
/// </summary>
|
||||
/// <param name="orgList">机构列表</param>
|
||||
/// <param name="parentId">父ID</param>
|
||||
/// <param name="orgId">用户ID</param>
|
||||
/// <returns></returns>
|
||||
public List<LoginOrgTreeOutput> ConstrucOrgTrees(List<SysOrg> orgList, long parentId, long orgId)
|
||||
{
|
||||
//找下级字典ID列表
|
||||
var orgParents = orgList.Where(it => it.ParentId == parentId).OrderBy(it => it.SortCode).ToList();
|
||||
if (orgParents.Count > 0)//如果数量大于0
|
||||
{
|
||||
var data = new List<LoginOrgTreeOutput>();
|
||||
foreach (var item in orgParents)//遍历字典
|
||||
{
|
||||
var loginOrg = new LoginOrgTreeOutput
|
||||
{
|
||||
Children = ConstrucOrgTrees(orgList, item.Id, orgId),//添加子节点
|
||||
Id = item.Id,
|
||||
Label = item.Name,
|
||||
Pid = item.ParentId,
|
||||
Style = orgId != item.Id ? null : new LoginOrgTreeOutput.MyStyle()
|
||||
};
|
||||
data.Add(loginOrg);//添加到列表
|
||||
}
|
||||
return data;//返回结果
|
||||
}
|
||||
return new List<LoginOrgTreeOutput>();
|
||||
}
|
||||
|
||||
#endregion 方法
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System.Services.Upload;
|
||||
|
||||
public class IUploadService
|
||||
{
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2022-Now 少林寺驻北固山办事处大神父王喇嘛
|
||||
//
|
||||
// SimpleAdmin 基于 Apache License Version 2.0 协议发布,可用于商业项目,但必须遵守以下补充条款:
|
||||
// 1.请不要删除和修改根目录下的LICENSE文件。
|
||||
// 2.请不要删除和修改SimpleAdmin源码头部的版权声明。
|
||||
// 3.分发源码时候,请注明软件出处 https://gitee.com/dotnetmoyu/SimpleAdmin
|
||||
// 4.基于本软件的作品,只能使用 SimpleAdmin 作为后台服务,除外情况不可商用且不允许二次分发或开源。
|
||||
// 5.请不得将本软件应用于危害国家安全、荣誉和利益的行为,不能以任何形式用于非法为目的的行为。
|
||||
// 6.任何基于本软件而产生的一切法律纠纷和责任,均于我司无关。
|
||||
|
||||
namespace SimpleAdmin.System.Services.Upload;
|
||||
|
||||
public class UploadService : IUploadService
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user