UserController.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. using Hotline.CallCenter.Tels;
  2. using Hotline.Identity.Accounts;
  3. using Hotline.Identity.Roles;
  4. using Hotline.Permissions;
  5. using Hotline.Repository.SqlSugar.Extensions;
  6. using Hotline.Users;
  7. using MapsterMapper;
  8. using Microsoft.AspNetCore.Mvc;
  9. using XF.Domain.Authentications;
  10. using XF.Domain.Exceptions;
  11. using Hotline.Share.Dtos;
  12. using Hotline.Share.Dtos.Users;
  13. using Hotline.Share.Enums.Order;
  14. using Microsoft.AspNetCore.Authorization;
  15. using Microsoft.Extensions.Options;
  16. using SqlSugar;
  17. using XF.Domain.Options;
  18. using XF.Utility.EnumExtensions;
  19. using Hotline.Caching.Interfaces;
  20. using Hotline.Share.Enums.CallCenter;
  21. using System;
  22. using Hotline.Settings.CommonOpinions;
  23. using Hotline.Share.Enums.Identity;
  24. using Hotline.Share.Enums.User;
  25. using XF.Domain.Repository;
  26. using Hotline.SeedData;
  27. namespace Hotline.Api.Controllers;
  28. /// <summary>
  29. /// 用户管理相关接口
  30. /// </summary>
  31. public class UserController : BaseController
  32. {
  33. private readonly ISessionContext _sessionContext;
  34. private readonly IUserDomainService _userDomainService;
  35. private readonly ITelRepository _telRepository;
  36. private readonly IRepository<User> _userRepository;
  37. private readonly IWorkRepository _workRepository;
  38. private readonly ITelCacheManager _telCacheManager;
  39. private readonly IUserCacheManager _userCacheManager;
  40. private readonly IMapper _mapper;
  41. private readonly IAccountRepository _accountRepository;
  42. private readonly IAccountDomainService _accountDomainService;
  43. private readonly IOptions<IdentityConfiguration> _identityConfigurationAccessor;
  44. private readonly ITelRestRepository _telRestRepository;
  45. private readonly IRepository<SystemCommonOpinion> _commonOpinionRepository;
  46. public UserController(
  47. ISessionContext sessionContext,
  48. IUserDomainService userDomainService,
  49. ITelRepository telRepository,
  50. IRepository<User> userRepository,
  51. IWorkRepository workRepository,
  52. ITelCacheManager telCacheManager,
  53. IUserCacheManager userCacheManager,
  54. IMapper mapper,
  55. IAccountRepository accountRepository,
  56. IAccountDomainService accountDomainService,
  57. IOptions<IdentityConfiguration> identityConfigurationAccessor,
  58. ITelRestRepository telRestRepository,
  59. IRepository<SystemCommonOpinion> commonOpinionRepository)
  60. {
  61. _sessionContext = sessionContext;
  62. _userDomainService = userDomainService;
  63. _telRepository = telRepository;
  64. _userRepository = userRepository;
  65. _workRepository = workRepository;
  66. _telCacheManager = telCacheManager;
  67. _userCacheManager = userCacheManager;
  68. _mapper = mapper;
  69. _accountRepository = accountRepository;
  70. _accountDomainService = accountDomainService;
  71. _identityConfigurationAccessor = identityConfigurationAccessor;
  72. _telRestRepository = telRestRepository;
  73. _commonOpinionRepository = commonOpinionRepository;
  74. }
  75. #region 小休申请
  76. /// <summary>
  77. /// 小休申请列表
  78. /// </summary>
  79. /// <param name="dto"></param>
  80. /// <returns></returns>
  81. [HttpGet("rest-apply-paged")]
  82. public async Task<PagedDto<RestDto>> RestApplyList([FromQuery] RestPagedDto dto)
  83. {
  84. var (total, items) = await _telRestRepository.Queryable(includeDeleted: false)
  85. .WhereIF(!string.IsNullOrEmpty(dto.KeyWords),
  86. d => d.UserName.Contains(dto.KeyWords) || d.StaffNo.Contains(dto.KeyWords))
  87. .WhereIF(dto.BeginTime != null && dto.BeginTime != DateTime.MinValue, d => d.CreationTime >= dto.BeginTime)
  88. .WhereIF(dto.EndTime != null && dto.EndTime != DateTime.MinValue, d => d.CreationTime <= dto.EndTime)
  89. .WhereIF(!string.IsNullOrEmpty(dto.Reason), d => d.Reason == dto.Reason)
  90. .WhereIF(dto.Status != null, d => d.ApplyStatus == dto.Status)
  91. .OrderByDescending(d => d.CreationTime)
  92. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  93. return new PagedDto<RestDto>(total, _mapper.Map<IReadOnlyList<RestDto>>(items));
  94. }
  95. /// <summary>
  96. /// 小休申请页面基础信息
  97. /// </summary>
  98. /// <returns></returns>
  99. [HttpGet("rest-apply-basedata")]
  100. public object RestApplyBaseData()
  101. {
  102. return new
  103. {
  104. RestApplyStatus = EnumExts.GetDescriptions<ETelRestApplyStatus>(),
  105. RestReason = _commonOpinionRepository.Queryable()
  106. .Where(x => x.CommonType == Share.Enums.Settings.ECommonType.RestReason).ToList()
  107. };
  108. }
  109. #endregion
  110. /// <summary>
  111. /// 上班
  112. /// </summary>
  113. [HttpPost("on-duty")]
  114. public async Task OnDuty([FromBody] OnDutyDto dto)
  115. {
  116. var telNo = dto.TelNo;
  117. if (string.IsNullOrEmpty(telNo))
  118. {
  119. var user = await _userRepository.GetAsync(d => d.Id == _sessionContext.RequiredUserId,
  120. HttpContext.RequestAborted);
  121. if (user == null)
  122. throw UserFriendlyException.SameMessage("无效用户编号");
  123. if (string.IsNullOrEmpty(user.DefaultTelNo))
  124. throw UserFriendlyException.SameMessage("未设置默认分机号");
  125. telNo = user.DefaultTelNo;
  126. }
  127. var tel = _telCacheManager.GetTel(telNo);
  128. await _userDomainService.OnDutyAsync(_sessionContext.RequiredUserId, tel, HttpContext.RequestAborted);
  129. }
  130. /// <summary>
  131. /// 下班
  132. /// </summary>
  133. [HttpPost("off-duty")]
  134. public Task<WorkDto?> OffDuty()
  135. {
  136. return _userDomainService.OffDutyAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  137. }
  138. /// <summary>
  139. /// 分页查询用户
  140. /// </summary>
  141. /// <returns></returns>
  142. [HttpGet("paged")]
  143. public async Task<PagedDto<UserDto>> QueryPaged([FromQuery] UserPagedDto dto)
  144. {
  145. var (total, items) = await _userRepository.Queryable(includeDeleted: true)
  146. .Includes(d => d.Account)
  147. .Includes(d => d.Roles)
  148. .Includes(d => d.Organization)
  149. .Where(d => d.Account.AccountType == EAccountType.Personal && d.Id != SysAccountSeedData.Id)
  150. .WhereIF(_sessionContext.OrgIsCenter == false, d => d.OrgId.StartsWith(_sessionContext.RequiredOrgId))
  151. .WhereIF(!string.IsNullOrEmpty(dto.Keyword),
  152. d => d.Name.Contains(dto.Keyword!) || d.PhoneNo.Contains(dto.Keyword!) ||
  153. d.Account.UserName.Contains(dto.Keyword))
  154. .WhereIF(!string.IsNullOrEmpty(dto.OrgCode), d => d.OrgId == dto.OrgCode)
  155. .WhereIF(!string.IsNullOrEmpty(dto.Role), d => d.Roles.Any(x => x.Id == dto.Role))
  156. .OrderBy(d => d.Account.Status)
  157. .OrderBy(d => d.Organization.OrgType)
  158. //.OrderBy(d => d.Organization.Id)
  159. .OrderByDescending(d => d.CreationTime)
  160. .ToPagedListAsync(dto.PageIndex, dto.PageSize, HttpContext.RequestAborted);
  161. return new PagedDto<UserDto>(total, _mapper.Map<IReadOnlyList<UserDto>>(items));
  162. }
  163. /// <summary>
  164. /// 更新用户
  165. /// </summary>
  166. /// <param name="dto"></param>
  167. /// <returns></returns>
  168. [Permission(EPermission.UpdateUser)]
  169. [HttpPut]
  170. public async Task Update([FromBody] UpdateUserDto dto)
  171. {
  172. //工号不能重复
  173. if (!string.IsNullOrEmpty(dto.StaffNo))
  174. {
  175. var isStaffNoExists = await _userRepository.Queryable()
  176. .AnyAsync(d => d.Id != dto.Id && d.StaffNo == dto.StaffNo, HttpContext.RequestAborted);
  177. if (isStaffNoExists)
  178. throw UserFriendlyException.SameMessage("工号已存在");
  179. }
  180. var user = await _userRepository.Queryable()
  181. .Includes(d => d.Account)
  182. .FirstAsync(d => d.Id == dto.Id, HttpContext.RequestAborted);
  183. if (user is null)
  184. throw UserFriendlyException.SameMessage("无效用户编号");
  185. if (user.IsDeleted)
  186. throw UserFriendlyException.SameMessage("账号不存在");
  187. CheckAccountStatus(user.Account);
  188. _mapper.Map(dto, user);
  189. await _userRepository.UpdateNav(user).Include(d => d.Account)
  190. .ExecuteCommandAsync();
  191. //set roles
  192. await _accountRepository.SetAccountRolesAsync(user.Id, dto.RoleIds, HttpContext.RequestAborted);
  193. }
  194. /// <summary>
  195. /// 新增用户
  196. /// </summary>
  197. /// <param name="dto"></param>
  198. /// <returns></returns>
  199. [Permission(EPermission.AddUser)]
  200. [HttpPost]
  201. public async Task<string> Add([FromBody] AddUserDto dto)
  202. {
  203. //工号不能重复
  204. if (!string.IsNullOrEmpty(dto.StaffNo))
  205. {
  206. var isStaffNoExists = await _userRepository.Queryable()
  207. .AnyAsync(d => d.StaffNo == dto.StaffNo, HttpContext.RequestAborted);
  208. if (isStaffNoExists)
  209. throw UserFriendlyException.SameMessage("工号已存在");
  210. }
  211. var account = await _accountRepository.GetAsync(d => d.UserName == dto.UserName, HttpContext.RequestAborted);
  212. if (account is null)
  213. {
  214. account = _mapper.Map<Account>(dto);
  215. var jwtOptions = _identityConfigurationAccessor.Value.Jwt;
  216. if (string.IsNullOrEmpty(jwtOptions.Issuer))
  217. throw new UserFriendlyException("jwt.Issuer未配置");
  218. account.ClientId = jwtOptions.Issuer;
  219. await _accountRepository.AddAsync(account, HttpContext.RequestAborted);
  220. var user = _mapper.Map<User>(dto);
  221. user.Id = account.Id;
  222. await _userRepository.AddAsync(user, HttpContext.RequestAborted);
  223. //initial pwd
  224. await _accountDomainService.InitialPasswordAsync(account, HttpContext.RequestAborted);
  225. //set roles
  226. await _accountRepository.SetAccountRolesAsync(account.Id, dto.RoleIds, HttpContext.RequestAborted);
  227. return account.Id;
  228. }
  229. else
  230. {
  231. //if (_accountDomainService.IsLockedOut(account))
  232. // throw UserFriendlyException.SameMessage("该账号已被锁定,请联系管理员");
  233. ////set roles
  234. //await _accountRepository.SetAccountRolesAsync(account.Id, dto.RoleIds, HttpContext.RequestAborted);
  235. //var user = await _userRepository.GetAsync(account.Id, HttpContext.RequestAborted);
  236. //if (user is null)
  237. //{
  238. // user = _mapper.Map<User>(dto);
  239. // user.Id = account.Id;
  240. // return await _userRepository.AddAsync(user, HttpContext.RequestAborted);
  241. //}
  242. //if (user.IsDeleted)
  243. //{
  244. // user.Recover();
  245. // _mapper.Map(dto, user);
  246. // await _userRepository.UpdateAsync(user);
  247. // return user.Id;
  248. //}
  249. throw UserFriendlyException.SameMessage("用户已存在");
  250. }
  251. }
  252. /// <summary>
  253. /// 删除用户
  254. /// </summary>
  255. /// <param name="id"></param>
  256. /// <returns></returns>
  257. [Permission(EPermission.RemoveUser)]
  258. [HttpDelete("{id}")]
  259. public async Task Remove(string id)
  260. {
  261. var work = await _workRepository.GetCurrentWorkByUserAsync(id, HttpContext.RequestAborted);
  262. if (work is not null)
  263. throw UserFriendlyException.SameMessage("用户正在工作中,请下班以后再删除");
  264. var account = await _accountRepository.GetAsync(id, HttpContext.RequestAborted);
  265. if (account is not null)
  266. {
  267. await _accountDomainService.UnRegisterAsync(account, HttpContext.RequestAborted);
  268. await _userRepository.RemoveAsync(id, true, HttpContext.RequestAborted);
  269. }
  270. }
  271. /// <summary>
  272. /// 查询用户当前状态
  273. /// </summary>
  274. /// <returns></returns>
  275. [HttpGet("state")]
  276. public async Task<UserStateDto> GetUserState()
  277. {
  278. var userId = _sessionContext.RequiredUserId;
  279. var isOnDuty = await _userCacheManager.IsWorkingByUserAsync(userId, HttpContext.RequestAborted);
  280. var isResting = false;
  281. var telNo = string.Empty;
  282. if (isOnDuty)
  283. {
  284. var work = _userCacheManager.GetWorkByUser(userId);
  285. isResting = await _telRestRepository.IsRestingAsync(work.TelNo, HttpContext.RequestAborted);
  286. telNo = work.TelNo;
  287. }
  288. return new UserStateDto(isOnDuty, isResting, telNo);
  289. }
  290. /// <summary>
  291. /// 查询用户角色
  292. /// </summary>
  293. /// <returns></returns>
  294. [HttpGet("{id}/roles")]
  295. public async Task<IReadOnlyList<Role>> GetUserRoles(string id)
  296. {
  297. var account = await _accountRepository.Queryable()
  298. .Includes(d => d.Roles)
  299. .FirstAsync(d => d.Id == id);
  300. if (account == null)
  301. throw UserFriendlyException.SameMessage("无效账号编号");
  302. return account.Roles;
  303. }
  304. /// <summary>
  305. /// 设置用户角色
  306. /// </summary>
  307. /// <returns></returns>
  308. [HttpPost("roles")]
  309. public async Task SetUserRoles([FromBody] SetUserRolesDto dto)
  310. {
  311. await _accountRepository.SetAccountRolesAsync(dto.UserId, dto.RoleIds, HttpContext.RequestAborted);
  312. }
  313. /// <summary>
  314. /// 查询密码更改状态
  315. /// </summary>
  316. /// <returns></returns>
  317. /// <exception cref="UserFriendlyException"></exception>
  318. [HttpGet("pwd-changed")]
  319. public Task<bool> GetPasswordChangeStatus()
  320. {
  321. var claim = User.Claims.FirstOrDefault(d => d.Type == AppClaimTypes.UserPasswordChanged);
  322. if (claim is null)
  323. throw UserFriendlyException.SameMessage("无密码更改信息");
  324. return Task.FromResult(Convert.ToBoolean(claim.Value));
  325. }
  326. /// <summary>
  327. /// 修改密码
  328. /// </summary>
  329. /// <param name="dto"></param>
  330. /// <returns></returns>
  331. [HttpPost("change-pwd")]
  332. public async Task ChangePassword([FromBody] ChangePasswordDto dto)
  333. {
  334. var account = await _accountRepository.GetAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  335. CheckAccountStatus(account);
  336. var result = await _accountDomainService.ResetPasswordAsync(account, dto.CurrentPassword, dto.NewPassword,
  337. HttpContext.RequestAborted);
  338. if (!result.Succeeded)
  339. throw UserFriendlyException.SameMessage(string.Join(',',
  340. result.Errors.Select(d => d.Description).ToList()));
  341. account.PasswordChanged = true;
  342. await _accountRepository.UpdateAsync(account, HttpContext.RequestAborted);
  343. }
  344. /// <summary>
  345. /// 修改默认密码
  346. /// </summary>
  347. /// <param name="dto"></param>
  348. /// <returns></returns>
  349. [HttpPost("change-default-pwd")]
  350. public async Task ChangeDefaultPassword([FromBody] NewPasswordDto dto)
  351. {
  352. var account = await _accountRepository.GetAsync(_sessionContext.RequiredUserId, HttpContext.RequestAborted);
  353. CheckAccountStatus(account);
  354. var accountOptions = _identityConfigurationAccessor.Value.Account;
  355. var result = await _accountDomainService.ResetPasswordAsync(account, accountOptions.DefaultPassword,
  356. dto.NewPassword,
  357. HttpContext.RequestAborted);
  358. if (!result.Succeeded)
  359. throw UserFriendlyException.SameMessage(string.Join(',',
  360. result.Errors.Select(d => d.Description).ToList()));
  361. account.PasswordChanged = true;
  362. await _accountRepository.UpdateAsync(account, HttpContext.RequestAborted);
  363. }
  364. /// <summary>
  365. /// 重置密码
  366. /// </summary>
  367. /// <returns></returns>
  368. [HttpPost("initial-pwd/{userId}")]
  369. public async Task InitialPassword(string userId)
  370. {
  371. var account = await _accountRepository.GetAsync(userId, HttpContext.RequestAborted);
  372. CheckAccountStatus(account);
  373. await _accountDomainService.InitialPasswordAsync(account, HttpContext.RequestAborted);
  374. }
  375. /// <summary>
  376. /// 根据id批量查询用户
  377. /// </summary>
  378. /// <param name="ids"></param>
  379. /// <returns></returns>
  380. [HttpPost("range")]
  381. public async Task<IReadOnlyList<UserDto>> Query([FromBody] IReadOnlyList<string> ids)
  382. {
  383. var users = await _userRepository.Queryable()
  384. .Includes(d => d.Account, x => x.Roles)
  385. .Includes(d => d.Organization)
  386. .Where(d => ids.Contains(d.Id))
  387. .OrderByDescending(d => d.CreationTime)
  388. .ToListAsync();
  389. return _mapper.Map<IReadOnlyList<UserDto>>(users);
  390. }
  391. /// <summary>
  392. /// 根据姓名模糊查询用户
  393. /// </summary>
  394. /// <param name="name"></param>
  395. /// <returns></returns>
  396. [HttpGet("withorg")]
  397. public async Task<IReadOnlyList<UserDto>> Query([FromQuery] string name)
  398. {
  399. var users = await _userRepository.Queryable()
  400. .Includes(d => d.Organization)
  401. .Where(d => d.Name.Contains(name))
  402. .OrderByDescending(d => d.Name)
  403. .ToListAsync();
  404. return _mapper.Map<IReadOnlyList<UserDto>>(users);
  405. }
  406. [HttpGet("base-data")]
  407. public object BaseData()
  408. {
  409. return new
  410. {
  411. GenderOptions = EnumExts.GetDescriptions<EGender>(),
  412. UserTypeOptions = EnumExts.GetDescriptions<EUserType>(),
  413. };
  414. }
  415. private void CheckAccountStatus(Account? account)
  416. {
  417. if (account == null)
  418. throw UserFriendlyException.SameMessage("无效账号编号");
  419. if (_accountDomainService.IsLockedOut(account))
  420. throw UserFriendlyException.SameMessage("账号已被锁定");
  421. if (account.IsDeleted)
  422. throw UserFriendlyException.SameMessage("账号不存在");
  423. }
  424. }