AppAccountOidcServiceImpl.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. package com.tzy.service.impl;
  2. import com.tzy.common.exception.ServiceException;
  3. import com.tzy.common.utils.UserType;
  4. import com.tzy.dto.AccountProfileInfoDTO;
  5. import com.tzy.dto.EndUserDTO;
  6. import com.tzy.entity.AppAccount;
  7. import com.tzy.entity.AppBaseUser;
  8. import com.tzy.mapper.PoyeeAppAccountMapper;
  9. import com.tzy.mapper.PoyeeAppBaseUserMapper;
  10. import com.tzy.req.*;
  11. import com.tzy.service.AppAccountOidcService;
  12. import org.springframework.stereotype.Service;
  13. import org.springframework.transaction.annotation.Transactional;
  14. import org.springframework.util.StringUtils;
  15. import javax.annotation.Resource;
  16. import java.security.MessageDigest;
  17. import java.security.NoSuchAlgorithmException;
  18. import java.security.SecureRandom;
  19. import java.time.LocalDate;
  20. import java.time.LocalDateTime;
  21. import java.time.ZoneId;
  22. import java.util.Collections;
  23. import java.util.Date;
  24. import java.util.List;
  25. @Service
  26. public class AppAccountOidcServiceImpl implements AppAccountOidcService {
  27. private static final String ACTIVE = "ACTIVE";
  28. private static final String INACTIVE = "INACTIVE";
  29. private static final String BANNED = "BANNED";
  30. private static final String DEFAULT_AVATAR = "https://static.public.hobbystock.cn/applet/share/share_logo2.png";
  31. private static final int DEFAULT_ROLE_ID = 0;
  32. private static final SecureRandom SECURE_RANDOM = new SecureRandom();
  33. @Resource
  34. private PoyeeAppAccountMapper poyeeAppAccountMapper;
  35. @Resource
  36. private PoyeeAppBaseUserMapper poyeeAppBaseUserMapper;
  37. public AppAccountOidcServiceImpl() {
  38. }
  39. public AppAccountOidcServiceImpl(PoyeeAppAccountMapper appAccountMapper, PoyeeAppBaseUserMapper appBaseUserMapper) {
  40. this.poyeeAppAccountMapper = appAccountMapper;
  41. this.poyeeAppBaseUserMapper = appBaseUserMapper;
  42. }
  43. @Override
  44. public EndUserDTO loadUserByLoginId(String loginId) {
  45. if (!StringUtils.hasText(loginId)) {
  46. return null;
  47. }
  48. AppAccount account = poyeeAppAccountMapper.selectByLoginId(loginId);
  49. AppBaseUser baseUser;
  50. if (account == null) {
  51. baseUser = poyeeAppBaseUserMapper.selectByExternalLoginId(loginId);
  52. if (baseUser == null) {
  53. return null;
  54. }
  55. account = poyeeAppAccountMapper.selectByLoginId(baseUser.getUsername());
  56. } else {
  57. baseUser = poyeeAppBaseUserMapper.selectByUsername(account.getAccount());
  58. }
  59. return toEndUser(loginId, account, baseUser);
  60. }
  61. @Override
  62. @Transactional(rollbackFor = Exception.class)
  63. public EndUserDTO createEmailUser(EmailCreateRequest request) {
  64. String email = request == null ? null : request.getEmail();
  65. String displayName = request != null && request.getDisplayName() != null && request.getDisplayName().isPresent()
  66. ? request.getDisplayName().get() : null;
  67. return createUser(email, null, email, null, null, displayName, null, "EMAIL", null);
  68. }
  69. @Override
  70. @Transactional(rollbackFor = Exception.class)
  71. public EndUserDTO createEmailPasswordUser(EmailPasswordCreateRequest request) {
  72. if (request == null) {
  73. throw new ServiceException("参数不能为空");
  74. }
  75. return createUser(request.getEmail(), null, request.getEmail(), request.getPassword(), null,
  76. request.getDisplayName(), null, "EMAIL_PASSWORD", null);
  77. }
  78. @Override
  79. @Transactional(rollbackFor = Exception.class)
  80. public EndUserDTO createPhoneUser(PhoneCreateRequest request) {
  81. if (request == null || !StringUtils.hasText(request.getPhone())) {
  82. throw new ServiceException("参数不能为空");
  83. }
  84. String phone = request.getPhone().trim();
  85. return createUser(phone, phone, null, null, null, null, null, resolvePhoneRegisterChannel(request), null);
  86. }
  87. @Override
  88. @Transactional(rollbackFor = Exception.class)
  89. public EndUserDTO createWeChatUser(WeChatCreateRequest request) {
  90. if (request == null) {
  91. throw new ServiceException("参数不能为空");
  92. }
  93. return createUser(request.getOpenId(), null, null, null, request.getOpenId(),
  94. request.getNickName(), request.getAvatarUrl(), "WECHAT_AUTH", null);
  95. }
  96. @Override
  97. @Transactional(rollbackFor = Exception.class)
  98. public EndUserDTO createWeChatPhoneUser(WeChatPhoneCreateRequest request) {
  99. if (request == null) {
  100. throw new ServiceException("参数不能为空");
  101. }
  102. return createUser(request.getPhone(), request.getPhone(), null, null, request.getOpenId(),
  103. request.getNickName(), request.getAvatarUrl(), "WECHAT_PHONE", null);
  104. }
  105. @Override
  106. @Transactional(rollbackFor = Exception.class)
  107. public EndUserDTO createAppleIdUser(AppleIdCreateRequest request) {
  108. if (request == null) {
  109. throw new ServiceException("参数不能为空");
  110. }
  111. return createUser(request.getAppleId(), null, request.getEmail(), null, null,
  112. request.getEmail(), request.getAvatar(), "APPLE_AUTH", request.getAppleId());
  113. }
  114. @Override
  115. public void resetPassword(AccountPasswordRequest request) {
  116. if (request == null || !StringUtils.hasText(request.getLoginId()) || !StringUtils.hasText(request.getNewPassword())) {
  117. throw new ServiceException("参数不能为空");
  118. }
  119. AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId());
  120. if (account == null) {
  121. throw new ServiceException("账号不存在");
  122. }
  123. String salt = randomSalt();
  124. String hash = hashPassword(request.getNewPassword(), salt);
  125. poyeeAppAccountMapper.updatePassword(account.getAccount(), hash, salt);
  126. }
  127. @Override
  128. public void changePassword(AccountPasswordRequest request) {
  129. if (request == null || !StringUtils.hasText(request.getOldPassword())) {
  130. throw new ServiceException("旧密码不能为空");
  131. }
  132. AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId());
  133. if (account == null) {
  134. throw new ServiceException("账号不存在");
  135. }
  136. String expected = hashPassword(request.getOldPassword(), resolvePasswordSalt(account));
  137. if (!expected.equalsIgnoreCase(account.getPassword())) {
  138. throw new ServiceException("旧密码错误");
  139. }
  140. resetPassword(request);
  141. }
  142. @Override
  143. @Transactional(rollbackFor = Exception.class)
  144. public EndUserDTO bindPhone(BindPhoneRequest request) {
  145. if (request == null || !StringUtils.hasText(request.getLoginId()) || !StringUtils.hasText(request.getPhone())) {
  146. throw new ServiceException("参数不能为空");
  147. }
  148. String loginId = request.getLoginId().trim();
  149. String phone = request.getPhone().trim();
  150. AppAccount account = poyeeAppAccountMapper.selectByLoginId(loginId);
  151. if (account == null) {
  152. throw new ServiceException("账号不存在");
  153. }
  154. AppAccount phoneAccount = poyeeAppAccountMapper.selectByLoginId(phone);
  155. if (phoneAccount != null && !isSameAccount(account, phoneAccount)) {
  156. throw new ServiceException("手机号已绑定");
  157. }
  158. poyeeAppAccountMapper.updatePhone(account.getAccount(), phone);
  159. account.setPhone(phone);
  160. AppBaseUser baseUser = poyeeAppBaseUserMapper.selectByUsername(account.getAccount());
  161. return toEndUser(account.getAccount(), account, baseUser);
  162. }
  163. @Override
  164. public AccountProfileInfoDTO getCurrentProfile(Integer userId) {
  165. if (userId == null) {
  166. throw new ServiceException("userId不能为空");
  167. }
  168. AppBaseUser baseUser = poyeeAppBaseUserMapper.selectById(userId);
  169. if (baseUser == null) {
  170. throw new ServiceException("用户不存在");
  171. }
  172. AppAccount account = StringUtils.hasText(baseUser.getUsername())
  173. ? poyeeAppAccountMapper.selectByLoginId(baseUser.getUsername())
  174. : null;
  175. AccountProfileInfoDTO profile = new AccountProfileInfoDTO();
  176. profile.setAvatar(baseUser.getAvatar());
  177. profile.setUserId(userId.longValue());
  178. profile.setLoginId(baseUser.getUsername());
  179. profile.setNickname(baseUser.getNickname());
  180. profile.setFaceVerify(baseUser.getFaceVerify());
  181. profile.setIdCard(baseUser.getIdCard());
  182. profile.setPhone(account == null ? null : account.getPhone());
  183. return profile;
  184. }
  185. @Override
  186. @Transactional(rollbackFor = Exception.class)
  187. public AccountProfileInfoDTO mockRealNameVerify(Integer userId) {
  188. if (userId == null) {
  189. throw new ServiceException("userId cannot be null");
  190. }
  191. AppBaseUser baseUser = poyeeAppBaseUserMapper.selectById(userId);
  192. if (baseUser == null) {
  193. throw new ServiceException("user not found");
  194. }
  195. String idCard = randomIdCard();
  196. poyeeAppBaseUserMapper.updateRealNameVerify(userId, 1, idCard);
  197. AccountProfileInfoDTO profile = new AccountProfileInfoDTO();
  198. profile.setAvatar(baseUser.getAvatar());
  199. profile.setUserId(userId.longValue());
  200. profile.setNickname(baseUser.getNickname());
  201. profile.setFaceVerify(1);
  202. profile.setIdCard(idCard);
  203. return profile;
  204. }
  205. @Override
  206. @Transactional(rollbackFor = Exception.class)
  207. public AccountProfileInfoDTO updateProfile(ProfileUpdateRequest request) {
  208. if (request == null || (request.getUserId() == null && !StringUtils.hasText(request.getLoginId()))) {
  209. throw new ServiceException("参数不能为空");
  210. }
  211. AppBaseUser baseUser = new AppBaseUser();
  212. baseUser.setId(request.getUserId());
  213. baseUser.setNickname(request.getNickname());
  214. baseUser.setAvatar(request.getAvatar());
  215. baseUser.setRealname(request.getRealname());
  216. baseUser.setBirthday(request.getBirthday());
  217. baseUser.setSex(request.getSex());
  218. if (baseUser.getId() == null && StringUtils.hasText(request.getLoginId())) {
  219. AppBaseUser existing = null;
  220. AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId());
  221. if (account != null) {
  222. existing = poyeeAppBaseUserMapper.selectByUsername(account.getAccount());
  223. }
  224. if (existing == null) {
  225. existing = poyeeAppBaseUserMapper.selectByExternalLoginId(request.getLoginId());
  226. }
  227. if (existing == null) {
  228. throw new ServiceException("用户不存在");
  229. }
  230. baseUser.setId(existing.getId());
  231. }
  232. poyeeAppBaseUserMapper.updateAppBaseUser(baseUser);
  233. return getCurrentProfile(baseUser.getId());
  234. }
  235. private boolean isSameAccount(AppAccount left, AppAccount right) {
  236. if (left.getId() != null && right.getId() != null) {
  237. return left.getId().equals(right.getId());
  238. }
  239. return left.getAccount() != null && left.getAccount().equals(right.getAccount());
  240. }
  241. private String resolvePhoneRegisterChannel(PhoneCreateRequest request) {
  242. String registerChannel = request.getRegisterChannel();
  243. if (!StringUtils.hasText(registerChannel)) {
  244. return UserType.THIRD_APK;
  245. }
  246. registerChannel = registerChannel.trim();
  247. if (UserType.THIRD_APK.equals(registerChannel) || UserType.THIRD_APP.equals(registerChannel)) {
  248. return registerChannel;
  249. }
  250. throw new ServiceException("手机号注册渠道不支持");
  251. }
  252. private EndUserDTO createUser(String username, String phone, String email, String password, String openId,
  253. String displayName, String avatar, String registerChannel, String transferSubId) {
  254. if (!StringUtils.hasText(username)) {
  255. throw new ServiceException("账号不能为空");
  256. }
  257. EndUserDTO existing = loadUserByLoginId(username);
  258. if (existing != null) {
  259. return existing;
  260. }
  261. AppAccount account = new AppAccount();
  262. account.setAccount(username);
  263. account.setPhone(phone);
  264. account.setEmail(email);
  265. account.setStatus((short) 0);
  266. account.setDelFlg((short) 0);
  267. account.setRoleid(DEFAULT_ROLE_ID);
  268. account.setCreateTime(new Date());
  269. if (StringUtils.hasText(password)) {
  270. String salt = randomSalt();
  271. account.setSalt(salt);
  272. account.setPassword(hashPassword(password, salt));
  273. }
  274. poyeeAppAccountMapper.insertAppAccount(account);
  275. AppBaseUser baseUser = new AppBaseUser();
  276. baseUser.setUsername(username);
  277. baseUser.setNickname(StringUtils.hasText(displayName) ? displayName : username);
  278. baseUser.setAvatar(StringUtils.hasText(avatar) ? avatar : DEFAULT_AVATAR);
  279. baseUser.setOpenid(openId);
  280. baseUser.setRegisterChannel(registerChannel);
  281. baseUser.setStatus((short) 0);
  282. baseUser.setDelFlg((short) 0);
  283. baseUser.setPoint(0L);
  284. baseUser.setLevel((short) 0);
  285. baseUser.setCreateTime(new Date());
  286. baseUser.setTransferSubId(transferSubId);
  287. poyeeAppBaseUserMapper.insertAppBaseUser(baseUser);
  288. return toEndUser(username, account, baseUser);
  289. }
  290. private EndUserDTO toEndUser(String loginId, AppAccount account, AppBaseUser baseUser) {
  291. if (account == null && baseUser == null) {
  292. return null;
  293. }
  294. EndUserDTO dto = new EndUserDTO();
  295. String username = account != null ? account.getAccount() : baseUser.getUsername();
  296. Integer userId = baseUser != null ? baseUser.getId() : account.getId();
  297. dto.setId(userId == null ? 0 : userId);
  298. dto.setUsername(username != null ? username : loginId);
  299. dto.setEmail(account == null ? null : account.getEmail());
  300. dto.setPhone(account == null ? null : account.getPhone());
  301. dto.setDisplayName(baseUser == null ? username : baseUser.getNickname());
  302. dto.setAvatarUrl(baseUser == null ? null : baseUser.getAvatar());
  303. dto.setPasswordHash(account == null ? null : account.getPassword());
  304. dto.setSalt(account == null ? null : account.getSalt());
  305. dto.setCode(baseUser == null ? null : baseUser.getCode());
  306. dto.setStatus(resolveStatus(account, baseUser));
  307. dto.setIdentityType(baseUser == null ? null : baseUser.getRegisterChannel());
  308. dto.setRegistrationTime(resolveRegistrationTime(account, baseUser));
  309. dto.setRoleCode(resolveRoleCodes(account));
  310. EndUserDTO.EndUserProfile profile = new EndUserDTO.EndUserProfile();
  311. profile.setRealName(baseUser == null ? null : baseUser.getRealname());
  312. profile.setGender(baseUser == null || baseUser.getSex() == null ? null : String.valueOf(baseUser.getSex()));
  313. profile.setBirthday(baseUser == null ? null : toLocalDate(baseUser.getBirthday()));
  314. profile.setIdCardNumber(baseUser == null ? null : baseUser.getIdCard());
  315. profile.setAddress(baseUser == null ? null : baseUser.getRegisterAddr());
  316. dto.setProfile(profile);
  317. return dto;
  318. }
  319. private LocalDateTime resolveRegistrationTime(AppAccount account, AppBaseUser baseUser) {
  320. Date createTime = baseUser != null && baseUser.getCreateTime() != null
  321. ? baseUser.getCreateTime()
  322. : account == null ? null : account.getCreateTime();
  323. return toLocalDateTime(createTime);
  324. }
  325. private LocalDateTime toLocalDateTime(Date date) {
  326. return date == null ? null : LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
  327. }
  328. private LocalDate toLocalDate(Date date) {
  329. return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
  330. }
  331. private List<String> resolveRoleCodes(AppAccount account) {
  332. if (account == null || account.getRoleid() == null) {
  333. return Collections.singletonList("PERSONAL");
  334. }
  335. List<String> roles = poyeeAppAccountMapper.selectRoleCodesByRoleId(account.getRoleid());
  336. return roles == null || roles.isEmpty() ? Collections.singletonList("PERSONAL") : roles;
  337. }
  338. private String resolveStatus(AppAccount account, AppBaseUser baseUser) {
  339. if (isDeleted(account, baseUser)) {
  340. return INACTIVE;
  341. }
  342. Short accountStatus = account == null ? null : account.getStatus();
  343. Short userStatus = baseUser == null ? null : baseUser.getStatus();
  344. if (Short.valueOf((short) 2).equals(accountStatus) || Short.valueOf((short) 2).equals(userStatus)) {
  345. return BANNED;
  346. }
  347. return ACTIVE;
  348. }
  349. private boolean isDeleted(AppAccount account, AppBaseUser baseUser) {
  350. return (account != null && Short.valueOf((short) 1).equals(account.getDelFlg()))
  351. || (baseUser != null && Short.valueOf((short) 1).equals(baseUser.getDelFlg()));
  352. }
  353. private String stableSalt(String username) {
  354. return md5("poyee-account:" + (username == null ? "" : username));
  355. }
  356. private String resolvePasswordSalt(AppAccount account) {
  357. return StringUtils.hasText(account.getSalt()) ? account.getSalt() : stableSalt(account.getAccount());
  358. }
  359. private String randomSalt() {
  360. byte[] bytes = new byte[16];
  361. SECURE_RANDOM.nextBytes(bytes);
  362. StringBuilder builder = new StringBuilder();
  363. for (byte b : bytes) {
  364. builder.append(String.format("%02x", b));
  365. }
  366. return builder.toString();
  367. }
  368. private String hashPassword(String password, String salt) {
  369. return md5(password + salt);
  370. }
  371. private String randomIdCard() {
  372. StringBuilder builder = new StringBuilder(18);
  373. for (int i = 0; i < 18; i++) {
  374. builder.append(SECURE_RANDOM.nextInt(10));
  375. }
  376. return builder.toString();
  377. }
  378. private String md5(String value) {
  379. try {
  380. MessageDigest digest = MessageDigest.getInstance("MD5");
  381. byte[] bytes = digest.digest(value.getBytes());
  382. StringBuilder builder = new StringBuilder();
  383. for (byte b : bytes) {
  384. builder.append(String.format("%02x", b));
  385. }
  386. return builder.toString();
  387. } catch (NoSuchAlgorithmException e) {
  388. throw new ServiceException("密码加密失败");
  389. }
  390. }
  391. }