package com.izouma.nineth.service; import cn.binarywang.wx.miniapp.api.WxMaService; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.binarywang.wx.miniapp.bean.WxMaUserInfo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.model.AdapayCommon; import com.huifu.adapay.model.SettleAccount; import com.izouma.nineth.config.AdapayProperties; import com.izouma.nineth.config.Constants; import com.izouma.nineth.domain.Follow; import com.izouma.nineth.domain.IdentityAuth; import com.izouma.nineth.domain.Invite; import com.izouma.nineth.domain.User; import com.izouma.nineth.dto.*; import com.izouma.nineth.enums.AuthStatus; import com.izouma.nineth.enums.AuthorityName; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.*; import com.izouma.nineth.security.Authority; import com.izouma.nineth.security.JwtTokenUtil; import com.izouma.nineth.security.JwtUserFactory; import com.izouma.nineth.service.sms.SmsService; import com.izouma.nineth.service.storage.StorageService; import com.izouma.nineth.utils.BankUtils; import com.izouma.nineth.utils.JpaUtils; import com.izouma.nineth.utils.ObjUtils; import com.izouma.nineth.utils.SecurityUtils; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import me.chanjar.weixin.mp.bean.result.WxMpUser; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.BeanUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import javax.persistence.criteria.Predicate; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service @Slf4j @AllArgsConstructor public class UserService { private UserRepo userRepo; private WxMaService wxMaService; private WxMpService wxMpService; private SmsService smsService; private StorageService storageService; private JwtTokenUtil jwtTokenUtil; private CaptchaService captchaService; private FollowService followService; private FollowRepo followRepo; private IdentityAuthRepo identityAuthRepo; private SysConfigService sysConfigService; private CollectionService collectionService; private AdapayService adapayService; private UserBankCardRepo userBankCardRepo; private CacheService cacheService; private InviteRepo inviteRepo; private AdapayProperties adapayProperties; @CacheEvict(value = "user", key = "#user.username") public User update(User user) { User orig = userRepo.findById(user.getId()).orElseThrow(new BusinessException("无记录")); ObjUtils.merge(orig, user); orig = userRepo.save(orig); userRepo.updateAssetMinter(orig.getId()); userRepo.updateAssetOwner(orig.getId()); userRepo.updateCollectionMinter(orig.getId()); userRepo.updateCollectionOwner(orig.getId()); userRepo.updateOrderMinter(orig.getId()); userRepo.updateHistoryFromUser(orig.getId()); userRepo.updateHistoryToUser(orig.getId()); cacheService.clearCollection(); return orig; } @CacheEvict(value = "user", allEntries = true) public void clearCache() { } public Page all(PageQuery pageQuery) { Specification specification = JpaUtils.toSpecification(pageQuery, User.class); specification = specification.and((Specification) (root, criteriaQuery, criteriaBuilder) -> { List and = new ArrayList<>(); and.add(criteriaBuilder.equal(root.get("del"), false)); if (!pageQuery.getQuery().containsKey("admin")) { and.add(criteriaBuilder.equal(root.get("admin"), false)); } if (pageQuery.getQuery().containsKey("hasRole")) { String roleName = (String) pageQuery.getQuery().get("hasRole"); and.add(criteriaBuilder .isMember(Authority.get(AuthorityName.valueOf(roleName)), root.get("authorities"))); } return criteriaBuilder.and(and.toArray(new Predicate[0])); }); return userRepo.findAll(specification, JpaUtils.toPageRequest(pageQuery)); } public User create(UserRegister userRegister) { if (StringUtils.isNoneEmpty(userRegister.getPhone()) && userRepo.findByPhoneAndDelFalse(userRegister.getPhone()) .orElse(null) != null) { throw new BusinessException("该手机号已注册"); } User user = new User(); BeanUtils.copyProperties(userRegister, user); user.setShareRatio(sysConfigService.getBigDecimal("share_ratio")); user.setAuthStatus(AuthStatus.NOT_AUTH); if (StringUtils.isNotBlank(userRegister.getPassword())) { user.setPassword(new BCryptPasswordEncoder().encode(userRegister.getPassword())); } return userRepo.save(user); } public User phoneRegister(String phone, String code, String password) { String name = "9th_" + RandomStringUtils.randomAlphabetic(8); User user = create(UserRegister.builder() .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .username(name) .nickname(name) .password(password) .avatar(Constants.DEFAULT_AVATAR) .phone(phone) .build()); return user; } public User phoneRegister(String phone, String code, String password, String inviteCode, Long invitor) { String name = "9th_" + RandomStringUtils.randomAlphabetic(8); Invite invite = null; if (StringUtils.isNotBlank(inviteCode)) { invite = inviteRepo.findFirstByCode(inviteCode).orElse(null); } smsService.verify(phone, code); User user = create(UserRegister.builder() .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .username(name) .nickname(name) .password(password) .avatar(Constants.DEFAULT_AVATAR) .phone(phone) .invitorPhone(Optional.ofNullable(invite).map(Invite::getPhone).orElse(null)) .invitorName(Optional.ofNullable(invite).map(Invite::getName).orElse(null)) .inviteCode(Optional.ofNullable(invite).map(Invite::getCode).orElse(null)) .invitor(invitor) .build()); if (invite != null) { inviteRepo.increaseNum(invite.getId()); } return user; } public void del(Long id) { User user = userRepo.findById(id).orElseThrow(new BusinessException("用户不存在")); user.setDel(true); if (StringUtils.isNoneEmpty(user.getOpenId())) { user.setOpenId(user.getOpenId() + "###" + RandomStringUtils.randomAlphabetic(8)); } if (StringUtils.isNoneEmpty(user.getPhone())) { user.setPhone(user.getPhone() + "###" + RandomStringUtils.randomAlphabetic(8)); } userRepo.save(user); } public User loginByPhone(String phone, String code) { User user = userRepo.findByPhoneAndDelFalse(phone).orElseThrow(new BusinessException("该手机未注册")); smsService.verify(phone, code); if (user == null) { String name = "9th_" + RandomStringUtils.randomAlphabetic(8); user = create(UserRegister.builder() .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .username(name) .nickname(name) .avatar(Constants.DEFAULT_AVATAR) .phone(phone) .build()); } return user; } public User loginByPhonePwd(String phone, String password) { if (StringUtils.isEmpty(phone)) { throw new BusinessException("手机号错误"); } User user = userRepo.findByPhoneAndDelFalse(phone).orElseThrow(new BusinessException("账号或密码错误")); if (StringUtils.isEmpty(user.getPassword())) { throw new BusinessException("账号或密码错误"); } if (StringUtils.isNoneEmpty(user.getPassword()) && !new BCryptPasswordEncoder().matches(password, user.getPassword())) { throw new BusinessException("账号或密码错误"); } return user; } public User loginMp(String code) throws WxErrorException { WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code); WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(accessToken, null); User user = userRepo.findByOpenIdAndDelFalse(wxMpUser.getOpenId()).orElse(null); if (user == null) { String name = "9th_" + RandomStringUtils.randomAlphabetic(8); user = User.builder() .username(name) .nickname(name) .avatar(wxMpUser.getHeadImgUrl()) .sex(wxMpUser.getSexDesc()) .country(wxMpUser.getCountry()) .province(wxMpUser.getProvince()) .city(wxMpUser.getCity()) .openId(wxMpUser.getOpenId()) .language(wxMpUser.getLanguage()) .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .authStatus(AuthStatus.NOT_AUTH) .build(); userRepo.save(user); } return user; } public String code2openId(String code) throws WxErrorException { WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code); return wxMpService.oauth2getUserInfo(accessToken, null).getOpenId(); } public User loginMa(String code) { try { WxMaJscode2SessionResult result = wxMaService.jsCode2SessionInfo(code); String openId = result.getOpenid(); String sessionKey = result.getSessionKey(); User userInfo = userRepo.findByOpenIdAndDelFalse(openId).orElse(null); ; if (userInfo != null) { return userInfo; } String name = "9th_" + RandomStringUtils.randomAlphabetic(8); userInfo = User.builder() .username(name) .nickname(name) .openId(openId) .avatar(Constants.DEFAULT_AVATAR) .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .authStatus(AuthStatus.NOT_AUTH) .build(); userInfo = userRepo.save(userInfo); return userInfo; } catch (WxErrorException e) { e.printStackTrace(); } throw new BusinessException("登录失败"); } public User getMaUserInfo(String sessionKey, String rawData, String signature, String encryptedData, String iv) { // 用户信息校验 if (!wxMaService.getUserService().checkUserInfo(sessionKey, rawData, signature)) { throw new BusinessException("获取用户信息失败"); } // 解密用户信息 WxMaUserInfo wxUserInfo = wxMaService.getUserService().getUserInfo(sessionKey, encryptedData, iv); User user = userRepo.findByOpenIdAndDelFalse(wxUserInfo.getOpenId()).orElse(null); String avatarUrl = Constants.DEFAULT_AVATAR; try { String path = "image/avatar/" + new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()) + RandomStringUtils.randomAlphabetic(8) + ".jpg"; avatarUrl = storageService.uploadFromUrl(wxUserInfo.getAvatarUrl(), path); } catch (Exception e) { log.error("获取头像失败", e); } if (user == null) { user = User.builder() .username(UUID.randomUUID().toString()) .nickname(wxUserInfo.getNickName()) .openId(wxUserInfo.getOpenId()) .avatar(avatarUrl) .sex(wxUserInfo.getGender()) .country(wxUserInfo.getCountry()) .province(wxUserInfo.getProvince()) .city(wxUserInfo.getCity()) .authorities(Collections.singleton(Authority.builder().name("ROLE_USER").build())) .build(); user = userRepo.save(user); } else { user.setAvatar(avatarUrl); user.setNickname(wxUserInfo.getNickName()); user.setSex(wxUserInfo.getGender()); user.setCountry(wxUserInfo.getCountry()); user.setProvince(wxUserInfo.getProvince()); user.setCity(wxUserInfo.getCity()); user = userRepo.save(user); } return user; } public String setPassword(Long userId, String password) { User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); user.setPassword(new BCryptPasswordEncoder().encode(password)); user = userRepo.save(user); return jwtTokenUtil.generateToken(JwtUserFactory.create(user)); } public String setPassword(Long userId, String code, String password) { User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); smsService.verify(user.getPhone(), code); return setPassword(userId, password); } public String forgotPassword(String phone, String password, String code) { User user = userRepo.findByPhoneAndDelFalse(phone).orElseThrow(new BusinessException("手机号未注册")); smsService.verify(user.getPhone(), code); return setPassword(user.getId(), password); } public void bindPhone(Long userId, String phone) { User user = userRepo.findByIdAndDelFalse(userId).orElseThrow(new BusinessException("用户不存在")); if (StringUtils.isNoneEmpty(user.getPhone())) { throw new BusinessException("该账号已绑定手机"); } userRepo.findByPhoneAndDelFalse(phone).ifPresent(user1 -> { if (!user1.getId().equals(userId)) { throw new BusinessException("该手机号已绑定其他账号"); } }); user.setPhone(phone); userRepo.save(user); } public UserDTO toDTO(User user) { return toDTO(user, true); } public UserDTO toDTO(User user, boolean join) { UserDTO userDTO = new UserDTO(); BeanUtils.copyProperties(user, userDTO); if (user.getAuthorities() != null) { userDTO.setAuthorities(new HashSet<>(user.getAuthorities())); } if (join) { if (SecurityUtils.getAuthenticatedUser() != null) { userDTO.setFollow(followService.isFollow(SecurityUtils.getAuthenticatedUser().getId(), user.getId())); } } return userDTO; } public List toDTO(List users) { List follows = new ArrayList<>(); if (SecurityUtils.getAuthenticatedUser() != null) { follows.addAll(followRepo.findByUserId(SecurityUtils.getAuthenticatedUser().getId())); } return users.stream().parallel().map(user -> { UserDTO dto = toDTO(user, false); if (!follows.isEmpty()) { dto.setFollow(follows.stream().anyMatch(f -> f.getFollowUserId().equals(user.getId()))); } return dto; }).collect(Collectors.toList()); } public Page toDTO(Page users) { List userDTOS = toDTO(users.getContent()); return new PageImpl<>(userDTOS, users.getPageable(), users.getTotalElements()); } @CacheEvict(value = "user", allEntries = true) public void setTradeCode(Long userId, String token, String tradeCode) { String phone = smsService.verifyToken(token); User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); if (!StringUtils.equals(phone, user.getPhone())) { throw new BusinessException("验证码无效"); } user.setTradeCode(new BCryptPasswordEncoder().encode(tradeCode)); userRepo.save(user); } public void verifyTradeCode(Long userId, String tradeCode) { User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); if (!new BCryptPasswordEncoder().matches(tradeCode, user.getTradeCode())) { throw new BusinessException("校验失败"); } } public Map searchByPhone(String phone) { if (AuthStatus.SUCCESS != SecurityUtils.getAuthenticatedUser().getAuthStatus()) { throw new BusinessException("实名认证后才能赠送"); } User user = userRepo.findByPhoneAndDelFalse(phone).orElseThrow(new BusinessException("用户不存在或未认证")); if (AuthStatus.SUCCESS != user.getAuthStatus()) { throw new BusinessException("用户不存在或未认证"); } String realName = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc( user.getId(), AuthStatus.SUCCESS) .map(IdentityAuth::getRealName).orElse("").replaceAll(".*(?=.)", "**"); Map map = new HashMap<>(); map.put("id", user.getId()); map.put("avatar", user.getAvatar()); map.put("phone", user.getPhone().replaceAll("(?<=.{3}).*(?=.{4})", "**")); map.put("realName", realName); return map; } public Map searchByPhoneAdmin(String phoneStr) { List phone = Arrays.stream(phoneStr.replaceAll("\n", " ") .replaceAll("\r\n", " ") .split(" ")) .map(String::trim) .filter(s -> !StringUtils.isEmpty(s)) .collect(Collectors.toList()); List users = userRepo.findByPhoneInAndDelFalse(phone); Map map = new HashMap<>(); map.put("users", users); List notFound = phone.stream().filter(p -> users.stream().noneMatch(u -> p.equals(u.getPhone()))) .collect(Collectors.toList()); map.put("notFound", notFound); return map; } public void addBankCard(Long userId, String bankNo, String phone, String code) throws BaseAdaPayException { User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); IdentityAuth identityAuth = identityAuthRepo .findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(userId, AuthStatus.SUCCESS) .orElseThrow(new BusinessException("用户未认证")); if (identityAuth.isOrg()) { //throw new BusinessException("企业认证用户请绑定对公账户"); } if (!StringUtils.isBlank(user.getSettleAccountId())) { throw new BusinessException("此账号已绑定"); } BankValidate bankValidate = BankUtils.validate(bankNo); if (!bankValidate.isValidated()) { throw new BusinessException("暂不支持此卡"); } if (StringUtils.isEmpty(user.getMemberId())) { user.setMemberId(adapayService.createMember(userId, user.getPhone(), identityAuth.getRealName(), identityAuth.getIdNo())); userRepo.save(user); } smsService.verify(phone, code); String accountId = adapayService.createSettleAccount(user.getMemberId(), identityAuth.getRealName(), identityAuth.getIdNo(), phone, bankNo); user.setSettleAccountId(accountId); userRepo.save(user); userBankCardRepo.save(UserBankCard.builder() .bank(bankValidate.getBank()) .bankName(bankValidate.getBankName()) .bankNo(bankNo) .cardType(bankValidate.getCardType()) .cardTypeDesc(bankValidate.getCardTypeDesc()) .userId(userId) .phone(phone) .build()); } public void removeBankCard(Long userId) throws BaseAdaPayException { User user = userRepo.findById(userId).orElseThrow(new BusinessException("用户不存在")); if (StringUtils.isNotBlank(user.getSettleAccountId()) && StringUtils.isNotBlank(user.getMemberId())) { adapayService.delSettleAccount(user.getMemberId(), user.getSettleAccountId()); user.setSettleAccountId(null); userRepo.save(user); userBankCardRepo.deleteByUserId(userId); } else { throw new BusinessException("未绑定"); } } public Map batchRegister(String phones, String defaultPassword) { List exist = new ArrayList<>(); List err = new ArrayList<>(); List success = new ArrayList<>(); Arrays.stream(phones.replaceAll(",", " ") .replaceAll(",", " ") .replaceAll("\n", " ") .replaceAll("\r\n", " ") .split(" ")).forEach(phone -> { if (userRepo.findByPhoneAndDelFalse(phone).isPresent()) { exist.add(phone); } else { if (!Pattern.matches("^1[3-9]\\d{9}$", phone)) { err.add(phone); } else { try { String name = "9th_" + RandomStringUtils.randomAlphabetic(8); User user = create(UserRegister.builder() .authorities(Collections.singleton(Authority.get(AuthorityName.ROLE_USER))) .username(name) .nickname(name) .password(defaultPassword) .avatar(Constants.DEFAULT_AVATAR) .phone(phone) .build()); success.add(phone); } catch (Exception e) { log.error("注册失败", e); err.add(phone); } } } }); Map map = new HashMap<>(); map.put("exist", exist); map.put("error", err); map.put("success", success); return map; } public void switchAccount() { switchAccount(adapayProperties.getAppId()); } public void switchAccount(String appId) { userRepo.findBySettleAccountIdIsNotNull().parallelStream().forEach(user -> { try { IdentityAuth identityAuth = identityAuthRepo.findFirstByUserIdAndStatusAndDelFalseOrderByCreatedAtDesc(user.getId(), AuthStatus.SUCCESS) .orElseThrow(new BusinessException("用户未认证")); UserBankCard userBankCard = userBankCardRepo.findByUserId(user.getId()).stream().findAny() .orElseThrow(new BusinessException("未绑卡")); createMember(appId, user.getId().toString(), Optional.ofNullable(userBankCard.getPhone()) .orElse(user.getPhone()), identityAuth.getRealName(), identityAuth.getIdNo()); createSettleAccount(appId, user.getId() .toString(), identityAuth.getRealName(), identityAuth.getIdNo(), Optional.ofNullable(userBankCard.getPhone()) .orElse(user.getPhone()), userBankCard.getBankNo()); userBankCard.setPhone(Optional.ofNullable(userBankCard.getPhone()).orElse(user.getPhone())); userBankCardRepo.save(userBankCard); } catch (Exception e) { try { adapayService.delSettleAccount(user.getMemberId(), user.getSettleAccountId()); } catch (Exception ex) { ex.printStackTrace(); } user.setSettleAccountId(null); userRepo.save(user); userBankCardRepo.deleteByUserId(user.getId()); } }); } public void createMember(String appId, String memberId, String tel, String realName, String idno) throws BaseAdaPayException { Map memberParams = new HashMap<>(); memberParams.put("adapay_func_code", "members.realname"); memberParams.put("member_id", memberId); memberParams.put("app_id", appId); memberParams.put("tel_no", tel); memberParams.put("user_name", realName); memberParams.put("cert_type", "00"); memberParams.put("cert_id", idno); Map res = AdapayCommon.requestAdapay(memberParams); log.info("createMember\n{}", JSON.toJSONString(res, SerializerFeature.PrettyFormat)); if (!("succeeded".equals(MapUtils.getString(res, "status")) || "member_id_exists".equals(MapUtils.getString(res, "error_code")))) { String errMsg = MapUtils.getString(res, "error_msg"); String errCode = MapUtils.getString(res, "error_code"); throw new BusinessException(errMsg + "(" + errCode + ")"); } } public String createSettleAccount(String appId, String memberId, String realName, String idNo, String phone, String bankNo) throws BaseAdaPayException { Map settleCountParams = new HashMap<>(); Map accountInfo = new HashMap<>(); accountInfo.put("card_id", bankNo); accountInfo.put("card_name", realName); accountInfo.put("cert_id", idNo); accountInfo.put("cert_type", "00"); accountInfo.put("tel_no", phone); accountInfo.put("bank_acct_type", "2"); settleCountParams.put("member_id", memberId); settleCountParams.put("app_id", appId); settleCountParams.put("channel", "bank_account"); settleCountParams.put("account_info", accountInfo); Map res = SettleAccount.create(settleCountParams); log.info("createSettleAccount\n{}", JSON.toJSONString(res, SerializerFeature.PrettyFormat)); if (!("succeeded".equals(MapUtils.getString(res, "status")) || "account_exists".equals(MapUtils.getString(res, "error_code")))) { String errMsg = MapUtils.getString(res, "error_msg"); String errCode = MapUtils.getString(res, "error_code"); throw new BusinessException(errMsg + "(" + errCode + ")"); } return MapUtils.getString(res, "id"); } }