From 3afd2cba0cd5e46e04a0dc9f20449f1adbaa83e0 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Sat, 4 May 2024 22:24:23 +0800 Subject: [PATCH 01/35] =?UTF-8?q?=E5=AE=8C=E6=88=90todo=E9=83=A8=E5=88=86?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=8E=AF=E6=AF=94=E5=90=8C=E6=AF=94?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E7=9A=84=E8=AE=A1=E7=AE=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CrmStatisticsPerformanceServiceImpl.java | 119 +++++++++--------- 1 file changed, 57 insertions(+), 62 deletions(-) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java index c93951f81..b8fa94c7a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java @@ -55,16 +55,15 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform } /** - * 获得员工业绩数据 - * + * 获得员工业绩数据,并通过如下方法拿到对应的lastYearCount,lastMonthCount + * 比如说,构造2024 年的CrmStatisticsPerformanceRespVO,获得 2023-01 到 2024-12的月统计数据即可 + * 可以数据 group by 年-月,2023-01 到 2024-12的,然后聚合出 CrmStatisticsPerformanceRespVO * @param performanceReqVO 参数 * @param performanceFunction 员工业绩统计方法 * @return 员工业绩数据 */ - private List getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function> performanceFunction) { - - List performanceRespVOList; + private List getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, + Function> performanceFunction) { // 1. 获得用户编号数组 final List userIds = getUserIds(performanceReqVO); @@ -72,75 +71,71 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform return Collections.emptyList(); } performanceReqVO.setUserIds(userIds); + // 2. 获得业绩数据 - List performance = performanceFunction.apply(performanceReqVO); + List performanceList = performanceFunction.apply(performanceReqVO); // 获取查询的年份 String currentYear = LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"); + Map currentYearMap = new TreeMap<>();//查询当年的map数据 + Map lastYearMap = new TreeMap<>();//前一年的map数据 - // 构造查询当年和前一年,每年12个月的年月组合 - List allMonths = new ArrayList<>(); - for (int year = Integer.parseInt(currentYear)-1; year <= Integer.parseInt(currentYear); year++) { - for (int month = 1; month <= 12; month++) { - allMonths.add(String.format("%d%02d", year, month)); - } + for (int month = 1; month <= 12; month++) { + //根据数据库的月销售数据查询结果,构造查询当年的map数据 + String currentYearKey = String.format("%d%02d", Integer.parseInt(currentYear), month); + buildYearMapData(performanceList, currentYearMap, currentYearKey); + + //根据数据库的月销售数据查询结果,构造查询前一年的map数据 + String lastYearKey = String.format("%d%02d", Integer.parseInt(currentYear)-1, month); + buildYearMapData(performanceList, lastYearMap, lastYearKey); } - - List computedList = new ArrayList<>(); + //根据构造好的map数据,计算查询当年的环比和同比数据,并构造好返回的respVOList List respVOList = new ArrayList<>(); - - // 生成computedList基础数据 - // 构造完整的2*12个月的数据,如果某月数据缺失,需要补上0,一年12个月不能有缺失 - for (String month : allMonths) { - CrmStatisticsPerformanceRespVO foundData = performance.stream() - .filter(data -> data.getTime().equals(month)) - .findFirst() - .orElse(null); - - if (foundData != null) { - computedList.add(foundData); - } else { - CrmStatisticsPerformanceRespVO missingData = new CrmStatisticsPerformanceRespVO(); - missingData.setTime(month); - missingData.setCurrentMonthCount(BigDecimal.ZERO); - missingData.setLastMonthCount(BigDecimal.ZERO); - missingData.setLastYearCount(BigDecimal.ZERO); - computedList.add(missingData); + for (int key : currentYearMap.keySet()) { + BigDecimal lastYearCount = lastYearMap.get(key-100).getCurrentMonthCount(); + BigDecimal lastMonthCount; + if (key % 100 > 1) {//2-12月份的前一个月数据 + lastMonthCount = currentYearMap.get(key-1).getCurrentMonthCount(); + } else {//1月份的前一个月数据 + lastMonthCount = lastYearMap.get(key-89).getCurrentMonthCount(); } + + currentYearMap.get(key).setLastYearCount(lastYearCount); + currentYearMap.get(key).setLastMonthCount(lastMonthCount); + + respVOList.add(currentYearMap.get(key)); } - //根据查询年份和前一年的数据,计算查询年份的同比环比数据 - for (CrmStatisticsPerformanceRespVO currentData : computedList) { - String currentMonth = currentData.getTime(); - // 根据当年和前一年的月销售数据,计算currentYear的完整数据 - if (currentMonth.startsWith(currentYear)) { - // 计算 LastMonthCount - int currentIndex = computedList.indexOf(currentData); - if (currentIndex > 0) { - CrmStatisticsPerformanceRespVO lastMonthData = computedList.get(currentIndex - 1); - currentData.setLastMonthCount(lastMonthData.getCurrentMonthCount()); - } else { - currentData.setLastMonthCount(BigDecimal.ZERO); // 第一个月的 LastMonthCount 设为0 - } - - // 计算 LastYearCount - String lastYearMonth = String.valueOf(Integer.parseInt(currentMonth) - 100); - CrmStatisticsPerformanceRespVO lastYearData = computedList.stream() - .filter(data -> data.getTime().equals(lastYearMonth)) - .findFirst() - .orElse(null); - - if (lastYearData != null) { - currentData.setLastYearCount(lastYearData.getCurrentMonthCount()); - } else { - currentData.setLastYearCount(BigDecimal.ZERO); // 如果去年同月数据不存在,设为0 - } - respVOList.add(currentData);//给前端只需要返回查询当年的数据,不需要前一年数据 - } - } return respVOList; } + /** + * 根据mapKey,添加当年和前一年的月销售记录到对应的map结构中 + * @param performanceList 数据库中查询到的月销售记录 + * @param YearDataMap 将查询到的月销售记录put到对应的map中,如果月销售记录为null,置为0 + * @param mapKey 对应的mapKey + */ + private void buildYearMapData(List performanceList, + Map YearDataMap, + String mapKey) + { + CrmStatisticsPerformanceRespVO currentYearData = performanceList.stream() + .filter(data -> data.getTime().equals(mapKey)) + .findFirst() + .orElse(null); + + if(currentYearData != null) { + YearDataMap.put(Integer.parseInt(mapKey), currentYearData); + } else { + CrmStatisticsPerformanceRespVO defaultVO = new CrmStatisticsPerformanceRespVO(); + defaultVO.setTime(mapKey); + defaultVO.setCurrentMonthCount(BigDecimal.ZERO); + defaultVO.setLastMonthCount(BigDecimal.ZERO); + defaultVO.setLastYearCount(BigDecimal.ZERO); + YearDataMap.put(Integer.parseInt(mapKey), defaultVO); + } + } + /** * 获取用户编号数组。如果用户编号为空, 则获得部门下的用户编号数组,包括子部门的所有用户编号 * From 52c86cd290d22bea92dcddf9ea6569b5c6c9d056 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 7 May 2024 23:08:38 +0800 Subject: [PATCH 02/35] =?UTF-8?q?=E3=80=90=E4=BC=98=E5=8C=96=E3=80=91CRM?= =?UTF-8?q?=EF=BC=9A=E5=91=98=E5=B7=A5=E4=B8=9A=E7=BB=A9=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 4 +- .../CrmStatisticsPerformanceServiceImpl.java | 91 ++++++------------- yudao-server/pom.xml | 20 ++-- 3 files changed, 38 insertions(+), 77 deletions(-) diff --git a/pom.xml b/pom.xml index e770c0359..b1bc573e8 100644 --- a/pom.xml +++ b/pom.xml @@ -16,12 +16,12 @@ yudao-module-system yudao-module-infra - + yudao-module-bpm - + yudao-module-crm diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java index 6679a4500..77b14a268 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.crm.service.statistics; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO; @@ -11,16 +11,19 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; - import java.math.BigDecimal; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 员工业绩分析 Service 实现类 @@ -56,9 +59,11 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform } /** - * 获得员工业绩数据,并通过如下方法拿到对应的lastYearCount,lastMonthCount - * 比如说,构造2024 年的CrmStatisticsPerformanceRespVO,获得 2023-01 到 2024-12的月统计数据即可 - * 可以数据 group by 年-月,2023-01 到 2024-12的,然后聚合出 CrmStatisticsPerformanceRespVO + * 获得员工业绩数据 + * + * 1. 获得今年 + 去年的数据 + * 2. 遍历今年的月份,逐个拼接去年的月份数据 + * * @param performanceReqVO 参数 * @param performanceFunction 员工业绩统计方法 * @return 员工业绩数据 @@ -67,7 +72,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform Function> performanceFunction) { // 1. 获得用户编号数组 - final List userIds = getUserIds(performanceReqVO); + List userIds = getUserIds(performanceReqVO); if (CollUtil.isEmpty(userIds)) { return Collections.emptyList(); } @@ -75,66 +80,22 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform // 2. 获得业绩数据 List performanceList = performanceFunction.apply(performanceReqVO); + Map performanceMap = convertMap(performanceList, CrmStatisticsPerformanceRespVO::getTime, + CrmStatisticsPerformanceRespVO::getCurrentMonthCount); - // 获取查询的年份 - String currentYear = LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"); - Map currentYearMap = new TreeMap<>();//查询当年的map数据 - Map lastYearMap = new TreeMap<>();//前一年的map数据 - + // 3. 组装数据返回 + List result = new ArrayList<>(); + int year = DateUtil.thisYear(); for (int month = 1; month <= 12; month++) { - //根据数据库的月销售数据查询结果,构造查询当年的map数据 - String currentYearKey = String.format("%d%02d", Integer.parseInt(currentYear), month); - buildYearMapData(performanceList, currentYearMap, currentYearKey); - - //根据数据库的月销售数据查询结果,构造查询前一年的map数据 - String lastYearKey = String.format("%d%02d", Integer.parseInt(currentYear)-1, month); - buildYearMapData(performanceList, lastYearMap, lastYearKey); - } - //根据构造好的map数据,计算查询当年的环比和同比数据,并构造好返回的respVOList - List respVOList = new ArrayList<>(); - for (int key : currentYearMap.keySet()) { - BigDecimal lastYearCount = lastYearMap.get(key-100).getCurrentMonthCount(); - BigDecimal lastMonthCount; - if (key % 100 > 1) {//2-12月份的前一个月数据 - lastMonthCount = currentYearMap.get(key-1).getCurrentMonthCount(); - } else {//1月份的前一个月数据 - lastMonthCount = lastYearMap.get(key-89).getCurrentMonthCount(); - } - - currentYearMap.get(key).setLastYearCount(lastYearCount); - currentYearMap.get(key).setLastMonthCount(lastMonthCount); - - respVOList.add(currentYearMap.get(key)); - } - - return respVOList; - } - - /** - * 根据mapKey,添加当年和前一年的月销售记录到对应的map结构中 - * @param performanceList 数据库中查询到的月销售记录 - * @param YearDataMap 将查询到的月销售记录put到对应的map中,如果月销售记录为null,置为0 - * @param mapKey 对应的mapKey - */ - private void buildYearMapData(List performanceList, - Map YearDataMap, - String mapKey) - { - CrmStatisticsPerformanceRespVO currentYearData = performanceList.stream() - .filter(data -> data.getTime().equals(mapKey)) - .findFirst() - .orElse(null); - - if(currentYearData != null) { - YearDataMap.put(Integer.parseInt(mapKey), currentYearData); - } else { - CrmStatisticsPerformanceRespVO defaultVO = new CrmStatisticsPerformanceRespVO(); - defaultVO.setTime(mapKey); - defaultVO.setCurrentMonthCount(BigDecimal.ZERO); - defaultVO.setLastMonthCount(BigDecimal.ZERO); - defaultVO.setLastYearCount(BigDecimal.ZERO); - YearDataMap.put(Integer.parseInt(mapKey), defaultVO); + String currentMonth = String.format("%d%02d", year, month); + String lastMonth = month == 1 ? String.format("%d%02d", year - 1, 12) : String.format("%d%02d", year, month - 1); + String lastYear = String.format("%d%02d", year - 1, month); + result.add(new CrmStatisticsPerformanceRespVO().setTime(currentMonth) + .setCurrentMonthCount(performanceMap.getOrDefault(currentMonth, BigDecimal.ZERO)) + .setLastMonthCount(performanceMap.getOrDefault(lastMonth, BigDecimal.ZERO)) + .setLastYearCount(performanceMap.getOrDefault(lastYear, BigDecimal.ZERO))); } + return result; } /** diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index bc850b590..ade54c068 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -46,11 +46,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-bpm-biz + ${revision} + @@ -88,11 +88,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-crm-biz + ${revision} + From 99ab07de69258ee04dee4e84a888f56151d83476 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Sat, 11 May 2024 11:35:27 +0800 Subject: [PATCH 03/35] =?UTF-8?q?=E5=AE=8C=E6=88=90todo=20xml=E9=83=A8?= =?UTF-8?q?=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CrmStatisticsPerformanceServiceImpl.java | 88 ++++++------------- .../CrmStatisticsPerformanceMapper.xml | 48 +++++----- 2 files changed, 50 insertions(+), 86 deletions(-) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java index b8fa94c7a..07734e01d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java @@ -16,10 +16,12 @@ import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.*; import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 员工业绩分析 Service 实现类 @@ -55,85 +57,47 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform } /** - * 获得员工业绩数据,并通过如下方法拿到对应的lastYearCount,lastMonthCount - * 比如说,构造2024 年的CrmStatisticsPerformanceRespVO,获得 2023-01 到 2024-12的月统计数据即可 - * 可以数据 group by 年-月,2023-01 到 2024-12的,然后聚合出 CrmStatisticsPerformanceRespVO + * 获得员工业绩数据 + * + * 1. 获得今年 + 去年的数据 + * 2. 遍历今年的月份,逐个拼接去年的月份数据 + * * @param performanceReqVO 参数 * @param performanceFunction 员工业绩统计方法 * @return 员工业绩数据 */ private List getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, - Function> performanceFunction) { + Function> performanceFunction) { // 1. 获得用户编号数组 - final List userIds = getUserIds(performanceReqVO); + List userIds = getUserIds(performanceReqVO); if (CollUtil.isEmpty(userIds)) { return Collections.emptyList(); } performanceReqVO.setUserIds(userIds); // 2. 获得业绩数据 + int year = Integer.parseInt(LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"));//获取查询的年份 + LocalDateTime[] timesRange = performanceReqVO.getTimes();//以时间段形式去数据库查询,时间段为所查询年份和前一年,两年时间的数据,便于计算同比数据 + timesRange[0] = performanceReqVO.getTimes()[0].minusYears(1);//查询的起始时间往前推一年 + timesRange[1] = performanceReqVO.getTimes()[1];//查询的结束时间 + performanceReqVO.setTimes(timesRange); List performanceList = performanceFunction.apply(performanceReqVO); + Map performanceMap = convertMap(performanceList, CrmStatisticsPerformanceRespVO::getTime, + CrmStatisticsPerformanceRespVO::getCurrentMonthCount); - // 获取查询的年份 - String currentYear = LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"); - Map currentYearMap = new TreeMap<>();//查询当年的map数据 - Map lastYearMap = new TreeMap<>();//前一年的map数据 - + // 3. 组装数据返回 + List result = new ArrayList<>(); for (int month = 1; month <= 12; month++) { - //根据数据库的月销售数据查询结果,构造查询当年的map数据 - String currentYearKey = String.format("%d%02d", Integer.parseInt(currentYear), month); - buildYearMapData(performanceList, currentYearMap, currentYearKey); - - //根据数据库的月销售数据查询结果,构造查询前一年的map数据 - String lastYearKey = String.format("%d%02d", Integer.parseInt(currentYear)-1, month); - buildYearMapData(performanceList, lastYearMap, lastYearKey); - } - //根据构造好的map数据,计算查询当年的环比和同比数据,并构造好返回的respVOList - List respVOList = new ArrayList<>(); - for (int key : currentYearMap.keySet()) { - BigDecimal lastYearCount = lastYearMap.get(key-100).getCurrentMonthCount(); - BigDecimal lastMonthCount; - if (key % 100 > 1) {//2-12月份的前一个月数据 - lastMonthCount = currentYearMap.get(key-1).getCurrentMonthCount(); - } else {//1月份的前一个月数据 - lastMonthCount = lastYearMap.get(key-89).getCurrentMonthCount(); - } - - currentYearMap.get(key).setLastYearCount(lastYearCount); - currentYearMap.get(key).setLastMonthCount(lastMonthCount); - - respVOList.add(currentYearMap.get(key)); - } - - return respVOList; - } - - /** - * 根据mapKey,添加当年和前一年的月销售记录到对应的map结构中 - * @param performanceList 数据库中查询到的月销售记录 - * @param YearDataMap 将查询到的月销售记录put到对应的map中,如果月销售记录为null,置为0 - * @param mapKey 对应的mapKey - */ - private void buildYearMapData(List performanceList, - Map YearDataMap, - String mapKey) - { - CrmStatisticsPerformanceRespVO currentYearData = performanceList.stream() - .filter(data -> data.getTime().equals(mapKey)) - .findFirst() - .orElse(null); - - if(currentYearData != null) { - YearDataMap.put(Integer.parseInt(mapKey), currentYearData); - } else { - CrmStatisticsPerformanceRespVO defaultVO = new CrmStatisticsPerformanceRespVO(); - defaultVO.setTime(mapKey); - defaultVO.setCurrentMonthCount(BigDecimal.ZERO); - defaultVO.setLastMonthCount(BigDecimal.ZERO); - defaultVO.setLastYearCount(BigDecimal.ZERO); - YearDataMap.put(Integer.parseInt(mapKey), defaultVO); + String currentMonth = String.format("%d%02d", year, month); + String lastMonth = month == 1 ? String.format("%d%02d", year - 1, 12) : String.format("%d%02d", year, month - 1); + String lastYear = String.format("%d%02d", year - 1, month); + result.add(new CrmStatisticsPerformanceRespVO().setTime(currentMonth) + .setCurrentMonthCount(performanceMap.getOrDefault(currentMonth, BigDecimal.ZERO)) + .setLastMonthCount(performanceMap.getOrDefault(lastMonth, BigDecimal.ZERO)) + .setLastYearCount(performanceMap.getOrDefault(lastYear, BigDecimal.ZERO))); } + return result; } /** diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml index ee9ec4e44..f22b4fabe 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml @@ -5,51 +5,51 @@ From 73ef9211f92a105290c92cc49114a620fe553b17 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 11 May 2024 17:55:36 +0800 Subject: [PATCH 04/35] =?UTF-8?q?feat=EF=BC=9A=E6=96=B0=E5=A2=9E=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 18 ++++- .../app/social/vo/AppSocialWxQrcodeReqVO.java | 65 +++++++++++++++++++ .../system/api/social/SocialUserApi.java | 20 ++++-- .../api/social/dto/SocialWxQrcodeReqDTO.java | 64 ++++++++++++++++++ .../system/enums/ErrorCodeConstants.java | 6 +- .../system/api/social/SocialUserApiImpl.java | 6 ++ .../service/social/SocialClientService.java | 26 +++++--- .../social/SocialClientServiceImpl.java | 24 +++++-- .../service/social/SocialUserService.java | 9 +++ .../service/social/SocialUserServiceImpl.java | 6 ++ 10 files changed, 219 insertions(+), 25 deletions(-) create mode 100644 yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java create mode 100644 yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index fcdd2132d..304bb1690 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -7,18 +7,21 @@ import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBindReqVO; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserRespVO; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; +import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialWxQrcodeReqVO; import cn.iocoder.yudao.module.system.api.social.SocialUserApi; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Operation; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; +import java.util.Base64; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -60,4 +63,13 @@ public class AppSocialUserController { return success(BeanUtils.toBean(socialUser, AppSocialUserRespVO.class)); } + @PostMapping("/wxacode") + @Operation(summary = "获得微信小程序码") + @PreAuthenticated + public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { + byte[] wxQrcode = socialUserApi.getWxQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class).setUserId(getLoginUserId()) + .setUserType(UserTypeEnum.MEMBER.getValue()).setSocialType(reqVO.getType())); + return success(Base64.getEncoder().encodeToString(wxQrcode)); + } + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java new file mode 100644 index 000000000..fd352fb8e --- /dev/null +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -0,0 +1,65 @@ +package cn.iocoder.yudao.module.member.controller.app.social.vo; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "用户 APP - 获得获取小程序码 Request VO") +@Data +public class AppSocialWxQrcodeReqVO { + + private static String SCENE = "1011"; // 默认场景值 1011 扫描二维码 + private static String ENV_VERSION = "develop"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" + private static Integer WIDTH = 430; // 二维码宽度 + private static Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + private static Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 + private static Boolean IS_HYALINE = true; // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + + /** + * 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, 其它字符请自行编码为合法字符 + * (因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) + */ + @Schema(description = "场景值/页面参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") + private String scene = SCENE; + /** + * 页面路径 + */ + @Schema(description = "页面路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "pages/goods/index") + @NotEmpty(message = "页面路径不能为空") + private String path; + /** + * 要打开的小程序版本。默认是开发版。 + */ + @Schema(description = "小程序版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "develop") + private String envVersion = ENV_VERSION; + /** + * 二维码宽度 + */ + @Schema(description = "二维码宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "430") + private Integer width = WIDTH; + /** + * 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + */ + @Schema(description = "是/否自动配置线条颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean isAutoColor = AUTO_COLOR; + /** + * 默认true 检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错); + * 为 false 时允许小程序未发布或者 page 不存在,但 page 有数量上限(60000个)请勿滥用 + */ + @Schema(description = "是/否检查 page 是否存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean isCheckPath = CHECK_PATH; + /** + * 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + */ + @Schema(description = "是/否需要透明底色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + private Boolean isHyaline = IS_HYALINE; + + @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer type; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java index e24f8356d..3bde3eab9 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; - +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import jakarta.validation.Valid; /** @@ -32,8 +32,8 @@ public interface SocialUserApi { /** * 获得社交用户,基于 userId * - * @param userType 用户类型 - * @param userId 用户编号 + * @param userType 用户类型 + * @param userId 用户编号 * @param socialType 社交平台的类型 * @return 社交用户 */ @@ -44,12 +44,20 @@ public interface SocialUserApi { * * 在认证信息不正确的情况下,也会抛出 {@link ServiceException} 业务异常 * - * @param userType 用户类型 + * @param userType 用户类型 * @param socialType 社交平台的类型 - * @param code 授权码 - * @param state state + * @param code 授权码 + * @param state state * @return 社交用户 */ SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxQrcode(@Valid SocialWxQrcodeReqDTO reqVO); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java new file mode 100644 index 000000000..00b7ea488 --- /dev/null +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.system.api.social.dto; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class SocialWxQrcodeReqDTO { + + /** + * 用户编号 + */ + @NotNull(message = "用户编号不能为空") + private Long userId; + /** + * 用户类型 + */ + @InEnum(UserTypeEnum.class) + @NotNull(message = "用户类型不能为空") + private Integer userType; + + /** + * 社交平台的类型 + */ + @InEnum(SocialTypeEnum.class) + @NotNull(message = "社交平台的类型不能为空") + private Integer socialType; + + /** + * 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, 其它字符请自行编码为合法字符 + * (因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) + */ + private String scene; + /** + * 页面路径 + */ + @NotEmpty(message = "页面路径不能为空") + private String path; + /** + * 要打开的小程序版本。默认是开发版。 + */ + private String envVersion; + /** + * 二维码宽度 + */ + private Integer width; + /** + * 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + */ + private Boolean isAutoColor; + /** + * 默认true 检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错); + * 为 false 时允许小程序未发布或者 page 不存在,但 page 有数量上限(60000个)请勿滥用 + */ + private Boolean isCheckPath; + /** + * 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + */ + private Boolean isHyaline; + +} diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java index 1b4c313c8..412ac413e 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/ErrorCodeConstants.java @@ -120,8 +120,10 @@ public interface ErrorCodeConstants { ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_001, "社交授权失败,找不到对应的用户"); ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_200, "获得手机号失败"); - ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_201, "社交客户端不存在"); - ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_202, "社交客户端已存在配置"); + ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR = new ErrorCode(1_002_018_201, "获得小程序码失败"); + ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_202, "社交客户端不存在"); + ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_203, "社交客户端已存在配置"); + // ========== OAuth2 客户端 1-002-020-000 ========= ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, "OAuth2 客户端不存在"); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java index eb8331618..c5e3e490a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.social; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserUnbindReqDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.service.social.SocialUserService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -42,4 +43,9 @@ public class SocialUserApiImpl implements SocialUserApi { return socialUserService.getSocialUserByCode(userType, socialType, code, state); } + @Override + public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { + return socialUserService.getWxQrcode(reqVO); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java index f41e8d371..f7d8cd0e2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java @@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.system.service.social; import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import com.xingyuv.jushauth.model.AuthUser; -import me.chanjar.weixin.common.bean.WxJsapiSignature; - import jakarta.validation.Valid; +import me.chanjar.weixin.common.bean.WxJsapiSignature; /** * 社交应用 Service 接口 @@ -21,8 +21,8 @@ public interface SocialClientService { /** * 获得社交平台的授权 URL * - * @param socialType 社交平台的类型 {@link SocialTypeEnum} - * @param userType 用户类型 + * @param socialType 社交平台的类型 {@link SocialTypeEnum} + * @param userType 用户类型 * @param redirectUri 重定向 URL * @return 社交平台的授权 URL */ @@ -32,9 +32,9 @@ public interface SocialClientService { * 请求社交平台,获得授权的用户 * * @param socialType 社交平台的类型 - * @param userType 用户类型 - * @param code 授权码 - * @param state 授权 state + * @param userType 用户类型 + * @param code 授权码 + * @param state 授权 state * @return 授权的用户 */ AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state); @@ -45,7 +45,7 @@ public interface SocialClientService { * 创建微信公众号的 JS SDK 初始化所需的签名 * * @param userType 用户类型 - * @param url 访问的 URL 地址 + * @param url 访问的 URL 地址 * @return 签名 */ WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url); @@ -55,12 +55,20 @@ public interface SocialClientService { /** * 获得微信小程序的手机信息 * - * @param userType 用户类型 + * @param userType 用户类型 * @param phoneCode 手机授权码 * @return 手机信息 */ WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO); + // =================== 客户端管理 =================== /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 84da0e5e1..978f6161c 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialClientDO; @@ -139,7 +140,7 @@ public class SocialClientServiceImpl implements SocialClientService { * 构建 AuthRequest 对象,支持多租户配置 * * @param socialType 社交类型 - * @param userType 用户类型 + * @param userType 用户类型 * @return AuthRequest 对象 */ @VisibleForTesting @@ -196,7 +197,7 @@ public class SocialClientServiceImpl implements SocialClientService { /** * 创建 clientId + clientSecret 对应的 WxMpService 对象 * - * @param clientId 微信公众号 appId + * @param clientId 微信公众号 appId * @param clientSecret 微信公众号 secret * @return WxMpService 对象 */ @@ -227,6 +228,19 @@ public class SocialClientServiceImpl implements SocialClientService { } } + @Override + public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { + WxMaService service = getWxMaService(reqVO.getUserType()); + try { + return service.getQrcodeService().createWxaCodeUnlimitBytes(reqVO.getScene(), reqVO.getPath(), + reqVO.getIsCheckPath(), reqVO.getEnvVersion(), reqVO.getWidth(), reqVO.getIsAutoColor(), + null, reqVO.getIsHyaline()); + } catch (WxErrorException e) { + log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e); + throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); + } + } + /** * 获得 clientId + clientSecret 对应的 WxMpService 对象 * @@ -248,7 +262,7 @@ public class SocialClientServiceImpl implements SocialClientService { /** * 创建 clientId + clientSecret 对应的 WxMaService 对象 * - * @param clientId 微信小程序 appId + * @param clientId 微信小程序 appId * @param clientSecret 微信小程序 secret * @return WxMaService 对象 */ @@ -310,8 +324,8 @@ public class SocialClientServiceImpl implements SocialClientService { * * 原因是,不同端(userType)选择某个社交登录(socialType)时,需要通过 {@link #buildAuthRequest(Integer, Integer)} 构建对应的请求 * - * @param id 编号 - * @param userType 用户类型 + * @param id 编号 + * @param userType 用户类型 * @param socialType 社交类型 */ private void validateSocialClientUnique(Long id, Integer userType, Integer socialType) { diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java index 739ff2d94..40ba45542 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; @@ -86,4 +87,12 @@ public interface SocialUserService { */ PageResult getSocialUserPage(SocialUserPageReqVO pageReqVO); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index 7f1b271ad..5d236e938 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserBindDO; import cn.iocoder.yudao.module.system.dal.dataobject.social.SocialUserDO; @@ -170,4 +171,9 @@ public class SocialUserServiceImpl implements SocialUserService { return socialUserMapper.selectPage(pageReqVO); } + @Override + public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { + return socialClientService.getWxQrcode(reqVO); + } + } From 0641f793a374b8056c97cc226156776858cc3608 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 11 May 2024 21:51:59 +0800 Subject: [PATCH 05/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=85=A8=E5=B1=80=EF=BC=9A=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 3 +- .../app/social/vo/AppSocialWxQrcodeReqVO.java | 39 +++++++------------ .../api/social/dto/SocialWxQrcodeReqDTO.java | 28 +++++++++---- .../system/api/social/SocialUserApiImpl.java | 1 + 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 304bb1690..f6a351a33 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -63,9 +63,10 @@ public class AppSocialUserController { return success(BeanUtils.toBean(socialUser, AppSocialUserRespVO.class)); } + // TODO @puhui999:是不是 url 叫 wxa-qrcode?然后相关的方法,都做下调整哈;因为是微信小程序的二维码 @PostMapping("/wxacode") @Operation(summary = "获得微信小程序码") - @PreAuthenticated + @PreAuthenticated // TODO @puhui999:可能不需要登录 public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { byte[] wxQrcode = socialUserApi.getWxQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class).setUserId(getLoginUserId()) .setUserType(UserTypeEnum.MEMBER.getValue()).setSocialType(reqVO.getType())); diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java index fd352fb8e..15d9e20a1 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -7,53 +7,44 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +// TODO @芋艿:需要精简下参数; @Schema(description = "用户 APP - 获得获取小程序码 Request VO") @Data public class AppSocialWxQrcodeReqVO { + // TODO @puhui999:这个后续不用前端传递,应该是后端搞的 private static String SCENE = "1011"; // 默认场景值 1011 扫描二维码 + // TODO @puhui999:这个默认是不是 release 哈? private static String ENV_VERSION = "develop"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" + // TODO @puhui999:这个去掉;因为本身就是 430 啦; private static Integer WIDTH = 430; // 二维码宽度 + // TODO @puhui999:这个去掉;因为本身就是 true 啦; private static Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + // TODO @puhui999:这个去掉;因为本身就是 true 啦; private static Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 + // TODO @puhui999:这个去掉;因为本身就是 true 啦; private static Boolean IS_HYALINE = true; // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 - /** - * 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, 其它字符请自行编码为合法字符 - * (因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) - */ - @Schema(description = "场景值/页面参数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") + @Schema(description = "场景值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") private String scene = SCENE; - /** - * 页面路径 - */ + @Schema(description = "页面路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "pages/goods/index") @NotEmpty(message = "页面路径不能为空") private String path; - /** - * 要打开的小程序版本。默认是开发版。 - */ + + // TODO @puhui999:这个应该不传递哈 @Schema(description = "小程序版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "develop") private String envVersion = ENV_VERSION; - /** - * 二维码宽度 - */ + @Schema(description = "二维码宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "430") private Integer width = WIDTH; - /** - * 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 - */ + @Schema(description = "是/否自动配置线条颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean isAutoColor = AUTO_COLOR; - /** - * 默认true 检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错); - * 为 false 时允许小程序未发布或者 page 不存在,但 page 有数量上限(60000个)请勿滥用 - */ + @Schema(description = "是/否检查 page 是否存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean isCheckPath = CHECK_PATH; - /** - * 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 - */ + @Schema(description = "是/否需要透明底色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean isHyaline = IS_HYALINE; diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java index 00b7ea488..d5e7f194f 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -7,9 +7,17 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +/** + * 获取小程序码 Request DTO + * + * @see 获取不限制的小程序码 + * + * @author HUIHUI + */ @Data public class SocialWxQrcodeReqDTO { + // TODO @puhui999:userId、userType 应该后续要搞成抽象参数;说白了,就是 path 的参数; socialType 应该去掉,因为就是微信的; /** * 用户编号 */ @@ -30,9 +38,9 @@ public class SocialWxQrcodeReqDTO { private Integer socialType; /** - * 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, 其它字符请自行编码为合法字符 - * (因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) + * 场景 */ + @NotEmpty(message = "场景不能为空") private String scene; /** * 页面路径 @@ -40,24 +48,30 @@ public class SocialWxQrcodeReqDTO { @NotEmpty(message = "页面路径不能为空") private String path; /** - * 要打开的小程序版本。默认是开发版。 + * 要打开的小程序版本 */ private String envVersion; /** * 二维码宽度 */ private Integer width; + + // TODO @puhui999:autoColor + /** - * 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + * 是否需要透明底色 */ private Boolean isAutoColor; + + // TODO @puhui999: checkPath /** - * 默认true 检查 page 是否存在,为 true 时 page 必须是已经发布的小程序存在的页面(否则报错); - * 为 false 时允许小程序未发布或者 page 不存在,但 page 有数量上限(60000个)请勿滥用 + * 是否检查 page 是否存在 */ private Boolean isCheckPath; + + // TODO @puhui999: hyaline /** - * 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + * 是否需要透明底色 */ private Boolean isHyaline; diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java index c5e3e490a..ad56c9ee5 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java @@ -43,6 +43,7 @@ public class SocialUserApiImpl implements SocialUserApi { return socialUserService.getSocialUserByCode(userType, socialType, code, state); } + // TODO @puhui999:貌似搞到 SocialClientApiImpl 更合适,二维码和用户关系不大;这样 socialUserService 也不用绕一次了 @Override public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { return socialUserService.getWxQrcode(reqVO); From e1a5302b251a603fd6440632cec176df815248a0 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 11 May 2024 22:05:41 +0800 Subject: [PATCH 06/35] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E5=95=86=E5=9F=8E=EF=BC=9A=E7=A7=92=E6=9D=80=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E6=AE=B5=E9=85=8D=E7=BD=AE=E7=9A=84=20URL=20`simple-list`=20?= =?UTF-8?q?=E4=B8=8D=E6=AD=A3=E7=A1=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/seckill/SeckillConfigController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java index 21b9a1400..093003bac 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/seckill/SeckillConfigController.java @@ -78,9 +78,9 @@ public class SeckillConfigController { return success(SeckillConfigConvert.INSTANCE.convertList(list)); } - @GetMapping("/list-all-simple") + @GetMapping("/simple-list") @Operation(summary = "获得所有开启状态的秒杀时段精简列表", description = "主要用于前端的下拉选项") - public CommonResult> getListAllSimple() { + public CommonResult> getSeckillConfigSimpleList() { List list = seckillConfigService.getSeckillConfigListByStatus( CommonStatusEnum.ENABLE.getStatus()); return success(SeckillConfigConvert.INSTANCE.convertList1(list)); From 4a0ad205ae13ba7ca104dc5b75723a4d6b1a37f9 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 11 May 2024 22:30:37 +0800 Subject: [PATCH 07/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91CRM=EF=BC=9A=E4=BC=98=E5=8C=96=E5=91=98?= =?UTF-8?q?=E5=B7=A5=E4=B8=9A=E7=BB=A9=E7=BB=9F=E8=AE=A1=E7=9A=84=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CrmStatisticsPerformanceReqVO.java | 1 - .../CrmStatisticsPerformanceServiceImpl.java | 17 +++++++---------- .../CrmStatisticsPerformanceMapper.xml | 6 +++--- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java index de76acbb3..bfb5c840f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/performance/CrmStatisticsPerformanceReqVO.java @@ -33,7 +33,6 @@ public class CrmStatisticsPerformanceReqVO { @Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2") private List userIds; - // TODO @scholar:应该传递的是 int year;年份 @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @NotEmpty(message = "时间范围不能为空") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java index 07734e01d..1e7e3bbb2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.crm.service.statistics; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO; @@ -10,14 +9,16 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; - import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @@ -40,7 +41,6 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform @Resource private DeptApi deptApi; - @Override public List getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) { return getPerformance(performanceReqVO, performanceMapper::selectContractCountPerformance); @@ -77,11 +77,8 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform performanceReqVO.setUserIds(userIds); // 2. 获得业绩数据 - int year = Integer.parseInt(LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy"));//获取查询的年份 - LocalDateTime[] timesRange = performanceReqVO.getTimes();//以时间段形式去数据库查询,时间段为所查询年份和前一年,两年时间的数据,便于计算同比数据 - timesRange[0] = performanceReqVO.getTimes()[0].minusYears(1);//查询的起始时间往前推一年 - timesRange[1] = performanceReqVO.getTimes()[1];//查询的结束时间 - performanceReqVO.setTimes(timesRange); + int year = performanceReqVO.getTimes()[0].getYear(); // 获取查询的年份 + performanceReqVO.getTimes()[0] = performanceReqVO.getTimes()[0].minusYears(1); List performanceList = performanceFunction.apply(performanceReqVO); Map performanceMap = convertMap(performanceList, CrmStatisticsPerformanceRespVO::getTime, CrmStatisticsPerformanceRespVO::getCurrentMonthCount); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml index f22b4fabe..81962cdae 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsPerformanceMapper.xml @@ -7,7 +7,7 @@ SELECT DATE_FORMAT(order_date, '%Y%m') AS time, COUNT(1) AS currentMonthCount - FROM crm_contract + FROM crm_contract WHERE deleted = 0 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND owner_user_id in @@ -24,7 +24,7 @@ SELECT DATE_FORMAT(order_date, '%Y%m') AS time, IFNULL(SUM(total_price), 0) AS currentMonthCount - FROM crm_contract + FROM crm_contract WHERE deleted = 0 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND owner_user_id in @@ -41,7 +41,7 @@ SELECT DATE_FORMAT(return_time, '%Y%m') AS time, IFNULL(SUM(price), 0) AS currentMonthCount - FROM crm_receivable + FROM crm_receivable WHERE deleted = 0 AND audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND owner_user_id in From 4c05bd340ef2ec1b42ebfac427fc05df71f32634 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 14 May 2024 23:28:44 +0800 Subject: [PATCH 08/35] =?UTF-8?q?fix:=20=E8=8E=B7=E5=BE=97=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81(base64=20image)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 15 ++++++++------- .../app/social/vo/AppSocialWxQrcodeReqVO.java | 9 +++++++-- .../module/system/api/social/SocialClientApi.java | 10 ++++++++++ .../module/system/api/social/SocialUserApi.java | 8 -------- .../system/api/social/SocialClientApiImpl.java | 6 ++++++ .../system/api/social/SocialUserApiImpl.java | 6 ------ .../service/social/SocialClientService.java | 2 +- .../service/social/SocialClientServiceImpl.java | 2 +- .../system/service/social/SocialUserService.java | 8 -------- .../service/social/SocialUserServiceImpl.java | 5 ----- 10 files changed, 33 insertions(+), 38 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index f6a351a33..a07e6324f 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserBind import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserRespVO; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO; import cn.iocoder.yudao.module.member.controller.app.social.vo.AppSocialWxQrcodeReqVO; +import cn.iocoder.yudao.module.system.api.social.SocialClientApi; import cn.iocoder.yudao.module.system.api.social.SocialUserApi; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; @@ -34,6 +35,8 @@ public class AppSocialUserController { @Resource private SocialUserApi socialUserApi; + @Resource + private SocialClientApi socialClientApi; @PostMapping("/bind") @Operation(summary = "社交绑定,使用 code 授权码") @@ -63,14 +66,12 @@ public class AppSocialUserController { return success(BeanUtils.toBean(socialUser, AppSocialUserRespVO.class)); } - // TODO @puhui999:是不是 url 叫 wxa-qrcode?然后相关的方法,都做下调整哈;因为是微信小程序的二维码 - @PostMapping("/wxacode") - @Operation(summary = "获得微信小程序码") - @PreAuthenticated // TODO @puhui999:可能不需要登录 + @PostMapping("/wxa-qrcode") + @Operation(summary = "获得微信小程序码(base64 image)") public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { - byte[] wxQrcode = socialUserApi.getWxQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class).setUserId(getLoginUserId()) - .setUserType(UserTypeEnum.MEMBER.getValue()).setSocialType(reqVO.getType())); - return success(Base64.getEncoder().encodeToString(wxQrcode)); + byte[] wxQrcode = socialClientApi.getWxaQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class) + .setUserId(getLoginUserId()).setUserType(UserTypeEnum.MEMBER.getValue()).setSocialType(reqVO.getType())); + return success("data:image/png;base64," + Base64.getEncoder().encodeToString(wxQrcode)); } } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java index 15d9e20a1..6465b2384 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -12,8 +12,9 @@ import lombok.Data; @Data public class AppSocialWxQrcodeReqVO { - // TODO @puhui999:这个后续不用前端传递,应该是后端搞的 - private static String SCENE = "1011"; // 默认场景值 1011 扫描二维码 + // TODO @puhui999:这个后续不用前端传递,应该是后端搞的。 + // 页面路径不能携带参数(参数请放在scene字段里) + private static String SCENE = ""; // 默认场景值 1011 扫描二维码 // TODO @puhui999:这个默认是不是 release 哈? private static String ENV_VERSION = "develop"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" // TODO @puhui999:这个去掉;因为本身就是 430 啦; @@ -28,6 +29,10 @@ public class AppSocialWxQrcodeReqVO { @Schema(description = "场景值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") private String scene = SCENE; + /** + * 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里), + * 如果不填写这个字段,默认跳主页面。scancode_time为系统保留参数,不允许配置 + */ @Schema(description = "页面路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "pages/goods/index") @NotEmpty(message = "页面路径不能为空") private String path; diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java index 7fdb35a32..cdc609b3f 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApi.java @@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.system.api.social; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; +import jakarta.validation.Valid; /** * 社交应用的 API 接口 @@ -39,4 +41,12 @@ public interface SocialClientApi { */ SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode); + /** + * 获得小程序二维码 + * + * @param reqVO 请求信息 + * @return 小程序二维码 + */ + byte[] getWxaQrcode(@Valid SocialWxQrcodeReqDTO reqVO); + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java index 3bde3eab9..6102c4eca 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApi.java @@ -52,12 +52,4 @@ public interface SocialUserApi { */ SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state); - /** - * 获得小程序二维码 - * - * @param reqVO 请求信息 - * @return 小程序二维码 - */ - byte[] getWxQrcode(@Valid SocialWxQrcodeReqDTO reqVO); - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java index 2e82ad492..2a7b69cfe 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialClientApiImpl.java @@ -4,6 +4,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO; +import cn.iocoder.yudao.module.system.api.social.dto.SocialWxQrcodeReqDTO; import cn.iocoder.yudao.module.system.service.social.SocialClientService; import me.chanjar.weixin.common.bean.WxJsapiSignature; import org.springframework.stereotype.Service; @@ -40,4 +41,9 @@ public class SocialClientApiImpl implements SocialClientApi { return BeanUtils.toBean(info, SocialWxPhoneNumberInfoRespDTO.class); } + @Override + public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { + return socialClientService.getWxaQrcode(reqVO); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java index ad56c9ee5..a8c30a0f8 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/social/SocialUserApiImpl.java @@ -43,10 +43,4 @@ public class SocialUserApiImpl implements SocialUserApi { return socialUserService.getSocialUserByCode(userType, socialType, code, state); } - // TODO @puhui999:貌似搞到 SocialClientApiImpl 更合适,二维码和用户关系不大;这样 socialUserService 也不用绕一次了 - @Override - public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { - return socialUserService.getWxQrcode(reqVO); - } - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java index f7d8cd0e2..7757d35d1 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientService.java @@ -67,7 +67,7 @@ public interface SocialClientService { * @param reqVO 请求信息 * @return 小程序二维码 */ - byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO); + byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO); // =================== 客户端管理 =================== diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 978f6161c..aeff40cce 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -229,7 +229,7 @@ public class SocialClientServiceImpl implements SocialClientService { } @Override - public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { + public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { WxMaService service = getWxMaService(reqVO.getUserType()); try { return service.getQrcodeService().createWxaCodeUnlimitBytes(reqVO.getScene(), reqVO.getPath(), diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java index 40ba45542..73d57687e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserService.java @@ -87,12 +87,4 @@ public interface SocialUserService { */ PageResult getSocialUserPage(SocialUserPageReqVO pageReqVO); - /** - * 获得小程序二维码 - * - * @param reqVO 请求信息 - * @return 小程序二维码 - */ - byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO); - } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java index 5d236e938..dedab0db3 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialUserServiceImpl.java @@ -171,9 +171,4 @@ public class SocialUserServiceImpl implements SocialUserService { return socialUserMapper.selectPage(pageReqVO); } - @Override - public byte[] getWxQrcode(SocialWxQrcodeReqDTO reqVO) { - return socialClientService.getWxQrcode(reqVO); - } - } From 52b972907a2d47e689d91cb16ac35f8528953ef3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 14 May 2024 23:29:47 +0800 Subject: [PATCH 09/35] =?UTF-8?q?member:=20=E5=A2=9E=E5=8A=A0=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BC=96=E5=8F=B7=E8=BF=94=E5=9B=9E=EF=BC=8C=E5=88=86?= =?UTF-8?q?=E4=BA=AB=E6=B5=B7=E6=8A=A5=E6=9C=89=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/app/user/vo/AppMemberUserInfoRespVO.java | 3 +++ .../module/member/convert/user/MemberUserConvert.java | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java index fa05e16d0..72e4fa3fa 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/user/vo/AppMemberUserInfoRespVO.java @@ -11,6 +11,9 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class AppMemberUserInfoRespVO { + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long id; + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") private String nickname; diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java index aae9a7601..eaa5ab50e 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/convert/user/MemberUserConvert.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.member.dal.dataobject.tag.MemberTagDO; import cn.iocoder.yudao.module.member.dal.dataobject.user.MemberUserDO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; import java.util.List; @@ -27,8 +28,12 @@ public interface MemberUserConvert { AppMemberUserInfoRespVO convert(MemberUserDO bean); - @Mapping(source = "level", target = "level") - @Mapping(source = "bean.experience", target = "experience") + + @Mappings({ + @Mapping(source = "level", target = "level"), + @Mapping(source = "bean.id", target = "id"), + @Mapping(source = "bean.experience", target = "experience") + }) AppMemberUserInfoRespVO convert(MemberUserDO bean, MemberLevelDO level); MemberUserRespDTO convert2(MemberUserDO bean); From 380ec112217a442b31172a527fb7f0c2ead7d428 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 16 May 2024 16:57:43 +0800 Subject: [PATCH 10/35] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 2 +- .../app/social/vo/AppSocialWxQrcodeReqVO.java | 37 ++++++------------ .../api/social/dto/SocialWxQrcodeReqDTO.java | 39 ++----------------- .../social/SocialClientServiceImpl.java | 7 ++-- 4 files changed, 20 insertions(+), 65 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index a07e6324f..6e3973525 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -70,7 +70,7 @@ public class AppSocialUserController { @Operation(summary = "获得微信小程序码(base64 image)") public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { byte[] wxQrcode = socialClientApi.getWxaQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class) - .setUserId(getLoginUserId()).setUserType(UserTypeEnum.MEMBER.getValue()).setSocialType(reqVO.getType())); + .setEnvVersion(AppSocialWxQrcodeReqVO.ENV_VERSION)); return success("data:image/png;base64," + Base64.getEncoder().encodeToString(wxQrcode)); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java index 6465b2384..10321519c 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -1,31 +1,25 @@ package cn.iocoder.yudao.module.member.controller.app.social.vo; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import lombok.Data; -// TODO @芋艿:需要精简下参数; + @Schema(description = "用户 APP - 获得获取小程序码 Request VO") @Data public class AppSocialWxQrcodeReqVO { - // TODO @puhui999:这个后续不用前端传递,应该是后端搞的。 - // 页面路径不能携带参数(参数请放在scene字段里) - private static String SCENE = ""; // 默认场景值 1011 扫描二维码 - // TODO @puhui999:这个默认是不是 release 哈? - private static String ENV_VERSION = "develop"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" - // TODO @puhui999:这个去掉;因为本身就是 430 啦; + // TODO @puhui999: 没有默认值 getQrcodeService().createWxaCodeUnlimitBytes() 转类型会报错 🤣 + public static String ENV_VERSION = "release"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" + private static String SCENE = ""; // 页面路径不能携带参数(参数请放在scene字段里) private static Integer WIDTH = 430; // 二维码宽度 - // TODO @puhui999:这个去掉;因为本身就是 true 啦; private static Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 - // TODO @puhui999:这个去掉;因为本身就是 true 啦; private static Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 - // TODO @puhui999:这个去掉;因为本身就是 true 啦; - private static Boolean IS_HYALINE = true; // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + private static Boolean HYALINE = true; // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + /** + * 页面路径不能携带参数(参数请放在scene字段里) + */ @Schema(description = "场景值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") private String scene = SCENE; @@ -37,25 +31,16 @@ public class AppSocialWxQrcodeReqVO { @NotEmpty(message = "页面路径不能为空") private String path; - // TODO @puhui999:这个应该不传递哈 - @Schema(description = "小程序版本", requiredMode = Schema.RequiredMode.REQUIRED, example = "develop") - private String envVersion = ENV_VERSION; - @Schema(description = "二维码宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "430") private Integer width = WIDTH; @Schema(description = "是/否自动配置线条颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean isAutoColor = AUTO_COLOR; + private Boolean autoColor = AUTO_COLOR; @Schema(description = "是/否检查 page 是否存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean isCheckPath = CHECK_PATH; + private Boolean checkPath = CHECK_PATH; @Schema(description = "是/否需要透明底色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean isHyaline = IS_HYALINE; - - @Schema(description = "社交平台的类型,参见 SocialTypeEnum 枚举值", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - @InEnum(SocialTypeEnum.class) - @NotNull(message = "社交平台的类型不能为空") - private Integer type; + private Boolean hyaline = HYALINE; } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java index d5e7f194f..efda43e81 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -1,42 +1,17 @@ package cn.iocoder.yudao.module.system.api.social.dto; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import lombok.Data; /** * 获取小程序码 Request DTO * - * @see 获取不限制的小程序码 - * * @author HUIHUI + * @see 获取不限制的小程序码 */ @Data public class SocialWxQrcodeReqDTO { - // TODO @puhui999:userId、userType 应该后续要搞成抽象参数;说白了,就是 path 的参数; socialType 应该去掉,因为就是微信的; - /** - * 用户编号 - */ - @NotNull(message = "用户编号不能为空") - private Long userId; - /** - * 用户类型 - */ - @InEnum(UserTypeEnum.class) - @NotNull(message = "用户类型不能为空") - private Integer userType; - - /** - * 社交平台的类型 - */ - @InEnum(SocialTypeEnum.class) - @NotNull(message = "社交平台的类型不能为空") - private Integer socialType; - /** * 场景 */ @@ -56,23 +31,17 @@ public class SocialWxQrcodeReqDTO { */ private Integer width; - // TODO @puhui999:autoColor - /** * 是否需要透明底色 */ - private Boolean isAutoColor; - - // TODO @puhui999: checkPath + private Boolean autoColor; /** * 是否检查 page 是否存在 */ - private Boolean isCheckPath; - - // TODO @puhui999: hyaline + private Boolean checkPath; /** * 是否需要透明底色 */ - private Boolean isHyaline; + private Boolean hyaline; } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index aeff40cce..41b27b171 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -9,6 +9,7 @@ import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ReflectUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; import cn.iocoder.yudao.framework.common.util.http.HttpUtils; @@ -230,11 +231,11 @@ public class SocialClientServiceImpl implements SocialClientService { @Override public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { - WxMaService service = getWxMaService(reqVO.getUserType()); + WxMaService service = getWxMaService(UserTypeEnum.MEMBER.getValue()); try { return service.getQrcodeService().createWxaCodeUnlimitBytes(reqVO.getScene(), reqVO.getPath(), - reqVO.getIsCheckPath(), reqVO.getEnvVersion(), reqVO.getWidth(), reqVO.getIsAutoColor(), - null, reqVO.getIsHyaline()); + reqVO.getCheckPath(), reqVO.getEnvVersion(), reqVO.getWidth(), reqVO.getAutoColor(), + null, reqVO.getHyaline()); } catch (WxErrorException e) { log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e); throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); From 33e8c7ca2e4b3e7579361600ef9c10e47cf1d296 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 18 May 2024 20:42:48 +0800 Subject: [PATCH 11/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91MEMBER=EF=BC=9A=E5=AE=8C=E5=96=84=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/controller/app/social/AppSocialUserController.java | 1 + .../member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java | 1 + 2 files changed, 2 insertions(+) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 6e3973525..2cf7b8641 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -71,6 +71,7 @@ public class AppSocialUserController { public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { byte[] wxQrcode = socialClientApi.getWxaQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class) .setEnvVersion(AppSocialWxQrcodeReqVO.ENV_VERSION)); + // TODO @puhui999:1)是不是 base64 返回,不拼接哈 data:image/png;base64;2)cn.hutool.core.codec.Base64.encode() return success("data:image/png;base64," + Base64.getEncoder().encodeToString(wxQrcode)); } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java index 10321519c..aabdc72c9 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -10,6 +10,7 @@ import lombok.Data; public class AppSocialWxQrcodeReqVO { // TODO @puhui999: 没有默认值 getQrcodeService().createWxaCodeUnlimitBytes() 转类型会报错 🤣 + // TODO @puhui999:懂了哈;default 最好在 controller 搞;对于 VO 来说,不给默认值; public static String ENV_VERSION = "release"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" private static String SCENE = ""; // 页面路径不能携带参数(参数请放在scene字段里) private static Integer WIDTH = 430; // 二维码宽度 From b227b039c961d04ba49af8f022278c784b4c3431 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 23 May 2024 17:14:35 +0800 Subject: [PATCH 12/35] =?UTF-8?q?MEMBER:=20=E6=A0=B9=E6=8D=AE=E3=80=90?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E8=AF=84=E5=AE=A1=E3=80=91=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 22 ++++++++++++++----- .../app/social/vo/AppSocialWxQrcodeReqVO.java | 19 +++++----------- 2 files changed, 21 insertions(+), 20 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 2cf7b8641..722da6dab 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -1,5 +1,7 @@ package cn.iocoder.yudao.module.member.controller.app.social; +import cn.hutool.core.codec.Base64; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -22,8 +24,6 @@ import jakarta.validation.Valid; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Base64; - import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -33,6 +33,13 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti @Validated public class AppSocialUserController { + public static final String ENV_VERSION = "release"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" + private static final String SCENE = ""; // 页面路径不能携带参数(参数请放在scene字段里) + private static final Integer WIDTH = 430; // 二维码宽度 + private static final Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + private static final Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 + private static final Boolean HYALINE = true; // 是否需要透明底色, hyaline 为true时,生成透明底色的小程序码 + @Resource private SocialUserApi socialUserApi; @Resource @@ -69,10 +76,13 @@ public class AppSocialUserController { @PostMapping("/wxa-qrcode") @Operation(summary = "获得微信小程序码(base64 image)") public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { - byte[] wxQrcode = socialClientApi.getWxaQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class) - .setEnvVersion(AppSocialWxQrcodeReqVO.ENV_VERSION)); - // TODO @puhui999:1)是不是 base64 返回,不拼接哈 data:image/png;base64;2)cn.hutool.core.codec.Base64.encode() - return success("data:image/png;base64," + Base64.getEncoder().encodeToString(wxQrcode)); + byte[] wxQrcode = socialClientApi.getWxaQrcode(new SocialWxQrcodeReqDTO().setPath(reqVO.getPath()) + .setEnvVersion(ENV_VERSION).setWidth(ObjUtil.defaultIfNull(reqVO.getWidth(), WIDTH)) + .setScene(ObjUtil.defaultIfNull(reqVO.getScene(), SCENE)) + .setAutoColor(ObjUtil.defaultIfNull(reqVO.getAutoColor(), AUTO_COLOR)) + .setHyaline(ObjUtil.defaultIfNull(reqVO.getHyaline(), HYALINE)) + .setCheckPath(ObjUtil.defaultIfNull(reqVO.getCheckPath(), CHECK_PATH))); + return success(Base64.encode(wxQrcode)); } } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java index aabdc72c9..8927a34c9 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/vo/AppSocialWxQrcodeReqVO.java @@ -9,20 +9,11 @@ import lombok.Data; @Data public class AppSocialWxQrcodeReqVO { - // TODO @puhui999: 没有默认值 getQrcodeService().createWxaCodeUnlimitBytes() 转类型会报错 🤣 - // TODO @puhui999:懂了哈;default 最好在 controller 搞;对于 VO 来说,不给默认值; - public static String ENV_VERSION = "release"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" - private static String SCENE = ""; // 页面路径不能携带参数(参数请放在scene字段里) - private static Integer WIDTH = 430; // 二维码宽度 - private static Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 - private static Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 - private static Boolean HYALINE = true; // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 - /** * 页面路径不能携带参数(参数请放在scene字段里) */ @Schema(description = "场景值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") - private String scene = SCENE; + private String scene; /** * 默认是主页,页面 page,例如 pages/index/index,根路径前不要填加 /,不能携带参数(参数请放在scene字段里), @@ -33,15 +24,15 @@ public class AppSocialWxQrcodeReqVO { private String path; @Schema(description = "二维码宽度", requiredMode = Schema.RequiredMode.REQUIRED, example = "430") - private Integer width = WIDTH; + private Integer width; @Schema(description = "是/否自动配置线条颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean autoColor = AUTO_COLOR; + private Boolean autoColor; @Schema(description = "是/否检查 page 是否存在", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean checkPath = CHECK_PATH; + private Boolean checkPath; @Schema(description = "是/否需要透明底色", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - private Boolean hyaline = HYALINE; + private Boolean hyaline; } From b073c1d37f0c5027f5d2476d8a12836654b01227 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 25 May 2024 22:06:55 +0800 Subject: [PATCH 13/35] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=8E=B7?= =?UTF-8?q?=E5=BE=97=E4=B8=AA=E4=BA=BA=E5=88=86=E9=94=80=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=20NPE=20=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../brokerage/AppBrokerageUserController.java | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java index bb2ba59fe..d82915cde 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage; import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; @@ -10,6 +11,8 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.*; import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageRecordConvert; import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; +import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; @@ -17,16 +20,17 @@ import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService; import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService; import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO; +import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; import java.time.LocalDateTime; import java.util.Collections; import java.util.Map; @@ -50,7 +54,8 @@ public class AppBrokerageUserController { private BrokerageRecordService brokerageRecordService; @Resource private BrokerageWithdrawService brokerageWithdrawService; - + @Resource + private TradeConfigService tradeConfigService; @Resource private MemberUserApi memberUserApi; @@ -59,9 +64,13 @@ public class AppBrokerageUserController { @PreAuthenticated public CommonResult getBrokerageUser() { Optional user = Optional.ofNullable(brokerageUserService.getBrokerageUser(getLoginUserId())); + // 获得交易中心配置 + TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig(); + // 如果是人人分佣 BrokerageUserDO 为 null 时,也有分销资格 + boolean brokerageEnabled = ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(), tradeConfig.getBrokerageEnabledCondition()); // 返回数据 AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO() - .setBrokerageEnabled(user.map(BrokerageUserDO::getBrokerageEnabled).orElse(false)) + .setBrokerageEnabled(user.map(BrokerageUserDO::getBrokerageEnabled).orElse(brokerageEnabled)) .setBrokeragePrice(user.map(BrokerageUserDO::getBrokeragePrice).orElse(0)) .setFrozenPrice(user.map(BrokerageUserDO::getFrozenPrice).orElse(0)); return success(respVO); @@ -79,21 +88,22 @@ public class AppBrokerageUserController { @PreAuthenticated public CommonResult getBrokerageUserSummary() { // 查询当前登录用户信息 - BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(getLoginUserId()); + Long userId = getLoginUserId(); + BrokerageUserDO brokerageUser = brokerageUserService.getBrokerageUser(userId); // 统计用户昨日的佣金 LocalDateTime yesterday = LocalDateTime.now().minusDays(1); LocalDateTime beginTime = LocalDateTimeUtil.beginOfDay(yesterday); LocalDateTime endTime = LocalDateTimeUtil.endOfDay(yesterday); - Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(brokerageUser.getId(), + Integer yesterdayPrice = brokerageRecordService.getSummaryPriceByUserId(userId, BrokerageRecordBizTypeEnum.ORDER, BrokerageRecordStatusEnum.SETTLEMENT, beginTime, endTime); // 统计用户提现的佣金 - Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(brokerageUser.getId()), + Integer withdrawPrice = brokerageWithdrawService.getWithdrawSummaryListByUserId(Collections.singleton(userId), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS).stream() .findFirst().map(BrokerageWithdrawSummaryRespBO::getPrice).orElse(0); // 统计分销用户数量(一级) - Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 1); + Long firstBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(userId, 1); // 统计分销用户数量(二级) - Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(brokerageUser.getId(), 2); + Long secondBrokerageUserCount = brokerageUserService.getBrokerageUserCountByBindUserId(userId, 2); // 拼接返回 return success(BrokerageUserConvert.INSTANCE.convert(yesterdayPrice, withdrawPrice, firstBrokerageUserCount, secondBrokerageUserCount, brokerageUser)); From 3025bff63111108a60a3999f5e8a041e86756232 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sun, 26 May 2024 09:47:41 +0800 Subject: [PATCH 14/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91SYSTEM=EF=BC=9A=E5=AE=8C=E5=96=84=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/social/AppSocialUserController.java | 15 +--------- .../api/social/dto/SocialWxQrcodeReqDTO.java | 29 +++++++++++++++++++ .../social/SocialClientServiceImpl.java | 12 ++++++-- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 722da6dab..038b225f2 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.member.controller.app.social; import cn.hutool.core.codec.Base64; -import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -33,13 +32,6 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti @Validated public class AppSocialUserController { - public static final String ENV_VERSION = "release"; // 小程序版本。正式版为 "release",体验版为 "trial",开发版为 "develop" - private static final String SCENE = ""; // 页面路径不能携带参数(参数请放在scene字段里) - private static final Integer WIDTH = 430; // 二维码宽度 - private static final Boolean AUTO_COLOR = true; // 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 - private static final Boolean CHECK_PATH = true; // 默认true 检查 page 是否存在 - private static final Boolean HYALINE = true; // 是否需要透明底色, hyaline 为true时,生成透明底色的小程序码 - @Resource private SocialUserApi socialUserApi; @Resource @@ -76,12 +68,7 @@ public class AppSocialUserController { @PostMapping("/wxa-qrcode") @Operation(summary = "获得微信小程序码(base64 image)") public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { - byte[] wxQrcode = socialClientApi.getWxaQrcode(new SocialWxQrcodeReqDTO().setPath(reqVO.getPath()) - .setEnvVersion(ENV_VERSION).setWidth(ObjUtil.defaultIfNull(reqVO.getWidth(), WIDTH)) - .setScene(ObjUtil.defaultIfNull(reqVO.getScene(), SCENE)) - .setAutoColor(ObjUtil.defaultIfNull(reqVO.getAutoColor(), AUTO_COLOR)) - .setHyaline(ObjUtil.defaultIfNull(reqVO.getHyaline(), HYALINE)) - .setCheckPath(ObjUtil.defaultIfNull(reqVO.getCheckPath(), CHECK_PATH))); + byte[] wxQrcode = socialClientApi.getWxaQrcode(new SocialWxQrcodeReqDTO().setPath(reqVO.getPath())); return success(Base64.encode(wxQrcode)); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java index efda43e81..4b964af4f 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -12,6 +12,35 @@ import lombok.Data; @Data public class SocialWxQrcodeReqDTO { + /** + * 小程序版本 + * + * 正式版为 "release";体验版为 "trial";开发版为 "develop" + */ + public static final String ENV_VERSION = "release"; + /** + * 页面路径不能携带参数(参数请放在scene字段里) + */ + public static final String SCENE = ""; + /** + * 二维码宽度 + */ + public static final Integer WIDTH = 430; + /** + * 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + */ + public static final Boolean AUTO_COLOR = true; + /** + * 检查 page 是否存在 + */ + public static final Boolean CHECK_PATH = true; + /** + * 是否需要透明底色 + * + * hyaline 为 true 时,生成透明底色的小程序码 + */ + public static final Boolean HYALINE = true; + /** * 场景 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 41b27b171..25d6d99d2 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -233,9 +233,15 @@ public class SocialClientServiceImpl implements SocialClientService { public byte[] getWxaQrcode(SocialWxQrcodeReqDTO reqVO) { WxMaService service = getWxMaService(UserTypeEnum.MEMBER.getValue()); try { - return service.getQrcodeService().createWxaCodeUnlimitBytes(reqVO.getScene(), reqVO.getPath(), - reqVO.getCheckPath(), reqVO.getEnvVersion(), reqVO.getWidth(), reqVO.getAutoColor(), - null, reqVO.getHyaline()); + return service.getQrcodeService().createWxaCodeUnlimitBytes( + ObjUtil.defaultIfEmpty(reqVO.getScene(), SocialWxQrcodeReqDTO.SCENE), + reqVO.getPath(), + ObjUtil.defaultIfNull(reqVO.getCheckPath(), SocialWxQrcodeReqDTO.CHECK_PATH), + ObjUtil.defaultIfBlank(reqVO.getEnvVersion(), SocialWxQrcodeReqDTO.ENV_VERSION), + ObjUtil.defaultIfNull(reqVO.getWidth(), SocialWxQrcodeReqDTO.WIDTH), + ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR), + null, + ObjUtil.defaultIfNull(reqVO.getHyaline(), SocialWxQrcodeReqDTO.HYALINE)); } catch (WxErrorException e) { log.error("[getWxQrcode][reqVO({})) 获得小程序码失败]", reqVO, e); throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_QRCODE_ERROR); From ea7f923072bc1879fa1b825f270d7cfb204eaa9f Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 27 May 2024 16:18:52 +0800 Subject: [PATCH 15/35] =?UTF-8?q?SYSTEM=EF=BC=9A=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/app/social/AppSocialUserController.java | 4 ++-- .../system/api/social/dto/SocialWxQrcodeReqDTO.java | 10 ---------- .../system/service/social/SocialClientServiceImpl.java | 9 ++++++++- yudao-server/src/main/resources/application-dev.yaml | 2 ++ yudao-server/src/main/resources/application-local.yaml | 2 ++ yudao-server/src/main/resources/application.yaml | 2 ++ 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java index 038b225f2..de76856c3 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/controller/app/social/AppSocialUserController.java @@ -67,8 +67,8 @@ public class AppSocialUserController { @PostMapping("/wxa-qrcode") @Operation(summary = "获得微信小程序码(base64 image)") - public CommonResult getWxQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { - byte[] wxQrcode = socialClientApi.getWxaQrcode(new SocialWxQrcodeReqDTO().setPath(reqVO.getPath())); + public CommonResult getWxaQrcode(@RequestBody @Valid AppSocialWxQrcodeReqVO reqVO) { + byte[] wxQrcode = socialClientApi.getWxaQrcode(BeanUtils.toBean(reqVO, SocialWxQrcodeReqDTO.class)); return success(Base64.encode(wxQrcode)); } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java index 4b964af4f..6f4b96f4e 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/social/dto/SocialWxQrcodeReqDTO.java @@ -12,12 +12,6 @@ import lombok.Data; @Data public class SocialWxQrcodeReqDTO { - /** - * 小程序版本 - * - * 正式版为 "release";体验版为 "trial";开发版为 "develop" - */ - public static final String ENV_VERSION = "release"; /** * 页面路径不能携带参数(参数请放在scene字段里) */ @@ -51,10 +45,6 @@ public class SocialWxQrcodeReqDTO { */ @NotEmpty(message = "页面路径不能为空") private String path; - /** - * 要打开的小程序版本 - */ - private String envVersion; /** * 二维码宽度 */ diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java index 25d6d99d2..b9a339223 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/social/SocialClientServiceImpl.java @@ -41,6 +41,7 @@ import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; @@ -60,6 +61,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; @Slf4j public class SocialClientServiceImpl implements SocialClientService { + /** + * 小程序版本 + */ + @Value("${yudao.wxa-code.env-version}") + public String envVersion; + @Resource private AuthRequestFactory authRequestFactory; @@ -237,7 +244,7 @@ public class SocialClientServiceImpl implements SocialClientService { ObjUtil.defaultIfEmpty(reqVO.getScene(), SocialWxQrcodeReqDTO.SCENE), reqVO.getPath(), ObjUtil.defaultIfNull(reqVO.getCheckPath(), SocialWxQrcodeReqDTO.CHECK_PATH), - ObjUtil.defaultIfBlank(reqVO.getEnvVersion(), SocialWxQrcodeReqDTO.ENV_VERSION), + envVersion, ObjUtil.defaultIfNull(reqVO.getWidth(), SocialWxQrcodeReqDTO.WIDTH), ObjUtil.defaultIfNull(reqVO.getAutoColor(), SocialWxQrcodeReqDTO.AUTO_COLOR), null, diff --git a/yudao-server/src/main/resources/application-dev.yaml b/yudao-server/src/main/resources/application-dev.yaml index 936aaae69..cc105790d 100644 --- a/yudao-server/src/main/resources/application-dev.yaml +++ b/yudao-server/src/main/resources/application-dev.yaml @@ -171,6 +171,8 @@ yudao: order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址 refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 demo: true # 开启演示模式 + wxa-code: + env-version: release # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop" tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc justauth: diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index f5912f690..d494593ab 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -220,6 +220,8 @@ yudao: access-log: # 访问日志的配置项 enable: false demo: false # 关闭演示模式 + wxa-code: + env-version: develop # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop" tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc justauth: diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 6cb5386e3..561db7a93 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -177,6 +177,8 @@ yudao: license-url: https://gitee.com/zhijiantianya/ruoyi-vue-pro/blob/master/LICENSE captcha: enable: true # 验证码的开关,默认为 true + wxa-code: + env-version: release # 小程序版本: 正式版为 "release";体验版为 "trial";开发版为 "develop"。默认为 release codegen: base-package: ${yudao.info.base-package} db-schemas: ${spring.datasource.dynamic.datasource.master.name} From ae493e66dface723e8b08f1b93f72a1ff90feebb Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 27 May 2024 16:24:07 +0800 Subject: [PATCH 16/35] =?UTF-8?q?MALL-TRADE=EF=BC=9A=E6=96=B0=E5=A2=9E=20B?= =?UTF-8?q?rokerageUserService#getOrCreateBrokerageUser=20=E8=8E=B7?= =?UTF-8?q?=E5=BE=97=E6=88=96=E5=88=9B=E5=BB=BA=E5=88=86=E9=94=80=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E6=96=B9=E6=B3=95=EF=BC=8C=E8=A7=A3=E5=86=B3=E7=88=B6?= =?UTF-8?q?=E7=BA=A7=E6=8E=A8=E5=B9=BF=E4=BA=BA=E4=B8=8D=E5=AD=98=E5=9C=A8?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../brokerage/AppBrokerageUserController.java | 14 ++------- .../vo/user/AppBrokerageUserBindReqVO.java | 2 +- .../brokerage/BrokerageUserService.java | 15 ++++++++-- .../brokerage/BrokerageUserServiceImpl.java | 29 +++++++++++++++---- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java index d82915cde..202ed3c42 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageUserController.java @@ -1,7 +1,6 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage; import cn.hutool.core.date.LocalDateTimeUtil; -import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; @@ -11,8 +10,6 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.*; import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageRecordConvert; import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageUserConvert; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; -import cn.iocoder.yudao.module.trade.dal.dataobject.config.TradeConfigDO; -import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageEnabledConditionEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; @@ -20,7 +17,6 @@ import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageRecordService; import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageUserService; import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService; import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO; -import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -55,22 +51,16 @@ public class AppBrokerageUserController { @Resource private BrokerageWithdrawService brokerageWithdrawService; @Resource - private TradeConfigService tradeConfigService; - @Resource private MemberUserApi memberUserApi; @GetMapping("/get") @Operation(summary = "获得个人分销信息") @PreAuthenticated public CommonResult getBrokerageUser() { - Optional user = Optional.ofNullable(brokerageUserService.getBrokerageUser(getLoginUserId())); - // 获得交易中心配置 - TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig(); - // 如果是人人分佣 BrokerageUserDO 为 null 时,也有分销资格 - boolean brokerageEnabled = ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(), tradeConfig.getBrokerageEnabledCondition()); + Optional user = Optional.ofNullable(brokerageUserService.getOrCreateBrokerageUser(getLoginUserId())); // 返回数据 AppBrokerageUserRespVO respVO = new AppBrokerageUserRespVO() - .setBrokerageEnabled(user.map(BrokerageUserDO::getBrokerageEnabled).orElse(brokerageEnabled)) + .setBrokerageEnabled(user.map(BrokerageUserDO::getBrokerageEnabled).orElse(false)) .setBrokeragePrice(user.map(BrokerageUserDO::getBrokeragePrice).orElse(0)) .setFrozenPrice(user.map(BrokerageUserDO::getFrozenPrice).orElse(0)); return success(respVO); diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java index e6f2982eb..db23d8e20 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/user/AppBrokerageUserBindReqVO.java @@ -8,7 +8,7 @@ import jakarta.validation.constraints.NotNull; @Schema(description = "应用 App - 绑定推广员 Request VO") @Data -public class AppBrokerageUserBindReqVO extends PageParam { +public class AppBrokerageUserBindReqVO { @Schema(description = "推广员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @NotNull(message = "推广员编号不能为空") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java index a50eedda1..7b6e4b110 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserService.java @@ -7,8 +7,8 @@ import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokera import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankByUserCountRespVO; import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.user.AppBrokerageUserRankPageReqVO; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageUserDO; - import jakarta.validation.constraints.NotNull; + import java.util.Collection; import java.util.List; @@ -67,6 +67,14 @@ public interface BrokerageUserService { */ BrokerageUserDO getBindBrokerageUser(Long id); + /** + * 获得或创建分销用户 + * + * @param id 用户编号 + * @return 分销用户 + */ + BrokerageUserDO getOrCreateBrokerageUser(Long id); + /** * 更新用户佣金 * @@ -104,8 +112,8 @@ public interface BrokerageUserService { /** * 【会员】绑定推广员 * - * @param userId 用户编号 - * @param bindUserId 推广员编号 + * @param userId 用户编号 + * @param bindUserId 推广员编号 * @return 是否绑定 */ boolean bindBrokerageUser(@NotNull Long userId, @NotNull Long bindUserId); @@ -134,4 +142,5 @@ public interface BrokerageUserService { * @return 下级分销统计分页 */ PageResult getBrokerageUserChildSummaryPage(AppBrokerageUserChildSummaryPageReqVO pageReqVO, Long userId); + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java index 355e1ee8b..08e42c9a0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; @@ -25,10 +26,10 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum; import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordStatusEnum; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import com.baomidou.mybatisplus.core.metadata.IPage; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.*; @@ -127,6 +128,18 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { .orElse(null); } + @Override + public BrokerageUserDO getOrCreateBrokerageUser(Long id) { + BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id); + if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(), + tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) { // 人人分销的情况下,如果分销人为空则创建分销人 + brokerageUser = new BrokerageUserDO().setId(id).setBrokerageEnabled(true).setBrokeragePrice(0) + .setBrokerageTime(LocalDateTime.now()).setFrozenPrice(0); + brokerageUserMapper.insert(brokerageUser); + } + return brokerageUser; + } + @Override public boolean updateUserPrice(Long id, Integer price) { if (price > 0) { @@ -184,7 +197,6 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { if (BrokerageEnabledConditionEnum.ALL.getCondition().equals(enabledCondition)) { // 人人分销:用户默认就有分销资格 brokerageUser.setBrokerageEnabled(true).setBrokerageTime(LocalDateTime.now()); } - brokerageUser.setBindUserId(bindUserId).setBindUserTime(LocalDateTime.now()); brokerageUserMapper.insert(fillBindUserData(bindUserId, brokerageUser)); } else { brokerageUserMapper.updateById(fillBindUserData(bindUserId, new BrokerageUserDO().setId(userId))); @@ -294,18 +306,23 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { } private void validateCanBindUser(BrokerageUserDO user, Long bindUserId) { - // 校验要绑定的用户有无推广资格 - BrokerageUserDO bindUser = brokerageUserMapper.selectById(bindUserId); + // 1.1 校验推广人是否存在 + MemberUserRespDTO bindUserInfo = memberUserApi.getUser(bindUserId); + if (bindUserInfo == null) { + throw exception(BROKERAGE_USER_NOT_EXISTS); + } + // 1.2 校验要绑定的用户有无推广资格 + BrokerageUserDO bindUser = getOrCreateBrokerageUser(bindUserId); if (bindUser == null || BooleanUtil.isFalse(bindUser.getBrokerageEnabled())) { throw exception(BROKERAGE_BIND_USER_NOT_ENABLED); } - // 校验绑定自己 + // 2. 校验绑定自己 if (Objects.equals(user.getId(), bindUserId)) { throw exception(BROKERAGE_BIND_SELF); } - // 下级不能绑定自己的上级 + // 3. 下级不能绑定自己的上级 for (int i = 0; i <= Short.MAX_VALUE; i++) { if (Objects.equals(bindUser.getBindUserId(), user.getId())) { throw exception(BROKERAGE_BIND_LOOP); From 020f3a9a12b715c8d648ae620160c67bdc113600 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Thu, 30 May 2024 16:09:36 +0800 Subject: [PATCH 17/35] =?UTF-8?q?MALL-KEHU:=20=E6=96=B0=E5=A2=9E=E5=AE=A2?= =?UTF-8?q?=E6=9C=8D=E7=9B=B8=E5=85=B3=E5=AE=9E=E4=BD=93=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promotion/enums/kehu/MessageTypeEnum.java | 42 +++++++ .../dal/dataobject/kefu/KehuMessageDO.java | 72 ++++++++++++ .../dal/dataobject/kefu/KehuTalkDO.java | 104 ++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java new file mode 100644 index 000000000..f866414c6 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.enums.kehu; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 消息类型枚举 + * + * @author HUIHUI + */ +@AllArgsConstructor +@Getter +public enum MessageTypeEnum implements IntArrayValuable { + + MESSAGE(1, "普通消息"), + PICTURE(2, "图片消息"), + VOICE(3, "语音消息"), + GOODS(4, "商品消息"), + ORDER(5, "订单消息"), + VIDEO(6, "视频消息"); + + private static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MessageTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + + /** + * 名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java new file mode 100644 index 000000000..e949daf0b --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java @@ -0,0 +1,72 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.promotion.enums.kehu.MessageTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 客户消息 DO + * + * @author HUIHUI + */ +@TableName("kehu_message") +@KeySequence("kehu_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KehuMessageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 会话编号 + * + * 关联 {@link KehuTalkDO#getId()} + */ + private Long talkId; + + /** + * 发送者 + */ + private Long fromUserId; + /** + * 发送者用户类型 + */ + private String fromUserType; + /** + * 接收者 + */ + private Long toUserId; + /** + * 接收着用户类型 + */ + private String toUserType; + + /** + * 消息类型 + * + * 枚举 {@link MessageTypeEnum} + */ + private Integer messageType; + /** + * 消息 + */ + private String message; + + //======================= 消息相关状态 ======================= + + /** + * 是/否已读 + */ + private Boolean isRead; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java new file mode 100644 index 000000000..303281438 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java @@ -0,0 +1,104 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.promotion.enums.kehu.MessageTypeEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 客户会话 DO + * + * @author HUIHUI + */ +@TableName("kehu_talk") +@KeySequence("kehu_talk_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KehuTalkDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 会话所属用户 + * + * 关联 {@link MemberUserRespDTO#getId()} + */ + private Long userId; + /** + * 用户名称 + * + * 关联 {@link MemberUserRespDTO#getNickname()} + */ + private String userName; + /** + * 用户头像 + * + * 关联 {@link MemberUserRespDTO#getAvatar()} + */ + private String userFace; + /** + * 管理员名称 + * + * 关联 {@link AdminUserRespDTO#getNickname()} + */ + private String adminName; + /** + * 管理员头像, 管理员搞个默认头像 + */ + private String adminFace; + + /** + * 最后聊天时间 + */ + private LocalDateTime lastTalkTime; + /** + * 最后聊天内容 + */ + private String lastTalkMessage; + /** + * 最后发送的消息类型 + * + * 枚举 {@link MessageTypeEnum} + */ + private Integer lastMessageType; + + //======================= 会话操作相关 ======================= + + /** + * 管理端置顶 + */ + private Boolean adminPinned; + /** + * 用户端不可见,默认为 true + * + * 用户删除此会话时设置为 false + */ + private Boolean userDisable; + /** + * 管理员端不可见,默认为 true + * + * 管理员删除此会话时设置为 false + */ + private Boolean adminDisable; + + /** + * 管理员未读消息数 + * + * 用户发送消息时增加,管理员查看后扣减 + */ + private Integer adminUnreadMessageCount; + +} From 0be9c67aa6b534e872b25b9e82e4a32e7fc7e331 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 May 2024 09:58:23 +0800 Subject: [PATCH 18/35] =?UTF-8?q?MALL-KEFU:=20=E6=A0=B9=E6=8D=AE=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=AF=84=E5=AE=A1=E4=BF=AE=E6=94=B9=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...m.java => KeFuMessageContentTypeEnum.java} | 15 +-- .../dataobject/kefu/KeFuConversationDO.java | 83 ++++++++++++++ .../dal/dataobject/kefu/KeFuMessageDO.java | 81 ++++++++++++++ .../dal/dataobject/kefu/KehuMessageDO.java | 72 ------------ .../dal/dataobject/kefu/KehuTalkDO.java | 104 ------------------ 5 files changed, 172 insertions(+), 183 deletions(-) rename yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/{MessageTypeEnum.java => KeFuMessageContentTypeEnum.java} (64%) create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuMessageDO.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java similarity index 64% rename from yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java rename to yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java index f866414c6..29c3b34c4 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/MessageTypeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java @@ -13,16 +13,17 @@ import java.util.Arrays; */ @AllArgsConstructor @Getter -public enum MessageTypeEnum implements IntArrayValuable { +public enum KeFuMessageContentTypeEnum implements IntArrayValuable { - MESSAGE(1, "普通消息"), - PICTURE(2, "图片消息"), + TEXT(1, "文本消息"), + IMAGE(2, "图片消息"), VOICE(3, "语音消息"), - GOODS(4, "商品消息"), - ORDER(5, "订单消息"), - VIDEO(6, "视频消息"); + VIDEO(4, "视频消息"), + // 和正常消息隔离下 + PRODUCT(10, "商品消息"), + ORDER(11, "订单消息"); - private static final int[] ARRAYS = Arrays.stream(values()).mapToInt(MessageTypeEnum::getType).toArray(); + private static final int[] ARRAYS = Arrays.stream(values()).mapToInt(KeFuMessageContentTypeEnum::getType).toArray(); /** * 类型 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java new file mode 100644 index 000000000..04432eebe --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java @@ -0,0 +1,83 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; +import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * 客服会话 DO + * + * @author HUIHUI + */ +@TableName("promotion_kefu_conversation") +@KeySequence("promotion_kefu_conversation_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KeFuConversationDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 会话所属用户 + * + * 关联 {@link MemberUserRespDTO#getId()} + */ + private Long userId; + + /** + * 最后聊天时间 + */ + private LocalDateTime lastMessageTime; + /** + * 最后聊天内容 + */ + private String lastMessageContent; + /** + * 最后发送的消息类型 + * + * 枚举 {@link KeFuMessageContentTypeEnum} + */ + private Integer lastMessageContentType; + + //======================= 会话操作相关 ======================= + + /** + * 管理端置顶 + */ + private Boolean adminPinned; + /** + * 用户是否可见 + * + * true - 可见,默认值 + * false - 不可见,用户删除时设置为 false + */ + private Boolean userDeleted; + /** + * 管理员是否可见 + * + * true - 可见,默认值 + * false - 不可见,管理员删除时设置为 false + */ + private Boolean adminDeleted; + + /** + * 管理员未读消息数 + * + * 用户发送消息时增加,管理员查看后扣减 + */ + private Integer adminUnreadMessageCount; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuMessageDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuMessageDO.java new file mode 100644 index 000000000..bd542f890 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuMessageDO.java @@ -0,0 +1,81 @@ +package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; + +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +/** + * 客服消息 DO + * + * @author HUIHUI + */ +@TableName("promotion_kefu_message") +@KeySequence("promotion_kefu_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class KeFuMessageDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 会话编号 + * + * 关联 {@link KeFuConversationDO#getId()} + */ + private Long conversationId; + + /** + * 发送人编号 + * + * 存储的是用户编号 + */ + private Long senderId; + /** + * 发送人类型 + * + * 枚举,{@link UserTypeEnum} + */ + private Integer senderType; + /** + * 接收人编号 + * + * 存储的是用户编号 + */ + private Long receiverId; + /** + * 接收人类型 + * + * 枚举,{@link UserTypeEnum} + */ + private Integer receiverType; + + /** + * 消息类型 + * + * 枚举 {@link KeFuMessageContentTypeEnum} + */ + private Integer contentType; + /** + * 消息 + */ + private String content; + + //======================= 消息相关状态 ======================= + + /** + * 是/否已读 + */ + private Boolean readStatus; + +} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java deleted file mode 100644 index e949daf0b..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuMessageDO.java +++ /dev/null @@ -1,72 +0,0 @@ -package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.promotion.enums.kehu.MessageTypeEnum; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -/** - * 客户消息 DO - * - * @author HUIHUI - */ -@TableName("kehu_message") -@KeySequence("kehu_message_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class KehuMessageDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 会话编号 - * - * 关联 {@link KehuTalkDO#getId()} - */ - private Long talkId; - - /** - * 发送者 - */ - private Long fromUserId; - /** - * 发送者用户类型 - */ - private String fromUserType; - /** - * 接收者 - */ - private Long toUserId; - /** - * 接收着用户类型 - */ - private String toUserType; - - /** - * 消息类型 - * - * 枚举 {@link MessageTypeEnum} - */ - private Integer messageType; - /** - * 消息 - */ - private String message; - - //======================= 消息相关状态 ======================= - - /** - * 是/否已读 - */ - private Boolean isRead; - -} diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java deleted file mode 100644 index 303281438..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KehuTalkDO.java +++ /dev/null @@ -1,104 +0,0 @@ -package cn.iocoder.yudao.module.promotion.dal.dataobject.kefu; - -import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; -import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; -import cn.iocoder.yudao.module.promotion.enums.kehu.MessageTypeEnum; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import com.baomidou.mybatisplus.annotation.KeySequence; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import lombok.*; - -import java.time.LocalDateTime; - -/** - * 客户会话 DO - * - * @author HUIHUI - */ -@TableName("kehu_talk") -@KeySequence("kehu_talk_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -@Builder -@NoArgsConstructor -@AllArgsConstructor -public class KehuTalkDO extends BaseDO { - - /** - * 编号 - */ - @TableId - private Long id; - /** - * 会话所属用户 - * - * 关联 {@link MemberUserRespDTO#getId()} - */ - private Long userId; - /** - * 用户名称 - * - * 关联 {@link MemberUserRespDTO#getNickname()} - */ - private String userName; - /** - * 用户头像 - * - * 关联 {@link MemberUserRespDTO#getAvatar()} - */ - private String userFace; - /** - * 管理员名称 - * - * 关联 {@link AdminUserRespDTO#getNickname()} - */ - private String adminName; - /** - * 管理员头像, 管理员搞个默认头像 - */ - private String adminFace; - - /** - * 最后聊天时间 - */ - private LocalDateTime lastTalkTime; - /** - * 最后聊天内容 - */ - private String lastTalkMessage; - /** - * 最后发送的消息类型 - * - * 枚举 {@link MessageTypeEnum} - */ - private Integer lastMessageType; - - //======================= 会话操作相关 ======================= - - /** - * 管理端置顶 - */ - private Boolean adminPinned; - /** - * 用户端不可见,默认为 true - * - * 用户删除此会话时设置为 false - */ - private Boolean userDisable; - /** - * 管理员端不可见,默认为 true - * - * 管理员删除此会话时设置为 false - */ - private Boolean adminDisable; - - /** - * 管理员未读消息数 - * - * 用户发送消息时增加,管理员查看后扣减 - */ - private Integer adminUnreadMessageCount; - -} From 127a98a93478abbaa0e54a49052b0793c6334d8e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 31 May 2024 16:59:06 +0800 Subject: [PATCH 19/35] =?UTF-8?q?MALL-KEFU:=20=E6=96=B0=E5=A2=9E=E5=AE=A2?= =?UTF-8?q?=E6=9C=8D=E7=9B=B8=E5=85=B3=E6=93=8D=E4=BD=9C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/mall-promotion-kefu.sql | 37 +++++ .../promotion/enums/ErrorCodeConstants.java | 6 + .../kefu/KeFuConversationController.java | 54 ++++++++ .../admin/kefu/KeFuMessageController.java | 58 ++++++++ .../conversation/KeFuConversationRespVO.java | 42 ++++++ .../KeFuConversationUpdatePinnedReqVO.java | 19 +++ .../kefu/vo/message/KeFuMessagePageReqVO.java | 16 +++ .../kefu/vo/message/KeFuMessageRespVO.java | 43 ++++++ .../kefu/vo/message/KeFuMessageSendReqVO.java | 43 ++++++ .../kefu/AppKeFuConversationController.java | 35 +++++ .../app/kefu/AppKeFuMessageController.java | 58 ++++++++ .../AppKeFuConversationRespVO.java | 42 ++++++ .../vo/message/AppKeFuMessagePageReqVO.java | 18 +++ .../kefu/vo/message/AppKeFuMessageRespVO.java | 42 ++++++ .../vo/message/AppKeFuMessageSendReqVO.java | 43 ++++++ .../dataobject/kefu/KeFuConversationDO.java | 8 +- .../mysql/kefu/KeFuConversationMapper.java | 38 ++++++ .../dal/mysql/kefu/KeFuMessageMapper.java | 42 ++++++ .../service/kefu/KeFuConversationService.java | 78 +++++++++++ .../kefu/KeFuConversationServiceImpl.java | 88 ++++++++++++ .../service/kefu/KeFuMessageService.java | 40 ++++++ .../service/kefu/KeFuMessageServiceImpl.java | 128 ++++++++++++++++++ .../module/member/api/user/MemberUserApi.java | 8 ++ .../member/api/user/MemberUserApiImpl.java | 11 ++ 24 files changed, 993 insertions(+), 4 deletions(-) create mode 100644 sql/mysql/mall-promotion-kefu.sql create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java diff --git a/sql/mysql/mall-promotion-kefu.sql b/sql/mysql/mall-promotion-kefu.sql new file mode 100644 index 000000000..e0b478f57 --- /dev/null +++ b/sql/mysql/mall-promotion-kefu.sql @@ -0,0 +1,37 @@ +DROP TABLE IF EXISTS `promotion_kefu_conversation`; +CREATE TABLE `promotion_kefu_conversation` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` BIGINT NOT NULL COMMENT '会话所属用户', + `last_message_time` DATETIME NOT NULL COMMENT '最后聊天时间', + `last_message_content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', + `last_message_content_type` INT NOT NULL COMMENT '最后发送的消息类型', + `admin_pinned` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', + `user_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', + `admin_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见', + `admin_unread_message_count` INT NOT NULL COMMENT '管理员未读消息数', + `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服会话' ROW_FORMAT = Dynamic; + +DROP TABLE IF EXISTS `promotion_kefu_message`; +CREATE TABLE `promotion_kefu_message` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', + `conversation_id` BIGINT NOT NULL COMMENT '会话编号', + `sender_id` BIGINT NOT NULL COMMENT '发送人编号', + `sender_type` INT NOT NULL COMMENT '发送人类型', + `receiver_id` BIGINT NOT NULL COMMENT '接收人编号', + `receiver_type` INT NOT NULL COMMENT '接收人类型', + `content_type` INT NOT NULL COMMENT '消息类型', + `content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', + `read_status` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', + `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服消息' ROW_FORMAT = Dynamic; diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java index 3b19d616a..8cebd6e13 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java @@ -125,4 +125,10 @@ public interface ErrorCodeConstants { ErrorCode DIY_PAGE_NOT_EXISTS = new ErrorCode(1_013_018_000, "装修页面不存在"); ErrorCode DIY_PAGE_NAME_USED = new ErrorCode(1_013_018_001, "装修页面名称({})已经被使用"); + // ========== 客服会话 1-013-019-000 ========== + ErrorCode KEFU_CONVERSATION_NOT_EXISTS = new ErrorCode(1_013_019_000, "客服会话不存在"); + + // ========== 客服消息 1-013-020-000 ========== + ErrorCode KEFU_MESSAGE_NOT_EXISTS = new ErrorCode(1_013_020_000, "客服消息不存在"); + } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java new file mode 100644 index 000000000..099cc264c --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 客服会话") +@RestController +@RequestMapping("/promotion/kefu-conversation") +@Validated +public class KeFuConversationController { + + @Resource + private KeFuConversationService conversationService; + + @PostMapping("/update-pinned") + @Operation(summary = "置顶客服会话") + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") + public CommonResult updatePinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { + conversationService.updatePinned(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除客服会话") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:delete')") + public CommonResult deleteKefuConversation(@RequestParam("id") Long id) { + conversationService.deleteKefuConversation(id); + return success(true); + } + + @GetMapping("/list") + @Operation(summary = "获得客服会话列表") + @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')") + public CommonResult> getKefuConversationPage() { + return success(BeanUtils.toBean(conversationService.getKefuConversationList(), KeFuConversationRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java new file mode 100644 index 000000000..b8adada3a --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu; + +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import org.springframework.web.bind.annotation.*; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.security.access.prepost.PreAuthorize; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; + +import jakarta.validation.*; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; + +@Tag(name = "管理后台 - 客服消息") +@RestController +@RequestMapping("/promotion/kefu-message") +@Validated +public class KeFuMessageController { + + @Resource + private KeFuMessageService messageService; + + @PostMapping("/send") + @Operation(summary = "发送客服消息") + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')") + public CommonResult createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { + return success(messageService.sendKefuMessage(sendReqVO)); + } + + @PutMapping("/update-read-status") + @Operation(summary = "更新客服消息已读状态") + @Parameter(name = "conversationId", description = "会话编号", required = true) + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") + public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { + messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得客服消息分页") + @PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')") + public CommonResult> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { + PageResult pageResult = messageService.getKefuMessagePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java new file mode 100644 index 000000000..b3748e147 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 客服会话 Response VO") +@Data +public class KeFuConversationRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24988") + private Long id; + + @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") + private Long userId; + + @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + private LocalDateTime lastMessageTime; + + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") + private String lastMessageContent; + + @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") + private Integer lastMessageContentType; + + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") + private Boolean adminPinned; + + @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + private Boolean userDeleted; + + @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + private Boolean adminDeleted; + + @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") + private Integer adminUnreadMessageCount; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java new file mode 100644 index 000000000..235f78227 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 客服会话置顶 Request VO") +@Data +public class KeFuConversationUpdatePinnedReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") + @NotNull(message = "会话编号,不能为空") + private Long id; + + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + @NotNull(message = "管理端置顶,不能为空") + private Boolean adminPinned; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java new file mode 100644 index 000000000..ec5e7261c --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message; + +import lombok.*; +import io.swagger.v3.oas.annotations.media.Schema; +import cn.iocoder.yudao.framework.common.pojo.PageParam; + +@Schema(description = "管理后台 - 客服消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class KeFuMessagePageReqVO extends PageParam { + + @Schema(description = "会话编号", example = "12580") + private Long conversationId; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java new file mode 100644 index 000000000..41f9c52f6 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; +import com.alibaba.excel.annotation.*; + +@Schema(description = "管理后台 - 客服消息 Response VO") +@Data +public class KeFuMessageRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") + private Long id; + + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") + private Long conversationId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") + private Long senderId; + + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer senderType; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") + private Long receiverId; + + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer receiverType; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer contentType; + + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "是否已读", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Boolean readStatus; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java new file mode 100644 index 000000000..5ac8f04c4 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 发送客服消息 Request VO") +@Data +public class KeFuMessageSendReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") + private Long id; + + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") + @NotNull(message = "会话编号不能为空") + private Long conversationId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") + @NotNull(message = "发送人编号不能为空") + private Long senderId; + + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "发送人类型不能为空") + private Integer senderType; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人类型不能为空") + private Integer receiverType; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "消息类型不能为空") + private Integer contentType; + + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "消息不能为空") + private String content; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java new file mode 100644 index 000000000..15c0e2892 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation.AppKeFuConversationRespVO; +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "用户 APP - 客户会话") +@RestController +@RequestMapping("/promotion/kefu-conversation") +@Validated +public class AppKeFuConversationController { + + @Resource + private KeFuConversationService conversationService; + + @GetMapping("/get") + @Operation(summary = "获得客服会话") + @PreAuthenticated + public CommonResult getDiyPage() { + return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java new file mode 100644 index 000000000..1af72dba7 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; +import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - 客服消息") +@RestController +@RequestMapping("/promotion/kefu-message") +@Validated +public class AppKeFuMessageController { + + @Resource + private KeFuMessageService kefuMessageService; + + @PostMapping("/send") + @Operation(summary = "发送客服消息") + @PreAuthenticated + public CommonResult createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) { + return success(kefuMessageService.sendKefuMessage(BeanUtils.toBean(sendReqVO, KeFuMessageSendReqVO.class))); + } + + @PutMapping("/update-read-status") + @Operation(summary = "更新客服消息已读状态") + @Parameter(name = "conversationId", description = "会话编号", required = true) + @PreAuthenticated + public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { + kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得客服消息分页") + @PreAuthenticated + public CommonResult> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { + PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO); + return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java new file mode 100644 index 000000000..39a295dc1 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.LocalDateTime; + +@Schema(description = "用户 App - 客服会话 Response VO") +@Data +public class AppKeFuConversationRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24988") + private Long id; + + @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") + private Long userId; + + @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + private LocalDateTime lastMessageTime; + + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") + private String lastMessageContent; + + @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") + private Integer lastMessageContentType; + + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") + private Boolean adminPinned; + + @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + private Boolean userDeleted; + + @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + private Boolean adminDeleted; + + @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") + private Integer adminUnreadMessageCount; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java new file mode 100644 index 000000000..a332d34ad --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Schema(description = "用户 App - 客服消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class AppKeFuMessagePageReqVO extends PageParam { + + @Schema(description = "会话编号", example = "12580") + private Long conversationId; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageRespVO.java new file mode 100644 index 000000000..fb7331afc --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "用户 App - 客服消息 Response VO") +@Data +public class AppKeFuMessageRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") + private Long id; + + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") + private Long conversationId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") + private Long senderId; + + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer senderType; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") + private Long receiverId; + + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer receiverType; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer contentType; + + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "是否已读", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Boolean readStatus; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java new file mode 100644 index 000000000..f2de632da --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "用户 App - 发送客服消息 Request VO") +@Data +public class AppKeFuMessageSendReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") + private Long id; + + @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") + @NotNull(message = "会话编号不能为空") + private Long conversationId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") + @NotNull(message = "发送人编号不能为空") + private Long senderId; + + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "发送人类型不能为空") + private Integer senderType; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") + @NotNull(message = "接收人编号不能为空") + private Long receiverId; + + @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "接收人类型不能为空") + private Integer receiverType; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "消息类型不能为空") + private Integer contentType; + + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "消息不能为空") + private String content; + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java index 04432eebe..e9a73284f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/kefu/KeFuConversationDO.java @@ -61,15 +61,15 @@ public class KeFuConversationDO extends BaseDO { /** * 用户是否可见 * - * true - 可见,默认值 - * false - 不可见,用户删除时设置为 false + * false - 可见,默认值 + * true - 不可见,用户删除时设置为 true */ private Boolean userDeleted; /** * 管理员是否可见 * - * true - 可见,默认值 - * false - 不可见,管理员删除时设置为 false + * false - 可见,默认值 + * true - 不可见,管理员删除时设置为 true */ private Boolean adminDeleted; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java new file mode 100644 index 000000000..eae9b7401 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.promotion.dal.mysql.kefu; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 客服会话 Mapper + * + * @author HUIHUI + */ +@Mapper +public interface KeFuConversationMapper extends BaseMapperX { + + default List selectListWithSort() { + return selectList(new LambdaQueryWrapperX() + .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE) + .orderByDesc(KeFuConversationDO::getAdminPinned) // 置顶优先 + .orderByDesc(KeFuConversationDO::getCreateTime)); + } + + default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + updateWrapper.eq(KeFuConversationDO::getId, id); + if (count != null && count > 0) { // 情况一:会员发送消息时增加管理员的未读消息数 + updateWrapper.setSql("admin_unread_message_count = admin_unread_message_count + 1"); + } else { // 情况二:管理员已读后重置 + updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0); + } + + update(updateWrapper); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java new file mode 100644 index 000000000..5ec0a6cd9 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.promotion.dal.mysql.kefu; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Collection; +import java.util.List; + +/** + * 客服消息 Mapper + * + * @author HUIHUI + */ +@Mapper +public interface KeFuMessageMapper extends BaseMapperX { + + default PageResult selectPage(KeFuMessagePageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(KeFuMessageDO::getConversationId, reqVO.getConversationId()) + .orderByDesc(KeFuMessageDO::getId)); + } + + default List selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, Long receiverId, Boolean readStatus) { + return selectList(new LambdaQueryWrapper() + .eq(KeFuMessageDO::getConversationId, conversationId) + .eq(KeFuMessageDO::getReceiverId, receiverId) + .eq(KeFuMessageDO::getReadStatus, readStatus)); + } + + default void updateReadStstusBatchByIds(Collection ids, Boolean readStatus) { + update(new LambdaUpdateWrapper() + .in(KeFuMessageDO::getId, ids) + .set(KeFuMessageDO::getReadStatus, readStatus)); + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java new file mode 100644 index 000000000..828626310 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -0,0 +1,78 @@ +package cn.iocoder.yudao.module.promotion.service.kefu; + +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 客服会话 Service 接口 + * + * @author HUIHUI + */ +public interface KeFuConversationService { + + /** + * 删除客服会话 + * + * @param id 编号 + */ + void deleteKefuConversation(Long id); + + /** + * 客服会话置顶 + * + * @param updateReqVO 请求 + */ + void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO); + + /** + * 更新会话客服消息冗余信息 + * + * @param id 编号 + * @param lastMessageTime 最后聊天时间 + * @param lastMessageContent 最后聊天内容 + * @param lastMessageContentType 最后聊天内容类型 + */ + void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType); + + /** + * 更新管理员未读消息数 + * + * @param id 编号 + * @param count 数量:0 则重置 1 则消息数加一 + */ + void updateAdminUnreadMessageCountByConversationId(Long id, Integer count); + + /** + * 更新会话对于管理员是否可见 + * + * @param adminDeleted 管理员是否可见 + */ + void updateConversationAdminDeleted(Long id, Boolean adminDeleted); + + /** + * 获得客服会话列表 + * + * @return 会话列表 + */ + List getKefuConversationList(); + + /** + * 获得或创建会话 + * + * @param userId 用户编号 + * @return 客服会话 + */ + KeFuConversationDO getOrCreateConversation(Long userId); + + /** + * 校验客服会话是否存在 + * + * @param id 编号 + * @return 客服会话 + */ + KeFuConversationDO validateKefuConversationExists(Long id); + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java new file mode 100644 index 000000000..416e613ff --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -0,0 +1,88 @@ +package cn.iocoder.yudao.module.promotion.service.kefu; + +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper; +import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.KEFU_CONVERSATION_NOT_EXISTS; + +/** + * 客服会话 Service 实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class KeFuConversationServiceImpl implements KeFuConversationService { + + @Resource + private KeFuConversationMapper conversationMapper; + + @Override + public void deleteKefuConversation(Long id) { + // 校验存在 + validateKefuConversationExists(id); + + // 只有管理员端可以删除会话,也不真的删,只是管理员端看不到啦 + conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminDeleted(Boolean.TRUE)); + } + + @Override + public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { + // 只有管理员端可以置顶会话 + conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); + } + + @Override + public void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType) { + conversationMapper.updateById(new KeFuConversationDO().setId(id).setLastMessageTime(lastMessageTime) + .setLastMessageContent(lastMessageContent).setLastMessageContentType(lastMessageContentType)); + } + + @Override + public void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { + conversationMapper.updateAdminUnreadMessageCountByConversationId(id, count); + } + + @Override + public void updateConversationAdminDeleted(Long id, Boolean adminDeleted) { + conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminDeleted(adminDeleted)); + } + + @Override + public List getKefuConversationList() { + return conversationMapper.selectListWithSort(); + } + + @Override + public KeFuConversationDO getOrCreateConversation(Long userId) { + KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); + if (conversation == null) { // 没有历史会话则初始化一个新会话 + conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now()) + .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) + .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE) + .setAdminUnreadMessageCount(0); + conversationMapper.insert(conversation); + } + return conversation; + } + + @Override + public KeFuConversationDO validateKefuConversationExists(Long id) { + KeFuConversationDO conversationDO = conversationMapper.selectById(id); + if (conversationDO == null) { + throw exception(KEFU_CONVERSATION_NOT_EXISTS); + } + + return conversationDO; + } + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java new file mode 100644 index 000000000..10d5aca32 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.promotion.service.kefu; + +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; +import jakarta.validation.Valid; + +/** + * 客服消息 Service 接口 + * + * @author HUIHUI + */ +public interface KeFuMessageService { + + /** + * 发送客服消息 + * + * @param sendReqVO 信息 + * @return 编号 + */ + Long sendKefuMessage(@Valid KeFuMessageSendReqVO sendReqVO); + + /** + * 更新消息已读状态 + * + * @param conversationId 会话编号 + * @param receiverId 用户编号 + */ + void updateKefuMessageReadStatus(Long conversationId, Long receiverId); + + /** + * 获得客服消息分页 + * + * @param pageReqVO 分页查询 + * @return 客服消息分页 + */ + PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO); + +} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java new file mode 100644 index 000000000..ff6224407 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.module.promotion.service.kefu; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; +import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import jakarta.annotation.Resource; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; + +import java.time.LocalDateTime; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getFirst; + +/** + * 客服消息 Service 实现类 + * + * @author HUIHUI + */ +@Service +@Validated +public class KeFuMessageServiceImpl implements KeFuMessageService { + + private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 + + @Resource + private KeFuMessageMapper messageMapper; + @Resource + private KeFuConversationService conversationService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private MemberUserApi memberUserApi; + @Resource + private WebSocketSenderApi webSocketSenderApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { + // 1.1 校验会话是否存在 + KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); + // 1.2 校验接收人是否存在 + validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType()); + + // 2.1 保存消息 + KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); + messageMapper.insert(kefuMessage); + // 2.2 更新会话消息冗余 + conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(), + kefuMessage.getContent(), kefuMessage.getContentType()); + // 2.3 更新管理员未读消息数 + if (UserTypeEnum.ADMIN.getValue().equals(kefuMessage.getReceiverType())) { + conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1); + } + // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) { + conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); + } + + // 3. 发送消息 + getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); + + // 返回 + return kefuMessage.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateKefuMessageReadStatus(Long conversationId, Long receiverId) { + // 1.1 校验会话是否存在 + conversationService.validateKefuConversationExists(conversationId); + // 1.2 查询接收人所有的未读消息 + List messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus( + conversationId, receiverId, Boolean.FALSE); + // 1.3 情况一:没有未读消息 + if (CollUtil.isEmpty(messageList)) { + return; + } + + // 2.1 情况二:更新未读消息状态为已读 + messageMapper.updateReadStstusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), Boolean.TRUE); + // 2.2 更新管理员未读消息数 + KeFuMessageDO message = getFirst(messageList); + assert message != null; + if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) { + conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0); + } + // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态 + getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange"); + } + + private void validateReceiverExist(Long receiverId, Integer receiverType) { + if (UserTypeEnum.ADMIN.getValue().equals(receiverType)) { + adminUserApi.validateUser(receiverId); + } + if (UserTypeEnum.MEMBER.getValue().equals(receiverType)) { + memberUserApi.validateUser(receiverId); + } + } + + @Async + public void sendAsyncMessage(Integer userType, Long userId, Object content) { + webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content); + } + + @Override + public PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) { + return messageMapper.selectPage(pageReqVO); + } + + private KeFuMessageServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); + } + +} \ No newline at end of file diff --git a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java index c9fb80100..da74aaa92 100644 --- a/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java +++ b/yudao-module-member/yudao-module-member-api/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApi.java @@ -57,4 +57,12 @@ public interface MemberUserApi { * @return 用户信息 */ MemberUserRespDTO getUserByMobile(String mobile); + + /** + * 校验用户是否存在 + * + * @param id 用户编号 + */ + void validateUser(Long id); + } diff --git a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java index 659c39b57..960930ddc 100644 --- a/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java +++ b/yudao-module-member/yudao-module-member-biz/src/main/java/cn/iocoder/yudao/module/member/api/user/MemberUserApiImpl.java @@ -11,6 +11,9 @@ import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.member.enums.ErrorCodeConstants.USER_MOBILE_NOT_EXISTS; + /** * 会员用户的 API 实现类 * @@ -44,4 +47,12 @@ public class MemberUserApiImpl implements MemberUserApi { return MemberUserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile)); } + @Override + public void validateUser(Long id) { + MemberUserDO user = userService.getUser(id); + if (user == null) { + throw exception(USER_MOBILE_NOT_EXISTS); + } + } + } From d89a19a92ba588724ae7858fc8488b1a6fdfac31 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 4 Jun 2024 20:25:24 +0800 Subject: [PATCH 20/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91=E5=95=86=E5=9F=8E=EF=BC=9A=E5=AE=A2=E6=9C=8D?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kehu/KeFuMessageContentTypeEnum.java | 4 +-- .../kefu/KeFuConversationController.java | 1 + .../admin/kefu/KeFuMessageController.java | 31 +++++++++---------- .../conversation/KeFuConversationRespVO.java | 6 ++-- .../KeFuConversationUpdatePinnedReqVO.java | 4 +-- .../kefu/vo/message/KeFuMessagePageReqVO.java | 1 + .../kefu/vo/message/KeFuMessageSendReqVO.java | 2 ++ .../kefu/AppKeFuConversationController.java | 2 ++ .../vo/message/AppKeFuMessagePageReqVO.java | 1 + .../mysql/kefu/KeFuConversationMapper.java | 3 +- .../dal/mysql/kefu/KeFuMessageMapper.java | 5 ++- .../service/kefu/KeFuConversationService.java | 7 ++++- .../kefu/KeFuConversationServiceImpl.java | 12 +++---- .../service/kefu/KeFuMessageService.java | 1 + .../service/kefu/KeFuMessageServiceImpl.java | 11 +++++-- .../brokerage/BrokerageUserServiceImpl.java | 3 +- 16 files changed, 60 insertions(+), 34 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java index 29c3b34c4..d12e3ed44 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java @@ -7,7 +7,7 @@ import lombok.Getter; import java.util.Arrays; /** - * 消息类型枚举 + * 客服消息的类型枚举 * * @author HUIHUI */ @@ -19,7 +19,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable { IMAGE(2, "图片消息"), VOICE(3, "语音消息"), VIDEO(4, "视频消息"), - // 和正常消息隔离下 + // ========== 商城特殊消息 ========== PRODUCT(10, "商品消息"), ORDER(11, "订单消息"); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 099cc264c..09be04c0e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -27,6 +27,7 @@ public class KeFuConversationController { @Resource private KeFuConversationService conversationService; + // TODO @puhui999:updateConversationPinned @PostMapping("/update-pinned") @Operation(summary = "置顶客服会话") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index b8adada3a..4409905c1 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -1,26 +1,24 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; -import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.security.access.prepost.PreAuthorize; -import io.swagger.v3.oas.annotations.tags.Tag; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.Operation; - -import jakarta.validation.*; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 客服消息") @RestController @@ -47,6 +45,7 @@ public class KeFuMessageController { return success(true); } + // TODO @puhui999:这个应该是某个会话,上翻、下翻;不是传统的分页哈; @GetMapping("/page") @Operation(summary = "获得客服消息分页") @PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')") diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java index b3748e147..2fdad1f80 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java @@ -15,9 +15,11 @@ public class KeFuConversationRespVO { @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") private Long userId; - @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime lastMessageTime; + // TODO @puhui999:, 缺了空格哈 + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") private String lastMessageContent; @@ -36,7 +38,7 @@ public class KeFuConversationRespVO { @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") private Integer adminUnreadMessageCount; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java index 235f78227..9b108891a 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationUpdatePinnedReqVO.java @@ -9,11 +9,11 @@ import lombok.Data; public class KeFuConversationUpdatePinnedReqVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") - @NotNull(message = "会话编号,不能为空") + @NotNull(message = "会话编号不能为空") private Long id; @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") - @NotNull(message = "管理端置顶,不能为空") + @NotNull(message = "管理端置顶不能为空") private Boolean adminPinned; } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java index ec5e7261c..f959aeb8b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; @Schema(description = "管理后台 - 客服消息分页 Request VO") @Data +// TODO @puhui999:不用 @EqualsAndHashCode 哈 @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class KeFuMessagePageReqVO extends PageParam { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java index 5ac8f04c4..0dca02410 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java @@ -9,6 +9,8 @@ import lombok.Data; @Data public class KeFuMessageSendReqVO { + // TODO @puhui999:貌似字段多了;1)id 不用;2)senderId、senderType 不用;3)receiverId、receiverType 也不用;原因可以想下哈 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") private Long id; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java index 15c0e2892..caf754edd 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java @@ -25,10 +25,12 @@ public class AppKeFuConversationController { @Resource private KeFuConversationService conversationService; + // TODO @puhui999:接口名不对噢; @GetMapping("/get") @Operation(summary = "获得客服会话") @PreAuthenticated public CommonResult getDiyPage() { + // TODO @puhui999:建议获取;和转换,分成 2 个哈;干净一些; return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class)); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java index a332d34ad..12afd6001 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java @@ -8,6 +8,7 @@ import lombok.ToString; @Schema(description = "用户 App - 客服消息分页 Request VO") @Data +// TODO @puhui999:不用 @EqualsAndHashCode 哈 @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class AppKeFuMessagePageReqVO extends PageParam { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index eae9b7401..909ce32ab 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -16,6 +16,7 @@ import java.util.List; @Mapper public interface KeFuConversationMapper extends BaseMapperX { + // TODO @puhui999:排序可以交给前端,或者 controller;数据库的计算尽量少哈; default List selectListWithSort() { return selectList(new LambdaQueryWrapperX() .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE) @@ -23,6 +24,7 @@ public interface KeFuConversationMapper extends BaseMapperX .orderByDesc(KeFuConversationDO::getCreateTime)); } + // TODO @puhui999:是不是置零,用 update 就 ok 拉;然后单独搞个 +1 的方法; default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(KeFuConversationDO::getId, id); @@ -31,7 +33,6 @@ public interface KeFuConversationMapper extends BaseMapperX } else { // 情况二:管理员已读后重置 updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0); } - update(updateWrapper); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java index 5ec0a6cd9..87dfb5340 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -26,13 +26,16 @@ public interface KeFuMessageMapper extends BaseMapperX { .orderByDesc(KeFuMessageDO::getId)); } - default List selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, Long receiverId, Boolean readStatus) { + default List selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, + Long receiverId, + Boolean readStatus) { return selectList(new LambdaQueryWrapper() .eq(KeFuMessageDO::getConversationId, conversationId) .eq(KeFuMessageDO::getReceiverId, receiverId) .eq(KeFuMessageDO::getReadStatus, readStatus)); } + // TODO @puhui999:status 拼写不对哈;ps:是不是搞个 ids + entity 的更新,更通用点 default void updateReadStstusBatchByIds(Collection ids, Boolean readStatus) { update(new LambdaUpdateWrapper() .in(KeFuMessageDO::getId, ids) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index 828626310..d6179383d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; import java.time.LocalDateTime; import java.util.List; +// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服会话 Service 接口 * @@ -20,6 +21,7 @@ public interface KeFuConversationService { */ void deleteKefuConversation(Long id); + // TODO @puhui999:是不是方法名,体现出更新的是管理员的置顶哈 /** * 客服会话置顶 * @@ -27,6 +29,7 @@ public interface KeFuConversationService { */ void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO); + // TODO @puhui999:updateConversationLastMessage 会好点哈 /** * 更新会话客服消息冗余信息 * @@ -60,7 +63,9 @@ public interface KeFuConversationService { List getKefuConversationList(); /** - * 获得或创建会话 + * 【会员】获得或创建会话 + * + * 对于【会员】来说,有且仅有一个对话 * * @param userId 用户编号 * @return 客服会话 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index 416e613ff..c78989c6c 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -37,7 +37,6 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { - // 只有管理员端可以置顶会话 conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); } @@ -62,10 +61,12 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { return conversationMapper.selectListWithSort(); } + // TODO @puhui999:貌似这个对话,得用户主动创建。不然管理员会看到一个空的对话? @Override public KeFuConversationDO getOrCreateConversation(Long userId) { KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); - if (conversation == null) { // 没有历史会话则初始化一个新会话 + // 没有历史会话,则初始化一个新会话 + if (conversation == null) { conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now()) .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE) @@ -77,12 +78,11 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public KeFuConversationDO validateKefuConversationExists(Long id) { - KeFuConversationDO conversationDO = conversationMapper.selectById(id); - if (conversationDO == null) { + KeFuConversationDO conversation = conversationMapper.selectById(id); + if (conversation == null) { throw exception(KEFU_CONVERSATION_NOT_EXISTS); } - - return conversationDO; + return conversation; } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index 10d5aca32..7f7a77375 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMe import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import jakarta.validation.Valid; +// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服消息 Service 接口 * diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index ff6224407..861f7cb92 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -34,12 +34,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @Validated public class KeFuMessageServiceImpl implements KeFuMessageService { + // TODO @puhui999:@芋艿:捉摸要不要拿到一个地方枚举; private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 + // TODO @puhui999:kefuMessageMapper;因为 messageMapper 可能会重叠 @Resource private KeFuMessageMapper messageMapper; + @Resource private KeFuConversationService conversationService; + @Resource private AdminUserApi adminUserApi; @Resource @@ -58,6 +62,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { // 2.1 保存消息 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); messageMapper.insert(kefuMessage); + // TODO @puhui999:是不是 updateConversationMessage,里面统一处理未读、恢复;直接设置 KeFuMessageDO 作为参数好了。。。 // 2.2 更新会话消息冗余 conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(), kefuMessage.getContent(), kefuMessage.getContentType()); @@ -66,14 +71,13 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1); } // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 + // TODO @puhui999:建议 && 换一行 if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) { conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); } // 3. 发送消息 getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); - - // 返回 return kefuMessage.getId(); } @@ -83,6 +87,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { // 1.1 校验会话是否存在 conversationService.validateKefuConversationExists(conversationId); // 1.2 查询接收人所有的未读消息 + // TODO @puhui999:应该不能 receiverId 过滤哈。因为多个客服,一个人点了,就都点了。 List messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus( conversationId, receiverId, Boolean.FALSE); // 1.3 情况一:没有未读消息 @@ -98,7 +103,9 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) { conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0); } + // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态 + // TODO @puhui999:待定~ getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange"); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java index 08e42c9a0..697829f4c 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageUserServiceImpl.java @@ -131,8 +131,9 @@ public class BrokerageUserServiceImpl implements BrokerageUserService { @Override public BrokerageUserDO getOrCreateBrokerageUser(Long id) { BrokerageUserDO brokerageUser = brokerageUserMapper.selectById(id); + // 特殊:人人分销的情况下,如果分销人为空则创建分销人 if (brokerageUser == null && ObjUtil.equal(BrokerageEnabledConditionEnum.ALL.getCondition(), - tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) { // 人人分销的情况下,如果分销人为空则创建分销人 + tradeConfigService.getTradeConfig().getBrokerageEnabledCondition())) { brokerageUser = new BrokerageUserDO().setId(id).setBrokerageEnabled(true).setBrokeragePrice(0) .setBrokerageTime(LocalDateTime.now()).setFrozenPrice(0); brokerageUserMapper.insert(brokerageUser); From 285a431616e8cfcf55f6f473cf0ef918c9799bc3 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 17 Jun 2024 10:17:46 +0800 Subject: [PATCH 21/35] =?UTF-8?q?MALL-KEFU:=20=E6=A0=B9=E6=8D=AE=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E8=AF=84=E5=AE=A1=E5=AE=8C=E5=96=84=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/WebSocketMessageTypeConstants.java | 15 ++++ .../kehu/KeFuMessageContentTypeEnum.java | 1 + .../kefu/KeFuConversationController.java | 7 +- .../admin/kefu/KeFuMessageController.java | 6 +- .../conversation/KeFuConversationRespVO.java | 16 ++-- .../kefu/vo/message/KeFuMessagePageReqVO.java | 2 - .../kefu/AppKeFuConversationController.java | 37 --------- .../app/kefu/AppKeFuMessageController.java | 6 +- .../AppKeFuConversationRespVO.java | 42 ---------- .../vo/message/AppKeFuMessagePageReqVO.java | 2 - .../vo/message/AppKeFuMessageSendReqVO.java | 16 ---- .../controller/app/kefu/vo/package-info.java | 1 + .../mysql/kefu/KeFuConversationMapper.java | 24 +++--- .../dal/mysql/kefu/KeFuMessageMapper.java | 13 +-- .../service/kefu/KeFuConversationService.java | 30 +++---- .../kefu/KeFuConversationServiceImpl.java | 37 ++++++--- .../service/kefu/KeFuMessageService.java | 17 ++-- .../service/kefu/KeFuMessageServiceImpl.java | 81 ++++++++++--------- .../src/main/resources/application.yaml | 2 +- 19 files changed, 144 insertions(+), 211 deletions(-) create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java delete mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java new file mode 100644 index 000000000..d060998ad --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.promotion.enums; + +/** + * websocket 消息类型枚举类 + * + * @author HUIHUI + */ +public interface WebSocketMessageTypeConstants { + + //======================= mall 客服 ======================= + + String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 + String KEFU_MESSAGE_ADMIN_READ = "kefu_message_read_status_change"; // 客服消息管理员已读 + +} diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java index d12e3ed44..616630c2e 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java @@ -19,6 +19,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable { IMAGE(2, "图片消息"), VOICE(3, "语音消息"), VIDEO(4, "视频消息"), + SYSTEM(5, "系统消息"), // ========== 商城特殊消息 ========== PRODUCT(10, "商品消息"), ORDER(11, "订单消息"); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 09be04c0e..5a1d9e77f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -27,12 +27,11 @@ public class KeFuConversationController { @Resource private KeFuConversationService conversationService; - // TODO @puhui999:updateConversationPinned - @PostMapping("/update-pinned") + @PostMapping("/update-conversation-pinned") @Operation(summary = "置顶客服会话") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") - public CommonResult updatePinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { - conversationService.updatePinned(updateReqVO); + public CommonResult updateConversationPinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { + conversationService.updateAdminPinned(updateReqVO); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index 4409905c1..04a32e156 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -18,7 +19,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 客服消息") @RestController @@ -37,11 +37,11 @@ public class KeFuMessageController { } @PutMapping("/update-read-status") - @Operation(summary = "更新客服消息已读状态") + @Operation(summary = "更新会员客服消息已读状态") @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + messageService.updateKefuMessageReadStatus(conversationId); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java index 2fdad1f80..9f499e09e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; import java.time.LocalDateTime; @@ -18,24 +18,22 @@ public class KeFuConversationRespVO { @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime lastMessageTime; - // TODO @puhui999:, 缺了空格哈 - - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") + @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "嗨,您好啊") private String lastMessageContent; - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") + @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer lastMessageContentType; - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") + @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") private Boolean adminPinned; - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean userDeleted; - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") + @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") private Boolean adminDeleted; - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") + @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED, example = "6") private Integer adminUnreadMessageCount; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java index f959aeb8b..f60c997be 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessagePageReqVO.java @@ -6,8 +6,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; @Schema(description = "管理后台 - 客服消息分页 Request VO") @Data -// TODO @puhui999:不用 @EqualsAndHashCode 哈 -@EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class KeFuMessagePageReqVO extends PageParam { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java deleted file mode 100644 index caf754edd..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuConversationController.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.promotion.controller.app.kefu; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation.AppKeFuConversationRespVO; -import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "用户 APP - 客户会话") -@RestController -@RequestMapping("/promotion/kefu-conversation") -@Validated -public class AppKeFuConversationController { - - @Resource - private KeFuConversationService conversationService; - - // TODO @puhui999:接口名不对噢; - @GetMapping("/get") - @Operation(summary = "获得客服会话") - @PreAuthenticated - public CommonResult getDiyPage() { - // TODO @puhui999:建议获取;和转换,分成 2 个哈;干净一些; - return success(BeanUtils.toBean(conversationService.getOrCreateConversation(getLoginUserId()), AppKeFuConversationRespVO.class)); - } - -} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index 1af72dba7..d17990857 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.controller.app.kefu; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -35,7 +36,8 @@ public class AppKeFuMessageController { @Operation(summary = "发送客服消息") @PreAuthenticated public CommonResult createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) { - return success(kefuMessageService.sendKefuMessage(BeanUtils.toBean(sendReqVO, KeFuMessageSendReqVO.class))); + sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.MEMBER.getValue()); // 设置用户编号和类型 + return success(kefuMessageService.sendKefuMessage(sendReqVO)); } @PutMapping("/update-read-status") @@ -43,7 +45,7 @@ public class AppKeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthenticated public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId()); + kefuMessageService.updateKefuMessageReadStatus(conversationId); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java deleted file mode 100644 index 39a295dc1..000000000 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/conversation/AppKeFuConversationRespVO.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.conversation; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import java.time.LocalDateTime; - -@Schema(description = "用户 App - 客服会话 Response VO") -@Data -public class AppKeFuConversationRespVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24988") - private Long id; - - @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") - private Long userId; - - @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") - private LocalDateTime lastMessageTime; - - @Schema(description = "最后聊天内容", requiredMode = Schema.RequiredMode.REQUIRED,example = "嗨,您好啊") - private String lastMessageContent; - - @Schema(description = "最后发送的消息类型", requiredMode = Schema.RequiredMode.REQUIRED,example = "1") - private Integer lastMessageContentType; - - @Schema(description = "管理端置顶", requiredMode = Schema.RequiredMode.REQUIRED,example = "false") - private Boolean adminPinned; - - @Schema(description = "用户是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") - private Boolean userDeleted; - - @Schema(description = "管理员是否可见", requiredMode = Schema.RequiredMode.REQUIRED,example = "true") - private Boolean adminDeleted; - - @Schema(description = "管理员未读消息数", requiredMode = Schema.RequiredMode.REQUIRED,example = "6") - private Integer adminUnreadMessageCount; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED,example = "2024-01-01 00:00:00") - private LocalDateTime createTime; - -} \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java index 12afd6001..a354a5858 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessagePageReqVO.java @@ -8,8 +8,6 @@ import lombok.ToString; @Schema(description = "用户 App - 客服消息分页 Request VO") @Data -// TODO @puhui999:不用 @EqualsAndHashCode 哈 -@EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) public class AppKeFuMessagePageReqVO extends PageParam { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java index f2de632da..78df4112d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java @@ -12,30 +12,14 @@ public class AppKeFuMessageSendReqVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") private Long id; - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") - @NotNull(message = "会话编号不能为空") - private Long conversationId; - @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") - @NotNull(message = "发送人编号不能为空") private Long senderId; - @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "发送人类型不能为空") private Integer senderType; - @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") - @NotNull(message = "接收人编号不能为空") - private Long receiverId; - - @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @NotNull(message = "接收人类型不能为空") - private Integer receiverType; - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "消息类型不能为空") private Integer contentType; - @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "消息不能为空") private String content; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java new file mode 100644 index 000000000..9116686b0 --- /dev/null +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo; \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index 909ce32ab..577ce1b50 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -16,24 +16,22 @@ import java.util.List; @Mapper public interface KeFuConversationMapper extends BaseMapperX { - // TODO @puhui999:排序可以交给前端,或者 controller;数据库的计算尽量少哈; - default List selectListWithSort() { + default List selectConversationList() { return selectList(new LambdaQueryWrapperX() .eq(KeFuConversationDO::getAdminDeleted, Boolean.FALSE) - .orderByDesc(KeFuConversationDO::getAdminPinned) // 置顶优先 .orderByDesc(KeFuConversationDO::getCreateTime)); } - // TODO @puhui999:是不是置零,用 update 就 ok 拉;然后单独搞个 +1 的方法; - default void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { - LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); - updateWrapper.eq(KeFuConversationDO::getId, id); - if (count != null && count > 0) { // 情况一:会员发送消息时增加管理员的未读消息数 - updateWrapper.setSql("admin_unread_message_count = admin_unread_message_count + 1"); - } else { // 情况二:管理员已读后重置 - updateWrapper.set(KeFuConversationDO::getAdminUnreadMessageCount, 0); - } - update(updateWrapper); + default void updateAdminUnreadMessageCountWithZero(Long id) { + update(new LambdaUpdateWrapper() + .eq(KeFuConversationDO::getId, id) + .set(KeFuConversationDO::getAdminUnreadMessageCount, 0)); + } + + default void updateAdminUnreadMessageCount(Long id) { + update(new LambdaUpdateWrapper() + .eq(KeFuConversationDO::getId, id) + .setSql("admin_unread_message_count = admin_unread_message_count + 1")); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java index 87dfb5340..6a732cff0 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -26,20 +26,15 @@ public interface KeFuMessageMapper extends BaseMapperX { .orderByDesc(KeFuMessageDO::getId)); } - default List selectListByConversationIdAndReceiverIdAndReadStatus(Long conversationId, - Long receiverId, - Boolean readStatus) { + default List selectListByConversationIdAndReadStatus(Long conversationId, Boolean readStatus) { return selectList(new LambdaQueryWrapper() .eq(KeFuMessageDO::getConversationId, conversationId) - .eq(KeFuMessageDO::getReceiverId, receiverId) .eq(KeFuMessageDO::getReadStatus, readStatus)); } - // TODO @puhui999:status 拼写不对哈;ps:是不是搞个 ids + entity 的更新,更通用点 - default void updateReadStstusBatchByIds(Collection ids, Boolean readStatus) { - update(new LambdaUpdateWrapper() - .in(KeFuMessageDO::getId, ids) - .set(KeFuMessageDO::getReadStatus, readStatus)); + default void updateReadStatusBatchByIds(Collection ids, KeFuMessageDO keFuMessageDO) { + update(keFuMessageDO, new LambdaUpdateWrapper() + .in(KeFuMessageDO::getId, ids)); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index d6179383d..c4bd59976 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -2,11 +2,10 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; -import java.time.LocalDateTime; import java.util.List; -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服会话 Service 接口 * @@ -15,48 +14,43 @@ import java.util.List; public interface KeFuConversationService { /** - * 删除客服会话 + * 【管理员】删除客服会话 * * @param id 编号 */ void deleteKefuConversation(Long id); - // TODO @puhui999:是不是方法名,体现出更新的是管理员的置顶哈 /** - * 客服会话置顶 + * 【管理员】客服会话置顶 * * @param updateReqVO 请求 */ - void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO); + void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO); - // TODO @puhui999:updateConversationLastMessage 会好点哈 /** * 更新会话客服消息冗余信息 * - * @param id 编号 - * @param lastMessageTime 最后聊天时间 - * @param lastMessageContent 最后聊天内容 - * @param lastMessageContentType 最后聊天内容类型 + * @param kefuMessage 消息 */ - void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType); + void updateConversationLastMessage(KeFuMessageDO kefuMessage); /** - * 更新管理员未读消息数 + * 【管理员】将管理员未读消息计数更新为零 * - * @param id 编号 - * @param count 数量:0 则重置 1 则消息数加一 + * @param id 编号 */ - void updateAdminUnreadMessageCountByConversationId(Long id, Integer count); + void updateAdminUnreadMessageCountWithZero(Long id); /** - * 更新会话对于管理员是否可见 + * 【管理员】更新会话对于管理员是否可见 * + * @param id 编号 * @param adminDeleted 管理员是否可见 */ void updateConversationAdminDeleted(Long id, Boolean adminDeleted); /** - * 获得客服会话列表 + * 【管理员】获得客服会话列表 * * @return 会话列表 */ diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index c78989c6c..89af0eec7 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -1,11 +1,15 @@ package cn.iocoder.yudao.module.promotion.service.kefu; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; +import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuConversationMapper; import cn.iocoder.yudao.module.promotion.enums.kehu.KeFuMessageContentTypeEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; @@ -36,19 +40,35 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { } @Override - public void updatePinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { + public void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); } @Override - public void updateConversationMessage(Long id, LocalDateTime lastMessageTime, String lastMessageContent, Integer lastMessageContentType) { - conversationMapper.updateById(new KeFuConversationDO().setId(id).setLastMessageTime(lastMessageTime) - .setLastMessageContent(lastMessageContent).setLastMessageContentType(lastMessageContentType)); + @Transactional(rollbackFor = Exception.class) + public void updateConversationLastMessage(KeFuMessageDO kefuMessage) { + // 1.1 校验会话是否存在 + KeFuConversationDO conversation = validateKefuConversationExists(kefuMessage.getConversationId()); + // 1.2 更新会话消息冗余 + conversationMapper.updateById(new KeFuConversationDO().setId(kefuMessage.getConversationId()) + .setLastMessageTime(kefuMessage.getCreateTime()).setLastMessageContent(kefuMessage.getContent()) + .setLastMessageContentType(kefuMessage.getContentType())); + + // 2.2 更新管理员未读消息数 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { + conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId()); + } + // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 + if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) + && Boolean.TRUE.equals(conversation.getAdminDeleted())) { + updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); + } } @Override - public void updateAdminUnreadMessageCountByConversationId(Long id, Integer count) { - conversationMapper.updateAdminUnreadMessageCountByConversationId(id, count); + public void updateAdminUnreadMessageCountWithZero(Long id) { + validateKefuConversationExists(id); + conversationMapper.updateAdminUnreadMessageCountWithZero(id); } @Override @@ -58,17 +78,16 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public List getKefuConversationList() { - return conversationMapper.selectListWithSort(); + return conversationMapper.selectConversationList(); } - // TODO @puhui999:貌似这个对话,得用户主动创建。不然管理员会看到一个空的对话? @Override public KeFuConversationDO getOrCreateConversation(Long userId) { KeFuConversationDO conversation = conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); // 没有历史会话,则初始化一个新会话 if (conversation == null) { conversation = new KeFuConversationDO().setUserId(userId).setLastMessageTime(LocalDateTime.now()) - .setLastMessageContent("").setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) + .setLastMessageContent(StrUtil.EMPTY).setLastMessageContentType(KeFuMessageContentTypeEnum.TEXT.getType()) .setAdminPinned(Boolean.FALSE).setUserDeleted(Boolean.FALSE).setAdminDeleted(Boolean.FALSE) .setAdminUnreadMessageCount(0); conversationMapper.insert(conversation); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index 7f7a77375..ad28df0b8 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -3,10 +3,10 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import jakarta.validation.Valid; -// TODO @puhui999:可以在每个方法前面,加个【会员】【管理员】区分下 /** * 客服消息 Service 接口 * @@ -15,7 +15,7 @@ import jakarta.validation.Valid; public interface KeFuMessageService { /** - * 发送客服消息 + * 【管理员】发送客服消息 * * @param sendReqVO 信息 * @return 编号 @@ -23,12 +23,19 @@ public interface KeFuMessageService { Long sendKefuMessage(@Valid KeFuMessageSendReqVO sendReqVO); /** - * 更新消息已读状态 + * 【会员】发送客服消息 + * + * @param sendReqVO 信息 + * @return 编号 + */ + Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO); + + /** + * 【管理员】更新消息已读状态 * * @param conversationId 会话编号 - * @param receiverId 用户编号 */ - void updateKefuMessageReadStatus(Long conversationId, Long receiverId); + void updateKefuMessageReadStatus(Long conversationId); /** * 获得客服消息分页 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index 861f7cb92..b7617bf32 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -9,6 +10,7 @@ import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper; @@ -19,11 +21,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.time.LocalDateTime; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getFirst; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ; +import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE; /** * 客服消息 Service 实现类 @@ -34,16 +36,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. @Validated public class KeFuMessageServiceImpl implements KeFuMessageService { - // TODO @puhui999:@芋艿:捉摸要不要拿到一个地方枚举; - private static final String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 - - // TODO @puhui999:kefuMessageMapper;因为 messageMapper 可能会重叠 @Resource - private KeFuMessageMapper messageMapper; - + private KeFuMessageMapper keFuMessageMapper; @Resource private KeFuConversationService conversationService; - @Resource private AdminUserApi adminUserApi; @Resource @@ -55,58 +51,60 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { @Transactional(rollbackFor = Exception.class) public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { // 1.1 校验会话是否存在 - KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); + conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); // 1.2 校验接收人是否存在 validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType()); // 2.1 保存消息 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); - messageMapper.insert(kefuMessage); - // TODO @puhui999:是不是 updateConversationMessage,里面统一处理未读、恢复;直接设置 KeFuMessageDO 作为参数好了。。。 + keFuMessageMapper.insert(kefuMessage); // 2.2 更新会话消息冗余 - conversationService.updateConversationMessage(kefuMessage.getConversationId(), LocalDateTime.now(), - kefuMessage.getContent(), kefuMessage.getContentType()); - // 2.3 更新管理员未读消息数 - if (UserTypeEnum.ADMIN.getValue().equals(kefuMessage.getReceiverType())) { - conversationService.updateAdminUnreadMessageCountByConversationId(kefuMessage.getConversationId(), 1); - } - // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 - // TODO @puhui999:建议 && 换一行 - if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) { - conversationService.updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); - } + conversationService.updateConversationLastMessage(kefuMessage); // 3. 发送消息 getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); return kefuMessage.getId(); } + @Override + public Long sendKefuMessage(AppKeFuMessageSendReqVO sendReqVO) { + // 1.1 设置会话编号 + KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); + KeFuConversationDO conversation = conversationService.getOrCreateConversation(sendReqVO.getSenderId()); + kefuMessage.setConversationId(conversation.getId()); + // 1.2 保存消息 + keFuMessageMapper.insert(kefuMessage); + + // 2. 更新会话消息冗余 + conversationService.updateConversationLastMessage(kefuMessage); + // 3. 发送消息 + getSelf().sendAsyncMessageToAdmin(kefuMessage); + return kefuMessage.getId(); + } + @Override @Transactional(rollbackFor = Exception.class) - public void updateKefuMessageReadStatus(Long conversationId, Long receiverId) { + public void updateKefuMessageReadStatus(Long conversationId) { // 1.1 校验会话是否存在 conversationService.validateKefuConversationExists(conversationId); - // 1.2 查询接收人所有的未读消息 - // TODO @puhui999:应该不能 receiverId 过滤哈。因为多个客服,一个人点了,就都点了。 - List messageList = messageMapper.selectListByConversationIdAndReceiverIdAndReadStatus( - conversationId, receiverId, Boolean.FALSE); + // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) + List messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); // 1.3 情况一:没有未读消息 if (CollUtil.isEmpty(messageList)) { return; } // 2.1 情况二:更新未读消息状态为已读 - messageMapper.updateReadStstusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), Boolean.TRUE); - // 2.2 更新管理员未读消息数 - KeFuMessageDO message = getFirst(messageList); - assert message != null; - if (UserTypeEnum.ADMIN.getValue().equals(message.getReceiverType())) { - conversationService.updateAdminUnreadMessageCountByConversationId(conversationId, 0); - } + keFuMessageMapper.updateReadStatusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), + new KeFuMessageDO().setReadStatus(Boolean.TRUE)); + // 2.2 将管理员未读消息计数更新为零 + conversationService.updateAdminUnreadMessageCountWithZero(conversationId); - // 2.3 发送消息通知发送者,接收者已读 -> 发送者更新发送的消息状态 + // 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态 // TODO @puhui999:待定~ - getSelf().sendAsyncMessage(message.getSenderType(), message.getSenderId(), "keFuMessageReadStatusChange"); + KeFuMessageDO keFuMessage = getFirst(filterList(messageList, message -> UserTypeEnum.MEMBER.getValue().equals(message.getSenderType()))); + assert keFuMessage != null; // 断言避免警告 + webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY); } private void validateReceiverExist(Long receiverId, Integer receiverType) { @@ -123,9 +121,14 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content); } + @Async + public void sendAsyncMessageToAdmin(Object content) { + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), KEFU_MESSAGE_TYPE, content); + } + @Override public PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) { - return messageMapper.selectPage(pageReqVO); + return keFuMessageMapper.selectPage(pageReqVO); } private KeFuMessageServiceImpl getSelf() { diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 561db7a93..32ebdd045 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -156,7 +156,7 @@ yudao: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 websocket: enable: true # websocket的开关 - path: /infra/ws # 路径 + path: /infra/ws/ # 路径 // TODO @puhui999: 小程序 socket.io 连接会多一个 / 🤣🤣🤣 sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq sender-rocketmq: topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic From cdb20539ea71b38dcae6d6db69ff916233b2267e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 17 Jun 2024 17:37:09 +0800 Subject: [PATCH 22/35] =?UTF-8?q?MALL-KEFU:=20=E5=AE=8C=E5=96=84APP?= =?UTF-8?q?=E7=AB=AF=E8=81=8A=E5=A4=A9=E6=B6=88=E6=81=AF=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/mall-promotion-kefu.sql | 62 ++++++++++--------- .../app/kefu/AppKeFuMessageController.java | 10 ++- .../dal/mysql/kefu/KeFuMessageMapper.java | 9 ++- .../service/kefu/KeFuConversationService.java | 8 +++ .../kefu/KeFuConversationServiceImpl.java | 5 ++ .../service/kefu/KeFuMessageService.java | 10 +++ .../service/kefu/KeFuMessageServiceImpl.java | 14 +++++ 7 files changed, 81 insertions(+), 37 deletions(-) diff --git a/sql/mysql/mall-promotion-kefu.sql b/sql/mysql/mall-promotion-kefu.sql index e0b478f57..67054cbb1 100644 --- a/sql/mysql/mall-promotion-kefu.sql +++ b/sql/mysql/mall-promotion-kefu.sql @@ -1,37 +1,39 @@ DROP TABLE IF EXISTS `promotion_kefu_conversation`; CREATE TABLE `promotion_kefu_conversation` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', - `user_id` BIGINT NOT NULL COMMENT '会话所属用户', - `last_message_time` DATETIME NOT NULL COMMENT '最后聊天时间', - `last_message_content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', - `last_message_content_type` INT NOT NULL COMMENT '最后发送的消息类型', - `admin_pinned` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', - `user_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', - `admin_deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见', - `admin_unread_message_count` INT NOT NULL COMMENT '管理员未读消息数', - `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `user_id` bigint NOT NULL COMMENT '会话所属用户', + `last_message_time` datetime NOT NULL COMMENT '最后聊天时间', + `last_message_content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', + `last_message_content_type` int NOT NULL COMMENT '最后发送的消息类型', + `admin_pinned` bit(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', + `user_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', + `admin_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '管理员是否可见', + `admin_unread_message_count` int NOT NULL COMMENT '管理员未读消息数', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服会话' ROW_FORMAT = Dynamic; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='客服会话'; DROP TABLE IF EXISTS `promotion_kefu_message`; CREATE TABLE `promotion_kefu_message` ( - `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '编号', - `conversation_id` BIGINT NOT NULL COMMENT '会话编号', - `sender_id` BIGINT NOT NULL COMMENT '发送人编号', - `sender_type` INT NOT NULL COMMENT '发送人类型', - `receiver_id` BIGINT NOT NULL COMMENT '接收人编号', - `receiver_type` INT NOT NULL COMMENT '接收人类型', - `content_type` INT NOT NULL COMMENT '消息类型', - `content` VARCHAR(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', - `read_status` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', - `creator` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '创建者', - `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updater` VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '更新者', - `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', + `conversation_id` bigint NOT NULL COMMENT '会话编号', + `sender_id` bigint NOT NULL COMMENT '发送人编号', + `sender_type` int NOT NULL COMMENT '发送人类型', + `receiver_id` bigint DEFAULT NULL COMMENT '接收人编号', + `receiver_type` int DEFAULT NULL COMMENT '接收人类型', + `content_type` int NOT NULL COMMENT '消息类型', + `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', + `read_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', + `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '更新者', + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', + `tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号', PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '客服消息' ROW_FORMAT = Dynamic; +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='客服消息'; \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index d17990857..8b54c8db4 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -5,9 +5,8 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.security.core.annotations.PreAuthenticated; -import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO; -import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; @@ -16,14 +15,13 @@ import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -@Tag(name = "管理后台 - 客服消息") +@Tag(name = "用户 APP - 客服消息") @RestController @RequestMapping("/promotion/kefu-message") @Validated @@ -52,8 +50,8 @@ public class AppKeFuMessageController { @GetMapping("/page") @Operation(summary = "获得客服消息分页") @PreAuthenticated - public CommonResult> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { - PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO); + public CommonResult> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) { + PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO, getLoginUserId()); return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java index 6a732cff0..3de68fe0f 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; @@ -23,7 +24,7 @@ public interface KeFuMessageMapper extends BaseMapperX { default PageResult selectPage(KeFuMessagePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eqIfPresent(KeFuMessageDO::getConversationId, reqVO.getConversationId()) - .orderByDesc(KeFuMessageDO::getId)); + .orderByDesc(KeFuMessageDO::getCreateTime)); } default List selectListByConversationIdAndReadStatus(Long conversationId, Boolean readStatus) { @@ -37,4 +38,10 @@ public interface KeFuMessageMapper extends BaseMapperX { .in(KeFuMessageDO::getId, ids)); } + default PageResult selectPage(AppKeFuMessagePageReqVO pageReqVO){ + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eqIfPresent(KeFuMessageDO::getConversationId, pageReqVO.getConversationId()) + .orderByDesc(KeFuMessageDO::getCreateTime)); + } + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index c4bd59976..c99e74b5c 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -74,4 +74,12 @@ public interface KeFuConversationService { */ KeFuConversationDO validateKefuConversationExists(Long id); + /** + * 【会员】获得客服会话 + * + * @param userId 用户编号 + * @return 客服会话 + */ + KeFuConversationDO getConversationByUserId(Long userId); + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index 89af0eec7..0f6c185f3 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -104,4 +104,9 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { return conversation; } + @Override + public KeFuConversationDO getConversationByUserId(Long userId) { + return conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); + } + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index ad28df0b8..2563ada34 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import jakarta.validation.Valid; @@ -45,4 +46,13 @@ public interface KeFuMessageService { */ PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO); + /** + * 【会员】获得客服消息分页 + * + * @param pageReqVO 请求 + * @param userId 用户编号 + * @return 客服消息分页 + */ + PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId); + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index b7617bf32..22339e608 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi; import cn.iocoder.yudao.module.member.api.user.MemberUserApi; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; +import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO; import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; @@ -21,6 +22,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -131,6 +133,18 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { return keFuMessageMapper.selectPage(pageReqVO); } + @Override + public PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId) { + // 1. 获得客服会话 + KeFuConversationDO conversation = conversationService.getConversationByUserId(userId); + if (conversation == null) { + return PageResult.empty(); + } + // 2. 设置会话编号 + pageReqVO.setConversationId(conversation.getId()); + return keFuMessageMapper.selectPage(pageReqVO); + } + private KeFuMessageServiceImpl getSelf() { return SpringUtil.getBean(getClass()); } From 866b38535eb3fb333ef8fdd8c140cedc37266f59 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Mon, 17 Jun 2024 19:41:29 +0800 Subject: [PATCH 23/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91MALL=EF=BC=9Areview=20=E5=AE=A2=E6=9C=8D?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../enums/WebSocketMessageTypeConstants.java | 4 ++-- .../enums/kehu/KeFuMessageContentTypeEnum.java | 1 + .../admin/kefu/KeFuConversationController.java | 2 +- .../admin/kefu/KeFuMessageController.java | 3 +-- .../app/kefu/AppKeFuMessageController.java | 3 ++- .../kefu/vo/message/AppKeFuMessageSendReqVO.java | 13 ++++++++----- .../dal/mysql/kefu/KeFuConversationMapper.java | 2 ++ .../service/kefu/KeFuConversationService.java | 2 +- .../service/kefu/KeFuConversationServiceImpl.java | 8 +++++--- 9 files changed, 23 insertions(+), 15 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java index d060998ad..4b1c34837 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/WebSocketMessageTypeConstants.java @@ -1,13 +1,13 @@ package cn.iocoder.yudao.module.promotion.enums; /** - * websocket 消息类型枚举类 + * Promotion 的 WebSocket 消息类型枚举类 * * @author HUIHUI */ public interface WebSocketMessageTypeConstants { - //======================= mall 客服 ======================= + // ======================= mall 客服 ======================= String KEFU_MESSAGE_TYPE = "kefu_message_type"; // 客服消息类型 String KEFU_MESSAGE_ADMIN_READ = "kefu_message_read_status_change"; // 客服消息管理员已读 diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java index 616630c2e..51ee59332 100644 --- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java +++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/kehu/KeFuMessageContentTypeEnum.java @@ -20,6 +20,7 @@ public enum KeFuMessageContentTypeEnum implements IntArrayValuable { VOICE(3, "语音消息"), VIDEO(4, "视频消息"), SYSTEM(5, "系统消息"), + // ========== 商城特殊消息 ========== PRODUCT(10, "商品消息"), ORDER(11, "订单消息"); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 5a1d9e77f..55042f547 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -31,7 +31,7 @@ public class KeFuConversationController { @Operation(summary = "置顶客服会话") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") public CommonResult updateConversationPinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { - conversationService.updateAdminPinned(updateReqVO); + conversationService.updateConversationPinnedByAdmin(updateReqVO); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index 04a32e156..58de6c476 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu; -import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -37,7 +36,7 @@ public class KeFuMessageController { } @PutMapping("/update-read-status") - @Operation(summary = "更新会员客服消息已读状态") + @Operation(summary = "更新客服消息已读状态") @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index 8b54c8db4..71eb8bdb1 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -33,7 +33,7 @@ public class AppKeFuMessageController { @PostMapping("/send") @Operation(summary = "发送客服消息") @PreAuthenticated - public CommonResult createKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) { + public CommonResult sendKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) { sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.MEMBER.getValue()); // 设置用户编号和类型 return success(kefuMessageService.sendKefuMessage(sendReqVO)); } @@ -43,6 +43,7 @@ public class AppKeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthenticated public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { + // TODO @puhui999:需要传递 userId;万一用户模拟一个 conversationId kefuMessageService.updateKefuMessageReadStatus(conversationId); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java index 78df4112d..432ed56b6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java @@ -9,14 +9,10 @@ import lombok.Data; @Data public class AppKeFuMessageSendReqVO { + // TODO @puhui999:应该没有传递编号哈 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") private Long id; - @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") - private Long senderId; - @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Integer senderType; - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "消息类型不能为空") private Integer contentType; @@ -24,4 +20,11 @@ public class AppKeFuMessageSendReqVO { @NotEmpty(message = "消息不能为空") private String content; + // ========== 后端设置的参数,前端无需传递 ========== + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571", hidden = true) + private Long senderId; + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", hidden = true) + private Integer senderType; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index 577ce1b50..f5c4fac29 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -22,12 +22,14 @@ public interface KeFuConversationMapper extends BaseMapperX .orderByDesc(KeFuConversationDO::getCreateTime)); } + // TODO @puhui999:这个不用单独搞个方法哈。Service 直接 new 一个对象,然后调用 update 方法。 default void updateAdminUnreadMessageCountWithZero(Long id) { update(new LambdaUpdateWrapper() .eq(KeFuConversationDO::getId, id) .set(KeFuConversationDO::getAdminUnreadMessageCount, 0)); } + // TODO @puhui999:改成 updateAdminUnreadMessageCountIncrement 增加 default void updateAdminUnreadMessageCount(Long id) { update(new LambdaUpdateWrapper() .eq(KeFuConversationDO::getId, id) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index c99e74b5c..04b39769b 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -25,7 +25,7 @@ public interface KeFuConversationService { * * @param updateReqVO 请求 */ - void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO); + void updateConversationPinnedByAdmin(KeFuConversationUpdatePinnedReqVO updateReqVO); /** * 更新会话客服消息冗余信息 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index 0f6c185f3..fcd609b9d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -40,7 +40,7 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { } @Override - public void updateAdminPinned(KeFuConversationUpdatePinnedReqVO updateReqVO) { + public void updateConversationPinnedByAdmin(KeFuConversationUpdatePinnedReqVO updateReqVO) { conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); } @@ -54,11 +54,12 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { .setLastMessageTime(kefuMessage.getCreateTime()).setLastMessageContent(kefuMessage.getContent()) .setLastMessageContentType(kefuMessage.getContentType())); - // 2.2 更新管理员未读消息数 + // 2.1 更新管理员未读消息数 if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId()); } - // 2.4 会员用户发送消息时,如果管理员删除过会话则进行恢复 + // 2.2 会员用户发送消息时,如果管理员删除过会话则进行恢复 + // TODO @puhui999:其实不用判断用户类型;只要be已删除,就恢复! if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) && Boolean.TRUE.equals(conversation.getAdminDeleted())) { updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); @@ -106,6 +107,7 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public KeFuConversationDO getConversationByUserId(Long userId) { + // TODO @puhui999:service 不写 dao 的逻辑哈 return conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); } From 66bbbc9514c81b4d9d9770dedd73551b1fecec41 Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Thu, 20 Jun 2024 11:46:26 +0800 Subject: [PATCH 24/35] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E5=AE=A2=E6=88=B7=E7=AB=AF=EF=BC=8C=E5=9F=BA?= =?UTF-8?q?=E4=BA=8EAPI=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/sms/SmsCallbackController.java | 11 + .../sms/core/client/impl/HuaweiSmsClient.java | 201 ++++++++++++ .../client/impl/SmsClientFactoryImpl.java | 1 + .../sms/core/client/utils/HuaweiRequest.java | 253 +++++++++++++++ .../sms/core/client/utils/HuaweiSigner.java | 300 ++++++++++++++++++ .../sms/core/enums/SmsChannelEnum.java | 2 +- 6 files changed, 767 insertions(+), 1 deletion(-) create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index f392ac759..0bb406710 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -45,4 +45,15 @@ public class SmsCallbackController { return success(true); } + + @PostMapping("/huawei") + @PermitAll + @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") + @OperateLog(enable = false) + public CommonResult receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable { + String text = ServletUtils.getBody(request); + smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); + return success(true); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java new file mode 100644 index 000000000..9f7946ec3 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -0,0 +1,201 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + + +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.StrUtil; + +import cn.hutool.json.JSONArray; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.client.utils.HuaweiRequest; +import cn.iocoder.yudao.module.system.framework.sms.core.client.utils.HuaweiSigner; +import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpResponse; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.*; + + +import java.time.LocalDateTime; + + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; + + +/** + * 华为短信客户端的实现类 + * + * @author scholar + * @since 2024/6/02 11:55 + */ +@Slf4j +public class HuaweiSmsClient extends AbstractSmsClient { + + /** + * 调用成功 code + */ + public static final String API_CODE_SUCCESS = "OK"; + private static final Logger LOGGER = LoggerFactory.getLogger(HuaweiSmsClient.class); + public HuaweiSmsClient(SmsChannelProperties properties) { + super(properties); + Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); + Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); + } + @Override + protected void doInit() { + + } + @Override + public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, + List> templateParams) throws Throwable { + String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI + // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 + // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 + String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 + String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID + + //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔 + String receiver = mobile; //短信接收人号码 + + //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 + String statusCallBack = properties.getCallbackUrl(); + + /** + * 选填,使用无变量模板时请赋空值 String templateParas = ""; + * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" + * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]" + */ + List templateParas = new ArrayList<>(); + for (KeyValue kv : templateParams) { + templateParas.add(String.valueOf(kv.getValue())); + } + + //请求Body,不携带签名名称时,signature请填null + String body = buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, null); + if (null == body || body.isEmpty()) { + LOGGER.warn("body is null."); + return null; + } + + HuaweiRequest request = new HuaweiRequest(); + request.setKey(properties.getApiKey()); + request.setSecret(properties.getApiSecret()); + request.setMethod("POST"); + request.setUrl(url); + request.addHeader("Content-Type", "application/x-www-form-urlencoded"); + request.setBody(body); + LOGGER.info("Print the body: {}", body); + + HuaweiSigner signer = new HuaweiSigner("SDK-HMAC-SHA256"); + signer.sign(request); + HttpUriRequest postMethod = RequestBuilder.post() + .setUri(url) + .setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8)) + .setHeader("Content-Type","application/x-www-form-urlencoded") + .setHeader("X-Sdk-Date",request.getHeaders().get("X-Sdk-Date")) + .setHeader("Authorization",request.getHeaders().get("Authorization")) + .build(); + CloseableHttpClient client = HttpClientBuilder.create().build(); + + HttpResponse response = client.execute(postMethod); + return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) + .setApiRequestId(null).setApiCode(null).setApiMsg(null); + } + + static String buildRequestBody(String sender, String receiver, String templateId, List templateParas, + String statusCallBack, String signature) throws UnsupportedEncodingException { + if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() + || templateId.isEmpty()) { + System.out.println("buildRequestBody(): sender, receiver or templateId is null."); + return null; + } + + StringBuilder body = new StringBuilder(); + appendToBody(body, "from=", sender); + appendToBody(body, "&to=", receiver); + appendToBody(body, "&templateId=", templateId); + appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString()); + appendToBody(body, "&statusCallback=", statusCallBack); + appendToBody(body, "&signature=", signature); + return body.toString(); + } + + private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException { + if (null != val && !val.isEmpty()) { + LOGGER.info("Print appendToBody: {}:{}", key, val); + body.append(key).append(URLEncoder.encode(val, "UTF-8")); + } + } + @Override + public List parseSmsReceiveStatus(String text) { + List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); + return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(Objects.equals(status.getStatus(),"DELIVRD")) + .setErrorCode(status.getStatus()).setErrorMsg(status.getStatus()) + .setMobile(status.getPhoneNumber()).setReceiveTime(status.getUpdateTime()) + .setSerialNo(status.getSmsMsgId())); + } + + @Override + public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { + //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 + return new SmsTemplateRespDTO().setId(null).setContent(null) + .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); + + } + + /** + * 短信接收状态 + * + * 参见 文档 + * + * @author scholar + */ + @Data + public static class SmsReceiveStatus { + + /** + * 本条状态报告对应的短信的接收方号码,仅当状态报告中携带了extend参数时才会同时携带该参数 + */ + @JsonProperty("to") + private String phoneNumber; + + /** + * 短信资源的更新时间,通常为短信平台接收短信状态报告的时间 + */ + @JsonProperty("updateTime") + @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) + private LocalDateTime updateTime; + + /** + * 短信状态报告枚举值 + */ + @JsonProperty("status") + private String status; + + /** + * 发送短信成功时返回的短信唯一标识。 + */ + @JsonProperty("smsMsgId") + private String smsMsgId; + } + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java index 94fe88da9..326cad058 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java @@ -78,6 +78,7 @@ public class SmsClientFactoryImpl implements SmsClientFactory { case ALIYUN: return new AliyunSmsClient(properties); case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties); case TENCENT: return new TencentSmsClient(properties); + case HUAWEI: return new HuaweiSmsClient(properties); } // 创建失败,错误日志 + 抛出异常 log.error("[createSmsClient][配置({}) 找不到合适的客户端实现]", properties); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java new file mode 100644 index 000000000..048fe1dd8 --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java @@ -0,0 +1,253 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; + + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 华为短信待签名request + * + * @author scholar + * @since 2024/6/02 11:55 + */ +public class HuaweiRequest { + private String key = null; + private String secret = null; + private String method = null; + private String url = null; + private String body = null; + private String fragment = null; + private Map headers = new Hashtable(); + private Map> queryString = new Hashtable(); + private static final Pattern PATTERN = Pattern.compile("^(?i)(post|put|patch|delete|get|options|head)$"); + + public HuaweiRequest() { + } + + /** @deprecated */ + @Deprecated + public String getRegion() { + return ""; + } + + /** @deprecated */ + @Deprecated + public String getServiceName() { + return ""; + } + + public String getKey() { + return this.key; + } + + public String getSecrect() { + return this.secret; + } + +// public HttpMethodName getMethod() { +// return HttpMethodName.valueOf(this.method.toUpperCase(Locale.getDefault())); +// } + + public String getBody() { + return this.body; + } + + public Map getHeaders() { + return this.headers; + } + + /** @deprecated */ + @Deprecated + public void setRegion(String region) { + } + + /** @deprecated */ + @Deprecated + public void setServiceName(String serviceName) { + } + + public void setAppKey(String appKey) throws RuntimeException { + if (null != appKey && !appKey.trim().isEmpty()) { + this.key = appKey; + } else { + throw new RuntimeException("appKey can not be empty"); + } + } + + public void setAppSecrect(String appSecret) throws RuntimeException { + if (null != appSecret && !appSecret.trim().isEmpty()) { + this.secret = appSecret; + } else { + throw new RuntimeException("appSecrect can not be empty"); + } + } + + public void setKey(String appKey) throws RuntimeException { + if (null != appKey && !appKey.trim().isEmpty()) { + this.key = appKey; + } else { + throw new RuntimeException("appKey can not be empty"); + } + } + + public void setSecret(String appSecret) throws RuntimeException { + if (null != appSecret && !appSecret.trim().isEmpty()) { + this.secret = appSecret; + } else { + throw new RuntimeException("appSecrect can not be empty"); + } + } + + public void setMethod(String method) throws RuntimeException { + if (null == method) { + throw new RuntimeException("method can not be empty"); + } else { + Matcher match = PATTERN.matcher(method); + if (!match.matches()) { + throw new RuntimeException("unsupported method"); + } else { + this.method = method; + } + } + } + + public String getUrl() throws UnsupportedEncodingException { + StringBuilder uri = new StringBuilder(); + uri.append(this.url); + if (this.queryString.size() > 0) { + uri.append("?"); + int loop = 0; + Iterator var3 = this.queryString.entrySet().iterator(); + + while(var3.hasNext()) { + Map.Entry> entry = (Map.Entry)var3.next(); + + for(Iterator var5 = ((List)entry.getValue()).iterator(); var5.hasNext(); ++loop) { + String value = (String)var5.next(); + if (loop > 0) { + uri.append("&"); + } + + uri.append(URLEncoder.encode((String)entry.getKey(), "UTF-8")); + uri.append("="); + uri.append(URLEncoder.encode(value, "UTF-8")); + } + } + } + + if (this.fragment != null) { + uri.append("#"); + uri.append(this.fragment); + } + + return uri.toString(); + } + + public void setUrl(String urlRet) throws RuntimeException, UnsupportedEncodingException { + if (urlRet != null && !urlRet.trim().isEmpty()) { + int i = urlRet.indexOf(35); + if (i >= 0) { + urlRet = urlRet.substring(0, i); + } + + i = urlRet.indexOf(63); + this.url = urlRet; + if (i >= 0) { + String query = urlRet.substring(i + 1, urlRet.length()); + String[] var4 = query.split("&"); + int var5 = var4.length; + + for(int var6 = 0; var6 < var5; ++var6) { + String item = var4[var6]; + String[] spl = item.split("=", 2); + String keyRet = spl[0]; + String value = ""; + if (spl.length > 1) { + value = spl[1]; + } + + if (!keyRet.trim().isEmpty()) { + keyRet = URLDecoder.decode(keyRet, "UTF-8"); + value = URLDecoder.decode(value, "UTF-8"); + this.addQueryStringParam(keyRet, value); + } + } + + urlRet = urlRet.substring(0, i); + this.url = urlRet; + } + } else { + throw new RuntimeException("url can not be empty"); + } + } + + public String getPath() { + String urlRet = this.url; + int i = urlRet.indexOf("://"); + if (i >= 0) { + urlRet = urlRet.substring(i + 3); + } + + i = urlRet.indexOf(47); + return i >= 0 ? urlRet.substring(i) : "/"; + } + + public String getHost() { + String urlRet = this.url; + int i = urlRet.indexOf("://"); + if (i >= 0) { + urlRet = urlRet.substring(i + 3); + } + + i = urlRet.indexOf(47); + if (i >= 0) { + urlRet = urlRet.substring(0, i); + } + + return urlRet; + } + + public void setBody(String body) { + this.body = body; + } + + public void addQueryStringParam(String name, String value) { + List paramList = (List)this.queryString.get(name); + if (paramList == null) { + paramList = new ArrayList(); + this.queryString.put(name, paramList); + } + + ((List)paramList).add(value); + } + + public Map> getQueryStringParams() { + return this.queryString; + } + + public String getFragment() { + return this.fragment; + } + + public void setFragment(String fragment) throws RuntimeException, UnsupportedEncodingException { + if (fragment != null && !fragment.trim().isEmpty()) { + this.fragment = URLEncoder.encode(fragment, "UTF-8"); + } else { + throw new RuntimeException("fragment can not be empty"); + } + } + + public void addHeader(String name, String value) { + if (name != null && !name.trim().isEmpty()) { + this.headers.put(name, value); + } + } +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java new file mode 100644 index 000000000..4d2d1647c --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java @@ -0,0 +1,300 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; + + +import cn.hutool.core.util.HexUtil; +import cn.hutool.core.util.URLUtil; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import org.apache.commons.codec.binary.StringUtils; +import org.bouncycastle.crypto.digests.SM3Digest; +import org.openeuler.BGMJCEProvider; + +/** + * 华为短信request签名实现类 + * + * @author scholar + * @since 2024/6/02 11:55 + */ +public class HuaweiSigner { + public static final String LINE_SEPARATOR = "\n"; + public static final String SDK_SIGNING_ALGORITHM = "SDK-HMAC-SHA256"; + public static final String X_SDK_CONTENT_SHA256 = "x-sdk-content-sha256"; + public static final String X_SDK_DATE = "X-Sdk-Date"; + public static final String AUTHORIZATION = "Authorization"; + private static final Pattern AUTHORIZATION_PATTERN_SHA256 = Pattern.compile("SDK-HMAC-SHA256\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); + private static final Pattern AUTHORIZATION_PATTERN_SM3 = Pattern.compile("SDK-HMAC-SM3\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); + private static final String LINUX_NEW_LINE = "\n"; + public static final String HOST = "Host"; + public String messageDigestAlgorithm = "SDK-HMAC-SHA256"; + + public HuaweiSigner(String messageDigestAlgorithm) { + this.messageDigestAlgorithm = messageDigestAlgorithm; + } + + public HuaweiSigner() { + } + + public void sign(HuaweiRequest request) throws UnsupportedEncodingException { + String singerDate = this.getHeader(request, "X-Sdk-Date"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + if (singerDate == null) { + singerDate = sdf.format(new Date()); + request.addHeader("X-Sdk-Date", singerDate); + } + + this.addHostHeader(request); + String messageDigestContent = this.calculateContentHash(request); + String[] signedHeaders = this.getSignedHeaders(request); + String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); + byte[] signingKey = this.deriveSigningKey(request.getSecrect()); + String stringToSign = this.createStringToSign(canonicalRequest, singerDate); + byte[] signature = this.computeSignature(stringToSign, signingKey); + String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); + request.addHeader("Authorization", signatureResult); + } + + protected String getCanonicalizedResourcePath(String resourcePath) throws UnsupportedEncodingException { + if (resourcePath != null && !resourcePath.isEmpty()) { + try { + resourcePath = (new URI(resourcePath)).getPath(); + } catch (URISyntaxException var3) { + return resourcePath; + } + + String value = URLUtil.encode(resourcePath); + if (!value.startsWith("/")) { + value = "/".concat(value); + } + + if (!value.endsWith("/")) { + value = value.concat("/"); + } + + return value; + } else { + return "/"; + } + } + + protected String getCanonicalizedQueryString(Map> parameters) throws UnsupportedEncodingException { + SortedMap> sorted = new TreeMap(); + Iterator var3 = parameters.entrySet().iterator(); + + while(var3.hasNext()) { + Map.Entry> entry = (Map.Entry)var3.next(); + String encodedParamName = URLUtil.encode((String) entry.getKey()); + List paramValues = (List)entry.getValue(); + List encodedValues = new ArrayList(paramValues.size()); + Iterator var8 = paramValues.iterator(); + + while(var8.hasNext()) { + String value = (String)var8.next(); + encodedValues.add(URLUtil.encode(value)); + } + + Collections.sort(encodedValues); + sorted.put(encodedParamName, encodedValues); + } + + StringBuilder result = new StringBuilder(); + Iterator var11 = sorted.entrySet().iterator(); + + while(var11.hasNext()) { + Map.Entry> entry = (Map.Entry)var11.next(); + + String value; + for(Iterator var13 = ((List)entry.getValue()).iterator(); var13.hasNext(); result.append((String)entry.getKey()).append("=").append(value)) { + value = (String)var13.next(); + if (result.length() > 0) { + result.append("&"); + } + } + } + + return result.toString(); + } + + protected String createCanonicalRequest(HuaweiRequest request, String[] signedHeaders, String messageDigestContent) throws UnsupportedEncodingException { + return "POST" + "\n" + this.getCanonicalizedResourcePath(request.getPath()) + "\n" + this.getCanonicalizedQueryString(request.getQueryStringParams()) + "\n" + this.getCanonicalizedHeaderString(request, signedHeaders) + "\n" + this.getSignedHeadersString(signedHeaders) + "\n" + messageDigestContent; + } + + protected String createStringToSign(String canonicalRequest, String singerDate) { + return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hash(canonicalRequest)) : this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hashSm3(canonicalRequest)); + } + + private byte[] deriveSigningKey(String secret) { + return secret.getBytes(StandardCharsets.UTF_8); + } + + protected byte[] sign(byte[] data, byte[] key, String algorithm) { + try { + if (Objects.equals(algorithm, "HmacSM3")) { + Security.insertProviderAt(new BGMJCEProvider(), 1); + } + + Mac mac = Mac.getInstance(algorithm); + mac.init(new SecretKeySpec(key, algorithm)); + return mac.doFinal(data); + } catch (InvalidKeyException | NoSuchAlgorithmException var5) { + return new byte[0]; + } + } + + protected final byte[] computeSignature(String stringToSign, byte[] signingKey) { + return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSHA256") : this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSM3"); + } + + private String buildAuthorizationHeader(String[] signedHeaders, byte[] signature, String accessKey) { + String credential = "Access=" + accessKey; + String signerHeaders = "SignedHeaders=" + this.getSignedHeadersString(signedHeaders); + String signatureHeader = "Signature=" + HexUtil.encodeHexStr(signature); + return this.messageDigestAlgorithm + " " + credential + ", " + signerHeaders + ", " + signatureHeader; + } + + protected String[] getSignedHeaders(HuaweiRequest request) { + String[] signedHeaders = (String[])request.getHeaders().keySet().toArray(new String[0]); + Arrays.sort(signedHeaders, String.CASE_INSENSITIVE_ORDER); + return signedHeaders; + } + + protected String getCanonicalizedHeaderString(HuaweiRequest request, String[] signedHeaders) { + Map requestHeaders = request.getHeaders(); + StringBuilder buffer = new StringBuilder(); + String[] var5 = signedHeaders; + int var6 = signedHeaders.length; + + for(int var7 = 0; var7 < var6; ++var7) { + String header = var5[var7]; + String key = header.toLowerCase(Locale.getDefault()); + String value = (String)requestHeaders.get(header); + buffer.append(key).append(":"); + if (value != null) { + buffer.append(value.trim()); + } + + buffer.append("\n"); + } + + return buffer.toString(); + } + + protected String getSignedHeadersString(String[] signedHeaders) { + StringBuilder buffer = new StringBuilder(); + String[] var3 = signedHeaders; + int var4 = signedHeaders.length; + + for(int var5 = 0; var5 < var4; ++var5) { + String header = var3[var5]; + if (buffer.length() > 0) { + buffer.append(";"); + } + + buffer.append(header.toLowerCase(Locale.getDefault())); + } + + return buffer.toString(); + } + + protected void addHostHeader(HuaweiRequest request) { + boolean haveHostHeader = false; + Iterator var3 = request.getHeaders().keySet().iterator(); + + while(var3.hasNext()) { + String key = (String)var3.next(); + if ("Host".equalsIgnoreCase(key)) { + haveHostHeader = true; + break; + } + } + + if (!haveHostHeader) { + request.addHeader("Host", request.getHost()); + } + + } + + protected String getHeader(HuaweiRequest request, String header) { + if (header == null) { + return null; + } else { + Map headers = request.getHeaders(); + Iterator var4 = headers.entrySet().iterator(); + + Map.Entry entry; + do { + if (!var4.hasNext()) { + return null; + } + + entry = (Map.Entry)var4.next(); + } while(!header.equalsIgnoreCase((String)entry.getKey())); + + return (String)entry.getValue(); + } + } + + public boolean verify(HuaweiRequest request) throws UnsupportedEncodingException { + String singerDate = this.getHeader(request, "X-Sdk-Date"); + String authorization = this.getHeader(request, "Authorization"); + Matcher match = AUTHORIZATION_PATTERN_SM3.matcher(authorization); + if (StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256")) { + match = AUTHORIZATION_PATTERN_SHA256.matcher(authorization); + } + + if (!match.find()) { + return false; + } else { + String[] signedHeaders = match.group(2).split(";"); + byte[] signingKey = this.deriveSigningKey(request.getSecrect()); + String messageDigestContent = this.calculateContentHash(request); + String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); + String stringToSign = this.createStringToSign(canonicalRequest, singerDate); + byte[] signature = this.computeSignature(stringToSign, signingKey); + String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); + return signatureResult.equals(authorization); + } + } + + protected String calculateContentHash(HuaweiRequest request) { + String content_sha256 = this.getHeader(request, "x-sdk-content-sha256"); + if (content_sha256 != null) { + return content_sha256; + } else { + return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? HexUtil.encodeHexStr(this.hash(request.getBody()),true) : HexUtil.encodeHexStr(this.hashSm3(request.getBody())); + } + } + + public byte[] hash(String text) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(text.getBytes(StandardCharsets.UTF_8)); + return md.digest(); + } catch (NoSuchAlgorithmException var3) { + return new byte[0]; + } + } + + public byte[] hashSm3(String text) { + byte[] srcData = text.getBytes(StandardCharsets.UTF_8); + SM3Digest digest = new SM3Digest(); + digest.update(srcData, 0, srcData.length); + byte[] hash = new byte[digest.getDigestSize()]; + digest.doFinal(hash, 0); + return hash; + } + + +} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java index 7bd192223..88f578a18 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/enums/SmsChannelEnum.java @@ -17,7 +17,7 @@ public enum SmsChannelEnum { DEBUG_DING_TALK("DEBUG_DING_TALK", "调试(钉钉)"), ALIYUN("ALIYUN", "阿里云"), TENCENT("TENCENT", "腾讯云"), -// HUA_WEI("HUA_WEI", "华为云"), + HUAWEI("HUAWEI", "华为云"), ; /** From 4ce2be724e91bf9838baa1cc9562f698bf8e2aa9 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 24 Jun 2024 15:45:05 +0800 Subject: [PATCH 25/35] =?UTF-8?q?MALL-KEFU:=20=E6=A0=B9=E6=8D=AE=20review?= =?UTF-8?q?=20=E5=AE=8C=E5=96=84=E5=AE=A2=E6=9C=8D=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/kefu/KeFuMessageController.java | 5 ++- .../kefu/vo/message/KeFuMessageSendReqVO.java | 32 ++++--------------- .../app/kefu/AppKeFuMessageController.java | 3 +- .../vo/message/AppKeFuMessageSendReqVO.java | 4 --- .../mysql/kefu/KeFuConversationMapper.java | 14 +++----- .../kefu/KeFuConversationServiceImpl.java | 14 ++++---- .../service/kefu/KeFuMessageService.java | 4 ++- .../service/kefu/KeFuMessageServiceImpl.java | 19 +++++++---- 8 files changed, 40 insertions(+), 55 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index 58de6c476..a5f543d79 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu; +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -18,6 +19,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 客服消息") @RestController @@ -32,6 +34,7 @@ public class KeFuMessageController { @Operation(summary = "发送客服消息") @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')") public CommonResult createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { + sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.ADMIN.getValue()); // 设置用户编号和类型 return success(messageService.sendKefuMessage(sendReqVO)); } @@ -40,7 +43,7 @@ public class KeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - messageService.updateKefuMessageReadStatus(conversationId); + messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java index 0dca02410..d01b163c2 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java @@ -9,37 +9,19 @@ import lombok.Data; @Data public class KeFuMessageSendReqVO { - // TODO @puhui999:貌似字段多了;1)id 不用;2)senderId、senderType 不用;3)receiverId、receiverType 也不用;原因可以想下哈 - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") - private Long id; - @Schema(description = "会话编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12580") @NotNull(message = "会话编号不能为空") private Long conversationId; - @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") - @NotNull(message = "发送人编号不能为空") - private Long senderId; - - @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "发送人类型不能为空") - private Integer senderType; - - @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29124") - @NotNull(message = "接收人编号不能为空") - private Long receiverId; - - @Schema(description = "接收人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - @NotNull(message = "接收人类型不能为空") - private Integer receiverType; - - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "消息类型不能为空") - private Integer contentType; - @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "消息不能为空") private String content; + // ========== 后端设置的参数,前端无需传递 ========== + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571", hidden = true) + private Long senderId; + @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", hidden = true) + private Integer senderType; + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index 71eb8bdb1..a38f68686 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -43,8 +43,7 @@ public class AppKeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthenticated public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - // TODO @puhui999:需要传递 userId;万一用户模拟一个 conversationId - kefuMessageService.updateKefuMessageReadStatus(conversationId); + kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.MEMBER.getValue()); return success(true); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java index 432ed56b6..5795254f2 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/vo/message/AppKeFuMessageSendReqVO.java @@ -9,10 +9,6 @@ import lombok.Data; @Data public class AppKeFuMessageSendReqVO { - // TODO @puhui999:应该没有传递编号哈 - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23202") - private Long id; - @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "消息类型不能为空") private Integer contentType; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index f5c4fac29..35a664e53 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -22,18 +22,14 @@ public interface KeFuConversationMapper extends BaseMapperX .orderByDesc(KeFuConversationDO::getCreateTime)); } - // TODO @puhui999:这个不用单独搞个方法哈。Service 直接 new 一个对象,然后调用 update 方法。 - default void updateAdminUnreadMessageCountWithZero(Long id) { - update(new LambdaUpdateWrapper() - .eq(KeFuConversationDO::getId, id) - .set(KeFuConversationDO::getAdminUnreadMessageCount, 0)); - } - - // TODO @puhui999:改成 updateAdminUnreadMessageCountIncrement 增加 - default void updateAdminUnreadMessageCount(Long id) { + default void updateAdminUnreadMessageCountIncrement(Long id) { update(new LambdaUpdateWrapper() .eq(KeFuConversationDO::getId, id) .setSql("admin_unread_message_count = admin_unread_message_count + 1")); } + default KeFuConversationDO selectByUserId(Long userId){ + return selectOne(KeFuConversationDO::getUserId, userId); + } + } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index fcd609b9d..7ad6427ed 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -56,20 +56,21 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { // 2.1 更新管理员未读消息数 if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType())) { - conversationMapper.updateAdminUnreadMessageCount(kefuMessage.getConversationId()); + conversationMapper.updateAdminUnreadMessageCountIncrement(kefuMessage.getConversationId()); } // 2.2 会员用户发送消息时,如果管理员删除过会话则进行恢复 - // TODO @puhui999:其实不用判断用户类型;只要be已删除,就恢复! - if (UserTypeEnum.MEMBER.getValue().equals(kefuMessage.getSenderType()) - && Boolean.TRUE.equals(conversation.getAdminDeleted())) { + if (Boolean.TRUE.equals(conversation.getAdminDeleted())) { updateConversationAdminDeleted(kefuMessage.getConversationId(), Boolean.FALSE); } } @Override public void updateAdminUnreadMessageCountWithZero(Long id) { + // 校验存在 validateKefuConversationExists(id); - conversationMapper.updateAdminUnreadMessageCountWithZero(id); + + // 管理员未读消息数归零 + conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminUnreadMessageCount(0)); } @Override @@ -107,8 +108,7 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public KeFuConversationDO getConversationByUserId(Long userId) { - // TODO @puhui999:service 不写 dao 的逻辑哈 - return conversationMapper.selectOne(KeFuConversationDO::getUserId, userId); + return conversationMapper.selectByUserId(userId); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index 2563ada34..fae4566cb 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -35,8 +35,10 @@ public interface KeFuMessageService { * 【管理员】更新消息已读状态 * * @param conversationId 会话编号 + * @param userId 用户编号 + * @param userType 用户类型 */ - void updateKefuMessageReadStatus(Long conversationId); + void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType); /** * 获得客服消息分页 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index 22339e608..cf79c53d1 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.promotion.service.kefu; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; @@ -22,10 +23,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.util.Collections; import java.util.List; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.KEFU_CONVERSATION_NOT_EXISTS; import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ; import static cn.iocoder.yudao.module.promotion.enums.WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE; @@ -53,18 +55,19 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { @Transactional(rollbackFor = Exception.class) public Long sendKefuMessage(KeFuMessageSendReqVO sendReqVO) { // 1.1 校验会话是否存在 - conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); + KeFuConversationDO conversation = conversationService.validateKefuConversationExists(sendReqVO.getConversationId()); // 1.2 校验接收人是否存在 - validateReceiverExist(sendReqVO.getReceiverId(), sendReqVO.getReceiverType()); + validateReceiverExist(conversation.getUserId(), UserTypeEnum.MEMBER.getValue()); // 2.1 保存消息 KeFuMessageDO kefuMessage = BeanUtils.toBean(sendReqVO, KeFuMessageDO.class); + kefuMessage.setReceiverId(conversation.getUserId()).setReceiverType(UserTypeEnum.MEMBER.getValue()); // 设置接收人 keFuMessageMapper.insert(kefuMessage); // 2.2 更新会话消息冗余 conversationService.updateConversationLastMessage(kefuMessage); // 3. 发送消息 - getSelf().sendAsyncMessage(sendReqVO.getReceiverType(), sendReqVO.getReceiverId(), kefuMessage); + getSelf().sendAsyncMessage(UserTypeEnum.MEMBER.getValue(), conversation.getUserId(), kefuMessage); return kefuMessage.getId(); } @@ -86,9 +89,13 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { @Override @Transactional(rollbackFor = Exception.class) - public void updateKefuMessageReadStatus(Long conversationId) { + public void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType) { // 1.1 校验会话是否存在 - conversationService.validateKefuConversationExists(conversationId); + KeFuConversationDO conversation = conversationService.validateKefuConversationExists(conversationId); + // 1.2 如果是会员端处理已读,需要传递 userId;万一用户模拟一个 conversationId + if (UserTypeEnum.MEMBER.getValue().equals(userType) && ObjUtil.notEqual(conversation.getUserId(), userId)) { + throw exception(KEFU_CONVERSATION_NOT_EXISTS); + } // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) List messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); // 1.3 情况一:没有未读消息 From eed4cf127e71b7917b196f26190248ac2d549b93 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Mon, 1 Jul 2024 17:20:24 +0800 Subject: [PATCH 26/35] =?UTF-8?q?MALL-KEFU:=20=E5=AE=8C=E5=96=84=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E7=AB=AF=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kefu/KeFuConversationController.java | 23 ++++++++++++-- .../admin/kefu/KeFuMessageController.java | 31 ++++++++++++++----- .../conversation/KeFuConversationRespVO.java | 4 +++ .../kefu/vo/message/KeFuMessageRespVO.java | 2 ++ .../app/kefu/AppKeFuMessageController.java | 4 +-- .../service/kefu/KeFuMessageService.java | 6 ++-- .../service/kefu/KeFuMessageServiceImpl.java | 6 ++-- .../system/api/user/dto/AdminUserRespDTO.java | 4 +++ 8 files changed, 62 insertions(+), 18 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 55042f547..3d1d10db5 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.member.api.user.MemberUserApi; +import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO; import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO; import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService; @@ -15,8 +17,11 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; @Tag(name = "管理后台 - 客服会话") @RestController @@ -26,6 +31,8 @@ public class KeFuConversationController { @Resource private KeFuConversationService conversationService; + @Resource + private MemberUserApi memberUserApi; @PostMapping("/update-conversation-pinned") @Operation(summary = "置顶客服会话") @@ -39,7 +46,7 @@ public class KeFuConversationController { @Operation(summary = "删除客服会话") @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:delete')") - public CommonResult deleteKefuConversation(@RequestParam("id") Long id) { + public CommonResult deleteConversation(@RequestParam("id") Long id) { conversationService.deleteKefuConversation(id); return success(true); } @@ -47,8 +54,18 @@ public class KeFuConversationController { @GetMapping("/list") @Operation(summary = "获得客服会话列表") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')") - public CommonResult> getKefuConversationPage() { - return success(BeanUtils.toBean(conversationService.getKefuConversationList(), KeFuConversationRespVO.class)); + public CommonResult> getConversationList() { + // 查询会话列表 + List respList = BeanUtils.toBean(conversationService.getKefuConversationList(), + KeFuConversationRespVO.class); + + // 拼接数据 + Map userMap = memberUserApi.getUserMap(convertSet(respList, KeFuConversationRespVO::getUserId)); + respList.forEach(item->{ + findAndThen(userMap, item.getUserId(), memberUser-> item.setUserAvatar(memberUser.getAvatar()) + .setUserNickname(memberUser.getNickname())); + }); + return success(respList); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index a5f543d79..4cbc296b6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -9,6 +9,8 @@ import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMe import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO; import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO; import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -18,7 +20,12 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import java.util.Map; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 客服消息") @@ -29,11 +36,13 @@ public class KeFuMessageController { @Resource private KeFuMessageService messageService; + @Resource + private AdminUserApi adminUserApi; @PostMapping("/send") @Operation(summary = "发送客服消息") @PreAuthorize("@ss.hasPermission('promotion:kefu-message:send')") - public CommonResult createKefuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { + public CommonResult sendKeFuMessage(@Valid @RequestBody KeFuMessageSendReqVO sendReqVO) { sendReqVO.setSenderId(getLoginUserId()).setSenderType(UserTypeEnum.ADMIN.getValue()); // 设置用户编号和类型 return success(messageService.sendKefuMessage(sendReqVO)); } @@ -42,18 +51,26 @@ public class KeFuMessageController { @Operation(summary = "更新客服消息已读状态") @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthorize("@ss.hasPermission('promotion:kefu-message:update')") - public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - messageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); + public CommonResult updateKeFuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { + messageService.updateKeFuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.ADMIN.getValue()); return success(true); } - // TODO @puhui999:这个应该是某个会话,上翻、下翻;不是传统的分页哈; @GetMapping("/page") @Operation(summary = "获得客服消息分页") @PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')") - public CommonResult> getKefuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { - PageResult pageResult = messageService.getKefuMessagePage(pageReqVO); - return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); + public CommonResult> getKeFuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) { + // 获得数据 + PageResult pageResult = messageService.getKeFuMessagePage(pageReqVO); + + // 拼接数据 + PageResult result = BeanUtils.toBean(pageResult, KeFuMessageRespVO.class); + Map userMap = adminUserApi.getUserMap(convertSet(filterList(result.getList(), + item -> UserTypeEnum.ADMIN.getValue().equals(item.getSenderType())), KeFuMessageRespVO::getSenderId)); + result.getList().forEach(item->{ + findAndThen(userMap, item.getSenderId(), adminUser -> item.setSenderAvatar(adminUser.getAvatar())); + }); + return success(result); } } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java index 9f499e09e..98cd5acd8 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/conversation/KeFuConversationRespVO.java @@ -14,6 +14,10 @@ public class KeFuConversationRespVO { @Schema(description = "会话所属用户", requiredMode = Schema.RequiredMode.REQUIRED, example = "8300") private Long userId; + @Schema(description = "会话所属用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://yudao.com/images/avatar.jpg") + private String userAvatar; + @Schema(description = "会话所属用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道") + private String userNickname; @Schema(description = "最后聊天时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime lastMessageTime; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java index 41f9c52f6..248160dd9 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageRespVO.java @@ -18,6 +18,8 @@ public class KeFuMessageRespVO { @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571") private Long senderId; + @Schema(description = "发送人头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://yudao.com/images/avatar.jpg") + private String senderAvatar; @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Integer senderType; diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java index a38f68686..2c99c75cb 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java @@ -43,7 +43,7 @@ public class AppKeFuMessageController { @Parameter(name = "conversationId", description = "会话编号", required = true) @PreAuthenticated public CommonResult updateKefuMessageReadStatus(@RequestParam("conversationId") Long conversationId) { - kefuMessageService.updateKefuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.MEMBER.getValue()); + kefuMessageService.updateKeFuMessageReadStatus(conversationId, getLoginUserId(), UserTypeEnum.MEMBER.getValue()); return success(true); } @@ -51,7 +51,7 @@ public class AppKeFuMessageController { @Operation(summary = "获得客服消息分页") @PreAuthenticated public CommonResult> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) { - PageResult pageResult = kefuMessageService.getKefuMessagePage(pageReqVO, getLoginUserId()); + PageResult pageResult = kefuMessageService.getKeFuMessagePage(pageReqVO, getLoginUserId()); return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class)); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java index fae4566cb..8af4f128e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageService.java @@ -38,7 +38,7 @@ public interface KeFuMessageService { * @param userId 用户编号 * @param userType 用户类型 */ - void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType); + void updateKeFuMessageReadStatus(Long conversationId, Long userId, Integer userType); /** * 获得客服消息分页 @@ -46,7 +46,7 @@ public interface KeFuMessageService { * @param pageReqVO 分页查询 * @return 客服消息分页 */ - PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO); + PageResult getKeFuMessagePage(KeFuMessagePageReqVO pageReqVO); /** * 【会员】获得客服消息分页 @@ -55,6 +55,6 @@ public interface KeFuMessageService { * @param userId 用户编号 * @return 客服消息分页 */ - PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId); + PageResult getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId); } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index cf79c53d1..45d49041e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -89,7 +89,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { @Override @Transactional(rollbackFor = Exception.class) - public void updateKefuMessageReadStatus(Long conversationId, Long userId, Integer userType) { + public void updateKeFuMessageReadStatus(Long conversationId, Long userId, Integer userType) { // 1.1 校验会话是否存在 KeFuConversationDO conversation = conversationService.validateKefuConversationExists(conversationId); // 1.2 如果是会员端处理已读,需要传递 userId;万一用户模拟一个 conversationId @@ -136,12 +136,12 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { } @Override - public PageResult getKefuMessagePage(KeFuMessagePageReqVO pageReqVO) { + public PageResult getKeFuMessagePage(KeFuMessagePageReqVO pageReqVO) { return keFuMessageMapper.selectPage(pageReqVO); } @Override - public PageResult getKefuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId) { + public PageResult getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId) { // 1. 获得客服会话 KeFuConversationDO conversation = conversationService.getConversationByUserId(userId); if (conversation == null) { diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java index ac13c3a8b..d86f3e548 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserRespDTO.java @@ -40,5 +40,9 @@ public class AdminUserRespDTO { * 手机号码 */ private String mobile; + /** + * 用户头像 + */ + private String avatar; } From 60c473a3129a910bfcd3f7d7d237aa439412d752 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 2 Jul 2024 17:11:45 +0800 Subject: [PATCH 27/35] =?UTF-8?q?MALL-KEFU:=20=E7=AE=A1=E7=90=86=E7=AB=AF?= =?UTF-8?q?=E5=8F=91=E9=80=81=E6=B6=88=E6=81=AF=E5=A2=9E=E5=8A=A0=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/kefu/vo/message/KeFuMessageSendReqVO.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java index d01b163c2..3247a655e 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java @@ -13,6 +13,10 @@ public class KeFuMessageSendReqVO { @NotNull(message = "会话编号不能为空") private Long conversationId; + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "消息类型不能为空") + private Integer contentType; + @Schema(description = "消息", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty(message = "消息不能为空") private String content; From b7e4f8e4609934ca972f8db5cbe41377e7c61e7d Mon Sep 17 00:00:00 2001 From: YunaiV Date: Tue, 2 Jul 2024 20:59:29 +0800 Subject: [PATCH 28/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91MALL=EF=BC=9Areview=20=E5=AE=A2=E6=9C=8D?= =?UTF-8?q?=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/kefu/KeFuConversationController.java | 6 ++---- .../controller/admin/kefu/KeFuMessageController.java | 5 ++--- .../admin/kefu/vo/message/KeFuMessageSendReqVO.java | 4 ++-- .../promotion/dal/mysql/kefu/KeFuConversationMapper.java | 2 +- .../promotion/service/kefu/KeFuConversationService.java | 2 +- .../promotion/service/kefu/KeFuConversationServiceImpl.java | 4 ++-- .../promotion/service/kefu/KeFuMessageServiceImpl.java | 5 ++--- 7 files changed, 12 insertions(+), 16 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 3d1d10db5..705118142 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -61,10 +61,8 @@ public class KeFuConversationController { // 拼接数据 Map userMap = memberUserApi.getUserMap(convertSet(respList, KeFuConversationRespVO::getUserId)); - respList.forEach(item->{ - findAndThen(userMap, item.getUserId(), memberUser-> item.setUserAvatar(memberUser.getAvatar()) - .setUserNickname(memberUser.getNickname())); - }); + respList.forEach(item-> findAndThen(userMap, item.getUserId(), + memberUser-> item.setUserAvatar(memberUser.getAvatar()).setUserNickname(memberUser.getNickname()))); return success(respList); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java index 4cbc296b6..72ca7b066 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java @@ -67,9 +67,8 @@ public class KeFuMessageController { PageResult result = BeanUtils.toBean(pageResult, KeFuMessageRespVO.class); Map userMap = adminUserApi.getUserMap(convertSet(filterList(result.getList(), item -> UserTypeEnum.ADMIN.getValue().equals(item.getSenderType())), KeFuMessageRespVO::getSenderId)); - result.getList().forEach(item->{ - findAndThen(userMap, item.getSenderId(), adminUser -> item.setSenderAvatar(adminUser.getAvatar())); - }); + result.getList().forEach(item-> findAndThen(userMap, item.getSenderId(), + user -> item.setSenderAvatar(user.getAvatar()))); return success(result); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java index 3247a655e..fb4e1a26d 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/vo/message/KeFuMessageSendReqVO.java @@ -23,9 +23,9 @@ public class KeFuMessageSendReqVO { // ========== 后端设置的参数,前端无需传递 ========== - @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "24571", hidden = true) + @Schema(description = "发送人编号", example = "24571", hidden = true) private Long senderId; - @Schema(description = "发送人类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1", hidden = true) + @Schema(description = "发送人类型", example = "1", hidden = true) private Integer senderType; } \ No newline at end of file diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java index 35a664e53..40efa44a5 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuConversationMapper.java @@ -28,7 +28,7 @@ public interface KeFuConversationMapper extends BaseMapperX .setSql("admin_unread_message_count = admin_unread_message_count + 1")); } - default KeFuConversationDO selectByUserId(Long userId){ + default KeFuConversationDO selectByUserId(Long userId) { return selectOne(KeFuConversationDO::getUserId, userId); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java index 04b39769b..2da8d0bc6 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationService.java @@ -39,7 +39,7 @@ public interface KeFuConversationService { * * @param id 编号 */ - void updateAdminUnreadMessageCountWithZero(Long id); + void updateAdminUnreadMessageCountToZero(Long id); /** * 【管理员】更新会话对于管理员是否可见 diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index 7ad6427ed..57df13d76 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -65,10 +65,10 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { } @Override - public void updateAdminUnreadMessageCountWithZero(Long id) { + public void updateAdminUnreadMessageCountToZero(Long id) { // 校验存在 validateKefuConversationExists(id); - + // 管理员未读消息数归零 conversationMapper.updateById(new KeFuConversationDO().setId(id).setAdminUnreadMessageCount(0)); } diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index 45d49041e..e11474f91 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -96,9 +96,8 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { if (UserTypeEnum.MEMBER.getValue().equals(userType) && ObjUtil.notEqual(conversation.getUserId(), userId)) { throw exception(KEFU_CONVERSATION_NOT_EXISTS); } - // 1.2 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) + // 1.3 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) List messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); - // 1.3 情况一:没有未读消息 if (CollUtil.isEmpty(messageList)) { return; } @@ -107,7 +106,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { keFuMessageMapper.updateReadStatusBatchByIds(convertSet(messageList, KeFuMessageDO::getId), new KeFuMessageDO().setReadStatus(Boolean.TRUE)); // 2.2 将管理员未读消息计数更新为零 - conversationService.updateAdminUnreadMessageCountWithZero(conversationId); + conversationService.updateAdminUnreadMessageCountToZero(conversationId); // 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态 // TODO @puhui999:待定~ From 0c8163c4092de70c9fb00e50754f85e04b7b18ff Mon Sep 17 00:00:00 2001 From: scholar <1145227973@qq.com> Date: Thu, 4 Jul 2024 10:56:20 +0800 Subject: [PATCH 29/35] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E7=9F=AD=E4=BF=A1=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E5=8E=BB=E9=99=A4=E4=B8=A4=E4=B8=AA=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=B1=BBHuaweiRequest=E3=80=81HuaweiSigner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/core/client/impl/HuaweiSmsClient.java | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index 9f7946ec3..84bc2645e 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -2,16 +2,17 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONArray; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; -import cn.iocoder.yudao.module.system.framework.sms.core.client.utils.HuaweiRequest; -import cn.iocoder.yudao.module.system.framework.sms.core.client.utils.HuaweiSigner; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; import org.apache.http.client.methods.*; @@ -30,6 +31,7 @@ import org.apache.http.HttpResponse; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; import java.util.*; @@ -55,6 +57,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { */ public static final String API_CODE_SUCCESS = "OK"; private static final Logger LOGGER = LoggerFactory.getLogger(HuaweiSmsClient.class); + public HuaweiSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); @@ -79,6 +82,18 @@ public class HuaweiSmsClient extends AbstractSmsClient { //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 String statusCallBack = properties.getCallbackUrl(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + String singerDate = sdf.format(new Date()); + + // ************* 步骤 1:拼接规范请求串 ************* + String httpRequestMethod = "POST"; + String canonicalUri = "/sms/batchSendSms/v1/"; + String canonicalQueryString = "";//查询参数为空 + String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n" + + "x-sdk-date:" + singerDate + "\n"; + String signedHeaders = "content-type;host;x-sdk-date"; /** * 选填,使用无变量模板时请赋空值 String templateParas = ""; * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" @@ -92,31 +107,34 @@ public class HuaweiSmsClient extends AbstractSmsClient { //请求Body,不携带签名名称时,signature请填null String body = buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, null); if (null == body || body.isEmpty()) { - LOGGER.warn("body is null."); return null; } + String hashedRequestBody = HexUtil.encodeHexStr(DigestUtil.sha256(body)); + String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" + + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; - HuaweiRequest request = new HuaweiRequest(); - request.setKey(properties.getApiKey()); - request.setSecret(properties.getApiSecret()); - request.setMethod("POST"); - request.setUrl(url); - request.addHeader("Content-Type", "application/x-www-form-urlencoded"); - request.setBody(body); - LOGGER.info("Print the body: {}", body); + // ************* 步骤 2:拼接待签名字符串 ************* + String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); + String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest; - HuaweiSigner signer = new HuaweiSigner("SDK-HMAC-SHA256"); - signer.sign(request); + // ************* 步骤 3:计算签名 ************* + String signature = SecureUtil.hmacSha256(properties.getApiSecret()).digestHex(stringToSign); + + // ************* 步骤 4:拼接 Authorization ************* + String authorization = "SDK-HMAC-SHA256" + " " + "Access=" + properties.getApiKey() + ", " + + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; + + // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* HttpUriRequest postMethod = RequestBuilder.post() .setUri(url) - .setEntity(new StringEntity(request.getBody(), StandardCharsets.UTF_8)) + .setEntity(new StringEntity(body, StandardCharsets.UTF_8)) .setHeader("Content-Type","application/x-www-form-urlencoded") - .setHeader("X-Sdk-Date",request.getHeaders().get("X-Sdk-Date")) - .setHeader("Authorization",request.getHeaders().get("Authorization")) + .setHeader("X-Sdk-Date",singerDate) + .setHeader("Authorization",authorization) .build(); CloseableHttpClient client = HttpClientBuilder.create().build(); - HttpResponse response = client.execute(postMethod); + return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) .setApiRequestId(null).setApiCode(null).setApiMsg(null); } @@ -141,7 +159,6 @@ public class HuaweiSmsClient extends AbstractSmsClient { private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException { if (null != val && !val.isEmpty()) { - LOGGER.info("Print appendToBody: {}:{}", key, val); body.append(key).append(URLEncoder.encode(val, "UTF-8")); } } @@ -181,20 +198,17 @@ public class HuaweiSmsClient extends AbstractSmsClient { /** * 短信资源的更新时间,通常为短信平台接收短信状态报告的时间 */ - @JsonProperty("updateTime") @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT) private LocalDateTime updateTime; /** * 短信状态报告枚举值 */ - @JsonProperty("status") private String status; /** * 发送短信成功时返回的短信唯一标识。 */ - @JsonProperty("smsMsgId") private String smsMsgId; } From 19e7763440974aa6d5570e2c59fdfc73af3e481e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 5 Jul 2024 16:30:38 +0800 Subject: [PATCH 30/35] =?UTF-8?q?MALL-KEFU:=20=E4=BC=98=E5=8C=96=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=B7=B2=E8=AF=BB=E5=A4=84=E7=90=86=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dal/mysql/kefu/KeFuMessageMapper.java | 6 ++++-- .../service/kefu/KeFuMessageServiceImpl.java | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java index 3de68fe0f..f565fd7f0 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/kefu/KeFuMessageMapper.java @@ -27,9 +27,11 @@ public interface KeFuMessageMapper extends BaseMapperX { .orderByDesc(KeFuMessageDO::getCreateTime)); } - default List selectListByConversationIdAndReadStatus(Long conversationId, Boolean readStatus) { + default List selectListByConversationIdAndUserTypeAndReadStatus(Long conversationId, Integer userType, + Boolean readStatus) { return selectList(new LambdaQueryWrapper() .eq(KeFuMessageDO::getConversationId, conversationId) + .ne(KeFuMessageDO::getSenderType, userType) // 管理员:查询出未读的会员消息,会员:查询出未读的客服消息 .eq(KeFuMessageDO::getReadStatus, readStatus)); } @@ -38,7 +40,7 @@ public interface KeFuMessageMapper extends BaseMapperX { .in(KeFuMessageDO::getId, ids)); } - default PageResult selectPage(AppKeFuMessagePageReqVO pageReqVO){ + default PageResult selectPage(AppKeFuMessagePageReqVO pageReqVO) { return selectPage(pageReqVO, new LambdaQueryWrapperX() .eqIfPresent(KeFuMessageDO::getConversationId, pageReqVO.getConversationId()) .orderByDesc(KeFuMessageDO::getCreateTime)); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index e11474f91..125628347 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -67,7 +67,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { conversationService.updateConversationLastMessage(kefuMessage); // 3. 发送消息 - getSelf().sendAsyncMessage(UserTypeEnum.MEMBER.getValue(), conversation.getUserId(), kefuMessage); + getSelf().sendAsyncMessageToMember(conversation.getUserId(), KEFU_MESSAGE_TYPE, kefuMessage); return kefuMessage.getId(); } @@ -83,7 +83,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { // 2. 更新会话消息冗余 conversationService.updateConversationLastMessage(kefuMessage); // 3. 发送消息 - getSelf().sendAsyncMessageToAdmin(kefuMessage); + getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_TYPE, kefuMessage); return kefuMessage.getId(); } @@ -97,7 +97,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { throw exception(KEFU_CONVERSATION_NOT_EXISTS); } // 1.3 查询会话所有的未读消息 (tips: 多个客服,一个人点了,就都点了) - List messageList = keFuMessageMapper.selectListByConversationIdAndReadStatus(conversationId, Boolean.FALSE); + List messageList = keFuMessageMapper.selectListByConversationIdAndUserTypeAndReadStatus(conversationId, userType, Boolean.FALSE); if (CollUtil.isEmpty(messageList)) { return; } @@ -109,10 +109,11 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { conversationService.updateAdminUnreadMessageCountToZero(conversationId); // 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态 - // TODO @puhui999:待定~ KeFuMessageDO keFuMessage = getFirst(filterList(messageList, message -> UserTypeEnum.MEMBER.getValue().equals(message.getSenderType()))); assert keFuMessage != null; // 断言避免警告 - webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY); + getSelf().sendAsyncMessageToMember(keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY); + // 2.4 通知所有管理员消息已读 + getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY); } private void validateReceiverExist(Long receiverId, Integer receiverType) { @@ -125,13 +126,13 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { } @Async - public void sendAsyncMessage(Integer userType, Long userId, Object content) { - webSocketSenderApi.sendObject(userType, userId, KEFU_MESSAGE_TYPE, content); + public void sendAsyncMessageToMember(Long userId, String messageType, Object content) { + webSocketSenderApi.sendObject(UserTypeEnum.MEMBER.getValue(), userId, messageType, content); } @Async - public void sendAsyncMessageToAdmin(Object content) { - webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), KEFU_MESSAGE_TYPE, content); + public void sendAsyncMessageToAdmin(String messageType, Object content) { + webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), messageType, content); } @Override From 4a395955769ca03baeff3820f4829a5f78864a60 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 5 Jul 2024 17:59:47 +0800 Subject: [PATCH 31/35] =?UTF-8?q?MALL-KEFU:=20=E4=BC=98=E5=8C=96=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E4=BC=9A=E8=AF=9D=E5=A4=84=E7=90=86=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/admin/kefu/KeFuConversationController.java | 4 ++-- .../promotion/service/kefu/KeFuConversationServiceImpl.java | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java index 705118142..8d286a36a 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuConversationController.java @@ -34,8 +34,8 @@ public class KeFuConversationController { @Resource private MemberUserApi memberUserApi; - @PostMapping("/update-conversation-pinned") - @Operation(summary = "置顶客服会话") + @PutMapping("/update-conversation-pinned") + @Operation(summary = "置顶/取消置顶客服会话") @PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')") public CommonResult updateConversationPinned(@Valid @RequestBody KeFuConversationUpdatePinnedReqVO updateReqVO) { conversationService.updateConversationPinnedByAdmin(updateReqVO); diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java index 57df13d76..fbc658abc 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuConversationServiceImpl.java @@ -41,6 +41,10 @@ public class KeFuConversationServiceImpl implements KeFuConversationService { @Override public void updateConversationPinnedByAdmin(KeFuConversationUpdatePinnedReqVO updateReqVO) { + // 校验存在 + validateKefuConversationExists(updateReqVO.getId()); + + // 更新管理员会话置顶状态 conversationMapper.updateById(new KeFuConversationDO().setId(updateReqVO.getId()).setAdminPinned(updateReqVO.getAdminPinned())); } From b5528bfc15e2765bfeba0537656bb46669d209f4 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 9 Jul 2024 15:21:56 +0800 Subject: [PATCH 32/35] =?UTF-8?q?MALL-KEFU:=20=E5=8F=91=E9=80=81=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E9=80=9A=E7=9F=A5=E6=89=80=E6=9C=89=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E5=91=98=E6=9B=B4=E6=96=B0=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../promotion/service/kefu/KeFuMessageServiceImpl.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java index 125628347..b47cd9004 100644 --- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java +++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java @@ -66,8 +66,10 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { // 2.2 更新会话消息冗余 conversationService.updateConversationLastMessage(kefuMessage); - // 3. 发送消息 + // 3.1 发送消息给会员 getSelf().sendAsyncMessageToMember(conversation.getUserId(), KEFU_MESSAGE_TYPE, kefuMessage); + // 3.2 通知所有管理员更新对话 + getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_TYPE, kefuMessage); return kefuMessage.getId(); } @@ -82,7 +84,7 @@ public class KeFuMessageServiceImpl implements KeFuMessageService { // 2. 更新会话消息冗余 conversationService.updateConversationLastMessage(kefuMessage); - // 3. 发送消息 + // 3. 通知所有管理员更新对话 getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_TYPE, kefuMessage); return kefuMessage.getId(); } From ebfcec4cfe3565ba5b8ab4f84562d695d2e5b06f Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 9 Jul 2024 17:32:28 +0800 Subject: [PATCH 33/35] =?UTF-8?q?MALL-KEFU:=20=E8=81=8A=E5=A4=A9=E5=86=85?= =?UTF-8?q?=E5=AE=B9=E5=AD=97=E6=AE=B5=E9=95=BF=E5=BA=A6=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sql/mysql/mall-promotion-kefu.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sql/mysql/mall-promotion-kefu.sql b/sql/mysql/mall-promotion-kefu.sql index 67054cbb1..83742e4a8 100644 --- a/sql/mysql/mall-promotion-kefu.sql +++ b/sql/mysql/mall-promotion-kefu.sql @@ -3,7 +3,7 @@ CREATE TABLE `promotion_kefu_conversation` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号', `user_id` bigint NOT NULL COMMENT '会话所属用户', `last_message_time` datetime NOT NULL COMMENT '最后聊天时间', - `last_message_content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', + `last_message_content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '最后聊天内容', `last_message_content_type` int NOT NULL COMMENT '最后发送的消息类型', `admin_pinned` bit(1) NOT NULL DEFAULT b'0' COMMENT '管理端置顶', `user_deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '用户是否可见', @@ -27,7 +27,7 @@ CREATE TABLE `promotion_kefu_message` ( `receiver_id` bigint DEFAULT NULL COMMENT '接收人编号', `receiver_type` int DEFAULT NULL COMMENT '接收人类型', `content_type` int NOT NULL COMMENT '消息类型', - `content` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', + `content` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '消息', `read_status` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否已读', `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '创建者', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', From ec5a209eed0ff958ebe1f25d38579cf2e6f91053 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Fri, 12 Jul 2024 18:01:30 +0800 Subject: [PATCH 34/35] =?UTF-8?q?=E3=80=90=E4=BF=AE=E5=A4=8D=E3=80=91?= =?UTF-8?q?=E6=81=A2=E5=A4=8D=20websocket=20path?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-server/src/main/resources/application.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 32ebdd045..561db7a93 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -156,7 +156,7 @@ yudao: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 websocket: enable: true # websocket的开关 - path: /infra/ws/ # 路径 // TODO @puhui999: 小程序 socket.io 连接会多一个 / 🤣🤣🤣 + path: /infra/ws # 路径 sender-type: local # 消息发送的类型,可选值为 local、redis、rocketmq、kafka、rabbitmq sender-rocketmq: topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic From 8da08072422404b1ee66781102beda41f51701e5 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 12 Jul 2024 23:16:34 +0800 Subject: [PATCH 35/35] =?UTF-8?q?=E3=80=90=E4=BB=A3=E7=A0=81=E8=AF=84?= =?UTF-8?q?=E5=AE=A1=E3=80=91SYSTEM=EF=BC=9A=E5=8D=8E=E4=B8=BA=E4=BA=91=20?= =?UTF-8?q?client=20=E7=9A=84=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/sms/SmsCallbackController.java | 1 - .../sms/core/client/impl/HuaweiSmsClient.java | 74 +++-- .../sms/core/client/utils/HuaweiRequest.java | 253 --------------- .../sms/core/client/utils/HuaweiSigner.java | 300 ------------------ .../sms/core/client/impl/SmsClientTests.java | 36 +++ 5 files changed, 76 insertions(+), 588 deletions(-) delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java delete mode 100644 yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java create mode 100644 yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java index 12af56198..4b68e7b6b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/sms/SmsCallbackController.java @@ -46,7 +46,6 @@ public class SmsCallbackController { @PostMapping("/huawei") @PermitAll @Operation(summary = "华为云短信的回调", description = "参见 https://support.huaweicloud.com/api-msgsms/sms_05_0003.html 文档") - @OperateLog(enable = false) public CommonResult receiveHuaweiSmsStatus(HttpServletRequest request) throws Throwable { String text = ServletUtils.getBody(request); smsSendService.receiveSmsStatus(SmsChannelEnum.HUAWEI.getCode(), text); diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java index 84bc2645e..1508f9a47 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/HuaweiSmsClient.java @@ -1,10 +1,8 @@ package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; - import cn.hutool.core.lang.Assert; import cn.hutool.core.util.HexUtil; import cn.hutool.core.util.StrUtil; - import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.digest.DigestUtil; import cn.hutool.json.JSONArray; @@ -15,34 +13,27 @@ import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespD import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO; import cn.iocoder.yudao.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum; import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; -import org.apache.http.client.methods.*; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.client.methods.RequestBuilder; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; -import java.util.*; - - import java.time.LocalDateTime; - +import java.util.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT; - /** * 华为短信客户端的实现类 * @@ -56,56 +47,63 @@ public class HuaweiSmsClient extends AbstractSmsClient { * 调用成功 code */ public static final String API_CODE_SUCCESS = "OK"; - private static final Logger LOGGER = LoggerFactory.getLogger(HuaweiSmsClient.class); public HuaweiSmsClient(SmsChannelProperties properties) { super(properties); Assert.notEmpty(properties.getApiKey(), "apiKey 不能为空"); Assert.notEmpty(properties.getApiSecret(), "apiSecret 不能为空"); } + @Override protected void doInit() { - } + @Override public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId, List> templateParams) throws Throwable { + // TODO @scholar:https://smsapi.cn-north-4.myhuaweicloud.com:443 是不是枚举成静态变量 String url = "https://smsapi.cn-north-4.myhuaweicloud.com:443/sms/batchSendSms/v1"; //APP接入地址+接口访问URI // 相比较阿里短信,华为短信发送的时候需要额外的参数“通道号”,考虑到不破坏原有的的结构 // 所以将 通道号 拼接到 apiTemplateId 字段中,格式为 "apiTemplateId 通道号"。空格为分隔符。 + // TODO @scholar:暂时只考虑中国大陆,所以不需要 sender 哈 String sender = StrUtil.subAfter(apiTemplateId, " ", true); //中国大陆短信签名通道号或全球短信通道号 String templateId = StrUtil.subBefore(apiTemplateId, " ", true); //模板ID - //必填,全局号码格式(包含国家码),示例:+86151****6789,多个号码之间用英文逗号分隔 - String receiver = mobile; //短信接收人号码 - - //选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 + // 选填,短信状态报告接收地址,推荐使用域名,为空或者不填表示不接收状态报告 String statusCallBack = properties.getCallbackUrl(); + // TODO @scholar:1)是不是用 LocalDateTimeUtil.format();这样 3 行变成一行 + // TODO @scholar:singerDate 叫 sdkDate 会更合适哈,这样理解起来简单。另外,singer 应该是 signed 么? SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); sdf.setTimeZone(TimeZone.getTimeZone("UTC")); String singerDate = sdf.format(new Date()); + // TODO @scholar:整个处理加密的过程,是不是应该抽成一个 private 方法哈。这样整个调用的主干更清晰。 // ************* 步骤 1:拼接规范请求串 ************* String httpRequestMethod = "POST"; String canonicalUri = "/sms/batchSendSms/v1/"; - String canonicalQueryString = "";//查询参数为空 + String canonicalQueryString = ""; // 查询参数为空 String canonicalHeaders = "content-type:application/x-www-form-urlencoded\n" + "host:smsapi.cn-north-4.myhuaweicloud.com:443\n" + "x-sdk-date:" + singerDate + "\n"; + // TODO @scholar:静态枚举了 String signedHeaders = "content-type;host;x-sdk-date"; - /** + // TODO @scholar:下面的注释,可以考虑去掉 + /* * 选填,使用无变量模板时请赋空值 String templateParas = ""; * 单变量模板示例:模板内容为"您的验证码是${NUM_6}"时,templateParas可填写为"[\"111111\"]" * 双变量模板示例:模板内容为"您有${NUM_2}件快递请到${TXT_20}领取"时,templateParas可填写为"[\"3\",\"人民公园正门\"]" */ + // TODO @scholar:CollectionUtils.convertList 可以把 4 行变成 1 行。 + // TODO @scholar:templateParams 拼写错误哈 List templateParas = new ArrayList<>(); for (KeyValue kv : templateParams) { templateParas.add(String.valueOf(kv.getValue())); } - //请求Body,不携带签名名称时,signature请填null - String body = buildRequestBody(sender, receiver, templateId, templateParas, statusCallBack, null); + // 请求Body,不携带签名名称时,signature请填null + String body = buildRequestBody(sender, mobile, templateId, templateParas, statusCallBack, null); + // TODO @scholar:Assert 断言,抛出异常 if (null == body || body.isEmpty()) { return null; } @@ -114,6 +112,7 @@ public class HuaweiSmsClient extends AbstractSmsClient { + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestBody; // ************* 步骤 2:拼接待签名字符串 ************* + // TODO @scholar:sha256Hex 是不是更简洁哈 String hashedCanonicalRequest = HexUtil.encodeHexStr(DigestUtil.sha256(canonicalRequest)); String stringToSign = "SDK-HMAC-SHA256" + "\n" + singerDate + "\n" + hashedCanonicalRequest; @@ -125,22 +124,26 @@ public class HuaweiSmsClient extends AbstractSmsClient { + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; // ************* 步骤 5:构造HttpRequest 并执行request请求,获得response ************* + // TODO @scholar:考虑了下,还是换 hutool 的 httpUtils。因为未来 httpclient 我们可能会移除掉 HttpUriRequest postMethod = RequestBuilder.post() .setUri(url) .setEntity(new StringEntity(body, StandardCharsets.UTF_8)) .setHeader("Content-Type","application/x-www-form-urlencoded") - .setHeader("X-Sdk-Date",singerDate) - .setHeader("Authorization",authorization) + .setHeader("X-Sdk-Date", singerDate) + .setHeader("Authorization", authorization) .build(); + // TODO @scholar:这种不太适合一直 new 的哈 CloseableHttpClient client = HttpClientBuilder.create().build(); HttpResponse response = client.execute(postMethod); - + // TODO @scholar:失败的情况下的处理 + // TODO @scholar:setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) 这部分,空一行。一行代码太多了,阅读性不太好哈 return new SmsSendRespDTO().setSuccess(Objects.equals(response.getStatusLine().getReasonPhrase(), API_CODE_SUCCESS)).setSerialNo(Integer.toString(response.getStatusLine().getStatusCode())) .setApiRequestId(null).setApiCode(null).setApiMsg(null); } static String buildRequestBody(String sender, String receiver, String templateId, List templateParas, - String statusCallBack, String signature) throws UnsupportedEncodingException { + String statusCallBack, @SuppressWarnings("SameParameterValue") String signature) { + // TODO @scholar:参数不满足,是不是抛出异常更好哈;通过 hutool 的 Assert 去断言 if (null == sender || null == receiver || null == templateId || sender.isEmpty() || receiver.isEmpty() || templateId.isEmpty()) { System.out.println("buildRequestBody(): sender, receiver or templateId is null."); @@ -151,17 +154,20 @@ public class HuaweiSmsClient extends AbstractSmsClient { appendToBody(body, "from=", sender); appendToBody(body, "&to=", receiver); appendToBody(body, "&templateId=", templateId); + // TODO @scholar:new JSONArray(templateParas).toString(),是不是 JsonUtils.toString 呀? appendToBody(body, "&templateParas=", new JSONArray(templateParas).toString()); appendToBody(body, "&statusCallback=", statusCallBack); appendToBody(body, "&signature=", signature); return body.toString(); } - private static void appendToBody(StringBuilder body, String key, String val) throws UnsupportedEncodingException { + private static void appendToBody(StringBuilder body, String key, String val) { + // TODO @scholar:StrUtils.isNotEmpty(val),是不是更简洁哈 if (null != val && !val.isEmpty()) { - body.append(key).append(URLEncoder.encode(val, "UTF-8")); + body.append(key).append(URLEncoder.encode(val, StandardCharsets.UTF_8)); } } + @Override public List parseSmsReceiveStatus(String text) { List statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class); @@ -173,10 +179,10 @@ public class HuaweiSmsClient extends AbstractSmsClient { @Override public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable { - //华为短信模板查询和发送短信,是不同的两套key和secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现。 - return new SmsTemplateRespDTO().setId(null).setContent(null) + // 华为短信模板查询和发送短信,是不同的两套 key 和 secret,与阿里、腾讯的区别较大,这里模板查询校验暂不实现 + // 对应文档 https://support.huaweicloud.com/api-msgsms/sms_05_0040.html + return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(null) .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(null); - } /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java deleted file mode 100644 index 048fe1dd8..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiRequest.java +++ /dev/null @@ -1,253 +0,0 @@ -package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; - - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * 华为短信待签名request - * - * @author scholar - * @since 2024/6/02 11:55 - */ -public class HuaweiRequest { - private String key = null; - private String secret = null; - private String method = null; - private String url = null; - private String body = null; - private String fragment = null; - private Map headers = new Hashtable(); - private Map> queryString = new Hashtable(); - private static final Pattern PATTERN = Pattern.compile("^(?i)(post|put|patch|delete|get|options|head)$"); - - public HuaweiRequest() { - } - - /** @deprecated */ - @Deprecated - public String getRegion() { - return ""; - } - - /** @deprecated */ - @Deprecated - public String getServiceName() { - return ""; - } - - public String getKey() { - return this.key; - } - - public String getSecrect() { - return this.secret; - } - -// public HttpMethodName getMethod() { -// return HttpMethodName.valueOf(this.method.toUpperCase(Locale.getDefault())); -// } - - public String getBody() { - return this.body; - } - - public Map getHeaders() { - return this.headers; - } - - /** @deprecated */ - @Deprecated - public void setRegion(String region) { - } - - /** @deprecated */ - @Deprecated - public void setServiceName(String serviceName) { - } - - public void setAppKey(String appKey) throws RuntimeException { - if (null != appKey && !appKey.trim().isEmpty()) { - this.key = appKey; - } else { - throw new RuntimeException("appKey can not be empty"); - } - } - - public void setAppSecrect(String appSecret) throws RuntimeException { - if (null != appSecret && !appSecret.trim().isEmpty()) { - this.secret = appSecret; - } else { - throw new RuntimeException("appSecrect can not be empty"); - } - } - - public void setKey(String appKey) throws RuntimeException { - if (null != appKey && !appKey.trim().isEmpty()) { - this.key = appKey; - } else { - throw new RuntimeException("appKey can not be empty"); - } - } - - public void setSecret(String appSecret) throws RuntimeException { - if (null != appSecret && !appSecret.trim().isEmpty()) { - this.secret = appSecret; - } else { - throw new RuntimeException("appSecrect can not be empty"); - } - } - - public void setMethod(String method) throws RuntimeException { - if (null == method) { - throw new RuntimeException("method can not be empty"); - } else { - Matcher match = PATTERN.matcher(method); - if (!match.matches()) { - throw new RuntimeException("unsupported method"); - } else { - this.method = method; - } - } - } - - public String getUrl() throws UnsupportedEncodingException { - StringBuilder uri = new StringBuilder(); - uri.append(this.url); - if (this.queryString.size() > 0) { - uri.append("?"); - int loop = 0; - Iterator var3 = this.queryString.entrySet().iterator(); - - while(var3.hasNext()) { - Map.Entry> entry = (Map.Entry)var3.next(); - - for(Iterator var5 = ((List)entry.getValue()).iterator(); var5.hasNext(); ++loop) { - String value = (String)var5.next(); - if (loop > 0) { - uri.append("&"); - } - - uri.append(URLEncoder.encode((String)entry.getKey(), "UTF-8")); - uri.append("="); - uri.append(URLEncoder.encode(value, "UTF-8")); - } - } - } - - if (this.fragment != null) { - uri.append("#"); - uri.append(this.fragment); - } - - return uri.toString(); - } - - public void setUrl(String urlRet) throws RuntimeException, UnsupportedEncodingException { - if (urlRet != null && !urlRet.trim().isEmpty()) { - int i = urlRet.indexOf(35); - if (i >= 0) { - urlRet = urlRet.substring(0, i); - } - - i = urlRet.indexOf(63); - this.url = urlRet; - if (i >= 0) { - String query = urlRet.substring(i + 1, urlRet.length()); - String[] var4 = query.split("&"); - int var5 = var4.length; - - for(int var6 = 0; var6 < var5; ++var6) { - String item = var4[var6]; - String[] spl = item.split("=", 2); - String keyRet = spl[0]; - String value = ""; - if (spl.length > 1) { - value = spl[1]; - } - - if (!keyRet.trim().isEmpty()) { - keyRet = URLDecoder.decode(keyRet, "UTF-8"); - value = URLDecoder.decode(value, "UTF-8"); - this.addQueryStringParam(keyRet, value); - } - } - - urlRet = urlRet.substring(0, i); - this.url = urlRet; - } - } else { - throw new RuntimeException("url can not be empty"); - } - } - - public String getPath() { - String urlRet = this.url; - int i = urlRet.indexOf("://"); - if (i >= 0) { - urlRet = urlRet.substring(i + 3); - } - - i = urlRet.indexOf(47); - return i >= 0 ? urlRet.substring(i) : "/"; - } - - public String getHost() { - String urlRet = this.url; - int i = urlRet.indexOf("://"); - if (i >= 0) { - urlRet = urlRet.substring(i + 3); - } - - i = urlRet.indexOf(47); - if (i >= 0) { - urlRet = urlRet.substring(0, i); - } - - return urlRet; - } - - public void setBody(String body) { - this.body = body; - } - - public void addQueryStringParam(String name, String value) { - List paramList = (List)this.queryString.get(name); - if (paramList == null) { - paramList = new ArrayList(); - this.queryString.put(name, paramList); - } - - ((List)paramList).add(value); - } - - public Map> getQueryStringParams() { - return this.queryString; - } - - public String getFragment() { - return this.fragment; - } - - public void setFragment(String fragment) throws RuntimeException, UnsupportedEncodingException { - if (fragment != null && !fragment.trim().isEmpty()) { - this.fragment = URLEncoder.encode(fragment, "UTF-8"); - } else { - throw new RuntimeException("fragment can not be empty"); - } - } - - public void addHeader(String name, String value) { - if (name != null && !name.trim().isEmpty()) { - this.headers.put(name, value); - } - } -} diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java deleted file mode 100644 index 4d2d1647c..000000000 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/framework/sms/core/client/utils/HuaweiSigner.java +++ /dev/null @@ -1,300 +0,0 @@ -package cn.iocoder.yudao.module.system.framework.sms.core.client.utils; - - -import cn.hutool.core.util.HexUtil; -import cn.hutool.core.util.URLUtil; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Security; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import org.apache.commons.codec.binary.StringUtils; -import org.bouncycastle.crypto.digests.SM3Digest; -import org.openeuler.BGMJCEProvider; - -/** - * 华为短信request签名实现类 - * - * @author scholar - * @since 2024/6/02 11:55 - */ -public class HuaweiSigner { - public static final String LINE_SEPARATOR = "\n"; - public static final String SDK_SIGNING_ALGORITHM = "SDK-HMAC-SHA256"; - public static final String X_SDK_CONTENT_SHA256 = "x-sdk-content-sha256"; - public static final String X_SDK_DATE = "X-Sdk-Date"; - public static final String AUTHORIZATION = "Authorization"; - private static final Pattern AUTHORIZATION_PATTERN_SHA256 = Pattern.compile("SDK-HMAC-SHA256\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); - private static final Pattern AUTHORIZATION_PATTERN_SM3 = Pattern.compile("SDK-HMAC-SM3\\s+Access=([^,]+),\\s?SignedHeaders=([^,]+),\\s?Signature=(\\w+)"); - private static final String LINUX_NEW_LINE = "\n"; - public static final String HOST = "Host"; - public String messageDigestAlgorithm = "SDK-HMAC-SHA256"; - - public HuaweiSigner(String messageDigestAlgorithm) { - this.messageDigestAlgorithm = messageDigestAlgorithm; - } - - public HuaweiSigner() { - } - - public void sign(HuaweiRequest request) throws UnsupportedEncodingException { - String singerDate = this.getHeader(request, "X-Sdk-Date"); - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.ENGLISH); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - if (singerDate == null) { - singerDate = sdf.format(new Date()); - request.addHeader("X-Sdk-Date", singerDate); - } - - this.addHostHeader(request); - String messageDigestContent = this.calculateContentHash(request); - String[] signedHeaders = this.getSignedHeaders(request); - String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); - byte[] signingKey = this.deriveSigningKey(request.getSecrect()); - String stringToSign = this.createStringToSign(canonicalRequest, singerDate); - byte[] signature = this.computeSignature(stringToSign, signingKey); - String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); - request.addHeader("Authorization", signatureResult); - } - - protected String getCanonicalizedResourcePath(String resourcePath) throws UnsupportedEncodingException { - if (resourcePath != null && !resourcePath.isEmpty()) { - try { - resourcePath = (new URI(resourcePath)).getPath(); - } catch (URISyntaxException var3) { - return resourcePath; - } - - String value = URLUtil.encode(resourcePath); - if (!value.startsWith("/")) { - value = "/".concat(value); - } - - if (!value.endsWith("/")) { - value = value.concat("/"); - } - - return value; - } else { - return "/"; - } - } - - protected String getCanonicalizedQueryString(Map> parameters) throws UnsupportedEncodingException { - SortedMap> sorted = new TreeMap(); - Iterator var3 = parameters.entrySet().iterator(); - - while(var3.hasNext()) { - Map.Entry> entry = (Map.Entry)var3.next(); - String encodedParamName = URLUtil.encode((String) entry.getKey()); - List paramValues = (List)entry.getValue(); - List encodedValues = new ArrayList(paramValues.size()); - Iterator var8 = paramValues.iterator(); - - while(var8.hasNext()) { - String value = (String)var8.next(); - encodedValues.add(URLUtil.encode(value)); - } - - Collections.sort(encodedValues); - sorted.put(encodedParamName, encodedValues); - } - - StringBuilder result = new StringBuilder(); - Iterator var11 = sorted.entrySet().iterator(); - - while(var11.hasNext()) { - Map.Entry> entry = (Map.Entry)var11.next(); - - String value; - for(Iterator var13 = ((List)entry.getValue()).iterator(); var13.hasNext(); result.append((String)entry.getKey()).append("=").append(value)) { - value = (String)var13.next(); - if (result.length() > 0) { - result.append("&"); - } - } - } - - return result.toString(); - } - - protected String createCanonicalRequest(HuaweiRequest request, String[] signedHeaders, String messageDigestContent) throws UnsupportedEncodingException { - return "POST" + "\n" + this.getCanonicalizedResourcePath(request.getPath()) + "\n" + this.getCanonicalizedQueryString(request.getQueryStringParams()) + "\n" + this.getCanonicalizedHeaderString(request, signedHeaders) + "\n" + this.getSignedHeadersString(signedHeaders) + "\n" + messageDigestContent; - } - - protected String createStringToSign(String canonicalRequest, String singerDate) { - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hash(canonicalRequest)) : this.messageDigestAlgorithm + "\n" + singerDate + "\n" + HexUtil.encodeHexStr(this.hashSm3(canonicalRequest)); - } - - private byte[] deriveSigningKey(String secret) { - return secret.getBytes(StandardCharsets.UTF_8); - } - - protected byte[] sign(byte[] data, byte[] key, String algorithm) { - try { - if (Objects.equals(algorithm, "HmacSM3")) { - Security.insertProviderAt(new BGMJCEProvider(), 1); - } - - Mac mac = Mac.getInstance(algorithm); - mac.init(new SecretKeySpec(key, algorithm)); - return mac.doFinal(data); - } catch (InvalidKeyException | NoSuchAlgorithmException var5) { - return new byte[0]; - } - } - - protected final byte[] computeSignature(String stringToSign, byte[] signingKey) { - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSHA256") : this.sign(stringToSign.getBytes(StandardCharsets.UTF_8), signingKey, "HmacSM3"); - } - - private String buildAuthorizationHeader(String[] signedHeaders, byte[] signature, String accessKey) { - String credential = "Access=" + accessKey; - String signerHeaders = "SignedHeaders=" + this.getSignedHeadersString(signedHeaders); - String signatureHeader = "Signature=" + HexUtil.encodeHexStr(signature); - return this.messageDigestAlgorithm + " " + credential + ", " + signerHeaders + ", " + signatureHeader; - } - - protected String[] getSignedHeaders(HuaweiRequest request) { - String[] signedHeaders = (String[])request.getHeaders().keySet().toArray(new String[0]); - Arrays.sort(signedHeaders, String.CASE_INSENSITIVE_ORDER); - return signedHeaders; - } - - protected String getCanonicalizedHeaderString(HuaweiRequest request, String[] signedHeaders) { - Map requestHeaders = request.getHeaders(); - StringBuilder buffer = new StringBuilder(); - String[] var5 = signedHeaders; - int var6 = signedHeaders.length; - - for(int var7 = 0; var7 < var6; ++var7) { - String header = var5[var7]; - String key = header.toLowerCase(Locale.getDefault()); - String value = (String)requestHeaders.get(header); - buffer.append(key).append(":"); - if (value != null) { - buffer.append(value.trim()); - } - - buffer.append("\n"); - } - - return buffer.toString(); - } - - protected String getSignedHeadersString(String[] signedHeaders) { - StringBuilder buffer = new StringBuilder(); - String[] var3 = signedHeaders; - int var4 = signedHeaders.length; - - for(int var5 = 0; var5 < var4; ++var5) { - String header = var3[var5]; - if (buffer.length() > 0) { - buffer.append(";"); - } - - buffer.append(header.toLowerCase(Locale.getDefault())); - } - - return buffer.toString(); - } - - protected void addHostHeader(HuaweiRequest request) { - boolean haveHostHeader = false; - Iterator var3 = request.getHeaders().keySet().iterator(); - - while(var3.hasNext()) { - String key = (String)var3.next(); - if ("Host".equalsIgnoreCase(key)) { - haveHostHeader = true; - break; - } - } - - if (!haveHostHeader) { - request.addHeader("Host", request.getHost()); - } - - } - - protected String getHeader(HuaweiRequest request, String header) { - if (header == null) { - return null; - } else { - Map headers = request.getHeaders(); - Iterator var4 = headers.entrySet().iterator(); - - Map.Entry entry; - do { - if (!var4.hasNext()) { - return null; - } - - entry = (Map.Entry)var4.next(); - } while(!header.equalsIgnoreCase((String)entry.getKey())); - - return (String)entry.getValue(); - } - } - - public boolean verify(HuaweiRequest request) throws UnsupportedEncodingException { - String singerDate = this.getHeader(request, "X-Sdk-Date"); - String authorization = this.getHeader(request, "Authorization"); - Matcher match = AUTHORIZATION_PATTERN_SM3.matcher(authorization); - if (StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256")) { - match = AUTHORIZATION_PATTERN_SHA256.matcher(authorization); - } - - if (!match.find()) { - return false; - } else { - String[] signedHeaders = match.group(2).split(";"); - byte[] signingKey = this.deriveSigningKey(request.getSecrect()); - String messageDigestContent = this.calculateContentHash(request); - String canonicalRequest = this.createCanonicalRequest(request, signedHeaders, messageDigestContent); - String stringToSign = this.createStringToSign(canonicalRequest, singerDate); - byte[] signature = this.computeSignature(stringToSign, signingKey); - String signatureResult = this.buildAuthorizationHeader(signedHeaders, signature, request.getKey()); - return signatureResult.equals(authorization); - } - } - - protected String calculateContentHash(HuaweiRequest request) { - String content_sha256 = this.getHeader(request, "x-sdk-content-sha256"); - if (content_sha256 != null) { - return content_sha256; - } else { - return StringUtils.equals(this.messageDigestAlgorithm, "SDK-HMAC-SHA256") ? HexUtil.encodeHexStr(this.hash(request.getBody()),true) : HexUtil.encodeHexStr(this.hashSm3(request.getBody())); - } - } - - public byte[] hash(String text) { - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - md.update(text.getBytes(StandardCharsets.UTF_8)); - return md.digest(); - } catch (NoSuchAlgorithmException var3) { - return new byte[0]; - } - } - - public byte[] hashSm3(String text) { - byte[] srcData = text.getBytes(StandardCharsets.UTF_8); - SM3Digest digest = new SM3Digest(); - digest.update(srcData, 0, srcData.length); - byte[] hash = new byte[digest.getDigestSize()]; - digest.doFinal(hash, 0); - return hash; - } - - -} diff --git a/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java new file mode 100644 index 000000000..677bf986e --- /dev/null +++ b/yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/framework/sms/core/client/impl/SmsClientTests.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.system.framework.sms.core.client.impl; + +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.module.system.framework.sms.core.client.dto.SmsSendRespDTO; +import cn.iocoder.yudao.module.system.framework.sms.core.property.SmsChannelProperties; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import java.util.List; + +/** + * 各种 {@link SmsClientTests 集成测试 + * + * @author 芋道源码 + */ +public class SmsClientTests { + + @Test + @Disabled + public void testHuaweiSmsClient() throws Throwable { + SmsChannelProperties properties = new SmsChannelProperties() + .setApiKey("123") + .setApiSecret("456"); + HuaweiSmsClient client = new HuaweiSmsClient(properties); + // 准备参数 + Long sendLogId = System.currentTimeMillis(); + String mobile = "15601691323"; + String apiTemplateId = "xx test01"; + List> templateParams = List.of(new KeyValue<>("code", "1024")); + // 调用 + SmsSendRespDTO smsSendRespDTO = client.sendSms(sendLogId, mobile, apiTemplateId, templateParams); + // 打印结果 + System.out.println(smsSendRespDTO); + } + +}