From 6b9b01d6e5ac66c4f97cdd08cb3218f77d4d034e Mon Sep 17 00:00:00 2001 From: Wang Date: Fri, 9 Jan 2026 13:44:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=B6=E9=95=BF=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=AD=A9=E5=AD=90=E6=80=A7=E6=A0=BC=E7=9A=84=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seer-admin/src/main/resources/application.yml | 2 + .../common/service/CommonFileService.java | 15 ++- .../service/impl/CommonFileServiceImpl.java | 7 ++ .../seer/teach/common/utils/CommonUtils.java | 2 +- .../MpActivityInfoCollectionEntity.java | 7 ++ seer-mp/seer-mp-service-app/pom.xml | 10 ++ .../AppAgentActivityParentInfoController.java | 9 ++ ...AppAgentActivityParticipantController.java | 13 ++- ...ller.java => AppMpActivityController.java} | 2 +- .../AppParentAgentActivityController.java | 11 +++ .../req/AppMpAgentActivityQrCodeQueryReq.java | 21 ++++ .../controller/req/RecordContactCallReq.java | 18 ++++ .../controller/req/TestChildCharacterReq.java | 35 +++++++ .../resp/AgentActivityParentInfoResp.java | 3 + .../AppParentAgentActivityService.java | 96 ++++++++++++++++++- .../IAppAgentActivityParentInfoService.java | 10 ++ .../IAppAgentActivityParticipantService.java | 9 ++ ...AppAgentActivityParentInfoServiceImpl.java | 17 ++++ ...ppAgentActivityParticipantServiceImpl.java | 54 ++++++++++- 19 files changed, 331 insertions(+), 10 deletions(-) rename seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/{AppAgentActivityController.java => AppMpActivityController.java} (98%) create mode 100644 seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/AppMpAgentActivityQrCodeQueryReq.java create mode 100644 seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/RecordContactCallReq.java create mode 100644 seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/TestChildCharacterReq.java diff --git a/seer-admin/src/main/resources/application.yml b/seer-admin/src/main/resources/application.yml index 56d71aa..c2bff99 100644 --- a/seer-admin/src/main/resources/application.yml +++ b/seer-admin/src/main/resources/application.yml @@ -24,6 +24,8 @@ spring: - optional:nacos:${spring.application.name}-${spring.profiles.active}.yaml - optional:nacos:shared-database.yaml - optional:nacos:shared-redis.yaml + - optional:nacos:shared-sa-token.yaml + - optional:nacos:shared-minio.yaml cloud: nacos: discovery: diff --git a/seer-common/common-file/src/main/java/com/seer/teach/common/service/CommonFileService.java b/seer-common/common-file/src/main/java/com/seer/teach/common/service/CommonFileService.java index 4c6aa5f..cede646 100644 --- a/seer-common/common-file/src/main/java/com/seer/teach/common/service/CommonFileService.java +++ b/seer-common/common-file/src/main/java/com/seer/teach/common/service/CommonFileService.java @@ -115,14 +115,23 @@ public interface CommonFileService { String uploadTextBook(InputStream inputStream, String fileName, String folderName, String contentType); /** - * 通过输入流上传视频 + * 通过输入流上传 * - * @param inputStream 要上传视频的输入流 + * @param inputStream 要上传的输入流 * @param folderName 文件夹名称 - * @return 视频在OSS上的访问地址 + * @return 文件在OSS上的访问地址 */ String upload(InputStream inputStream,String bucketName, String folderName,String fileName,String contentType); + /** + * 通过输入流上传 + * + * @param inputStream 要上传的输入流 + * @param folderName 文件夹名称 + * @return 文件在OSS上的访问地址 + */ + String upload(InputStream inputStream,String bucketName, String folderName,String contentType); + /** * 从MinIO下载文件 * diff --git a/seer-common/common-file/src/main/java/com/seer/teach/common/service/impl/CommonFileServiceImpl.java b/seer-common/common-file/src/main/java/com/seer/teach/common/service/impl/CommonFileServiceImpl.java index f2ae583..71422c6 100644 --- a/seer-common/common-file/src/main/java/com/seer/teach/common/service/impl/CommonFileServiceImpl.java +++ b/seer-common/common-file/src/main/java/com/seer/teach/common/service/impl/CommonFileServiceImpl.java @@ -304,6 +304,13 @@ public class CommonFileServiceImpl implements CommonFileService { return MinioConfig.URL + "/" + bucketName + "/" + folderName + "/" + originalFilename; } + @Override + public String upload(InputStream inputStream, String bucketName, String folderName, String contentType) { + String originalFilename = CommonUtils.generateUniqueFileName(); + minioService.upload(bucketName, folderName, originalFilename, inputStream, contentType); + return MinioConfig.URL + "/" + bucketName + "/" + folderName + "/" + originalFilename; + } + /** * 从MinIO下载文件 * diff --git a/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/CommonUtils.java b/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/CommonUtils.java index 6ac7d4c..041cf1f 100644 --- a/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/CommonUtils.java +++ b/seer-common/common-utils/src/main/java/com/seer/teach/common/utils/CommonUtils.java @@ -1 +1 @@ -package com.seer.teach.common.utils; import cn.hutool.core.collection.CollUtil; import cn.hutool.crypto.SecureUtil; import com.alibaba.fastjson2.JSON; import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.enums.Salt; import com.seer.teach.common.exception.FileUploadException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.BinaryOperator; import java.util.function.Function; /** * @Author: Captain * @Autograph: 安稳 * @Description: 通用工具类,提供各种常用方法 * @Date: 2023-07-03 20:32:08 */ @Slf4j @Component public class CommonUtils { /** * 格式化数字,单位为 K(千)或 W(万) * * @param number 原始整数 * @return 格式化后的字符串,带单位 */ public static String formatNumber(int number) { if (number >= 10000) { // 单位为 W(万) double wValue = number / 10000.0; return String.format("%.2fW", wValue); } else { // 单位为 K(千) double kValue = number / 1000.0; return String.format("%.2fK", kValue); } } /** * 将 Linux 秒级时间戳字符串转换为 "MM月dd日" 格式 * * @param timestampStr 秒级时间戳字符串,如 "1703558400" * @return 格式化后的日期字符串,如 "12月26日" */ public static String formatTimestampToMonthDay(String timestampStr) { long timestamp = Long.parseLong(timestampStr); LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M月d日"); return dateTime.format(formatter); } /** * 将比例小数(如 0.9500)转换为百分比字符串(如 "95%") * * @param ratio 比例小数,范围应在 0 ~ 1 之间 * @return 百分比字符串,例如 "95%", "87.5%" * @throws IllegalArgumentException 如果比例不在 0~1 之间 */ public static String formatToPercentage(BigDecimal ratio) { if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) { throw new IllegalArgumentException("比例必须在 0 和 1 之间"); } // 比例 × 100,保留最多一位小数(如 87.5%),也可以改为 setScale(0) 表示整数百分比 BigDecimal percentage = ratio.multiply(BigDecimal.valueOf(100)).setScale(1, RoundingMode.HALF_UP); return percentage.stripTrailingZeros().toPlainString() + "%"; } /** * 将比例小数(如 0.9500)转换为百分比数值(如 95.0) * * @param ratio 比例小数,范围应在 0 ~ 1 之间 * @return 百分比数值,例如 95.0, 87.5 * @throws IllegalArgumentException 如果比例不在 0~1 之间 */ public static BigDecimal formatToPercentageAsDecimal(BigDecimal ratio) { if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) { throw new IllegalArgumentException("比例必须在 0 和 1 之间"); } // 比例 × 100,保留最多一位小数 BigDecimal percentage = ratio.multiply(BigDecimal.valueOf(100)).setScale(1, RoundingMode.HALF_UP); return percentage; } /** * 计算百分比,返回整数形式(如:95% 返回 95) * * @param part 部分值(分子) * @param total 总值(分母) * @return 百分比整数,如果总值为 0 则返回 0 */ public static int calculatePercentage(int part, int total) { if (total == 0) { // 防止除以0 return 0; } return (int) Math.round((double) part * 100 / total); } /** * 获取当前时间的Linux时间戳 * * @return 当前时间的Linux时间戳字符串 */ public static String getCurrentLinuxTimeCode() { return Long.toString(Instant.now().getEpochSecond()); } /** * 生成基于 userId 和当前时间戳的唯一文件名,保留文件原始扩展名 * * @param fileName 文件名称 * @return 唯一文件名(包含扩展名) */ public static String generateUniqueFileName(String fileName) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); // 获取原始文件名 String originalFilename = fileName; // 提取扩展名 String extension = ""; if (originalFilename != null && originalFilename.contains(".")) { extension = originalFilename.substring(originalFilename.lastIndexOf(".")); } // 返回组合后的唯一文件名 return timestamp + randomPart + extension; } public static String generateUniqueFileName(Integer userId,MultipartFile file) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); if(Objects.nonNull(userId)){ randomPart = randomPart + "_" + userId; } // 获取原始文件名 String originalFilename = file.getOriginalFilename(); // 提取扩展名 String extension = ""; if (originalFilename != null && originalFilename.contains(".")) { extension = originalFilename.substring(originalFilename.lastIndexOf(".")); } // 返回组合后的唯一文件名 return timestamp + randomPart + extension; } /** * 生成基于 childrenId 和当前时间戳的唯一 .wav 文件名 * * @param childrenId 用户ID * @return 唯一的 .wav 文件名 */ public static String generateWavFileName(Integer childrenId) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); return childrenId + "_" + timestamp + "_" + randomPart + ".flac"; } /** * 基于当前时间生成唯一UUID格式的消息ID * * @return UUID风格的消息ID */ public static String generateUUIDMessageId() { try { String timestamp = getCurrentLinuxTimeCode(); String randomPart = UUID.randomUUID().toString(); String base = timestamp + "_" + randomPart; // 使用 SHA-1 生成摘要 MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] hash = md.digest(base.getBytes(StandardCharsets.UTF_8)); // 构造 UUID long mostSigBits = 0; long leastSigBits = 0; for (int i = 0; i < 8; i++) { mostSigBits = (mostSigBits << 8) | (hash[i] & 0xff); } for (int i = 8; i < 16; i++) { leastSigBits = (leastSigBits << 8) | (hash[i] & 0xff); } return new UUID(mostSigBits, leastSigBits).toString(); } catch (Exception e) { throw new RuntimeException("生成UUID消息ID失败", e); } } /** * 验证上传的文件是否为合法的图片类型 * * @param file 上传的文件 */ public static void isIllegalImageType(MultipartFile file) { long size = file.getSize(); String originalFilename = file.getOriginalFilename(); String contentType = file.getContentType(); log.info("上传文件信息 -> 文件名: {}, 大小: {} bytes (≈{} KB), 类型: {}", originalFilename, size, size / 1024, contentType); String filename = file.getOriginalFilename(); if (filename == null || !filename.contains(".")) { throw new FileUploadException(ResultCodeEnum.FILE_FORMAT_ERROR); } String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); List allowedTypes = Arrays.asList("jpg", "jpeg", "png", "mp3", "apk"); if (!allowedTypes.contains(extension)) { throw new FileUploadException(ResultCodeEnum.FILE_FORMAT_ERROR); } } /** * 将data对象转换成List * * @param data 要转换的数据对象 * @param clazz 目标列表元素类型 * @return 转换后的列表对象 */ public static List parseJsonToListObject(Object data, Class clazz) { return JSON.parseArray(JSON.toJSONString(data), clazz); } /** * 将data对象转换成clazz对象 * * @param data 要转换的数据对象 * @param clazz 目标对象类型 * @return 转换后的对象 */ public static T parseJsonToObject(Object data, Class clazz) { return JSON.parseObject(JSON.toJSONString(data), clazz); } /** * 加密密码 * * @param password 需要加密的原始密码 * @return 加密后的密码字符串 */ public static String encryptPassword(String password) { return SecureUtil.md5(Salt.PREFIX.getMsg() + password + Salt.SUFFIX.getMsg()); } /** * 将时间戳转换为LocalDateTime * * @param timestampStr 时间戳字符串 * @return 转换后的LocalDateTime对象 */ public static LocalDateTime parseTimestamp(String timestampStr) { if (timestampStr == null || timestampStr.trim().isEmpty()) { return null; } long seconds = Long.parseLong(timestampStr); return LocalDateTime.ofInstant(Instant.ofEpochSecond(seconds), ZoneId.systemDefault()); } /** * 从列表中获取指定属性的最小值 * * @param from 数据源列表 * @param valueFunc 属性提取函数 * @param 数据源类型 * @param 属性类型 * @return 最小值 */ public static > V getMinValue(List from, Function valueFunc) { if (CollUtil.isEmpty(from)) { return null; } // 断言,避免告警 assert from.size() > 0; T t = from.stream().min(Comparator.comparing(valueFunc)).get(); return valueFunc.apply(t); } /** * 计算评价最终综合评分 * * @param descriptionScores 商品评星 * @param benefitScores 服务评星 * @return 综合评分结果 */ public static Integer calcScores(Integer descriptionScores, Integer benefitScores) { // 计算评价最终综合评分 最终星数 = (商品评星 + 服务评星) / 2 BigDecimal sumScore = new BigDecimal(descriptionScores + benefitScores); BigDecimal divide = sumScore.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN); return divide.intValue(); } /** * 对集合中的元素按指定属性进行求和 * * @param from 数据源集合 * @param valueFunc 属性提取函数 * @param accumulator 累加器函数 * @param 数据源类型 * @param 属性类型 * @return 求和结果 */ public static > V getSumValue(Collection from, Function valueFunc, BinaryOperator accumulator) { return getSumValue(from, valueFunc, accumulator, null); } /** * 对集合中的元素按指定属性进行求和 * * @param from 数据源集合 * @param valueFunc 属性提取函数 * @param accumulator 累加器函数 * @param defaultValue 默认值 * @param 数据源类型 * @param 属性类型 * @return 求和结果 */ public static > V getSumValue(Collection from, Function valueFunc, BinaryOperator accumulator, V defaultValue) { if (CollUtil.isEmpty(from)) { return defaultValue; } // 断言,避免告警 assert !from.isEmpty(); return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue); } } \ No newline at end of file +package com.seer.teach.common.utils; import cn.hutool.core.collection.CollUtil; import cn.hutool.crypto.SecureUtil; import com.alibaba.fastjson2.JSON; import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.enums.Salt; import com.seer.teach.common.exception.FileUploadException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.math.RoundingMode; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.*; import java.util.function.BinaryOperator; import java.util.function.Function; /** * @Author: Captain * @Autograph: 安稳 * @Description: 通用工具类,提供各种常用方法 * @Date: 2023-07-03 20:32:08 */ @Slf4j @Component public class CommonUtils { /** * 格式化数字,单位为 K(千)或 W(万) * * @param number 原始整数 * @return 格式化后的字符串,带单位 */ public static String formatNumber(int number) { if (number >= 10000) { // 单位为 W(万) double wValue = number / 10000.0; return String.format("%.2fW", wValue); } else { // 单位为 K(千) double kValue = number / 1000.0; return String.format("%.2fK", kValue); } } /** * 将 Linux 秒级时间戳字符串转换为 "MM月dd日" 格式 * * @param timestampStr 秒级时间戳字符串,如 "1703558400" * @return 格式化后的日期字符串,如 "12月26日" */ public static String formatTimestampToMonthDay(String timestampStr) { long timestamp = Long.parseLong(timestampStr); LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault()); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("M月d日"); return dateTime.format(formatter); } /** * 将比例小数(如 0.9500)转换为百分比字符串(如 "95%") * * @param ratio 比例小数,范围应在 0 ~ 1 之间 * @return 百分比字符串,例如 "95%", "87.5%" * @throws IllegalArgumentException 如果比例不在 0~1 之间 */ public static String formatToPercentage(BigDecimal ratio) { if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) { throw new IllegalArgumentException("比例必须在 0 和 1 之间"); } // 比例 × 100,保留最多一位小数(如 87.5%),也可以改为 setScale(0) 表示整数百分比 BigDecimal percentage = ratio.multiply(BigDecimal.valueOf(100)).setScale(1, RoundingMode.HALF_UP); return percentage.stripTrailingZeros().toPlainString() + "%"; } /** * 将比例小数(如 0.9500)转换为百分比数值(如 95.0) * * @param ratio 比例小数,范围应在 0 ~ 1 之间 * @return 百分比数值,例如 95.0, 87.5 * @throws IllegalArgumentException 如果比例不在 0~1 之间 */ public static BigDecimal formatToPercentageAsDecimal(BigDecimal ratio) { if (ratio == null || ratio.compareTo(BigDecimal.ZERO) < 0 || ratio.compareTo(BigDecimal.ONE) > 0) { throw new IllegalArgumentException("比例必须在 0 和 1 之间"); } // 比例 × 100,保留最多一位小数 BigDecimal percentage = ratio.multiply(BigDecimal.valueOf(100)).setScale(1, RoundingMode.HALF_UP); return percentage; } /** * 计算百分比,返回整数形式(如:95% 返回 95) * * @param part 部分值(分子) * @param total 总值(分母) * @return 百分比整数,如果总值为 0 则返回 0 */ public static int calculatePercentage(int part, int total) { if (total == 0) { // 防止除以0 return 0; } return (int) Math.round((double) part * 100 / total); } /** * 获取当前时间的Linux时间戳 * * @return 当前时间的Linux时间戳字符串 */ public static String getCurrentLinuxTimeCode() { return Long.toString(Instant.now().getEpochSecond()); } /** * 生成基于 userId 和当前时间戳的唯一文件名,保留文件原始扩展名 * * @param fileName 文件名称 * @return 唯一文件名(包含扩展名) */ public static String generateUniqueFileName(String fileName) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); // 获取原始文件名 String originalFilename = fileName; // 提取扩展名 String extension = ""; if (originalFilename != null && originalFilename.contains(".")) { extension = originalFilename.substring(originalFilename.lastIndexOf(".")); } // 返回组合后的唯一文件名 return timestamp + randomPart + extension; } public static String generateUniqueFileName() { return generateUniqueFileName(""); } public static String generateUniqueFileName(Integer userId,MultipartFile file) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); if(Objects.nonNull(userId)){ randomPart = randomPart + "_" + userId; } // 获取原始文件名 String originalFilename = file.getOriginalFilename(); // 提取扩展名 String extension = ""; if (originalFilename != null && originalFilename.contains(".")) { extension = originalFilename.substring(originalFilename.lastIndexOf(".")); } // 返回组合后的唯一文件名 return timestamp + randomPart + extension; } /** * 生成基于 childrenId 和当前时间戳的唯一 .wav 文件名 * * @param childrenId 用户ID * @return 唯一的 .wav 文件名 */ public static String generateWavFileName(Integer childrenId) { long timestamp = System.currentTimeMillis(); String randomPart = UUID.randomUUID().toString().replace("-", "").substring(0, 8); return childrenId + "_" + timestamp + "_" + randomPart + ".flac"; } /** * 基于当前时间生成唯一UUID格式的消息ID * * @return UUID风格的消息ID */ public static String generateUUIDMessageId() { try { String timestamp = getCurrentLinuxTimeCode(); String randomPart = UUID.randomUUID().toString(); String base = timestamp + "_" + randomPart; // 使用 SHA-1 生成摘要 MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] hash = md.digest(base.getBytes(StandardCharsets.UTF_8)); // 构造 UUID long mostSigBits = 0; long leastSigBits = 0; for (int i = 0; i < 8; i++) { mostSigBits = (mostSigBits << 8) | (hash[i] & 0xff); } for (int i = 8; i < 16; i++) { leastSigBits = (leastSigBits << 8) | (hash[i] & 0xff); } return new UUID(mostSigBits, leastSigBits).toString(); } catch (Exception e) { throw new RuntimeException("生成UUID消息ID失败", e); } } /** * 验证上传的文件是否为合法的图片类型 * * @param file 上传的文件 */ public static void isIllegalImageType(MultipartFile file) { long size = file.getSize(); String originalFilename = file.getOriginalFilename(); String contentType = file.getContentType(); log.info("上传文件信息 -> 文件名: {}, 大小: {} bytes (≈{} KB), 类型: {}", originalFilename, size, size / 1024, contentType); String filename = file.getOriginalFilename(); if (filename == null || !filename.contains(".")) { throw new FileUploadException(ResultCodeEnum.FILE_FORMAT_ERROR); } String extension = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(); List allowedTypes = Arrays.asList("jpg", "jpeg", "png", "mp3", "apk"); if (!allowedTypes.contains(extension)) { throw new FileUploadException(ResultCodeEnum.FILE_FORMAT_ERROR); } } /** * 将data对象转换成List * * @param data 要转换的数据对象 * @param clazz 目标列表元素类型 * @return 转换后的列表对象 */ public static List parseJsonToListObject(Object data, Class clazz) { return JSON.parseArray(JSON.toJSONString(data), clazz); } /** * 将data对象转换成clazz对象 * * @param data 要转换的数据对象 * @param clazz 目标对象类型 * @return 转换后的对象 */ public static T parseJsonToObject(Object data, Class clazz) { return JSON.parseObject(JSON.toJSONString(data), clazz); } /** * 加密密码 * * @param password 需要加密的原始密码 * @return 加密后的密码字符串 */ public static String encryptPassword(String password) { return SecureUtil.md5(Salt.PREFIX.getMsg() + password + Salt.SUFFIX.getMsg()); } /** * 将时间戳转换为LocalDateTime * * @param timestampStr 时间戳字符串 * @return 转换后的LocalDateTime对象 */ public static LocalDateTime parseTimestamp(String timestampStr) { if (timestampStr == null || timestampStr.trim().isEmpty()) { return null; } long seconds = Long.parseLong(timestampStr); return LocalDateTime.ofInstant(Instant.ofEpochSecond(seconds), ZoneId.systemDefault()); } /** * 从列表中获取指定属性的最小值 * * @param from 数据源列表 * @param valueFunc 属性提取函数 * @param 数据源类型 * @param 属性类型 * @return 最小值 */ public static > V getMinValue(List from, Function valueFunc) { if (CollUtil.isEmpty(from)) { return null; } // 断言,避免告警 assert from.size() > 0; T t = from.stream().min(Comparator.comparing(valueFunc)).get(); return valueFunc.apply(t); } /** * 计算评价最终综合评分 * * @param descriptionScores 商品评星 * @param benefitScores 服务评星 * @return 综合评分结果 */ public static Integer calcScores(Integer descriptionScores, Integer benefitScores) { // 计算评价最终综合评分 最终星数 = (商品评星 + 服务评星) / 2 BigDecimal sumScore = new BigDecimal(descriptionScores + benefitScores); BigDecimal divide = sumScore.divide(BigDecimal.valueOf(2L), 0, RoundingMode.DOWN); return divide.intValue(); } /** * 对集合中的元素按指定属性进行求和 * * @param from 数据源集合 * @param valueFunc 属性提取函数 * @param accumulator 累加器函数 * @param 数据源类型 * @param 属性类型 * @return 求和结果 */ public static > V getSumValue(Collection from, Function valueFunc, BinaryOperator accumulator) { return getSumValue(from, valueFunc, accumulator, null); } /** * 对集合中的元素按指定属性进行求和 * * @param from 数据源集合 * @param valueFunc 属性提取函数 * @param accumulator 累加器函数 * @param defaultValue 默认值 * @param 数据源类型 * @param 属性类型 * @return 求和结果 */ public static > V getSumValue(Collection from, Function valueFunc, BinaryOperator accumulator, V defaultValue) { if (CollUtil.isEmpty(from)) { return defaultValue; } // 断言,避免告警 assert !from.isEmpty(); return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue); } } \ No newline at end of file diff --git a/seer-mp/seer-mp-data-module/src/main/java/com/seer/teach/mp/entity/MpActivityInfoCollectionEntity.java b/seer-mp/seer-mp-data-module/src/main/java/com/seer/teach/mp/entity/MpActivityInfoCollectionEntity.java index db0f640..b6eedec 100644 --- a/seer-mp/seer-mp-data-module/src/main/java/com/seer/teach/mp/entity/MpActivityInfoCollectionEntity.java +++ b/seer-mp/seer-mp-data-module/src/main/java/com/seer/teach/mp/entity/MpActivityInfoCollectionEntity.java @@ -112,4 +112,11 @@ public class MpActivityInfoCollectionEntity extends BaseEntity { @TableField("strong_subject_ids") private List strongSubjectIds; + /** + * 代理商拨打电话联系家长的次数 + */ + @TableField("contact_call_count") + private Integer contactCallCount = 0; + + } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/pom.xml b/seer-mp/seer-mp-service-app/pom.xml index 39b1124..16b7ff0 100644 --- a/seer-mp/seer-mp-service-app/pom.xml +++ b/seer-mp/seer-mp-service-app/pom.xml @@ -52,6 +52,16 @@ lombok-mapstruct-binding provided + + ${project.groupId} + seer-teacher-api + ${project.version} + + + ${project.groupId} + seer-teacher-service + ${project.version} + \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParentInfoController.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParentInfoController.java index 3570f83..0dc8504 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParentInfoController.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParentInfoController.java @@ -8,6 +8,7 @@ import com.seer.teach.common.annotation.DecryptionAnnotation; import com.seer.teach.common.annotation.EncryptionAnnotation; import com.seer.teach.common.annotation.LogPrint; import com.seer.teach.mp.app.controller.req.AgentActivityParentQueryReq; +import com.seer.teach.mp.app.controller.req.RecordContactCallReq; import com.seer.teach.mp.app.controller.resp.AgentActivityParentInfoResp; import com.seer.teach.mp.app.service.IAppAgentActivityParentInfoService; import io.swagger.v3.oas.annotations.Operation; @@ -46,4 +47,12 @@ public class AppAgentActivityParentInfoController { Integer userId = StpUtil.getLoginIdAsInt(); return ResultBean.success(agentActivityParentInfoService.getParentsByActivityAndAgent(userId, queryReq)); } + + @Operation(summary = "记录代理商拨打电话联系家长的次数") + @PostMapping("/record-contact-call") + @SaCheckLogin + public ResultBean recordContactCall(@RequestBody @Valid RecordContactCallReq req) { + Integer userId = StpUtil.getLoginIdAsInt(); + return ResultBean.success(agentActivityParentInfoService.recordContactCall(req.getCollectionId(), req.getIncrementCount(),userId)); + } } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParticipantController.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParticipantController.java index 3f7af26..ecfd552 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParticipantController.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityParticipantController.java @@ -6,12 +6,15 @@ import com.seer.teach.common.ResultBean; import com.seer.teach.common.annotation.DecryptionAnnotation; import com.seer.teach.common.annotation.EncryptionAnnotation; import com.seer.teach.common.annotation.LogPrint; +import com.seer.teach.mp.app.controller.req.AppMpAgentActivityQrCodeQueryReq; import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp; import com.seer.teach.mp.app.service.IAppAgentActivityParticipantService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -26,7 +29,7 @@ import java.util.List; * @author Lingma * @since 2025-12-29 */ -@Tag(name = "APP - 代理商参与活动记录") +@Tag(name = "APP - 代理商参与活动") @RestController @RequestMapping("/app/agent/activity/participant") @LogPrint @@ -44,4 +47,12 @@ public class AppAgentActivityParticipantController { Integer userId = StpUtil.getLoginIdAsInt(); return ResultBean.success(agentActivityParticipantService.getParticipantsByActivityAndAgent(agentId,userId)); } + + @Operation(summary = "获取代理参与活动的二维码") + @PostMapping("/qrcode") + @SaCheckPermission("mp:app:agent:activity:qrcode") + public ResultBean getQrCode(@RequestBody AppMpAgentActivityQrCodeQueryReq req) { + Integer userId = StpUtil.getLoginIdAsInt(); + return ResultBean.success(agentActivityParticipantService.getQrCode(req,userId)); + } } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityController.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppMpActivityController.java similarity index 98% rename from seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityController.java rename to seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppMpActivityController.java index 9afced3..43a540f 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppAgentActivityController.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppMpActivityController.java @@ -34,7 +34,7 @@ import org.springframework.web.bind.annotation.RestController; @EncryptionAnnotation @DecryptionAnnotation @RequiredArgsConstructor -public class AppAgentActivityController { +public class AppMpActivityController { private final IAppActivityService agentActivityService; diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppParentAgentActivityController.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppParentAgentActivityController.java index 9cedf95..b26c31c 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppParentAgentActivityController.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/AppParentAgentActivityController.java @@ -4,10 +4,13 @@ import cn.dev33.satoken.annotation.SaCheckLogin; import cn.dev33.satoken.stp.StpUtil; import com.seer.teach.common.ResultBean; import com.seer.teach.mp.app.controller.req.AppMpSignUpActivityReq; +import com.seer.teach.mp.app.controller.req.TestChildCharacterReq; import com.seer.teach.mp.app.controller.resp.AppMpSignUpActivityResp; +import com.seer.teach.mp.app.controller.resp.TestChildCharacterResp; import com.seer.teach.mp.app.service.AppParentAgentActivityService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -59,4 +62,12 @@ public class AppParentAgentActivityController { Integer parentId = StpUtil.getLoginIdAsInt(); return ResultBean.success(appParentAgentActivityService.getByActivityIdAndParentId(agentId,activityId,parentId)); } + + @PostMapping("/test-child-character") + @SaCheckLogin + @Operation(summary = "测试孩子性格") + public ResultBean testChildCharacter(@RequestBody @Valid TestChildCharacterReq request) { + Integer parentId = StpUtil.getLoginIdAsInt(); + return ResultBean.success(appParentAgentActivityService.testChildCharacter(request, parentId)); + } } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/AppMpAgentActivityQrCodeQueryReq.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/AppMpAgentActivityQrCodeQueryReq.java new file mode 100644 index 0000000..662f443 --- /dev/null +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/AppMpAgentActivityQrCodeQueryReq.java @@ -0,0 +1,21 @@ +package com.seer.teach.mp.app.controller.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "App 代理商活动二维码查询请求参数") +public class AppMpAgentActivityQrCodeQueryReq { + + @Schema(description = "代理商id",example = "1") + private Integer agentId; + + @Schema(description = "活动id",example = "1") + private Integer activityId; + + @Schema(description = "公众号appId",example = "wx1234567890") + private String appId; + + @Schema(description = "二维码类型 1:临时 2:永久",example = "1") + private Integer qrCodeType; +} diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/RecordContactCallReq.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/RecordContactCallReq.java new file mode 100644 index 0000000..6f643a5 --- /dev/null +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/RecordContactCallReq.java @@ -0,0 +1,18 @@ +package com.seer.teach.mp.app.controller.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +@Schema(description = "记录代理商拨打电话联系家长次数请求对象") +@Data +public class RecordContactCallReq { + + @NotNull(message = "活动信息收集记录ID不能为空") + @Schema(description = "活动信息收集记录ID") + private Integer collectionId; + + @Schema(description = "增加的联系次数,默认为1") + private Integer incrementCount = 1; +} \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/TestChildCharacterReq.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/TestChildCharacterReq.java new file mode 100644 index 0000000..899627c --- /dev/null +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/req/TestChildCharacterReq.java @@ -0,0 +1,35 @@ +package com.seer.teach.mp.app.controller.req; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; + +@Schema(description = "测试孩子性格请求对象") +@Data +public class TestChildCharacterReq { + + @NotNull(message = "孩子性别不能为空") + @Schema(description = "孩子性别(M-男,F-女)") + private String childGender; + + @NotNull(message = "出生年月不能为空") + @Schema(description = "出生年月") + private LocalDate childBirthDate; + + @Schema(description = "星座") + private String constellation; + + @NotNull(message = "年级不能为空") + @Schema(description = "年级") + private String grade; + + @NotNull(message = "学习情况不能为空") + @Schema(description = "学习情况(优、良、中、差)") + private String learningSituation; + + @NotNull(message = "偏科情况不能为空") + @Schema(description = "偏科(数学、英语等)") + private String weakSubject; +} \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/resp/AgentActivityParentInfoResp.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/resp/AgentActivityParentInfoResp.java index 7611267..fbe768f 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/resp/AgentActivityParentInfoResp.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/controller/resp/AgentActivityParentInfoResp.java @@ -55,5 +55,8 @@ public class AgentActivityParentInfoResp { @Schema(description = "劣势学科ID列表") private List weakSubjectIds; + @Schema(description = "代理商拨打电话联系家长的次数") + private Integer contactCallCount; + } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/AppParentAgentActivityService.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/AppParentAgentActivityService.java index f3c87db..6db92c7 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/AppParentAgentActivityService.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/AppParentAgentActivityService.java @@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.utils.AssertUtils; import com.seer.teach.mp.app.controller.req.AppMpSignUpActivityReq; +import com.seer.teach.mp.app.controller.req.TestChildCharacterReq; import com.seer.teach.mp.app.controller.resp.AppMpSignUpActivityResp; +import com.seer.teach.mp.app.controller.resp.TestChildCharacterResp; import com.seer.teach.mp.app.convert.AppMpActivityInfoCollectionConvert; import com.seer.teach.mp.entity.MpActivityEntity; import com.seer.teach.mp.entity.MpActivityInfoCollectionEntity; @@ -16,24 +18,28 @@ import com.seer.teach.mp.service.IMpActivityService; import com.seer.teach.mp.service.IMpAgentActivityParticipantService; import com.seer.teach.mp.service.IMpAgentService; import com.seer.teach.mp.service.IMpParentAgentActivityRelationService; +import com.seer.teach.teacher.service.AiModelCallService; +import com.seer.teach.teacher.service.platform.LlmResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.Objects; @RequiredArgsConstructor @Slf4j @Service -public class AppParentAgentActivityService { +public class AppParentAgentActivityService implements IAppParentAgentActivityService { private final IMpParentAgentActivityRelationService parentAgentActivityRelationService; private final IMpActivityInfoCollectionService activityInfoCollectionService; private final IMpActivityService activityService; private final IMpAgentService agentService; private final IMpAgentActivityParticipantService mpAgentActivityParticipantService; + private final AiModelCallService aiModelCallService; /** * 报名活动 @@ -134,4 +140,92 @@ public class AppParentAgentActivityService { return AppMpActivityInfoCollectionConvert.INSTANCE.convert2Resp(activityInfoCollection); } + /** + * 测试孩子性格 + * + * @param request 测试请求对象 + * @param parentId 家长ID + * @return 性格测试结果 + */ + public TestChildCharacterResp testChildCharacter(TestChildCharacterReq request, Integer parentId) { + log.info("开始测试孩子性格,家长ID: {},请求参数: {}", parentId, request); + + // 构建AI模型的提示词 + String prompt = buildCharacterTestPrompt(request); + + // 调用AI模型获取性格分析 + LlmResponse response = aiModelCallService.callModel("child_character_analysis", prompt, new HashMap<>(), parentId); + + TestChildCharacterResp result = new TestChildCharacterResp(); + + if (response != null && response.getChoices() != null && !response.getChoices().isEmpty()) { + // 获取AI返回的内容 + String content = response.getChoices().get(0).getMessage().getContent(); + result = parseAiResponse(content); + } else { + // 如果AI调用失败,返回默认响应 + result.setCharacterAnalysis("暂时无法分析孩子的性格,请稍后重试。"); + result.setCharacterTraits("未知"); + result.setSuggestions("请尝试重新分析或咨询专业教育专家。"); + result.setCharacterType("待定"); + } + + log.info("性格测试完成,返回结果: {}", result); + return result; + } + + /** + * 构建性格测试的提示词 + * + * @param request 请求参数 + * @return 提示词内容 + */ + private String buildCharacterTestPrompt(TestChildCharacterReq request) { + StringBuilder prompt = new StringBuilder(); + prompt.append("请根据以下孩子信息分析其性格特点,并给出教育建议:\n"); + prompt.append("- 性别: ").append(request.getChildGender()).append("\n"); + prompt.append("- 出生日期: ").append(request.getChildBirthDate()).append("\n"); + prompt.append("- 星座: ").append(request.getConstellation() != null ? request.getConstellation() : "未知").append("\n"); + prompt.append("- 年级: ").append(request.getGrade()).append("\n"); + prompt.append("- 学习情况: ").append(request.getLearningSituation()).append("\n"); + prompt.append("- 偏科情况: ").append(request.getWeakSubject()).append("\n"); + prompt.append("\n请按照以下格式返回分析结果:\n"); + prompt.append("1. 性格分析:[具体分析内容]\n"); + prompt.append("2. 性格特征:[关键特征]\n"); + prompt.append("3. 教育建议:[针对性建议]\n"); + prompt.append("4. 性格类型:[简要分类]"); + + return prompt.toString(); + } + + /** + * 解析AI返回的结果 + * + * @param aiResponse AI返回的原始内容 + * @return 解析后的响应对象 + */ + private TestChildCharacterResp parseAiResponse(String aiResponse) { + TestChildCharacterResp resp = new TestChildCharacterResp(); + + // 简单解析AI返回的内容 + String[] parts = aiResponse.split("\n"); + for (String part : parts) { + if (part.startsWith("1. 性格分析:") || part.contains("性格分析")) { + resp.setCharacterAnalysis(part.replaceFirst("^[0-9]+\\.\\s*", "").replace("性格分析:", "").trim()); + } else if (part.startsWith("2. 性格特征:") || part.contains("性格特征")) { + resp.setCharacterTraits(part.replaceFirst("^[0-9]+\\.\\s*", "").replace("性格特征:", "").trim()); + } else if (part.startsWith("3. 教育建议:") || part.contains("教育建议")) { + resp.setSuggestions(part.replaceFirst("^[0-9]+\\.\\s*", "").replace("教育建议:", "").trim()); + } else if (part.startsWith("4. 性格类型:") || part.contains("性格类型")) { + resp.setCharacterType(part.replaceFirst("^[0-9]+\\.\\s*", "").replace("性格类型:", "").trim()); + } + } + + // 如果解析失败,将整个内容作为性格分析 + if (resp.getCharacterAnalysis() == null || resp.getCharacterAnalysis().isEmpty()) { + resp.setCharacterAnalysis(aiResponse); + } + + return resp; + } } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParentInfoService.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParentInfoService.java index 5fc98a4..a0009c2 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParentInfoService.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParentInfoService.java @@ -33,4 +33,14 @@ public interface IAppAgentActivityParentInfoService { * @return 分页的家长信息列表 */ PageListBean getParentsByActivityAndAgent(Integer userId,AgentActivityParentQueryReq queryReq); + + /** + * 记录代理商拨打电话联系家长的次数 + * + * @param collectionId 活动信息收集记录ID + * @param incrementCount 增加的联系次数 + * @param userId 用户ID + * @return 更新后的联系次数 + */ + boolean recordContactCall(Integer collectionId, Integer incrementCount,Integer userId); } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParticipantService.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParticipantService.java index 7e3a46f..9fe42ac 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParticipantService.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/IAppAgentActivityParticipantService.java @@ -1,5 +1,6 @@ package com.seer.teach.mp.app.service; +import com.seer.teach.mp.app.controller.req.AppMpAgentActivityQrCodeQueryReq; import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp; import java.util.List; @@ -23,4 +24,12 @@ public interface IAppAgentActivityParticipantService { */ List getParticipantsByActivityAndAgent(Integer agentId,Integer userId); + /** + * 获取二维码 + * + * @param req 请求参数 + * @param userId 用户Id + * @return 二维码的url + */ + String getQrCode(AppMpAgentActivityQrCodeQueryReq req, Integer userId); } \ No newline at end of file diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParentInfoServiceImpl.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParentInfoServiceImpl.java index 88ba34c..ffaa332 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParentInfoServiceImpl.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParentInfoServiceImpl.java @@ -84,6 +84,23 @@ public class AppAgentActivityParentInfoServiceImpl implements IAppAgentActivityP return PageConverterUtils.convertPageList(pageResult,this::convertToResp); } + @Override + public boolean recordContactCall(Integer collectionId, Integer incrementCount,Integer userId) { + // 查询指定的活动信息收集记录 + MpActivityInfoCollectionEntity entity = activityInfoCollectionService.getById(collectionId); + if (entity == null) { + throw new CommonException(ResultCodeEnum.ACTIVITY_NOT_FOUND); + } + // 更新联系次数 + Integer currentCount = entity.getContactCallCount(); + if (currentCount == null) { + currentCount = 0; + } + Integer newCount = currentCount + incrementCount; + entity.setContactCallCount(newCount); + return activityInfoCollectionService.updateById(entity); + } + private AgentActivityParentInfoResp convertToResp(MpActivityInfoCollectionEntity entity) { AgentActivityParentInfoResp resp = new AgentActivityParentInfoResp(); resp.setId(entity.getId()); diff --git a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParticipantServiceImpl.java b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParticipantServiceImpl.java index 540773e..8b20643 100644 --- a/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParticipantServiceImpl.java +++ b/seer-mp/seer-mp-service-app/src/main/java/com/seer/teach/mp/app/service/impl/AppAgentActivityParticipantServiceImpl.java @@ -1,20 +1,30 @@ package com.seer.teach.mp.app.service.impl; import cn.hutool.core.collection.CollectionUtil; +import cn.hutool.http.HttpDownloader; import com.seer.teach.common.enums.ResultCodeEnum; import com.seer.teach.common.exception.CommonException; +import com.seer.teach.common.service.CommonFileService; +import com.seer.teach.common.utils.AssertUtils; +import com.seer.teach.mp.app.controller.req.AppMpAgentActivityQrCodeQueryReq; +import com.seer.teach.mp.app.controller.req.MpGenerateQrCodeReq; import com.seer.teach.mp.app.controller.resp.AgentActivityParticipantResp; +import com.seer.teach.mp.app.controller.resp.MpQrCodeResp; +import com.seer.teach.mp.app.service.AppOfficialQrCodeService; import com.seer.teach.mp.app.service.IAppAgentActivityParticipantService; import com.seer.teach.mp.app.service.IAppAgentService; import com.seer.teach.mp.entity.MpActivityEntity; import com.seer.teach.mp.entity.MpAgentActivityParticipantEntity; import com.seer.teach.mp.service.IMpActivityService; import com.seer.teach.mp.service.IMpAgentActivityParticipantService; -import com.seer.teach.user.api.UserInfoServiceApi; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; +import java.io.ByteArrayInputStream; +import java.io.InputStream; import java.util.List; import java.util.Map; import java.util.Objects; @@ -36,12 +46,14 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity private final IMpAgentActivityParticipantService agentActivityParticipantService; - private final UserInfoServiceApi userInfoServiceApi; - private final IAppAgentService appAgentService; private final IMpActivityService mpActivityService; + private final AppOfficialQrCodeService officialQrCodeService; + + private final CommonFileService commonFileService; + @Override public List getParticipantsByActivityAndAgent(Integer agentId, Integer userId) { var userAgentId = appAgentService.getAgentIdByUserId(userId); @@ -68,6 +80,42 @@ public class AppAgentActivityParticipantServiceImpl implements IAppAgentActivity .collect(Collectors.toList()); } + @Override + public String getQrCode(AppMpAgentActivityQrCodeQueryReq req, Integer userId) { + Integer activityId = req.getActivityId(); + Integer agentId = req.getAgentId(); + log.info("getQrCode param activityId:{},agentId:{}", activityId, agentId); + MpActivityEntity activity = mpActivityService.getById(activityId); + AssertUtils.notNull(activity, ResultCodeEnum.INVALID_ACTIVITY); + + if(activity.getStatus() != 1){ + throw new CommonException(ResultCodeEnum.INVALID_ACTIVITY); + } + MpAgentActivityParticipantEntity relation = agentActivityParticipantService.getParticipantsByActivityAndAgent(activityId, agentId); + AssertUtils.notNull(relation, ResultCodeEnum.INVALID_ACTIVITY); + if(StringUtils.isNotBlank(relation.getQrCodeUrl())){ + return relation.getQrCodeUrl(); + } + MpGenerateQrCodeReq generateQrCodeReq = new MpGenerateQrCodeReq(); + generateQrCodeReq.setSceneStr("agentId=" + agentId + "&activityId=" + activityId); + generateQrCodeReq.setAppId(req.getAppId()); + generateQrCodeReq.setType(2); + MpQrCodeResp mpQrCodeResp = officialQrCodeService.generateQrCode(generateQrCodeReq); + if(StringUtils.isNotEmpty(mpQrCodeResp.getQrCodeUrl())){ + try { + byte[] bytes = HttpDownloader.downloadBytes(mpQrCodeResp.getQrCodeUrl()); + InputStream inputStream = new ByteArrayInputStream(bytes); + String url = commonFileService.upload(inputStream,"qrcode", "agent_activity", MediaType.IMAGE_PNG_VALUE); + relation.setQrCodeUrl(url); + }catch (Exception e){ + log.error("download qrcode error", e); + relation.setQrCodeUrl(mpQrCodeResp.getQrCodeUrl()); + } + } + agentActivityParticipantService.updateById(relation); + return mpQrCodeResp.getQrCodeUrl(); + } + private AgentActivityParticipantResp convertToDto(MpAgentActivityParticipantEntity entity, Map activityInfoMap) { AgentActivityParticipantResp resp = new AgentActivityParticipantResp();