feat: init
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,6 +10,8 @@
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
.idea
|
||||
target
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
|
||||
126
pom.xml
Normal file
126
pom.xml
Normal file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.metis</groupId>
|
||||
<artifactId>metis</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-boot.vserion>3.3.4</spring-boot.vserion>
|
||||
<fastjson.version>2.0.45</fastjson.version>
|
||||
<lombok.version>1.18.34</lombok.version>
|
||||
<sanitizer.version>1.2.3</sanitizer.version>
|
||||
<langchain4j.version>1.0.0-beta2</langchain4j.version>
|
||||
<mybatis-plus.version>3.5.8</mybatis-plus.version>
|
||||
</properties>
|
||||
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.vserion}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-open-ai</artifactId>
|
||||
<version>${langchain4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.langchain4j</groupId>
|
||||
<artifactId>langchain4j-mcp</artifactId>
|
||||
<version>1.0.0-beta2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mikesamuel</groupId>
|
||||
<artifactId>json-sanitizer</artifactId>
|
||||
<version>${sanitizer.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>${mybatis-plus.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.33</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.22</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.mapstruct</groupId>
|
||||
<artifactId>mapstruct-processor</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.34</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok-mapstruct-binding</artifactId>
|
||||
<version>0.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
15
src/main/java/com/metis/MetisApplication.java
Normal file
15
src/main/java/com/metis/MetisApplication.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.metis;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||
|
||||
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
|
||||
public class MetisApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MetisApplication.class, args);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
src/main/java/com/metis/controller/TestController.java
Normal file
18
src/main/java/com/metis/controller/TestController.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.metis.controller;
|
||||
|
||||
import com.metis.result.Result;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/test")
|
||||
public class TestController {
|
||||
|
||||
|
||||
@GetMapping
|
||||
public Result<String> test() {
|
||||
return Result.ok("测试成功");
|
||||
}
|
||||
|
||||
}
|
||||
45
src/main/java/com/metis/domain/BaseEntity.java
Normal file
45
src/main/java/com/metis/domain/BaseEntity.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package com.metis.domain;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2022/10/30
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity implements Serializable {
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
// private Object createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(locale = "zh",timezone = "GMT+8",pattern = "yyyy-MM-dd")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
// private Object updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(locale = "zh",timezone = "GMT+8",pattern = "yyyy-MM-dd")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 逻辑删除字段
|
||||
*/
|
||||
private Boolean isDeleted;
|
||||
|
||||
|
||||
}
|
||||
1
src/main/java/com/metis/domain/package-info.java
Normal file
1
src/main/java/com/metis/domain/package-info.java
Normal file
@@ -0,0 +1 @@
|
||||
package com.metis.domain;
|
||||
59
src/main/java/com/metis/enums/BaseEnum.java
Normal file
59
src/main/java/com/metis/enums/BaseEnum.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package com.metis.enums;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IEnum;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public interface BaseEnum<T extends Enum<T>> extends IEnum<Integer> {
|
||||
|
||||
/**
|
||||
* 获取编码
|
||||
*
|
||||
* @return {@link Integer}
|
||||
*/
|
||||
Integer getCode();
|
||||
|
||||
/**
|
||||
* 获取名称
|
||||
*
|
||||
* @return {@link String}
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 获取value值
|
||||
*
|
||||
* @return {@link Integer}
|
||||
*/
|
||||
@Override
|
||||
default Integer getValue() {
|
||||
return getCode();
|
||||
}
|
||||
/**
|
||||
* 解析通过编码
|
||||
*
|
||||
* @param enumClass 枚举班
|
||||
* @param code 编码
|
||||
* @return {@link E}
|
||||
*/
|
||||
static <E extends Enum<E> & BaseEnum<E>> E parseByCode(Class<E> enumClass, Integer code) {
|
||||
return Arrays.stream(enumClass.getEnumConstants())
|
||||
.filter(e -> e.getCode().equals(code))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按名称解析
|
||||
*
|
||||
* @param enumClass 枚举班
|
||||
* @param name 名称
|
||||
* @return {@link E}
|
||||
*/
|
||||
static <E extends Enum<E> & BaseEnum<E>> E parseByName(Class<E> enumClass, String name) {
|
||||
return Arrays.stream(enumClass.getEnumConstants())
|
||||
.filter(e -> e.getName().equals(name))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
47
src/main/java/com/metis/enums/ResultEnum.java
Normal file
47
src/main/java/com/metis/enums/ResultEnum.java
Normal file
@@ -0,0 +1,47 @@
|
||||
package com.metis.enums;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-10
|
||||
*/
|
||||
public enum ResultEnum {
|
||||
/**
|
||||
* 返回状态枚举
|
||||
*/
|
||||
SUCCESS(1000, "操作成功", HttpStatus.OK),
|
||||
|
||||
NO_DATA(1001, "查询结果为空", HttpStatus.OK),
|
||||
|
||||
RESUBMIT_LOCK(2002, "重复提交", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
ERROR(2000, "操作失败", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
SYS_ERROR(2001, "系统异常", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
SENTINEL_FLOW(3000, "限流了", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
SENTINEL_PARAM_FLOW(3000, "热点参数限流", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
SENTINEL_SYSTEM(3000, "系统规则负载等不满足要求", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
|
||||
SENTINEL_AUTHORITY(3000, "授权规则不通过", HttpStatus.UNAUTHORIZED),
|
||||
|
||||
SENTINEL_DEGRADE(3000, "降级了", HttpStatus.INTERNAL_SERVER_ERROR),
|
||||
;
|
||||
|
||||
ResultEnum(int code, String msg, HttpStatus status) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public final int code;
|
||||
|
||||
public final String msg;
|
||||
|
||||
public final transient HttpStatus status;
|
||||
|
||||
|
||||
}
|
||||
92
src/main/java/com/metis/handle/GlobalExceptionHandler.java
Normal file
92
src/main/java/com/metis/handle/GlobalExceptionHandler.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package com.metis.handle;
|
||||
|
||||
|
||||
import com.metis.result.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
|
||||
import javax.security.auth.login.AccountExpiredException;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2022/10/30
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
public GlobalExceptionHandler() {
|
||||
log.info("开始初始化全局异常处理器");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 路径不存在
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public Result<Object> handlerNoFoundException(Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.notFound( "路径不存在,请检查路径是否正确");
|
||||
}
|
||||
|
||||
/**
|
||||
* 授权失败
|
||||
*
|
||||
* @param e
|
||||
* @return
|
||||
*/
|
||||
@ExceptionHandler(AccessDeniedException.class)
|
||||
public Result<String> handleAuthorizationException(AccessDeniedException e) {
|
||||
log.error(e.getMessage());
|
||||
return Result.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
@ExceptionHandler(AccountExpiredException.class)
|
||||
public Result<String> handleAccountExpiredException(AccountExpiredException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public Result<String> validatedBindException(BindException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getAllErrors().get(0).getDefaultMessage();
|
||||
return Result.error(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result<String> validExceptionHandler(MethodArgumentNotValidException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
String message = e.getBindingResult().getFieldError().getDefaultMessage();
|
||||
return Result.error(message);
|
||||
}
|
||||
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public Result<String> runtimeExceptionHandler(RuntimeException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package com.metis.mybatis;
|
||||
|
||||
import cn.hutool.core.net.NetUtil;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
|
||||
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
|
||||
import com.metis.mybatis.handler.BaseEntityMetaObjectHandler;
|
||||
import com.metis.mybatis.support.CustomSqlInjector;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* MyBatis-Plus配置
|
||||
*
|
||||
* @author ZhangQiang
|
||||
* @date 2024/09/24
|
||||
*/
|
||||
@Configuration
|
||||
public class MybatisPlusConfiguration {
|
||||
|
||||
|
||||
/**
|
||||
* mybatis-plus 拦截器
|
||||
*/
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
// 配置分页拦截器
|
||||
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
|
||||
paginationInnerInterceptor.setOptimizeJoin(false);
|
||||
paginationInnerInterceptor.setMaxLimit(500L);
|
||||
paginationInnerInterceptor.setOverflow(false);
|
||||
interceptor.addInnerInterceptor(paginationInnerInterceptor);
|
||||
// 乐观锁插件
|
||||
interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor());
|
||||
// 添加防止全表更新与删除插件
|
||||
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动填充字段信息
|
||||
*/
|
||||
@Bean
|
||||
public MetaObjectHandler metaObjectHandler() {
|
||||
return new BaseEntityMetaObjectHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 乐观锁插件
|
||||
*/
|
||||
public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() {
|
||||
return new OptimisticLockerInnerInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义SQL注入器
|
||||
*
|
||||
* @return 自定义SQL注入器
|
||||
*/
|
||||
@Bean
|
||||
public ISqlInjector iSqlInjector() {
|
||||
return new CustomSqlInjector();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 使用网卡信息绑定雪花生成器,实现集群生成id不重复
|
||||
*/
|
||||
@Bean
|
||||
public IdentifierGenerator idGenerator() {
|
||||
return new DefaultIdentifierGenerator(NetUtil.getLocalhost());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.metis.mybatis.handler;
|
||||
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体-自动填充处理器
|
||||
*
|
||||
* @author ZhangQiang
|
||||
* @date 2024/09/29
|
||||
*/
|
||||
@Slf4j
|
||||
public class BaseEntityMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
|
||||
/**
|
||||
* 插入元对象字段填充(用于插入时对公共字段的填充)
|
||||
*
|
||||
* @param metaObject 元对象
|
||||
*/
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
this.strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新元对象字段填充(用于更新时对公共字段的填充)
|
||||
*
|
||||
* @param metaObject 元对象
|
||||
*/
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.metis.mybatis.logic;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.enums.SqlMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.baomidou.mybatisplus.core.toolkit.sql.SqlScriptUtils;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.SqlSource;
|
||||
|
||||
/**
|
||||
* 选择忽略逻辑删除
|
||||
*
|
||||
* @author clay
|
||||
* @date 2024/12/02
|
||||
*/
|
||||
public class SelectIgnoreLogicDelete extends AbstractMethod {
|
||||
public SelectIgnoreLogicDelete() {
|
||||
super("SelectIgnoreLogicDelete");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
|
||||
SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
|
||||
String sql = String.format(sqlMethod.getSql(),
|
||||
StrUtil.EMPTY,
|
||||
sqlFirst(),
|
||||
sqlSelectColumns(tableInfo, true),
|
||||
tableInfo.getTableName(),
|
||||
sqlWhereEntityWrapper(true, tableInfo),
|
||||
sqlComment());
|
||||
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
|
||||
return this.addSelectMappedStatementForTable(mapperClass, "selectIgnoreLogicDelete", sqlSource, tableInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) {
|
||||
String sqlScript = table.getAllSqlWhere(false, true, true, WRAPPER_ENTITY_DOT);
|
||||
sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true);
|
||||
sqlScript += NEWLINE;
|
||||
sqlScript += SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", WRAPPER_NONEMPTYOFENTITY, WRAPPER_NONEMPTYOFNORMAL), false) + " ${%s}", WRAPPER_SQLSEGMENT),
|
||||
String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
|
||||
WRAPPER_NONEMPTYOFWHERE), true);
|
||||
sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE;
|
||||
sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),
|
||||
String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,
|
||||
WRAPPER_EMPTYOFWHERE), true);
|
||||
sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER), true);
|
||||
return newLine ? NEWLINE + sqlScript : sqlScript;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.metis.mybatis.support;
|
||||
|
||||
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
|
||||
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfo;
|
||||
import com.metis.mybatis.logic.SelectIgnoreLogicDelete;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 自定义SQL注入器
|
||||
*
|
||||
* @author clay
|
||||
* @date 2024/12/02
|
||||
*/
|
||||
public class CustomSqlInjector extends DefaultSqlInjector {
|
||||
@Override
|
||||
public List<AbstractMethod> getMethodList(Configuration configuration, Class<?> mapperClass, TableInfo tableInfo) {
|
||||
List<AbstractMethod> methodList = super.getMethodList(configuration, mapperClass, tableInfo);
|
||||
// 查询所有数据(包括已经逻辑删除数据)
|
||||
methodList.add(new SelectIgnoreLogicDelete());
|
||||
return methodList;
|
||||
}
|
||||
}
|
||||
149
src/main/java/com/metis/result/Result.java
Normal file
149
src/main/java/com/metis/result/Result.java
Normal file
@@ -0,0 +1,149 @@
|
||||
package com.metis.result;
|
||||
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.metis.enums.ResultEnum;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 返回结果集
|
||||
*
|
||||
* @author: Clay
|
||||
* @date: 2022/5/31 10:00
|
||||
*/
|
||||
public class Result<T> implements Serializable {
|
||||
private Integer code;
|
||||
private String msg;
|
||||
private T data;
|
||||
private transient HttpStatus status;
|
||||
|
||||
|
||||
public Result() {
|
||||
}
|
||||
|
||||
public Result(Integer code, String msg, T data, HttpStatus status) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(Integer code, String msg, T data) {
|
||||
return new Result<>(code, msg, data, HttpStatus.OK);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(Integer code, String msg, T data, HttpStatus status) {
|
||||
return new Result<>(code, msg, data, status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String msg, T data) {
|
||||
return Result.ok(ResultEnum.SUCCESS.code, msg, data, ResultEnum.SUCCESS.status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(Integer code, T data) {
|
||||
return Result.ok(code, ResultEnum.SUCCESS.msg, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(String msg) {
|
||||
return ok(ResultEnum.SUCCESS.code, msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok(T data) {
|
||||
return Result.ok(ResultEnum.SUCCESS.msg, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> ok() {
|
||||
return Result.ok(ResultEnum.SUCCESS.msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String msg, T data) {
|
||||
return Result.error(ResultEnum.ERROR.code, msg, data);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String msg) {
|
||||
return Result.error(code, msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> notFound(String msg) {
|
||||
return Result.error(HttpStatus.NOT_FOUND.value(), msg, null, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String msg, T data) {
|
||||
return new Result<>(code, msg, data, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(Integer code, String msg, T data, HttpStatus status) {
|
||||
return new Result<>(code, msg, data, status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> unauthorized(String msg) {
|
||||
return new Result<>(HttpStatus.UNAUTHORIZED.value(), msg, null, HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(HttpStatus status, String msg) {
|
||||
return new Result<>(status.value(), msg, null, status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(ResultEnum resultEnum) {
|
||||
return Result.error(resultEnum.code, resultEnum.msg, null, resultEnum.status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error(String msg) {
|
||||
return Result.error(ResultEnum.ERROR.code, msg, null, ResultEnum.ERROR.status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> error() {
|
||||
return Result.error(ResultEnum.ERROR.code, ResultEnum.ERROR.msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> info(String msg) {
|
||||
return Result.ok(ResultEnum.NO_DATA.code, msg, null);
|
||||
}
|
||||
|
||||
public static <T> Result<T> info(ResultEnum resultEnum) {
|
||||
return Result.error(resultEnum.code, resultEnum.msg, null, resultEnum.status);
|
||||
}
|
||||
|
||||
public static <T> Result<T> noData() {
|
||||
return Result.ok(ResultEnum.NO_DATA.code, ResultEnum.NO_DATA.msg, null);
|
||||
}
|
||||
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public T getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public HttpStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public void setStatus(HttpStatus status) {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
45
src/main/java/com/metis/result/page/TableDataInfo.java
Normal file
45
src/main/java/com/metis/result/page/TableDataInfo.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package com.metis.result.page;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 表格分页数据对象
|
||||
*
|
||||
* @author binlin
|
||||
*/
|
||||
@Data
|
||||
public class TableDataInfo<T> implements Serializable {
|
||||
|
||||
|
||||
/**
|
||||
* 总记录数
|
||||
*/
|
||||
private long total;
|
||||
|
||||
/**
|
||||
* 列表数据
|
||||
*/
|
||||
private List<T> rows;
|
||||
|
||||
/**
|
||||
* 表格数据对象
|
||||
*/
|
||||
public TableDataInfo() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页
|
||||
*
|
||||
* @param list 列表数据
|
||||
* @param total 总记录数
|
||||
*/
|
||||
public TableDataInfo(List<T> list, int total) {
|
||||
this.rows = list;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
}
|
||||
119
src/main/java/com/metis/sseclient/ToolSpecificationHelper.java
Normal file
119
src/main/java/com/metis/sseclient/ToolSpecificationHelper.java
Normal file
@@ -0,0 +1,119 @@
|
||||
package com.metis.sseclient;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.model.chat.request.json.JsonArraySchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonBooleanSchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonEnumSchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonIntegerSchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonNumberSchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonObjectSchema;
|
||||
import dev.langchain4j.model.chat.request.json.JsonSchemaElement;
|
||||
import dev.langchain4j.model.chat.request.json.JsonStringSchema;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class ToolSpecificationHelper {
|
||||
|
||||
/**
|
||||
* Converts the 'tools' element from a ListToolsResult MCP message
|
||||
* to a list of ToolSpecification objects.
|
||||
*/
|
||||
public static List<ToolSpecification> toolSpecificationListFromMcpResponse(ArrayNode array) {
|
||||
List<ToolSpecification> result = new ArrayList<>();
|
||||
for (JsonNode tool : array) {
|
||||
final ToolSpecification.Builder builder = ToolSpecification.builder();
|
||||
builder.name(tool.get("name").asText());
|
||||
if (tool.has("description")) {
|
||||
builder.description(tool.get("description").asText());
|
||||
}
|
||||
builder.parameters((JsonObjectSchema) jsonNodeToJsonSchemaElement(tool.get("inputSchema")));
|
||||
result.add(builder.build());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the 'inputSchema' element (inside the 'Tool' type in the MCP schema)
|
||||
* to a JsonSchemaElement object that describes the tool's arguments.
|
||||
*/
|
||||
public static JsonSchemaElement jsonNodeToJsonSchemaElement(JsonNode node) {
|
||||
String nodeType = node.get("type").asText();
|
||||
if (nodeType.equals("object")) {
|
||||
JsonObjectSchema.Builder builder = JsonObjectSchema.builder();
|
||||
JsonNode required = node.get("required");
|
||||
if (required != null) {
|
||||
builder.required(toStringArray((ArrayNode) required));
|
||||
}
|
||||
if (node.has("additionalProperties")) {
|
||||
builder.additionalProperties(node.get("additionalProperties").asBoolean(false));
|
||||
}
|
||||
JsonNode description = node.get("description");
|
||||
if (description != null) {
|
||||
builder.description(description.asText());
|
||||
}
|
||||
JsonNode properties = node.get("properties");
|
||||
if (properties != null) {
|
||||
ObjectNode propertiesObject = (ObjectNode) properties;
|
||||
for (Map.Entry<String, JsonNode> property : propertiesObject.properties()) {
|
||||
builder.addProperty(property.getKey(), jsonNodeToJsonSchemaElement(property.getValue()));
|
||||
}
|
||||
}
|
||||
return builder.build();
|
||||
} else if (nodeType.equals("string")) {
|
||||
if (node.has("enum")) {
|
||||
JsonEnumSchema.Builder builder = JsonEnumSchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
builder.enumValues(toStringArray((ArrayNode) node.get("enum")));
|
||||
return builder.build();
|
||||
} else {
|
||||
JsonStringSchema.Builder builder = JsonStringSchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
} else if (nodeType.equals("number")) {
|
||||
JsonNumberSchema.Builder builder = JsonNumberSchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
return builder.build();
|
||||
} else if (nodeType.equals("integer")) {
|
||||
JsonIntegerSchema.Builder builder = JsonIntegerSchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
return builder.build();
|
||||
} else if (nodeType.equals("boolean")) {
|
||||
JsonBooleanSchema.Builder builder = JsonBooleanSchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
return builder.build();
|
||||
} else if (nodeType.equals("array")) {
|
||||
JsonArraySchema.Builder builder = JsonArraySchema.builder();
|
||||
if (node.has("description")) {
|
||||
builder.description(node.get("description").asText());
|
||||
}
|
||||
builder.items(jsonNodeToJsonSchemaElement(node.get("items")));
|
||||
return builder.build();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown element type: " + nodeType);
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] toStringArray(ArrayNode jsonArray) {
|
||||
String[] result = new String[jsonArray.size()];
|
||||
for (int i = 0; i < jsonArray.size(); i++) {
|
||||
result[i] = jsonArray.get(i).asText();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
180
src/main/java/com/metis/sseclient/check/SseCheck.java
Normal file
180
src/main/java/com/metis/sseclient/check/SseCheck.java
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.metis.sseclient.check;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.metis.sseclient.ToolSpecificationHelper;
|
||||
import com.metis.sseclient.event.SseEventListener;
|
||||
import com.metis.sseclient.handler.McpOperationHandler;
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import dev.langchain4j.mcp.client.protocol.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import okhttp3.sse.EventSource;
|
||||
import okhttp3.sse.EventSources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class SseCheck {
|
||||
private final String sseUrl;
|
||||
private volatile String postUrl;
|
||||
private final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private final AtomicLong idGenerator = new AtomicLong(0);
|
||||
private final OkHttpClient client;
|
||||
private EventSource eventSource;
|
||||
private final String clientVersion = "1.0";
|
||||
private final String clientName = "metis";
|
||||
private final String protocolVersion = "2024-11-05";
|
||||
|
||||
|
||||
public SseCheck(String sseUrl) {
|
||||
this.sseUrl = sseUrl;
|
||||
OkHttpClient.Builder httpClientBuilder = getHttpClientBuilder();
|
||||
this.client = httpClientBuilder.build();
|
||||
initialize();
|
||||
}
|
||||
|
||||
public void start() {
|
||||
Map<Long, CompletableFuture<JsonNode>> pendingOperations = new ConcurrentHashMap<>();
|
||||
Request request = new Request.Builder().url(sseUrl).build();
|
||||
CompletableFuture<String> initializationFinished = new CompletableFuture<>();
|
||||
McpOperationHandler operationHandler = new McpOperationHandler(pendingOperations);
|
||||
SseEventListener listener = new SseEventListener(operationHandler, true, initializationFinished);
|
||||
this.eventSource = EventSources.createFactory(client).newEventSource(request, listener);
|
||||
// wait for the SSE channel to be created, receive the POST url from the server, throw an exception if that
|
||||
// failed
|
||||
try {
|
||||
int timeout = client.callTimeoutMillis() > 0 ? client.callTimeoutMillis() : Integer.MAX_VALUE;
|
||||
String relativePostUrl = initializationFinished.get(timeout, TimeUnit.MILLISECONDS);
|
||||
postUrl = buildAbsolutePostUrl(relativePostUrl);
|
||||
log.debug("Received the server's POST URL: {}", postUrl);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private OkHttpClient.Builder getHttpClientBuilder() {
|
||||
OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
|
||||
Duration timeout = Duration.ofSeconds(60);
|
||||
httpClientBuilder.callTimeout(timeout);
|
||||
httpClientBuilder.connectTimeout(timeout);
|
||||
httpClientBuilder.readTimeout(timeout);
|
||||
httpClientBuilder.writeTimeout(timeout);
|
||||
return httpClientBuilder;
|
||||
}
|
||||
|
||||
private String buildAbsolutePostUrl(String relativePostUrl) {
|
||||
try {
|
||||
return URI.create(sseUrl).resolve(relativePostUrl).toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
start();
|
||||
long operationId = idGenerator.getAndIncrement();
|
||||
McpInitializeRequest request = new McpInitializeRequest(operationId);
|
||||
InitializeParams params = createInitializeParams();
|
||||
request.setParams(params);
|
||||
try {
|
||||
Request httpRequest = null;
|
||||
Request initializationNotification = null;
|
||||
try {
|
||||
httpRequest = createRequest(request);
|
||||
initializationNotification = createRequest(new InitializationNotification());
|
||||
} catch (JsonProcessingException e) {
|
||||
return;
|
||||
}
|
||||
final Request finalInitializationNotification = initializationNotification;
|
||||
execute(httpRequest, operationId)
|
||||
.thenCompose(originalResponse -> execute(finalInitializationNotification, null)
|
||||
.thenCompose(nullNode -> CompletableFuture.completedFuture(originalResponse)));
|
||||
// log.debug("MCP server capabilities: {}", capabilities.get("result"));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private InitializeParams createInitializeParams() {
|
||||
InitializeParams params = new InitializeParams();
|
||||
params.setProtocolVersion(protocolVersion);
|
||||
|
||||
InitializeParams.ClientInfo clientInfo = new InitializeParams.ClientInfo();
|
||||
clientInfo.setName(clientName);
|
||||
clientInfo.setVersion(clientVersion);
|
||||
params.setClientInfo(clientInfo);
|
||||
|
||||
InitializeParams.Capabilities capabilities = new InitializeParams.Capabilities();
|
||||
InitializeParams.Capabilities.Roots roots = new InitializeParams.Capabilities.Roots();
|
||||
roots.setListChanged(false); // TODO: listChanged is not supported yet
|
||||
capabilities.setRoots(roots);
|
||||
params.setCapabilities(capabilities);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
public List<ToolSpecification> listTools() throws JsonProcessingException {
|
||||
McpListToolsRequest operation = new McpListToolsRequest(idGenerator.getAndIncrement());
|
||||
Request request = createRequest(operation);
|
||||
CompletableFuture<JsonNode> resultFuture = execute(request, idGenerator.getAndIncrement());
|
||||
JsonNode result = null;
|
||||
try {
|
||||
result = resultFuture.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return ToolSpecificationHelper.toolSpecificationListFromMcpResponse(
|
||||
(ArrayNode) result.get("result").get("tools"));
|
||||
}
|
||||
|
||||
private Request createRequest(McpClientMessage message) throws JsonProcessingException {
|
||||
return new Request.Builder()
|
||||
.url(postUrl)
|
||||
.header("Content-Type", "application/json")
|
||||
.post(RequestBody.create(OBJECT_MAPPER.writeValueAsBytes(message)))
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
private CompletableFuture<JsonNode> execute(Request request, Long id) {
|
||||
CompletableFuture<JsonNode> future = new CompletableFuture<>();
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
future.completeExceptionally(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
int statusCode = response.code();
|
||||
if (!isExpectedStatusCode(statusCode)) {
|
||||
future.completeExceptionally(new RuntimeException("Unexpected status code: " + statusCode));
|
||||
}
|
||||
// For messages with null ID, we don't wait for a response in the SSE channel
|
||||
if (id == null) {
|
||||
future.complete(null);
|
||||
}
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
private boolean isExpectedStatusCode(int statusCode) {
|
||||
return statusCode >= 200 && statusCode < 300;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.metis.sseclient.event;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.metis.sseclient.handler.McpOperationHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.sse.EventSource;
|
||||
import okhttp3.sse.EventSourceListener;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@Slf4j
|
||||
public class SseEventListener extends EventSourceListener {
|
||||
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
private final boolean logEvents;
|
||||
// this will contain the POST url for sending commands to the server
|
||||
private final CompletableFuture<String> initializationFinished;
|
||||
private final McpOperationHandler messageHandler;
|
||||
|
||||
public SseEventListener(
|
||||
McpOperationHandler messageHandler, boolean logEvents, CompletableFuture initializationFinished) {
|
||||
this.messageHandler = messageHandler;
|
||||
this.logEvents = logEvents;
|
||||
this.initializationFinished = initializationFinished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(EventSource eventSource) {
|
||||
log.debug("SSE channel closed");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEvent(EventSource eventSource, String id, String type, String data) {
|
||||
if (type.equals("message")) {
|
||||
if (logEvents) {
|
||||
log.info("< {}", data);
|
||||
}
|
||||
try {
|
||||
JsonNode jsonNode = OBJECT_MAPPER.readTree(data);
|
||||
messageHandler.handle(jsonNode);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Failed to parse JSON message: {}", data, e);
|
||||
}
|
||||
} else if (type.equals("endpoint")) {
|
||||
if (initializationFinished.isDone()) {
|
||||
log.warn("Received endpoint event after initialization");
|
||||
return;
|
||||
}
|
||||
initializationFinished.complete(data);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(EventSource eventSource, Throwable t, Response response) {
|
||||
if (!initializationFinished.isDone()) {
|
||||
if (t != null) {
|
||||
initializationFinished.completeExceptionally(t);
|
||||
} else if (response != null) {
|
||||
initializationFinished.completeExceptionally(
|
||||
new RuntimeException("The server returned: " + response.message()));
|
||||
}
|
||||
}
|
||||
if (t != null && (t.getMessage() == null || !t.getMessage().contains("Socket closed"))) {
|
||||
log.warn("SSE channel failure", t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(EventSource eventSource, Response response) {
|
||||
log.debug("Connected to SSE channel at {}", response.request().url());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.metis.sseclient.handler;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Handles incoming messages from the MCP server. Transport implementations
|
||||
* should call the "handle" method on each received message. A transport also has
|
||||
* to call "startOperation" when before starting an operation that requires a response
|
||||
* to register its ID in the map of pending operations.
|
||||
*/
|
||||
public class McpOperationHandler {
|
||||
|
||||
private final Map<Long, CompletableFuture<JsonNode>> pendingOperations;
|
||||
private static final Logger log = LoggerFactory.getLogger(McpOperationHandler.class);
|
||||
|
||||
public McpOperationHandler(
|
||||
Map<Long, CompletableFuture<JsonNode>> pendingOperations) {
|
||||
this.pendingOperations = pendingOperations;
|
||||
}
|
||||
|
||||
public void handle(JsonNode message) {
|
||||
if (message.has("id")) {
|
||||
long messageId = message.get("id").asLong();
|
||||
CompletableFuture<JsonNode> op = pendingOperations.remove(messageId);
|
||||
if (op != null) {
|
||||
op.complete(message);
|
||||
} else {
|
||||
if (message.has("method")) {
|
||||
String method = message.get("method").asText();
|
||||
if (method.equals("ping")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
log.warn("Received response for unknown message id: {}", messageId);
|
||||
}
|
||||
} else if (message.has("method") && message.get("method").asText().equals("notifications/message")) {
|
||||
// this is a log message
|
||||
if (message.has("params")) {
|
||||
log.info("Received log message: {}", message);
|
||||
} else {
|
||||
log.warn("Received log message without params: {}", message);
|
||||
}
|
||||
} else {
|
||||
log.warn("Received unknown message: {}", message);
|
||||
}
|
||||
}
|
||||
|
||||
public void startOperation(Long id, CompletableFuture<JsonNode> future) {
|
||||
pendingOperations.put(id, future);
|
||||
}
|
||||
}
|
||||
399
src/main/java/com/metis/utils/LocalDateTimeUtils.java
Normal file
399
src/main/java/com/metis/utils/LocalDateTimeUtils.java
Normal file
@@ -0,0 +1,399 @@
|
||||
package com.metis.utils;
|
||||
|
||||
import java.time.*;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.time.temporal.TemporalUnit;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 地方日期时间工具类
|
||||
*
|
||||
* @author ZhangQiang
|
||||
* @date 2024/10/28
|
||||
*/
|
||||
public class LocalDateTimeUtils extends cn.hutool.core.date.LocalDateTimeUtil {
|
||||
|
||||
/**
|
||||
* 在范围内
|
||||
*
|
||||
* @param target 目标
|
||||
* @param start 开始
|
||||
* @param end 结束
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isWithinRange(LocalDateTime target, LocalDateTime start, LocalDateTime end) {
|
||||
return !target.isBefore(start) && !target.isAfter(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 现在时间在范围内
|
||||
*
|
||||
* @param start 开始
|
||||
* @param end 结束
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean nowIsWithinRange(LocalDateTime start, LocalDateTime end) {
|
||||
LocalDateTime target = LocalDateTime.now();
|
||||
return !target.isBefore(start) && !target.isAfter(end);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定时间是周几
|
||||
*
|
||||
* @param time 时间
|
||||
* @return int
|
||||
*/
|
||||
public static int week(LocalDateTime time) {
|
||||
return time.getDayOfWeek().getValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取加或减N月的第一天
|
||||
*
|
||||
* @param num 数字
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime monthFirst(int num) {
|
||||
LocalDateTime newTime = plus(LocalDateTime.now(), num, ChronoUnit.MONTHS);
|
||||
newTime = newTime.with(TemporalAdjusters.firstDayOfMonth());
|
||||
return getDayStart(newTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加或减N月的最后天
|
||||
*
|
||||
* @param num 数字
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime monthLast(int num) {
|
||||
LocalDateTime newTime = plus(LocalDateTime.now(), num, ChronoUnit.MONTHS);
|
||||
newTime = newTime.with(TemporalAdjusters.lastDayOfMonth());
|
||||
return getDayEnd(newTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取加或减N周的第一天
|
||||
*
|
||||
* @param num 数字
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime weekFirst(int num) {
|
||||
int week = week(LocalDateTime.now());
|
||||
LocalDateTime newTime = subtract(LocalDateTime.now(), week - 1, ChronoUnit.DAYS);
|
||||
newTime = plus(newTime, num * 7L, ChronoUnit.DAYS);
|
||||
return getDayStart(newTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取加或减N周的最后一天
|
||||
*
|
||||
* @param num 数字
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime weekLast(int num) {
|
||||
int week = week(LocalDateTime.now());
|
||||
LocalDateTime newTime = plus(LocalDateTime.now(), 7 - week, ChronoUnit.DAYS);
|
||||
newTime = plus(newTime, num * 7L, ChronoUnit.DAYS);
|
||||
return getDayEnd(newTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断时间 ==> t1 < t2 = true (2019-10-13 11:11:00 < 2020-11-13 13:13:00 = true)
|
||||
*
|
||||
* @param t1 t1
|
||||
* @param t2 t2
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isBefore(LocalDateTime t1, LocalDateTime t2) {
|
||||
return t1.isBefore(t2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断时间 ==> t1 > t2 = true(2019-10-13 11:11:00 > 2020-11-13 13:13:00 = false)
|
||||
*
|
||||
* @param t1 t1
|
||||
* @param t2 t2
|
||||
* @return boolean
|
||||
*/
|
||||
public static boolean isAfter(LocalDateTime t1, LocalDateTime t2) {
|
||||
return t1.isAfter(t2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Date 转 LocalDateTime
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime convertToLocalDateTime(Date date) {
|
||||
return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDateTime 转 Date
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link Date}
|
||||
*/
|
||||
public static Date convertToDate(LocalDateTime time) {
|
||||
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
|
||||
}
|
||||
|
||||
/**
|
||||
* Date转LocalDate
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link LocalDate}
|
||||
*/
|
||||
public static LocalDate convertLocalDate(Date date) {
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Date转LocalDate
|
||||
*
|
||||
* @param date 日期
|
||||
* @return {@link LocalTime}
|
||||
*/
|
||||
public static LocalTime convertLocalTime(Date date) {
|
||||
return date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指定日期的毫秒
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link Long}
|
||||
*/
|
||||
public static Long getMilliByTime(LocalDateTime time) {
|
||||
return time.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取秒数通过时间
|
||||
* 获取指定日期的秒
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link Long}
|
||||
*/
|
||||
public static Long getSecondsByTime(LocalDateTime time) {
|
||||
return time.atZone(ZoneId.systemDefault()).toInstant().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定时间的指定格式 ==> yyyy-MM-dd HH:mm:ss:SSS (HH是24小时制,而hh是12小时制, ss是秒,SSS是毫秒)
|
||||
*
|
||||
* @param time 时间
|
||||
* @param pattern 图案
|
||||
* @return {@link String}
|
||||
*/
|
||||
public static String formatTime(LocalDateTime time, String pattern) {
|
||||
return time.format(DateTimeFormatter.ofPattern(pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期加上一个数,根据field不同加不同值,field为ChronoUnit.*
|
||||
*
|
||||
* @param time 时间
|
||||
* @param number 数
|
||||
* @param field 领域
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime plus(LocalDateTime time, long number, TemporalUnit field) {
|
||||
return time.plus(number, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减去
|
||||
* 日期减去一个数,根据field不同减不同值,field参数为ChronoUnit.*
|
||||
*
|
||||
* @param time 时间
|
||||
* @param number 数
|
||||
* @param field 领域
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime subtract(LocalDateTime time, long number, TemporalUnit field) {
|
||||
return time.minus(number, field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取白天开始
|
||||
* 获取指定某一天的开始时间 00:00:00
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getDayStart(LocalDateTime time) {
|
||||
return time.withHour(0)
|
||||
.withMinute(0)
|
||||
.withSecond(0)
|
||||
.withNano(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取白天结束
|
||||
* 获取指定某一天的结束时间 23:59:59.999
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getDayEnd(LocalDateTime time) {
|
||||
return time.withHour(23)
|
||||
.withMinute(59)
|
||||
.withSecond(59)
|
||||
.withNano(999999999);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周周一
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getWeekOfFirst(LocalDateTime time) {
|
||||
return time.with(TemporalAdjusters.previous(DayOfWeek.SUNDAY)).
|
||||
plusDays(1).withHour(0).withMinute(0).withSecond(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本周周日
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getWeekOfLast(LocalDateTime time) {
|
||||
return time.with(TemporalAdjusters.next(DayOfWeek.MONDAY)).
|
||||
minusDays(1).withHour(23).withMinute(59).withSecond(59);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月第一天
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getMonthOfFirst(LocalDateTime time) {
|
||||
LocalDateTime firsthand = time.with(TemporalAdjusters.firstDayOfMonth());
|
||||
return LocalDateTime.of(firsthand.toLocalDate(), LocalTime.MIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月最后一天
|
||||
*
|
||||
* @param time 时间
|
||||
* @return {@link LocalDateTime}
|
||||
*/
|
||||
public static LocalDateTime getMonthOfLast(LocalDateTime time) {
|
||||
LocalDateTime lastDay = time.with(TemporalAdjusters.lastDayOfMonth());
|
||||
return LocalDateTime.of(lastDay.toLocalDate(), LocalTime.MAX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期相隔天数
|
||||
*
|
||||
* @param startDateInclusive 开始日期(含)
|
||||
* @param endDateExclusive 结束日期除外
|
||||
* @return int
|
||||
*/
|
||||
public static int periodDays(LocalDate startDateInclusive, LocalDate endDateExclusive) {
|
||||
return Period.between(startDateInclusive, endDateExclusive).getDays();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期相隔小时
|
||||
*
|
||||
* @param startInclusive 开始包容性
|
||||
* @param endExclusive 结束独家
|
||||
* @return long
|
||||
*/
|
||||
public static long durationHours(Temporal startInclusive, Temporal endExclusive) {
|
||||
return Duration.between(startInclusive, endExclusive).toHours();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 日期相隔分钟
|
||||
*
|
||||
* @param startInclusive 开始包容性
|
||||
* @param endExclusive 结束独家
|
||||
* @return long
|
||||
*/
|
||||
public static long durationMinutes(Temporal startInclusive, Temporal endExclusive) {
|
||||
return Duration.between(startInclusive, endExclusive).toMinutes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 持续时间秒
|
||||
*
|
||||
* @param startInclusive 开始包容
|
||||
* @param endExclusive 结束独家
|
||||
* @return long
|
||||
*/
|
||||
public static long durationSeconds(Temporal startInclusive, Temporal endExclusive) {
|
||||
return Duration.between(startInclusive, endExclusive).toSeconds();
|
||||
}
|
||||
|
||||
/**
|
||||
* 日期相隔毫秒数
|
||||
*
|
||||
* @param startInclusive 开始包容性
|
||||
* @param endExclusive 结束独家
|
||||
* @return long
|
||||
*/
|
||||
public static long durationMillis(Temporal startInclusive, Temporal endExclusive) {
|
||||
return Duration.between(startInclusive, endExclusive).toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否当天
|
||||
*/
|
||||
public static boolean isToday(LocalDate date) {
|
||||
return LocalDate.now().equals(date);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断两个时间是否相差指定时间(精确到毫秒)
|
||||
*
|
||||
* @param time1 第一个时间点
|
||||
* @param time2 第二个时间点
|
||||
* @param milliseconds 指定时间差(毫秒)
|
||||
* @return true 如果两个时间相差超过指定毫秒数,否则 false
|
||||
*/
|
||||
public static boolean isTimeDifferenceExceeds(LocalDateTime time1, LocalDateTime time2, long milliseconds) {
|
||||
if (time1 == null || time2 == null) {
|
||||
throw new IllegalArgumentException("Time arguments cannot be null");
|
||||
}
|
||||
|
||||
Duration duration = Duration.between(time1, time2);
|
||||
return Math.abs(duration.toMillis()) > milliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定时间是否与当前时间相差超过 24 小时(精确到毫秒)
|
||||
*
|
||||
* @param targetTime 指定的 LocalDateTime 时间
|
||||
* @return true 如果相差 24 小时以上,否则 false
|
||||
*/
|
||||
public static boolean isMoreThan24HoursApart(LocalDateTime targetTime) {
|
||||
if (targetTime == null) {
|
||||
throw new IllegalArgumentException("Target time cannot be null");
|
||||
}
|
||||
// 比较时间差的绝对值是否大于 24 小时
|
||||
return isTimeDifferenceExceeds(LocalDateTime.now(), targetTime, 86400000L);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
66
src/main/java/com/metis/utils/PageConditionUtil.java
Normal file
66
src/main/java/com/metis/utils/PageConditionUtil.java
Normal file
@@ -0,0 +1,66 @@
|
||||
package com.metis.utils;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.metis.result.page.TableDataInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2023-05-25
|
||||
*/
|
||||
public class PageConditionUtil {
|
||||
|
||||
|
||||
/**
|
||||
* 转换为TableDataInfo对象
|
||||
*
|
||||
* @param page 源对象
|
||||
* @param map 转换方法
|
||||
* @param <T> 转换后的对象类型
|
||||
* @param <R> 需要转换的对象类型
|
||||
* @return 转换后的对象
|
||||
*/
|
||||
public static <T, R> TableDataInfo<T> convertDataTable(IPage<R> page, Function<R, T> map) {
|
||||
List<T> convertList = page.getRecords().stream().map(map).collect(Collectors.toList());
|
||||
return convertDataTable(convertList, page.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为TableDataInfo对象
|
||||
*
|
||||
* @param page 源对象
|
||||
* @return 转换后的对象
|
||||
*/
|
||||
public static <T> TableDataInfo<T> convertDataTable(IPage<T> page) {
|
||||
return convertDataTable(page.getRecords(), page.getTotal());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为TableDataInfo对象
|
||||
*
|
||||
* @param list
|
||||
* @param count
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> TableDataInfo<T> convertDataTable(List<T> list, Long count) {
|
||||
if (null == list) {
|
||||
return new TableDataInfo<>(new ArrayList<>(), 0);
|
||||
}
|
||||
TableDataInfo<T> tableDataInfo = new TableDataInfo<>();
|
||||
tableDataInfo.setRows(list);
|
||||
tableDataInfo.setTotal(count);
|
||||
return tableDataInfo;
|
||||
}
|
||||
|
||||
public static <T> IPage<T> getPage() {
|
||||
PageInfo pageInfo = TableSupport.getPageInfo();
|
||||
return new Page<>(pageInfo.getPageNum(), pageInfo.getPageSize());
|
||||
}
|
||||
}
|
||||
44
src/main/java/com/metis/utils/PageInfo.java
Normal file
44
src/main/java/com/metis/utils/PageInfo.java
Normal file
@@ -0,0 +1,44 @@
|
||||
package com.metis.utils;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Clay
|
||||
* @date 2022/10/30
|
||||
*/
|
||||
@Data
|
||||
public class PageInfo {
|
||||
|
||||
/**
|
||||
* 当前记录起始索引
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 每页显示记录数
|
||||
*/
|
||||
private Integer pageSize;
|
||||
|
||||
/**
|
||||
* 排序列
|
||||
*/
|
||||
private String orderByColumn;
|
||||
|
||||
/**
|
||||
* 排序的方向desc或者asc
|
||||
*/
|
||||
private String isAsc = "asc";
|
||||
|
||||
/**
|
||||
* 分页参数合理化
|
||||
*/
|
||||
private Boolean reasonable = true;
|
||||
|
||||
public String getOrderBy() {
|
||||
if (StrUtil.isEmpty(orderByColumn)) {
|
||||
return "";
|
||||
}
|
||||
return StrUtil.toUnderlineCase(orderByColumn) + " " + isAsc;
|
||||
}
|
||||
}
|
||||
79
src/main/java/com/metis/utils/TableSupport.java
Normal file
79
src/main/java/com/metis/utils/TableSupport.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package com.metis.utils;
|
||||
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
|
||||
/**
|
||||
* 表格数据处理
|
||||
*
|
||||
* @author Clay
|
||||
* @date 2022/10/30
|
||||
*/
|
||||
public class TableSupport {
|
||||
|
||||
/**
|
||||
* 当前记录起始索引
|
||||
*/
|
||||
public static final String PAGE_NUM = "pageNum";
|
||||
|
||||
/**
|
||||
* 每页显示记录数
|
||||
*/
|
||||
public static final String PAGE_SIZE = "pageSize";
|
||||
|
||||
/**
|
||||
* 排序列
|
||||
*/
|
||||
public static final String ORDER_BY_COLUMN = "orderByColumn";
|
||||
|
||||
/**
|
||||
* 排序的方向 "desc" 或者 "asc".
|
||||
*/
|
||||
public static final String IS_ASC = "isAsc";
|
||||
|
||||
/**
|
||||
* 分页参数合理化
|
||||
*/
|
||||
public static final String REASONABLE = "reasonable";
|
||||
|
||||
/**
|
||||
* 封装分页对象
|
||||
*/
|
||||
public static PageInfo getPageInfo() {
|
||||
PageInfo pageInfo = new PageInfo();
|
||||
pageInfo.setPageNum(Convert.toInt(getParameter(PAGE_NUM), 1));
|
||||
pageInfo.setPageSize(Convert.toInt(getParameter(PAGE_SIZE), 10));
|
||||
pageInfo.setOrderByColumn(getParameter(ORDER_BY_COLUMN));
|
||||
pageInfo.setIsAsc(getParameter(IS_ASC));
|
||||
pageInfo.setReasonable(getParameterToBool(REASONABLE));
|
||||
return pageInfo;
|
||||
}
|
||||
|
||||
public static PageInfo buildPageRequest() {
|
||||
return getPageInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Boolean参数
|
||||
*/
|
||||
public static Boolean getParameterToBool(String name) {
|
||||
return Convert.toBool(getRequest().getParameter(name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取String参数
|
||||
*/
|
||||
public static String getParameter(String name) {
|
||||
return Convert.toStr(getRequest().getParameter(name));
|
||||
}
|
||||
|
||||
|
||||
public static HttpServletRequest getRequest() {
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
return attributes.getRequest();
|
||||
}
|
||||
|
||||
}
|
||||
43
src/main/java/com/metis/utils/TransactionalUtils.java
Normal file
43
src/main/java/com/metis/utils/TransactionalUtils.java
Normal file
@@ -0,0 +1,43 @@
|
||||
package com.metis.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
@Slf4j
|
||||
public class TransactionalUtils {
|
||||
|
||||
/**
|
||||
* 提交后执行
|
||||
*
|
||||
* @param runnable 可运行
|
||||
*/
|
||||
public static void afterCommitExecute(Runnable runnable) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
log.info("事务提交后执行业务");
|
||||
runnable.run();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 事务回滚时执行
|
||||
*
|
||||
* @param runnable 可运行
|
||||
*/
|
||||
public static void afterRollbackExecute(Runnable runnable) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
// 只在事务状态为 STATUS_ROLLED_BACK 时执行
|
||||
if (status == STATUS_ROLLED_BACK) {
|
||||
log.info("事务回滚后执行业务");
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
20
src/main/resources/application-dev.yml
Normal file
20
src/main/resources/application-dev.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
# Spring配置
|
||||
spring:
|
||||
datasource:
|
||||
url: jdbc:mysql://frp.feashow.cn:39306/metis?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: yyz@2024
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
data:
|
||||
redis:
|
||||
host: frp.feashow.cn
|
||||
port: 39379
|
||||
password: yyz@2024
|
||||
database: 13
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
min-idle: 0
|
||||
max-idle: 8
|
||||
max-active: 8
|
||||
max-wait: -1ms
|
||||
7
src/main/resources/application.yml
Normal file
7
src/main/resources/application.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: metis
|
||||
45
src/main/resources/script/sse.html
Normal file
45
src/main/resources/script/sse.html
Normal file
@@ -0,0 +1,45 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SSE Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SSE 连接示例</h1>
|
||||
<div id="messages"></div>
|
||||
|
||||
<script> // 创建一个新的EventSource对象,连接到SSE服务
|
||||
const eventSource = new EventSource('http://localhost:8081/sse');
|
||||
|
||||
// 监听服务器发送的消息事件
|
||||
eventSource.onmessage = function(event) {
|
||||
const message = document.createElement('p');
|
||||
message.textContent = '收到消息: ' + event.data;
|
||||
document.getElementById('messages').appendChild(message);
|
||||
};
|
||||
|
||||
// 监听自定义事件(如果有)
|
||||
eventSource.addEventListener('customEvent', function(event) {
|
||||
const message = document.createElement('p');
|
||||
message.textContent = '收到自定义事件: ' + event.data;
|
||||
document.getElementById('messages').appendChild(message);
|
||||
});
|
||||
|
||||
// 监听连接打开事件
|
||||
eventSource.onopen = function(event) {
|
||||
const message = document.createElement('p');
|
||||
message.textContent = 'SSE连接已建立';
|
||||
document.getElementById('messages').appendChild(message);
|
||||
};
|
||||
|
||||
// 监听错误事件
|
||||
eventSource.onerror = function(event) {
|
||||
const message = document.createElement('p');
|
||||
message.textContent = 'SSE连接发生错误';
|
||||
document.getElementById('messages').appendChild(message);
|
||||
// 在这里处理错误,例如尝试重新连接
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
8
src/test/java/AnsMsgHandler.java
Normal file
8
src/test/java/AnsMsgHandler.java
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public interface AnsMsgHandler {
|
||||
|
||||
void actMsg(InputStream is, String line);
|
||||
|
||||
}
|
||||
16
src/test/java/SSeTest.java
Normal file
16
src/test/java/SSeTest.java
Normal file
@@ -0,0 +1,16 @@
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.metis.sseclient.check.SseCheck;
|
||||
import dev.langchain4j.agent.tool.ToolSpecification;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class SSeTest {
|
||||
public static void main(String[] args) throws JsonProcessingException {
|
||||
SseCheck sseCheck = new SseCheck("http://localhost:8081/sse");
|
||||
List<ToolSpecification> listTools = sseCheck.listTools();
|
||||
System.out.println(listTools);
|
||||
}
|
||||
}
|
||||
77
src/test/java/SseClient.java
Normal file
77
src/test/java/SseClient.java
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hyd
|
||||
*
|
||||
*/
|
||||
public class SseClient {
|
||||
|
||||
/**
|
||||
* 获取SSE输入流。
|
||||
*
|
||||
* @param urlPath
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static InputStream getSseInputStream(String urlPath) throws IOException {
|
||||
URL url = new URL(urlPath);
|
||||
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
|
||||
// 这儿根据自己的情况选择get或post
|
||||
urlConnection.setRequestMethod("GET");
|
||||
urlConnection.setDoOutput(true);
|
||||
urlConnection.setDoInput(true);
|
||||
urlConnection.setUseCaches(false);
|
||||
urlConnection.setRequestProperty("Connection", "Keep-Alive");
|
||||
urlConnection.setRequestProperty("Charset", "UTF-8");
|
||||
//读取过期时间(很重要,建议加上)
|
||||
urlConnection.setReadTimeout(60 * 1000);
|
||||
// text/plain模式
|
||||
urlConnection.setRequestProperty("Content-Type", "text/plain; charset=UTF-8");
|
||||
InputStream inputStream = urlConnection.getInputStream();
|
||||
InputStream is = new BufferedInputStream(inputStream);
|
||||
return is;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取数据。
|
||||
*
|
||||
* @param is
|
||||
* @param ansMsgHandler
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void readStream(InputStream is, AnsMsgHandler ansMsgHandler) throws IOException {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||
String line = "";
|
||||
while ((line = reader.readLine()) != null) {
|
||||
// 处理数据接口
|
||||
ansMsgHandler.actMsg(is, line);
|
||||
}
|
||||
// 当服务器端主动关闭的时候,客户端无法获取到信号。现在还不清楚原因。所以无法执行的此处。
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
throw new IOException("关闭数据流!");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
String urlPath = "http://localhost:8081/sse";
|
||||
InputStream inputStream = getSseInputStream(urlPath);
|
||||
readStream(inputStream, new AnsMsgHandler() {
|
||||
|
||||
public void actMsg(InputStream is, String line) {
|
||||
System.out.println(line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user