zhangyeguang
1 month ago
69 changed files with 3734 additions and 140 deletions
@ -0,0 +1,199 @@ |
|||||
|
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
<parent> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-parent</artifactId> |
||||
|
<version>2.2.6.RELEASE</version> |
||||
|
<relativePath/> <!-- lookup parent from repository --> |
||||
|
</parent> |
||||
|
<groupId>com.jiagutech</groupId> |
||||
|
<artifactId>siliqua_recognition</artifactId> |
||||
|
<version>0.0.1-SNAPSHOT</version> |
||||
|
<name>siliqua_recognition</name> |
||||
|
<description>角果识别</description> |
||||
|
|
||||
|
|
||||
|
<properties> |
||||
|
<java.version>8</java.version> |
||||
|
<maven.compiler.source>8</maven.compiler.source> |
||||
|
<maven.compiler.target>8</maven.compiler.target> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
<mapstruct.version>1.6.1</mapstruct.version> |
||||
|
<lombok.version>1.18.30</lombok.version> |
||||
|
<hutool.version>5.8.22</hutool.version> |
||||
|
|
||||
|
</properties> |
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-redis</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-web</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- <dependency>--> |
||||
|
<!-- <groupId>org.springframework.boot</groupId>--> |
||||
|
<!-- <artifactId>spring-boot-devtools</artifactId>--> |
||||
|
<!-- <scope>runtime</scope>--> |
||||
|
<!-- <optional>true</optional>--> |
||||
|
<!-- </dependency>--> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-data-jdbc</artifactId> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.mybatis.spring.boot</groupId> |
||||
|
<artifactId>mybatis-spring-boot-starter</artifactId> |
||||
|
<version>2.3.2</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>com.baomidou</groupId> |
||||
|
<artifactId>mybatis-plus-boot-starter</artifactId> |
||||
|
<version>3.5.0</version> |
||||
|
<exclusions> |
||||
|
<exclusion> |
||||
|
<groupId>org.mybatis</groupId> |
||||
|
<artifactId>mybatis</artifactId> |
||||
|
</exclusion> |
||||
|
</exclusions> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.mysql</groupId> |
||||
|
<artifactId>mysql-connector-j</artifactId> |
||||
|
<version>8.2.0</version> |
||||
|
<scope>runtime</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-validation</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
<optional>true</optional> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.mapstruct</groupId> |
||||
|
<artifactId>mapstruct</artifactId> |
||||
|
<version>${mapstruct.version}</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.alibaba.fastjson2</groupId> |
||||
|
<artifactId>fastjson2</artifactId> |
||||
|
<version>2.0.43</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.apache.commons</groupId> |
||||
|
<artifactId>commons-lang3</artifactId> |
||||
|
<version>3.12.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.google.code.gson</groupId> |
||||
|
<artifactId>gson</artifactId> |
||||
|
<version>2.10.1</version> |
||||
|
</dependency> |
||||
|
|
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>com.alibaba</groupId> |
||||
|
<artifactId>easyexcel</artifactId> |
||||
|
<version>4.0.2</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>commons-io</groupId> |
||||
|
<artifactId>commons-io</artifactId> |
||||
|
<version>2.16.1</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>cn.dev33</groupId> |
||||
|
<artifactId>sa-token-spring-boot-starter</artifactId> |
||||
|
<version>1.37.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>cn.dev33</groupId> |
||||
|
<artifactId>sa-token-jwt</artifactId> |
||||
|
<version>1.37.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>cn.dev33</groupId> |
||||
|
<artifactId>sa-token-redis-jackson</artifactId> |
||||
|
<version>1.37.0</version> |
||||
|
</dependency> |
||||
|
|
||||
|
|
||||
|
<!-- hutool 的依赖配置--> |
||||
|
<dependency> |
||||
|
<groupId>cn.hutool</groupId> |
||||
|
<artifactId>hutool-bom</artifactId> |
||||
|
<version>${hutool.version}</version> |
||||
|
<type>pom</type> |
||||
|
<scope>import</scope> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.huaweicloud</groupId> |
||||
|
<artifactId>esdk-obs-java</artifactId> |
||||
|
<version>3.20.6.1</version> |
||||
|
<scope>compile</scope> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>org.apache.logging.log4j</groupId> |
||||
|
<artifactId>log4j-core</artifactId> |
||||
|
<version>2.23.1</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.apache.logging.log4j</groupId> |
||||
|
<artifactId>log4j-api</artifactId> |
||||
|
<version>2.23.1</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger2</artifactId> |
||||
|
<version>2.7.0</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<dependency> |
||||
|
<groupId>io.springfox</groupId> |
||||
|
<artifactId>springfox-swagger-ui</artifactId> |
||||
|
<version>2.7.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>com.github.xiaoymin</groupId> |
||||
|
<artifactId>knife4j-spring-boot-starter</artifactId> |
||||
|
<version>2.0.8</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-starter-mail</artifactId> |
||||
|
</dependency> |
||||
|
|
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.springframework.boot</groupId> |
||||
|
<artifactId>spring-boot-maven-plugin</artifactId> |
||||
|
<configuration> |
||||
|
<excludes> |
||||
|
<exclude> |
||||
|
<groupId>org.projectlombok</groupId> |
||||
|
<artifactId>lombok</artifactId> |
||||
|
</exclude> |
||||
|
</excludes> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
|
||||
|
</plugins> |
||||
|
</build> |
||||
|
|
||||
|
</project> |
@ -0,0 +1,15 @@ |
|||||
|
package com.jiagutech; |
||||
|
|
||||
|
import org.mybatis.spring.annotation.MapperScan; |
||||
|
import org.springframework.boot.SpringApplication; |
||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
import org.springframework.scheduling.annotation.EnableAsync; |
||||
|
|
||||
|
@MapperScan("com.jiagutech.mapper") |
||||
|
@EnableAsync |
||||
|
@SpringBootApplication |
||||
|
public class RecognitionRun { |
||||
|
public static void main(String[] args) { |
||||
|
SpringApplication.run(RecognitionRun.class, args); |
||||
|
} |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
package com.jiagutech.client; |
||||
|
|
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
import com.alibaba.fastjson2.JSONObject; |
||||
|
import com.jiagutech.dto.ModelPredictResponse; |
||||
|
import com.jiagutech.dto.RecordModel; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.core.io.FileSystemResource; |
||||
|
import org.springframework.http.*; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.util.CollectionUtils; |
||||
|
import org.springframework.util.LinkedMultiValueMap; |
||||
|
import org.springframework.util.MultiValueMap; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class RecognitionModelClient { |
||||
|
|
||||
|
@Value("${recognition.model.url}") |
||||
|
private String modelUrl; |
||||
|
|
||||
|
private final RestTemplate restTemplate; |
||||
|
|
||||
|
|
||||
|
public List<ModelPredictResponse> predict(File file, String modeName, float lou, float conf) { |
||||
|
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); |
||||
|
FileSystemResource fileResource = new FileSystemResource(file); |
||||
|
if (!fileResource.exists()) { |
||||
|
throw new IllegalArgumentException("文件不存在: " + file.getAbsolutePath()); |
||||
|
} |
||||
|
body.add("image", fileResource); |
||||
|
body.add("model_name", modeName); |
||||
|
body.add("lou", lou); |
||||
|
body.add("conf", conf); |
||||
|
|
||||
|
// 设置请求头
|
||||
|
HttpHeaders headers = new HttpHeaders(); |
||||
|
headers.setContentType(MediaType.parseMediaType("multipart/form-data; charset=UTF-8")); |
||||
|
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers); |
||||
|
ResponseEntity<String> result = restTemplate.exchange(modelUrl + "/api/predict", HttpMethod.POST, requestEntity, String.class); |
||||
|
if (result.getStatusCode() == HttpStatus.OK) { |
||||
|
return JSON.parseArray(result.getBody(), ModelPredictResponse.class); |
||||
|
} |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
|
||||
|
public RecordModel models() { |
||||
|
return restTemplate.getForObject(modelUrl + "/api/models", RecordModel.class); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,93 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
/** |
||||
|
* 返回状态码 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
*/ |
||||
|
public interface HttpStatus { |
||||
|
/** |
||||
|
* 操作成功 |
||||
|
*/ |
||||
|
int SUCCESS = 200; |
||||
|
|
||||
|
/** |
||||
|
* 对象创建成功 |
||||
|
*/ |
||||
|
int CREATED = 201; |
||||
|
|
||||
|
/** |
||||
|
* 请求已经被接受 |
||||
|
*/ |
||||
|
int ACCEPTED = 202; |
||||
|
|
||||
|
/** |
||||
|
* 操作已经执行成功,但是没有返回数据 |
||||
|
*/ |
||||
|
int NO_CONTENT = 204; |
||||
|
|
||||
|
/** |
||||
|
* 资源已被移除 |
||||
|
*/ |
||||
|
int MOVED_PERM = 301; |
||||
|
|
||||
|
/** |
||||
|
* 重定向 |
||||
|
*/ |
||||
|
int SEE_OTHER = 303; |
||||
|
|
||||
|
/** |
||||
|
* 资源没有被修改 |
||||
|
*/ |
||||
|
int NOT_MODIFIED = 304; |
||||
|
|
||||
|
/** |
||||
|
* 参数列表错误(缺少,格式不匹配) |
||||
|
*/ |
||||
|
int BAD_REQUEST = 400; |
||||
|
|
||||
|
/** |
||||
|
* 未授权 |
||||
|
*/ |
||||
|
int UNAUTHORIZED = 401; |
||||
|
|
||||
|
/** |
||||
|
* 访问受限,授权过期 |
||||
|
*/ |
||||
|
int FORBIDDEN = 403; |
||||
|
|
||||
|
/** |
||||
|
* 资源,服务未找到 |
||||
|
*/ |
||||
|
int NOT_FOUND = 404; |
||||
|
|
||||
|
/** |
||||
|
* 不允许的http方法 |
||||
|
*/ |
||||
|
int BAD_METHOD = 405; |
||||
|
|
||||
|
/** |
||||
|
* 资源冲突,或者资源被锁 |
||||
|
*/ |
||||
|
int CONFLICT = 409; |
||||
|
|
||||
|
/** |
||||
|
* 不支持的数据,媒体类型 |
||||
|
*/ |
||||
|
int UNSUPPORTED_TYPE = 415; |
||||
|
|
||||
|
/** |
||||
|
* 系统内部错误 |
||||
|
*/ |
||||
|
int ERROR = 500; |
||||
|
|
||||
|
/** |
||||
|
* 接口未实现 |
||||
|
*/ |
||||
|
int NOT_IMPLEMENTED = 501; |
||||
|
|
||||
|
/** |
||||
|
* 系统警告消息 |
||||
|
*/ |
||||
|
int WARN = 601; |
||||
|
} |
@ -0,0 +1,50 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.context.annotation.Profile; |
||||
|
import springfox.documentation.builders.ApiInfoBuilder; |
||||
|
import springfox.documentation.builders.PathSelectors; |
||||
|
import springfox.documentation.builders.RequestHandlerSelectors; |
||||
|
import springfox.documentation.service.ApiInfo; |
||||
|
import springfox.documentation.service.Contact; |
||||
|
import springfox.documentation.spi.DocumentationType; |
||||
|
import springfox.documentation.spring.web.plugins.Docket; |
||||
|
import springfox.documentation.swagger2.annotations.EnableSwagger2; |
||||
|
|
||||
|
/** |
||||
|
* 自定义 Knife4j 接口文档的配置 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
@EnableSwagger2 |
||||
|
@EnableKnife4j |
||||
|
public class Knife4jConfig { |
||||
|
|
||||
|
@Bean(value = "defaultApi2") |
||||
|
public Docket defaultApi2() { |
||||
|
return new Docket(DocumentationType.SWAGGER_2) |
||||
|
.apiInfo(apiInfo()) |
||||
|
.select() |
||||
|
// 项目controller的位置
|
||||
|
.apis(RequestHandlerSelectors.basePackage("com.jiagutech.controller")) |
||||
|
.paths(PathSelectors.any()) |
||||
|
.build(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* api 信息 |
||||
|
* |
||||
|
* @return |
||||
|
*/ |
||||
|
private ApiInfo apiInfo() { |
||||
|
return new ApiInfoBuilder() |
||||
|
.title("角果识别") |
||||
|
.description("角果识别接口文档") |
||||
|
.termsOfServiceUrl("http://recognition.jiagutech.com") |
||||
|
.contact(new Contact("yeguangzhang", "http://recognition.jiagutech.com", "yeguangzhang@126.com")) |
||||
|
.version("1.0.0") |
||||
|
.build(); |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,23 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.DbType; |
||||
|
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; |
||||
|
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
@Configuration |
||||
|
public class MybatisPlusConfig { |
||||
|
|
||||
|
/** |
||||
|
* 添加分页插件 |
||||
|
*/ |
||||
|
@Bean |
||||
|
public MybatisPlusInterceptor mybatisPlusInterceptor() { |
||||
|
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); |
||||
|
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
|
||||
|
return interceptor; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
|
||||
|
import com.obs.services.ObsClient; |
||||
|
import com.obs.services.ObsConfiguration; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.context.annotation.Scope; |
||||
|
|
||||
|
@Data |
||||
|
@Configuration |
||||
|
@NoArgsConstructor |
||||
|
@ConfigurationProperties(prefix = "huawei.obs") |
||||
|
public class ObsConfig { |
||||
|
|
||||
|
|
||||
|
private String endPoint; |
||||
|
private String ak; |
||||
|
private String sk; |
||||
|
|
||||
|
private String bucketName; |
||||
|
private String pathPrefix; |
||||
|
|
||||
|
@Bean |
||||
|
@Scope("singleton") |
||||
|
public ObsClient obsClient() { |
||||
|
// ObsConfiguration config = new ObsConfiguration();
|
||||
|
// config.setConnectionTimeout(3000);
|
||||
|
// config.setSocketTimeout(5000);
|
||||
|
// config.setMaxErrorRetry(2);
|
||||
|
// config.setEndPoint(endPoint);
|
||||
|
|
||||
|
ObsClient obsClient = new ObsClient(ak, sk, endPoint); |
||||
|
return obsClient; |
||||
|
} |
||||
|
} |
@ -0,0 +1,41 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory; |
||||
|
import org.springframework.http.converter.StringHttpMessageConverter; |
||||
|
import org.springframework.web.client.RestTemplate; |
||||
|
|
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.Collections; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* RestTemplate工具类,主要用来提供RestTemplate对象 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
public class RestTemplateConfig { |
||||
|
|
||||
|
|
||||
|
|
||||
|
@Value("${http.template.connect-timeout}") |
||||
|
private int connectionTimeout; |
||||
|
@Value("${http.template.read-timeout}") |
||||
|
private int readTimeout; |
||||
|
|
||||
|
/** |
||||
|
* 创建RestTemplate对象 |
||||
|
*/ |
||||
|
@Bean |
||||
|
public RestTemplate restTemplate() { |
||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); |
||||
|
factory.setConnectTimeout(connectionTimeout); // 设置连接超时时间(毫秒)
|
||||
|
factory.setReadTimeout(readTimeout); // 设置读取超时时间(毫秒)
|
||||
|
RestTemplate restTemplate = new RestTemplate(factory); |
||||
|
restTemplate.setInterceptors(Collections.singletonList(new RestTemplateInterceptor())); |
||||
|
restTemplate.getMessageConverters().set(1,new StringHttpMessageConverter(StandardCharsets.UTF_8)); // 支持中文编码
|
||||
|
return restTemplate; |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,33 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.HttpRequest; |
||||
|
import org.springframework.http.client.ClientHttpRequestExecution; |
||||
|
import org.springframework.http.client.ClientHttpRequestInterceptor; |
||||
|
import org.springframework.http.client.ClientHttpResponse; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class RestTemplateInterceptor implements ClientHttpRequestInterceptor { |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { |
||||
|
|
||||
|
// 打印出请求的详细信息
|
||||
|
log.info("URI: {}", request.getURI()); |
||||
|
|
||||
|
// 执行请求并获取响应
|
||||
|
ClientHttpResponse response = execution.execute(request, body); |
||||
|
|
||||
|
// 打印出响应的详细信息
|
||||
|
log.info("Response Status: {}", response.getStatusCode()); |
||||
|
log.info("Response Headers: {}", response.getHeaders()); |
||||
|
response.getBody(); // 读取响应体,确保内容被完全读取,这样连接才能被关闭
|
||||
|
|
||||
|
return response; |
||||
|
|
||||
|
} |
||||
|
} |
@ -0,0 +1,143 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
/** |
||||
|
* 用户常量信息 |
||||
|
* |
||||
|
* @author ruoyi |
||||
|
*/ |
||||
|
public interface UserConstants { |
||||
|
|
||||
|
/** |
||||
|
* 平台内系统用户的唯一标志 |
||||
|
*/ |
||||
|
String SYS_SESSION = "CURRENT_USER"; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 正常状态 |
||||
|
*/ |
||||
|
String NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 异常状态 |
||||
|
*/ |
||||
|
String EXCEPTION = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 用户正常状态 |
||||
|
*/ |
||||
|
String USER_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 用户封禁状态 |
||||
|
*/ |
||||
|
String USER_DISABLE = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 角色正常状态 |
||||
|
*/ |
||||
|
String ROLE_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 角色封禁状态 |
||||
|
*/ |
||||
|
String ROLE_DISABLE = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 部门正常状态 |
||||
|
*/ |
||||
|
String DEPT_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 部门停用状态 |
||||
|
*/ |
||||
|
String DEPT_DISABLE = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 岗位正常状态 |
||||
|
*/ |
||||
|
String POST_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 岗位停用状态 |
||||
|
*/ |
||||
|
String POST_DISABLE = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 字典正常状态 |
||||
|
*/ |
||||
|
String DICT_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 是否为系统默认(是) |
||||
|
*/ |
||||
|
String YES = "Y"; |
||||
|
|
||||
|
/** |
||||
|
* 是否菜单外链(是) |
||||
|
*/ |
||||
|
String YES_FRAME = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 是否菜单外链(否) |
||||
|
*/ |
||||
|
String NO_FRAME = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 菜单正常状态 |
||||
|
*/ |
||||
|
String MENU_NORMAL = "0"; |
||||
|
|
||||
|
/** |
||||
|
* 菜单停用状态 |
||||
|
*/ |
||||
|
String MENU_DISABLE = "1"; |
||||
|
|
||||
|
/** |
||||
|
* 菜单类型(目录) |
||||
|
*/ |
||||
|
String TYPE_DIR = "M"; |
||||
|
|
||||
|
/** |
||||
|
* 菜单类型(菜单) |
||||
|
*/ |
||||
|
String TYPE_MENU = "C"; |
||||
|
|
||||
|
/** |
||||
|
* 菜单类型(按钮) |
||||
|
*/ |
||||
|
String TYPE_BUTTON = "F"; |
||||
|
|
||||
|
/** |
||||
|
* Layout组件标识 |
||||
|
*/ |
||||
|
String LAYOUT = "Layout"; |
||||
|
|
||||
|
/** |
||||
|
* ParentView组件标识 |
||||
|
*/ |
||||
|
String PARENT_VIEW = "ParentView"; |
||||
|
|
||||
|
/** |
||||
|
* InnerLink组件标识 |
||||
|
*/ |
||||
|
String INNER_LINK = "InnerLink"; |
||||
|
|
||||
|
/** |
||||
|
* 用户名长度限制 |
||||
|
*/ |
||||
|
int USERNAME_MIN_LENGTH = 2; |
||||
|
int USERNAME_MAX_LENGTH = 20; |
||||
|
|
||||
|
/** |
||||
|
* 密码长度限制 |
||||
|
*/ |
||||
|
int PASSWORD_MIN_LENGTH = 5; |
||||
|
int PASSWORD_MAX_LENGTH = 20; |
||||
|
|
||||
|
/** |
||||
|
* 超级管理员ID |
||||
|
*/ |
||||
|
Long SUPER_ADMIN_ID = 1L; |
||||
|
|
||||
|
} |
@ -0,0 +1,32 @@ |
|||||
|
package com.jiagutech.common; |
||||
|
|
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.cors.CorsConfiguration; |
||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource; |
||||
|
import org.springframework.web.filter.CorsFilter; |
||||
|
|
||||
|
@Configuration |
||||
|
public class WebCrosConfig { |
||||
|
|
||||
|
|
||||
|
@Bean |
||||
|
public CorsFilter corsFilter() { |
||||
|
CorsConfiguration config = new CorsConfiguration(); |
||||
|
config.addAllowedOrigin("*"); |
||||
|
config.addAllowedMethod("GET"); |
||||
|
config.addAllowedMethod("POST"); |
||||
|
config.addAllowedMethod("PUT"); |
||||
|
config.addAllowedMethod("DELETE"); |
||||
|
config.addAllowedMethod("OPTIONS"); |
||||
|
config.addAllowedHeader("*"); |
||||
|
config.setAllowCredentials(false); |
||||
|
config.setMaxAge(3600L); |
||||
|
|
||||
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); |
||||
|
source.registerCorsConfiguration("/**", config); |
||||
|
|
||||
|
return new CorsFilter(source); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,63 @@ |
|||||
|
package com.jiagutech.common.exception; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
|
||||
|
@Getter |
||||
|
public enum BizCode implements CodeInterface { |
||||
|
General_Success(200, "接口调用成功"), |
||||
|
ServerError(10001, "服务器异常"), |
||||
|
General_Failure(10004, "接口调用失败"), |
||||
|
General_DBError(10005, "DB错误"), |
||||
|
General_ParameterInvalid(12001, "参数校验失败"), |
||||
|
/** |
||||
|
* 通用类 |
||||
|
*/ |
||||
|
USER_UNREGISTERED(12001, "用户未注册"), |
||||
|
NOT_FOUND(12002, "数据不存在"), |
||||
|
LOGIN_TYPE_ERROR(12005, "登录类型错误"), |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 文件服务 |
||||
|
*/ |
||||
|
FILE_DOWNLOAD_ERROR(90001, "文件下载错误"), |
||||
|
|
||||
|
FILE_UPLOAD_ERROR(90002, "文件上传失败"), |
||||
|
|
||||
|
|
||||
|
|
||||
|
USER_NOT_FOUND(40004, "用户不存在"), |
||||
|
|
||||
|
CAPTCHA_ERROR(40003, "验证码错误"), |
||||
|
|
||||
|
PASSWORD_ERROR(40002, "密码错误"), |
||||
|
|
||||
|
TOKEN_TIMEOUT_ERROR(40006, "token已过期"), |
||||
|
|
||||
|
USER_STOP_FOUND(40008, "用户被禁用"), |
||||
|
USER_STATUS_ERROR(40009,"用户未审核通过"), |
||||
|
|
||||
|
USER_NOT_LOGIN(40011, "用户未登录,请登录后再操作"), |
||||
|
|
||||
|
ACCESS_TOKEN_INVALID(40040, "用户token无效,请重新登录"), |
||||
|
|
||||
|
ACCESS_NOT_ALLOWABLE(50001, "服务不允许直接访问"), |
||||
|
|
||||
|
PERMISSION_NOT_FOUND(40001,"该用户无操作权限"), |
||||
|
|
||||
|
USER_PHONE_EXIST(40012, "用户手机号已存在"), |
||||
|
|
||||
|
|
||||
|
; |
||||
|
|
||||
|
|
||||
|
private final Integer code; |
||||
|
|
||||
|
private final String msg; |
||||
|
|
||||
|
BizCode(Integer code, String msg) { |
||||
|
this.code = code; |
||||
|
this.msg = msg; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
package com.jiagutech.common.exception; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class BusinessException extends RuntimeException { |
||||
|
|
||||
|
private int code; |
||||
|
|
||||
|
private String message; |
||||
|
|
||||
|
public BusinessException( int code,String message) { |
||||
|
super(message); |
||||
|
this.code = code; |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
public BusinessException(String message, Throwable cause, int code, String message1) { |
||||
|
super(message, cause); |
||||
|
this.code = code; |
||||
|
this.message = message1; |
||||
|
} |
||||
|
|
||||
|
public BusinessException(Throwable cause, int code, String message) { |
||||
|
super(cause); |
||||
|
this.code = code; |
||||
|
this.message = message; |
||||
|
} |
||||
|
|
||||
|
public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, int code, String message1) { |
||||
|
super(message, cause, enableSuppression, writableStackTrace); |
||||
|
this.code = code; |
||||
|
this.message = message1; |
||||
|
} |
||||
|
|
||||
|
public BusinessException(CodeInterface biz) { |
||||
|
super(); |
||||
|
this.code = biz.getCode(); |
||||
|
this.message = biz.getMsg(); |
||||
|
} |
||||
|
|
||||
|
public BusinessException(String message) { |
||||
|
super(message); |
||||
|
this.code= BizCode.General_ParameterInvalid.getCode(); |
||||
|
this.message =message; |
||||
|
} |
||||
|
|
||||
|
public BusinessException(BizCode biz, String msg) { |
||||
|
super(); |
||||
|
this.code = biz.getCode(); |
||||
|
this.message = msg; |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package com.jiagutech.common.exception; |
||||
|
|
||||
|
/** |
||||
|
* 业务异常接口 |
||||
|
*/ |
||||
|
public interface CodeInterface { |
||||
|
|
||||
|
/** |
||||
|
* 消息 |
||||
|
* |
||||
|
* @return String |
||||
|
*/ |
||||
|
String getMsg(); |
||||
|
|
||||
|
/** |
||||
|
* 业务代码 |
||||
|
* |
||||
|
* @return int |
||||
|
*/ |
||||
|
Integer getCode(); |
||||
|
|
||||
|
} |
@ -0,0 +1,80 @@ |
|||||
|
package com.jiagutech.common.exception; |
||||
|
|
||||
|
|
||||
|
import cn.dev33.satoken.exception.NotLoginException; |
||||
|
import cn.dev33.satoken.exception.NotRoleException; |
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
import com.jiagutech.dto.common.R; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.HttpStatus; |
||||
|
import org.springframework.http.ResponseEntity; |
||||
|
import org.springframework.validation.BindException; |
||||
|
import org.springframework.validation.FieldError; |
||||
|
import org.springframework.web.bind.MethodArgumentNotValidException; |
||||
|
import org.springframework.web.bind.annotation.ExceptionHandler; |
||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice; |
||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; |
||||
|
|
||||
|
import java.sql.SQLIntegrityConstraintViolationException; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
@Slf4j |
||||
|
@RestControllerAdvice |
||||
|
public class GlobalExceptionHandler { |
||||
|
|
||||
|
@ExceptionHandler(BindException.class) |
||||
|
public ResponseEntity<R> handleBindExceptions(BindException ex) { |
||||
|
log.error("参数绑定失败", ex); |
||||
|
Map<String, String> errorMap = new HashMap<>(); |
||||
|
for (FieldError fieldError : ex.getFieldErrors()) { |
||||
|
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()); |
||||
|
} |
||||
|
return new ResponseEntity<>(R.fail(BizCode.General_ParameterInvalid.getCode(), JSON.toJSONString(errorMap)), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(MethodArgumentNotValidException.class) |
||||
|
public ResponseEntity<R> handleValidationExceptions(MethodArgumentNotValidException ex) { |
||||
|
log.error("参数校验失败", ex); |
||||
|
Map<String, String> errorMap = new HashMap<>(); |
||||
|
for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) { |
||||
|
errorMap.put(fieldError.getField(), fieldError.getDefaultMessage()); |
||||
|
} |
||||
|
return new ResponseEntity<>(R.fail(BizCode.General_ParameterInvalid.getCode(), JSON.toJSONString(errorMap)), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(BusinessException.class) |
||||
|
public ResponseEntity<R> handleBusinessException(BusinessException ex) { |
||||
|
return new ResponseEntity<>(R.fail(ex.getCode(), ex.getMessage()), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class) |
||||
|
public ResponseEntity<R> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { |
||||
|
log.error("参数类型错误", ex); |
||||
|
return new ResponseEntity<>(R.fail(new BusinessException("参数类型错误")), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(NotLoginException.class) |
||||
|
public ResponseEntity<R> handleLoginException(Exception ex) { |
||||
|
log.error("系统异常", ex); |
||||
|
return new ResponseEntity<>(R.fail(BizCode.USER_NOT_LOGIN.getCode(), BizCode.USER_NOT_LOGIN.getMsg()), HttpStatus.UNAUTHORIZED); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(NotRoleException.class) |
||||
|
public ResponseEntity<R> handlePermissionException(Exception ex) { |
||||
|
log.error("鉴权失败", ex); |
||||
|
return new ResponseEntity<>(R.fail(BizCode.PERMISSION_NOT_FOUND.getCode(), BizCode.PERMISSION_NOT_FOUND.getMsg()), HttpStatus.FORBIDDEN); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(SQLIntegrityConstraintViolationException.class) |
||||
|
public ResponseEntity<R> handleDuplicateException(Exception ex) { |
||||
|
log.error("数据重复", ex); |
||||
|
return new ResponseEntity<>(R.fail(BizCode.USER_PHONE_EXIST.getCode(), BizCode.USER_PHONE_EXIST.getMsg()), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
|
||||
|
@ExceptionHandler(Exception.class) |
||||
|
public ResponseEntity<R> handleCustomException(Exception ex) { |
||||
|
log.error("系统异常", ex); |
||||
|
return new ResponseEntity<>(R.fail(BizCode.General_Failure), HttpStatus.BAD_REQUEST); |
||||
|
} |
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
package com.jiagutech.common.sa; |
||||
|
|
||||
|
import cn.dev33.satoken.stp.StpInterface; |
||||
|
import com.jiagutech.dto.LoginUser; |
||||
|
import com.jiagutech.utils.LoginUtil; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* sa-token 权限管理实现类 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
*/ |
||||
|
public class SaPermissionImpl implements StpInterface { |
||||
|
|
||||
|
/** |
||||
|
* 获取菜单权限列表 |
||||
|
*/ |
||||
|
@Override |
||||
|
public List<String> getPermissionList(Object loginId, String loginType) { |
||||
|
|
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取角色权限列表 |
||||
|
*/ |
||||
|
@Override |
||||
|
public List<String> getRoleList(Object loginId, String loginType) { |
||||
|
LoginUser loginUser = LoginUtil.getLoginUser(); |
||||
|
if (loginUser == null) { |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
return Collections.emptyList(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
package com.jiagutech.common.sa; |
||||
|
|
||||
|
import cn.dev33.satoken.jwt.StpLogicJwtForSimple; |
||||
|
import cn.dev33.satoken.stp.StpInterface; |
||||
|
import cn.dev33.satoken.stp.StpLogic; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
/** |
||||
|
* sa-token 配置 |
||||
|
*/ |
||||
|
@Configuration |
||||
|
public class SaTokenConfig { |
||||
|
|
||||
|
@Bean |
||||
|
public StpLogic getStpLogicJwt() { |
||||
|
// Sa-Token 整合 jwt (简单模式)
|
||||
|
return new StpLogicJwtForSimple(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 权限接口实现(使用bean注入方便用户替换) |
||||
|
*/ |
||||
|
@Bean |
||||
|
public StpInterface stpInterface() { |
||||
|
return new SaPermissionImpl(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
package com.jiagutech.common.sa; |
||||
|
|
||||
|
import cn.dev33.satoken.context.SaHolder; |
||||
|
import cn.dev33.satoken.interceptor.SaInterceptor; |
||||
|
import cn.dev33.satoken.stp.StpUtil; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; |
||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Configuration |
||||
|
public class SaTokenConfigure implements WebMvcConfigurer { |
||||
|
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
|
||||
|
@Override |
||||
|
public void addInterceptors(InterceptorRegistry registry) { |
||||
|
// 注册 Sa-Token 拦截器,打开注解式鉴权功能
|
||||
|
registry.addInterceptor(new SaInterceptor(handle -> { |
||||
|
try { |
||||
|
log.info("-------- 前端访问path:" + SaHolder.getRequest().getRequestPath()); |
||||
|
StpUtil.checkLogin(); |
||||
|
log.info("-------- 此 path 校验成功:" + SaHolder.getRequest().getRequestPath()); |
||||
|
} catch (Exception e) { |
||||
|
log.info("-------- 此 path 校验失败:" + SaHolder.getRequest().getRequestPath()); |
||||
|
throw e; |
||||
|
} |
||||
|
})) |
||||
|
.addPathPatterns("/**") |
||||
|
.excludePathPatterns("/user/login", "user/logout", |
||||
|
"/user/register", "/record/export/**", "/favicon.ico","/error", |
||||
|
"/swagger-ui/**", "/swagger-resources/**", "/v2/api-docs/**", "/doc.html/**", "/webjars/**" |
||||
|
); |
||||
|
} |
||||
|
} |
@ -0,0 +1,105 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.util.concurrent.*; |
||||
|
|
||||
|
@Slf4j |
||||
|
public class MonitorThreadPoolExecutor extends ThreadPoolExecutor { |
||||
|
|
||||
|
private final ThreadLocal<Long> executeStartTime; |
||||
|
protected String poolName; |
||||
|
private final int slowTaskThreshold; |
||||
|
private final int queueTimeThreshold; |
||||
|
|
||||
|
private final int queueSizeThreshold; |
||||
|
|
||||
|
private static final int DEFAULT_SLOW_TASK_TIME = 1000; |
||||
|
|
||||
|
private static final int DEFAULT_QUEUE_TIME = 100; |
||||
|
|
||||
|
private static final int DEFAULT_QUEUE_SIZE = 450; |
||||
|
|
||||
|
|
||||
|
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, String poolName) { |
||||
|
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NameThreadFactory(poolName), new AbortPolicy(), poolName); |
||||
|
} |
||||
|
|
||||
|
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, String poolName) { |
||||
|
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, new AbortPolicy(), poolName); |
||||
|
} |
||||
|
|
||||
|
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, String poolName) { |
||||
|
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new NameThreadFactory(poolName), handler, poolName); |
||||
|
} |
||||
|
|
||||
|
public MonitorThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, String poolName) { |
||||
|
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); |
||||
|
this.poolName = poolName; |
||||
|
executeStartTime = new ThreadLocal<>(); |
||||
|
slowTaskThreshold = 0; |
||||
|
queueTimeThreshold = 0; |
||||
|
queueSizeThreshold = 0; |
||||
|
} |
||||
|
|
||||
|
public MonitorThreadPoolExecutor(ThreadPoolConfig poolConfig) { |
||||
|
super(poolConfig.getCorePoolSize(), |
||||
|
poolConfig.getMaxPoolSize(), |
||||
|
poolConfig.getKeepAliveSeconds(), |
||||
|
TimeUnit.SECONDS, |
||||
|
new ArrayBlockingQueue<>(poolConfig.getQueueCapacity()), new NameThreadFactory(poolConfig.getThreadName()), |
||||
|
(r, executor1) -> log.info("任务被中断")); |
||||
|
this.poolName = poolConfig.getThreadName(); |
||||
|
executeStartTime = new ThreadLocal<>(); |
||||
|
this.slowTaskThreshold = poolConfig.getTaskThreshold() > 0 ? poolConfig.getTaskThreshold() : DEFAULT_SLOW_TASK_TIME; |
||||
|
this.queueTimeThreshold = poolConfig.getQueueTimeThreshold() > 0 ? poolConfig.getQueueTimeThreshold() : DEFAULT_QUEUE_TIME; |
||||
|
this.queueSizeThreshold = poolConfig.getQueueSizeThreshold() > 0 ? poolConfig.getQueueSizeThreshold() : DEFAULT_QUEUE_SIZE; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void execute(Runnable command) { |
||||
|
super.execute(command); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void beforeExecute(Thread t, Runnable r) { |
||||
|
super.beforeExecute(t, r); |
||||
|
executeStartTime.set(System.nanoTime()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected void afterExecute(Runnable r, Throwable t) { |
||||
|
MonitoredRunnable monitored = null; |
||||
|
try { |
||||
|
long executeEndNano = System.nanoTime(); |
||||
|
Long executeStartTime = this.executeStartTime.get(); |
||||
|
monitored = (MonitoredRunnable) r; |
||||
|
long queueNanoTime = monitored.getInQueueNanoTime(); |
||||
|
int queueTime = (int) ((executeStartTime - queueNanoTime) / 1000000L); |
||||
|
int executeTime = (int) ((executeEndNano - executeStartTime) / 1000000L); |
||||
|
ThreadStatistics packStatistics = null; |
||||
|
if (monitored.getThreadLocal() != null) { |
||||
|
packStatistics = monitored.getThreadLocal().get(); |
||||
|
} |
||||
|
if (executeTime > this.slowTaskThreshold || queueTime > this.queueTimeThreshold || getQueue().size() >= queueSizeThreshold) { |
||||
|
log.warn("线程池({}),触发告警,当前任务执行时间={},当前任务排队时间={},队列线程总数={} ", Thread.currentThread().getName(), executeTime, queueTime, getQueue().size()); |
||||
|
if (packStatistics != null) { |
||||
|
log.warn("当前任务各阶段耗时统计:{}", packStatistics); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (t != null) { |
||||
|
log.error("线程池名称 = {}, 执行异常的任务数+1", poolName); |
||||
|
} |
||||
|
} catch (Exception ignore) { |
||||
|
} finally { |
||||
|
executeStartTime.remove(); |
||||
|
if (monitored != null && monitored.getThreadLocal() != null) { |
||||
|
monitored.getThreadLocal().remove(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,40 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
|
||||
|
|
||||
|
public class MonitoredRunnable implements Runnable { |
||||
|
|
||||
|
private final Runnable runnable; |
||||
|
private ThreadLocal<ThreadStatistics> threadLocal; |
||||
|
private final long inQueueNanoTime; |
||||
|
|
||||
|
public MonitoredRunnable(Runnable runnable, ThreadLocal<ThreadStatistics> threadLocal) { |
||||
|
this.runnable = runnable; |
||||
|
this.threadLocal = threadLocal; |
||||
|
this.inQueueNanoTime = System.nanoTime(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public ThreadLocal<ThreadStatistics> getThreadLocal() { |
||||
|
return threadLocal; |
||||
|
} |
||||
|
|
||||
|
public void setThreadLocal(ThreadLocal<ThreadStatistics> threadLocal) { |
||||
|
this.threadLocal = threadLocal; |
||||
|
} |
||||
|
|
||||
|
public long getInQueueNanoTime() { |
||||
|
return inQueueNanoTime; |
||||
|
} |
||||
|
|
||||
|
public MonitoredRunnable(Runnable runnable) { |
||||
|
this.runnable = runnable; |
||||
|
this.inQueueNanoTime = System.nanoTime(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public void run() { |
||||
|
this.runnable.run(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
import java.util.concurrent.ThreadFactory; |
||||
|
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
|
||||
|
public class NameThreadFactory implements ThreadFactory { |
||||
|
|
||||
|
private String namePrefix; |
||||
|
|
||||
|
private AtomicInteger nextId = new AtomicInteger(1); |
||||
|
|
||||
|
public NameThreadFactory(String namePrefix) { |
||||
|
this.namePrefix = namePrefix; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Thread newThread(Runnable r) { |
||||
|
return new Thread(r, namePrefix + nextId.getAndIncrement()); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
import io.netty.util.concurrent.ThreadProperties; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; |
||||
|
import org.springframework.context.annotation.Bean; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
import java.util.concurrent.ThreadPoolExecutor; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Configuration |
||||
|
@ConditionalOnProperty(prefix = "thread", name = "enabled", havingValue = "true") |
||||
|
public class ThreadExecutorConfig { |
||||
|
@Resource |
||||
|
private ThreadPoolConfig threadPoolConfig; |
||||
|
|
||||
|
|
||||
|
// @Primary
|
||||
|
@Bean(name = "customThreadPool") |
||||
|
public ThreadPoolExecutor getcustomExecutor() { |
||||
|
return generateExecutor(threadPoolConfig); |
||||
|
} |
||||
|
|
||||
|
private ThreadPoolExecutor generateExecutor(ThreadPoolConfig threadPoolConfig) { |
||||
|
return new MonitorThreadPoolExecutor(threadPoolConfig); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
|
||||
|
@Data |
||||
|
@Configuration |
||||
|
@ConfigurationProperties(prefix = "thread.pool") |
||||
|
public class ThreadPoolConfig { |
||||
|
|
||||
|
private int corePoolSize; |
||||
|
|
||||
|
private int maxPoolSize; |
||||
|
|
||||
|
private int keepAliveSeconds; |
||||
|
|
||||
|
private int queueCapacity; |
||||
|
|
||||
|
private String threadName; |
||||
|
|
||||
|
private int taskThreshold; |
||||
|
|
||||
|
private int queueTimeThreshold; |
||||
|
|
||||
|
private int queueSizeThreshold; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,118 @@ |
|||||
|
package com.jiagutech.common.thread; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
public class ThreadStatistics { |
||||
|
|
||||
|
private String imei; |
||||
|
|
||||
|
private long totalCost; |
||||
|
|
||||
|
private long start; |
||||
|
|
||||
|
private long finish; |
||||
|
|
||||
|
private Map<StatisticsEnum, Item> phases = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
/** |
||||
|
* 指定阶段处理统计 |
||||
|
* |
||||
|
* @param statisticsEnum |
||||
|
*/ |
||||
|
public void startPhase(StatisticsEnum statisticsEnum) { |
||||
|
phases.computeIfAbsent(statisticsEnum, key -> new Item()); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 完成指定部分的处理统计 |
||||
|
* |
||||
|
* @param statisticsEnum 指定部分 |
||||
|
*/ |
||||
|
public void finishPhase(StatisticsEnum statisticsEnum) { |
||||
|
Item item = phases.getOrDefault(statisticsEnum, null); |
||||
|
if (item != null) { |
||||
|
item.finish(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void finishStatistics() { |
||||
|
this.finish = System.currentTimeMillis(); |
||||
|
this.totalCost = this.finish - this.start; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public ThreadStatistics(long start) { |
||||
|
this.start = start; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Data |
||||
|
public static class Item { |
||||
|
public Item() { |
||||
|
start = System.currentTimeMillis(); |
||||
|
} |
||||
|
|
||||
|
long start; |
||||
|
|
||||
|
volatile long finish; |
||||
|
|
||||
|
volatile long cost; |
||||
|
|
||||
|
/** |
||||
|
* 完成处理统计 |
||||
|
*/ |
||||
|
public void finish() { |
||||
|
if (finish != 0) { |
||||
|
return; |
||||
|
} |
||||
|
this.finish = System.currentTimeMillis(); |
||||
|
this.cost = this.finish - start; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Item{" + |
||||
|
"cost=" + cost + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public enum StatisticsEnum { |
||||
|
/** |
||||
|
* 基础信息解析 |
||||
|
*/ |
||||
|
base_info, |
||||
|
/** |
||||
|
* 解密 |
||||
|
*/ |
||||
|
decrypt, |
||||
|
/** |
||||
|
* 数据包解析 |
||||
|
*/ |
||||
|
deserialization, |
||||
|
|
||||
|
/** |
||||
|
* 数据保存到ck |
||||
|
*/ |
||||
|
save; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Statistics{" + |
||||
|
"imei='" + imei + '\'' + |
||||
|
", totalCost=" + totalCost + |
||||
|
", start=" + start + |
||||
|
", finish=" + finish + |
||||
|
", phases=" + phases + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
@ -0,0 +1,76 @@ |
|||||
|
package com.jiagutech.controller; |
||||
|
|
||||
|
import cn.hutool.core.map.MapUtil; |
||||
|
import com.jiagutech.dto.CreateRecordRequest; |
||||
|
import com.jiagutech.dto.RecordItem; |
||||
|
import com.jiagutech.dto.RecordModel; |
||||
|
import com.jiagutech.dto.common.PageRequest; |
||||
|
import com.jiagutech.dto.common.PageResult; |
||||
|
import com.jiagutech.dto.common.R; |
||||
|
import com.jiagutech.dto.RecordDetail; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import com.jiagutech.service.RecordService; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.http.MediaType; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import javax.validation.Valid; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Api(tags = "识别记录") |
||||
|
@Slf4j |
||||
|
@RestController |
||||
|
@RequiredArgsConstructor |
||||
|
@RequestMapping("/record") |
||||
|
public class RecordController { |
||||
|
private final RecordService recordService; |
||||
|
|
||||
|
|
||||
|
@ApiOperation(value = "创建识别记录") |
||||
|
@PostMapping(value = "/createRecord", consumes = "multipart/form-data", produces = "application/json") |
||||
|
public R createRecord(@Valid @ModelAttribute CreateRecordRequest request) { |
||||
|
String taskId = recordService.createRecordAsync(request); |
||||
|
return R.ok(MapUtil.of("taskId", taskId)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "识别记录处理进度") |
||||
|
@GetMapping("/taskProgress/{taskId}") |
||||
|
public Float getTaskProgress(@PathVariable String taskId) { |
||||
|
return recordService.getTaskProgress(taskId); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "获取识别记录详情") |
||||
|
@GetMapping("/getRecordDetail/{recordId}") |
||||
|
public R<RecordDetail> getRecordDetail(@PathVariable Long recordId) { |
||||
|
return R.ok(recordService.getRecordDetail(recordId)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "导出识别记录",notes = "下载文件(响应类型为文件流)") |
||||
|
@GetMapping(value = "/export/{recordId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) |
||||
|
public void exportRecord(@PathVariable Long recordId, HttpServletResponse response) { |
||||
|
recordService.exportRecord(recordId, response); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "分页获取识别记录") |
||||
|
@PostMapping("/page") |
||||
|
public R<PageResult<RecordItem>> getPages(@RequestBody PageRequest pageRequest) { |
||||
|
return R.ok(recordService.getPages(pageRequest)); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "更新识别记录内容") |
||||
|
@PutMapping("/updateRecordContent") |
||||
|
public void updateRecord(@RequestBody RecordContentEntity recordContentEntity) { |
||||
|
recordService.updateRecordContent(recordContentEntity); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@ApiOperation(value = "获取模型列表") |
||||
|
@GetMapping("/models") |
||||
|
public R<RecordModel> getModels() { |
||||
|
return R.ok(recordService.getModels()); |
||||
|
} |
||||
|
} |
@ -0,0 +1,61 @@ |
|||||
|
package com.jiagutech.controller; |
||||
|
|
||||
|
import cn.dev33.satoken.stp.StpUtil; |
||||
|
import com.jiagutech.dto.LoginRequest; |
||||
|
import com.jiagutech.dto.LoginResponse; |
||||
|
import com.jiagutech.dto.common.R; |
||||
|
import com.jiagutech.dto.UserRequest; |
||||
|
import com.jiagutech.service.UserService; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.validation.annotation.Validated; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
|
||||
|
@Api(tags = "用户") |
||||
|
@Slf4j |
||||
|
@RestController |
||||
|
@RequestMapping("/user") |
||||
|
@RequiredArgsConstructor |
||||
|
public class UserController { |
||||
|
|
||||
|
private final UserService userService; |
||||
|
|
||||
|
@GetMapping("/info") |
||||
|
public String info() { |
||||
|
log.info("info"); |
||||
|
return "info"; |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "登录") |
||||
|
@PostMapping("/login") |
||||
|
public R<LoginResponse> login(@Validated @RequestBody LoginRequest loginBody) { |
||||
|
LoginResponse loginVo = userService.login(loginBody); |
||||
|
return R.ok(loginVo); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "登出") |
||||
|
@GetMapping("/logout") |
||||
|
@PostMapping("/logout") |
||||
|
public R<String> logout() { |
||||
|
StpUtil.logout(); |
||||
|
return R.ok("logout"); |
||||
|
} |
||||
|
|
||||
|
@ApiOperation(value = "注册") |
||||
|
@PostMapping(value = "/register", consumes = "application/json") |
||||
|
public R<Void> register(@Validated @RequestBody UserRequest user) { |
||||
|
userService.register(user); |
||||
|
return R.ok(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@ApiOperation(value = "审批") |
||||
|
@PutMapping(value = "/approval/{userId}") |
||||
|
public R<Void> approval(@PathVariable Long userId) { |
||||
|
userService.approval(userId); |
||||
|
return R.ok(); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,38 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import javax.validation.constraints.*; |
||||
|
|
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
@ApiModel("创建记录请求") |
||||
|
public class CreateRecordRequest { |
||||
|
@ApiModelProperty(value = "图片列表", required = true) |
||||
|
@NotEmpty(message = "文件不能为空") |
||||
|
private MultipartFile[] files; |
||||
|
@ApiModelProperty(value = "模型名称", required = true) |
||||
|
@NotBlank(message = "模型名称不能为空") |
||||
|
private String modelName; |
||||
|
@ApiModelProperty(value = "重叠度", required = true) |
||||
|
@NotNull(message = "IOU 不能为空") |
||||
|
@Min(value = 0, message = "IOU 不能小于 0") |
||||
|
@Max(value = 1, message = "IOU 不能大于 1") |
||||
|
private Float iou; |
||||
|
@ApiModelProperty(value = "置信度", required = true) |
||||
|
@NotNull(message = "Conf 不能为空") |
||||
|
@Min(value = 0, message = "Conf 不能小于 0") |
||||
|
@Max(value = 1, message = "Conf 不能大于 1") |
||||
|
private Float conf; |
||||
|
@ApiModelProperty(value = "任务名称", required = true) |
||||
|
@NotBlank(message = "任务名称不能为空") |
||||
|
private String taskName; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
|
||||
|
import com.jiagutech.common.UserConstants; |
||||
|
import lombok.Data; |
||||
|
import org.hibernate.validator.constraints.Length; |
||||
|
|
||||
|
import javax.validation.constraints.NotBlank; |
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
@Data |
||||
|
public class LoginRequest implements Serializable { |
||||
|
|
||||
|
@NotBlank(message = "{user.phone.not.blank}") |
||||
|
private String phone; |
||||
|
@NotBlank(message = "{user.password.not.blank}") |
||||
|
@Length(min = UserConstants.PASSWORD_MIN_LENGTH, max = UserConstants.PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}") |
||||
|
private String password; |
||||
|
|
||||
|
private String grantType = "password"; |
||||
|
} |
@ -0,0 +1,37 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* 登录验证信息 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class LoginResponse { |
||||
|
|
||||
|
/** |
||||
|
* 授权令牌 |
||||
|
*/ |
||||
|
@JsonProperty("access_token") |
||||
|
private String accessToken; |
||||
|
|
||||
|
/** |
||||
|
* 刷新令牌 |
||||
|
*/ |
||||
|
@JsonProperty("refresh_token") |
||||
|
private String refreshToken; |
||||
|
|
||||
|
/** |
||||
|
* 授权令牌 access_token 的有效期 |
||||
|
*/ |
||||
|
@JsonProperty("expire_in") |
||||
|
private Long expireIn; |
||||
|
|
||||
|
/** |
||||
|
* 刷新令牌 refresh_token 的有效期 |
||||
|
*/ |
||||
|
@JsonProperty("refresh_expire_in") |
||||
|
private Long refreshExpireIn; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,86 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonIgnore; |
||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize; |
||||
|
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import java.io.Serializable; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @ClassName LoginUser |
||||
|
* @author: zhangyeguang |
||||
|
* @create: 2024-09-01 09:28 |
||||
|
* @Version 1.0 |
||||
|
* @description: |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class LoginUser implements Serializable { |
||||
|
|
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 用户ID |
||||
|
*/ |
||||
|
@JsonSerialize(using = ToStringSerializer.class) |
||||
|
private Long userId; |
||||
|
|
||||
|
/** |
||||
|
* 部门 |
||||
|
*/ |
||||
|
private String unitName; |
||||
|
|
||||
|
/** |
||||
|
* 用户唯一标识 |
||||
|
*/ |
||||
|
private String token; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 登录时间 |
||||
|
*/ |
||||
|
private Long loginTime; |
||||
|
|
||||
|
/** |
||||
|
* 过期时间 |
||||
|
*/ |
||||
|
private Long expireTime; |
||||
|
|
||||
|
/** |
||||
|
* 登录IP地址 |
||||
|
*/ |
||||
|
private String ipaddr; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 用户名 |
||||
|
*/ |
||||
|
private String username; |
||||
|
|
||||
|
/** |
||||
|
* 用户昵称 |
||||
|
*/ |
||||
|
private String realName; |
||||
|
/** |
||||
|
* 手机号 |
||||
|
*/ |
||||
|
private String phone; |
||||
|
@JsonIgnore |
||||
|
private String password; |
||||
|
|
||||
|
private String email; |
||||
|
/** |
||||
|
* 头像 |
||||
|
*/ |
||||
|
private String avatar; |
||||
|
|
||||
|
private LocalDateTime createTime; |
||||
|
|
||||
|
private Integer status; |
||||
|
|
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class ModelPredictData{ |
||||
|
private List<ModelPredictResponse> recognitionData; |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class ModelPredictResponse { |
||||
|
private Integer classify; |
||||
|
private String name; |
||||
|
private int[][] points; |
||||
|
private Float length; |
||||
|
} |
||||
|
|
@ -0,0 +1,31 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.alibaba.excel.annotation.ExcelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class RecordContentExcel { |
||||
|
|
||||
|
@ExcelProperty("文件名") |
||||
|
private String name; |
||||
|
@ExcelProperty("总喙数") |
||||
|
private int beakNum; |
||||
|
@ExcelProperty("总柄数") |
||||
|
private int handleNum; |
||||
|
@ExcelProperty("总角果数") |
||||
|
private int rapeNum; |
||||
|
@ExcelProperty("置信度") |
||||
|
private float conf; |
||||
|
@ExcelProperty("重叠度") |
||||
|
private float iou; |
||||
|
@ExcelProperty("比例系数") |
||||
|
private int scale; |
||||
|
|
||||
|
@ExcelProperty("角果长") |
||||
|
private float rapeLength; |
||||
|
|
||||
|
@ExcelProperty("喙长") |
||||
|
private float beakLength; |
||||
|
@ExcelProperty("柄长") |
||||
|
private float handleLength; |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonFormat; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
public class RecordDetail { |
||||
|
private Long id; |
||||
|
|
||||
|
private String name; |
||||
|
|
||||
|
private int imageNum; |
||||
|
|
||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") |
||||
|
private LocalDateTime createTime; |
||||
|
; |
||||
|
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss") |
||||
|
private LocalDateTime updateTime; |
||||
|
|
||||
|
private List<RecordContentEntity> content; |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.alibaba.excel.annotation.ExcelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
@Data |
||||
|
public class RecordExcel { |
||||
|
|
||||
|
@ExcelProperty("总图片数") |
||||
|
private int imageNum; |
||||
|
@ExcelProperty("总喙数") |
||||
|
private int beakNum; |
||||
|
@ExcelProperty("总柄数") |
||||
|
private int handleNum; |
||||
|
@ExcelProperty("总角果数") |
||||
|
private int rapeNum; |
||||
|
@ExcelProperty("平均角果数") |
||||
|
private float avgRapeNum; |
||||
|
@ExcelProperty("最大角果数") |
||||
|
private int maxRapeNum; |
||||
|
@ExcelProperty("最少角果数") |
||||
|
private int minRapeNum; |
||||
|
|
||||
|
@ExcelProperty("平均角果长度") |
||||
|
private float avgRapeLength; |
||||
|
|
||||
|
@ExcelProperty("平均喙长度") |
||||
|
private float avgBeakLength; |
||||
|
@ExcelProperty("平均柄长度") |
||||
|
private float avgHandleLength; |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonFormat; |
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
public class RecordItem { |
||||
|
|
||||
|
private String recordName; |
||||
|
|
||||
|
private Long recordId; |
||||
|
|
||||
|
private int imageNum; |
||||
|
|
||||
|
private String userName; |
||||
|
|
||||
|
private Long userId; |
||||
|
|
||||
|
private String unitName; |
||||
|
|
||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") |
||||
|
private LocalDateTime createTime; |
||||
|
|
||||
|
private String phone; |
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
public class RecordModel { |
||||
|
private List<String> loaded_models; |
||||
|
|
||||
|
private List<ModelDetail> models; |
||||
|
|
||||
|
@Data |
||||
|
public static class ModelDetail { |
||||
|
|
||||
|
private float conf; |
||||
|
|
||||
|
private float iou; |
||||
|
|
||||
|
private String name; |
||||
|
|
||||
|
private String desc; |
||||
|
|
||||
|
private String file; |
||||
|
} |
||||
|
} |
@ -0,0 +1,43 @@ |
|||||
|
package com.jiagutech.dto; |
||||
|
|
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
|
||||
|
import javax.validation.constraints.Pattern; |
||||
|
import javax.validation.constraints.Size; |
||||
|
|
||||
|
/** |
||||
|
* @ClassName UserRequest |
||||
|
* @author: zhangyeguang |
||||
|
* @create: 2024-08-31 10:00 |
||||
|
* @Version 1.0 |
||||
|
* @description: |
||||
|
**/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class UserRequest { |
||||
|
|
||||
|
/** |
||||
|
* 用户昵称 |
||||
|
*/ |
||||
|
@Size(min = 2, max = 30, message = "用户姓名长度不能超过{max}个字符") |
||||
|
private String realName; |
||||
|
|
||||
|
/** |
||||
|
* 手机号码 |
||||
|
*/ |
||||
|
@Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号码格式不正确") |
||||
|
private String phone; |
||||
|
|
||||
|
|
||||
|
/** |
||||
|
* 密码 |
||||
|
*/ |
||||
|
private String password; |
||||
|
|
||||
|
|
||||
|
private String unitName; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,23 @@ |
|||||
|
package com.jiagutech.dto.common; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @ClassName PageInfo |
||||
|
* @author: zhangyeguang |
||||
|
* @create: 2024-09-02 15:21 |
||||
|
* @Version 1.0 |
||||
|
* @description: |
||||
|
**/ |
||||
|
@Data |
||||
|
public class PageRequest<T> implements java.io.Serializable { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private int pageNum; |
||||
|
private int pageSize; |
||||
|
private String oderByColumn; |
||||
|
|
||||
|
private T request; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,33 @@ |
|||||
|
package com.jiagutech.dto.common; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @ClassName PageResult |
||||
|
* @author: zhangyeguang |
||||
|
* @create: 2024-09-02 15:22 |
||||
|
* @Version 1.0 |
||||
|
* @description: |
||||
|
**/ |
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
public class PageResult<T> { |
||||
|
private int total; |
||||
|
private int pageSize; |
||||
|
private int pageNum; |
||||
|
private int pages; |
||||
|
private List<T> records; |
||||
|
|
||||
|
public static <T> PageResult<T> of(int total, int pageSize, int pageNum, List<T> records) { |
||||
|
PageResult<T> pageResult = new PageResult<>(); |
||||
|
pageResult.setTotal(total); |
||||
|
pageResult.setPageSize(pageSize); |
||||
|
pageResult.setPageNum(pageNum); |
||||
|
pageResult.setPages(total==0||pageSize==0?0:total % pageSize == 0 ? total / pageSize : total / pageSize + 1); |
||||
|
pageResult.setRecords(records); |
||||
|
return pageResult; |
||||
|
} |
||||
|
} |
@ -0,0 +1,110 @@ |
|||||
|
package com.jiagutech.dto.common; |
||||
|
|
||||
|
|
||||
|
|
||||
|
import com.jiagutech.common.HttpStatus; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import java.io.Serializable; |
||||
|
|
||||
|
/** |
||||
|
* 响应信息主体 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
*/ |
||||
|
@Data |
||||
|
@NoArgsConstructor |
||||
|
public class R<T> implements Serializable { |
||||
|
|
||||
|
|
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
/** |
||||
|
* 成功 |
||||
|
*/ |
||||
|
public static final int SUCCESS = 200; |
||||
|
|
||||
|
/** |
||||
|
* 失败 |
||||
|
*/ |
||||
|
public static final int FAIL = 500; |
||||
|
|
||||
|
private int code; |
||||
|
|
||||
|
private String msg; |
||||
|
|
||||
|
private T data; |
||||
|
|
||||
|
public static <T> R<T> ok() { |
||||
|
return restResult(null, SUCCESS, "操作成功"); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> ok(T data) { |
||||
|
return restResult(data, SUCCESS, "操作成功"); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> ok(String msg) { |
||||
|
return restResult(null, SUCCESS, msg); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> ok(String msg, T data) { |
||||
|
return restResult(data, SUCCESS, msg); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> fail() { |
||||
|
return restResult(null, FAIL, "操作失败"); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> fail(String msg) { |
||||
|
return restResult(null, FAIL, msg); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> fail(T data) { |
||||
|
return restResult(data, FAIL, "操作失败"); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> fail(String msg, T data) { |
||||
|
return restResult(data, FAIL, msg); |
||||
|
} |
||||
|
|
||||
|
public static <T> R<T> fail(int code, String msg) { |
||||
|
return restResult(null, code, msg); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 返回警告消息 |
||||
|
* |
||||
|
* @param msg 返回内容 |
||||
|
* @return 警告消息 |
||||
|
*/ |
||||
|
public static <T> R<T> warn(String msg) { |
||||
|
return restResult(null, HttpStatus.WARN, msg); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 返回警告消息 |
||||
|
* |
||||
|
* @param msg 返回内容 |
||||
|
* @param data 数据对象 |
||||
|
* @return 警告消息 |
||||
|
*/ |
||||
|
public static <T> R<T> warn(String msg, T data) { |
||||
|
return restResult(data, HttpStatus.WARN, msg); |
||||
|
} |
||||
|
|
||||
|
private static <T> R<T> restResult(T data, int code, String msg) { |
||||
|
R<T> r = new R<>(); |
||||
|
r.setCode(code); |
||||
|
r.setData(data); |
||||
|
r.setMsg(msg); |
||||
|
return r; |
||||
|
} |
||||
|
|
||||
|
public static <T> Boolean isError(R<T> ret) { |
||||
|
return !isSuccess(ret); |
||||
|
} |
||||
|
|
||||
|
public static <T> Boolean isSuccess(R<T> ret) { |
||||
|
return R.SUCCESS == ret.getCode(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,42 @@ |
|||||
|
package com.jiagutech.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableField; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
||||
|
import com.jiagutech.dto.ModelPredictResponse; |
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
@TableName(value = "record_content", autoResultMap = true) |
||||
|
public class RecordContentEntity { |
||||
|
@TableId(type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
private Long recordId; |
||||
|
|
||||
|
private String imageName; |
||||
|
|
||||
|
private String imageUrl; |
||||
|
@TableField(typeHandler = JacksonTypeHandler.class) |
||||
|
private List<ModelPredictResponse> recognitionData; |
||||
|
|
||||
|
private int siliquaNum; |
||||
|
|
||||
|
private int handleNum; |
||||
|
|
||||
|
private int beakNum; |
||||
|
|
||||
|
private int size; |
||||
|
//比例
|
||||
|
private int scale; |
||||
|
|
||||
|
private float conf; |
||||
|
|
||||
|
private float iou; |
||||
|
} |
@ -0,0 +1,29 @@ |
|||||
|
package com.jiagutech.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.IdType; |
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
import org.springframework.data.annotation.Id; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
@TableName("record_info") |
||||
|
public class RecordEntity { |
||||
|
@TableId(type = IdType.AUTO) |
||||
|
private Long id; |
||||
|
|
||||
|
private String name; |
||||
|
|
||||
|
private Long userId; |
||||
|
|
||||
|
private int imageNum; |
||||
|
|
||||
|
private LocalDateTime createTime; |
||||
|
|
||||
|
private LocalDateTime updateTime; |
||||
|
|
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package com.jiagutech.entity; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.annotation.TableId; |
||||
|
import com.baomidou.mybatisplus.annotation.TableName; |
||||
|
import lombok.Data; |
||||
|
import lombok.experimental.Accessors; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Data |
||||
|
@TableName("siliqua_user") |
||||
|
@Accessors(chain = true) |
||||
|
public class UserEntity { |
||||
|
@TableId |
||||
|
private long id; |
||||
|
|
||||
|
private String phone; |
||||
|
private String password; |
||||
|
private String realName; |
||||
|
|
||||
|
private String unitName; |
||||
|
|
||||
|
private LocalDateTime createTime; |
||||
|
|
||||
|
private int status; |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,26 @@ |
|||||
|
package com.jiagutech.handler; |
||||
|
|
||||
|
import com.alibaba.excel.write.handler.SheetWriteHandler; |
||||
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; |
||||
|
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; |
||||
|
import org.apache.poi.ss.usermodel.Sheet; |
||||
|
import org.apache.poi.ss.util.CellRangeAddress; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class AddCellRangeWriteHandler implements SheetWriteHandler { |
||||
|
|
||||
|
private final List<CellRangeAddress> rangeCellList; |
||||
|
|
||||
|
public AddCellRangeWriteHandler(List<CellRangeAddress> rangeCellList) { |
||||
|
this.rangeCellList = (rangeCellList == null) ? Collections.emptyList() : rangeCellList; |
||||
|
} |
||||
|
|
||||
|
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { |
||||
|
Sheet sheet = writeSheetHolder.getSheet(); |
||||
|
for (CellRangeAddress cellRangeAddress : this.rangeCellList) { |
||||
|
sheet.addMergedRegionUnsafe(cellRangeAddress); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,103 @@ |
|||||
|
package com.jiagutech.handler; |
||||
|
|
||||
|
import com.alibaba.excel.metadata.Head; |
||||
|
import com.alibaba.excel.metadata.data.WriteCellData; |
||||
|
import com.alibaba.excel.write.handler.CellWriteHandler; |
||||
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; |
||||
|
import com.alibaba.excel.write.metadata.holder.WriteTableHolder; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.poi.ss.usermodel.Cell; |
||||
|
import org.apache.poi.ss.usermodel.CellType; |
||||
|
import org.apache.poi.ss.usermodel.Row; |
||||
|
import org.apache.poi.ss.usermodel.Sheet; |
||||
|
import org.apache.poi.ss.util.CellRangeAddress; |
||||
|
@Slf4j |
||||
|
public class ExcelFillCellMergeStrategy implements CellWriteHandler { |
||||
|
|
||||
|
// 需要创建合并区的列
|
||||
|
private int[] mergeColumnIndex; |
||||
|
// 从第几行后开始合并,取列头行
|
||||
|
private int mergeRowIndex; |
||||
|
|
||||
|
public ExcelFillCellMergeStrategy() { |
||||
|
} |
||||
|
|
||||
|
public ExcelFillCellMergeStrategy(int mergeRowIndex, int[] mergeColumnIndex) { |
||||
|
this.mergeRowIndex = mergeRowIndex; |
||||
|
this.mergeColumnIndex = mergeColumnIndex; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row, Head head, Integer columnIndex, |
||||
|
Integer relativeRowIndex, Boolean isHead) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell, Head head, Integer relativeRowIndex, |
||||
|
Boolean isHead) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, |
||||
|
List<WriteCellData<?>> list, Cell cell, Head head, |
||||
|
Integer integer, Boolean aBoolean) { |
||||
|
if (aBoolean){ |
||||
|
log.info("表头,不处理"); |
||||
|
return; |
||||
|
} |
||||
|
int curRowIndex = cell.getRowIndex(); |
||||
|
int curColIndex = cell.getColumnIndex(); |
||||
|
if (curRowIndex > mergeRowIndex) { |
||||
|
for (int i = 0; i < mergeColumnIndex.length; i++) { |
||||
|
// 需合并的列
|
||||
|
if (curColIndex == mergeColumnIndex[i]) { |
||||
|
mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 当前单元格向上合并 |
||||
|
* |
||||
|
* @param writeSheetHolder |
||||
|
* @param cell 当前单元格 |
||||
|
* @param curRowIndex 当前行 |
||||
|
* @param curColIndex 当前列 |
||||
|
*/ |
||||
|
private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) { |
||||
|
Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue(); |
||||
|
Cell preCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex); |
||||
|
Object preData = preCell.getCellType() == CellType.STRING ? preCell.getStringCellValue() : preCell.getNumericCellValue(); |
||||
|
// 将当前单元格数据与上一个单元格数据比较
|
||||
|
Boolean dataBool = preData.equals(curData); |
||||
|
//此处需要注意:因为我是按照序号确定是否需要合并的,所以获取每一行第一列数据和上一行第一列数据进行比较,如果相等合并
|
||||
|
Boolean bool = cell.getRow().getCell(0).getNumericCellValue() == cell.getSheet().getRow(curRowIndex - 1).getCell(0).getNumericCellValue(); |
||||
|
if (dataBool && bool) { |
||||
|
Sheet sheet = writeSheetHolder.getSheet(); |
||||
|
List<CellRangeAddress> mergeRegions = sheet.getMergedRegions(); |
||||
|
boolean isMerged = false; |
||||
|
for (int i = 0; i < mergeRegions.size() && !isMerged; i++) { |
||||
|
CellRangeAddress cellRangeAddr = mergeRegions.get(i); |
||||
|
// 若上一个单元格已经被合并,则先移出原有的合并单元,再重新添加合并单元
|
||||
|
if (cellRangeAddr.isInRange(curRowIndex - 1, curColIndex)) { |
||||
|
sheet.removeMergedRegion(i); |
||||
|
cellRangeAddr.setLastRow(curRowIndex); |
||||
|
sheet.addMergedRegion(cellRangeAddr); |
||||
|
isMerged = true; |
||||
|
} |
||||
|
} |
||||
|
// 若上一个单元格未被合并,则新增合并单元
|
||||
|
if (!isMerged) { |
||||
|
CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex); |
||||
|
sheet.addMergedRegion(cellRangeAddress); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,27 @@ |
|||||
|
package com.jiagutech.handler; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; |
||||
|
import com.fasterxml.jackson.core.type.TypeReference; |
||||
|
import com.jiagutech.dto.ModelPredictResponse; |
||||
|
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
|
|
||||
|
import java.sql.ResultSet; |
||||
|
import java.sql.SQLException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class ModelPredictResponseListTypeHandler extends JacksonTypeHandler { |
||||
|
public ModelPredictResponseListTypeHandler(Class<?> type) { |
||||
|
super(type); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Object getResult(ResultSet rs, String columnName) throws SQLException { |
||||
|
String json = rs.getString(columnName); |
||||
|
try { |
||||
|
ObjectMapper objectMapper = new ObjectMapper(); |
||||
|
return objectMapper.readValue(json, new TypeReference<List<ModelPredictResponse>>(){}); |
||||
|
} catch (Exception e) { |
||||
|
throw new SQLException("Error deserializing recognition_data", e); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.jiagutech.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import org.apache.ibatis.annotations.Param; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
|
||||
|
public interface RecordContentMapper extends BaseMapper<RecordContentEntity> { |
||||
|
|
||||
|
List<RecordContentEntity> selectListByRecordId(@Param("recordId") Long recordId); |
||||
|
} |
@ -0,0 +1,14 @@ |
|||||
|
package com.jiagutech.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
|
import com.jiagutech.dto.RecordDetail; |
||||
|
import com.jiagutech.dto.RecordItem; |
||||
|
import com.jiagutech.entity.RecordEntity; |
||||
|
|
||||
|
public interface RecordMapper extends BaseMapper<RecordEntity> { |
||||
|
|
||||
|
RecordDetail getRecordDetail(long id); |
||||
|
|
||||
|
Page<RecordItem> pageList(Page<RecordItem> page, Object o); |
||||
|
} |
@ -0,0 +1,10 @@ |
|||||
|
package com.jiagutech.mapper; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
||||
|
import com.jiagutech.dto.LoginUser; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import com.jiagutech.entity.UserEntity; |
||||
|
|
||||
|
public interface UserMapper extends BaseMapper<UserEntity> { |
||||
|
LoginUser selectLoginUserByPhone(String phone); |
||||
|
} |
@ -0,0 +1,93 @@ |
|||||
|
package com.jiagutech.record; |
||||
|
|
||||
|
import com.jiagutech.common.thread.MonitoredRunnable; |
||||
|
import com.jiagutech.dto.CreateRecordRequest; |
||||
|
import com.jiagutech.dto.LoginUser; |
||||
|
import com.jiagutech.utils.LoginUtil; |
||||
|
import lombok.Data; |
||||
|
import lombok.SneakyThrows; |
||||
|
import lombok.experimental.Accessors; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.net.URL; |
||||
|
import java.util.concurrent.ThreadPoolExecutor; |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
@Slf4j |
||||
|
public class RecordTaskHandler { |
||||
|
|
||||
|
private RecordTask recordTask; |
||||
|
private ThreadPoolExecutor theadPoolExecutor; |
||||
|
private RecordTaskProcessor processor; |
||||
|
|
||||
|
|
||||
|
|
||||
|
public RecordTaskHandler(ThreadPoolExecutor threadPoolExecutor, RecordTaskProcessor processor) { |
||||
|
this.theadPoolExecutor = threadPoolExecutor; |
||||
|
this.processor = processor; |
||||
|
} |
||||
|
|
||||
|
protected void processTask() { |
||||
|
theadPoolExecutor.execute(new MonitoredRunnable(() -> { |
||||
|
try { |
||||
|
processor.handle(recordTask); |
||||
|
log.info("任务完成,任务ID:{}", recordTask.getTaskId()); |
||||
|
} finally { |
||||
|
for (File file : recordTask.getFiles()) { |
||||
|
log.info("删除临时文件:{}", file.getAbsolutePath()); |
||||
|
if (file.exists()) { |
||||
|
if (!file.delete()) |
||||
|
log.error("删除临时文件失败:{}", file.getAbsolutePath()); |
||||
|
} |
||||
|
} |
||||
|
RecordTaskManager.removeRecordTask(recordTask.getTaskId()); |
||||
|
recordTask = null; |
||||
|
|
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
@SneakyThrows |
||||
|
public void createTask(CreateRecordRequest request, String taskId) { |
||||
|
URL resource = RecordTaskHandler.class.getClassLoader().getResource(""); |
||||
|
assert resource != null; |
||||
|
String path = resource.getPath(); |
||||
|
File[] files = new File[request.getFiles().length]; |
||||
|
for (int i = 0; i < request.getFiles().length; i++) { |
||||
|
MultipartFile file = request.getFiles()[i]; |
||||
|
File file1 = new File(path + file.getOriginalFilename()); |
||||
|
file.transferTo(file1); |
||||
|
files[i] = file1; |
||||
|
} |
||||
|
|
||||
|
this.recordTask = new RecordTask() |
||||
|
.setFiles(files) |
||||
|
.setModelName(request.getModelName()) |
||||
|
.setIou(request.getIou()) |
||||
|
.setConf(request.getConf()) |
||||
|
.setProgress(5f) |
||||
|
.setTaskName(request.getTaskName()) |
||||
|
.setTaskId(taskId) |
||||
|
.setLoginUser(LoginUtil.getLoginUser()); |
||||
|
processTask(); |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
@Accessors(chain = true) |
||||
|
public static class RecordTask { |
||||
|
private String taskId; |
||||
|
private File[] files; |
||||
|
private String modelName; |
||||
|
private Float iou; |
||||
|
private Float conf; |
||||
|
private Float progress; |
||||
|
private String taskName; |
||||
|
private LoginUser loginUser; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,21 @@ |
|||||
|
package com.jiagutech.record; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
public class RecordTaskManager { |
||||
|
|
||||
|
private static final ConcurrentHashMap<String, RecordTaskHandler> recordTasks = new ConcurrentHashMap<>(); |
||||
|
|
||||
|
public static void addRecordTask(String taskId, RecordTaskHandler handler) { |
||||
|
recordTasks.put(taskId, handler); |
||||
|
} |
||||
|
|
||||
|
public static RecordTaskHandler getRecordTask(String taskId) { |
||||
|
return recordTasks.get(taskId); |
||||
|
} |
||||
|
|
||||
|
public static void removeRecordTask(String taskId) { |
||||
|
recordTasks.remove(taskId); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,112 @@ |
|||||
|
package com.jiagutech.record; |
||||
|
|
||||
|
import com.jiagutech.client.RecognitionModelClient; |
||||
|
import com.jiagutech.dto.ModelPredictResponse; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import com.jiagutech.entity.RecordEntity; |
||||
|
import com.jiagutech.mapper.RecordContentMapper; |
||||
|
import com.jiagutech.mapper.RecordMapper; |
||||
|
import com.jiagutech.utils.HuaweiObs; |
||||
|
import lombok.NonNull; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
import org.springframework.util.CollectionUtils; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.math.BigDecimal; |
||||
|
import java.math.RoundingMode; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.List; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class RecordTaskProcessor { |
||||
|
private final RecordMapper recordMapper; |
||||
|
|
||||
|
private final RecordContentMapper recordContentMapper; |
||||
|
|
||||
|
private final RecognitionModelClient modelClient; |
||||
|
|
||||
|
private final HuaweiObs huaweiObs; |
||||
|
|
||||
|
@Transactional |
||||
|
public void handle(@NonNull RecordTaskHandler.RecordTask recordTask) { |
||||
|
log.info("开始处理任务:{}", recordTask.getTaskId()); |
||||
|
float progress = 5f; |
||||
|
RecordEntity recordEntity = new RecordEntity().setName(recordTask.getTaskName()) |
||||
|
.setImageNum(recordTask.getFiles().length) |
||||
|
.setUserId(recordTask.getLoginUser().getUserId()) |
||||
|
.setCreateTime(LocalDateTime.now()) |
||||
|
.setUpdateTime(LocalDateTime.now()); |
||||
|
recordMapper.insert(recordEntity); |
||||
|
recordTask.setProgress(progress); |
||||
|
for (File file : recordTask.getFiles()) { |
||||
|
List<ModelPredictResponse> predict = modelClient.predict(file, recordTask.getModelName(), recordTask.getIou(), recordTask.getConf()); |
||||
|
int handleNum = 0; |
||||
|
int rapeNum = 0; |
||||
|
int beakNum = 0; |
||||
|
for (ModelPredictResponse item : predict) { |
||||
|
item.setLength(getMaxLength(item.getPoints())); |
||||
|
switch (item.getClassify()) { |
||||
|
case 0: |
||||
|
rapeNum++; |
||||
|
break; |
||||
|
case 1: |
||||
|
beakNum++; |
||||
|
break; |
||||
|
default: |
||||
|
handleNum++; |
||||
|
} |
||||
|
} |
||||
|
progress += (float) (100 / recordTask.getFiles().length - 10); |
||||
|
recordTask.setProgress(progress); |
||||
|
if (!CollectionUtils.isEmpty(predict)) { |
||||
|
String imageUrl; |
||||
|
try { |
||||
|
imageUrl = huaweiObs.uploadFile(file); |
||||
|
progress += 2f; |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} finally { |
||||
|
file.deleteOnExit(); |
||||
|
} |
||||
|
RecordContentEntity recordContentEntity = new RecordContentEntity() |
||||
|
.setRecordId(recordEntity.getId()).setImageUrl(imageUrl) |
||||
|
.setRecognitionData(predict) |
||||
|
.setImageName(file.getName()) |
||||
|
.setConf(recordTask.getConf()) |
||||
|
.setIou(recordTask.getIou()) |
||||
|
.setHandleNum(handleNum) |
||||
|
.setBeakNum(beakNum) |
||||
|
.setSiliquaNum(rapeNum); |
||||
|
recordContentMapper.insert(recordContentEntity); |
||||
|
progress += 3f; |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
private static double calculateDistance(int x1, int y1, int x2, int y2) { |
||||
|
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); |
||||
|
} |
||||
|
|
||||
|
private float getMaxLength(int[][] points) { |
||||
|
// 计算四条边的长度
|
||||
|
double ab = calculateDistance(points[0][0], points[0][1], points[1][0], points[1][1]); // AB
|
||||
|
double bc = calculateDistance(points[1][0], points[1][1], points[2][0], points[2][1]); // BC
|
||||
|
double cd = calculateDistance(points[2][0], points[2][1], points[3][0], points[3][1]); // CD
|
||||
|
double da = calculateDistance(points[3][0], points[3][1], points[0][0], points[0][1]); // DA
|
||||
|
double max = Math.max(Math.max(ab, bc), Math.max(cd, da)); |
||||
|
BigDecimal b = new BigDecimal(max); |
||||
|
// 找出最大边长
|
||||
|
return b.setScale(2, RoundingMode.HALF_UP).floatValue(); |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,33 @@ |
|||||
|
package com.jiagutech.service; |
||||
|
|
||||
|
import com.jiagutech.dto.CreateRecordRequest; |
||||
|
import com.jiagutech.dto.RecordDetail; |
||||
|
import com.jiagutech.dto.RecordItem; |
||||
|
import com.jiagutech.dto.RecordModel; |
||||
|
import com.jiagutech.dto.common.PageRequest; |
||||
|
import com.jiagutech.dto.common.PageResult; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
|
||||
|
public interface RecordService { |
||||
|
|
||||
|
SseEmitter createRecord(MultipartFile[] files, String modelName, Float lou, Float conf); |
||||
|
|
||||
|
String createRecordAsync(CreateRecordRequest request); |
||||
|
|
||||
|
Float getTaskProgress(String taskId); |
||||
|
|
||||
|
RecordDetail getRecordDetail(Long recordId); |
||||
|
|
||||
|
void exportRecord(Long recordId, HttpServletResponse response); |
||||
|
|
||||
|
PageResult<RecordItem> getPages(PageRequest pageRequest); |
||||
|
|
||||
|
void updateRecordContent(RecordContentEntity recordContentEntity); |
||||
|
|
||||
|
RecordModel getModels(); |
||||
|
|
||||
|
} |
@ -0,0 +1,286 @@ |
|||||
|
package com.jiagutech.service; |
||||
|
|
||||
|
import com.alibaba.excel.EasyExcel; |
||||
|
import com.alibaba.excel.ExcelWriter; |
||||
|
import com.alibaba.excel.util.MapUtils; |
||||
|
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; |
||||
|
import com.alibaba.excel.write.merge.OnceAbsoluteMergeStrategy; |
||||
|
import com.alibaba.excel.write.metadata.WriteSheet; |
||||
|
import com.alibaba.excel.write.metadata.style.WriteCellStyle; |
||||
|
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; |
||||
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; |
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
import com.baomidou.mybatisplus.core.conditions.Wrapper; |
||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; |
||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
||||
|
import com.jiagutech.client.RecognitionModelClient; |
||||
|
import com.jiagutech.dto.*; |
||||
|
import com.jiagutech.dto.common.PageRequest; |
||||
|
import com.jiagutech.dto.common.PageResult; |
||||
|
import com.jiagutech.entity.RecordContentEntity; |
||||
|
import com.jiagutech.handler.ExcelFillCellMergeStrategy; |
||||
|
import com.jiagutech.mapper.RecordContentMapper; |
||||
|
import com.jiagutech.mapper.RecordMapper; |
||||
|
import com.jiagutech.record.RecordTaskHandler; |
||||
|
import com.jiagutech.record.RecordTaskManager; |
||||
|
import com.jiagutech.record.RecordTaskProcessor; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.SneakyThrows; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.math3.analysis.function.Max; |
||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment; |
||||
|
import org.apache.poi.ss.usermodel.VerticalAlignment; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
||||
|
|
||||
|
import javax.servlet.ServletOutputStream; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.io.IOException; |
||||
|
import java.io.UnsupportedEncodingException; |
||||
|
import java.math.BigDecimal; |
||||
|
import java.math.RoundingMode; |
||||
|
import java.net.URLEncoder; |
||||
|
import java.text.DecimalFormat; |
||||
|
import java.util.*; |
||||
|
import java.util.concurrent.ThreadPoolExecutor; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class RecordServiceImpl implements RecordService { |
||||
|
|
||||
|
private final ThreadPoolExecutor customThreadPool; |
||||
|
|
||||
|
private final RecordTaskProcessor taskProcessor; |
||||
|
private final RecordMapper recordMapper; |
||||
|
private final RecordContentMapper recordContentMapper; |
||||
|
private final RecognitionModelClient recognitionModelClient; |
||||
|
|
||||
|
@Override |
||||
|
public SseEmitter createRecord(MultipartFile[] files, String modelName, Float lou, Float conf) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String createRecordAsync(CreateRecordRequest request) { |
||||
|
String taskId = UUID.randomUUID().toString(); |
||||
|
RecordTaskHandler recordTaskHandler = new RecordTaskHandler(customThreadPool, taskProcessor); |
||||
|
recordTaskHandler.createTask(request, taskId); |
||||
|
RecordTaskManager.addRecordTask(taskId, recordTaskHandler); |
||||
|
return taskId; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public Float getTaskProgress(String taskId) { |
||||
|
RecordTaskHandler recordTask = RecordTaskManager.getRecordTask(taskId); |
||||
|
if (recordTask == null) { |
||||
|
return 100f; |
||||
|
} |
||||
|
if (recordTask.getRecordTask() == null) { |
||||
|
return 100f; |
||||
|
} |
||||
|
|
||||
|
return recordTask.getRecordTask().getProgress(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RecordDetail getRecordDetail(Long recordId) { |
||||
|
return recordMapper.getRecordDetail(recordId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void exportRecord(Long recordId, HttpServletResponse response) { |
||||
|
RecordDetail recordDetail = this.getRecordDetail(recordId); |
||||
|
List<RecordContentEntity> content = recordDetail.getContent(); |
||||
|
if (CollectionUtils.isNotEmpty(content)) { |
||||
|
RecordExcel recordExcel = new RecordExcel(); |
||||
|
recordExcel.setImageNum(recordDetail.getImageNum()); |
||||
|
int beakNum = 0; |
||||
|
int rapeNum = 0; |
||||
|
int handleNum = 0; |
||||
|
int rapeMax = 0; |
||||
|
int rapeMin = 10000; |
||||
|
List<Double> avgBeakLen = new ArrayList<>(); |
||||
|
List<Double> avgRapeLen = new ArrayList<>(); |
||||
|
List<Double> avgHandleLen = new ArrayList<>(); |
||||
|
List<RecordContentExcel> recordContentExcelList = new ArrayList<>(); |
||||
|
List<OnceAbsoluteMergeStrategy> mergeStrategies = new ArrayList<>(); |
||||
|
int startRowIndex = 1; |
||||
|
int endRowIndex = 0; |
||||
|
//处理记录详情数据
|
||||
|
for (RecordContentEntity record : content) { |
||||
|
//统计各分类总数
|
||||
|
beakNum += record.getBeakNum(); |
||||
|
rapeNum += record.getSiliquaNum(); |
||||
|
handleNum += record.getHandleNum(); |
||||
|
if (record.getSiliquaNum() > rapeMax) { |
||||
|
rapeMax = record.getSiliquaNum(); |
||||
|
} |
||||
|
if (record.getSiliquaNum() < rapeMin) { |
||||
|
rapeMin = record.getSiliquaNum(); |
||||
|
} |
||||
|
List<Float> rapeLenList = new ArrayList<>(); |
||||
|
List<Float> beakLenList = new ArrayList<>(); |
||||
|
List<Float> handleLenList = new ArrayList<>(); |
||||
|
|
||||
|
List<ModelPredictResponse> recognitionData = record.getRecognitionData(); |
||||
|
int size = record.getSize(); |
||||
|
int scale = record.getScale(); |
||||
|
//把识别数据进行分类
|
||||
|
for (ModelPredictResponse item : recognitionData) { |
||||
|
float len; |
||||
|
if (size > 0 && scale > 0) { |
||||
|
BigDecimal b = new BigDecimal(item.getLength() * size / scale); |
||||
|
len = b.setScale(2, RoundingMode.HALF_UP).floatValue(); |
||||
|
} else { |
||||
|
len = item.getLength(); |
||||
|
} |
||||
|
|
||||
|
switch (item.getClassify()) { |
||||
|
case 0: |
||||
|
rapeLenList.add(len); |
||||
|
break; |
||||
|
case 1: |
||||
|
beakLenList.add(len); |
||||
|
break; |
||||
|
default: |
||||
|
handleLenList.add(len); |
||||
|
} |
||||
|
} |
||||
|
int len = Math.max(rapeLenList.size(), Math.max(handleLenList.size(), beakLenList.size())); |
||||
|
//创建表格行记录
|
||||
|
for (int i = 0; i < len; i++) { |
||||
|
endRowIndex++; |
||||
|
RecordContentExcel contentExcel = new RecordContentExcel(); |
||||
|
//需要合并的列开始
|
||||
|
contentExcel.setName(record.getImageName()); |
||||
|
contentExcel.setBeakNum(record.getBeakNum()); |
||||
|
contentExcel.setRapeNum(record.getSiliquaNum()); |
||||
|
contentExcel.setHandleNum(record.getHandleNum()); |
||||
|
contentExcel.setScale(record.getScale()); |
||||
|
contentExcel.setConf(record.getConf()); |
||||
|
contentExcel.setIou(record.getIou()); |
||||
|
//需要合并的列结束
|
||||
|
recordContentExcelList.add(contentExcel); |
||||
|
if (i <= rapeLenList.size() - 1) { |
||||
|
contentExcel.setRapeLength(rapeLenList.get(i)); |
||||
|
} |
||||
|
if (i <= beakLenList.size() - 1) { |
||||
|
contentExcel.setBeakLength(beakLenList.get(i)); |
||||
|
} |
||||
|
if (i <= handleLenList.size() - 1) { |
||||
|
contentExcel.setHandleLength(handleLenList.get(i)); |
||||
|
} |
||||
|
} |
||||
|
//创建单元格的合并策略, 前7列数据需要合并
|
||||
|
for (int i = 0; i <= 6; i++) { |
||||
|
OnceAbsoluteMergeStrategy mergeStrategy = new OnceAbsoluteMergeStrategy( |
||||
|
startRowIndex, |
||||
|
endRowIndex, |
||||
|
i, i); |
||||
|
mergeStrategies.add(mergeStrategy); |
||||
|
} |
||||
|
|
||||
|
startRowIndex += len; |
||||
|
avgHandleLen.add(handleLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0)); |
||||
|
avgRapeLen.add(rapeLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0)); |
||||
|
avgBeakLen.add(beakLenList.stream().mapToDouble(Float::doubleValue).average().orElse(0.0)); |
||||
|
} |
||||
|
recordExcel.setBeakNum(beakNum); |
||||
|
recordExcel.setRapeNum(rapeNum); |
||||
|
recordExcel.setHandleNum(handleNum); |
||||
|
recordExcel.setMaxRapeNum(rapeMax); |
||||
|
recordExcel.setMinRapeNum(rapeMin); |
||||
|
DecimalFormat dfnum = new DecimalFormat("##0.00"); |
||||
|
float avgRapeNum = Float.parseFloat(dfnum.format((float) rapeNum / content.size())); |
||||
|
recordExcel.setAvgRapeNum(avgRapeNum); |
||||
|
double v = avgBeakLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); |
||||
|
BigDecimal b = new BigDecimal(v); |
||||
|
recordExcel.setAvgBeakLength(b.setScale(2, RoundingMode.HALF_UP).floatValue()); |
||||
|
double r = avgRapeLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); |
||||
|
BigDecimal b1 = new BigDecimal(r); |
||||
|
recordExcel.setAvgRapeLength(b1.setScale(2, RoundingMode.HALF_UP).floatValue()); |
||||
|
double h = avgHandleLen.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); |
||||
|
BigDecimal b2 = new BigDecimal(h); |
||||
|
recordExcel.setAvgHandleLength(b2.setScale(2, RoundingMode.HALF_UP).floatValue()); |
||||
|
ServletOutputStream outputStream = null; |
||||
|
try { |
||||
|
outputStream = response.getOutputStream(); |
||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); |
||||
|
response.setCharacterEncoding("utf-8"); |
||||
|
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
|
||||
|
String fileName = URLEncoder.encode(recordDetail.getName(), "UTF-8").replaceAll("\\+", "%20"); |
||||
|
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); |
||||
|
//创建 Cell 样式
|
||||
|
WriteCellStyle writeCellStyle = new WriteCellStyle(); |
||||
|
//设置垂直对齐方式
|
||||
|
writeCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); |
||||
|
//设置水平对齐方式
|
||||
|
writeCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); |
||||
|
ExcelWriter writer = EasyExcel.write(outputStream).autoCloseStream(false).build(); |
||||
|
//总览表格的sheet
|
||||
|
WriteSheet recordSheet = EasyExcel.writerSheet("总览").head(RecordExcel.class).build(); |
||||
|
//详情表格的sheet
|
||||
|
ExcelWriterSheetBuilder sheetBuilder = EasyExcel.writerSheet("详情") |
||||
|
.registerWriteHandler(new HorizontalCellStyleStrategy(null, writeCellStyle)) |
||||
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) |
||||
|
.head(RecordContentExcel.class); |
||||
|
//添加单元格合并策略
|
||||
|
for (OnceAbsoluteMergeStrategy mergeStrategy : mergeStrategies) { |
||||
|
sheetBuilder.registerWriteHandler(mergeStrategy); |
||||
|
} |
||||
|
WriteSheet contentSheet = sheetBuilder.build(); |
||||
|
writer.write(Collections.singletonList(recordExcel), recordSheet); |
||||
|
writer.write(recordContentExcelList, contentSheet); |
||||
|
writer.finish(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("下载文件失败", e); |
||||
|
response.reset(); |
||||
|
response.setContentType("application/json"); |
||||
|
response.setCharacterEncoding("utf-8"); |
||||
|
Map<String, String> map = MapUtils.newHashMap(); |
||||
|
map.put("status", "failure"); |
||||
|
map.put("message", "下载文件失败" + e.getMessage()); |
||||
|
try { |
||||
|
response.getWriter().println(JSON.toJSONString(map)); |
||||
|
} catch (IOException ex) { |
||||
|
throw new RuntimeException(ex); |
||||
|
} |
||||
|
} finally { |
||||
|
try { |
||||
|
if (outputStream != null) { |
||||
|
outputStream.close(); |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public PageResult<RecordItem> getPages(PageRequest pageRequest) { |
||||
|
Page<RecordItem> page = new Page(pageRequest.getPageNum(), pageRequest.getPageSize()); |
||||
|
Page<RecordItem> result = recordMapper.pageList(page, null); |
||||
|
return PageResult.of((int) result.getTotal(), (int) result.getSize(), (int) result.getCurrent(), result.getRecords()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public void updateRecordContent(RecordContentEntity recordContentEntity) { |
||||
|
recordContentMapper.updateById(recordContentEntity); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RecordModel getModels() { |
||||
|
return recognitionModelClient.models(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,36 @@ |
|||||
|
package com.jiagutech.service; |
||||
|
|
||||
|
import org.springframework.stereotype.Service; |
||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; |
||||
|
|
||||
|
import java.util.concurrent.ConcurrentHashMap; |
||||
|
|
||||
|
@Service |
||||
|
public class TaskProgressSSEService { |
||||
|
|
||||
|
private final ConcurrentHashMap<String, SseEmitter> sseEmitters = new ConcurrentHashMap<>(); |
||||
|
public SseEmitter createProgressEmitter(String taskId) { |
||||
|
SseEmitter emitter = new SseEmitter(); |
||||
|
sseEmitters.put(taskId, emitter); |
||||
|
return emitter; |
||||
|
} |
||||
|
|
||||
|
public void sendProgress(String taskId, int progress) throws Exception { |
||||
|
SseEmitter emitter = sseEmitters.get(taskId); |
||||
|
if (emitter != null) { |
||||
|
emitter.send(progress); |
||||
|
if (progress >= 100) { |
||||
|
emitter.complete(); |
||||
|
sseEmitters.remove(taskId); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void completeEmitter(String taskId) { |
||||
|
SseEmitter emitter = sseEmitters.get(taskId); |
||||
|
if (emitter != null) { |
||||
|
emitter.complete(); |
||||
|
sseEmitters.remove(taskId); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,13 @@ |
|||||
|
package com.jiagutech.service; |
||||
|
|
||||
|
import com.jiagutech.dto.LoginRequest; |
||||
|
import com.jiagutech.dto.LoginResponse; |
||||
|
import com.jiagutech.dto.UserRequest; |
||||
|
|
||||
|
public interface UserService { |
||||
|
LoginResponse login(LoginRequest loginBody); |
||||
|
|
||||
|
void register(UserRequest user); |
||||
|
|
||||
|
void approval(Long userId); |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
package com.jiagutech.service; |
||||
|
|
||||
|
import cn.dev33.satoken.secure.BCrypt; |
||||
|
import cn.dev33.satoken.stp.StpUtil; |
||||
|
import com.jiagutech.common.UserConstants; |
||||
|
import com.jiagutech.common.exception.BizCode; |
||||
|
import com.jiagutech.common.exception.BusinessException; |
||||
|
import com.jiagutech.dto.LoginRequest; |
||||
|
import com.jiagutech.dto.LoginResponse; |
||||
|
import com.jiagutech.dto.LoginUser; |
||||
|
import com.jiagutech.dto.UserRequest; |
||||
|
import com.jiagutech.entity.UserEntity; |
||||
|
import com.jiagutech.mapper.UserMapper; |
||||
|
import com.jiagutech.utils.EmailUtil; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.time.LocalDateTime; |
||||
|
|
||||
|
@Service |
||||
|
@RequiredArgsConstructor |
||||
|
public class UserServiceImpl implements UserService { |
||||
|
private final UserMapper userMapper; |
||||
|
private final EmailUtil emailUtil; |
||||
|
|
||||
|
|
||||
|
@Override |
||||
|
public LoginResponse login(LoginRequest loginBody) { |
||||
|
String phone = loginBody.getPhone(); |
||||
|
String password = loginBody.getPassword(); |
||||
|
LoginUser user = userMapper.selectLoginUserByPhone(phone); |
||||
|
if (user == null) { |
||||
|
throw new BusinessException(BizCode.USER_NOT_FOUND); |
||||
|
} |
||||
|
if (user.getStatus() != 1) { |
||||
|
throw new BusinessException(BizCode.USER_STATUS_ERROR); |
||||
|
} |
||||
|
boolean checkPw = BCrypt.checkpw(password, user.getPassword()); |
||||
|
if (!checkPw) { |
||||
|
throw new BusinessException(BizCode.PASSWORD_ERROR); |
||||
|
} |
||||
|
StpUtil.login(user.getUserId()); |
||||
|
user.setLoginTime(System.currentTimeMillis()); |
||||
|
StpUtil.getSession().set(UserConstants.SYS_SESSION, user); |
||||
|
LoginResponse loginVO = new LoginResponse(); |
||||
|
loginVO.setAccessToken(StpUtil.getTokenValue()); |
||||
|
loginVO.setExpireIn(StpUtil.getTokenTimeout()); |
||||
|
return loginVO; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void register(UserRequest user) { |
||||
|
UserEntity userEntity = new UserEntity() |
||||
|
.setCreateTime(LocalDateTime.now()) |
||||
|
.setStatus(2) |
||||
|
.setRealName(user.getRealName()) |
||||
|
.setUnitName(user.getUnitName()) |
||||
|
.setPhone(user.getPhone()) |
||||
|
.setPassword(BCrypt.hashpw(user.getPassword())); |
||||
|
userMapper.insert(userEntity); |
||||
|
emailUtil.sendEmail(null, user.getPhone(), user.getUnitName(), user.getRealName()); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void approval(Long userId) { |
||||
|
UserEntity userEntity = userMapper.selectById(userId); |
||||
|
userEntity.setStatus(1); |
||||
|
userMapper.updateById(userEntity); |
||||
|
} |
||||
|
} |
@ -0,0 +1,45 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.mail.javamail.JavaMailSender; |
||||
|
import org.springframework.mail.javamail.MimeMessageHelper; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.mail.MessagingException; |
||||
|
import javax.mail.internet.MimeMessage; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class EmailUtil { |
||||
|
|
||||
|
private final JavaMailSender mailSender; |
||||
|
@Value("${email.to}") |
||||
|
private String MAILTO; |
||||
|
|
||||
|
public void sendEmail(String to, String phone, String unitName, String realName) { |
||||
|
try { |
||||
|
// 构建邮件
|
||||
|
MimeMessage message = mailSender.createMimeMessage(); |
||||
|
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); |
||||
|
|
||||
|
helper.setFrom("noreply@pushmail.jiagutech.com"); |
||||
|
String receiver = to == null ? MAILTO : to; |
||||
|
helper.setTo(receiver); |
||||
|
helper.setSubject("新注册用户待审核"); |
||||
|
|
||||
|
String text = String.format("【用户待审核】现有<b>%s</b>用户<b>%s(%s)</b><br>已注册成为角果识别系统的新用户" + |
||||
|
",请您尽快审核!<br><br>", unitName, realName, phone); |
||||
|
helper.setText(text, true); // true 表示 HTML 格式
|
||||
|
|
||||
|
// 发送邮件
|
||||
|
mailSender.send(message); |
||||
|
log.info("邮件发送成功: {}", receiver); |
||||
|
|
||||
|
} catch (MessagingException e) { |
||||
|
log.error("邮件发送失败: ", e); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,52 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import cn.hutool.core.convert.Convert; |
||||
|
import cn.hutool.core.util.ObjectUtil; |
||||
|
import com.alibaba.excel.converters.Converter; |
||||
|
import com.alibaba.excel.enums.CellDataTypeEnum; |
||||
|
import com.alibaba.excel.metadata.GlobalConfiguration; |
||||
|
import com.alibaba.excel.metadata.data.ReadCellData; |
||||
|
import com.alibaba.excel.metadata.data.WriteCellData; |
||||
|
import com.alibaba.excel.metadata.property.ExcelContentProperty; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
|
||||
|
import java.math.BigDecimal; |
||||
|
|
||||
|
/** |
||||
|
* 大数值转换 |
||||
|
* Excel 数值长度位15位 大于15位的数值转换位字符串 |
||||
|
* |
||||
|
* @author Lion Li |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public class ExcelBigNumberConvert implements Converter<Long> { |
||||
|
|
||||
|
@Override |
||||
|
public Class<Long> supportJavaTypeKey() { |
||||
|
return Long.class; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CellDataTypeEnum supportExcelTypeKey() { |
||||
|
return CellDataTypeEnum.STRING; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Long convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { |
||||
|
return Convert.toLong(cellData.getData()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public WriteCellData<Object> convertToExcelData(Long object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { |
||||
|
if (ObjectUtil.isNotNull(object)) { |
||||
|
String str = Convert.toStr(object); |
||||
|
if (str.length() > 15) { |
||||
|
return new WriteCellData<>(str); |
||||
|
} |
||||
|
} |
||||
|
WriteCellData<Object> cellData = new WriteCellData<>(new BigDecimal(object)); |
||||
|
cellData.setType(CellDataTypeEnum.NUMBER); |
||||
|
return cellData; |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,53 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import cn.hutool.core.util.IdUtil; |
||||
|
import com.alibaba.excel.EasyExcel; |
||||
|
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder; |
||||
|
import com.alibaba.excel.write.handler.impl.DefaultRowWriteHandler; |
||||
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; |
||||
|
|
||||
|
import javax.servlet.ServletOutputStream; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import java.io.IOException; |
||||
|
import java.net.URLEncoder; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* @ClassName ExcelUtil |
||||
|
* @author: zhangyeguang |
||||
|
* @create: 2024-09-04 10:28 |
||||
|
* @Version 1.0 |
||||
|
* @description: |
||||
|
**/ |
||||
|
public class ExcelUtil { |
||||
|
|
||||
|
|
||||
|
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) { |
||||
|
try { |
||||
|
String filename = IdUtil.fastSimpleUUID() + "_" + sheetName + ".xlsx"; |
||||
|
String percentEncodedFileName = URLEncoder.encode(filename, String.valueOf(StandardCharsets.UTF_8)).replaceAll("\\+", "%20"); |
||||
|
String contentDispositionValue = String.format( |
||||
|
"attachment; filename=%s;filename*=utf-8''%s", |
||||
|
percentEncodedFileName, |
||||
|
percentEncodedFileName |
||||
|
); |
||||
|
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); |
||||
|
response.setHeader("Content-disposition", contentDispositionValue); |
||||
|
response.setHeader("download-filename", percentEncodedFileName); |
||||
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); |
||||
|
ServletOutputStream os = response.getOutputStream(); |
||||
|
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz) |
||||
|
.autoCloseStream(false) |
||||
|
// 自动适配
|
||||
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) |
||||
|
// 大数值自动转换 防止失真
|
||||
|
.registerConverter(new ExcelBigNumberConvert()) |
||||
|
.sheet(sheetName); |
||||
|
builder.registerWriteHandler(new DefaultRowWriteHandler()); |
||||
|
builder.doWrite(list); |
||||
|
} catch (IOException e) { |
||||
|
throw new RuntimeException("导出Excel异常"); |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,300 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import com.alibaba.fastjson2.JSON; |
||||
|
import com.obs.services.ObsClient; |
||||
|
import com.obs.services.exception.ObsException; |
||||
|
import com.obs.services.model.*; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.commons.io.IOUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import java.io.*; |
||||
|
import java.util.Calendar; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* @author Lenovo |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class HuaweiObs { |
||||
|
|
||||
|
//Access Key Id
|
||||
|
//private String ak = "Access Key Id";
|
||||
|
|
||||
|
//Secret Access Key
|
||||
|
//private String sk = "Secret Access Key";
|
||||
|
|
||||
|
//桶名称
|
||||
|
private String bucketName = "jg-iot"; |
||||
|
|
||||
|
// 终端节点访问Endpoint
|
||||
|
//private String endpoint = "Endpoint";
|
||||
|
|
||||
|
// 文件目录
|
||||
|
private String prifix = "/recognition"; |
||||
|
|
||||
|
// 访问域名 在域名后面或文件目录前加“/”
|
||||
|
private String path = "/recognition/"; |
||||
|
|
||||
|
|
||||
|
@Autowired |
||||
|
private ObsClient obsClient; |
||||
|
|
||||
|
/** |
||||
|
* 文件上传 |
||||
|
* |
||||
|
* @param file |
||||
|
* @return |
||||
|
* @throws IOException |
||||
|
*/ |
||||
|
public String upload(MultipartFile file) throws Exception { |
||||
|
//ObsClient obsClient = null;
|
||||
|
|
||||
|
Calendar cal = Calendar.getInstance(); |
||||
|
int year = cal.get(Calendar.YEAR); |
||||
|
int month = cal.get(Calendar.MONTH); |
||||
|
int day = cal.get(Calendar.DATE); |
||||
|
|
||||
|
String fileName = Md5Util.calculateMD5(file.getInputStream()); |
||||
|
|
||||
|
String objectName = prifix + "/" + year + "/" + month + "/" + day + "/" + fileName; |
||||
|
//obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
PutObjectResult response = obsClient.putObject(bucketName, objectName, file.getInputStream()); |
||||
|
log.info(JSON.toJSONString(response)); |
||||
|
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
|
||||
|
int statusCode = response.getStatusCode(); |
||||
|
|
||||
|
if (200 == statusCode) { |
||||
|
|
||||
|
return response.getObjectUrl(); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 上传文件--流式 |
||||
|
* |
||||
|
* @param fileName 上传文件名称 |
||||
|
* @param is 文件流 |
||||
|
* @return |
||||
|
*/ |
||||
|
public String uploadFile(String fileName, InputStream is) { |
||||
|
try { |
||||
|
if (is.available() == 0) { |
||||
|
log.error("InputStream is empty."); |
||||
|
return null; |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
|
||||
|
Calendar cal = Calendar.getInstance(); |
||||
|
int year = cal.get(Calendar.YEAR); |
||||
|
int month = cal.get(Calendar.MONTH); |
||||
|
int day = cal.get(Calendar.DATE); |
||||
|
String objectName = prifix + "/" + year + "/" + month + "/" + day + "/" + fileName; |
||||
|
PutObjectResult response = obsClient.putObject(bucketName, objectName, is); |
||||
|
log.info(JSON.toJSONString(response)); |
||||
|
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
|
||||
|
int statusCode = response.getStatusCode(); |
||||
|
|
||||
|
if (200 == statusCode) { |
||||
|
|
||||
|
return response.getObjectUrl(); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
public String uploadFile(File file) { |
||||
|
|
||||
|
try { |
||||
|
String subbfix = file.getName().substring(file.getName().indexOf(".")); |
||||
|
String fileName = UUID.randomUUID().toString().replaceAll("-", "") + subbfix; |
||||
|
Calendar cal = Calendar.getInstance(); |
||||
|
int year = cal.get(Calendar.YEAR); |
||||
|
int month = cal.get(Calendar.MONTH); |
||||
|
int day = cal.get(Calendar.DATE); |
||||
|
String objectName = prifix + "/" + year + "/" + month + "/" + fileName; |
||||
|
PutObjectRequest putObjectRequest = new PutObjectRequest(); |
||||
|
putObjectRequest.setFile(file); |
||||
|
putObjectRequest.setBucketName(bucketName); |
||||
|
putObjectRequest.setObjectKey(objectName); |
||||
|
PutObjectResult response = obsClient.putObject(putObjectRequest); |
||||
|
log.info(JSON.toJSONString(response)); |
||||
|
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
|
||||
|
int statusCode = response.getStatusCode(); |
||||
|
if (200 == statusCode) { |
||||
|
return response.getObjectUrl(); |
||||
|
} |
||||
|
} catch (ObsException e) { |
||||
|
log.error("文件上传失败:http code={},error code={},error message={}", e.getResponseCode(), e.getErrorCode(), e.getErrorMessage(), e); |
||||
|
} catch (Exception e) { |
||||
|
throw new RuntimeException(e); |
||||
|
} |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 上传文件--字节数组 |
||||
|
* |
||||
|
* @param fileName 上传文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @param is 文件流 |
||||
|
* @return |
||||
|
*/ |
||||
|
public boolean uploadFileByte(String fileName, FileType fileType, byte[] is) { |
||||
|
//ObsClient obsClient = null;
|
||||
|
try { |
||||
|
String objectName = fileType.getType().concat("/").concat(fileName); |
||||
|
//obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
HeaderResponse response = obsClient.putObject(bucketName, objectName, new ByteArrayInputStream(is)); |
||||
|
// 可选:调用成功后,记录调用成功的HTTP状态码和服务端请求ID
|
||||
|
int statusCode = response.getStatusCode(); |
||||
|
if (200 == statusCode) { |
||||
|
return true; |
||||
|
} |
||||
|
obsClient.close(); |
||||
|
} catch (IOException e) { |
||||
|
log.info("文件上传失败:{}", e.getMessage(), e); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载文件 |
||||
|
* |
||||
|
* @param fileName 文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @return |
||||
|
*/ |
||||
|
public String getDownloadUrl(String fileName, FileType fileType) { |
||||
|
//ObsClient obsClient = null;
|
||||
|
//obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
// URL有效期,3600秒.5分钟
|
||||
|
long expireSeconds = 3600L; |
||||
|
String objectName = fileType.getType().concat("/").concat(fileName); |
||||
|
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.GET, expireSeconds); |
||||
|
request.setBucketName(bucketName); |
||||
|
request.setObjectKey(objectName); |
||||
|
TemporarySignatureResponse response = obsClient.createTemporarySignature(request); |
||||
|
return response.getSignedUrl(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取上传地址 |
||||
|
* |
||||
|
* @param fileName 文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @return |
||||
|
*/ |
||||
|
public String getUploadUrl(String fileName, FileType fileType) { |
||||
|
try { |
||||
|
// 创建ObsClient实例
|
||||
|
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
// URL有效期,3600秒
|
||||
|
long expireSeconds = 3600L; |
||||
|
Map<String, String> headers = new HashMap<String, String>(); |
||||
|
headers.put("Content-Type", "application/octet-stream"); |
||||
|
String objectName = fileType.getType().concat("/").concat(fileName); |
||||
|
TemporarySignatureRequest request = new TemporarySignatureRequest(HttpMethodEnum.PUT, expireSeconds); |
||||
|
request.setBucketName(bucketName); |
||||
|
request.setObjectKey(objectName); |
||||
|
request.setHeaders(headers); |
||||
|
TemporarySignatureResponse response = obsClient.createTemporarySignature(request); |
||||
|
return response.getSignedUrl(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("获取上传地址异常:{}", e.getMessage(), e); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载文件返回字节数组 |
||||
|
* |
||||
|
* @param fileName 文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @return |
||||
|
*/ |
||||
|
public byte[] downloadFile(String fileName, FileType fileType) { |
||||
|
try { |
||||
|
InputStream inputStream = downloadFileInputStream(fileName, fileType); |
||||
|
byte[] bytes = IOUtils.toByteArray(inputStream); |
||||
|
return bytes; |
||||
|
} catch (Exception e) { |
||||
|
log.error("下载文件异常:{}", e.getMessage(), e); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 上传视频 |
||||
|
* |
||||
|
* @param fileName 文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @return |
||||
|
*/ |
||||
|
public boolean uploadFileVideo(String fileName, FileType fileType, InputStream is) { |
||||
|
try { |
||||
|
String objectName = fileType.getType().concat("/").concat(fileName); |
||||
|
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
// 添加 ContentType (添加后可在浏览器中直接浏览,而非下载链接)
|
||||
|
obsClient.putObject(bucketName, objectName, is); |
||||
|
obsClient.close(); |
||||
|
return true; |
||||
|
} catch (Exception e) { |
||||
|
log.error("上传视频文件异常:{}", e.getMessage(), e); |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 下载文件返回流式 |
||||
|
* |
||||
|
* @param fileName 文件名称 |
||||
|
* @param fileType 文件路径 |
||||
|
* @return |
||||
|
*/ |
||||
|
public InputStream downloadFileInputStream(String fileName, FileType fileType) { |
||||
|
try { |
||||
|
String objectName = fileType.getType().concat("/").concat(fileName); |
||||
|
// 用户拿到STS临时凭证后,通过其中的安全令牌(SecurityToken)和临时访问密钥(AccessKeyId和AccessKeySecret)生成OSSClient。
|
||||
|
//ObsClient obsClient = new ObsClient(ak, sk, endpoint);
|
||||
|
ObsObject obsObject = obsClient.getObject(bucketName, objectName); |
||||
|
obsClient.close(); |
||||
|
return obsObject.getObjectContent(); |
||||
|
} catch (Exception e) { |
||||
|
log.error("下载文件异常:{}", e.getMessage(), e); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public enum FileType { |
||||
|
TEST("TEST", "测试"); |
||||
|
|
||||
|
private String type; |
||||
|
private String desc; |
||||
|
|
||||
|
FileType(String type, String desc) { |
||||
|
this.type = type; |
||||
|
this.desc = desc; |
||||
|
} |
||||
|
|
||||
|
public String getType() { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
public String getDesc() { |
||||
|
return desc; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
@ -0,0 +1,14 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import cn.dev33.satoken.stp.StpUtil; |
||||
|
import com.jiagutech.common.UserConstants; |
||||
|
import com.jiagutech.dto.LoginUser; |
||||
|
|
||||
|
public class LoginUtil { |
||||
|
|
||||
|
|
||||
|
public static LoginUser getLoginUser() { |
||||
|
return (LoginUser) StpUtil.getSession().get(UserConstants.SYS_SESSION); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,34 @@ |
|||||
|
package com.jiagutech.utils; |
||||
|
|
||||
|
import java.io.InputStream; |
||||
|
import java.security.MessageDigest; |
||||
|
|
||||
|
public class Md5Util { |
||||
|
public static String calculateMD5(InputStream inputStream) throws Exception { |
||||
|
// 创建 MD5 消息摘要实例
|
||||
|
MessageDigest digest = MessageDigest.getInstance("MD5"); |
||||
|
|
||||
|
byte[] buffer = new byte[1024]; // 缓冲区大小
|
||||
|
int bytesRead; |
||||
|
|
||||
|
// 读取 InputStream 并更新 MD5 摘要
|
||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) { |
||||
|
digest.update(buffer, 0, bytesRead); |
||||
|
} |
||||
|
|
||||
|
// 获取 MD5 摘要结果
|
||||
|
byte[] md5Bytes = digest.digest(); |
||||
|
|
||||
|
// 转换为 16 进制字符串
|
||||
|
StringBuilder hexString = new StringBuilder(); |
||||
|
for (byte b : md5Bytes) { |
||||
|
String hex = Integer.toHexString(0xff & b); |
||||
|
if (hex.length() == 1) { |
||||
|
hexString.append('0'); |
||||
|
} |
||||
|
hexString.append(hex); |
||||
|
} |
||||
|
|
||||
|
return hexString.toString(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,101 @@ |
|||||
|
server: |
||||
|
port: 9196 |
||||
|
spring: |
||||
|
application: |
||||
|
name: siliqua_recognition |
||||
|
|
||||
|
datasource: |
||||
|
driver-class-name: com.mysql.cj.jdbc.Driver |
||||
|
url: jdbc:mysql://101.34.243.138:7306/siliqua_recognition?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true |
||||
|
username: ams |
||||
|
password: ams@1234 |
||||
|
type: com.zaxxer.hikari.HikariDataSource |
||||
|
hikari: |
||||
|
maximum-pool-size: 10 |
||||
|
minimum-idle: 5 |
||||
|
idle-timeout: 30000 |
||||
|
connection-timeout: 30000 |
||||
|
max-lifetime: 1800000 |
||||
|
pool-name: siliqua-recognition |
||||
|
|
||||
|
jackson: |
||||
|
date-format: yyyy-MM-dd HH:mm:ss |
||||
|
time-zone: GMT+8 |
||||
|
|
||||
|
servlet: |
||||
|
multipart: |
||||
|
enabled: true |
||||
|
max-file-size: 20MB |
||||
|
max-request-size: 500MB |
||||
|
redis: |
||||
|
host: 192.168.10.111 |
||||
|
port: 6379 |
||||
|
database: 8 |
||||
|
mail: |
||||
|
host: smtpdm.aliyun.com |
||||
|
port: 465 |
||||
|
username: noreply@pushmail.jiagutech.com |
||||
|
password: Uv4eG2qUxFOmzX |
||||
|
protocol: smtp |
||||
|
properties: |
||||
|
mail: |
||||
|
smtp: |
||||
|
auth: true |
||||
|
ssl: |
||||
|
enable: true |
||||
|
email: |
||||
|
to: yeguangzhang@126.com |
||||
|
mybatis-plus: |
||||
|
mapper-locations: classpath*:mapper/*.xml |
||||
|
type-aliases-package: com.jiagutech.entity |
||||
|
configuration: |
||||
|
map-underscore-to-camel-case: true |
||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl |
||||
|
|
||||
|
recognition: |
||||
|
model: |
||||
|
url: http://api.camacloud.org.cn/recognition |
||||
|
|
||||
|
sa-token: |
||||
|
# token名称 (同时也是cookie名称) |
||||
|
token-name: Authorization |
||||
|
token-prefix: Bearer |
||||
|
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) |
||||
|
is-concurrent: true |
||||
|
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) |
||||
|
is-share: false |
||||
|
# jwt秘钥 |
||||
|
jwt-secret-key: abcdefghijklmnopqrstuvwxyz |
||||
|
is-print: false |
||||
|
is-log: false |
||||
|
|
||||
|
huawei: |
||||
|
obs: |
||||
|
end-point: "obs.cn-east-2.myhuaweicloud.com" |
||||
|
ak: "NMUAWG3TN50OSJESCIRV" |
||||
|
sk: "qG0VFwhiTAtiCuUaMAxJL3zsusp3W4GHs7THR9pC" |
||||
|
bucket-name: iot |
||||
|
path-prefix: recognition |
||||
|
|
||||
|
|
||||
|
thread: |
||||
|
enabled: true |
||||
|
pool: |
||||
|
core-pool-size: 5 |
||||
|
max-pool-size: 8 |
||||
|
queue-capacity: 500 |
||||
|
thread-name: siliqua-recognition |
||||
|
task-threshold: 300000 |
||||
|
queue-size-threshold: 200 |
||||
|
queue-time-threshold: 1000 |
||||
|
|
||||
|
http: |
||||
|
template: |
||||
|
# 连接超时时间 |
||||
|
connect-timeout: 3000 |
||||
|
# 读取超时时间 |
||||
|
read-timeout: 20000 |
||||
|
logging: |
||||
|
level: |
||||
|
com.jiagutech.mapper: debug |
||||
|
root: info |
@ -0,0 +1,23 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.jiagutech.mapper.RecordContentMapper"> |
||||
|
<resultMap id="RecordContentMap" type="com.jiagutech.entity.RecordContentEntity"> |
||||
|
<id column="id" property="id"/> |
||||
|
<result column="record_id" property="recordId"/> |
||||
|
<result column="image_name" property="imageName"/> |
||||
|
<result column="image_url" property="imageUrl"/> |
||||
|
<result column="recognition_data" property="recognitionData" |
||||
|
typeHandler="com.jiagutech.handler.ModelPredictResponseListTypeHandler" |
||||
|
javaType="java.util.List"/> |
||||
|
<result column="siliqua_num" property="siliquaNum"/> |
||||
|
<result column="handle_num" property="handleNum"/> |
||||
|
<result column="beak_num" property="beakNum"/> |
||||
|
|
||||
|
</resultMap> |
||||
|
<select id="selectListByRecordId" resultMap="RecordContentMap"> |
||||
|
select * |
||||
|
from record_content |
||||
|
where record_id = #{recordId} |
||||
|
order by id asc |
||||
|
</select> |
||||
|
</mapper> |
@ -0,0 +1,30 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.jiagutech.mapper.RecordMapper"> |
||||
|
<resultMap id="RecordDetailMap" type="com.jiagutech.dto.RecordDetail"> |
||||
|
<id property="id" column="id"/> |
||||
|
<collection property="content" javaType="java.util.List" column="id" |
||||
|
select="com.jiagutech.mapper.RecordContentMapper.selectListByRecordId"/> |
||||
|
|
||||
|
</resultMap> |
||||
|
|
||||
|
<select id="getRecordDetail" resultMap="RecordDetailMap"> |
||||
|
select * |
||||
|
from record_info |
||||
|
where id = #{id} |
||||
|
</select> |
||||
|
<select id="pageList" resultType="com.jiagutech.dto.RecordItem"> |
||||
|
select r.id as recordId, |
||||
|
r.name as recordName, |
||||
|
r.user_id, |
||||
|
r.image_num as imageNum, |
||||
|
r.create_time as createTime, |
||||
|
u.real_name as userName, |
||||
|
u.unit_name as unitName, |
||||
|
u.phone |
||||
|
from record_info r |
||||
|
left join siliqua_user u on r.user_id = u.id |
||||
|
order by r.create_time desc |
||||
|
|
||||
|
</select> |
||||
|
</mapper> |
@ -0,0 +1,20 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
|
<mapper namespace="com.jiagutech.mapper.UserMapper"> |
||||
|
|
||||
|
|
||||
|
<select id="selectLoginUserByPhone" resultType="com.jiagutech.dto.LoginUser"> |
||||
|
select id as user_id, |
||||
|
real_name, |
||||
|
phone, |
||||
|
create_time, |
||||
|
password, |
||||
|
unit_name, |
||||
|
status |
||||
|
from siliqua_user |
||||
|
where phone = #{phone} |
||||
|
and status != 0 |
||||
|
</select> |
||||
|
|
||||
|
|
||||
|
</mapper> |
Loading…
Reference in new issue