1.链接跳转功能
2.归属地功能
fix:
1.修复回复跳转功能
2.优化翻页工具类
3,修复下线重置ip的bug
This commit is contained in:
zhongzb
2023-05-28 15:29:50 +08:00
parent de27f7791f
commit 53201465f2
23 changed files with 400 additions and 31 deletions

View File

@@ -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>

View File

@@ -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();
}
}

View File

@@ -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;
/**
* 创建时间

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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)));
}}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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)-正品低价、品质保障、配送及时、轻松购物!}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}

View File

@@ -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;