diff --git a/MallChat b/MallChat new file mode 160000 index 0000000..c47b487 --- /dev/null +++ b/MallChat @@ -0,0 +1 @@ +Subproject commit c47b48760dd1eaaed6cf1c62930c65032ed66752 diff --git a/README.md b/README.md index b642c57..b4572a8 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,22 @@ - - [![MallChat](https://github.com/Evansy/MallChatWeb/actions/workflows/deploy.yml/badge.svg?branch=main)](https://github.com/Evansy/MallChatWeb/actions/workflows/deploy.yml) +
+
+ Commit + - Commit + Commit - Issues [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/zongzibinbin/MallChat/blob/master/LICENSE) - [![GitHub stars](https://img.shields.io/github/stars/zongzibinbin/MallChat.svg?style=social)](https://github.com/zongzibinbin/MallChat/stargazers) + Issues + + + License: Apache-2.0 + + + License + + ## 项目导航 @@ -130,7 +139,7 @@ 虚拟列表 - 后端 + 后端 @@ -145,6 +154,18 @@ Ac自动机敏感词检测 + + + + + 限流编程式 + + + + + + 握手认证 + diff --git a/docs/mallchat.sql b/docs/mallchat.sql index 9a94090..f2db654 100644 --- a/docs/mallchat.sql +++ b/docs/mallchat.sql @@ -190,4 +190,16 @@ CREATE TABLE `sensitive_word` ( `word` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '敏感词' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='敏感词库'; INSERT INTO `sensitive_word` (`word`) VALUES ('TMD'); -INSERT INTO `sensitive_word` (`word`) VALUES ('tmd'); \ No newline at end of file +INSERT INTO `sensitive_word` (`word`) VALUES ('tmd'); + +DROP TABLE IF EXISTS `user_emoji`; +CREATE TABLE `user_emoji` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uid` bigint(20) NOT NULL COMMENT '用户表ID', + `expression_url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表情地址', + `delete_status` int(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-正常,1-删除)', + `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `IDX_USER_EMOJIS_UID` (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='用户表情包'; \ No newline at end of file diff --git a/docs/version/2023-07-09.sql b/docs/version/2023-07-09.sql new file mode 100644 index 0000000..22ea074 --- /dev/null +++ b/docs/version/2023-07-09.sql @@ -0,0 +1,10 @@ +CREATE TABLE `user_emoji` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uid` bigint(20) NOT NULL COMMENT '用户表ID', + `expression_url` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '表情地址', + `delete_status` int(1) NOT NULL DEFAULT '0' COMMENT '逻辑删除(0-正常,1-删除)', + `create_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间', + `update_time` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '修改时间', + PRIMARY KEY (`id`) USING BTREE, + KEY `IDX_USER_EMOJIS_UID` (`uid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='用户表情包'; \ No newline at end of file diff --git a/mallchat-common/pom.xml b/mallchat-common/pom.xml index 0a33126..f20da8b 100644 --- a/mallchat-common/pom.xml +++ b/mallchat-common/pom.xml @@ -123,6 +123,16 @@ com.alibaba fastjson + + org.springframework + spring-test + 5.3.19 + test + + + org.springframework.boot + spring-boot-test + diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/EmojisMsgDTO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/EmojisMsgDTO.java new file mode 100644 index 0000000..d8aaa2c --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/EmojisMsgDTO.java @@ -0,0 +1,29 @@ +package com.abin.mallchat.common.chat.domain.entity.msg; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; + +/** + * Description: 表情图片消息入参 + * Author: abin + * Date: 2023-06-04 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class EmojisMsgDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @ApiModelProperty("下载地址") + @NotBlank + private String url; +} + + diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/MessageExtra.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/MessageExtra.java index 9a68c74..fa085b9 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/MessageExtra.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/entity/msg/MessageExtra.java @@ -1,5 +1,6 @@ package com.abin.mallchat.common.chat.domain.entity.msg; +import com.abin.mallchat.common.common.utils.discover.domain.UrlInfo; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Builder; @@ -23,7 +24,7 @@ import java.util.Map; public class MessageExtra implements Serializable { private static final long serialVersionUID = 1L; //url跳转链接 - private Map urlTitleMap; + private Map urlContentMap; //消息撤回详情 private MsgRecall recall; //艾特的uid @@ -36,4 +37,9 @@ public class MessageExtra implements Serializable { private SoundMsgDTO soundMsgDTO; //文件消息 private VideoMsgDTO videoMsgDTO; + + /** + * 表情图片信息 + */ + private EmojisMsgDTO emojisMsgDTO; } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java index cc262a9..73d7b97 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/chat/domain/enums/MessageTypeEnum.java @@ -22,6 +22,7 @@ public enum MessageTypeEnum { FILE(4, "文件"), SOUND(5, "语音"), VIDEO(6, "视频"), + EMOJI(7, "表情"), ; private final Integer type; diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java index 25418a6..f0290ab 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/aspect/FrequencyControlAspect.java @@ -2,9 +2,8 @@ package com.abin.mallchat.common.common.aspect; import cn.hutool.core.util.StrUtil; import com.abin.mallchat.common.common.annotation.FrequencyControl; -import com.abin.mallchat.common.common.exception.BusinessException; -import com.abin.mallchat.common.common.exception.CommonErrorEnum; -import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlUtil; import com.abin.mallchat.common.common.utils.RequestHolder; import com.abin.mallchat.common.common.utils.SpElUtils; import lombok.extern.slf4j.Slf4j; @@ -15,7 +14,12 @@ import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import java.lang.reflect.Method; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlStrategyFactory.TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; /** * Description: 频控实现 @@ -48,25 +52,25 @@ public class FrequencyControlAspect { } keyMap.put(prefix + ":" + key, frequencyControl); } - //批量获取redis统计的值 - ArrayList keyList = new ArrayList<>(keyMap.keySet()); - List countList = RedisUtils.mget(keyList, Integer.class); - for (int i = 0; i < keyList.size(); i++) { - String key = keyList.get(i); - Integer count = countList.get(i); - FrequencyControl frequencyControl = keyMap.get(key); - if (Objects.nonNull(count) && count >= frequencyControl.count()) {//频率超过了 - log.warn("frequencyControl limit key:{},count:{}", key, count); - throw new BusinessException(CommonErrorEnum.FREQUENCY_LIMIT); - } - } - try { - return joinPoint.proceed(); - } finally { - //不管成功还是失败,都增加次数 - keyMap.forEach((k, v) -> { - RedisUtils.inc(k, v.time(), v.unit()); - }); - } + // 将注解的参数转换为编程式调用需要的参数 + List frequencyControlDTOS = keyMap.entrySet().stream().map(entrySet -> buildFrequencyControlDTO(entrySet.getKey(), entrySet.getValue())).collect(Collectors.toList()); + // 调用编程式注解 + return FrequencyControlUtil.executeWithFrequencyControlList(TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER, frequencyControlDTOS, joinPoint::proceed); + } + + /** + * 将注解参数转换为编程式调用所需要的参数 + * + * @param key 频率控制Key + * @param frequencyControl 注解 + * @return 编程式调用所需要的参数-FrequencyControlDTO + */ + private FrequencyControlDTO buildFrequencyControlDTO(String key, FrequencyControl frequencyControl) { + FrequencyControlDTO frequencyControlDTO = new FrequencyControlDTO(); + frequencyControlDTO.setCount(frequencyControl.count()); + frequencyControlDTO.setTime(frequencyControl.time()); + frequencyControlDTO.setUnit(frequencyControl.unit()); + frequencyControlDTO.setKey(key); + return frequencyControlDTO; } } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/SensitiveWordConfig.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/SensitiveWordConfig.java new file mode 100644 index 0000000..6220d51 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/config/SensitiveWordConfig.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.common.common.config; + +import com.abin.mallchat.common.common.utils.sensitiveWord.DFAFilter; +import com.abin.mallchat.common.common.utils.sensitiveWord.SensitiveWordBs; +import com.abin.mallchat.common.sensitive.MyWordDeny; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SensitiveWordConfig { + + @Autowired + private MyWordDeny myWordDeny; + + /** + * 初始化引导类 + * + * @return 初始化引导类 + * @since 1.0.0 + */ + @Bean + public SensitiveWordBs sensitiveWordBs() { + return SensitiveWordBs.newInstance() + .filterStrategy(DFAFilter.getInstance()) + .sensitiveWord(myWordDeny) + .init(); + } + +} \ No newline at end of file diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/FrequencyControlDTO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/FrequencyControlDTO.java new file mode 100644 index 0000000..3399773 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/dto/FrequencyControlDTO.java @@ -0,0 +1,42 @@ +package com.abin.mallchat.common.common.domain.dto; + +import lombok.*; + +import java.util.concurrent.TimeUnit; + +@Data +@ToString +@Builder +@NoArgsConstructor +@AllArgsConstructor +/** 限流策略定义 + * @author linzhihan + * @date 2023/07/03 + * + */ +public class FrequencyControlDTO { + /** + * 代表频控的Key 如果target为Key的话 这里要传值用于构建redis的Key target为Ip或者UID的话会从上下文取值 Key字段无需传值 + */ + private String key; + /** + * 频控时间范围,默认单位秒 + * + * @return 时间范围 + */ + private Integer time; + + /** + * 频控时间单位,默认秒 + * + * @return 单位 + */ + private TimeUnit unit; + + /** + * 单位时间内最大访问次数 + * + * @return 次数 + */ + private Integer count; +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/IdReqVO.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/IdReqVO.java new file mode 100644 index 0000000..9c0f417 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/domain/vo/request/IdReqVO.java @@ -0,0 +1,20 @@ +package com.abin.mallchat.common.common.domain.vo.request; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotNull; + +/** + * @author zhongzb create on 2021/05/31 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdReqVO { + @ApiModelProperty("id") + @NotNull + private long id; +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java index edcd880..b1b7ec8 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/CommonErrorEnum.java @@ -13,7 +13,7 @@ import lombok.Getter; public enum CommonErrorEnum implements ErrorEnum { SYSTEM_ERROR(-1, "系统出小差了,请稍后再试哦~~"), - PARAM_VALID(-2, "参数校验失败"), + PARAM_VALID(-2, "参数校验失败{0}"), FREQUENCY_LIMIT(-3, "请求太频繁了,请稍后再试哦~~"), LOCK_LIMIT(-4, "请求太频繁了,请稍后再试哦~~"), ; diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/FrequencyControlException.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/FrequencyControlException.java new file mode 100644 index 0000000..fbcaff7 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/FrequencyControlException.java @@ -0,0 +1,39 @@ +package com.abin.mallchat.common.common.exception; + +import lombok.Data; + +/** + * 自定义限流异常 + * + * @author linzhihan + * @date 2023/07/034 + */ +@Data +public class FrequencyControlException extends RuntimeException { + private static final long serialVersionUID = 1L; + + /** + *  错误码 + */ + protected Integer errorCode; + + /** + *  错误信息 + */ + protected String errorMsg; + + public FrequencyControlException() { + super(); + } + + public FrequencyControlException(String errorMsg) { + super(errorMsg); + this.errorMsg = errorMsg; + } + + public FrequencyControlException(ErrorEnum error) { + super(error.getErrorMsg()); + this.errorCode = error.getErrorCode(); + this.errorMsg = error.getErrorMsg(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java index 3a986e1..066e210 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/exception/GlobalExceptionHandler.java @@ -42,7 +42,7 @@ public class GlobalExceptionHandler { */ @ExceptionHandler(value = NullPointerException.class) public ApiResult exceptionHandler(NullPointerException e) { - log.error("null point exception!The reason is:{}", e.getMessage(), e); + log.error("null point exception!The reason is: ", e); return ApiResult.fail(CommonErrorEnum.SYSTEM_ERROR); } @@ -73,4 +73,12 @@ public class GlobalExceptionHandler { return ApiResult.fail(-1, String.format("不支持'%s'请求", e.getMethod())); } + /** + * 限流异常 + */ + @ExceptionHandler(value = FrequencyControlException.class) + public ApiResult frequencyControlExceptionHandler(FrequencyControlException e) { + log.info("frequencyControl exception!The reason is:{}", e.getMessage(), e); + return ApiResult.fail(e.getErrorCode(), e.getMessage()); + } } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/handler/GlobalUncaughtExceptionHandler.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/handler/GlobalUncaughtExceptionHandler.java index b3229e6..4dc9775 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/handler/GlobalUncaughtExceptionHandler.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/handler/GlobalUncaughtExceptionHandler.java @@ -9,7 +9,6 @@ public class GlobalUncaughtExceptionHandler implements Thread.UncaughtException @Override public void uncaughtException(Thread t, Throwable e) { log.error("Exception in thread {} ", t.getName(), e); - e.printStackTrace(); } } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/AbstractFrequencyControlService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/AbstractFrequencyControlService.java new file mode 100644 index 0000000..211f9f8 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/AbstractFrequencyControlService.java @@ -0,0 +1,124 @@ +package com.abin.mallchat.common.common.service.frequencycontrol; + +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.exception.CommonErrorEnum; +import com.abin.mallchat.common.common.exception.FrequencyControlException; +import com.abin.mallchat.common.common.utils.AssertUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ObjectUtils; + +import javax.annotation.PostConstruct; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 抽象类频控服务 其他类如果要实现限流服务 直接注入使用通用限流类 + * 后期会通过继承此类实现令牌桶等算法 + * + * @author linzhihan + * @date 2023/07/03 + * @see TotalCountWithInFixTimeFrequencyController 通用限流类 + */ +@Slf4j +public abstract class AbstractFrequencyControlService { + + @PostConstruct + protected void registerMyselfToFactory() { + FrequencyControlStrategyFactory.registerFrequencyController(getStrategyName(), this); + } + + /** + * @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value + * @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 + * @return 业务方法执行的返回值 + * @throws Throwable + */ + private T executeWithFrequencyControlMap(Map frequencyControlMap, SupplierThrowWithoutParam supplier) throws Throwable { + if (reachRateLimit(frequencyControlMap)) { + throw new FrequencyControlException(CommonErrorEnum.FREQUENCY_LIMIT); + } + try { + return supplier.get(); + } finally { + //不管成功还是失败,都增加次数 + addFrequencyControlStatisticsCount(frequencyControlMap); + } + } + + + /** + * 多限流策略的编程式调用方法 无参的调用方法 + * + * @param frequencyControlList 频控列表 包含每一个频率控制的定义以及顺序 + * @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 + * @return 业务方法执行的返回值 + * @throws Throwable 被限流或者限流策略定义错误 + */ + @SuppressWarnings("unchecked") + public T executeWithFrequencyControlList(List frequencyControlList, SupplierThrowWithoutParam supplier) throws Throwable { + boolean existsFrequencyControlHasNullKey = frequencyControlList.stream().anyMatch(frequencyControl -> ObjectUtils.isEmpty(frequencyControl.getKey())); + AssertUtil.isFalse(existsFrequencyControlHasNullKey, "限流策略的Key字段不允许出现空值"); + Map frequencyControlDTOMap = frequencyControlList.stream().collect(Collectors.groupingBy(FrequencyControlDTO::getKey, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(0)))); + return executeWithFrequencyControlMap((Map) frequencyControlDTOMap, supplier); + } + + /** + * 单限流策略的调用方法-编程式调用 + * + * @param frequencyControl 单个频控对象 + * @param supplier 服务提供着 + * @return 业务方法执行结果 + * @throws Throwable + */ + public T executeWithFrequencyControl(K frequencyControl, SupplierThrowWithoutParam supplier) throws Throwable { + return executeWithFrequencyControlList(Collections.singletonList(frequencyControl), supplier); + } + + + @FunctionalInterface + public interface SupplierThrowWithoutParam { + + /** + * Gets a result. + * + * @return a result + */ + T get() throws Throwable; + } + + @FunctionalInterface + public interface Executor { + + /** + * Gets a result. + * + * @return a result + */ + void execute() throws Throwable; + } + + /** + * 是否达到限流阈值 子类实现 每个子类都可以自定义自己的限流逻辑判断 + * + * @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value + * @return true-方法被限流 false-方法没有被限流 + */ + protected abstract boolean reachRateLimit(Map frequencyControlMap); + + /** + * 增加限流统计次数 子类实现 每个子类都可以自定义自己的限流统计信息增加的逻辑 + * + * @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value + */ + protected abstract void addFrequencyControlStatisticsCount(Map frequencyControlMap); + + /** + * 获取策略名称 + * + * @return 策略名称 + */ + protected abstract String getStrategyName(); + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlStrategyFactory.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlStrategyFactory.java new file mode 100644 index 0000000..ec211a8 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlStrategyFactory.java @@ -0,0 +1,51 @@ +package com.abin.mallchat.common.common.service.frequencycontrol; + +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 限流策略工厂 + * + * @author linzhihan + * @date 2023/07/03 + */ +public class FrequencyControlStrategyFactory { + /** + * 指定时间内总次数限流 + */ + public static final String TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER = "TotalCountWithInFixTime"; + /** + * 限流策略集合 + */ + static Map> frequencyControlServiceStrategyMap = new ConcurrentHashMap<>(8); + + /** + * 将策略类放入工厂 + * + * @param strategyName 策略名称 + * @param abstractFrequencyControlService 策略类 + */ + public static void registerFrequencyController(String strategyName, AbstractFrequencyControlService abstractFrequencyControlService) { + frequencyControlServiceStrategyMap.put(strategyName, abstractFrequencyControlService); + } + + /** + * 根据名称获取策略类 + * + * @param strategyName 策略名称 + * @return 对应的限流策略类 + */ + @SuppressWarnings("unchecked") + public static AbstractFrequencyControlService getFrequencyControllerByName(String strategyName) { + return (AbstractFrequencyControlService) frequencyControlServiceStrategyMap.get(strategyName); + } + + /** + * 构造器私有 + */ + private FrequencyControlStrategyFactory() { + + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlUtil.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlUtil.java new file mode 100644 index 0000000..0e94041 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/FrequencyControlUtil.java @@ -0,0 +1,63 @@ +package com.abin.mallchat.common.common.service.frequencycontrol; + +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.utils.AssertUtil; +import org.apache.commons.lang3.ObjectUtils; + +import java.util.List; + +/** + * 限流工具类 提供编程式的限流调用方法 + * + * @author linzhihan + * @date 2023/07/03 + */ +public class FrequencyControlUtil { + + /** + * 单限流策略的调用方法-编程式调用 + * + * @param strategyName 策略名称 + * @param frequencyControl 单个频控对象 + * @param supplier 服务提供着 + * @return 业务方法执行结果 + * @throws Throwable + */ + public static T executeWithFrequencyControl(String strategyName, K frequencyControl, AbstractFrequencyControlService.SupplierThrowWithoutParam supplier) throws Throwable { + AbstractFrequencyControlService frequencyController = FrequencyControlStrategyFactory.getFrequencyControllerByName(strategyName); + return frequencyController.executeWithFrequencyControl(frequencyControl, supplier); + } + + public static void executeWithFrequencyControl(String strategyName, K frequencyControl, AbstractFrequencyControlService.Executor executor) throws Throwable { + AbstractFrequencyControlService frequencyController = FrequencyControlStrategyFactory.getFrequencyControllerByName(strategyName); + frequencyController.executeWithFrequencyControl(frequencyControl, () -> { + executor.execute(); + return null; + }); + } + + + /** + * 多限流策略的编程式调用方法调用方法 + * + * @param strategyName 策略名称 + * @param frequencyControlList 频控列表 包含每一个频率控制的定义以及顺序 + * @param supplier 函数式入参-代表每个频控方法执行的不同的业务逻辑 + * @return 业务方法执行的返回值 + * @throws Throwable 被限流或者限流策略定义错误 + */ + public static T executeWithFrequencyControlList(String strategyName, List frequencyControlList, AbstractFrequencyControlService.SupplierThrowWithoutParam supplier) throws Throwable { + boolean existsFrequencyControlHasNullKey = frequencyControlList.stream().anyMatch(frequencyControl -> ObjectUtils.isEmpty(frequencyControl.getKey())); + AssertUtil.isFalse(existsFrequencyControlHasNullKey, "限流策略的Key字段不允许出现空值"); + AbstractFrequencyControlService frequencyController = FrequencyControlStrategyFactory.getFrequencyControllerByName(strategyName); + return frequencyController.executeWithFrequencyControlList(frequencyControlList, supplier); + } + + /** + * 构造器私有 + */ + private FrequencyControlUtil() { + + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/TotalCountWithInFixTimeFrequencyController.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/TotalCountWithInFixTimeFrequencyController.java new file mode 100644 index 0000000..531e36c --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/service/frequencycontrol/TotalCountWithInFixTimeFrequencyController.java @@ -0,0 +1,64 @@ +package com.abin.mallchat.common.common.service.frequencycontrol; + +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.utils.RedisUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlStrategyFactory.TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; + +/** + * 抽象类频控服务 -使用redis实现 固定时间内不超过固定次数的限流类 + * + * @author linzhihan + * @date 2023/07/03 + */ +@Slf4j +@Service +public class TotalCountWithInFixTimeFrequencyController extends AbstractFrequencyControlService { + + + /** + * 是否达到限流阈值 子类实现 每个子类都可以自定义自己的限流逻辑判断 + * + * @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value + * @return true-方法被限流 false-方法没有被限流 + */ + @Override + protected boolean reachRateLimit(Map frequencyControlMap) { + //批量获取redis统计的值 + List frequencyKeys = new ArrayList<>(frequencyControlMap.keySet()); + List countList = RedisUtils.mget(frequencyKeys, Integer.class); + for (int i = 0; i < frequencyKeys.size(); i++) { + String key = frequencyKeys.get(i); + Integer count = countList.get(i); + int frequencyControlCount = frequencyControlMap.get(key).getCount(); + if (Objects.nonNull(count) && count >= frequencyControlCount) { + //频率超过了 + log.warn("frequencyControl limit key:{},count:{}", key, count); + return true; + } + } + return false; + } + + /** + * 增加限流统计次数 子类实现 每个子类都可以自定义自己的限流统计信息增加的逻辑 + * + * @param frequencyControlMap 定义的注解频控 Map中的Key-对应redis的单个频控的Key Map中的Value-对应redis的单个频控的Key限制的Value + */ + @Override + protected void addFrequencyControlStatisticsCount(Map frequencyControlMap) { + frequencyControlMap.forEach((k, v) -> RedisUtils.inc(k, v.getTime(), v.getUnit())); + } + + @Override + protected String getStrategyName() { + return TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java index fe2a97e..f9ce4d5 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/JwtUtils.java @@ -67,7 +67,7 @@ public class JwtUtils { DecodedJWT jwt = verifier.verify(token); return jwt.getClaims(); } catch (Exception e) { - log.info("decode error,token:{}", token, e); + log.error("decode error,token:{}", token, e); } return null; } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlTitleDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlDiscover.java similarity index 64% rename from mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlTitleDiscover.java rename to mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlDiscover.java index 79cbefe..4da24e9 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlTitleDiscover.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/AbstractUrlDiscover.java @@ -3,14 +3,14 @@ package com.abin.mallchat.common.common.utils.discover; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import com.abin.mallchat.common.common.utils.FutureUtils; +import com.abin.mallchat.common.common.utils.discover.domain.UrlInfo; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.jsoup.Connection; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.springframework.data.util.Pair; -import javax.annotation.Nullable; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,46 +20,55 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; /** - * Description: urlTitle查询抽象类 - * Author: abin - * Date: 2023-05-27 + * @author zhaoqichao + * @date 2023/7/3 16:38 */ @Slf4j -public abstract class AbstractUrlTitleDiscover implements UrlTitleDiscover { +public abstract class AbstractUrlDiscover implements UrlDiscover { //链接识别的正则 private static final Pattern PATTERN = Pattern.compile("((http|https)://)?(www.)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?"); + @Nullable @Override - public Map getContentTitleMap(String content) { + public Map getUrlContentMap(String content) { + if (StrUtil.isBlank(content)) { return new HashMap<>(); } List matchList = ReUtil.findAll(PATTERN, content, 0); + //并行请求 - List>> futures = matchList.stream().map(match -> CompletableFuture.supplyAsync(() -> { - String title = getUrlTitle(match); - return StringUtils.isNotEmpty(title) ? Pair.of(match, title) : null; + List>> futures = matchList.stream().map(match -> CompletableFuture.supplyAsync(() -> { + UrlInfo urlInfo = getContent(match); + return Objects.isNull(urlInfo) ? null : Pair.of(match, urlInfo); })).collect(Collectors.toList()); - CompletableFuture>> future = FutureUtils.sequenceNonNull(futures); + CompletableFuture>> future = FutureUtils.sequenceNonNull(futures); //结果组装 return future.join().stream().collect(Collectors.toMap(Pair::getFirst, Pair::getSecond, (a, b) -> a)); } @Nullable @Override - public String getUrlTitle(String url) { + public UrlInfo getContent(String url) { Document document = getUrlDocument(assemble(url)); if (Objects.isNull(document)) { return null; } - return getDocTitle(document); + + return UrlInfo.builder() + .title(getTitle(document)) + .description(getDescription(document)) + .image(getImage(assemble(url),document)).build(); } + private String assemble(String url) { + if (!StrUtil.startWith(url, "http")) { return "http://" + url; } + return url; } @@ -69,8 +78,9 @@ public abstract class AbstractUrlTitleDiscover implements UrlTitleDiscover { connect.timeout(2000); return connect.get(); } catch (Exception e) { - log.error("find title error:url:{}", matchUrl, e); + log.error("find error:url:{}", matchUrl, e); } return null; } + } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlDiscover.java new file mode 100644 index 0000000..2f3b842 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlDiscover.java @@ -0,0 +1,52 @@ +package com.abin.mallchat.common.common.utils.discover; + +import cn.hutool.core.util.StrUtil; +import io.jsonwebtoken.lang.Objects; +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; +import org.jsoup.nodes.Document; +import org.jsoup.select.Elements; + +/** + * @author zhaoqichao + * @date 2023/7/3 16:54 + */ +public class CommonUrlDiscover extends AbstractUrlDiscover { + @Nullable + @Override + public String getTitle(Document document) { + return document.title(); + } + + @Nullable + @Override + public String getDescription(Document document) { + String description = document.head().select("meta[name=description]").attr("content"); + String keywords = document.head().select("meta[name=keywords]").attr("content"); + String content = StrUtil.isNotBlank(description) ? description : keywords; + //只保留一句话的描述 + return StrUtil.isNotBlank(content) ? content.substring(0, content.indexOf("。")) : content; + } + + @Nullable + @Override + public String getImage(String url, Document document) { + String image = document.select("link[type=image/x-icon]").attr("href"); + //如果没有去匹配含有icon属性的logo + String href = StrUtil.isEmpty(image) ? document.select("link[rel$=icon]").attr("href") : image; + //如果icon中已经包含了url部分域名 + if (StrUtil.isNotBlank(StrUtil.removeAny(StrUtil.removeAny(href, "/"), "favicon.ico")) && + StrUtil.containsAny(StrUtil.removePrefix(url, "http://"), StrUtil.removeAny(StrUtil.removeAny(href, "/"), "favicon.ico"))) { + return "http://" + StrUtil.removePrefix(href, "/"); + } + //如果url已经包含了logo + if (StrUtil.containsAny(url, "favicon")) { + return url; + } + //如果logo中有url + if (StrUtil.containsAny(href, "http") || StrUtil.containsAny(href, "https")) { + return href; + } + return StrUtil.format("{}/{}", url, StrUtil.removePrefix(href, "/")); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlTitleDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlTitleDiscover.java deleted file mode 100644 index 6471610..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/CommonUrlTitleDiscover.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.abin.mallchat.common.common.utils.discover; - -import org.jsoup.nodes.Document; - -/** - * Description: 通用的标题解析类 - * Author: abin - * Date: 2023-05-27 - */ -public class CommonUrlTitleDiscover extends AbstractUrlTitleDiscover { - @Override - public String getDocTitle(Document document) { - return document.title(); - } -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlDiscover.java new file mode 100644 index 0000000..eef3c11 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlDiscover.java @@ -0,0 +1,60 @@ +package com.abin.mallchat.common.common.utils.discover; + +import cn.hutool.core.util.StrUtil; +import org.jetbrains.annotations.Nullable; +import org.jsoup.nodes.Document; + +import java.util.ArrayList; +import java.util.List; + +/** + * Description: 具有优先级的title查询器 + * Author: abin + * Date: 2023-05-27 + */ +public class PrioritizedUrlDiscover extends AbstractUrlDiscover { + + private final List urlDiscovers = new ArrayList<>(2); + + public PrioritizedUrlDiscover() { + urlDiscovers.add(new WxUrlDiscover()); + urlDiscovers.add(new CommonUrlDiscover()); + } + + + @Nullable + @Override + public String getTitle(Document document) { + for (UrlDiscover urlDiscover : urlDiscovers) { + String urlTitle = urlDiscover.getTitle(document); + if (StrUtil.isNotBlank(urlTitle)) { + return urlTitle; + } + } + return null; + } + + @Nullable + @Override + public String getDescription(Document document) { + for (UrlDiscover urlDiscover : urlDiscovers) { + String urlDescription = urlDiscover.getDescription(document); + if (StrUtil.isNotBlank(urlDescription)) { + return urlDescription; + } + } + return null; + } + + @Nullable + @Override + public String getImage(String url, Document document) { + for (UrlDiscover urlDiscover : urlDiscovers) { + String urlImage = urlDiscover.getImage(url,document); + if (StrUtil.isNotBlank(urlImage)) { + return urlImage; + } + } + return null; + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlTitleDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlTitleDiscover.java deleted file mode 100644 index 8c7fb4d..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/PrioritizedUrlTitleDiscover.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.abin.mallchat.common.common.utils.discover; - -import cn.hutool.core.util.StrUtil; -import org.jsoup.nodes.Document; - -import java.util.ArrayList; -import java.util.List; - -/** - * Description: 具有优先级的title查询器 - * Author: abin - * Date: 2023-05-27 - */ -public class PrioritizedUrlTitleDiscover extends AbstractUrlTitleDiscover { - - private final List urlTitleDiscovers = new ArrayList<>(2); - - public PrioritizedUrlTitleDiscover() { - urlTitleDiscovers.add(new CommonUrlTitleDiscover()); - urlTitleDiscovers.add(new WxUrlTitleDiscover()); - } - - @Override - public String getDocTitle(Document document) { - for (UrlTitleDiscover urlTitleDiscover : urlTitleDiscovers) { - String urlTitle = urlTitleDiscover.getDocTitle(document); - if (StrUtil.isNotBlank(urlTitle)) { - return urlTitle; - } - } - return null; - } -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlDiscover.java new file mode 100644 index 0000000..3515c28 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlDiscover.java @@ -0,0 +1,46 @@ +package com.abin.mallchat.common.common.utils.discover; + +import cn.hutool.core.date.StopWatch; +import cn.hutool.core.util.StrUtil; +import com.abin.mallchat.common.common.utils.discover.domain.UrlInfo; +import org.jsoup.nodes.Document; + +import javax.annotation.Nullable; +import java.util.Map; + +/** + * @author zhaoqichao + * @date 2023/7/3 16:34 + */ +public interface UrlDiscover { + + + @Nullable + Map getUrlContentMap(String content); + + @Nullable + UrlInfo getContent(String url); + + @Nullable + String getTitle(Document document); + + @Nullable + String getDescription(Document document); + + @Nullable + String getImage(String url, Document document); + + public static void main(String[] args) { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + String longStr = "其中包含一个URL www.baidu.com,一个带有端口号的URL http://www.jd.com:80, 一个带有路径的URL http://mallchat.cn, 还有美团技术文章https://mp.weixin.qq.com/s/hwTf4bDck9_tlFpgVDeIKg "; +// String longStr = "一个带有端口号的URL http://www.jd.com:80,"; +// String longStr = "一个带有路径的URL http://mallchat.cn"; + PrioritizedUrlDiscover discover = new PrioritizedUrlDiscover(); + final Map map = discover.getUrlContentMap(longStr); + System.out.println(map); + stopWatch.stop(); + long cost = stopWatch.getTotalTimeMillis(); + System.out.println(cost); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlTitleDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlTitleDiscover.java deleted file mode 100644 index e2fac68..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/UrlTitleDiscover.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.abin.mallchat.common.common.utils.discover; - -import cn.hutool.core.date.StopWatch; -import org.jsoup.nodes.Document; - -import javax.annotation.Nullable; -import java.util.Map; - -public interface UrlTitleDiscover { - - - @Nullable - Map getContentTitleMap(String content); - - - @Nullable - String getUrlTitle(String url); - - @Nullable - String getDocTitle(Document document); - - public static void main(String[] args) {//用异步多任务查询并合并 974 //串行访问的速度1349 1291 1283 1559 - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); - String longStr = "这是一个很长的字符串再来 www.github.com,其中包含一个URL www.baidu.com,, 一个带有端口号的URL http://www.jd.com:80, 一个带有路径的URL http://mallchat.cn, 还有美团技术文章https://mp.weixin.qq.com/s/hwTf4bDck9_tlFpgVDeIKg "; - PrioritizedUrlTitleDiscover discover = new PrioritizedUrlTitleDiscover(); - Map contentTitleMap = discover.getContentTitleMap(longStr); - System.out.println(contentTitleMap); -// -// Jsoup.connect("http:// www.github.com"); - stopWatch.stop(); - long cost = stopWatch.getTotalTimeMillis(); - System.out.println(cost); - }//{http://mallchat.cn=MallChat, www.baidu.com=百度一下,你就知道, https://mp.weixin.qq.com/s/hwTf4bDck9_tlFpgVDeIKg=超大规模数据库集群保稳系列之二:数据库攻防演练建设实践, http://www.jd.com:80=京东(JD.COM)-正品低价、品质保障、配送及时、轻松购物!} -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlDiscover.java new file mode 100644 index 0000000..a5bb330 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlDiscover.java @@ -0,0 +1,30 @@ +package com.abin.mallchat.common.common.utils.discover; + +import org.jetbrains.annotations.Nullable; +import org.jsoup.nodes.Document; + +/** + * Description: 针对微信公众号文章的标题获取类 + * Author: abin + * Date: 2023-05-27 + */ +public class WxUrlDiscover extends AbstractUrlDiscover { + + @Nullable + @Override + public String getTitle(Document document) { + return document.getElementsByAttributeValue("property", "og:title").attr("content"); + } + + @Nullable + @Override + public String getDescription(Document document) { + return document.getElementsByAttributeValue("property", "og:description").attr("content"); + } + + @Nullable + @Override + public String getImage(String url, Document document) { + return document.getElementsByAttributeValue("property", "og:image").attr("content"); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlTitleDiscover.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlTitleDiscover.java deleted file mode 100644 index 29b1172..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/WxUrlTitleDiscover.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.abin.mallchat.common.common.utils.discover; - -import org.jsoup.nodes.Document; - -/** - * Description: 针对微信公众号文章的标题获取类 - * Author: abin - * Date: 2023-05-27 - */ -public class WxUrlTitleDiscover extends AbstractUrlTitleDiscover { - @Override - public String getDocTitle(Document document) { - return document.getElementsByAttributeValue("property", "og:title").attr("content"); - } -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/domain/UrlInfo.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/domain/UrlInfo.java new file mode 100644 index 0000000..8f02b7c --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/discover/domain/UrlInfo.java @@ -0,0 +1,32 @@ +package com.abin.mallchat.common.common.utils.discover.domain; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhaoqichao + * @date 2023/7/3 16:12 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UrlInfo { + /** + * 标题 + **/ + String title; + + /** + * 描述 + **/ + String description; + + /** + * 网站LOGO + **/ + String image; + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/oss/MinIOTemplate.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/oss/MinIOTemplate.java index f49a5fa..99e6e6a 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/oss/MinIOTemplate.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/oss/MinIOTemplate.java @@ -136,8 +136,8 @@ public class MinIOTemplate { String uid = Optional.ofNullable(req.getUid()).map(String::valueOf).orElse("000000"); cn.hutool.core.lang.UUID uuid = cn.hutool.core.lang.UUID.fastUUID(); String suffix = FileNameUtil.getSuffix(req.getFileName()); - String year = DateUtil.format(new Date(), DatePattern.NORM_YEAR_PATTERN); - return req.getFilePath() + StrUtil.SLASH + year + StrUtil.SLASH + uid + StrUtil.SLASH + uuid + StrUtil.DOT + suffix; + String yearAndMonth = DateUtil.format(new Date(), DatePattern.NORM_MONTH_PATTERN); + return req.getFilePath() + StrUtil.SLASH + yearAndMonth + StrUtil.SLASH + uid + StrUtil.SLASH + uuid + StrUtil.DOT + suffix; } /** diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils0.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/ACFilter.java similarity index 88% rename from mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils0.java rename to mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/ACFilter.java index f67c694..618908f 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils0.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/ACFilter.java @@ -1,4 +1,4 @@ -package com.abin.mallchat.common.common.utils; +package com.abin.mallchat.common.common.utils.sensitiveWord; import com.abin.mallchat.common.common.algorithm.ac.ACTrie; import com.abin.mallchat.common.common.algorithm.ac.MatchResult; @@ -15,7 +15,7 @@ import java.util.Objects; * * Created by berg on 2023/6/18. */ -public class SensitiveWordUtils0 { +public class ACFilter implements SensitiveWordFilter { private final static char mask_char = '*'; // 替代字符 @@ -27,7 +27,7 @@ public class SensitiveWordUtils0 { * @param text 文本 * @return boolean */ - public static boolean hasSensitiveWord(String text) { + public boolean hasSensitiveWord(String text) { if (StringUtils.isBlank(text)) return false; return !Objects.equals(filter(text), text); } @@ -38,7 +38,7 @@ public class SensitiveWordUtils0 { * @param text 待替换文本 * @return 替换后的文本 */ - public static String filter(String text) { + public String filter(String text) { if (StringUtils.isBlank(text)) return text; List matchResults = ac_trie.matches(text); StringBuffer result = new StringBuffer(text); @@ -62,7 +62,7 @@ public class SensitiveWordUtils0 { * * @param words 敏感词数组 */ - public static void loadWord(List words) { + public void loadWord(List words) { if (words == null) return; ac_trie = new ACTrie(words); } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/DFAFilter.java similarity index 87% rename from mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils.java rename to mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/DFAFilter.java index 298f876..e766e2e 100644 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/SensitiveWordUtils.java +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/DFAFilter.java @@ -1,4 +1,4 @@ -package com.abin.mallchat.common.common.utils; +package com.abin.mallchat.common.common.utils.sensitiveWord; import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; @@ -18,7 +18,10 @@ import java.util.*; * @author zhaoyuhang * @date 2023/06/19 */ -public final class SensitiveWordUtils { +public final class DFAFilter implements SensitiveWordFilter { + + private DFAFilter() { + } private static Word root = new Word(' '); // 敏感词字典的根节点 private final static char replace = '*'; // 替代字符 private final static String skipChars = " !*-+_=,,.@;:;:。、??()()【】[]《》<>“”\"‘’"; // 遇到这些字符就会跳过 @@ -30,6 +33,10 @@ public final class SensitiveWordUtils { } } + public static DFAFilter getInstance() { + return new DFAFilter(); + } + /** * 判断文本中是否存在敏感词 @@ -37,7 +44,7 @@ public final class SensitiveWordUtils { * @param text 文本 * @return true: 存在敏感词, false: 不存在敏感词 */ - public static boolean hasSensitiveWord(String text) { + public boolean hasSensitiveWord(String text) { if (StringUtils.isBlank(text)) return false; return !Objects.equals(filter(text), text); } @@ -48,7 +55,7 @@ public final class SensitiveWordUtils { * @param text 待替换文本 * @return 替换后的文本 */ - public static String filter(String text) { + public String filter(String text) { StringBuilder result = new StringBuilder(text); int index = 0; while (index < result.length()) { @@ -93,7 +100,7 @@ public final class SensitiveWordUtils { * * @param words 敏感词数组 */ - public static void loadWord(List words) { + public void loadWord(List words) { if (!CollectionUtils.isEmpty(words)) { Word newRoot = new Word(' '); words.forEach(word -> loadWord(word, newRoot)); @@ -106,7 +113,7 @@ public final class SensitiveWordUtils { * * @param word 词 */ - public static void loadWord(String word, Word root) { + public void loadWord(String word, Word root) { if (StringUtils.isBlank(word)) { return; } @@ -136,7 +143,7 @@ public final class SensitiveWordUtils { * * @param path 文本文件的绝对路径 */ - public static void loadWordFromFile(String path) { + public void loadWordFromFile(String path) { try (InputStream inputStream = Files.newInputStream(Paths.get(path))) { loadWord(inputStream); } catch (IOException e) { @@ -150,7 +157,7 @@ public final class SensitiveWordUtils { * @param inputStream 文本文件输入流 * @throws IOException IO异常 */ - public static void loadWord(InputStream inputStream) throws IOException { + public void loadWord(InputStream inputStream) throws IOException { try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { String line; ArrayList list = new ArrayList<>(); @@ -167,7 +174,7 @@ public final class SensitiveWordUtils { * @param c 待检测字符 * @return true: 需要跳过, false: 不需要跳过 */ - private static boolean skip(char c) { + private boolean skip(char c) { return skipSet.contains(c); } @@ -186,17 +193,7 @@ public final class SensitiveWordUtils { public Word(char c) { this.c = c; - this.end = false; this.next = new HashMap<>(); } } - - public static void main(String[] args) { - String text = "白日,梦"; - String filter = filter(text); - System.out.println(filter); - - - } - } diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/IWordDeny.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/IWordDeny.java new file mode 100644 index 0000000..4259c91 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/IWordDeny.java @@ -0,0 +1,18 @@ +package com.abin.mallchat.common.common.utils.sensitiveWord; + +import java.util.List; + +/** + * 敏感词 + * + * @author zhaoyuhang + * @date 2023/07/09 + */ +public interface IWordDeny { + /** + * 获取结果 + * @return 结果 + * @since 0.0.13 + */ + List deny(); +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordBs.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordBs.java new file mode 100644 index 0000000..006fd2f --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordBs.java @@ -0,0 +1,102 @@ +package com.abin.mallchat.common.common.utils.sensitiveWord; + +import java.util.List; + +/** + * 敏感词引导类 + * + * @author zhaoyuhang + * @date 2023/07/08 + */ +public class SensitiveWordBs { + + /** + * 私有化构造器 + */ + private SensitiveWordBs() { + } + + /** + * 脱敏策略 + */ + private SensitiveWordFilter sensitiveWordFilter = DFAFilter.getInstance(); + + /** + * 敏感词列表 + */ + private IWordDeny wordDeny; + + public static SensitiveWordBs newInstance() { + return new SensitiveWordBs(); + } + + /** + * 初始化 + * + * 1. 根据配置,初始化对应的 map。比较消耗性能。 + * @since 0.0.13 + * @return this + */ + public SensitiveWordBs init() { + + List words = wordDeny.deny(); + loadWord(words); + return this; + } + + /** + * 过滤策略 + * + * @param filter 过滤器 + * @return 结果 + * @since 0.7.0 + */ + public SensitiveWordBs filterStrategy(SensitiveWordFilter filter) { + if (filter == null) { + throw new IllegalArgumentException("filter can not be null"); + } + this.sensitiveWordFilter = filter; + return this; + } + + public SensitiveWordBs sensitiveWord(IWordDeny wordDeny) { + if (wordDeny == null) { + throw new IllegalArgumentException("wordDeny can not be null"); + } + this.wordDeny = wordDeny; + return this; + } + + + + + /** + * 有敏感词 + * + * @param text 文本 + * @return boolean + */ + public boolean hasSensitiveWord(String text) { + return sensitiveWordFilter.hasSensitiveWord(text); + } + + /** + * 过滤 + * + * @param text 文本 + * @return {@link String} + */ + public String filter(String text) { + return sensitiveWordFilter.filter(text); + } + + /** + * 加载敏感词列表 + * + * @param words 敏感词数组 + */ + private void loadWord(List words) { + sensitiveWordFilter.loadWord(words); + } + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordFilter.java b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordFilter.java new file mode 100644 index 0000000..afe2533 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/common/utils/sensitiveWord/SensitiveWordFilter.java @@ -0,0 +1,37 @@ +package com.abin.mallchat.common.common.utils.sensitiveWord; + + +import java.util.List; + +/** + * 敏感词过滤 + * + * @author zhaoyuhang + * @date 2023/07/08 + */ +public interface SensitiveWordFilter { + /** + * 有敏感词 + * + * @param text 文本 + * @return boolean + */ + boolean hasSensitiveWord(String text); + + /** + * 过滤 + * + * @param text 文本 + * @return {@link String} + */ + String filter(String text); + + /** + * 加载敏感词列表 + * + * @param words 敏感词数组 + */ + void loadWord(List words); + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/MyWordDeny.java b/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/MyWordDeny.java new file mode 100644 index 0000000..a47d623 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/MyWordDeny.java @@ -0,0 +1,24 @@ +package com.abin.mallchat.common.sensitive; + +import com.abin.mallchat.common.common.utils.sensitiveWord.IWordDeny; +import com.abin.mallchat.common.sensitive.dao.SensitiveWordDao; +import com.abin.mallchat.common.sensitive.domain.SensitiveWord; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class MyWordDeny implements IWordDeny { + @Autowired + private SensitiveWordDao sensitiveWordDao; + + @Override + public List deny() { + return sensitiveWordDao.list() + .stream() + .map(SensitiveWord::getWord) + .collect(Collectors.toList()); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/ISensitiveWordService.java b/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/ISensitiveWordService.java deleted file mode 100644 index f62783a..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/ISensitiveWordService.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.abin.mallchat.common.sensitive.service; - -public interface ISensitiveWordService { - -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/impl/SensitiveWordServiceImpl.java b/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/impl/SensitiveWordServiceImpl.java deleted file mode 100644 index d7fd50f..0000000 --- a/mallchat-common/src/main/java/com/abin/mallchat/common/sensitive/service/impl/SensitiveWordServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.abin.mallchat.common.sensitive.service.impl; - -import com.abin.mallchat.common.common.utils.SensitiveWordUtils; -import com.abin.mallchat.common.sensitive.dao.SensitiveWordDao; -import com.abin.mallchat.common.sensitive.domain.SensitiveWord; -import com.abin.mallchat.common.sensitive.service.ISensitiveWordService; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; - -import javax.annotation.PostConstruct; -import java.util.List; -import java.util.stream.Collectors; - -@Service -@Slf4j -public class SensitiveWordServiceImpl implements ISensitiveWordService { - @Autowired - private SensitiveWordDao sensitiveWordDao; - @Autowired - private ThreadPoolTaskExecutor threadPoolTaskExecutor; - - @PostConstruct - public void initSensitiveWord() { - threadPoolTaskExecutor.execute(() -> { - log.info("[initSensitiveWord] start"); - List list = sensitiveWordDao.list(); - if (!CollectionUtils.isEmpty(list)) { - List wordList = list.stream() - .map(SensitiveWord::getWord) - .collect(Collectors.toList()); - SensitiveWordUtils.loadWord(wordList); - } - log.info("[initSensitiveWord] end; loading sensitiveWords num:{}", list.size()); - }); - } -} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserEmojiDao.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserEmojiDao.java new file mode 100644 index 0000000..8b8b2ad --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/dao/UserEmojiDao.java @@ -0,0 +1,28 @@ +package com.abin.mallchat.common.user.dao; + +import com.abin.mallchat.common.user.domain.entity.UserEmoji; +import com.abin.mallchat.common.user.mapper.UserEmojiMapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + *

+ * 用户表情包 服务实现类 + *

+ * + * @author abin + * @since 2023-07-09 + */ +@Service +public class UserEmojiDao extends ServiceImpl { + + public List listByUid(Long uid) { + return lambdaQuery().eq(UserEmoji::getUid, uid).list(); + } + + public int countByUid(Long uid) { + return lambdaQuery().eq(UserEmoji::getUid, uid).count(); + } +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserEmoji.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserEmoji.java new file mode 100644 index 0000000..4a9f2dd --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/domain/entity/UserEmoji.java @@ -0,0 +1,65 @@ +package com.abin.mallchat.common.user.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.*; + +import java.io.Serializable; +import java.util.Date; + +/** + *

+ * 用户表情包 + *

+ * + * @author abin + * @since 2023-07-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +@TableName("user_emoji") +public class UserEmoji implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * id + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 用户表ID + */ + @TableField("uid") + private Long uid; + + /** + * 表情地址 + */ + @TableField("expression_url") + private String expressionUrl; + + /** + * 逻辑删除(0-正常,1-删除) + */ + @TableField("delete_status") + @TableLogic(value = "0", delval = "1") + private Integer deleteStatus; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 修改时间 + */ + @TableField("update_time") + private Date updateTime; + + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojiMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojiMapper.java new file mode 100644 index 0000000..cc36c9c --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojiMapper.java @@ -0,0 +1,16 @@ +package com.abin.mallchat.common.user.mapper; + +import com.abin.mallchat.common.user.domain.entity.UserEmoji; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 用户表情包 Mapper 接口 + *

+ * + * @author abin + * @since 2023-07-09 + */ +public interface UserEmojiMapper extends BaseMapper { + +} diff --git a/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojisMapper.java b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojisMapper.java new file mode 100644 index 0000000..2f1fbd2 --- /dev/null +++ b/mallchat-common/src/main/java/com/abin/mallchat/common/user/mapper/UserEmojisMapper.java @@ -0,0 +1,13 @@ +package com.abin.mallchat.common.user.mapper; + +import com.abin.mallchat.common.user.domain.entity.UserEmoji; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * 用户表情包 Mapper + * + * @author: WuShiJie + * @createTime: 2023/7/3 14:24 + */ +public interface UserEmojisMapper extends BaseMapper { +} diff --git a/mallchat-common/src/main/resources/mapper/user/UserEmojiMapper.xml b/mallchat-common/src/main/resources/mapper/user/UserEmojiMapper.xml new file mode 100644 index 0000000..eea5b91 --- /dev/null +++ b/mallchat-common/src/main/resources/mapper/user/UserEmojiMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mallchat-custom-server/pom.xml b/mallchat-custom-server/pom.xml index 6436bcb..f47902c 100644 --- a/mallchat-custom-server/pom.xml +++ b/mallchat-custom-server/pom.xml @@ -16,6 +16,19 @@ com.abin.mallchat mallchat-common + + + junit + junit + ${junit.version} + test + + + org.springframework + spring-test + 5.3.19 + test + com.knuddels diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java index a74ed2b..10bbbd6 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/controller/ChatController.java @@ -84,7 +84,7 @@ public class ChatController { @GetMapping("/public/msg/page") @ApiOperation("消息列表") @FrequencyControl(time = 120, count = 20, target = FrequencyControl.Target.IP) - public ApiResult> getMsgPage1(@Valid ChatMessagePageReq request) { + public ApiResult> getMsgPage(@Valid ChatMessagePageReq request) { // black(request); CursorPageBaseResp msgPage = chatService.getMsgPage(request, RequestHolder.get().getUid()); filterBlackMsg(msgPage); @@ -94,7 +94,6 @@ public class ChatController { private void filterBlackMsg(CursorPageBaseResp memberPage) { Set blackMembers = getBlackUidSet(); memberPage.getList().removeIf(a -> blackMembers.contains(a.getFromUser().getUid().toString())); - System.out.println(1); } @PostMapping("/msg") diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java index 2fd2040..b255a34 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/request/ChatMessageReq.java @@ -1,6 +1,5 @@ package com.abin.mallchat.custom.chat.domain.vo.request; -import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; @@ -30,7 +29,7 @@ public class ChatMessageReq { @ApiModelProperty("消息类型") @NotNull - private Integer msgType = MessageTypeEnum.TEXT.getType(); + private Integer msgType; @ApiModelProperty("消息内容,类型不同传值不同,见https://www.yuque.com/snab/mallcaht/rkb2uz5k1qqdmcmd") @NotNull diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java index 968aae9..096eaf7 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/ChatMessageResp.java @@ -7,7 +7,6 @@ import lombok.Data; import lombok.NoArgsConstructor; import java.util.Date; -import java.util.Map; /** * Description: 消息 @@ -27,16 +26,8 @@ public class ChatMessageResp { @Data public static class UserInfo { - @ApiModelProperty("用户名称") - private String username; @ApiModelProperty("用户id") private Long uid; - @ApiModelProperty("头像") - private String avatar; - @ApiModelProperty("归属地") - private String locPlace; - @ApiModelProperty("徽章标识,如果没有展示null") - private Badge badge; } @Data @@ -45,36 +36,12 @@ public class ChatMessageResp { private Long id; @ApiModelProperty("消息发送时间") private Date sendTime; - @ApiModelProperty("消息内容-废弃") - @Deprecated - private String content; - @ApiModelProperty("消息链接映射-废弃") - @Deprecated - private Map urlTitleMap; @ApiModelProperty("消息类型 1正常文本 2.撤回消息") private Integer type; @ApiModelProperty("消息内容不同的消息类型,内容体不同,见https://www.yuque.com/snab/mallcaht/rkb2uz5k1qqdmcmd") private Object body; @ApiModelProperty("消息标记") private MessageMark messageMark; - @ApiModelProperty("父消息,如果没有父消息,返回的是null") - private ReplyMsg reply; - - } - - @Data - @Deprecated - public static class ReplyMsg { - @ApiModelProperty("消息id") - private Long id; - @ApiModelProperty("用户名称") - private String username; - @ApiModelProperty("消息内容") - private String content; - @ApiModelProperty("是否可消息跳转 0否 1是") - private Integer canCallback; - @ApiModelProperty("跳转间隔的消息条数") - private Integer gapCount; } @Data @@ -88,12 +55,4 @@ public class ChatMessageResp { @ApiModelProperty("该用户是否已经举报 0否 1是") private Integer userDislike; } - - @Data - public static class Badge { - @ApiModelProperty("徽章图像") - private String img; - @ApiModelProperty("徽章说明") - private String describe; - } } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/msg/TextMsgResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/msg/TextMsgResp.java index 34db6c5..17a7b48 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/msg/TextMsgResp.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/domain/vo/response/msg/TextMsgResp.java @@ -1,5 +1,6 @@ package com.abin.mallchat.custom.chat.domain.vo.response.msg; +import com.abin.mallchat.common.common.utils.discover.domain.UrlInfo; import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; @@ -22,7 +23,7 @@ public class TextMsgResp { @ApiModelProperty("消息内容") private String content; @ApiModelProperty("消息链接映射") - private Map urlTitleMap; + private Map urlContentMap; @ApiModelProperty("艾特的uid") private List atUidList; @ApiModelProperty("父消息,如果没有父消息,返回的是null") diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/WeChatMsgOperationService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/WeChatMsgOperationService.java new file mode 100644 index 0000000..1e08b4a --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/WeChatMsgOperationService.java @@ -0,0 +1,14 @@ +package com.abin.mallchat.custom.chat.service; + +import java.util.List; + +public interface WeChatMsgOperationService { + /** + * 向被at的用户微信推送群聊消息 + * + * @param senderUid senderUid + * @param receiverUidList receiverUidList + * @param msg msg + */ + void publishChatMsgToWeChatUser(long senderUid, List receiverUidList, String msg); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java index 53e10fa..38f0a3c 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/adapter/MessageAdapter.java @@ -3,14 +3,9 @@ package com.abin.mallchat.custom.chat.service.adapter; import cn.hutool.core.bean.BeanUtil; import com.abin.mallchat.common.chat.domain.entity.Message; import com.abin.mallchat.common.chat.domain.entity.MessageMark; -import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra; import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum; import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum; import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; -import com.abin.mallchat.common.user.domain.entity.IpDetail; -import com.abin.mallchat.common.user.domain.entity.IpInfo; -import com.abin.mallchat.common.user.domain.entity.ItemConfig; -import com.abin.mallchat.common.user.domain.entity.User; import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; import com.abin.mallchat.custom.chat.service.strategy.msg.AbstractMsgHandler; @@ -38,37 +33,25 @@ public class MessageAdapter { } - public static List buildMsgResp(List messages, Map replyMap, Map userMap, List msgMark, Long receiveUid, Map itemMap) { + public static List buildMsgResp(List messages, Map replyMap, List msgMark, Long receiveUid) { Map> markMap = msgMark.stream().collect(Collectors.groupingBy(MessageMark::getMsgId)); return messages.stream().map(a -> { ChatMessageResp resp = new ChatMessageResp(); - resp.setFromUser(buildFromUser(userMap.get(a.getFromUid()), itemMap)); - resp.setMessage(buildMessage(a, replyMap, userMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid)); + resp.setFromUser(buildFromUser(a.getFromUid())); + resp.setMessage(buildMessage(a, replyMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid)); return resp; }) .sorted(Comparator.comparing(a -> a.getMessage().getSendTime()))//帮前端排好序,更方便它展示 .collect(Collectors.toList()); } - private static ChatMessageResp.Message buildMessage(Message message, Map replyMap, Map userMap, List marks, Long receiveUid) { + private static ChatMessageResp.Message buildMessage(Message message, Map replyMap, List marks, Long receiveUid) { ChatMessageResp.Message messageVO = new ChatMessageResp.Message(); BeanUtil.copyProperties(message, messageVO); messageVO.setSendTime(message.getCreateTime()); AbstractMsgHandler msgHandler = MsgHandlerFactory.getStrategyNoNull(message.getType()); - messageVO.setBody(msgHandler.showMsg(message)); - messageVO.setUrlTitleMap(Optional.ofNullable(message.getExtra()).map(MessageExtra::getUrlTitleMap).orElse(null)); - Message replyMessage = replyMap.get(message.getReplyMsgId()); - - //回复消息 - if (Objects.nonNull(replyMessage)) { - ChatMessageResp.ReplyMsg replyMsgVO = new ChatMessageResp.ReplyMsg(); - replyMsgVO.setId(replyMessage.getId()); - replyMsgVO.setContent(replyMessage.getContent()); - User replyUser = userMap.get(replyMessage.getFromUid()); - replyMsgVO.setUsername(replyUser.getName()); - replyMsgVO.setCanCallback(YesOrNoEnum.toStatus(Objects.nonNull(message.getGapCount()) && message.getGapCount() <= CAN_CALLBACK_GAP_COUNT)); - replyMsgVO.setGapCount(message.getGapCount()); - messageVO.setReply(replyMsgVO); + if (Objects.nonNull(msgHandler)) { + messageVO.setBody(msgHandler.showMsg(message)); } //消息标记 messageVO.setMessageMark(buildMsgMark(marks, receiveUid)); @@ -87,19 +70,9 @@ public class MessageAdapter { return mark; } - private static ChatMessageResp.UserInfo buildFromUser(User fromUser, Map itemMap) { + private static ChatMessageResp.UserInfo buildFromUser(Long fromUid) { ChatMessageResp.UserInfo userInfo = new ChatMessageResp.UserInfo(); - userInfo.setUsername(fromUser.getName()); - userInfo.setAvatar(fromUser.getAvatar()); - userInfo.setLocPlace(Optional.ofNullable(fromUser.getIpInfo()).map(IpInfo::getUpdateIpDetail).map(IpDetail::getCity).orElse(null)); - userInfo.setUid(fromUser.getId()); - if (Objects.nonNull(fromUser.getItemId())) { - ChatMessageResp.Badge badge = new ChatMessageResp.Badge(); - ItemConfig itemConfig = itemMap.get(fromUser.getItemId()); - badge.setImg(itemConfig.getImg()); - badge.setDescribe(itemConfig.getDescribe()); - userInfo.setBadge(badge); - } + userInfo.setUid(fromUid); return userInfo; } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java index adbb1d3..03ab62f 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/ChatServiceImpl.java @@ -19,8 +19,6 @@ import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp; import com.abin.mallchat.common.common.event.MessageSendEvent; import com.abin.mallchat.common.common.utils.AssertUtil; import com.abin.mallchat.common.user.dao.UserDao; -import com.abin.mallchat.common.user.domain.entity.ItemConfig; -import com.abin.mallchat.common.user.domain.entity.User; import com.abin.mallchat.common.user.domain.enums.ChatActiveStatusEnum; import com.abin.mallchat.common.user.domain.enums.RoleEnum; import com.abin.mallchat.common.user.service.IRoleService; @@ -49,7 +47,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * Description: 消息处理类 @@ -226,21 +223,14 @@ public class ChatServiceImpl implements ChatService { return new ArrayList<>(); } Map replyMap = new HashMap<>(); - Map userMap; - Map itemMap; //批量查出回复的消息 List replyIds = messages.stream().map(Message::getReplyMsgId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); if (CollectionUtil.isNotEmpty(replyIds)) { replyMap = messageDao.listByIds(replyIds).stream().collect(Collectors.toMap(Message::getId, Function.identity())); } - //批量查询消息关联用户 - Set uidSet = Stream.concat(replyMap.values().stream().map(Message::getFromUid), messages.stream().map(Message::getFromUid)).collect(Collectors.toSet()); - userMap = userCache.getUserInfoBatch(uidSet); - //批量查询item信息 - itemMap = userMap.values().stream().map(User::getItemId).distinct().filter(Objects::nonNull).map(itemCache::getById).collect(Collectors.toMap(ItemConfig::getId, Function.identity())); //查询消息标志 List msgMark = messageMarkDao.getValidMarkByMsgIdBatch(messages.stream().map(Message::getId).collect(Collectors.toList())); - return MessageAdapter.buildMsgResp(messages, replyMap, userMap, msgMark, receiveUid, itemMap); + return MessageAdapter.buildMsgResp(messages, replyMap, msgMark, receiveUid); } } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/WeChatMsgOperationServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/WeChatMsgOperationServiceImpl.java new file mode 100644 index 0000000..429068c --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/impl/WeChatMsgOperationServiceImpl.java @@ -0,0 +1,115 @@ +package com.abin.mallchat.custom.chat.service.impl; + +import cn.hutool.core.thread.NamedThreadFactory; +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.exception.FrequencyControlException; +import com.abin.mallchat.common.common.handler.GlobalUncaughtExceptionHandler; +import com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlUtil; +import com.abin.mallchat.common.user.domain.entity.User; +import com.abin.mallchat.common.user.service.cache.UserCache; +import com.abin.mallchat.custom.chat.service.WeChatMsgOperationService; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateData; +import me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlStrategyFactory.TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; + +@Slf4j +@Component +public class WeChatMsgOperationServiceImpl implements WeChatMsgOperationService { + + private static final ExecutorService executor = new ThreadPoolExecutor(1, 10, 3000L, + TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(20), + new NamedThreadFactory("wechat-operation-thread", null, false, + new GlobalUncaughtExceptionHandler())); + + // at消息的微信推送模板id + private final String atMsgPublishTemplateId = "Xd7sWPZsuWa0UmpvLaZPvaJVjNj1KjEa0zLOm5_Z7IU"; + + private final String WE_CHAT_MSG_COLOR = "#A349A4"; + + @Autowired + private UserCache userCache; + + @Autowired + WxMpService wxMpService; + + @Override + public void publishChatMsgToWeChatUser(long senderUid, List receiverUidList, String msg) { + User sender = userCache.getUserInfo(senderUid); + Set uidSet = new HashSet(); + uidSet.addAll(receiverUidList); + Map userMap = userCache.getUserInfoBatch(uidSet); + userMap.values().forEach(user -> { + if (Objects.nonNull(user.getOpenId())) { + executor.execute(() -> { + WxMpTemplateMessage msgTemplate = getAtMsgTemplate(sender, user.getOpenId(), msg); + publishTemplateMsgCheckLimit(msgTemplate); + }); + } + }); + } + + private void publishTemplateMsgCheckLimit(WxMpTemplateMessage msgTemplate) { + try { + FrequencyControlDTO frequencyControlDTO = new FrequencyControlDTO(); + frequencyControlDTO.setKey("TemplateMsg:" + msgTemplate.getToUser()); + frequencyControlDTO.setUnit(TimeUnit.HOURS); + frequencyControlDTO.setCount(1); + frequencyControlDTO.setTime(1); + FrequencyControlUtil.executeWithFrequencyControl(TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER, frequencyControlDTO, + () -> publishTemplateMsg(msgTemplate)); + } catch (FrequencyControlException e) { + log.info("wx push limit openid:{}", msgTemplate.getToUser()); + } catch (Throwable e) { + log.error("wx push error openid:{}", msgTemplate.getToUser()); + } + } + + /* + * 构造微信模板消息 + */ + private WxMpTemplateMessage getAtMsgTemplate(User sender, String openId, String msg) { + return WxMpTemplateMessage.builder() + .toUser(openId) + .templateId(atMsgPublishTemplateId) + .data(generateAtMsgData(sender, msg)) + .build(); + } + + /* + * 构造微信消息模板的数据 + */ + private List generateAtMsgData(User sender, String msg) { + List dataList = new ArrayList(); +// todo: 没有消息模板,暂不实现 + dataList.add(new WxMpTemplateData("name", sender.getName(), WE_CHAT_MSG_COLOR)); + dataList.add(new WxMpTemplateData("content", msg, WE_CHAT_MSG_COLOR)); + return dataList; + } + + /** + * 推送微信模板消息 + * + * @param templateMsg 微信模板消息 + */ + protected void publishTemplateMsg(WxMpTemplateMessage templateMsg) { +// WxMpTemplateMsgService wxMpTemplateMsgService = wxMpService.getTemplateMsgService();todo 等审核通过 +// try { +// wxMpTemplateMsgService.sendTemplateMsg(templateMsg); +// } catch (WxErrorException e) { +// log.error("publish we chat msg failed! open id is {}, msg is {}.", +// templateMsg.getToUser(), JsonUtils.toStr(templateMsg.getData())); +// } + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/EmojisMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/EmojisMsgHandler.java new file mode 100644 index 0000000..d7f93a5 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/EmojisMsgHandler.java @@ -0,0 +1,57 @@ +package com.abin.mallchat.custom.chat.service.strategy.msg; + +import cn.hutool.core.bean.BeanUtil; +import com.abin.mallchat.common.chat.dao.MessageDao; +import com.abin.mallchat.common.chat.domain.entity.Message; +import com.abin.mallchat.common.chat.domain.entity.msg.EmojisMsgDTO; +import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra; +import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum; +import com.abin.mallchat.common.common.utils.AssertUtil; +import com.abin.mallchat.custom.chat.domain.vo.request.ChatMessageReq; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +/** + * Description:表情消息 + * Author: abin + * Date: 2023-06-04 + */ +@Component +public class EmojisMsgHandler extends AbstractMsgHandler { + @Autowired + private MessageDao messageDao; + + @Override + MessageTypeEnum getMsgTypeEnum() { + return MessageTypeEnum.EMOJI; + } + + @Override + public void checkMsg(ChatMessageReq request, Long uid) { + EmojisMsgDTO body = BeanUtil.toBean(request.getBody(), EmojisMsgDTO.class); + AssertUtil.allCheckValidateThrow(body); + } + + @Override + public void saveMsg(Message msg, ChatMessageReq request) { + EmojisMsgDTO body = BeanUtil.toBean(request.getBody(), EmojisMsgDTO.class); + MessageExtra extra = Optional.ofNullable(msg.getExtra()).orElse(new MessageExtra()); + Message update = new Message(); + update.setId(msg.getId()); + update.setExtra(extra); + extra.setEmojisMsgDTO(body); + messageDao.updateById(update); + } + + @Override + public Object showMsg(Message msg) { + return msg.getExtra().getEmojisMsgDTO(); + } + + @Override + public Object showReplyMsg(Message msg) { + return "表情"; + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java index d8e347a..5ec867a 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chat/service/strategy/msg/TextMsgHandler.java @@ -10,8 +10,9 @@ import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum; import com.abin.mallchat.common.chat.service.cache.MsgCache; import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum; import com.abin.mallchat.common.common.utils.AssertUtil; -import com.abin.mallchat.common.common.utils.SensitiveWordUtils; -import com.abin.mallchat.common.common.utils.discover.PrioritizedUrlTitleDiscover; +import com.abin.mallchat.common.common.utils.discover.PrioritizedUrlDiscover; +import com.abin.mallchat.common.common.utils.discover.domain.UrlInfo; +import com.abin.mallchat.common.common.utils.sensitiveWord.SensitiveWordBs; import com.abin.mallchat.common.user.domain.entity.User; import com.abin.mallchat.common.user.domain.enums.RoleEnum; import com.abin.mallchat.common.user.service.IRoleService; @@ -46,8 +47,10 @@ public class TextMsgHandler extends AbstractMsgHandler { private UserInfoCache userInfoCache; @Autowired private IRoleService iRoleService; - - private static final PrioritizedUrlTitleDiscover URL_TITLE_DISCOVER = new PrioritizedUrlTitleDiscover(); + @Autowired + private SensitiveWordBs sensitiveWordBs; + + private static final PrioritizedUrlDiscover URL_TITLE_DISCOVER = new PrioritizedUrlDiscover(); @Override MessageTypeEnum getMsgTypeEnum() { @@ -81,7 +84,7 @@ public class TextMsgHandler extends AbstractMsgHandler { MessageExtra extra = Optional.ofNullable(msg.getExtra()).orElse(new MessageExtra()); Message update = new Message(); update.setId(msg.getId()); - update.setContent(SensitiveWordUtils.filter(body.getContent())); + update.setContent(sensitiveWordBs.filter(body.getContent())); update.setExtra(extra); //如果有回复消息 if (Objects.nonNull(body.getReplyMsgId())) { @@ -91,8 +94,8 @@ public class TextMsgHandler extends AbstractMsgHandler { } //判断消息url跳转 - Map urlTitleMap = URL_TITLE_DISCOVER.getContentTitleMap(body.getContent()); - extra.setUrlTitleMap(urlTitleMap); + Map urlContentMap = URL_TITLE_DISCOVER.getUrlContentMap(body.getContent()); + extra.setUrlContentMap(urlContentMap); //艾特功能 if (CollectionUtil.isNotEmpty(body.getAtUidList())) { extra.setAtUidList(body.getAtUidList()); @@ -106,7 +109,7 @@ public class TextMsgHandler extends AbstractMsgHandler { public Object showMsg(Message msg) { TextMsgResp resp = new TextMsgResp(); resp.setContent(msg.getContent()); - resp.setUrlTitleMap(Optional.ofNullable(msg.getExtra()).map(MessageExtra::getUrlTitleMap).orElse(null)); + resp.setUrlContentMap(Optional.ofNullable(msg.getExtra()).map(MessageExtra::getUrlContentMap).orElse(null)); resp.setAtUidList(Optional.ofNullable(msg.getExtra()).map(MessageExtra::getAtUidList).orElse(null)); //回复消息 Optional reply = Optional.ofNullable(msg.getReplyMsgId()) diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/dto/GPTRequestDTO.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/dto/GPTRequestDTO.java new file mode 100644 index 0000000..cb72432 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/dto/GPTRequestDTO.java @@ -0,0 +1,21 @@ +package com.abin.mallchat.custom.chatai.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class GPTRequestDTO { + /** + * 聊天内容 + */ + private String content; + /** + * 用户Id + */ + private Long uid; +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java index 0f7ff1b..83f665a 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/ChatGLM2Handler.java @@ -4,12 +4,17 @@ import cn.hutool.http.HttpResponse; import com.abin.mallchat.common.chat.domain.entity.Message; import com.abin.mallchat.common.chat.domain.entity.msg.MessageExtra; import com.abin.mallchat.common.common.constant.RedisKey; +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.exception.FrequencyControlException; +import com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlUtil; import com.abin.mallchat.common.common.utils.RedisUtils; +import com.abin.mallchat.custom.chatai.dto.GPTRequestDTO; import com.abin.mallchat.custom.chatai.properties.ChatGLM2Properties; import com.abin.mallchat.custom.chatai.utils.ChatGLM2Utils; import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; @@ -21,10 +26,15 @@ import java.util.Random; import java.util.concurrent.TimeUnit; import static com.abin.mallchat.common.common.constant.RedisKey.USER_GLM2_TIME_LAST; +import static com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlStrategyFactory.TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; @Slf4j @Component public class ChatGLM2Handler extends AbstractChatAIHandler { + /** + * ChatGLM2Handler限流前缀 + */ + private static final String CHAT_GLM2_FREQUENCY_PREFIX = "ChatGLM2Handler"; private static final List ERROR_MSG = Arrays.asList( "还摸鱼呢?你不下班我还要下班呢。。。。", @@ -74,27 +84,42 @@ public class ChatGLM2Handler extends AbstractChatAIHandler { protected String doChat(Message message) { String content = message.getContent().replace("@" + AI_NAME, "").trim(); Long uid = message.getFromUid(); - Long minute; + try { + FrequencyControlDTO frequencyControlDTO = new FrequencyControlDTO(); + frequencyControlDTO.setKey(CHAT_GLM2_FREQUENCY_PREFIX + ":" + uid); + frequencyControlDTO.setUnit(TimeUnit.MINUTES); + frequencyControlDTO.setCount(1); + frequencyControlDTO.setTime(glm2Properties.getMinute().intValue()); + return FrequencyControlUtil.executeWithFrequencyControl(TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER, frequencyControlDTO, () -> sendRequestToGPT(new GPTRequestDTO(content, uid))); + } catch (FrequencyControlException e) { + return "你太快了亲爱的~过一会再来找人家~"; + } catch (Throwable e) { + return "系统开小差啦~~"; + } + } + + /** + * TODO + * + * @param gptRequestDTO + * @return + */ + @Nullable + private String sendRequestToGPT(GPTRequestDTO gptRequestDTO) { + String content = gptRequestDTO.getContent(); String text; - if ((minute = userMinutesLater(uid)) > 0) { - text = "你太快了 " + minute + "分钟后重试"; - } else { - HttpResponse response = null; - try { - response = ChatGLM2Utils - .create() - .url(glm2Properties.getUrl()) - .prompt(content) - .timeout(glm2Properties.getTimeout()) - .send(); - text = ChatGLM2Utils.parseText(response); - } catch (Exception e) { - log.warn("glm2 doChat warn:", e); - return getErrorText(); - } - if (StringUtils.isNotBlank(text)) { - RedisUtils.set(RedisKey.getKey(USER_GLM2_TIME_LAST, uid), new Date(), glm2Properties.getMinute(), TimeUnit.MINUTES); - } + HttpResponse response = null; + try { + response = ChatGLM2Utils + .create() + .url(glm2Properties.getUrl()) + .prompt(content) + .timeout(glm2Properties.getTimeout()) + .send(); + text = ChatGLM2Utils.parseText(response); + } catch (Exception e) { + log.warn("glm2 doChat warn:", e); + return getErrorText(); } return text; } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java index 1fd85ea..d4b5b55 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/chatai/handler/GPTChatAIHandler.java @@ -9,6 +9,10 @@ import com.abin.mallchat.custom.chatai.domain.ChatGPTContext; import com.abin.mallchat.custom.chatai.domain.ChatGPTMsg; import com.abin.mallchat.custom.chatai.domain.builder.ChatGPTContextBuilder; import com.abin.mallchat.custom.chatai.domain.builder.ChatGPTMsgBuilder; +import com.abin.mallchat.common.common.domain.dto.FrequencyControlDTO; +import com.abin.mallchat.common.common.exception.FrequencyControlException; +import com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlUtil; +import com.abin.mallchat.custom.chatai.dto.GPTRequestDTO; import com.abin.mallchat.custom.chatai.properties.ChatGPTProperties; import com.abin.mallchat.custom.chatai.utils.ChatGPTUtils; import com.abin.mallchat.custom.user.domain.vo.response.user.UserInfoResp; @@ -24,9 +28,15 @@ import java.util.concurrent.TimeUnit; import static com.abin.mallchat.common.common.constant.RedisKey.USER_CHAT_CONTEXT; +import static com.abin.mallchat.common.common.service.frequencycontrol.FrequencyControlStrategyFactory.TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER; + @Slf4j @Component public class GPTChatAIHandler extends AbstractChatAIHandler { + /** + * GPTChatAIHandler限流前缀 + */ + private static final String CHAT_FREQUENCY_PREFIX = "GPTChatAIHandler"; @Autowired private ChatGPTProperties chatGPTProperties; @@ -36,16 +46,18 @@ public class GPTChatAIHandler extends AbstractChatAIHandler { @Override protected void init() { super.init(); - UserInfoResp userInfo = userService.getUserInfo(chatGPTProperties.getAIUserId()); - if (userInfo == null) { - log.error("根据AIUserId:{} 找不到用户信息", chatGPTProperties.getAIUserId()); - throw new RuntimeException("根据AIUserId: " + chatGPTProperties.getAIUserId() + " 找不到用户信息"); + if (isUse()) { + UserInfoResp userInfo = userService.getUserInfo(chatGPTProperties.getAIUserId()); + if (userInfo == null) { + log.error("根据AIUserId:{} 找不到用户信息", chatGPTProperties.getAIUserId()); + throw new RuntimeException("根据AIUserId: " + chatGPTProperties.getAIUserId() + " 找不到用户信息"); + } + if (StringUtils.isBlank(userInfo.getName())) { + log.warn("根据AIUserId:{} 找到的用户信息没有name", chatGPTProperties.getAIUserId()); + throw new RuntimeException("根据AIUserId: " + chatGPTProperties.getAIUserId() + " 找到的用户没有名字"); + } + AI_NAME = userInfo.getName(); } - if (StringUtils.isBlank(userInfo.getName())) { - log.warn("根据AIUserId:{} 找到的用户信息没有name", chatGPTProperties.getAIUserId()); - throw new RuntimeException("根据AIUserId: " + chatGPTProperties.getAIUserId() + " 找到的用户没有名字"); - } - AI_NAME = userInfo.getName(); } @Override @@ -58,37 +70,41 @@ public class GPTChatAIHandler extends AbstractChatAIHandler { return chatGPTProperties.getAIUserId(); } - @Override protected String doChat(Message message) { - String prompt = message.getContent().replace("@" + AI_NAME, "").trim(); + String content = message.getContent().replace("@" + AI_NAME, "").trim(); Long uid = message.getFromUid(); + try { + FrequencyControlDTO frequencyControlDTO = new FrequencyControlDTO(); + frequencyControlDTO.setKey(CHAT_FREQUENCY_PREFIX + ":" + uid); + frequencyControlDTO.setUnit(TimeUnit.HOURS); + frequencyControlDTO.setCount(chatGPTProperties.getLimit()); + frequencyControlDTO.setTime(24); + return FrequencyControlUtil.executeWithFrequencyControl(TOTAL_COUNT_WITH_IN_FIX_TIME_FREQUENCY_CONTROLLER, frequencyControlDTO, () -> sendRequestToGPT(new GPTRequestDTO(content, uid))); + } catch (FrequencyControlException e) { + return "亲爱的,你今天找我聊了" + chatGPTProperties.getLimit() + "次了~人家累了~明天见"; + } catch (Throwable e) { + return "系统开小差啦~~"; + } + } + + private String sendRequestToGPT(GPTRequestDTO gptRequestDTO) { + String content = gptRequestDTO.getContent(); Long roomId = message.getRoomId(); Long chatNum; String text; - if ((chatNum = getUserChatNum(uid)) > chatGPTProperties.getLimit()) { - text = "你今天已经和我聊了" + chatNum + "次了,我累了,明天再聊吧"; - } else { - try { - ChatGPTContext context = buildContext(message, prompt);// 构建上下文 - context = tailorContext(context);// 裁剪上下文 - log.info("prompt = {}" , prompt); - Response response = ChatGPTUtils.create(chatGPTProperties.getKey()) - .proxyUrl(chatGPTProperties.getProxyUrl()) - .model(chatGPTProperties.getModelName()) - .timeout(chatGPTProperties.getTimeout()) - .maxTokens(chatGPTProperties.getMaxTokens()) - .message(context.getMsg()) - .send(); - text = ChatGPTUtils.parseText(response); - ChatGPTMsg chatGPTMsg = ChatGPTMsgBuilder.assistantMsg(text); - context.addMsg(chatGPTMsg); - RedisUtils.set(RedisKey.getKey(USER_CHAT_CONTEXT, uid, roomId), context, 1L, TimeUnit.HOURS); - userChatNumInrc(uid); - } catch (Exception e) { - log.warn("gpt doChat warn:", e); - text = "我累了,明天再聊吧"; - } + HttpResponse response = null; + try { + response = ChatGPTUtils.create(chatGPTProperties.getKey()) + .proxyUrl(chatGPTProperties.getProxyUrl()) + .model(chatGPTProperties.getModelName()) + .timeout(chatGPTProperties.getTimeout()) + .prompt(content) + .send(); + text = ChatGPTUtils.parseText(response); + } catch (Exception e) { + log.warn("gpt doChat warn:", e); + text = "我累了,明天再聊吧"; } return text; } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java index ba8bcf0..e87bc0a 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/event/listener/MessageSendListener.java @@ -5,6 +5,8 @@ import com.abin.mallchat.common.chat.domain.entity.Message; import com.abin.mallchat.common.common.event.MessageSendEvent; import com.abin.mallchat.custom.chat.domain.vo.response.ChatMessageResp; import com.abin.mallchat.custom.chat.service.ChatService; +import com.abin.mallchat.custom.chat.service.WeChatMsgOperationService; +import com.abin.mallchat.custom.chat.service.impl.WeChatMsgOperationServiceImpl; import com.abin.mallchat.custom.chatai.service.IChatAIService; import com.abin.mallchat.custom.user.service.WebSocketService; import com.abin.mallchat.custom.user.service.adapter.WSAdapter; @@ -15,6 +17,8 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; +import java.util.Objects; + /** * 消息发送监听器 * @@ -32,6 +36,9 @@ public class MessageSendListener { @Autowired private IChatAIService openAIService; + @Autowired + WeChatMsgOperationService weChatMsgOperationService; + @Async @TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true) public void notifyAllOnline(MessageSendEvent event) { @@ -46,4 +53,12 @@ public class MessageSendListener { openAIService.chat(message); } + @TransactionalEventListener(classes = MessageSendEvent.class, fallbackExecution = true) + public void publishChatToWechat(@NotNull MessageSendEvent event) { + Message message = messageDao.getById(event.getMsgId()); + if (Objects.nonNull(message.getExtra().getAtUidList())) { + weChatMsgOperationService.publishChatMsgToWeChatUser(message.getFromUid(), message.getExtra().getAtUidList(), + message.getContent()); + } + } } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java index 719fb0d..814297b 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/common/intecepter/TokenInterceptor.java @@ -45,6 +45,11 @@ public class TokenInterceptor implements HandlerInterceptor { return true; } + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + MDC.remove(MDCKey.UID); + } + /** * 判断是不是公共方法,可以未登录访问的 * diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserEmojiController.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserEmojiController.java new file mode 100644 index 0000000..9692359 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/controller/UserEmojiController.java @@ -0,0 +1,77 @@ +package com.abin.mallchat.custom.user.controller; + +import com.abin.mallchat.common.common.domain.vo.request.IdReqVO; +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.common.domain.vo.response.IdRespVO; +import com.abin.mallchat.common.common.utils.RequestHolder; +import com.abin.mallchat.custom.user.domain.vo.request.user.UserEmojiReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserEmojiResp; +import com.abin.mallchat.custom.user.service.UserEmojiService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.validation.Valid; +import java.util.List; + +/** + * 用户表情包 + * + * @author: WuShiJie + * @createTime: 2023/7/3 14:21 + */ +@RestController +@RequestMapping("/capi/user/emoji") +@Api(tags = "用户表情包管理相关接口") +public class UserEmojiController { + + /** + * 用户表情包 Service + */ + @Resource + private UserEmojiService emojiService; + + + /** + * 表情包列表 + * + * @return 表情包列表 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + @GetMapping("/list") + @ApiOperation("表情包列表") + public ApiResult> getEmojisPage() { + return ApiResult.success(emojiService.list(RequestHolder.get().getUid())); + } + + + /** + * 新增表情包 + * + * @param req 用户表情包 + * @return 表情包 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + @PostMapping() + @ApiOperation("新增表情包") + public ApiResult insertEmojis(@Valid @RequestBody UserEmojiReq req) { + return emojiService.insert(req, RequestHolder.get().getUid()); + } + + /** + * 删除表情包 + * + * @return 删除结果 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + @DeleteMapping() + @ApiOperation("删除表情包") + public ApiResult deleteEmojis(@Valid @RequestBody IdReqVO reqVO) { + emojiService.remove(reqVO.getId(), RequestHolder.get().getUid()); + return ApiResult.success(); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/OssSceneEnum.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/OssSceneEnum.java index ce7c7ac..67a5214 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/OssSceneEnum.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/enums/OssSceneEnum.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; @Getter public enum OssSceneEnum { CHAT(1, "聊天", "/chat"), + EMOJI(2, "表情包", "/emoji"), ; private final Integer type; diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/oss/UploadUrlReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/oss/UploadUrlReq.java index d0b0028..df1f473 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/oss/UploadUrlReq.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/oss/UploadUrlReq.java @@ -23,7 +23,7 @@ public class UploadUrlReq { @ApiModelProperty(value = "文件名(带后缀)") @NotBlank private String fileName; - @ApiModelProperty(value = "上传场景1.聊天室") + @ApiModelProperty(value = "上传场景1.聊天室,2.表情包") @NotNull private Integer scene; } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/EmojisPageReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/EmojisPageReq.java new file mode 100644 index 0000000..9dfe00f --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/EmojisPageReq.java @@ -0,0 +1,14 @@ +package com.abin.mallchat.custom.user.domain.vo.request.user; + +import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq; +import lombok.Data; + +/** + * 描述此类的作用 + * + * @author: WuShiJie + * @createTime: 2023/7/3 14:52 + */ +@Data +public class EmojisPageReq extends CursorPageBaseReq { +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/UserEmojiReq.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/UserEmojiReq.java new file mode 100644 index 0000000..5a31343 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/request/user/UserEmojiReq.java @@ -0,0 +1,26 @@ +package com.abin.mallchat.custom.user.domain.vo.request.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Description: 表情包反参 + * Author: abin + * Date: 2023-07-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserEmojiReq { + /** + * 表情地址 + */ + @ApiModelProperty(value = "新增的表情url") + private String expressionUrl; + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserEmojiResp.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserEmojiResp.java new file mode 100644 index 0000000..696393d --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/domain/vo/response/user/UserEmojiResp.java @@ -0,0 +1,32 @@ +package com.abin.mallchat.custom.user.domain.vo.response.user; + +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + + +/** + * Description: 表情包反参 + * Author: abin + * Date: 2023-07-09 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserEmojiResp { + /** + * id + */ + @ApiModelProperty(value = "id") + private Long id; + + /** + * 表情地址 + */ + @ApiModelProperty(value = "表情url") + private String expressionUrl; + +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java index ed28384..3f57542 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/LoginService.java @@ -38,4 +38,5 @@ public interface LoginService { * @return */ Long getValidUid(String token); + } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserEmojiService.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserEmojiService.java new file mode 100644 index 0000000..302a58b --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/UserEmojiService.java @@ -0,0 +1,45 @@ +package com.abin.mallchat.custom.user.service; + +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.common.domain.vo.response.IdRespVO; +import com.abin.mallchat.custom.user.domain.vo.request.user.UserEmojiReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserEmojiResp; + +import java.util.List; + +/** + * 用户表情包 Service + * + * @author: WuShiJie + * @createTime: 2023/7/3 14:22 + */ +public interface UserEmojiService { + + /** + * 表情包列表 + * + * @return 表情包列表 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + List list(Long uid); + + /** + * 新增表情包 + * + * @param emojis 用户表情包 + * @param uid 用户ID + * @return 表情包 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + ApiResult insert(UserEmojiReq emojis, Long uid); + + /** + * 删除表情包 + * + * @param id + * @param uid + */ + void remove(Long id, Long uid); +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/OssServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/OssServiceImpl.java index 08bf513..fa533e8 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/OssServiceImpl.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/OssServiceImpl.java @@ -17,7 +17,6 @@ import org.springframework.stereotype.Service; */ @Service public class OssServiceImpl implements OssService { - private static final String BUCKET_NAME = "mallchat"; @Autowired private MinIOTemplate minIOTemplate; diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserEmojiServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserEmojiServiceImpl.java new file mode 100644 index 0000000..3a0b882 --- /dev/null +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserEmojiServiceImpl.java @@ -0,0 +1,75 @@ +package com.abin.mallchat.custom.user.service.impl; + +import com.abin.mallchat.common.common.annotation.RedissonLock; +import com.abin.mallchat.common.common.domain.vo.response.ApiResult; +import com.abin.mallchat.common.common.domain.vo.response.IdRespVO; +import com.abin.mallchat.common.common.utils.AssertUtil; +import com.abin.mallchat.common.user.dao.UserEmojiDao; +import com.abin.mallchat.common.user.domain.entity.UserEmoji; +import com.abin.mallchat.custom.user.domain.vo.request.user.UserEmojiReq; +import com.abin.mallchat.custom.user.domain.vo.response.user.UserEmojiResp; +import com.abin.mallchat.custom.user.service.UserEmojiService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 用户表情包 ServiceImpl + * + * @author: WuShiJie + * @createTime: 2023/7/3 14:23 + */ +@Service +@Slf4j +public class UserEmojiServiceImpl implements UserEmojiService { + + @Autowired + private UserEmojiDao userEmojiDao; + + @Override + public List list(Long uid) { + return userEmojiDao.listByUid(uid). + stream() + .map(a -> UserEmojiResp.builder() + .id(a.getId()) + .expressionUrl(a.getExpressionUrl()) + .build()) + .collect(Collectors.toList()); + } + + /** + * 新增表情包 + * + * @param uid 用户ID + * @return 表情包 + * @author WuShiJie + * @createTime 2023/7/3 14:46 + **/ + @Override + @RedissonLock(key = "#uid") + public ApiResult insert(UserEmojiReq req, Long uid) { + //校验表情数量是否超过30 + int count = userEmojiDao.countByUid(uid); + AssertUtil.isFalse(count > 30, "最多只能添加30个表情哦~~"); + //校验表情是否存在 + Integer existsCount = userEmojiDao.lambdaQuery() + .eq(UserEmoji::getExpressionUrl, req.getExpressionUrl()) + .eq(UserEmoji::getUid, uid) + .count(); + AssertUtil.isFalse(existsCount > 0, "当前表情已存在哦~~"); + UserEmoji insert = UserEmoji.builder().uid(uid).expressionUrl(req.getExpressionUrl()).build(); + userEmojiDao.save(insert); + return ApiResult.success(IdRespVO.id(insert.getId())); + } + + @Override + public void remove(Long id, Long uid) { + UserEmoji userEmoji = userEmojiDao.getById(id); + AssertUtil.isNotEmpty(userEmoji, "表情不能为空"); + AssertUtil.equal(userEmoji.getUid(), uid, "小黑子,别人表情不是你能删的"); + userEmojiDao.removeById(id); + } +} diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java index dc63558..e8e5d62 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/UserServiceImpl.java @@ -4,7 +4,7 @@ import cn.hutool.core.util.StrUtil; import com.abin.mallchat.common.common.event.UserBlackEvent; import com.abin.mallchat.common.common.event.UserRegisterEvent; import com.abin.mallchat.common.common.utils.AssertUtil; -import com.abin.mallchat.common.common.utils.SensitiveWordUtils; +import com.abin.mallchat.common.common.utils.sensitiveWord.SensitiveWordBs; import com.abin.mallchat.common.user.dao.BlackDao; import com.abin.mallchat.common.user.dao.ItemConfigDao; import com.abin.mallchat.common.user.dao.UserBackpackDao; @@ -63,6 +63,8 @@ public class UserServiceImpl implements UserService { private BlackDao blackDao; @Autowired private UserSummaryCache userSummaryCache; + @Autowired + private SensitiveWordBs sensitiveWordBs; @Override public UserInfoResp getUserInfo(Long uid) { @@ -76,7 +78,7 @@ public class UserServiceImpl implements UserService { public void modifyName(Long uid, ModifyNameReq req) { //判断名字是不是重复 String newName = req.getName(); - AssertUtil.isFalse(SensitiveWordUtils.hasSensitiveWord(newName), "名字中包含敏感词,请重新输入"); // 判断名字中有没有敏感词 + AssertUtil.isFalse(sensitiveWordBs.hasSensitiveWord(newName), "名字中包含敏感词,请重新输入"); // 判断名字中有没有敏感词 User oldUser = userDao.getByName(newName); AssertUtil.isEmpty(oldUser, "名字已经被抢占了,请换一个哦~~"); //判断改名卡够不够 diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java index 7282473..04efa6d 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/service/impl/WebSocketServiceImpl.java @@ -2,7 +2,6 @@ package com.abin.mallchat.custom.user.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.json.JSONUtil; import com.abin.mallchat.common.common.annotation.FrequencyControl; import com.abin.mallchat.common.common.config.ThreadPoolConfig; @@ -20,6 +19,8 @@ import com.abin.mallchat.custom.user.service.LoginService; import com.abin.mallchat.custom.user.service.WebSocketService; import com.abin.mallchat.custom.user.service.adapter.WSAdapter; import com.abin.mallchat.custom.user.websocket.NettyUtil; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; import io.netty.channel.Channel; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import lombok.SneakyThrows; @@ -32,9 +33,13 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; + +import java.time.Duration; import java.util.*; + import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; @@ -47,12 +52,18 @@ import java.util.concurrent.locks.ReentrantLock; @Slf4j public class WebSocketServiceImpl implements WebSocketService { + private static final Duration EXPIRE_TIME = Duration.ofHours(1); + private static final Long MAX_MUM_SIZE = 10000L; + + private static final AtomicInteger CODE = new AtomicInteger(); /** * 所有请求登录的code与channel关系 - * todo 有可能有人请求了二维码,就是不登录,留个坑,之后处理 */ - private static final ConcurrentHashMap WAIT_LOGIN_MAP = new ConcurrentHashMap<>(); + private static final Cache WAIT_LOGIN_MAP = Caffeine.newBuilder() + .expireAfterWrite(EXPIRE_TIME) + .maximumSize(MAX_MUM_SIZE) + .build(); /** * 所有已连接的websocket连接列表和一些额外参数 */ @@ -66,7 +77,6 @@ public class WebSocketServiceImpl implements WebSocketService { return ONLINE_WS_MAP; } - public static final int EXPIRE_SECONDS = 60 * 60; @Autowired private WxMpService wxMpService; @Autowired @@ -95,7 +105,7 @@ public class WebSocketServiceImpl implements WebSocketService { //生成随机不重复的登录码 Integer code = generateLoginCode(channel); //请求微信接口,获取登录码地址 - WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, EXPIRE_SECONDS); + WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) EXPIRE_TIME.getSeconds()); //返回给前端 sendMsg(channel, WSAdapter.buildLoginResp(wxMpQrCodeTicket)); } @@ -107,12 +117,11 @@ public class WebSocketServiceImpl implements WebSocketService { * @return */ private Integer generateLoginCode(Channel channel) { - int code; do { - code = RandomUtil.randomInt(Integer.MAX_VALUE); - } while (WAIT_LOGIN_MAP.contains(code) - || Objects.nonNull(WAIT_LOGIN_MAP.putIfAbsent(code, channel))); - return code; + CODE.getAndIncrement(); + } while (WAIT_LOGIN_MAP.asMap().containsKey(CODE.get()) + || Objects.isNull(WAIT_LOGIN_MAP.get(CODE.get(), c -> channel))); + return CODE.get(); } /** @@ -199,12 +208,12 @@ public class WebSocketServiceImpl implements WebSocketService { @Override public Boolean scanLoginSuccess(Integer loginCode, User user, String token) { //发送消息 - Channel channel = WAIT_LOGIN_MAP.get(loginCode); + Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode); if (Objects.isNull(channel)) { return Boolean.FALSE; } //移除code - WAIT_LOGIN_MAP.remove(loginCode); + WAIT_LOGIN_MAP.invalidate(loginCode); //用户登录 loginSuccess(channel, user, token); return true; @@ -212,7 +221,7 @@ public class WebSocketServiceImpl implements WebSocketService { @Override public Boolean scanSuccess(Integer loginCode) { - Channel channel = WAIT_LOGIN_MAP.get(loginCode); + Channel channel = WAIT_LOGIN_MAP.getIfPresent(loginCode); if (Objects.isNull(channel)) { return Boolean.FALSE; } @@ -287,4 +296,6 @@ public class WebSocketServiceImpl implements WebSocketService { reentrantLock.unlock(); Thread.sleep(1000); } + + } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java index 30f6cdd..6752060 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/HttpHeadersHandler.java @@ -1,5 +1,6 @@ package com.abin.mallchat.custom.user.websocket; +import cn.hutool.core.net.url.UrlBuilder; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.FullHttpRequest; @@ -7,13 +8,23 @@ import io.netty.handler.codec.http.HttpHeaders; import org.apache.commons.lang3.StringUtils; import java.net.InetSocketAddress; +import java.util.Optional; public class HttpHeadersHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { - HttpHeaders headers = ((FullHttpRequest) msg).headers(); + FullHttpRequest request = (FullHttpRequest) msg; + UrlBuilder urlBuilder = UrlBuilder.ofHttp(request.uri()); + + // 获取token参数 + String token = Optional.ofNullable(urlBuilder.getQuery()).map(k->k.get("token")).map(CharSequence::toString).orElse(""); + NettyUtil.setAttr(ctx.channel(), NettyUtil.TOKEN, token); + + // 获取请求路径 + request.setUri(urlBuilder.getPath().toString()); + HttpHeaders headers = request.headers(); String ip = headers.get("X-Real-IP"); if (StringUtils.isEmpty(ip)) {//如果没经过nginx,就直接获取远端地址 InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress(); @@ -21,7 +32,10 @@ public class HttpHeadersHandler extends ChannelInboundHandlerAdapter { } NettyUtil.setAttr(ctx.channel(), NettyUtil.IP, ip); ctx.pipeline().remove(this); + ctx.fireChannelRead(request); + }else + { + ctx.fireChannelRead(msg); } - ctx.fireChannelRead(msg); } } \ No newline at end of file diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java index 79daafa..bcc5b05 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyUtil.java @@ -1,6 +1,7 @@ package com.abin.mallchat.custom.user.websocket; import io.netty.channel.Channel; +import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.util.Attribute; import io.netty.util.AttributeKey; @@ -15,6 +16,7 @@ public class NettyUtil { public static AttributeKey TOKEN = AttributeKey.valueOf("token"); public static AttributeKey IP = AttributeKey.valueOf("ip"); public static AttributeKey UID = AttributeKey.valueOf("uid"); + public static AttributeKey HANDSHAKER_ATTR_KEY = AttributeKey.valueOf(WebSocketServerHandshaker.class, "HANDSHAKER"); public static void setAttr(Channel channel, AttributeKey attributeKey, T data) { Attribute attr = channel.attr(attributeKey); diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java index b70b867..4ebf470 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServer.java @@ -10,6 +10,7 @@ import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.stream.ChunkedWriteHandler; @@ -88,7 +89,7 @@ public class NettyWebSocketServer { * 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接; * 是通过一个状态码 101 来切换的 */ - pipeline.addLast(new WebSocketHandshakeHandler()); + pipeline.addLast(new WebSocketServerProtocolHandler("/")); // 自定义handler ,处理业务逻辑 pipeline.addLast(new NettyWebSocketServerHandler()); } diff --git a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java index 3d75069..fe8a92a 100644 --- a/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java +++ b/mallchat-custom-server/src/main/java/com/abin/mallchat/custom/user/websocket/NettyWebSocketServerHandler.java @@ -19,10 +19,12 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler { + private WebSocketService webSocketService; + // 当web客户端连接后,触发该方法 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { -// getService().connect(ctx.channel()); + this.webSocketService = getService(); } // 客户端离线 @@ -45,7 +47,7 @@ public class NettyWebSocketServerHandler extends SimpleChannelInboundHandlerabin + * Date: 2023-07-06 + */ +@RunWith(SpringRunner.class) +@SpringBootTest +public class WXTemplate { + + @Autowired + private WeChatMsgOperationService chatMsgOperationService; + @Autowired + private ChatServiceImpl chatService; + + @Test + public void test() { + chatMsgOperationService.publishChatMsgToWeChatUser(1L, Collections.singletonList(10008L), "你家规下去"); + } +}