package com.izouma.nineth.service; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.github.kevinsawicki.http.HttpRequest; import com.izouma.nineth.annotations.RedisLock; import com.izouma.nineth.domain.IdentityAuth; import com.izouma.nineth.domain.User; import com.izouma.nineth.dto.PageQuery; import com.izouma.nineth.enums.AuthStatus; import com.izouma.nineth.exception.BusinessException; import com.izouma.nineth.repo.IdentityAuthRepo; import com.izouma.nineth.repo.UserRepo; import com.izouma.nineth.utils.DateTimeUtils; import com.izouma.nineth.utils.JpaUtils; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import org.springframework.core.env.Environment; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.temporal.ChronoUnit; import java.util.*; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.regex.Pattern; import java.util.stream.Collectors; @Service @AllArgsConstructor @Slf4j public class IdentityAuthService { private IdentityAuthRepo identityAuthRepo; private UserRepo userRepo; private UserService userService; private AdapayService adapayService; private RedisTemplate redisTemplate; private Environment env; private SysConfigService sysConfigService; private CacheService cacheService; public Page all(PageQuery pageQuery) { return identityAuthRepo .findAll(JpaUtils.toSpecification(pageQuery, IdentityAuth.class), JpaUtils.toPageRequest(pageQuery)); } public void apply(IdentityAuth identityAuth) { if (identityAuth.getUserId() == null) { throw new BusinessException("用户不存在"); } User user = userRepo.findByIdAndDelFalse(identityAuth.getUserId()).orElseThrow(new BusinessException("用户不存在")); List auths = identityAuthRepo.findByUserIdAndDelFalse(identityAuth.getUserId()); auths.stream().filter(auth -> auth.getStatus() == AuthStatus.PENDING).findAny().ifPresent(a -> { throw new BusinessException("正在审核中,请勿重复提交"); }); auths.stream().filter(auth -> auth.getStatus() == AuthStatus.SUCCESS).findAny().ifPresent(a -> { throw new BusinessException("已认证,请勿重复提交"); }); identityAuth.setStatus(AuthStatus.PENDING); identityAuthRepo.save(identityAuth); user.setAuthStatus(AuthStatus.PENDING); userService.save(user); cacheService.clearUserMy(user.getId()); identityAuthRepo.deleteDuplicated(identityAuth.getUserId(), identityAuth.getId()); } public void audit(Long id, AuthStatus status, String reason) { IdentityAuth auth = identityAuthRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("申请不存在")); if (auth.getStatus() != AuthStatus.PENDING) { throw new BusinessException("已经审核过"); } User user = userRepo.findByIdAndDelFalse(auth.getUserId()).orElseThrow(new BusinessException("用户不存在")); if (user.getAuthStatus() != AuthStatus.SUCCESS) { if (status == AuthStatus.SUCCESS) { user.setAuthId(auth.getId()); } user.setAuthStatus(status); userService.save(user); } auth.setStatus(status); auth.setReason(reason); auth.setAutoValidated(true); identityAuthRepo.save(auth); cacheService.clearUserMy(user.getId()); identityAuthRepo.deleteDuplicated(auth.getUserId(), auth.getId()); } public List repeat(String idNo, Long userId) { List auths = identityAuthRepo.findAllByIdNoAndUserIdIsNotAndDelFalse(idNo, userId); if (auths.isEmpty()) { return null; } List userIds = auths.stream().map(IdentityAuth::getUserId).distinct().collect(Collectors.toList()); return userRepo.findByIdInAndDelFalse(userIds); } // public void validate(String name, String phone, String idno) { // String body = HttpRequest.post("https://jubrige.market.alicloudapi.com/mobile/3-validate-transfer") // .header("Authorization", "APPCODE b48bc8f6759345a79ae20a951f03dabe") // .contentType(HttpRequest.CONTENT_TYPE_FORM) // .form("idCardNo", idno) // .form("mobile", phone) // .form("name", name) // .body(); // JSONObject jsonObject = JSONObject.parseObject(body); // if (jsonObject.getInteger("code") != 200) { // String msg = jsonObject.getString("msg"); // throw new BusinessException(msg); // } else { // JSONObject data = jsonObject.getJSONObject("data"); // int result = data.getIntValue("result"); // String desc = data.getString("desc"); // if (result != 0) { // throw new BusinessException(desc); // } else { // log.info("{} {} {} 实名认证通过", name, phone, idno); // } // } // } public static void validateV2(String name, String phone, String idno) { String body = HttpRequest.post("https://zid.market.alicloudapi.com/idcheck/Post") .header("Authorization", "APPCODE af29c2d37c4f415fac930d82f01fb559") .contentType(HttpRequest.CONTENT_TYPE_FORM) .form("cardNo", idno) .form("realName", name) .body(); JSONObject jsonObject = JSONObject.parseObject(body); log.info("validate {} {} \n{}", name, idno, JSON.toJSONString(jsonObject, SerializerFeature.PrettyFormat)); if (jsonObject.getInteger("error_code") != 0) { String msg = jsonObject.getString("reason"); throw new BusinessException(msg); } else { JSONObject data = jsonObject.getJSONObject("result"); boolean isOK = Optional.ofNullable(data.getBoolean("isok")).orElse(Boolean.FALSE); if (!isOK) { throw new BusinessException("不匹配"); } else { log.info("{} {} {} 实名认证通过", name, phone, idno); } } } public static void validate(String name, String phone, String idno) { HttpRequest request = HttpRequest.get("https://mobilecert.market.alicloudapi.com/mobile3MetaSimple?userName=" + URLEncoder.encode(name, StandardCharsets.UTF_8) + "&identifyNum=" + idno + "&mobile=" + phone) .header("Authorization", "APPCODE af29c2d37c4f415fac930d82f01fb559"); String body = request.body(); if (request.code() != 200) { throw new BusinessException(request.code() + "", request.code()); } JSONObject jsonObject = JSONObject.parseObject(body); log.info("validate {} {} \n{}", name, idno, JSON.toJSONString(jsonObject, SerializerFeature.PrettyFormat)); if (jsonObject.getInteger("code") != 200) { String msg = jsonObject.getString("message"); throw new BusinessException(msg); } else { JSONObject data = jsonObject.getJSONObject("data"); Integer bizCode = Optional.ofNullable(data.getInteger("bizCode")).orElse(3); if (bizCode == 1) { log.info("{} {} {} 实名认证通过", name, phone, idno); return; } } throw new BusinessException("不匹配"); } public void removeDuplicated() { boolean hasMore = true; int pageNum = 0; AtomicInteger count = new AtomicInteger(); while (hasMore) { Page page = identityAuthRepo.listUserId(PageRequest.of(pageNum, 100)); List userIds = page.getContent(); userIds.forEach(userId -> { userRepo.findById(userId).ifPresent(user -> { log.info("removeDuplicated {}/{} ", count.incrementAndGet(), page.getTotalElements()); List list = identityAuthRepo.findByUserId(userId); if (list.size() > 1) { IdentityAuth auth = list.stream() .filter(i -> i.getStatus() == AuthStatus.SUCCESS) .findAny().orElse(null); if (auth != null) { userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId()); int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId()); log.info("deleted {}", num); return; } auth = list.stream() .filter(i -> i.getStatus() == AuthStatus.PENDING) .findAny().orElse(null); if (auth != null) { userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId()); int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId()); log.info("deleted {}", num); return; } auth = list.stream() .filter(i -> i.getStatus() == AuthStatus.FAIL) .findAny().orElse(null); if (auth != null) { userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId()); int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId()); log.info("deleted {}", num); return; } } else if (list.size() == 1) { userRepo.setAuthStatus(user.getId(), list.get(0).getStatus(), list.get(0).getId()); } }); }); hasMore = page.hasNext(); pageNum++; } } @Scheduled(fixedRate = 12000) @RedisLock(value = "autoValidate", expire = 30, unit = TimeUnit.MINUTES) public void autoValidate() { if (!sysConfigService.getBoolean("auto_validate")) return; log.info("autoValidate"); if (Arrays.asList(env.getActiveProfiles()).contains("dev")) { return; } try { List list = identityAuthRepo.findByStatusAndAutoValidated(AuthStatus.PENDING, false); new ForkJoinPool(2).submit(() -> { list.parallelStream().forEach(identityAuth -> { Map map = auth(identityAuth); audit(identityAuth.getId(), (AuthStatus) map.get("status"), (String) map.get("reason")); }); }).get(); } catch (Exception e) { log.error("批量自动实名出错", e); } } public Map auth(IdentityAuth identityAuth) { log.info("实名 {}", identityAuth.getRealName()); Map result = new HashMap<>(); String reason = null; User user = userRepo.findById(identityAuth.getUserId()).orElseThrow(new BusinessException("用户不存在")); if (user.getAuthStatus() == AuthStatus.SUCCESS) { result.put("status", AuthStatus.SUCCESS); } else if (!Pattern .matches("[1-9]{1}[0-9]{5}(19|20)[0-9]{2}((0[1-9]{1})|(1[0-2]{1}))((0[1-9]{1})|([1-2]{1}[0-9]{1}|(3[0-1]{1})))[0-9]{3}[0-9x]{1}", identityAuth .getIdNo() .toLowerCase())) { result.put("status", AuthStatus.FAIL); result.put("reason", "身份证格式错误"); } else { LocalDate birth = DateTimeUtils.toLocalDate(identityAuth.getIdNo().substring(6, 14), "yyyyMMdd"); long age = ChronoUnit.YEARS.between(birth, LocalDate.now()); if (user.getPhone().startsWith("170") || user.getPhone().startsWith("171") || user.getPhone().startsWith("162") || user.getPhone().startsWith("165") || user.getPhone().startsWith("167")) { result.put("status", AuthStatus.FAIL); result.put("reason", "虚拟号"); } else if (age < 18) { result.put("status", AuthStatus.FAIL); result.put("reason", "未满18岁"); } else if (age > 60) { result.put("status", AuthStatus.FAIL); result.put("reason", "超过60岁"); } else { int count = identityAuthRepo.countByIdNoAndStatus(identityAuth.getIdNo(), AuthStatus.SUCCESS); if (count >= 3) { result.put("status", AuthStatus.FAIL); result.put("reason", "同一身份证注册超过3个"); } else { try { validateV2(identityAuth.getRealName(), identityAuth.getPhone(), identityAuth.getIdNo()); result.put("status", AuthStatus.SUCCESS); } catch (Exception e) { log.error("自动实名出错", e); if (e instanceof BusinessException && ((BusinessException) e).getCode() == 403) { result.put("status", AuthStatus.PENDING); } else { result.put("status", AuthStatus.FAIL); result.put("reason", e.getMessage()); } } } } } return result; } }