Compare commits
4 Commits
34c7092abc
...
39d2eb1063
| Author | SHA1 | Date | |
|---|---|---|---|
| 39d2eb1063 | |||
| 96b4127873 | |||
| 7c62f1dcf9 | |||
| 1335582827 |
1
.idea/dictionaries/project.xml
generated
1
.idea/dictionaries/project.xml
generated
@@ -2,6 +2,7 @@
|
|||||||
<dictionary name="project">
|
<dictionary name="project">
|
||||||
<words>
|
<words>
|
||||||
<w>asyn</w>
|
<w>asyn</w>
|
||||||
|
<w>entrys</w>
|
||||||
<w>hannote</w>
|
<w>hannote</w>
|
||||||
<w>hanserwei</w>
|
<w>hanserwei</w>
|
||||||
<w>jobhandler</w>
|
<w>jobhandler</w>
|
||||||
|
|||||||
@@ -63,6 +63,20 @@
|
|||||||
<groupId>org.elasticsearch.client</groupId>
|
<groupId>org.elasticsearch.client</groupId>
|
||||||
<artifactId>elasticsearch-rest-client</artifactId>
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Canal -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.protocol</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package com.hanserwei.hannote.search;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableScheduling
|
||||||
public class HannoteSearchApplication {
|
public class HannoteSearchApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(HannoteSearchApplication.class, args);
|
SpringApplication.run(HannoteSearchApplication.class, args);
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.hanserwei.hannote.search.canal;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnectors;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.DisposableBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class CanalClient implements DisposableBean {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CanalProperties canalProperties;
|
||||||
|
|
||||||
|
private CanalConnector canalConnector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例化 Canal 链接对象
|
||||||
|
*
|
||||||
|
* @return CanalConnector
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CanalConnector getCanalConnector() {
|
||||||
|
// Canal 链接地址
|
||||||
|
String address = canalProperties.getAddress();
|
||||||
|
String[] addressArr = address.split(":");
|
||||||
|
// IP 地址
|
||||||
|
String host = addressArr[0];
|
||||||
|
// 端口
|
||||||
|
int port = Integer.parseInt(addressArr[1]);
|
||||||
|
|
||||||
|
// 创建一个 CanalConnector 实例,连接到指定的 Canal 服务端
|
||||||
|
canalConnector = CanalConnectors.newSingleConnector(
|
||||||
|
new InetSocketAddress(host, port),
|
||||||
|
canalProperties.getDestination(),
|
||||||
|
canalProperties.getUsername(),
|
||||||
|
canalProperties.getPassword());
|
||||||
|
|
||||||
|
// 连接到 Canal 服务端
|
||||||
|
canalConnector.connect();
|
||||||
|
// 订阅 Canal 中的数据变化,指定要监听的数据库和表(可以使用表名、数据库名的通配符)
|
||||||
|
canalConnector.subscribe(canalProperties.getSubscribe());
|
||||||
|
// 回滚 Canal 消费者的位点,回滚到上次提交的消费位置
|
||||||
|
canalConnector.rollback();
|
||||||
|
return canalConnector;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在 Spring 容器销毁时释放资源
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void destroy() {
|
||||||
|
if (Objects.nonNull(canalConnector)) {
|
||||||
|
// 断开 canalConnector 与 Canal 服务的连接
|
||||||
|
canalConnector.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.hanserwei.hannote.search.canal;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@ConfigurationProperties(prefix = CanalProperties.PREFIX)
|
||||||
|
@Component
|
||||||
|
@Data
|
||||||
|
public class CanalProperties {
|
||||||
|
|
||||||
|
public static final String PREFIX = "canal";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canal 链接地址
|
||||||
|
*/
|
||||||
|
private String address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据目标
|
||||||
|
*/
|
||||||
|
private String destination;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订阅规则
|
||||||
|
*/
|
||||||
|
private String subscribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一批次拉取数据量,默认 1000 条
|
||||||
|
*/
|
||||||
|
private int batchSize = 1000;
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.hanserwei.hannote.search.canal;
|
||||||
|
|
||||||
|
import com.alibaba.otter.canal.client.CanalConnector;
|
||||||
|
import com.alibaba.otter.canal.protocol.CanalEntry;
|
||||||
|
import com.alibaba.otter.canal.protocol.Message;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class CanalSchedule implements Runnable {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CanalProperties canalProperties;
|
||||||
|
@Resource
|
||||||
|
private CanalConnector canalConnector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印字段信息
|
||||||
|
*
|
||||||
|
* @param columns 字段信息
|
||||||
|
*/
|
||||||
|
private static void printColumn(List<CanalEntry.Column> columns) {
|
||||||
|
for (CanalEntry.Column column : columns) {
|
||||||
|
System.out.println(column.getName() + " : " + column.getValue() + " update=" + column.getUpdated());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Scheduled(fixedDelay = 100) // 每隔 100ms 被执行一次
|
||||||
|
public void run() {
|
||||||
|
// 初始化批次 ID,-1 表示未开始或未获取到数据
|
||||||
|
long batchId = -1;
|
||||||
|
try {
|
||||||
|
// 从 canalConnector 获取批量消息,返回的数据量由 batchSize 控制,若不足,则拉取已有的
|
||||||
|
Message message = canalConnector.getWithoutAck(canalProperties.getBatchSize());
|
||||||
|
|
||||||
|
// 获取当前拉取消息的批次 ID
|
||||||
|
batchId = message.getId();
|
||||||
|
|
||||||
|
// 获取当前批次中的数据条数
|
||||||
|
long size = message.getEntries().size();
|
||||||
|
if (batchId == -1 || size == 0) {
|
||||||
|
try {
|
||||||
|
// 拉取数据为空,休眠 1s, 防止频繁拉取
|
||||||
|
TimeUnit.SECONDS.sleep(1);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.error("消费 Canal 批次数据异常", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果当前批次有数据,打印这批次中的数据条目
|
||||||
|
printEntry(message.getEntries());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对当前批次的消息进行 ack 确认,表示该批次的数据已经被成功消费
|
||||||
|
canalConnector.ack(batchId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("消费 Canal 批次数据异常", e);
|
||||||
|
// 如果出现异常,需要进行数据回滚,以便重新消费这批次的数据
|
||||||
|
canalConnector.rollback(batchId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印这一批次中的数据条目(和官方示例代码一致,后续小节中会自定义这块)
|
||||||
|
*
|
||||||
|
* @param entrys 批次数据
|
||||||
|
*/
|
||||||
|
private void printEntry(List<CanalEntry.Entry> entrys) {
|
||||||
|
for (CanalEntry.Entry entry : entrys) {
|
||||||
|
if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
|
||||||
|
|| entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.RowChange rowChage;
|
||||||
|
try {
|
||||||
|
rowChage = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry,
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
|
||||||
|
CanalEntry.EventType eventType = rowChage.getEventType();
|
||||||
|
System.out.printf("================> binlog[%s:%s] , name[%s,%s] , eventType : %s%n",
|
||||||
|
entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
|
||||||
|
entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
|
||||||
|
eventType);
|
||||||
|
|
||||||
|
for (CanalEntry.RowData rowData : rowChage.getRowDatasList()) {
|
||||||
|
if (eventType == CanalEntry.EventType.DELETE) {
|
||||||
|
printColumn(rowData.getBeforeColumnsList());
|
||||||
|
} else if (eventType == CanalEntry.EventType.INSERT) {
|
||||||
|
printColumn(rowData.getAfterColumnsList());
|
||||||
|
} else {
|
||||||
|
System.out.println("-------> before");
|
||||||
|
printColumn(rowData.getBeforeColumnsList());
|
||||||
|
System.out.println("-------> after");
|
||||||
|
printColumn(rowData.getAfterColumnsList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.hanserwei.hannote.search.controller;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
|
||||||
|
import com.hanserwei.hannote.search.service.ExtDictService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/search")
|
||||||
|
@Slf4j
|
||||||
|
public class ExtDictController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ExtDictService extDictService;
|
||||||
|
|
||||||
|
@GetMapping("/ext/dict")
|
||||||
|
@ApiOperationLog(description = "热更新词典")
|
||||||
|
public ResponseEntity<String> extDict() {
|
||||||
|
return extDictService.getHotUpdateExtDict();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.hanserwei.hannote.search.enums;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum NotePublishTimeRangeEnum {
|
||||||
|
|
||||||
|
// 一天内
|
||||||
|
DAY(0),
|
||||||
|
// 一周内
|
||||||
|
WEEK(1),
|
||||||
|
// 半年内
|
||||||
|
HALF_YEAR(2),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型 code 获取对应的枚举
|
||||||
|
*
|
||||||
|
* @param code 类型 code
|
||||||
|
* @return 枚举
|
||||||
|
*/
|
||||||
|
public static NotePublishTimeRangeEnum valueOf(Integer code) {
|
||||||
|
for (NotePublishTimeRangeEnum notePublishTimeRangeEnum : NotePublishTimeRangeEnum.values()) {
|
||||||
|
if (Objects.equals(code, notePublishTimeRangeEnum.getCode())) {
|
||||||
|
return notePublishTimeRangeEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -29,4 +29,9 @@ public class SearchNoteReqVO {
|
|||||||
*/
|
*/
|
||||||
private Integer sort;
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发布时间范围:null:不限 / 0:一天内 / 1:一周内 / 2:半年内
|
||||||
|
*/
|
||||||
|
private Integer publishTimeRange;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -7,8 +7,6 @@ import lombok.Builder;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -51,7 +49,7 @@ public class SearchNoteRspVO {
|
|||||||
* 最后一次编辑时间
|
* 最后一次编辑时间
|
||||||
*/
|
*/
|
||||||
@JsonAlias("update_time")
|
@JsonAlias("update_time")
|
||||||
private LocalDateTime updateTime;
|
private String updateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 被点赞总数
|
* 被点赞总数
|
||||||
@@ -59,4 +57,14 @@ public class SearchNoteRspVO {
|
|||||||
@JsonAlias("like_total")
|
@JsonAlias("like_total")
|
||||||
private String likeTotal;
|
private String likeTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被评论数
|
||||||
|
*/
|
||||||
|
private String commentTotal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被收藏数
|
||||||
|
*/
|
||||||
|
private String collectTotal;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.hanserwei.hannote.search.service;
|
||||||
|
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
public interface ExtDictService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取热更新词典
|
||||||
|
*
|
||||||
|
* @return 热更新词典
|
||||||
|
*/
|
||||||
|
ResponseEntity<String> getHotUpdateExtDict();
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.hanserwei.hannote.search.service.impl;
|
||||||
|
|
||||||
|
import com.hanserwei.hannote.search.service.ExtDictService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class ExtDictServiceImpl implements ExtDictService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 热更新词典路径
|
||||||
|
*/
|
||||||
|
@Value("${elasticsearch.hotUpdateExtDict}")
|
||||||
|
private String hotUpdateExtDict;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<String> getHotUpdateExtDict() {
|
||||||
|
try {
|
||||||
|
// 获取文件的最后修改时间
|
||||||
|
Path path = Paths.get(hotUpdateExtDict);
|
||||||
|
long lastModifiedTime = Files.getLastModifiedTime(path).toMillis();
|
||||||
|
|
||||||
|
// 生成 ETag(使用文件内容的哈希值)
|
||||||
|
String fileContent = Files.lines(path).collect(Collectors.joining("\n"));
|
||||||
|
String eTag = String.valueOf(fileContent.hashCode());
|
||||||
|
|
||||||
|
// 设置响应头
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.set("ETag", eTag);
|
||||||
|
|
||||||
|
// 设置内容类型为 UTF-8
|
||||||
|
headers.setContentType(MediaType.valueOf("text/plain;charset=UTF-8"));
|
||||||
|
|
||||||
|
// 返回文件内容和 HTTP 头部
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.headers(headers)
|
||||||
|
.lastModified(lastModifiedTime) // 请求头中设置 Last-Modified
|
||||||
|
.body(fileContent);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("==> 获取热更新词典异常: ", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,11 @@ import co.elastic.clients.elasticsearch.core.search.Highlight;
|
|||||||
import co.elastic.clients.elasticsearch.core.search.HighlightField;
|
import co.elastic.clients.elasticsearch.core.search.HighlightField;
|
||||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
import co.elastic.clients.util.NamedValue;
|
import co.elastic.clients.util.NamedValue;
|
||||||
|
import com.hanserwei.framework.common.constant.DateConstants;
|
||||||
import com.hanserwei.framework.common.response.PageResponse;
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
|
import com.hanserwei.framework.common.utils.DateUtils;
|
||||||
import com.hanserwei.framework.common.utils.NumberUtils;
|
import com.hanserwei.framework.common.utils.NumberUtils;
|
||||||
|
import com.hanserwei.hannote.search.enums.NotePublishTimeRangeEnum;
|
||||||
import com.hanserwei.hannote.search.enums.NoteSortTypeEnum;
|
import com.hanserwei.hannote.search.enums.NoteSortTypeEnum;
|
||||||
import com.hanserwei.hannote.search.index.NoteIndex;
|
import com.hanserwei.hannote.search.index.NoteIndex;
|
||||||
import com.hanserwei.hannote.search.model.vo.SearchNoteReqVO;
|
import com.hanserwei.hannote.search.model.vo.SearchNoteReqVO;
|
||||||
@@ -20,6 +23,7 @@ import com.hanserwei.hannote.search.model.vo.SearchNoteRspVO;
|
|||||||
import com.hanserwei.hannote.search.service.NoteService;
|
import com.hanserwei.hannote.search.service.NoteService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,6 +50,8 @@ public class NoteServiceImpl implements NoteService {
|
|||||||
Integer type = searchNoteReqVO.getType();
|
Integer type = searchNoteReqVO.getType();
|
||||||
// 排序方式
|
// 排序方式
|
||||||
Integer sort = searchNoteReqVO.getSort();
|
Integer sort = searchNoteReqVO.getSort();
|
||||||
|
// 发布时间范围
|
||||||
|
Integer publishTimeRange = searchNoteReqVO.getPublishTimeRange();
|
||||||
|
|
||||||
// --- 2. 分页参数 ---
|
// --- 2. 分页参数 ---
|
||||||
int pageSize = 10;
|
int pageSize = 10;
|
||||||
@@ -93,6 +99,31 @@ public class NoteServiceImpl implements NoteService {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// 按发布时间范围过滤
|
||||||
|
NotePublishTimeRangeEnum notePublishTimeRangeEnum = NotePublishTimeRangeEnum.valueOf(publishTimeRange);
|
||||||
|
|
||||||
|
if (Objects.nonNull(notePublishTimeRangeEnum)) {
|
||||||
|
// 结束时间
|
||||||
|
String endTime = LocalDateTime.now().format(DateConstants.DATE_FORMAT_Y_M_D_H_M_S);
|
||||||
|
// 开始时间
|
||||||
|
String startTime = null;
|
||||||
|
switch (notePublishTimeRangeEnum) {
|
||||||
|
case DAY -> startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusDays(1)); // 一天之前的时间
|
||||||
|
case WEEK -> startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusWeeks(1)); // 一周之前的时间
|
||||||
|
case HALF_YEAR ->
|
||||||
|
startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusMonths(6)); // 半年之前的时间
|
||||||
|
}
|
||||||
|
// 设置时间范围
|
||||||
|
if (StringUtils.isNoneBlank(startTime)) {
|
||||||
|
String finalStartTime = startTime;
|
||||||
|
boolQueryBuilder.filter(f -> f.range(r -> r
|
||||||
|
.term(t -> t.field(NoteIndex.FIELD_NOTE_CREATE_TIME)
|
||||||
|
.gte(finalStartTime)
|
||||||
|
.lte(endTime)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BoolQuery boolQuery = boolQueryBuilder.build();
|
BoolQuery boolQuery = boolQueryBuilder.build();
|
||||||
// --- 4. 构建排序 (Sort) 和 FunctionScore ---
|
// --- 4. 构建排序 (Sort) 和 FunctionScore ---
|
||||||
Query finalQuery;
|
Query finalQuery;
|
||||||
@@ -238,8 +269,10 @@ public class NoteServiceImpl implements NoteService {
|
|||||||
String highlightTitle = source.getHighlightTitle();
|
String highlightTitle = source.getHighlightTitle();
|
||||||
String avatar = source.getAvatar();
|
String avatar = source.getAvatar();
|
||||||
String nickname = source.getNickname();
|
String nickname = source.getNickname();
|
||||||
LocalDateTime updateTime = source.getUpdateTime();
|
String updateTime = source.getUpdateTime();
|
||||||
String likeTotal = source.getLikeTotal();
|
String likeTotal = source.getLikeTotal();
|
||||||
|
String collectTotal = source.getCollectTotal();
|
||||||
|
String commentTotal = source.getCommentTotal();
|
||||||
searchNoteRspVOS.add(SearchNoteRspVO.builder()
|
searchNoteRspVOS.add(SearchNoteRspVO.builder()
|
||||||
.noteId(noteId)
|
.noteId(noteId)
|
||||||
.cover(cover)
|
.cover(cover)
|
||||||
@@ -247,9 +280,11 @@ public class NoteServiceImpl implements NoteService {
|
|||||||
.highlightTitle(highlightTitle)
|
.highlightTitle(highlightTitle)
|
||||||
.avatar(avatar)
|
.avatar(avatar)
|
||||||
.nickname(nickname)
|
.nickname(nickname)
|
||||||
.updateTime(updateTime)
|
.updateTime(DateUtils.formatRelativeTime(LocalDateTime.parse(updateTime, DateConstants.DATE_FORMAT_Y_M_D_H_M_S)))
|
||||||
.highlightTitle(highlightedTitle)
|
.highlightTitle(highlightedTitle)
|
||||||
.likeTotal(NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
|
.likeTotal(likeTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(likeTotal)))
|
||||||
|
.collectTotal(collectTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(collectTotal)))
|
||||||
|
.collectTotal(commentTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(commentTotal)))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
package com.hanserwei.hannote.search.service.impl;
|
package com.hanserwei.hannote.search.service.impl;
|
||||||
|
|
||||||
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
import co.elastic.clients.elasticsearch.ElasticsearchClient;
|
||||||
|
import co.elastic.clients.elasticsearch._types.SortOptions;
|
||||||
import co.elastic.clients.elasticsearch._types.SortOrder;
|
import co.elastic.clients.elasticsearch._types.SortOrder;
|
||||||
|
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
|
||||||
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
|
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
import co.elastic.clients.elasticsearch.core.SearchRequest;
|
||||||
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
import co.elastic.clients.elasticsearch.core.SearchResponse;
|
||||||
|
import co.elastic.clients.elasticsearch.core.search.Highlight;
|
||||||
import co.elastic.clients.elasticsearch.core.search.HighlightField;
|
import co.elastic.clients.elasticsearch.core.search.HighlightField;
|
||||||
import co.elastic.clients.elasticsearch.core.search.Hit;
|
import co.elastic.clients.elasticsearch.core.search.Hit;
|
||||||
import co.elastic.clients.util.NamedValue;
|
import co.elastic.clients.util.NamedValue;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.hanserwei.framework.common.response.PageResponse;
|
import com.hanserwei.framework.common.response.PageResponse;
|
||||||
import com.hanserwei.framework.common.utils.NumberUtils;
|
import com.hanserwei.framework.common.utils.NumberUtils;
|
||||||
import com.hanserwei.hannote.search.index.UserIndex;
|
import com.hanserwei.hannote.search.index.UserIndex;
|
||||||
@@ -19,6 +21,8 @@ import jakarta.annotation.Resource;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -31,128 +35,83 @@ public class UserServiceImpl implements UserService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
|
public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {
|
||||||
// 查询关键词
|
// --- 2. 获取请求参数 ---
|
||||||
String keyword = searchUserReqVO.getKeyword();
|
String keyword = searchUserReqVO.getKeyword();
|
||||||
// 当前页码
|
|
||||||
Integer pageNo = searchUserReqVO.getPageNo();
|
Integer pageNo = searchUserReqVO.getPageNo();
|
||||||
|
|
||||||
int pageSize = 10; // 每页展示数据量
|
// --- 3. 设置分页 ---
|
||||||
int from = (pageNo - 1) * pageSize; // 偏移量
|
int pageSize = 10; // 假设每页大小为10
|
||||||
|
int from = (pageNo - 1) * pageSize;
|
||||||
HighlightField nicknameHighlight = HighlightField.of(hf -> hf
|
// --- 4. 构建 multi_match 查询 ---
|
||||||
|
Query multiMatchQuery = Query.of(q -> q
|
||||||
|
.multiMatch(m -> m
|
||||||
|
.query(keyword)
|
||||||
|
.type(TextQueryType.PhrasePrefix)
|
||||||
|
.fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
// --- 5. 构建排序 ---
|
||||||
|
SortOptions sortOptions = SortOptions.of(so -> so
|
||||||
|
.field(f -> f
|
||||||
|
.field(UserIndex.FIELD_USER_FANS_TOTAL)
|
||||||
|
.order(SortOrder.Desc)));
|
||||||
|
// --- 6. 构建高亮 ---
|
||||||
|
HighlightField nikeNameHighlight = HighlightField.of(hf -> hf
|
||||||
.preTags("<strong>")
|
.preTags("<strong>")
|
||||||
.postTags("</strong>")
|
.postTags("</strong>")
|
||||||
);
|
);
|
||||||
|
Highlight highlight = Highlight.of(h -> h
|
||||||
|
.fields(NamedValue.of(UserIndex.FIELD_USER_NICKNAME, nikeNameHighlight)));
|
||||||
SearchRequest searchRequest = SearchRequest.of(r -> r
|
// --- 7. 构建 SearchRequest ---
|
||||||
|
SearchRequest searchRequest = SearchRequest.of(s -> s
|
||||||
.index(UserIndex.NAME)
|
.index(UserIndex.NAME)
|
||||||
|
.query(multiMatchQuery)
|
||||||
// 1. 构建 Query: multiMatchQuery (RHL 风格的匹配)
|
.sort(sortOptions)
|
||||||
.query(q -> q
|
.highlight(highlight)
|
||||||
.multiMatch(m -> m
|
|
||||||
.query(keyword)
|
|
||||||
.fields(UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_HAN_NOTE_ID)
|
|
||||||
// 默认使用 MatchQuery 行为,如果要模糊匹配,请添加 .fuzziness("AUTO")
|
|
||||||
.type(TextQueryType.PhrasePrefix)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 2. 构建 Sort
|
|
||||||
.sort(s -> s
|
|
||||||
.field(f -> f
|
|
||||||
.field(UserIndex.FIELD_USER_FANS_TOTAL)
|
|
||||||
.order(SortOrder.Desc)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
.highlight(h -> h.fields(NamedValue.of(UserIndex.FIELD_USER_NICKNAME, nicknameHighlight)))
|
|
||||||
|
|
||||||
// 3. 分页 from 和 size
|
|
||||||
.from(from)
|
.from(from)
|
||||||
.size(pageSize)
|
.size(pageSize));
|
||||||
);
|
// --- 8. 执行查询和解析响应 ---
|
||||||
|
List<SearchUserRspVO> searchUserRspVOS = new ArrayList<>();
|
||||||
|
|
||||||
// 返参 VO 集合
|
|
||||||
List<SearchUserRspVO> searchUserRspVOS = Lists.newArrayList();
|
|
||||||
// 总文档数,默认为 0
|
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.info("==> SearchRequest: {}", searchRequest.toString());
|
log.info("==> searchRequest: {}", searchRequest);
|
||||||
|
|
||||||
// 执行查询请求
|
|
||||||
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
SearchResponse<SearchUserRspVO> searchResponse = client.search(searchRequest, SearchUserRspVO.class);
|
||||||
|
// 8.2. 处理响应
|
||||||
// 处理搜索结果
|
|
||||||
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
|
||||||
if (searchResponse.hits().total() != null) {
|
if (searchResponse.hits().total() != null) {
|
||||||
total = searchResponse.hits().total().value();
|
total = searchResponse.hits().total().value();
|
||||||
}
|
}
|
||||||
|
log.info("==> 命中文档总数, hits: {}", total);
|
||||||
searchUserRspVOS = Lists.newArrayList();
|
List<Hit<SearchUserRspVO>> hits = searchResponse.hits().hits();
|
||||||
|
|
||||||
for (Hit<SearchUserRspVO> hit : hits) {
|
for (Hit<SearchUserRspVO> hit : hits) {
|
||||||
// 1. 获取原始文档数据 (source)
|
// 获取source
|
||||||
SearchUserRspVO source = hit.source();
|
SearchUserRspVO source = hit.source();
|
||||||
|
// 8.3. 获取高亮字段
|
||||||
// 2. 获取高亮数据 (highlight)
|
String highlightNickname = null;
|
||||||
Map<String, List<String>> highlights = hit.highlight();
|
Map<String, List<String>> highlightFiled = hit.highlight();
|
||||||
|
if (highlightFiled.containsKey(UserIndex.FIELD_USER_NICKNAME)) {
|
||||||
|
highlightNickname = highlightFiled.get(UserIndex.FIELD_USER_NICKNAME).getFirst();
|
||||||
|
}
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
// 3. 调用辅助方法合并数据和高亮
|
Long userId = source.getUserId();
|
||||||
SearchUserRspVO searchUserRspVO = mergeHitToRspVO(source, highlights);
|
String nickname = source.getNickname();
|
||||||
searchUserRspVOS.add(searchUserRspVO);
|
String avatar = source.getAvatar();
|
||||||
|
String hanNoteId = source.getHanNoteId();
|
||||||
|
Integer noteTotal = source.getNoteTotal();
|
||||||
|
String fansTotal = source.getFansTotal();
|
||||||
|
searchUserRspVOS.add(SearchUserRspVO.builder()
|
||||||
|
.userId(userId)
|
||||||
|
.nickname(nickname)
|
||||||
|
.highlightNickname(highlightNickname)
|
||||||
|
.avatar(avatar)
|
||||||
|
.hanNoteId(hanNoteId)
|
||||||
|
.noteTotal(noteTotal)
|
||||||
|
.fansTotal(fansTotal == null ? "0" : NumberUtils.formatNumberString(Long.parseLong(fansTotal)))
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
} catch (Exception e) {
|
log.error("==> search error: {}", e.getMessage());
|
||||||
log.error("==> 查询 Elasticsearch 异常: ", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return PageResponse.success(searchUserRspVOS, pageNo, total);
|
return PageResponse.success(searchUserRspVOS, pageNo, total);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将原始文档和高亮数据合并到 SearchUserRspVO
|
|
||||||
*
|
|
||||||
* @param source 原始文档数据 (已自动反序列化)
|
|
||||||
* @param highlights 高亮数据 Map
|
|
||||||
* @return SearchUserRspVO
|
|
||||||
*/
|
|
||||||
private SearchUserRspVO mergeHitToRspVO(SearchUserRspVO source, Map<String, List<String>> highlights) {
|
|
||||||
if (source == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 复制原始文档字段 (假设 SearchUserRspVO 使用 Lombok @Data 或 Builder)
|
|
||||||
SearchUserRspVO searchUserRspVO = SearchUserRspVO.builder()
|
|
||||||
.userId(source.getUserId())
|
|
||||||
.nickname(source.getNickname())
|
|
||||||
.avatar(source.getAvatar())
|
|
||||||
.hanNoteId(source.getHanNoteId()) // 字段名应与您的 VO 保持一致
|
|
||||||
.noteTotal(source.getNoteTotal())
|
|
||||||
.build();
|
|
||||||
if (source.getFansTotal() != null) {
|
|
||||||
searchUserRspVO.setFansTotal(NumberUtils.formatNumberString(Long.parseLong(source.getFansTotal())));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. ⭐️ 核心逻辑:处理并设置高亮字段
|
|
||||||
if (highlights != null) {
|
|
||||||
// 尝试从 highlights Map 中获取 nickname 字段的高亮结果
|
|
||||||
List<String> nicknameHighlights = highlights.get(UserIndex.FIELD_USER_NICKNAME);
|
|
||||||
|
|
||||||
if (nicknameHighlights != null && !nicknameHighlights.isEmpty()) {
|
|
||||||
searchUserRspVO.setHighlightNickname(nicknameHighlights.getFirst());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 如果高亮字段为空,默认使用原始 nickname
|
|
||||||
if (searchUserRspVO.getHighlightNickname() == null) {
|
|
||||||
searchUserRspVO.setHighlightNickname(source.getNickname());
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchUserRspVO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,15 @@ public interface DateConstants {
|
|||||||
* DateTimeFormatter:年-月
|
* DateTimeFormatter:年-月
|
||||||
*/
|
*/
|
||||||
DateTimeFormatter DATE_FORMAT_Y_M = DateTimeFormatter.ofPattern("yyyy-MM");
|
DateTimeFormatter DATE_FORMAT_Y_M = DateTimeFormatter.ofPattern("yyyy-MM");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTimeFormatter:月-日
|
||||||
|
*/
|
||||||
|
DateTimeFormatter DATE_FORMAT_M_D = DateTimeFormatter.ofPattern("MM-dd");
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DateTimeFormatter:时:分
|
||||||
|
*/
|
||||||
|
DateTimeFormatter DATE_FORMAT_H_M = DateTimeFormatter.ofPattern("HH:mm");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package com.hanserwei.framework.common.utils;
|
package com.hanserwei.framework.common.utils;
|
||||||
|
|
||||||
|
import com.hanserwei.framework.common.constant.DateConstants;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.ZoneOffset;
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
public class DateUtils {
|
public class DateUtils {
|
||||||
|
|
||||||
@@ -14,4 +17,63 @@ public class DateUtils {
|
|||||||
public static long localDateTime2Timestamp(LocalDateTime localDateTime) {
|
public static long localDateTime2Timestamp(LocalDateTime localDateTime) {
|
||||||
return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli();
|
return localDateTime.toInstant(ZoneOffset.UTC).toEpochMilli();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LocalDateTime 转 String 字符串
|
||||||
|
*
|
||||||
|
* @param time LocalDateTime
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
public static String localDateTime2String(LocalDateTime time) {
|
||||||
|
return time.format(DateConstants.DATE_FORMAT_Y_M_D_H_M_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LocalDateTime 转友好的相对时间字符串
|
||||||
|
*
|
||||||
|
* @param dateTime LocalDateTime
|
||||||
|
* @return String友好的相对时间字符串
|
||||||
|
*/
|
||||||
|
public static String formatRelativeTime(LocalDateTime dateTime) {
|
||||||
|
// 当前时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
// 计算与当前时间的差距
|
||||||
|
long daysDiff = ChronoUnit.DAYS.between(dateTime, now);
|
||||||
|
long hoursDiff = ChronoUnit.HOURS.between(dateTime, now);
|
||||||
|
long minutesDiff = ChronoUnit.MINUTES.between(dateTime, now);
|
||||||
|
|
||||||
|
if (daysDiff < 1) { // 如果是今天
|
||||||
|
if (hoursDiff < 1) { // 如果是几分钟前
|
||||||
|
return minutesDiff + "分钟前";
|
||||||
|
} else { // 如果是几小时前
|
||||||
|
return hoursDiff + "小时前";
|
||||||
|
}
|
||||||
|
} else if (daysDiff == 1) { // 如果是昨天
|
||||||
|
return "昨天 " + dateTime.format(DateConstants.DATE_FORMAT_H_M);
|
||||||
|
} else if (daysDiff < 7) { // 如果是最近一周
|
||||||
|
return daysDiff + "天前";
|
||||||
|
} else if (dateTime.getYear() == now.getYear()) { // 如果是今年
|
||||||
|
return dateTime.format(DateConstants.DATE_FORMAT_M_D);
|
||||||
|
} else { // 如果是去年或更早
|
||||||
|
return dateTime.format(DateConstants.DATE_FORMAT_Y_M_D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// 测试示例
|
||||||
|
LocalDateTime dateTime1 = LocalDateTime.now().minusMinutes(10); // 10分钟前
|
||||||
|
LocalDateTime dateTime2 = LocalDateTime.now().minusHours(3); // 3小时前
|
||||||
|
LocalDateTime dateTime3 = LocalDateTime.now().minusDays(1).minusHours(5); // 昨天 20:12
|
||||||
|
LocalDateTime dateTime4 = LocalDateTime.now().minusDays(2); // 2天前
|
||||||
|
LocalDateTime dateTime5 = LocalDateTime.now().minusDays(10); // 11-06
|
||||||
|
LocalDateTime dateTime6 = LocalDateTime.of(2023, 12, 1, 12, 30, 0); // 2023-12-01
|
||||||
|
|
||||||
|
System.out.println(formatRelativeTime(dateTime1)); // 输出 "10分钟前"
|
||||||
|
System.out.println(formatRelativeTime(dateTime2)); // 输出 "3小时前"
|
||||||
|
System.out.println(formatRelativeTime(dateTime3)); // 输出 "昨天 20:12"
|
||||||
|
System.out.println(formatRelativeTime(dateTime4)); // 输出 "2天前"
|
||||||
|
System.out.println(formatRelativeTime(dateTime5)); // 输出 "11-06"
|
||||||
|
System.out.println(formatRelativeTime(dateTime6)); // 输出 "2023-12-01"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
pom.xml
17
pom.xml
@@ -70,6 +70,7 @@
|
|||||||
<xxl-job.version>3.2.0</xxl-job.version>
|
<xxl-job.version>3.2.0</xxl-job.version>
|
||||||
<elasticsearch-java.version>9.2.0</elasticsearch-java.version>
|
<elasticsearch-java.version>9.2.0</elasticsearch-java.version>
|
||||||
<elasticsearch.version>9.2.0</elasticsearch.version>
|
<elasticsearch.version>9.2.0</elasticsearch.version>
|
||||||
|
<canal.version>1.1.8</canal.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -314,6 +315,22 @@
|
|||||||
<artifactId>elasticsearch-rest-client</artifactId>
|
<artifactId>elasticsearch-rest-client</artifactId>
|
||||||
<version>${elasticsearch.version}</version>
|
<version>${elasticsearch.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Canal -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.client</artifactId>
|
||||||
|
<version>${canal.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.common</artifactId>
|
||||||
|
<version>${canal.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.otter</groupId>
|
||||||
|
<artifactId>canal.protocol</artifactId>
|
||||||
|
<version>${canal.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user