IdentityAuthService.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package com.izouma.nineth.service;
  2. import com.alibaba.fastjson.JSON;
  3. import com.alibaba.fastjson.JSONObject;
  4. import com.alibaba.fastjson.serializer.SerializerFeature;
  5. import com.github.kevinsawicki.http.HttpRequest;
  6. import com.izouma.nineth.annotations.RedisLock;
  7. import com.izouma.nineth.config.GeneralProperties;
  8. import com.izouma.nineth.domain.IdentityAuth;
  9. import com.izouma.nineth.domain.User;
  10. import com.izouma.nineth.dto.PageQuery;
  11. import com.izouma.nineth.enums.AuthStatus;
  12. import com.izouma.nineth.exception.BusinessException;
  13. import com.izouma.nineth.repo.IdentityAuthRepo;
  14. import com.izouma.nineth.repo.UserRepo;
  15. import com.izouma.nineth.utils.DateTimeUtils;
  16. import com.izouma.nineth.utils.JpaUtils;
  17. import lombok.AllArgsConstructor;
  18. import lombok.extern.slf4j.Slf4j;
  19. import org.apache.commons.lang3.StringUtils;
  20. import org.springframework.core.env.Environment;
  21. import org.springframework.data.domain.Page;
  22. import org.springframework.data.domain.PageRequest;
  23. import org.springframework.data.redis.core.RedisTemplate;
  24. import org.springframework.scheduling.annotation.Scheduled;
  25. import org.springframework.stereotype.Service;
  26. import java.time.LocalDate;
  27. import java.time.temporal.ChronoUnit;
  28. import java.util.*;
  29. import java.util.concurrent.TimeUnit;
  30. import java.util.concurrent.atomic.AtomicInteger;
  31. import java.util.regex.Pattern;
  32. import java.util.stream.Collectors;
  33. @Service
  34. @AllArgsConstructor
  35. @Slf4j
  36. public class IdentityAuthService {
  37. private IdentityAuthRepo identityAuthRepo;
  38. private UserRepo userRepo;
  39. private UserService userService;
  40. private AdapayService adapayService;
  41. private RedisTemplate<String, Object> redisTemplate;
  42. private Environment env;
  43. private SysConfigService sysConfigService;
  44. private CacheService cacheService;
  45. private GeneralProperties generalProperties;
  46. public Page<IdentityAuth> all(PageQuery pageQuery) {
  47. return identityAuthRepo.findAll(JpaUtils.toSpecification(pageQuery, IdentityAuth.class), JpaUtils.toPageRequest(pageQuery));
  48. }
  49. public void apply(IdentityAuth identityAuth) {
  50. if (StringUtils.isBlank(identityAuth.getRealName())) {
  51. throw new BusinessException("请填写姓名");
  52. }
  53. 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()
  54. .toLowerCase())) {
  55. throw new BusinessException("身份证格式错误");
  56. }
  57. if (identityAuth.getUserId() == null) {
  58. throw new BusinessException("用户不存在");
  59. }
  60. User user = userRepo.findByIdAndDelFalse(identityAuth.getUserId()).orElseThrow(new BusinessException("用户不存在"));
  61. List<IdentityAuth> auths = identityAuthRepo.findByUserIdAndDelFalse(identityAuth.getUserId());
  62. auths.stream().filter(auth -> auth.getStatus() == AuthStatus.PENDING).findAny().ifPresent(a -> {
  63. throw new BusinessException("正在审核中,请勿重复提交");
  64. });
  65. auths.stream().filter(auth -> auth.getStatus() == AuthStatus.SUCCESS).findAny().ifPresent(a -> {
  66. throw new BusinessException("已认证,请勿重复提交");
  67. });
  68. identityAuth.setStatus(AuthStatus.PENDING);
  69. identityAuthRepo.save(identityAuth);
  70. user.setAuthStatus(AuthStatus.PENDING);
  71. userService.save(user);
  72. cacheService.clearUserMy(user.getId());
  73. identityAuthRepo.deleteDuplicated(identityAuth.getUserId(), identityAuth.getId());
  74. }
  75. public void audit(Long id, AuthStatus status, String reason) {
  76. IdentityAuth auth = identityAuthRepo.findByIdAndDelFalse(id).orElseThrow(new BusinessException("申请不存在"));
  77. if (auth.getStatus() != AuthStatus.PENDING) {
  78. throw new BusinessException("已经审核过");
  79. }
  80. User user = userRepo.findByIdAndDelFalse(auth.getUserId()).orElseThrow(new BusinessException("用户不存在"));
  81. if (user.getAuthStatus() != AuthStatus.SUCCESS) {
  82. if (status == AuthStatus.SUCCESS) {
  83. user.setAuthId(auth.getId());
  84. }
  85. user.setAuthStatus(status);
  86. userService.save(user);
  87. }
  88. auth.setStatus(status);
  89. auth.setReason(reason);
  90. auth.setAutoValidated(true);
  91. identityAuthRepo.save(auth);
  92. identityAuthRepo.deleteDuplicated(auth.getUserId(), auth.getId());
  93. }
  94. public List<User> repeat(String idNo, Long userId) {
  95. List<IdentityAuth> auths = identityAuthRepo.findAllByIdNoAndUserIdIsNotAndDelFalse(idNo, userId);
  96. if (auths.isEmpty()) {
  97. return null;
  98. }
  99. List<Long> userIds = auths.stream().map(IdentityAuth::getUserId).distinct().collect(Collectors.toList());
  100. return userRepo.findByIdInAndDelFalse(userIds);
  101. }
  102. // public void validate(String name, String phone, String idno) {
  103. // String body = HttpRequest.post("https://jubrige.market.alicloudapi.com/mobile/3-validate-transfer")
  104. // .header("Authorization", "APPCODE b48bc8f6759345a79ae20a951f03dabe")
  105. // .contentType(HttpRequest.CONTENT_TYPE_FORM)
  106. // .form("idCardNo", idno)
  107. // .form("mobile", phone)
  108. // .form("name", name)
  109. // .body();
  110. // JSONObject jsonObject = JSONObject.parseObject(body);
  111. // if (jsonObject.getInteger("code") != 200) {
  112. // String msg = jsonObject.getString("msg");
  113. // throw new BusinessException(msg);
  114. // } else {
  115. // JSONObject data = jsonObject.getJSONObject("data");
  116. // int result = data.getIntValue("result");
  117. // String desc = data.getString("desc");
  118. // if (result != 0) {
  119. // throw new BusinessException(desc);
  120. // } else {
  121. // log.info("{} {} {} 实名认证通过", name, phone, idno);
  122. // }
  123. // }
  124. // }
  125. public static void validate(String name, String phone, String idno) {
  126. HttpRequest request = HttpRequest.post("https://zid.market.alicloudapi.com/idcheck/Post")
  127. .header("Authorization", "APPCODE b48bc8f6759345a79ae20a951f03dabe")
  128. .contentType(HttpRequest.CONTENT_TYPE_FORM)
  129. .form("cardNo", idno)
  130. .form("realName", name);
  131. int code = request.code();
  132. if (code != 200) {
  133. throw new BusinessException("网络异常", "网络异常");
  134. }
  135. String body = request.body();
  136. JSONObject jsonObject = JSONObject.parseObject(body);
  137. log.info("validate {} {} \n{}", name, idno, JSON.toJSONString(jsonObject, SerializerFeature.PrettyFormat));
  138. if (jsonObject.getInteger("error_code") != 0) {
  139. String msg = jsonObject.getString("reason");
  140. throw new BusinessException(msg);
  141. } else {
  142. JSONObject data = jsonObject.getJSONObject("result");
  143. boolean isOK = Optional.ofNullable(data.getBoolean("isok")).orElse(Boolean.FALSE);
  144. if (!isOK) {
  145. throw new BusinessException("不匹配");
  146. } else {
  147. log.info("{} {} {} 实名认证通过", name, phone, idno);
  148. }
  149. }
  150. }
  151. public void removeDuplicated() {
  152. boolean hasMore = true;
  153. int pageNum = 0;
  154. AtomicInteger count = new AtomicInteger();
  155. while (hasMore) {
  156. Page<Long> page = identityAuthRepo.listUserId(PageRequest.of(pageNum, 100));
  157. List<Long> userIds = page.getContent();
  158. userIds.forEach(userId -> {
  159. userRepo.findById(userId).ifPresent(user -> {
  160. log.info("removeDuplicated {}/{} ", count.incrementAndGet(), page.getTotalElements());
  161. List<IdentityAuth> list = identityAuthRepo.findByUserId(userId);
  162. if (list.size() > 1) {
  163. IdentityAuth auth = list.stream()
  164. .filter(i -> i.getStatus() == AuthStatus.SUCCESS)
  165. .findAny().orElse(null);
  166. if (auth != null) {
  167. userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId());
  168. int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId());
  169. log.info("deleted {}", num);
  170. return;
  171. }
  172. auth = list.stream()
  173. .filter(i -> i.getStatus() == AuthStatus.PENDING)
  174. .findAny().orElse(null);
  175. if (auth != null) {
  176. userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId());
  177. int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId());
  178. log.info("deleted {}", num);
  179. return;
  180. }
  181. auth = list.stream()
  182. .filter(i -> i.getStatus() == AuthStatus.FAIL)
  183. .findAny().orElse(null);
  184. if (auth != null) {
  185. userRepo.setAuthStatus(user.getId(), auth.getStatus(), auth.getId());
  186. int num = identityAuthRepo.deleteDuplicated(user.getId(), auth.getId());
  187. log.info("deleted {}", num);
  188. return;
  189. }
  190. } else if (list.size() == 1) {
  191. userRepo.setAuthStatus(user.getId(), list.get(0).getStatus(), list.get(0).getId());
  192. }
  193. });
  194. });
  195. hasMore = page.hasNext();
  196. pageNum++;
  197. }
  198. }
  199. @Scheduled(fixedRate = 60000)
  200. @RedisLock(value = "autoValidate", expire = 30, unit = TimeUnit.MINUTES)
  201. public void autoValidate() {
  202. if (generalProperties.isNotifyServer()) return;
  203. if (!sysConfigService.getBoolean("auto_validate")) return;
  204. log.info("autoValidate");
  205. if (Arrays.asList(env.getActiveProfiles()).contains("dev")) {
  206. return;
  207. }
  208. try {
  209. List<IdentityAuth> list = identityAuthRepo.findByStatusAndAutoValidated(AuthStatus.PENDING, false);
  210. list.parallelStream().forEach(identityAuth -> {
  211. Map<String, Object> map = auth(identityAuth);
  212. if (map != null) {
  213. audit(identityAuth.getId(), (AuthStatus) map.get("status"), (String) map.get("reason"));
  214. }
  215. });
  216. } catch (Exception e) {
  217. log.error("批量自动实名出错", e);
  218. }
  219. }
  220. public Map<String, Object> auth(IdentityAuth identityAuth) {
  221. log.info("实名 {}", identityAuth.getRealName());
  222. Map<String, Object> result = new HashMap<>();
  223. String reason = null;
  224. User user = userRepo.findById(identityAuth.getUserId()).orElseThrow(new BusinessException("用户不存在"));
  225. if (user.getAuthStatus() == AuthStatus.SUCCESS) {
  226. result.put("status", AuthStatus.SUCCESS);
  227. } 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()
  228. .toLowerCase())) {
  229. result.put("status", AuthStatus.FAIL);
  230. result.put("reason", "身份证格式错误");
  231. } else {
  232. LocalDate birth = DateTimeUtils.toLocalDate(identityAuth.getIdNo().substring(6, 14), "yyyyMMdd");
  233. long age = ChronoUnit.YEARS.between(birth, LocalDate.now());
  234. if (user.getPhone().startsWith("170") ||
  235. user.getPhone().startsWith("171") ||
  236. user.getPhone().startsWith("162") ||
  237. user.getPhone().startsWith("165") ||
  238. user.getPhone().startsWith("167")) {
  239. result.put("status", AuthStatus.FAIL);
  240. result.put("reason", "虚拟号");
  241. } else if (age < 18) {
  242. result.put("status", AuthStatus.FAIL);
  243. result.put("reason", "未满18岁");
  244. } else if (age > 60) {
  245. result.put("status", AuthStatus.FAIL);
  246. result.put("reason", "超过60岁");
  247. } else {
  248. int count = identityAuthRepo.countByIdNoAndStatus(identityAuth.getIdNo(), AuthStatus.SUCCESS);
  249. if (count >= 3) {
  250. result.put("status", AuthStatus.FAIL);
  251. result.put("reason", "同一身份证注册超过3个");
  252. } else {
  253. try {
  254. validate(identityAuth.getRealName(), user.getPhone(), identityAuth.getIdNo());
  255. result.put("status", AuthStatus.SUCCESS);
  256. } catch (Exception e) {
  257. log.error("自动实名出错", e);
  258. if ("不匹配".equals(e.getMessage())) {
  259. result.put("status", AuthStatus.FAIL);
  260. } else if ("网络异常".equals(e.getMessage())) {
  261. return null;
  262. } else {
  263. result.put("status", AuthStatus.PENDING);
  264. }
  265. result.put("reason", e.getMessage());
  266. }
  267. }
  268. }
  269. }
  270. return result;
  271. }
  272. }