IdentityAuthService.java 15 KB

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