You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

393 lines
17 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. package com.inscloudtech.system.service;
  2. import cn.dev33.satoken.exception.NotLoginException;
  3. import cn.dev33.satoken.secure.BCrypt;
  4. import cn.dev33.satoken.stp.StpUtil;
  5. import cn.hutool.core.bean.BeanUtil;
  6. import cn.hutool.core.collection.CollectionUtil;
  7. import cn.hutool.core.util.ObjectUtil;
  8. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  9. import com.inscloudtech.bankStatementAnalysis.util.DesUtil;
  10. import com.inscloudtech.common.constant.CacheConstants;
  11. import com.inscloudtech.common.constant.Constants;
  12. import com.inscloudtech.common.core.domain.entity.SysRole;
  13. import com.inscloudtech.common.core.domain.event.LogininforEvent;
  14. import com.inscloudtech.common.core.domain.dto.RoleDTO;
  15. import com.inscloudtech.common.core.domain.entity.SysUser;
  16. import com.inscloudtech.common.core.domain.model.LoginUser;
  17. import com.inscloudtech.common.core.domain.model.XcxLoginUser;
  18. import com.inscloudtech.common.enums.DeviceType;
  19. import com.inscloudtech.common.enums.LoginType;
  20. import com.inscloudtech.common.enums.UserStatus;
  21. import com.inscloudtech.common.exception.user.CaptchaException;
  22. import com.inscloudtech.common.exception.user.CaptchaExpireException;
  23. import com.inscloudtech.common.exception.user.UserException;
  24. import com.inscloudtech.common.helper.LoginHelper;
  25. import com.inscloudtech.common.utils.DateUtils;
  26. import com.inscloudtech.common.utils.MessageUtils;
  27. import com.inscloudtech.common.utils.ServletUtils;
  28. import com.inscloudtech.common.utils.StringUtils;
  29. import com.inscloudtech.common.utils.redis.RedisUtils;
  30. import com.inscloudtech.common.utils.spring.SpringUtils;
  31. import com.inscloudtech.system.mapper.SysUserMapper;
  32. import lombok.RequiredArgsConstructor;
  33. import lombok.extern.slf4j.Slf4j;
  34. import org.springframework.beans.factory.annotation.Value;
  35. import org.springframework.stereotype.Service;
  36. import java.time.Duration;
  37. import java.util.HashMap;
  38. import java.util.List;
  39. import java.util.Map;
  40. import java.util.Set;
  41. import java.util.function.Supplier;
  42. import java.util.stream.Collectors;
  43. /**
  44. * 登录校验方法
  45. *
  46. * @author inscloudtech
  47. */
  48. @RequiredArgsConstructor
  49. @Slf4j
  50. @Service
  51. public class SysLoginService {
  52. private final SysUserMapper userMapper;
  53. private final ISysConfigService configService;
  54. private final SysPermissionService permissionService;
  55. @Value("${user.password.maxRetryCount}")
  56. private Integer maxRetryCount;
  57. @Value("${user.password.lockTime}")
  58. private Integer lockTime;
  59. /**
  60. * 登录验证
  61. *
  62. * @param username 用户名
  63. * @param password 密码
  64. * @param code 验证码
  65. * @param uuid 唯一标识
  66. * @return 结果
  67. */
  68. public Map<String, Object> login(String username, String password, String code, String uuid,String idCardNo) {
  69. // boolean captchaEnabled = configService.selectCaptchaEnabled();
  70. // // 验证码开关
  71. // if (captchaEnabled) {
  72. // validateCaptcha(username, code, uuid);
  73. // }
  74. Map<String, Object> ajax = new HashMap<>();
  75. SysUser user = loadUserByUsername(username,idCardNo);
  76. checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(DesUtil.decrypt(password), user.getPassword()));
  77. // 此处可根据登录用户的数据不同 自行创建 loginUser
  78. LoginUser loginUser = buildLoginUser(user);
  79. // 生成token
  80. LoginHelper.loginByDevice(loginUser, DeviceType.PC);
  81. recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
  82. recordLoginInfo(user.getUserId(), username);
  83. ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
  84. ajax.put("origPasswdStatus", user.getOrigPasswdStatus());
  85. ajax.put("type", Constants.USER_TYPE_OTHER);
  86. List<SysRole> roles = user.getRoles();
  87. if(CollectionUtil.isNotEmpty(roles)){
  88. Set<Long> roleIds = roles.stream().filter(item -> item.getRoleId() != null).map(SysRole::getRoleId).collect(Collectors.toSet());
  89. if (roleIds.contains(Constants.LEADER_ROLE_ID)) {
  90. ajax.put("type", Constants.USER_TYPE_LEADER);
  91. } else if (roleIds.contains(Constants.INVESTIGATOR_ROLE_ID)) {
  92. ajax.put("type", Constants.USER_TYPE_INVESTIGATOR);
  93. } else {
  94. ajax.put("type", Constants.USER_TYPE_OTHER);
  95. }
  96. }
  97. return ajax;
  98. }
  99. public String smsLogin(String phonenumber, String smsCode) {
  100. // 通过手机号查找用户
  101. SysUser user = loadUserByPhonenumber(phonenumber);
  102. checkLogin(LoginType.SMS, user.getUserName(), () -> !validateSmsCode(phonenumber, smsCode));
  103. // 此处可根据登录用户的数据不同 自行创建 loginUser
  104. LoginUser loginUser = buildLoginUser(user);
  105. // 生成token
  106. LoginHelper.loginByDevice(loginUser, DeviceType.APP);
  107. recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
  108. recordLoginInfo(user.getUserId(), user.getUserName());
  109. return StpUtil.getTokenValue();
  110. }
  111. public String emailLogin(String email, String emailCode) {
  112. // 通过手机号查找用户
  113. SysUser user = loadUserByEmail(email);
  114. checkLogin(LoginType.EMAIL, user.getUserName(), () -> !validateEmailCode(email, emailCode));
  115. // 此处可根据登录用户的数据不同 自行创建 loginUser
  116. LoginUser loginUser = buildLoginUser(user);
  117. // 生成token
  118. LoginHelper.loginByDevice(loginUser, DeviceType.APP);
  119. recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
  120. recordLoginInfo(user.getUserId(), user.getUserName());
  121. return StpUtil.getTokenValue();
  122. }
  123. public String xcxLogin(String xcxCode) {
  124. // xcxCode 为 小程序调用 wx.login 授权后获取
  125. // todo 以下自行实现
  126. // 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key 与 openid
  127. String openid = "";
  128. SysUser user = loadUserByOpenid(openid);
  129. // 此处可根据登录用户的数据不同 自行创建 loginUser
  130. XcxLoginUser loginUser = new XcxLoginUser();
  131. loginUser.setUserId(user.getUserId());
  132. loginUser.setUsername(user.getUserName());
  133. loginUser.setUserType(user.getUserType());
  134. loginUser.setOpenid(openid);
  135. // 生成token
  136. LoginHelper.loginByDevice(loginUser, DeviceType.XCX);
  137. recordLogininfor(user.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
  138. recordLoginInfo(user.getUserId(), user.getUserName());
  139. return StpUtil.getTokenValue();
  140. }
  141. /**
  142. * 退出登录
  143. */
  144. public void logout() {
  145. try {
  146. LoginUser loginUser = LoginHelper.getLoginUser();
  147. if(loginUser == null){
  148. return;
  149. }
  150. StpUtil.logout();
  151. recordLogininfor(loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
  152. } catch (NotLoginException ignored) {
  153. }
  154. }
  155. /**
  156. * 记录登录信息
  157. *
  158. * @param username 用户名
  159. * @param status 状态
  160. * @param message 消息内容
  161. */
  162. private void recordLogininfor(String username, String status, String message) {
  163. LogininforEvent logininforEvent = new LogininforEvent();
  164. logininforEvent.setUsername(username);
  165. logininforEvent.setStatus(status);
  166. logininforEvent.setMessage(message);
  167. logininforEvent.setRequest(ServletUtils.getRequest());
  168. SpringUtils.context().publishEvent(logininforEvent);
  169. }
  170. /**
  171. * 校验短信验证码
  172. */
  173. private boolean validateSmsCode(String phonenumber, String smsCode) {
  174. String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + phonenumber);
  175. if (StringUtils.isBlank(code)) {
  176. recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
  177. throw new CaptchaExpireException();
  178. }
  179. return code.equals(smsCode);
  180. }
  181. /**
  182. * 校验邮箱验证码
  183. */
  184. private boolean validateEmailCode(String email, String emailCode) {
  185. String code = RedisUtils.getCacheObject(CacheConstants.CAPTCHA_CODE_KEY + email);
  186. if (StringUtils.isBlank(code)) {
  187. recordLogininfor(email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
  188. throw new CaptchaExpireException();
  189. }
  190. return code.equals(emailCode);
  191. }
  192. /**
  193. * 校验验证码
  194. *
  195. * @param username 用户名
  196. * @param code 验证码
  197. * @param uuid 唯一标识
  198. */
  199. public void validateCaptcha(String username, String code, String uuid) {
  200. String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(uuid, "");
  201. String captcha = RedisUtils.getCacheObject(verifyKey);
  202. RedisUtils.deleteObject(verifyKey);
  203. if (captcha == null) {
  204. recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
  205. throw new CaptchaExpireException();
  206. }
  207. if (!code.equalsIgnoreCase(captcha)) {
  208. recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
  209. throw new CaptchaException();
  210. }
  211. }
  212. private SysUser loadUserByUsername(String username,String idCardNo) {
  213. SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
  214. .select(SysUser::getUserName, SysUser::getStatus, SysUser::getNickName)
  215. .eq(SysUser::getUserName, username).eq(SysUser::getIdCardNo, idCardNo));
  216. if (ObjectUtil.isNull(user)) {
  217. log.info("登录用户:{} 不存在.", username);
  218. throw new UserException("user.not.exists", username);
  219. } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
  220. log.info("登录用户:{} 已被停用.", username);
  221. throw new UserException("user.blocked", username);
  222. }
  223. return userMapper.selectUserByUserName(username);
  224. }
  225. private SysUser loadUserByPhonenumber(String phonenumber) {
  226. SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
  227. .select(SysUser::getPhonenumber, SysUser::getStatus)
  228. .eq(SysUser::getPhonenumber, phonenumber));
  229. if (ObjectUtil.isNull(user)) {
  230. log.info("登录用户:{} 不存在.", phonenumber);
  231. throw new UserException("user.not.exists", phonenumber);
  232. } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
  233. log.info("登录用户:{} 已被停用.", phonenumber);
  234. throw new UserException("user.blocked", phonenumber);
  235. }
  236. return userMapper.selectUserByPhonenumber(phonenumber);
  237. }
  238. private SysUser loadUserByEmail(String email) {
  239. SysUser user = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
  240. .select(SysUser::getPhonenumber, SysUser::getStatus)
  241. .eq(SysUser::getEmail, email));
  242. if (ObjectUtil.isNull(user)) {
  243. log.info("登录用户:{} 不存在.", email);
  244. throw new UserException("user.not.exists", email);
  245. } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
  246. log.info("登录用户:{} 已被停用.", email);
  247. throw new UserException("user.blocked", email);
  248. }
  249. return userMapper.selectUserByEmail(email);
  250. }
  251. private SysUser loadUserByOpenid(String openid) {
  252. // 使用 openid 查询绑定用户 如未绑定用户 则根据业务自行处理 例如 创建默认用户
  253. // todo 自行实现 userService.selectUserByOpenid(openid);
  254. SysUser user = new SysUser();
  255. if (ObjectUtil.isNull(user)) {
  256. log.info("登录用户:{} 不存在.", openid);
  257. // todo 用户不存在 业务逻辑自行实现
  258. } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
  259. log.info("登录用户:{} 已被停用.", openid);
  260. // todo 用户已被停用 业务逻辑自行实现
  261. }
  262. return user;
  263. }
  264. /**
  265. * 构建登录用户
  266. */
  267. private LoginUser buildLoginUser(SysUser user) {
  268. LoginUser loginUser = new LoginUser();
  269. loginUser.setUserId(user.getUserId());
  270. loginUser.setDeptId(user.getDeptId());
  271. loginUser.setUsername(user.getUserName());
  272. loginUser.setNickname(user.getNickName());
  273. loginUser.setUserType(user.getUserType());
  274. loginUser.setMenuPermission(permissionService.getMenuPermission(user));
  275. loginUser.setRolePermission(permissionService.getRolePermission(user));
  276. loginUser.setDeptName(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptName());
  277. loginUser.setDeptCode(ObjectUtil.isNull(user.getDept()) ? "" : user.getDept().getDeptCode());
  278. List<RoleDTO> roles = BeanUtil.copyToList(user.getRoles(), RoleDTO.class);
  279. loginUser.setRoles(roles);
  280. return loginUser;
  281. }
  282. /**
  283. * 记录登录信息
  284. *
  285. * @param userId 用户ID
  286. */
  287. public void recordLoginInfo(Long userId, String username) {
  288. SysUser sysUser = new SysUser();
  289. sysUser.setUserId(userId);
  290. sysUser.setLoginIp(ServletUtils.getClientIP());
  291. sysUser.setLoginDate(DateUtils.getNowDate());
  292. sysUser.setUpdateBy(username);
  293. userMapper.updateById(sysUser);
  294. }
  295. /**
  296. * 登录校验
  297. */
  298. private void checkLogin(LoginType loginType, String username, Supplier<Boolean> supplier) {
  299. String errorKey = CacheConstants.PWD_ERR_CNT_KEY + username;
  300. String loginFail = Constants.LOGIN_FAIL;
  301. // 获取用户登录错误次数(可自定义限制策略 例如: key + username + ip)
  302. Integer errorNumber = RedisUtils.getCacheObject(errorKey);
  303. // 锁定时间内登录 则踢出
  304. if (ObjectUtil.isNotNull(errorNumber) && errorNumber.equals(maxRetryCount)) {
  305. recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
  306. throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
  307. }
  308. if (supplier.get()) {
  309. // 是否第一次
  310. errorNumber = ObjectUtil.isNull(errorNumber) ? 1 : errorNumber + 1;
  311. // 达到规定错误次数 则锁定登录
  312. if (errorNumber.equals(maxRetryCount)) {
  313. RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
  314. recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
  315. throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
  316. } else {
  317. // 未达到规定错误次数 则递增
  318. RedisUtils.setCacheObject(errorKey, errorNumber);
  319. recordLogininfor(username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
  320. throw new UserException(loginType.getRetryLimitCount(), errorNumber);
  321. }
  322. }
  323. // 登录成功 清空错误次数
  324. RedisUtils.deleteObject(errorKey);
  325. }
  326. public Map<String, Object> login4Test(String username, String password) {
  327. Map<String, Object> ajax = new HashMap<>();
  328. SysUser tempUser = userMapper.selectOne(new LambdaQueryWrapper<SysUser>()
  329. .select(SysUser::getUserName, SysUser::getStatus, SysUser::getNickName)
  330. .eq(SysUser::getUserName, username));
  331. if (ObjectUtil.isNull(tempUser)) {
  332. log.info("登录用户:{} 不存在.", username);
  333. throw new UserException("user.not.exists", username);
  334. } else if (UserStatus.DISABLE.getCode().equals(tempUser.getStatus())) {
  335. log.info("登录用户:{} 已被停用.", username);
  336. throw new UserException("user.blocked", username);
  337. }
  338. SysUser user = userMapper.selectUserByUserName(username);
  339. checkLogin(LoginType.PASSWORD, username, () -> !BCrypt.checkpw(password, user.getPassword()));
  340. // 此处可根据登录用户的数据不同 自行创建 loginUser
  341. LoginUser loginUser = buildLoginUser(user);
  342. // 生成token
  343. LoginHelper.loginByDevice(loginUser, DeviceType.PC);
  344. recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"));
  345. recordLoginInfo(user.getUserId(), username);
  346. ajax.put(Constants.TOKEN, StpUtil.getTokenValue());
  347. ajax.put("origPasswdStatus", user.getOrigPasswdStatus());
  348. ajax.put("type", Constants.USER_TYPE_OTHER);
  349. List<SysRole> roles = user.getRoles();
  350. if(CollectionUtil.isNotEmpty(roles)){
  351. Set<Long> roleIds = roles.stream().filter(item -> item.getRoleId() != null).map(SysRole::getRoleId).collect(Collectors.toSet());
  352. if (roleIds.contains(Constants.LEADER_ROLE_ID)) {
  353. ajax.put("type", Constants.USER_TYPE_LEADER);
  354. } else if (roleIds.contains(Constants.INVESTIGATOR_ROLE_ID)) {
  355. ajax.put("type", Constants.USER_TYPE_INVESTIGATOR);
  356. } else {
  357. ajax.put("type", Constants.USER_TYPE_OTHER);
  358. }
  359. }
  360. return ajax;
  361. }
  362. }