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