package com.tzy.service.impl; import com.tzy.common.exception.ServiceException; import com.tzy.common.utils.UserType; import com.tzy.dto.AccountProfileInfoDTO; import com.tzy.dto.EndUserDTO; import com.tzy.entity.AppAccount; import com.tzy.entity.AppBaseUser; import com.tzy.mapper.PoyeeAppAccountMapper; import com.tzy.mapper.PoyeeAppBaseUserMapper; import com.tzy.req.*; import com.tzy.service.AppAccountOidcService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import javax.annotation.Resource; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.Collections; import java.util.Date; import java.util.List; @Service public class AppAccountOidcServiceImpl implements AppAccountOidcService { private static final String ACTIVE = "ACTIVE"; private static final String INACTIVE = "INACTIVE"; private static final String BANNED = "BANNED"; private static final String DEFAULT_AVATAR = "https://static.public.hobbystock.cn/applet/share/share_logo2.png"; private static final int DEFAULT_ROLE_ID = 0; private static final SecureRandom SECURE_RANDOM = new SecureRandom(); @Resource private PoyeeAppAccountMapper poyeeAppAccountMapper; @Resource private PoyeeAppBaseUserMapper poyeeAppBaseUserMapper; public AppAccountOidcServiceImpl() { } public AppAccountOidcServiceImpl(PoyeeAppAccountMapper appAccountMapper, PoyeeAppBaseUserMapper appBaseUserMapper) { this.poyeeAppAccountMapper = appAccountMapper; this.poyeeAppBaseUserMapper = appBaseUserMapper; } @Override public EndUserDTO loadUserByLoginId(String loginId) { if (!StringUtils.hasText(loginId)) { return null; } AppAccount account = poyeeAppAccountMapper.selectByLoginId(loginId); AppBaseUser baseUser; if (account == null) { baseUser = poyeeAppBaseUserMapper.selectByExternalLoginId(loginId); if (baseUser == null) { return null; } account = poyeeAppAccountMapper.selectByLoginId(baseUser.getUsername()); } else { baseUser = poyeeAppBaseUserMapper.selectByUsername(account.getAccount()); } return toEndUser(loginId, account, baseUser); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createEmailUser(EmailCreateRequest request) { String email = request == null ? null : request.getEmail(); String displayName = request != null && request.getDisplayName() != null && request.getDisplayName().isPresent() ? request.getDisplayName().get() : null; return createUser(email, null, email, null, null, displayName, null, "EMAIL", null); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createEmailPasswordUser(EmailPasswordCreateRequest request) { if (request == null) { throw new ServiceException("参数不能为空"); } return createUser(request.getEmail(), null, request.getEmail(), request.getPassword(), null, request.getDisplayName(), null, "EMAIL_PASSWORD", null); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createPhoneUser(PhoneCreateRequest request) { if (request == null || !StringUtils.hasText(request.getPhone())) { throw new ServiceException("参数不能为空"); } String phone = request.getPhone().trim(); return createUser(phone, phone, null, null, null, null, null, resolvePhoneRegisterChannel(request), null); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createWeChatUser(WeChatCreateRequest request) { if (request == null) { throw new ServiceException("参数不能为空"); } return createUser(request.getOpenId(), null, null, null, request.getOpenId(), request.getNickName(), request.getAvatarUrl(), "WECHAT_AUTH", null); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createWeChatPhoneUser(WeChatPhoneCreateRequest request) { if (request == null) { throw new ServiceException("参数不能为空"); } return createUser(request.getPhone(), request.getPhone(), null, null, request.getOpenId(), request.getNickName(), request.getAvatarUrl(), "WECHAT_PHONE", null); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO createAppleIdUser(AppleIdCreateRequest request) { if (request == null) { throw new ServiceException("参数不能为空"); } return createUser(request.getAppleId(), null, request.getEmail(), null, null, request.getEmail(), request.getAvatar(), "APPLE_AUTH", request.getAppleId()); } @Override public void resetPassword(AccountPasswordRequest request) { if (request == null || !StringUtils.hasText(request.getLoginId()) || !StringUtils.hasText(request.getNewPassword())) { throw new ServiceException("参数不能为空"); } AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId()); if (account == null) { throw new ServiceException("账号不存在"); } String salt = randomSalt(); String hash = hashPassword(request.getNewPassword(), salt); poyeeAppAccountMapper.updatePassword(account.getAccount(), hash, salt); } @Override public void changePassword(AccountPasswordRequest request) { if (request == null || !StringUtils.hasText(request.getOldPassword())) { throw new ServiceException("旧密码不能为空"); } AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId()); if (account == null) { throw new ServiceException("账号不存在"); } String expected = hashPassword(request.getOldPassword(), resolvePasswordSalt(account)); if (!expected.equalsIgnoreCase(account.getPassword())) { throw new ServiceException("旧密码错误"); } resetPassword(request); } @Override @Transactional(rollbackFor = Exception.class) public EndUserDTO bindPhone(BindPhoneRequest request) { if (request == null || !StringUtils.hasText(request.getLoginId()) || !StringUtils.hasText(request.getPhone())) { throw new ServiceException("参数不能为空"); } String loginId = request.getLoginId().trim(); String phone = request.getPhone().trim(); AppAccount account = poyeeAppAccountMapper.selectByLoginId(loginId); if (account == null) { throw new ServiceException("账号不存在"); } AppAccount phoneAccount = poyeeAppAccountMapper.selectByLoginId(phone); if (phoneAccount != null && !isSameAccount(account, phoneAccount)) { throw new ServiceException("手机号已绑定"); } poyeeAppAccountMapper.updatePhone(account.getAccount(), phone); account.setPhone(phone); AppBaseUser baseUser = poyeeAppBaseUserMapper.selectByUsername(account.getAccount()); return toEndUser(account.getAccount(), account, baseUser); } @Override public AccountProfileInfoDTO getCurrentProfile(Integer userId) { if (userId == null) { throw new ServiceException("userId不能为空"); } AppBaseUser baseUser = poyeeAppBaseUserMapper.selectById(userId); if (baseUser == null) { throw new ServiceException("用户不存在"); } AppAccount account = StringUtils.hasText(baseUser.getUsername()) ? poyeeAppAccountMapper.selectByLoginId(baseUser.getUsername()) : null; AccountProfileInfoDTO profile = new AccountProfileInfoDTO(); profile.setAvatar(baseUser.getAvatar()); profile.setUserId(userId.longValue()); profile.setLoginId(baseUser.getUsername()); profile.setNickname(baseUser.getNickname()); profile.setFaceVerify(baseUser.getFaceVerify()); profile.setIdCard(baseUser.getIdCard()); profile.setPhone(account == null ? null : account.getPhone()); return profile; } @Override @Transactional(rollbackFor = Exception.class) public AccountProfileInfoDTO mockRealNameVerify(Integer userId) { if (userId == null) { throw new ServiceException("userId cannot be null"); } AppBaseUser baseUser = poyeeAppBaseUserMapper.selectById(userId); if (baseUser == null) { throw new ServiceException("user not found"); } String idCard = randomIdCard(); poyeeAppBaseUserMapper.updateRealNameVerify(userId, 1, idCard); AccountProfileInfoDTO profile = new AccountProfileInfoDTO(); profile.setAvatar(baseUser.getAvatar()); profile.setUserId(userId.longValue()); profile.setNickname(baseUser.getNickname()); profile.setFaceVerify(1); profile.setIdCard(idCard); return profile; } @Override @Transactional(rollbackFor = Exception.class) public AccountProfileInfoDTO updateProfile(ProfileUpdateRequest request) { if (request == null || (request.getUserId() == null && !StringUtils.hasText(request.getLoginId()))) { throw new ServiceException("参数不能为空"); } AppBaseUser baseUser = new AppBaseUser(); baseUser.setId(request.getUserId()); baseUser.setNickname(request.getNickname()); baseUser.setAvatar(request.getAvatar()); baseUser.setRealname(request.getRealname()); baseUser.setBirthday(request.getBirthday()); baseUser.setSex(request.getSex()); if (baseUser.getId() == null && StringUtils.hasText(request.getLoginId())) { AppBaseUser existing = null; AppAccount account = poyeeAppAccountMapper.selectByLoginId(request.getLoginId()); if (account != null) { existing = poyeeAppBaseUserMapper.selectByUsername(account.getAccount()); } if (existing == null) { existing = poyeeAppBaseUserMapper.selectByExternalLoginId(request.getLoginId()); } if (existing == null) { throw new ServiceException("用户不存在"); } baseUser.setId(existing.getId()); } poyeeAppBaseUserMapper.updateAppBaseUser(baseUser); return getCurrentProfile(baseUser.getId()); } private boolean isSameAccount(AppAccount left, AppAccount right) { if (left.getId() != null && right.getId() != null) { return left.getId().equals(right.getId()); } return left.getAccount() != null && left.getAccount().equals(right.getAccount()); } private String resolvePhoneRegisterChannel(PhoneCreateRequest request) { String registerChannel = request.getRegisterChannel(); if (!StringUtils.hasText(registerChannel)) { return UserType.THIRD_APK; } registerChannel = registerChannel.trim(); if (UserType.THIRD_APK.equals(registerChannel) || UserType.THIRD_APP.equals(registerChannel)) { return registerChannel; } throw new ServiceException("手机号注册渠道不支持"); } private EndUserDTO createUser(String username, String phone, String email, String password, String openId, String displayName, String avatar, String registerChannel, String transferSubId) { if (!StringUtils.hasText(username)) { throw new ServiceException("账号不能为空"); } EndUserDTO existing = loadUserByLoginId(username); if (existing != null) { return existing; } AppAccount account = new AppAccount(); account.setAccount(username); account.setPhone(phone); account.setEmail(email); account.setStatus((short) 0); account.setDelFlg((short) 0); account.setRoleid(DEFAULT_ROLE_ID); account.setCreateTime(new Date()); if (StringUtils.hasText(password)) { String salt = randomSalt(); account.setSalt(salt); account.setPassword(hashPassword(password, salt)); } poyeeAppAccountMapper.insertAppAccount(account); AppBaseUser baseUser = new AppBaseUser(); baseUser.setUsername(username); baseUser.setNickname(StringUtils.hasText(displayName) ? displayName : username); baseUser.setAvatar(StringUtils.hasText(avatar) ? avatar : DEFAULT_AVATAR); baseUser.setOpenid(openId); baseUser.setRegisterChannel(registerChannel); baseUser.setStatus((short) 0); baseUser.setDelFlg((short) 0); baseUser.setPoint(0L); baseUser.setLevel((short) 0); baseUser.setCreateTime(new Date()); baseUser.setTransferSubId(transferSubId); poyeeAppBaseUserMapper.insertAppBaseUser(baseUser); return toEndUser(username, account, baseUser); } private EndUserDTO toEndUser(String loginId, AppAccount account, AppBaseUser baseUser) { if (account == null && baseUser == null) { return null; } EndUserDTO dto = new EndUserDTO(); String username = account != null ? account.getAccount() : baseUser.getUsername(); Integer userId = baseUser != null ? baseUser.getId() : account.getId(); dto.setId(userId == null ? 0 : userId); dto.setUsername(username != null ? username : loginId); dto.setEmail(account == null ? null : account.getEmail()); dto.setPhone(account == null ? null : account.getPhone()); dto.setDisplayName(baseUser == null ? username : baseUser.getNickname()); dto.setAvatarUrl(baseUser == null ? null : baseUser.getAvatar()); dto.setPasswordHash(account == null ? null : account.getPassword()); dto.setSalt(account == null ? null : account.getSalt()); dto.setCode(baseUser == null ? null : baseUser.getCode()); dto.setStatus(resolveStatus(account, baseUser)); dto.setIdentityType(baseUser == null ? null : baseUser.getRegisterChannel()); dto.setRegistrationTime(resolveRegistrationTime(account, baseUser)); dto.setRoleCode(resolveRoleCodes(account)); EndUserDTO.EndUserProfile profile = new EndUserDTO.EndUserProfile(); profile.setRealName(baseUser == null ? null : baseUser.getRealname()); profile.setGender(baseUser == null || baseUser.getSex() == null ? null : String.valueOf(baseUser.getSex())); profile.setBirthday(baseUser == null ? null : toLocalDate(baseUser.getBirthday())); profile.setIdCardNumber(baseUser == null ? null : baseUser.getIdCard()); profile.setAddress(baseUser == null ? null : baseUser.getRegisterAddr()); dto.setProfile(profile); return dto; } private LocalDateTime resolveRegistrationTime(AppAccount account, AppBaseUser baseUser) { Date createTime = baseUser != null && baseUser.getCreateTime() != null ? baseUser.getCreateTime() : account == null ? null : account.getCreateTime(); return toLocalDateTime(createTime); } private LocalDateTime toLocalDateTime(Date date) { return date == null ? null : LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); } private LocalDate toLocalDate(Date date) { return date == null ? null : date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(); } private List resolveRoleCodes(AppAccount account) { if (account == null || account.getRoleid() == null) { return Collections.singletonList("PERSONAL"); } List roles = poyeeAppAccountMapper.selectRoleCodesByRoleId(account.getRoleid()); return roles == null || roles.isEmpty() ? Collections.singletonList("PERSONAL") : roles; } private String resolveStatus(AppAccount account, AppBaseUser baseUser) { if (isDeleted(account, baseUser)) { return INACTIVE; } Short accountStatus = account == null ? null : account.getStatus(); Short userStatus = baseUser == null ? null : baseUser.getStatus(); if (Short.valueOf((short) 2).equals(accountStatus) || Short.valueOf((short) 2).equals(userStatus)) { return BANNED; } return ACTIVE; } private boolean isDeleted(AppAccount account, AppBaseUser baseUser) { return (account != null && Short.valueOf((short) 1).equals(account.getDelFlg())) || (baseUser != null && Short.valueOf((short) 1).equals(baseUser.getDelFlg())); } private String stableSalt(String username) { return md5("poyee-account:" + (username == null ? "" : username)); } private String resolvePasswordSalt(AppAccount account) { return StringUtils.hasText(account.getSalt()) ? account.getSalt() : stableSalt(account.getAccount()); } private String randomSalt() { byte[] bytes = new byte[16]; SECURE_RANDOM.nextBytes(bytes); StringBuilder builder = new StringBuilder(); for (byte b : bytes) { builder.append(String.format("%02x", b)); } return builder.toString(); } private String hashPassword(String password, String salt) { return md5(password + salt); } private String randomIdCard() { StringBuilder builder = new StringBuilder(18); for (int i = 0; i < 18; i++) { builder.append(SECURE_RANDOM.nextInt(10)); } return builder.toString(); } private String md5(String value) { try { MessageDigest digest = MessageDigest.getInstance("MD5"); byte[] bytes = digest.digest(value.getBytes()); StringBuilder builder = new StringBuilder(); for (byte b : bytes) { builder.append(String.format("%02x", b)); } return builder.toString(); } catch (NoSuchAlgorithmException e) { throw new ServiceException("密码加密失败"); } } }