mirror of
https://github.com/zongzibinbin/MallChat.git
synced 2026-03-13 21:53:41 +08:00
feat:
1.链接跳转功能 2.归属地功能 fix: 1.修复回复跳转功能 2.优化翻页工具类 3,修复下线重置ip的bug
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -67,6 +67,7 @@ CodeGenerator.java
|
||||
TestController.java
|
||||
application-my-prod.properties
|
||||
application-remote-prod.properties
|
||||
application-my-test.properties
|
||||
mybatis-generator-config.xml
|
||||
ChannelBizRankClient.java
|
||||
ChannelBizBrandWallClient.java
|
||||
|
||||
@@ -54,6 +54,7 @@ CREATE TABLE `message` (
|
||||
`status` int(11) NOT NULL COMMENT '消息状态 0正常 1删除',
|
||||
`gap_count` int(11) NULL DEFAULT NULL COMMENT '与回复的消息间隔多少条',
|
||||
`type` int(11) NULL DEFAULT 1 COMMENT '消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)',
|
||||
`extra` json DEFAULT NULL COMMENT '扩展信息',
|
||||
`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,
|
||||
@@ -163,10 +164,10 @@ DROP TABLE IF EXISTS `black`;
|
||||
CREATE TABLE `black` (
|
||||
`id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id',
|
||||
`type` int(11) NOT NULL COMMENT '拉黑目标类型 1.ip 2uid',
|
||||
`target` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '拉黑目标',
|
||||
`target` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '拉黑目标',
|
||||
`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,
|
||||
UNIQUE INDEX `idx_type_target`(`type`, `target`) USING BTREE
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '黑名单' ROW_FORMAT = Dynamic;
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '黑名单' ROW_FORMAT = Dynamic;
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
@@ -24,6 +24,11 @@
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
</dependency>
|
||||
<!-- MyBatis-->
|
||||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
|
||||
@@ -55,6 +55,7 @@ public class MessageDao extends ServiceImpl<MessageMapper, Message> {
|
||||
public void updateGapCount(Long id, Integer gapCount) {
|
||||
lambdaUpdate()
|
||||
.eq(Message::getId, id)
|
||||
.set(Message::getGapCount, gapCount);
|
||||
.set(Message::getGapCount, gapCount)
|
||||
.update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.abin.mallchat.common.chat.domain.entity;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.IpInfo;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -8,6 +9,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@@ -21,7 +23,7 @@ import lombok.experimental.Accessors;
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("message")
|
||||
@TableName(value = "message",autoResultMap = true)
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -79,6 +81,11 @@ public class Message implements Serializable {
|
||||
@TableField("type")
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 最后上下线时间
|
||||
*/
|
||||
@TableField(value = "extra", typeHandler = JacksonTypeHandler.class)
|
||||
private MessageExtra extra;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.abin.mallchat.common.chat.domain.entity;
|
||||
|
||||
import com.abin.mallchat.common.user.domain.entity.IpDetail;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Description: 消息扩展属性
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-05-28
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class MessageExtra implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
//注册时的ip
|
||||
private Map<String, String> urlTitleMap;
|
||||
}
|
||||
@@ -32,22 +32,23 @@ public class CursorPageBaseResp<T> {
|
||||
@ApiModelProperty("数据列表")
|
||||
private List<T> list;
|
||||
|
||||
public static CursorPageBaseResp init(CursorPageBaseResp cursorPage, List list) {
|
||||
CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp();
|
||||
public static <T> CursorPageBaseResp<T> init(CursorPageBaseResp cursorPage, List<T> list) {
|
||||
CursorPageBaseResp<T> cursorPageBaseResp = new CursorPageBaseResp<T>();
|
||||
cursorPageBaseResp.setIsLast(cursorPage.getIsLast());
|
||||
cursorPageBaseResp.setList(list);
|
||||
cursorPageBaseResp.setCursor(cursorPage.getCursor());
|
||||
return cursorPageBaseResp;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Boolean isEmpty() {
|
||||
return CollectionUtil.isEmpty(list);
|
||||
}
|
||||
|
||||
public static CursorPageBaseResp empty() {
|
||||
CursorPageBaseResp cursorPageBaseResp = new CursorPageBaseResp();
|
||||
public static <T> CursorPageBaseResp<T> empty() {
|
||||
CursorPageBaseResp<T> cursorPageBaseResp = new CursorPageBaseResp<T>();
|
||||
cursorPageBaseResp.setIsLast(true);
|
||||
cursorPageBaseResp.setList(new ArrayList());
|
||||
cursorPageBaseResp.setList(new ArrayList<T>());
|
||||
return cursorPageBaseResp;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.abin.mallchat.common.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.BinaryOperator;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 美团的CompletableFuture封装工具类,参考文章https://mp.weixin.qq.com/s/GQGidprakfticYnbVYVYGQ
|
||||
*/
|
||||
@Slf4j
|
||||
public class FutureUtils {
|
||||
/**
|
||||
* 设置CF状态为失败
|
||||
*/
|
||||
public static <T> CompletableFuture<T> failed(Throwable ex) {
|
||||
CompletableFuture<T> completableFuture = new CompletableFuture<>();
|
||||
completableFuture.completeExceptionally(ex);
|
||||
return completableFuture;
|
||||
}
|
||||
/**
|
||||
* 设置CF状态为成功
|
||||
*/
|
||||
public static <T> CompletableFuture<T> success(T result) {
|
||||
CompletableFuture<T> completableFuture = new CompletableFuture<>();
|
||||
completableFuture.complete(result);
|
||||
return completableFuture;
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequence(Collection<CompletableFuture<T>> completableFutures) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>
|
||||
* 多用于分页查询的场景
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequenceList(Collection<CompletableFuture<List<T>>> completableFutures) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.flatMap( listFuture -> listFuture.join().stream())
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/*
|
||||
* 将List<CompletableFuture<Map<K, V>>> 转为 CompletableFuture<Map<K, V>>
|
||||
* @Param mergeFunction 自定义key冲突时的merge策略
|
||||
*/
|
||||
public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(
|
||||
Collection<CompletableFuture<Map<K, V>>> completableFutures, BinaryOperator<V> mergeFunction) {
|
||||
return CompletableFuture
|
||||
.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream().map(CompletableFuture::join)
|
||||
.flatMap(map -> map.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, mergeFunction)));
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<T>> 转为 CompletableFuture<List<T>>,并过滤调null值
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequenceNonNull(Collection<CompletableFuture<T>> completableFutures) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>,并过滤调null值
|
||||
* 多用于分页查询的场景
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequenceListNonNull(Collection<CompletableFuture<List<T>>> completableFutures) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.flatMap( listFuture -> listFuture.join().stream().filter(Objects::nonNull))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<Map<K, V>>> 转为 CompletableFuture<Map<K, V>>
|
||||
* @Param filterFunction 自定义过滤策略
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequence(Collection<CompletableFuture<T>> completableFutures,
|
||||
Predicate<? super T> filterFunction) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.map(CompletableFuture::join)
|
||||
.filter(filterFunction)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 将List<CompletableFuture<List<T>>> 转为 CompletableFuture<List<T>>
|
||||
* @Param filterFunction 自定义过滤策略
|
||||
*/
|
||||
public static <T> CompletableFuture<List<T>> sequenceList(Collection<CompletableFuture<List<T>>> completableFutures,
|
||||
Predicate<? super T> filterFunction) {
|
||||
return CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream()
|
||||
.flatMap( listFuture -> listFuture.join().stream().filter(filterFunction))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 将CompletableFuture<Map<K,V>>的list转为 CompletableFuture<Map<K,V>>。 多个map合并为一个map。 如果key冲突,采用新的value覆盖。
|
||||
*/
|
||||
public static <K, V> CompletableFuture<Map<K, V>> sequenceMap(
|
||||
Collection<CompletableFuture<Map<K, V>>> completableFutures) {
|
||||
return CompletableFuture
|
||||
.allOf(completableFutures.toArray(new CompletableFuture<?>[0]))
|
||||
.thenApply(v -> completableFutures.stream().map(CompletableFuture::join)
|
||||
.flatMap(map -> map.entrySet().stream())
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b)));
|
||||
}}
|
||||
@@ -0,0 +1,77 @@
|
||||
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.google.errorprone.annotations.Var;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
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.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Description: urlTitle查询抽象类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-05-27
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class AbstractUrlTitleDiscover implements UrlTitleDiscover {
|
||||
//链接识别的正则
|
||||
private static final Pattern PATTERN = Pattern.compile("((http|https)://)?(www.)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?");
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, String> getContentTitleMap(String content) {
|
||||
if (StrUtil.isBlank(content)) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
List<String> matchList = ReUtil.findAll(PATTERN, content, 0);
|
||||
//并行请求
|
||||
List<CompletableFuture<Pair<String, String>>> futures = matchList.stream().map(match -> CompletableFuture.supplyAsync(() -> {
|
||||
String title = getUrlTitle(match);
|
||||
return Objects.nonNull(title) ? Pair.of(match, title) : null;
|
||||
})).collect(Collectors.toList());
|
||||
CompletableFuture<List<Pair<String, String>>> 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) {
|
||||
Document document = getUrlDocument(assemble(url));
|
||||
if (Objects.isNull(document)) {
|
||||
return null;
|
||||
}
|
||||
return getDocTitle(document);
|
||||
}
|
||||
|
||||
private String assemble(String url) {
|
||||
if (!StrUtil.startWith(url, "http")) {
|
||||
return "http://" + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
protected Document getUrlDocument(String matchUrl) {
|
||||
try {
|
||||
Connection connect = Jsoup.connect(matchUrl);
|
||||
connect.timeout(1000);
|
||||
return connect.get();
|
||||
} catch (Exception e) {
|
||||
log.error("find title error:url:{}", matchUrl, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.abin.mallchat.common.common.utils.discover;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
/**
|
||||
* Description: 通用的标题解析类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-05-27
|
||||
*/
|
||||
public class CommonUrlTitleDiscover extends AbstractUrlTitleDiscover {
|
||||
@Override
|
||||
public String getDocTitle(Document document) {
|
||||
return document.title();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.abin.mallchat.common.common.utils.discover;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Description: 具有优先级的title查询器
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-05-27
|
||||
*/
|
||||
public class PrioritizedUrlTitleDiscover extends AbstractUrlTitleDiscover {
|
||||
|
||||
private final List<UrlTitleDiscover> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.abin.mallchat.common.common.utils.discover;
|
||||
|
||||
import cn.hutool.core.date.StopWatch;
|
||||
import com.google.common.base.Stopwatch;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.Signed;
|
||||
import java.util.Map;
|
||||
|
||||
public interface UrlTitleDiscover {
|
||||
|
||||
|
||||
@Nullable
|
||||
Map<String, String> 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<String, String> 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)-正品低价、品质保障、配送及时、轻松购物!}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.abin.mallchat.common.common.utils.discover;
|
||||
|
||||
import org.jsoup.nodes.Document;
|
||||
|
||||
/**
|
||||
* Description: 针对微信公众号文章的标题获取类
|
||||
* Author: <a href="https://github.com/zongzibinbin">abin</a>
|
||||
* Date: 2023-05-27
|
||||
*/
|
||||
public class WxUrlTitleDiscover extends AbstractUrlTitleDiscover {
|
||||
@Override
|
||||
public String getDocTitle(Document document) {
|
||||
return document.getElementsByAttributeValue("property", "og:title").attr("content");
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
|
||||
import lombok.*;
|
||||
@@ -99,11 +100,10 @@ public class User implements Serializable {
|
||||
@TableField("update_time")
|
||||
private Date updateTime;
|
||||
|
||||
public IpInfo getIpInfo() {
|
||||
public void refreshIp(String ip) {
|
||||
if (ipInfo == null) {
|
||||
ipInfo = new IpInfo();
|
||||
}
|
||||
return ipInfo;
|
||||
ipInfo.refreshIp(ip);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,6 +45,9 @@ public class IpServiceImpl implements IpService, DisposableBean {
|
||||
executor.execute(() -> {
|
||||
User user = userDao.getById(uid);
|
||||
IpInfo ipInfo = user.getIpInfo();
|
||||
if (Objects.isNull(ipInfo)) {
|
||||
return;
|
||||
}
|
||||
String ip = ipInfo.needRefreshIp();
|
||||
if (StrUtil.isBlank(ip)) {
|
||||
return;
|
||||
|
||||
@@ -27,6 +27,8 @@ import org.springframework.web.bind.annotation.*;
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -66,7 +68,7 @@ public class ChatController {
|
||||
return userCache.getBlackMap().get(BlackTypeEnum.UID.getType());
|
||||
}
|
||||
|
||||
@GetMapping("/public/member/statistic")
|
||||
@GetMapping("public/member/statistic/")
|
||||
@ApiOperation("群成员人数统计")
|
||||
public ApiResult<ChatMemberStatisticResp> getMemberStatistic() {
|
||||
return ApiResult.success(chatService.getMemberStatistic());
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
package com.abin.mallchat.custom.chat.domain.vo.response;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Description: 消息
|
||||
@@ -38,6 +32,8 @@ public class ChatMessageResp {
|
||||
private Long uid;
|
||||
@ApiModelProperty("头像")
|
||||
private String avatar;
|
||||
@ApiModelProperty("归属地")
|
||||
private String locPlace;
|
||||
@ApiModelProperty("徽章标识,如果没有展示null")
|
||||
private Badge badge;
|
||||
}
|
||||
@@ -50,6 +46,8 @@ public class ChatMessageResp {
|
||||
private Date sendTime;
|
||||
@ApiModelProperty("消息内容")
|
||||
private String content;
|
||||
@ApiModelProperty("消息链接映射")
|
||||
private Map<String,String> urlTitleMap;
|
||||
@ApiModelProperty("消息类型 1正常文本 2.爆赞 (点赞超过10)3.危险发言(举报超5)")
|
||||
private Integer type;
|
||||
@ApiModelProperty("消息标记")
|
||||
|
||||
@@ -2,11 +2,15 @@ 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.MessageExtra;
|
||||
import com.abin.mallchat.common.chat.domain.entity.MessageMark;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageMarkTypeEnum;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageStatusEnum;
|
||||
import com.abin.mallchat.common.chat.domain.enums.MessageTypeEnum;
|
||||
import com.abin.mallchat.common.common.domain.enums.YesOrNoEnum;
|
||||
import com.abin.mallchat.common.common.utils.discover.PrioritizedUrlTitleDiscover;
|
||||
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;
|
||||
@@ -23,24 +27,32 @@ import java.util.stream.Collectors;
|
||||
* Date: 2023-03-26
|
||||
*/
|
||||
public class MessageAdapter {
|
||||
public static final int CAN_CALLBACK_GAP_COUNT = 100;
|
||||
public static final int CAN_CALLBACK_GAP_COUNT = 50;
|
||||
private static final PrioritizedUrlTitleDiscover URL_TITLE_DISCOVER = new PrioritizedUrlTitleDiscover();
|
||||
|
||||
public static Message buildMsgSave(ChatMessageReq request, Long uid) {
|
||||
|
||||
return Message.builder()
|
||||
.replyMsgId(request.getReplyMsgId())
|
||||
.content(request.getContent())
|
||||
.fromUid(uid)
|
||||
.roomId(request.getRoomId())
|
||||
.status(MessageStatusEnum.NORMAL.getStatus())
|
||||
.extra(buildExtra(request))
|
||||
.build();
|
||||
|
||||
}
|
||||
|
||||
private static MessageExtra buildExtra(ChatMessageReq request) {
|
||||
Map<String, String> contentTitleMap = URL_TITLE_DISCOVER.getContentTitleMap(request.getContent());
|
||||
return MessageExtra.builder().urlTitleMap(contentTitleMap).build();
|
||||
}
|
||||
|
||||
public static List<ChatMessageResp> buildMsgResp(List<Message> messages, Map<Long, Message> replyMap, Map<Long, User> userMap, List<MessageMark> msgMark, Long receiveUid, Map<Long, ItemConfig> itemMap) {
|
||||
Map<Long, List<MessageMark>> 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.setFromUser(buildFromUser(userMap.get(a.getFromUid()), itemMap));
|
||||
resp.setMessage(buildMessage(a, replyMap, userMap, markMap.getOrDefault(a.getId(), new ArrayList<>()), receiveUid));
|
||||
return resp;
|
||||
})
|
||||
@@ -52,6 +64,7 @@ public class MessageAdapter {
|
||||
ChatMessageResp.Message messageVO = new ChatMessageResp.Message();
|
||||
BeanUtil.copyProperties(message, messageVO);
|
||||
messageVO.setSendTime(message.getCreateTime());
|
||||
messageVO.setUrlTitleMap(Optional.ofNullable(message.getExtra()).map(MessageExtra::getUrlTitleMap).orElse(null));
|
||||
Message replyMessage = replyMap.get(message.getReplyMsgId());
|
||||
//回复消息
|
||||
if (Objects.nonNull(replyMessage)) {
|
||||
@@ -85,9 +98,10 @@ public class MessageAdapter {
|
||||
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();
|
||||
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());
|
||||
|
||||
@@ -16,6 +16,7 @@ import com.abin.mallchat.common.common.domain.vo.request.CursorPageBaseReq;
|
||||
import com.abin.mallchat.common.common.domain.vo.response.CursorPageBaseResp;
|
||||
import com.abin.mallchat.common.common.exception.BusinessException;
|
||||
import com.abin.mallchat.common.common.utils.AssertUtil;
|
||||
import com.abin.mallchat.common.common.utils.discover.PrioritizedUrlTitleDiscover;
|
||||
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;
|
||||
@@ -76,8 +77,6 @@ public class ChatServiceImpl implements ChatService {
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*
|
||||
* @param request
|
||||
*/
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -90,6 +89,7 @@ public class ChatServiceImpl implements ChatService {
|
||||
AssertUtil.equal(replyMsg.getRoomId(), request.getRoomId(), "只能回复相同会话内的消息");
|
||||
|
||||
}
|
||||
//同步获取消息的跳转链接标题
|
||||
Message insert = MessageAdapter.buildMsgSave(request, uid);
|
||||
messageDao.save(insert);
|
||||
//如果有回复消息
|
||||
@@ -224,10 +224,10 @@ public class ChatServiceImpl implements ChatService {
|
||||
Set<Long> 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()));
|
||||
itemMap = userMap.values().stream().map(User::getItemId).distinct().filter(Objects::nonNull).map(itemCache::getById).collect(Collectors.toMap(ItemConfig::getId, Function.identity()));
|
||||
//查询消息标志
|
||||
List<MessageMark> 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, userMap, msgMark, receiveUid, itemMap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class LoginServiceImpl implements LoginService {
|
||||
return false;
|
||||
}
|
||||
String key = RedisKey.getKey(RedisKey.USER_TOKEN_STRING, uid);
|
||||
String realToken = redisUtils.getStr(key);
|
||||
String realToken = RedisUtils.getStr(key);
|
||||
return token.equals(realToken);//有可能token失效了,需要校验是不是和最新token一致
|
||||
}
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ public class WebSocketServiceImpl implements WebSocketService {
|
||||
boolean online = userCache.isOnline(user.getId());
|
||||
if (!online) {
|
||||
user.setLastOptTime(new Date());
|
||||
user.getIpInfo().refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
|
||||
user.refreshIp(NettyUtil.getAttr(channel, NettyUtil.IP));
|
||||
applicationEventPublisher.publishEvent(new UserOnlineEvent(this, user));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class NettyWebSocketServer {
|
||||
protected void initChannel(SocketChannel socketChannel) throws Exception {
|
||||
ChannelPipeline pipeline = socketChannel.pipeline();
|
||||
//30秒客户端没有向服务器发送心跳则关闭连接
|
||||
pipeline.addLast(new IdleStateHandler(30, 0, 0));
|
||||
// pipeline.addLast(new IdleStateHandler(30, 0, 0));
|
||||
// 因为使用http协议,所以需要使用http的编码器,解码器
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
// 以块方式写,添加 chunkedWriter 处理器
|
||||
|
||||
6
pom.xml
6
pom.xml
@@ -44,6 +44,7 @@
|
||||
<netty-all.version>4.1.76.Final</netty-all.version>
|
||||
<weixin-java-mp.version>4.4.0</weixin-java-mp.version>
|
||||
<mybatis-plus-boot-starter.version>3.4.0</mybatis-plus-boot-starter.version>
|
||||
<jsoup.version>1.15.3</jsoup.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -53,6 +54,11 @@
|
||||
<artifactId>mallchat-common</artifactId>
|
||||
<version>${mallchat-common.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>${jsoup.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user