IntimacyService.java 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. package com.qingjian.module.person.service;
  2. import cn.hutool.core.collection.CollUtil;
  3. import cn.hutool.core.util.ReUtil;
  4. import cn.hutool.core.util.StrUtil;
  5. import cn.hutool.json.JSONObject;
  6. import cn.hutool.json.JSONUtil;
  7. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  8. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  9. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  10. import com.qingjian.common.cache.CaseDataCache;
  11. import com.qingjian.common.exception.ServerException;
  12. import com.qingjian.common.model.person.dto.*;
  13. import com.qingjian.common.model.person.entity.Intimacy;
  14. import com.qingjian.common.model.person.entity.PersonBasicInfo;
  15. import com.qingjian.common.model.person.entity.PersonGroup;
  16. import com.qingjian.common.model.person.entity.PersonGroupRel;
  17. import com.qingjian.common.model.track.dto.TravelDataDTO;
  18. import com.qingjian.common.model.track.dto.TravelPairDTO;
  19. import com.qingjian.common.model.track.dto.TravelTimelineDTO;
  20. import com.qingjian.common.model.trans.entity.AcctOpenInfo;
  21. import com.qingjian.common.model.trans.entity.TransRecord;
  22. import com.qingjian.module.govern.serivce.GovernConfService;
  23. import com.qingjian.module.person.mapper.IntimacyMapper;
  24. import com.qingjian.module.person.mapper.PersonBasicInfoMapper;
  25. import com.qingjian.module.person.mapper.PersonGroupMapper;
  26. import com.qingjian.module.person.mapper.PersonGroupRelMapper;
  27. import com.qingjian.module.plat.service.CacheManageService;
  28. import com.qingjian.module.track.mapper.TrackTogetherTravelMapper;
  29. import com.qingjian.module.trans.mapper.AcctOpenInfoMapper;
  30. import com.qingjian.module.trans.mapper.TransRecordMapper;
  31. import lombok.extern.slf4j.Slf4j;
  32. import org.noear.solon.annotation.Inject;
  33. import org.noear.solon.annotation.Managed;
  34. import java.math.BigDecimal;
  35. import java.time.LocalDate;
  36. import java.time.format.DateTimeFormatter;
  37. import java.util.*;
  38. import java.util.concurrent.atomic.AtomicInteger;
  39. import java.util.function.Function;
  40. import java.util.stream.Collectors;
  41. import java.util.stream.IntStream;
  42. @Slf4j
  43. @Managed
  44. public class IntimacyService extends ServiceImpl<IntimacyMapper, Intimacy> {
  45. @Inject
  46. private IntimacyMapper intimacyMapper;
  47. @Inject
  48. private TransRecordMapper transRecordMapper;
  49. @Inject
  50. private PersonGroupMapper personGroupMapper;
  51. @Inject
  52. private PersonGroupRelMapper personGroupRelMapper;
  53. @Inject
  54. private AcctOpenInfoMapper acctOpenInfoMapper;
  55. @Inject
  56. private GovernConfService governConfService;
  57. @Inject
  58. private PersonBasicInfoMapper personBasicInfoMapper;
  59. @Inject
  60. private CacheManageService cacheManageService;
  61. @Inject
  62. private TrackTogetherTravelMapper trackTogetherTravelMapper;
  63. public List<Intimacy> dataList() {
  64. return this.list();
  65. }
  66. public void update(List<Intimacy> intimacyList) {
  67. if (CollUtil.isEmpty(intimacyList)) {
  68. return;
  69. }
  70. Integer totalWeight = intimacyList.stream().map(Intimacy::getWeight).reduce(0, Integer::sum);
  71. if (totalWeight > 100) {
  72. throw new ServerException("权重之和不能大于 100!");
  73. }
  74. intimacyMapper.updateById(intimacyList);
  75. cacheManageService.removeByPrefix("dp_intimacySummary_");
  76. }
  77. // ==================== 亲密度汇总 ====================
  78. public List<IntimacyDTO> intimacySummary(String personName) {
  79. List<IntimacyDTO> allBehaviors = new ArrayList<>();
  80. allBehaviors.addAll(bigTrans(personName));
  81. allBehaviors.addAll(specialDayTrans(personName));
  82. allBehaviors.addAll(samePhoneBinding(personName));
  83. allBehaviors.addAll(keepCall(personName));
  84. allBehaviors.addAll(nightCall(personName));
  85. allBehaviors.addAll(specialDayCall(personName));
  86. allBehaviors.addAll(keepTrans(personName));
  87. allBehaviors.addAll(travelTogether(personName));
  88. Map<String, List<IntimacyDTO>> groupedByOther = allBehaviors.stream()
  89. .filter(i -> StrUtil.isNotEmpty(i.getOtherName()))
  90. .collect(Collectors.groupingBy(IntimacyDTO::getOtherName));
  91. List<IntimacyDTO> results = new ArrayList<>();
  92. groupedByOther.forEach((otherName, behaviors) -> {
  93. if (CollUtil.isEmpty(behaviors)) {
  94. return;
  95. }
  96. int totalIntimacy = behaviors.stream().map(IntimacyDTO::getIntimacy).reduce(0, Integer::sum);
  97. List<String> behaviorLabels = behaviors.stream().map(IntimacyDTO::getIntimacyBehavior).distinct().collect(Collectors.toList());
  98. JSONObject detail = mergeDetail(behaviors);
  99. IntimacyDTO result = new IntimacyDTO();
  100. result.setOtherName(otherName);
  101. result.setIntimacy(totalIntimacy / 100);
  102. result.setIntimacyBehavior(StrUtil.join(",", behaviorLabels));
  103. result.setDetail(detail);
  104. results.add(result);
  105. });
  106. return results.stream()
  107. .filter(i -> i.getIntimacy() != 0)
  108. .sorted(Comparator.comparingInt(IntimacyDTO::getIntimacy).reversed())
  109. .toList();
  110. }
  111. private JSONObject mergeDetail(List<IntimacyDTO> behaviors) {
  112. return behaviors.stream()
  113. .map(IntimacyDTO::getDetail)
  114. .flatMap(d -> d.entrySet().stream())
  115. .collect(
  116. JSONObject::new,
  117. (jsonObj, entry) -> jsonObj.set(
  118. entry.getKey(),
  119. jsonObj.getInt(entry.getKey(), 0) + (Integer) entry.getValue()
  120. ),
  121. JSONObject::putAll
  122. );
  123. }
  124. // ==================== 大额转账 ====================
  125. public List<IntimacyDTO> bigTrans(String personName) {
  126. Intimacy config = getAndCheckIntimacy("大额转账");
  127. if (config == null) {
  128. return List.of();
  129. }
  130. int weight = config.getWeight();
  131. JSONObject scoreConfig = parseScores(config);
  132. Set<String> personCards = CaseDataCache.getPerCards(personName);
  133. if (CollUtil.isEmpty(personCards)) {
  134. return List.of();
  135. }
  136. List<TransRecord> records = transRecordMapper.selectListIntimacy(personCards, governConfService.getTransTableName());
  137. Map<String, List<TransRecord>> groupedByOther = records.stream()
  138. .collect(Collectors.groupingBy(record -> resolveOtherName(record.getOtherName(), record.getOtherCardNo())));
  139. List<IntimacyDTO> results = new ArrayList<>();
  140. groupedByOther.forEach((otherName, transRecords) -> {
  141. JSONObject detail = new JSONObject();
  142. int above50kCount = 0;
  143. int between10kAnd50kCount = 0;
  144. for (TransRecord record : transRecords) {
  145. if (record.getTransAmount().compareTo(new BigDecimal(50000)) >= 0) {
  146. above50kCount++;
  147. } else if (record.getTransAmount().compareTo(new BigDecimal(10000)) >= 0
  148. && record.getTransAmount().compareTo(new BigDecimal(50000)) < 0) {
  149. between10kAnd50kCount++;
  150. }
  151. }
  152. int score;
  153. if (above50kCount > 0) {
  154. score = weight * scoreConfig.getInt("大于等于5万");
  155. detail.set("大额交易次数", above50kCount + between10kAnd50kCount);
  156. } else if (between10kAnd50kCount > 0) {
  157. score = weight * scoreConfig.getInt("1万到5万");
  158. detail.set("大额交易次数", between10kAnd50kCount);
  159. } else {
  160. return;
  161. }
  162. results.add(buildIntimacyDTO(otherName, score, "大额转账", weight, detail));
  163. });
  164. return results;
  165. }
  166. // ==================== 特殊日期特殊转账 ====================
  167. public List<IntimacyDTO> specialDayTrans(String personName) {
  168. Intimacy config = getAndCheckIntimacy("特殊日期特殊转账");
  169. if (config == null) {
  170. return List.of();
  171. }
  172. int weight = config.getWeight();
  173. JSONObject scoreConfig = parseScores(config);
  174. Set<String> personCards = CaseDataCache.getPerCards(personName);
  175. if (CollUtil.isEmpty(personCards)) {
  176. return List.of();
  177. }
  178. List<SpecialDmDTO> records = intimacyMapper.specialDm(personName, personCards, governConfService.getTransTableName());
  179. Map<Integer, List<SpecialDmDTO>> countGroups = records.stream().collect(Collectors.groupingBy(SpecialDmDTO::getCount));
  180. List<List<SpecialDmDTO>> topGroups = countGroups.entrySet().stream()
  181. .sorted(Map.Entry.<Integer, List<SpecialDmDTO>>comparingByKey().reversed())
  182. .map(Map.Entry::getValue)
  183. .limit(20)
  184. .toList();
  185. return IntStream.range(0, topGroups.size())
  186. .mapToObj(i -> {
  187. int scoreTier = i < 5 ? 0 : i < 10 ? 1 : 2;
  188. int baseScore = scoreTier == 0 ? scoreConfig.getInt("交易次数top1~5")
  189. : scoreTier == 1 ? scoreConfig.getInt("交易次数top6~10")
  190. : scoreConfig.getInt("交易次数top11~top20");
  191. return topGroups.get(i).stream().map(record -> buildIntimacyDTO(
  192. record.getOtherName(),
  193. weight * baseScore,
  194. "特殊日期特殊转账",
  195. weight,
  196. buildDetail("特殊日期特殊转账", record.getCount())
  197. ));
  198. })
  199. .flatMap(Function.identity())
  200. .collect(Collectors.toList());
  201. }
  202. // ==================== 异卡同号 ====================
  203. public List<IntimacyDTO> samePhoneBinding(String personName) {
  204. Intimacy config = getAndCheckIntimacy("异卡同号");
  205. if (config == null) {
  206. return List.of();
  207. }
  208. int weight = config.getWeight();
  209. JSONObject scoreConfig = parseScores(config);
  210. PersonGroupRel groupRel = personGroupRelMapper.selectOne(Wrappers.lambdaQuery(PersonGroupRel.class)
  211. .eq(PersonGroupRel::getPersonName, personName));
  212. if (groupRel == null) {
  213. return List.of();
  214. }
  215. PersonGroup group = personGroupMapper.selectWithPersonsById(groupRel.getGroupId());
  216. String mainPersonName = null;
  217. List<PersonGroupRel> personList = group.getPersonList();
  218. Map<String, String> cardToPerson = new HashMap<>();
  219. for (PersonGroupRel rel : personList) {
  220. if (StrUtil.equals("主体", rel.getMainDesc())) {
  221. mainPersonName = rel.getPersonName();
  222. } else {
  223. Set<String> cards = CaseDataCache.getPerCards(rel.getPersonName());
  224. for (String card : cards) {
  225. cardToPerson.put(card, rel.getPersonName());
  226. }
  227. }
  228. }
  229. if (!StrUtil.equals(personName, mainPersonName)) {
  230. return List.of();
  231. }
  232. Set<String> mainPersonPhones = CaseDataCache.getPerPhones(mainPersonName);
  233. List<AcctOpenInfo> otherAccounts = acctOpenInfoMapper.selectList(new LambdaQueryWrapper<AcctOpenInfo>()
  234. .ne(AcctOpenInfo::getPersonName, mainPersonName)
  235. .isNotNull(AcctOpenInfo::getPersonName)
  236. .isNotNull(AcctOpenInfo::getPhone));
  237. List<AcctOpenInfo> matchedAccounts = otherAccounts.stream()
  238. .filter(account -> CollUtil.contains(mainPersonPhones, account.getPhone()))
  239. .filter(account -> cardToPerson.containsKey(account.getPersonCardNo()))
  240. .collect(Collectors.toList());
  241. List<IntimacyDTO> results = new ArrayList<>();
  242. if (CollUtil.isNotEmpty(matchedAccounts)) {
  243. Map<String, List<AcctOpenInfo>> groupedByName = matchedAccounts.stream()
  244. .filter(account -> !StrUtil.isBlank(account.getPersonName()))
  245. .collect(Collectors.groupingBy(AcctOpenInfo::getPersonName));
  246. groupedByName.forEach((ownerName, accounts) -> {
  247. IntimacyDTO result = new IntimacyDTO();
  248. result.setOtherName(ownerName);
  249. result.setIntimacyBehavior("异卡同号");
  250. List<String> matchedCards = accounts.stream().map(AcctOpenInfo::getPersonCardNo).distinct().collect(Collectors.toList());
  251. if (CollUtil.size(matchedCards) >= 2) {
  252. result.setIntimacy(weight * scoreConfig.getInt("绑定号码关联银行卡数量大于等于2张"));
  253. } else if (CollUtil.size(matchedCards) == 1) {
  254. result.setIntimacy(weight * scoreConfig.getInt("绑定号码关联银行卡数量等于1张"));
  255. } else {
  256. return;
  257. }
  258. JSONObject detail = new JSONObject();
  259. detail.set("异卡同号", CollUtil.size(matchedCards));
  260. result.setDetail(detail);
  261. results.add(result);
  262. });
  263. }
  264. return results;
  265. }
  266. // ==================== 频繁联系 ====================
  267. public List<IntimacyDTO> keepCall(String personName) {
  268. Intimacy config = intimacyMapper.selectOne(Wrappers.<Intimacy>lambdaQuery()
  269. .eq(Intimacy::getTags, "频繁联系").last("limit 1"));
  270. if (config == null) {
  271. return List.of();
  272. }
  273. JSONObject scoreConfig = parseScores(config);
  274. List<IntimacyDTO> results = new ArrayList<>();
  275. List<CallListDTO> callRecords = fetchCallList(personName);
  276. Map<Integer, List<CallListDTO>> countGroups = callRecords.stream()
  277. .collect(Collectors.groupingBy(CallListDTO::getCount));
  278. AtomicInteger index = new AtomicInteger(0);
  279. countGroups.forEach((count, records) -> {
  280. if (index.incrementAndGet() < 6) {
  281. records.forEach(record -> {
  282. int score = scoreConfig.getInt("联系次数top前5");
  283. results.add(buildIntimacyDTO(record.getOtherName(), config.getWeight() * score, "联系次数",
  284. config.getWeight(), buildDetail("联系次数", record.getCount())));
  285. });
  286. } else if (index.incrementAndGet() < 11) {
  287. records.forEach(record -> {
  288. int score = scoreConfig.getInt("联系次数top6~10");
  289. results.add(buildIntimacyDTO(record.getOtherName(), config.getWeight() * score, "联系次数",
  290. config.getWeight(), buildDetail("联系次数", record.getCount())));
  291. });
  292. } else if (index.incrementAndGet() < 21) {
  293. records.forEach(record -> {
  294. int score = scoreConfig.getInt("联系次数top11~20");
  295. results.add(buildIntimacyDTO(record.getOtherName(), config.getWeight() * score, "联系次数",
  296. config.getWeight(), buildDetail("联系次数", record.getCount())));
  297. });
  298. }
  299. });
  300. filterEmptyDetail(results);
  301. return results;
  302. }
  303. private List<CallListDTO> fetchCallList(String personName) {
  304. String callTableName = governConfService.getCallTableName();
  305. List<CallListDTO> records = intimacyMapper.selectCallAllListByName(personName, callTableName);
  306. return records.stream()
  307. .filter(a -> !ReUtil.isMatch("^(12|95|96|106)", a.getOtherPhone()))
  308. .collect(Collectors.toList());
  309. }
  310. // ==================== 特殊日期联系 ====================
  311. public List<IntimacyDTO> specialDayCall(String personName) {
  312. Intimacy config = getAndCheckIntimacy("特殊日期联系");
  313. if (config == null) {
  314. return List.of();
  315. }
  316. JSONObject scoreConfig = parseScores(config);
  317. Set<String> personPhones = CaseDataCache.getPerPhones(personName);
  318. if (CollUtil.isEmpty(personPhones)) {
  319. return List.of();
  320. }
  321. String callTableName = governConfService.getCallTableName();
  322. List<CallVoiceDTO> records = intimacyMapper.specialCallList(personPhones, callTableName);
  323. AtomicInteger index = new AtomicInteger(0);
  324. List<IntimacyDTO> results = records.stream()
  325. .sorted(Comparator.comparing(CallVoiceDTO::getVcCount, Comparator.reverseOrder()))
  326. .limit(20).map(record -> {
  327. int i = index.incrementAndGet();
  328. int weightedScore = calcSpecialDayScore(i, scoreConfig, config.getWeight());
  329. return buildIntimacyDTO(record.getOtherName(), weightedScore, "特殊日期通话",
  330. config.getWeight(), buildDetail("特殊日期通话次数", record.getVcCount()));
  331. }).collect(Collectors.toList());
  332. filterEmptyDetail(results);
  333. return results;
  334. }
  335. private int calcSpecialDayScore(int rank, JSONObject scoreConfig, int weight) {
  336. int score;
  337. if (rank < 6) {
  338. score = scoreConfig.getInt("联系次数top1~5", 0);
  339. } else if (rank < 11) {
  340. score = scoreConfig.getInt("联系次数top6~10", 0);
  341. } else if (rank < 21) {
  342. score = scoreConfig.getInt("联系次数top11~20", 0);
  343. } else {
  344. score = scoreConfig.getInt("联系次数在top20以后", 0);
  345. }
  346. return weight * score;
  347. }
  348. // ==================== 频繁交易 ====================
  349. public List<IntimacyDTO> keepTrans(String personName) {
  350. Set<String> personCards = CaseDataCache.getPerCards(personName);
  351. if (CollUtil.isEmpty(personCards)) {
  352. return List.of();
  353. }
  354. List<DealSummaryVO> summaries = intimacyMapper.dealCountSummary(personCards, governConfService.getTransTableName());
  355. if (CollUtil.isEmpty(summaries)) {
  356. return List.of();
  357. }
  358. Integer weight = null;
  359. Integer score1to5 = null;
  360. Integer score6to10 = null;
  361. Integer score11to20 = null;
  362. Integer score21plus = null;
  363. try {
  364. Intimacy config = intimacyMapper.selectOne(Wrappers.<Intimacy>lambdaQuery()
  365. .eq(Intimacy::getTags, "频繁交易").last("limit 1"));
  366. weight = config.getWeight();
  367. JSONObject scoreConfig = parseScores(config);
  368. score1to5 = scoreConfig.getInt("交易次数top1~5");
  369. score6to10 = scoreConfig.getInt("交易次数top6~10");
  370. score11to20 = scoreConfig.getInt("交易次数top11~top20");
  371. score21plus = scoreConfig.getInt("交易次数大于top20");
  372. } catch (Exception e) {
  373. log.error("亲密度 - 频繁交易配置信息获取", e);
  374. }
  375. int finalWeight = weight == null ? 15 : weight;
  376. int finalScore1to5 = score1to5 == null ? 100 : score1to5;
  377. int finalScore6to10 = score6to10 == null ? 60 : score6to10;
  378. int finalScore11to20 = score11to20 == null ? 40 : score11to20;
  379. int finalScore21plus = score21plus == null ? 0 : score21plus;
  380. Map<Integer, List<DealSummaryVO>> countGroups = summaries.stream()
  381. .collect(Collectors.groupingBy(DealSummaryVO::getDealCount, LinkedHashMap::new, Collectors.toList()));
  382. List<List<DealSummaryVO>> groupedSummaries = new ArrayList<>(countGroups.values());
  383. List<IntimacyDTO> results = new ArrayList<>();
  384. for (int i = 0; i < groupedSummaries.size(); i++) {
  385. List<DealSummaryVO> sameCountSummaries = groupedSummaries.get(i);
  386. for (DealSummaryVO summary : sameCountSummaries) {
  387. IntimacyDTO result = new IntimacyDTO();
  388. String name = summary.getOtherName();
  389. if (StrUtil.isNotEmpty(name)) {
  390. result.setOtherName(name);
  391. int score = i < 5 ? finalScore1to5
  392. : i < 10 ? finalScore6to10
  393. : i < 20 ? finalScore11to20 : finalScore21plus;
  394. result.setIntimacy(score * finalWeight);
  395. result.setIntimacyBehavior(buildBehaviorLabel("频繁交易", finalWeight));
  396. result.setDetail(buildDetail("交易次数", summary.getDealCount()));
  397. results.add(result);
  398. }
  399. }
  400. }
  401. return results.stream().filter(e -> e.getIntimacy() > 0).collect(Collectors.toList());
  402. }
  403. // ==================== 同交通工具出行 ====================
  404. public List<IntimacyDTO> travelTogether(String personName) {
  405. List<TravelDataDTO> serviceRecords = new ArrayList<>();
  406. Map<String, List<TravelDataDTO>> tripMatchMap = new HashMap<>();
  407. PersonBasicInfo personBasicInfo = personBasicInfoMapper.selectOne(Wrappers.<PersonBasicInfo>lambdaQuery()
  408. .eq(PersonBasicInfo::getPersonName, personName), false);
  409. if (personBasicInfo != null && StrUtil.isNotEmpty(personBasicInfo.getPersonCertNo())) {
  410. List<TravelDataDTO> flightTrainRecords = intimacyMapper.travelsTrainFlight(personName, personBasicInfo.getPersonCertNo());
  411. if (CollUtil.isNotEmpty(flightTrainRecords)) {
  412. List<TravelDataDTO> mappedRecords = flightTrainRecords.stream().map(record -> {
  413. TravelDataDTO dto = new TravelDataDTO();
  414. dto.setId(record.getId());
  415. dto.setPersonName(personName);
  416. dto.setCompanionName(record.getCompanionName());
  417. dto.setCount(1);
  418. return dto;
  419. }).toList();
  420. serviceRecords.addAll(mappedRecords);
  421. }
  422. }
  423. List<PersonBasicInfo> allPersons = personBasicInfoMapper.selectList(Wrappers.emptyWrapper());
  424. List<TravelDataDTO> travelRecords = trackTogetherTravelMapper.travels(null, null, personName);
  425. Map<String, List<TravelDataDTO>> timeTripGroup = travelRecords.stream().collect(Collectors.groupingBy(item ->
  426. String.format("%s_%s",
  427. StrUtil.isBlank(item.getTripDateTime()) ? "" : item.getTripDateTime().trim(),
  428. StrUtil.isBlank(item.getTripNo()) ? "" : item.getTripNo().trim())));
  429. for (PersonBasicInfo otherPerson : allPersons) {
  430. if (Objects.equals(otherPerson.getPersonName(), personName)) {
  431. continue;
  432. }
  433. List<TravelDataDTO> otherTravelRecords = trackTogetherTravelMapper.travels(null, null, otherPerson.getPersonName());
  434. if (CollUtil.isEmpty(otherTravelRecords)) {
  435. continue;
  436. }
  437. otherTravelRecords.forEach(record -> {
  438. List<TravelDataDTO> matchedRecords = timeTripGroup.get(String.format("%s_%s",
  439. StrUtil.isBlank(record.getTripDateTime()) ? "" : record.getTripDateTime().trim(),
  440. StrUtil.isBlank(record.getTripNo()) ? "" : record.getTripNo().trim()));
  441. if (CollUtil.isEmpty(matchedRecords)) {
  442. return;
  443. }
  444. AtomicInteger matchCount = new AtomicInteger();
  445. TravelDataDTO dto = new TravelDataDTO();
  446. matchCount.set(matchedRecords.size());
  447. dto.setId(record.getId());
  448. dto.setPersonName(personName);
  449. dto.setCompanionName(otherPerson.getPersonName());
  450. dto.setCount(matchCount.get());
  451. List<TravelDataDTO> matchedGroup = new ArrayList<>(matchedRecords);
  452. matchedGroup.add(record);
  453. tripMatchMap.put(String.format("%s_%s",
  454. StrUtil.isBlank(record.getTripDateTime()) ? "" : record.getTripDateTime().trim(),
  455. StrUtil.isBlank(record.getTripNo()) ? "" : record.getTripNo().trim()), matchedGroup);
  456. });
  457. }
  458. List<TravelTimelineDTO> timelines = buildTravelTimeline(tripMatchMap, serviceRecords);
  459. Integer weight = null;
  460. Integer score10Plus = null;
  461. Integer score5to9 = null;
  462. Integer score1to4 = null;
  463. try {
  464. Intimacy config = intimacyMapper.selectOne(Wrappers.<Intimacy>lambdaQuery()
  465. .eq(Intimacy::getTags, "同交通工具出行").last("limit 1"));
  466. weight = config.getWeight();
  467. JSONObject scoreConfig = parseScores(config);
  468. score10Plus = scoreConfig.getInt("同行次数大于等于10次");
  469. score5to9 = scoreConfig.getInt("同行次数5~9次");
  470. score1to4 = scoreConfig.getInt("同行次数1~4次");
  471. } catch (Exception e) {
  472. log.error("亲密度 - 同交通工具出行配置信息获取", e);
  473. }
  474. int finalScore10Plus = score10Plus == null ? 100 : score10Plus;
  475. int finalScore5to9 = score5to9 == null ? 80 : score5to9;
  476. int finalScore1to4 = score1to4 == null ? 70 : score1to4;
  477. int finalWeight = weight == null ? 5 : weight;
  478. List<IntimacyDTO> results = timelines.stream().map(timeline -> {
  479. IntimacyDTO result = new IntimacyDTO();
  480. if (timeline.getPersonName().equals(personName)) {
  481. result.setOtherName(timeline.getCompanionName());
  482. } else {
  483. result.setOtherName(timeline.getPersonName());
  484. }
  485. int score = timeline.getCount() >= 10 ? finalScore10Plus
  486. : timeline.getCount() >= 5 ? finalScore5to9
  487. : timeline.getCount() >= 1 ? finalScore1to4 : 0;
  488. result.setIntimacy(score * finalWeight);
  489. result.setIntimacyBehavior(buildBehaviorLabel("同交通工具出行", finalWeight));
  490. result.setDetail(buildDetail("同行次数", timeline.getCount()));
  491. return result;
  492. }).toList();
  493. return results.stream().filter(e -> e.getIntimacy() > 0).collect(Collectors.toList());
  494. }
  495. private List<TravelTimelineDTO> buildTravelTimeline(Map<String, List<TravelDataDTO>> tripMatchMap,
  496. List<TravelDataDTO> serviceRecords) {
  497. List<TravelTimelineDTO> results = new ArrayList<>();
  498. tripMatchMap.forEach((key, matchedGroup) -> {
  499. if (CollUtil.isEmpty(matchedGroup) || matchedGroup.size() < 2) {
  500. return;
  501. }
  502. TravelTimelineDTO timelineDTO = new TravelTimelineDTO();
  503. TravelDataDTO first = matchedGroup.get(0);
  504. TravelDataDTO second = matchedGroup.get(1);
  505. timelineDTO.setPersonName(first.getPersonName());
  506. timelineDTO.setPersonName(second.getPersonName());
  507. timelineDTO.setCount(1);
  508. timelineDTO.setIdList(matchedGroup.stream().map(TravelDataDTO::getId).collect(Collectors.toList()));
  509. results.add(timelineDTO);
  510. });
  511. Map<TravelPairDTO, List<TravelDataDTO>> pairGroup = serviceRecords.stream()
  512. .collect(Collectors.groupingBy(item -> TravelPairDTO.create(item.getPersonName(), item.getCompanionName())));
  513. pairGroup.forEach((pair, pairRecords) -> {
  514. if (CollUtil.isEmpty(pairRecords)) {
  515. return;
  516. }
  517. TravelTimelineDTO timelineDTO = new TravelTimelineDTO();
  518. timelineDTO.setPersonName(pair.getPersonName());
  519. timelineDTO.setCompanionName(pair.getCompanionName());
  520. timelineDTO.setCount(pairRecords.size());
  521. timelineDTO.setIdList(pairRecords.stream().map(TravelDataDTO::getId).collect(Collectors.toList()));
  522. results.add(timelineDTO);
  523. });
  524. return aggregateTravelTimeline(results);
  525. }
  526. private List<TravelTimelineDTO> aggregateTravelTimeline(List<TravelTimelineDTO> timelines) {
  527. List<TravelTimelineDTO> results = new ArrayList<>();
  528. Map<String, List<TravelTimelineDTO>> pairGroup = timelines.stream()
  529. .collect(Collectors.groupingBy(t -> t.getPersonName() + "-" + t.getCompanionName()));
  530. pairGroup.forEach((key, items) -> {
  531. List<String> split = StrUtil.split(key, "-");
  532. TravelTimelineDTO merged = new TravelTimelineDTO();
  533. merged.setPersonName(split.get(0));
  534. merged.setCompanionName(split.get(1));
  535. Integer totalCount = items.stream().map(TravelTimelineDTO::getCount).reduce(0, Integer::sum);
  536. List<String> ids = items.stream().flatMap(t -> t.getIdList().stream()).collect(Collectors.toList());
  537. merged.setCount(totalCount);
  538. merged.setIdList(ids);
  539. results.add(merged);
  540. });
  541. return results;
  542. }
  543. // ==================== 夜间通话 ====================
  544. public List<IntimacyDTO> nightCall(String personName) {
  545. Intimacy config = getAndCheckIntimacy("夜间通话");
  546. if (config == null) {
  547. return List.of();
  548. }
  549. JSONObject scoreConfig = parseScores(config);
  550. List<NightVoiceDTO> nightCallRecords = new ArrayList<>();
  551. buildNightCallRecords(personName, nightCallRecords);
  552. if (CollUtil.isEmpty(nightCallRecords)) {
  553. return List.of();
  554. }
  555. List<IntimacyDTO> results = new ArrayList<>();
  556. NightVoiceDTO nightCall = nightCallRecords.getFirst();
  557. List<NightVoiceTop5DTO> topRecords = nightCall.getTop5List();
  558. Map<Integer, List<NightVoiceTop5DTO>> countGroups = topRecords.stream()
  559. .collect(Collectors.groupingBy(NightVoiceTop5DTO::getCmnCount));
  560. AtomicInteger index = new AtomicInteger(0);
  561. countGroups.forEach((count, records) -> {
  562. if (index.incrementAndGet() < 6) {
  563. records.forEach(record -> {
  564. int score = scoreConfig.getInt("夜间通话次数top前5");
  565. results.add(buildIntimacyDTO(record.getOtherPhone(), config.getWeight() * score, "夜间通话次数",
  566. config.getWeight(), buildDetail("夜间通话次数", record.getCmnCount())));
  567. });
  568. } else if (index.incrementAndGet() < 11) {
  569. records.forEach(record -> {
  570. int score = scoreConfig.getInt("夜间通话次数top6~10");
  571. results.add(buildIntimacyDTO(record.getOtherPhone(), config.getWeight() * score, "夜间通话次数",
  572. config.getWeight(), buildDetail("夜间通话次数", record.getCmnCount())));
  573. });
  574. } else if (index.incrementAndGet() < 21) {
  575. records.forEach(record -> {
  576. int score = scoreConfig.getInt("夜间通话次数top11~20");
  577. results.add(buildIntimacyDTO(record.getOtherPhone(), config.getWeight() * score, "夜间通话次数",
  578. config.getWeight(), buildDetail("夜间通话次数", record.getCmnCount())));
  579. });
  580. }
  581. });
  582. filterEmptyDetail(results);
  583. return results;
  584. }
  585. // ==================== 工具方法 ====================
  586. private Intimacy getAndCheckIntimacy(String tags) {
  587. return intimacyMapper.selectOne(new LambdaQueryWrapper<Intimacy>()
  588. .eq(Intimacy::getTags, tags));
  589. }
  590. private JSONObject parseScores(Intimacy intimacy) {
  591. return JSONUtil.parseObj(intimacy.getScores());
  592. }
  593. private String resolveOtherName(String otherName, String fallback) {
  594. return StrUtil.emptyToDefault(otherName, fallback);
  595. }
  596. private String buildBehaviorLabel(String behavior, int weight) {
  597. return behavior + "-" + "(权重" + weight + "%)";
  598. }
  599. private JSONObject buildDetail(String key, Object value) {
  600. JSONObject detail = new JSONObject();
  601. detail.set(key, value);
  602. return detail;
  603. }
  604. private IntimacyDTO buildIntimacyDTO(String otherName, int intimacy, String behavior, int weight, JSONObject detail) {
  605. IntimacyDTO dto = new IntimacyDTO();
  606. dto.setOtherName(otherName);
  607. dto.setIntimacy(intimacy);
  608. dto.setIntimacyBehavior(buildBehaviorLabel(behavior, weight));
  609. dto.setDetail(detail);
  610. return dto;
  611. }
  612. private void filterEmptyDetail(List<IntimacyDTO> dtos) {
  613. for (IntimacyDTO dto : dtos) {
  614. JSONObject entries = new JSONObject();
  615. dto.getDetail().forEach((k, v) -> {
  616. if (!v.equals(0)) {
  617. entries.set(k, v);
  618. }
  619. });
  620. dto.setDetail(entries);
  621. }
  622. dtos.removeIf(a -> a.getDetail().isEmpty());
  623. }
  624. private void buildNightCallRecords(String personName, List<NightVoiceDTO> nightCallList) {
  625. String callTableName = governConfService.getCallTableName();
  626. List<NightVoiceTempDTO> rawRecords = intimacyMapper.selectAllDataByName(personName, callTableName);
  627. Map<String, List<NightVoiceTempDTO>> groupedByName = rawRecords.stream()
  628. .filter(a -> !ReUtil.isMatch("^(12|95|96|106)", a.getOtherName()))
  629. .collect(Collectors.groupingBy(NightVoiceTempDTO::getOtherName));
  630. for (Map.Entry<String, List<NightVoiceTempDTO>> nameEntry : groupedByName.entrySet()) {
  631. NightVoiceDTO nightCall = new NightVoiceDTO();
  632. List<NightVoiceTop5DTO> topRecords = new ArrayList<>();
  633. List<NightVoiceTempDTO> nameRecords = nameEntry.getValue();
  634. Map<String, List<NightVoiceTempDTO>> groupedByPhone = nameRecords.stream()
  635. .collect(Collectors.groupingBy(NightVoiceTempDTO::getOtherName));
  636. for (Map.Entry<String, List<NightVoiceTempDTO>> phoneEntry : groupedByPhone.entrySet()) {
  637. NightVoiceTop5DTO topRecord = new NightVoiceTop5DTO();
  638. String phone = phoneEntry.getKey();
  639. List<NightVoiceTempDTO> phoneRecords = phoneEntry.getValue();
  640. List<String> yearMonths = phoneRecords.stream().map(record -> {
  641. LocalDate callDate = record.getCallDate();
  642. return callDate.format(DateTimeFormatter.ofPattern("yyyy-MM"));
  643. }).distinct().sorted().toList();
  644. topRecord.setOtherPhone(phone);
  645. topRecord.setCmnCount(phoneRecords.size());
  646. topRecord.setTotalMonth(yearMonths.size());
  647. topRecords.add(topRecord);
  648. }
  649. topRecords = topRecords.stream()
  650. .filter(record -> record.getTotalMonth() >= (Long) 2L)
  651. .sorted(Comparator.comparing(NightVoiceTop5DTO::getCmnCount, Comparator.reverseOrder()))
  652. .limit(20)
  653. .collect(Collectors.toList());
  654. nightCall.setTop5List(topRecords);
  655. nightCallList.add(nightCall);
  656. }
  657. }
  658. }