UserController.cs 15 KB

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