From 94f3fbb8677153eecf8500479aedb11c9d55544d Mon Sep 17 00:00:00 2001 From: Timi Date: Fri, 11 Sep 2026 20:24:09 +0800 Subject: [PATCH] add customer age and disease chart --- pom.xml | 5 +++ .../GaoCustomerChartController.java | 13 ++++++ .../controller/GaoStoreChartController.java | 10 +++++ .../modules/gao/mapper/GaoCustomerMapper.java | 14 ++++++ .../gao/service/GaoCustomerChartService.java | 5 +++ .../gao/service/GaoCustomerService.java | 7 +++ .../gao/service/GaoStoreChartService.java | 4 ++ .../GaoCustomerChartServiceImplement.java | 45 +++++++++++++++++++ .../GaoCustomerServiceImplement.java | 7 +++ .../GaoStoreChartServiceImplement.java | 7 +++ .../gao/vo/GaoCustomerAgeStatView.java | 18 ++++++++ .../gao/vo/GaoCustomerDiseaseTextView.java | 19 ++++++++ .../gao/vo/GaoCustomerDiseaseWordReq.java | 17 +++++++ .../gao/vo/GaoCustomerDiseaseWordView.java | 19 ++++++++ .../timi-server/gao/GaoCustomerMapper.xml | 27 +++++++++++ 15 files changed, 217 insertions(+) create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerAgeStatView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseTextView.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordReq.java create mode 100644 src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordView.java diff --git a/pom.xml b/pom.xml index 3cc388d..e49dd2e 100644 --- a/pom.xml +++ b/pom.xml @@ -215,6 +215,11 @@ tinypinyin 2.0.3 + + com.hankcs + hanlp + portable-1.8.4 + com.imyeyu.font.icon timi-icon-font diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java index e738d60..7f54169 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoCustomerChartController.java @@ -5,6 +5,8 @@ import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.service.GaoCustomerChartService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; import com.imyeyu.java.bean.timi.TimiException; @@ -51,4 +53,15 @@ public class GaoCustomerChartController { req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId())); return service.eventRecordCalendar(req); } + + /// 查询客户病症词频 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/disease/word") + public List customerDiseaseWord(@RequestBody GaoCustomerDiseaseWordReq req) { + TimiException.required(req, "not found req"); + req.setStoreId(storeScopeService.resolveStoreId(req.getStoreId())); + return service.customerDiseaseWord(req); + } } diff --git a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java index ab17bd6..f8f32d9 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java +++ b/src/main/java/com/imyeyu/api/modules/gao/controller/GaoStoreChartController.java @@ -4,6 +4,7 @@ import com.imyeyu.api.modules.gao.annotation.RequireGaoPermission; import com.imyeyu.api.modules.gao.bean.GaoPermissionCode; import com.imyeyu.api.modules.gao.service.GaoStoreChartService; import com.imyeyu.api.modules.gao.service.GaoStoreScopeService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; @@ -54,6 +55,15 @@ public class GaoStoreChartController { return service.customerGender(storeScopeService.resolveStoreId(storeId)); } + /// 查询客户年龄分布 + @AOPLog + @RequestRateLimit + @RequireGaoPermission(GaoPermissionCode.STAT_READ) + @PostMapping("/customer/age") + public List customerAge(@RequestParam String storeId) { + return service.customerAge(storeScopeService.resolveStoreId(storeId)); + } + /// 查询登记事件每日统计,可同时支撑登记趋势和事件结构图表 @AOPLog @RequestRateLimit diff --git a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java index 118e40c..e0d1e35 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java +++ b/src/main/java/com/imyeyu/api/modules/gao/mapper/GaoCustomerMapper.java @@ -1,7 +1,9 @@ package com.imyeyu.api.modules.gao.mapper; import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseTextView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerPage; import com.imyeyu.spring.mapper.BaseMapper; @@ -38,6 +40,18 @@ public interface GaoCustomerMapper extends BaseMapper { /// @return 性别统计列表 List stateGender(@Param("storeId") String storeId); + /// 统计客户年龄分布 + /// + /// @param storeId 门店 ID + /// @return 客户年龄列表 + List stateAge(@Param("storeId") String storeId); + + /// 查询客户病症原始文本 + /// + /// @param storeId 门店 ID + /// @return 客户备注和病症 + List selectDiseaseTextByStoreId(@Param("storeId") String storeId); + /// 查询客户总数 /// /// @param page 查询条件 diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java index 252a50f..84dbb5b 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerChartService.java @@ -1,6 +1,8 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; @@ -17,4 +19,7 @@ public interface GaoCustomerChartService { /// 查询客户登记日历记录 List eventRecordCalendar(GaoCustomerChartReq req); + + /// 查询客户病症词频 + List customerDiseaseWord(GaoCustomerDiseaseWordReq req); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java index 85a77dc..aec9311 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoCustomerService.java @@ -1,6 +1,7 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.entity.GaoCustomer; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerPage; @@ -87,4 +88,10 @@ public interface GaoCustomerService extends BaseService { /// @param storeId 门店 ID /// @return 性别统计列表 List stateGender(String storeId); + + /// 统计客户年龄分布 + /// + /// @param storeId 门店 ID + /// @return 客户年龄列表 + List stateAge(String storeId); } diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java index cc2e6e1..1cc6f73 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/GaoStoreChartService.java @@ -1,6 +1,7 @@ package com.imyeyu.api.modules.gao.service; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; @@ -21,6 +22,9 @@ public interface GaoStoreChartService { /// 查询客户性别统计 List customerGender(String storeId); + /// 查询客户年龄分布 + List customerAge(String storeId); + /// 查询登记事件每日统计 List eventRecordDailyStat(GaoStoreChartReq req); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java index f1341d0..6f30c4f 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerChartServiceImplement.java @@ -1,15 +1,23 @@ package com.imyeyu.api.modules.gao.service.implement; +import com.hankcs.hanlp.seg.common.Term; +import com.hankcs.hanlp.tokenizer.StandardTokenizer; +import com.imyeyu.api.modules.gao.mapper.GaoCustomerMapper; import com.imyeyu.api.modules.gao.service.GaoCustomerChartService; import com.imyeyu.api.modules.gao.service.GaoEventRecordService; import com.imyeyu.api.modules.gao.vo.GaoCustomerChartReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseTextView; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordReq; +import com.imyeyu.api.modules.gao.vo.GaoCustomerDiseaseWordView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordCalendarView; import com.imyeyu.api.modules.gao.vo.GaoEventRecordDailyStatView; import com.imyeyu.java.bean.timi.TimiException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.HashMap; import java.util.List; +import java.util.Map; /// 客户报表服务实现 /// @@ -20,6 +28,7 @@ import java.util.List; public class GaoCustomerChartServiceImplement implements GaoCustomerChartService { private final GaoEventRecordService eventRecordService; + private final GaoCustomerMapper customerMapper; @Override public List eventRecordDailyStat(GaoCustomerChartReq req) { @@ -33,6 +42,42 @@ public class GaoCustomerChartServiceImplement implements GaoCustomerChartService return eventRecordService.calendarByCustomerRange(req); } + @Override + public List customerDiseaseWord(GaoCustomerDiseaseWordReq req) { + TimiException.required(req, "not found req"); + TimiException.required(req.getStoreId(), "not found req.storeId"); + + int topN = normalizeTopN(req.getTopN()); + Map wordCountMap = new HashMap<>(); + for (GaoCustomerDiseaseTextView customer : customerMapper.selectDiseaseTextByStoreId(req.getStoreId())) { + countNounWord(customer.getDisease(), wordCountMap); + countNounWord(customer.getRemark(), wordCountMap); + } + return wordCountMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue().reversed().thenComparing(Map.Entry.comparingByKey())) + .limit(topN) + .map(item -> new GaoCustomerDiseaseWordView(item.getKey(), item.getValue())) + .toList(); + } + + private void countNounWord(String text, Map wordCountMap) { + if (text == null || text.isBlank()) { + return; + } + for (Term term : StandardTokenizer.segment(text)) { + if (term.nature != null && term.nature.toString().startsWith("n")) { + wordCountMap.merge(term.word, 1L, Long::sum); + } + } + } + + private int normalizeTopN(Integer topN) { + if (topN == null) { + return 60; + } + return Math.clamp(topN, 1, 100); + } + private void checkRangeReq(GaoCustomerChartReq req) { TimiException.required(req, "not found req"); TimiException.required(req.getStoreId(), "not found req.storeId"); diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java index d070e4d..e60a960 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoCustomerServiceImplement.java @@ -14,6 +14,7 @@ import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoEventRecordService; import com.imyeyu.api.modules.gao.service.GaoPointAccountService; import com.imyeyu.api.modules.gao.util.CustomerXSSFBuilder; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerPage; @@ -431,6 +432,12 @@ public class GaoCustomerServiceImplement extends AbstractEntityService stateAge(String storeId) { + TimiException.required(storeId, "not found storeId"); + return mapper.stateAge(storeId); + } + private void changeInvitedCount(String customerId, long delta) { if (TimiJava.isEmpty(customerId) || delta == 0) { return; diff --git a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java index 4406f60..666a3d2 100644 --- a/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java +++ b/src/main/java/com/imyeyu/api/modules/gao/service/implement/GaoStoreChartServiceImplement.java @@ -3,6 +3,7 @@ package com.imyeyu.api.modules.gao.service.implement; import com.imyeyu.api.modules.gao.service.GaoCustomerService; import com.imyeyu.api.modules.gao.service.GaoEventRecordService; import com.imyeyu.api.modules.gao.service.GaoStoreChartService; +import com.imyeyu.api.modules.gao.vo.GaoCustomerAgeStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerDailyStateView; import com.imyeyu.api.modules.gao.vo.GaoCustomerGenderStatView; import com.imyeyu.api.modules.gao.vo.GaoCustomerTrendStateReq; @@ -43,6 +44,12 @@ public class GaoStoreChartServiceImplement implements GaoStoreChartService { return customerService.stateGender(storeId); } + @Override + public List customerAge(String storeId) { + TimiException.required(storeId, "not found storeId"); + return customerService.stateAge(storeId); + } + @Override public List eventRecordDailyStat(GaoStoreChartReq req) { checkRangeReq(req); diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerAgeStatView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerAgeStatView.java new file mode 100644 index 0000000..3d42c52 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerAgeStatView.java @@ -0,0 +1,18 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// +/// 客户年龄统计视图 +/// +/// @author Codex +/// @since 2026-09-11 +@Data +public class GaoCustomerAgeStatView { + + /// 客户年龄 + private Long age; + + /// 该年龄的客户数量 + private Long count; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseTextView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseTextView.java new file mode 100644 index 0000000..798fdf7 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseTextView.java @@ -0,0 +1,19 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// 客户病症原始文本 +/// +/// 只用于后端分词,不对外返回。 +/// +/// @author Codex +/// @since 2026-09-11 +@Data +public class GaoCustomerDiseaseTextView { + + /// 备注 + private String remark; + + /// 病症 + private String disease; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordReq.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordReq.java new file mode 100644 index 0000000..965ccf0 --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordReq.java @@ -0,0 +1,17 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.Data; + +/// 客户病症词频查询请求 +/// +/// @author Codex +/// @since 2026-09-11 +@Data +public class GaoCustomerDiseaseWordReq { + + /// 门店 ID + private String storeId; + + /// 返回词语数量 + private Integer topN; +} diff --git a/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordView.java b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordView.java new file mode 100644 index 0000000..7459aca --- /dev/null +++ b/src/main/java/com/imyeyu/api/modules/gao/vo/GaoCustomerDiseaseWordView.java @@ -0,0 +1,19 @@ +package com.imyeyu.api.modules.gao.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; + +/// 客户病症词频 +/// +/// @author Codex +/// @since 2026-09-11 +@Data +@AllArgsConstructor +public class GaoCustomerDiseaseWordView { + + /// 词语 + private String word; + + /// 词语出现次数 + private Long value; +} diff --git a/src/main/resources/mapper/timi-server/gao/GaoCustomerMapper.xml b/src/main/resources/mapper/timi-server/gao/GaoCustomerMapper.xml index 1b95a6d..67b701d 100644 --- a/src/main/resources/mapper/timi-server/gao/GaoCustomerMapper.xml +++ b/src/main/resources/mapper/timi-server/gao/GaoCustomerMapper.xml @@ -112,4 +112,31 @@ AND `deleted_at` IS NULL GROUP BY IFNULL(CAST(`gender` AS CHAR), 'UNKNOWN') + + + +