Browse Source

角果识别后台

master
zhangyeguang 1 month ago
parent
commit
f05838a4ed
  1. 156
      .gitignore
  2. 199
      pom.xml
  3. 15
      src/main/java/com/jiagutech/RecognitionRun.java
  4. 61
      src/main/java/com/jiagutech/client/RecognitionModelClient.java
  5. 93
      src/main/java/com/jiagutech/common/HttpStatus.java
  6. 50
      src/main/java/com/jiagutech/common/Knife4jConfig.java
  7. 23
      src/main/java/com/jiagutech/common/MybatisPlusConfig.java
  8. 39
      src/main/java/com/jiagutech/common/ObsConfig.java
  9. 41
      src/main/java/com/jiagutech/common/RestTemplateConfig.java
  10. 33
      src/main/java/com/jiagutech/common/RestTemplateInterceptor.java
  11. 143
      src/main/java/com/jiagutech/common/UserConstants.java
  12. 32
      src/main/java/com/jiagutech/common/WebCrosConfig.java
  13. 63
      src/main/java/com/jiagutech/common/exception/BizCode.java
  14. 53
      src/main/java/com/jiagutech/common/exception/BusinessException.java
  15. 22
      src/main/java/com/jiagutech/common/exception/CodeInterface.java
  16. 80
      src/main/java/com/jiagutech/common/exception/GlobalExceptionHandler.java
  17. 38
      src/main/java/com/jiagutech/common/sa/SaPermissionImpl.java
  18. 30
      src/main/java/com/jiagutech/common/sa/SaTokenConfig.java
  19. 34
      src/main/java/com/jiagutech/common/sa/SaTokenConfigure.java
  20. 105
      src/main/java/com/jiagutech/common/thread/MonitorThreadPoolExecutor.java
  21. 40
      src/main/java/com/jiagutech/common/thread/MonitoredRunnable.java
  22. 21
      src/main/java/com/jiagutech/common/thread/NameThreadFactory.java
  23. 31
      src/main/java/com/jiagutech/common/thread/ThreadExecutorConfig.java
  24. 29
      src/main/java/com/jiagutech/common/thread/ThreadPoolConfig.java
  25. 118
      src/main/java/com/jiagutech/common/thread/ThreadStatistics.java
  26. 76
      src/main/java/com/jiagutech/controller/RecordController.java
  27. 61
      src/main/java/com/jiagutech/controller/UserController.java
  28. 38
      src/main/java/com/jiagutech/dto/CreateRecordRequest.java
  29. 21
      src/main/java/com/jiagutech/dto/LoginRequest.java
  30. 37
      src/main/java/com/jiagutech/dto/LoginResponse.java
  31. 86
      src/main/java/com/jiagutech/dto/LoginUser.java
  32. 10
      src/main/java/com/jiagutech/dto/ModelPredictData.java
  33. 14
      src/main/java/com/jiagutech/dto/ModelPredictResponse.java
  34. 31
      src/main/java/com/jiagutech/dto/RecordContentExcel.java
  35. 27
      src/main/java/com/jiagutech/dto/RecordDetail.java
  36. 31
      src/main/java/com/jiagutech/dto/RecordExcel.java
  37. 29
      src/main/java/com/jiagutech/dto/RecordItem.java
  38. 26
      src/main/java/com/jiagutech/dto/RecordModel.java
  39. 43
      src/main/java/com/jiagutech/dto/UserRequest.java
  40. 23
      src/main/java/com/jiagutech/dto/common/PageRequest.java
  41. 33
      src/main/java/com/jiagutech/dto/common/PageResult.java
  42. 110
      src/main/java/com/jiagutech/dto/common/R.java
  43. 42
      src/main/java/com/jiagutech/entity/RecordContentEntity.java
  44. 29
      src/main/java/com/jiagutech/entity/RecordEntity.java
  45. 28
      src/main/java/com/jiagutech/entity/UserEntity.java
  46. 26
      src/main/java/com/jiagutech/handler/AddCellRangeWriteHandler.java
  47. 103
      src/main/java/com/jiagutech/handler/ExcelFillCellMergeStrategy.java
  48. 27
      src/main/java/com/jiagutech/handler/ModelPredictResponseListTypeHandler.java
  49. 13
      src/main/java/com/jiagutech/mapper/RecordContentMapper.java
  50. 14
      src/main/java/com/jiagutech/mapper/RecordMapper.java
  51. 10
      src/main/java/com/jiagutech/mapper/UserMapper.java
  52. 93
      src/main/java/com/jiagutech/record/RecordTaskHandler.java
  53. 21
      src/main/java/com/jiagutech/record/RecordTaskManager.java
  54. 112
      src/main/java/com/jiagutech/record/RecordTaskProcessor.java
  55. 33
      src/main/java/com/jiagutech/service/RecordService.java
  56. 286
      src/main/java/com/jiagutech/service/RecordServiceImpl.java
  57. 36
      src/main/java/com/jiagutech/service/TaskProgressSSEService.java
  58. 13
      src/main/java/com/jiagutech/service/UserService.java
  59. 71
      src/main/java/com/jiagutech/service/UserServiceImpl.java
  60. 45
      src/main/java/com/jiagutech/utils/EmailUtil.java
  61. 52
      src/main/java/com/jiagutech/utils/ExcelBigNumberConvert.java
  62. 53
      src/main/java/com/jiagutech/utils/ExcelUtil.java
  63. 300
      src/main/java/com/jiagutech/utils/HuaweiObs.java
  64. 14
      src/main/java/com/jiagutech/utils/LoginUtil.java
  65. 34
      src/main/java/com/jiagutech/utils/Md5Util.java
  66. 101
      src/main/resources/application.yml
  67. 23
      src/main/resources/mapper/RecordContentMapper.xml
  68. 30
      src/main/resources/mapper/RecordMapper.xml
  69. 20
      src/main/resources/mapper/UserMapper.xml

156
.gitignore

@ -22,144 +22,20 @@
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid* hs_err_pid*
# ---> Python
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
.vscode
log
# ---> Maven
target/ target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
.idea/
.DS_Store

199
pom.xml

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

15
src/main/java/com/jiagutech/RecognitionRun.java

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

61
src/main/java/com/jiagutech/client/RecognitionModelClient.java

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

93
src/main/java/com/jiagutech/common/HttpStatus.java

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

50
src/main/java/com/jiagutech/common/Knife4jConfig.java

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

23
src/main/java/com/jiagutech/common/MybatisPlusConfig.java

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

39
src/main/java/com/jiagutech/common/ObsConfig.java

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

41
src/main/java/com/jiagutech/common/RestTemplateConfig.java

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

33
src/main/java/com/jiagutech/common/RestTemplateInterceptor.java

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

143
src/main/java/com/jiagutech/common/UserConstants.java

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

32
src/main/java/com/jiagutech/common/WebCrosConfig.java

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

63
src/main/java/com/jiagutech/common/exception/BizCode.java

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

53
src/main/java/com/jiagutech/common/exception/BusinessException.java

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

22
src/main/java/com/jiagutech/common/exception/CodeInterface.java

@ -0,0 +1,22 @@
package com.jiagutech.common.exception;
/**
* 业务异常接口
*/
public interface CodeInterface {
/**
* 消息
*
* @return String
*/
String getMsg();
/**
* 业务代码
*
* @return int
*/
Integer getCode();
}

80
src/main/java/com/jiagutech/common/exception/GlobalExceptionHandler.java

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

38
src/main/java/com/jiagutech/common/sa/SaPermissionImpl.java

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

30
src/main/java/com/jiagutech/common/sa/SaTokenConfig.java

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

34
src/main/java/com/jiagutech/common/sa/SaTokenConfigure.java

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

105
src/main/java/com/jiagutech/common/thread/MonitorThreadPoolExecutor.java

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

40
src/main/java/com/jiagutech/common/thread/MonitoredRunnable.java

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

21
src/main/java/com/jiagutech/common/thread/NameThreadFactory.java

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

31
src/main/java/com/jiagutech/common/thread/ThreadExecutorConfig.java

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

29
src/main/java/com/jiagutech/common/thread/ThreadPoolConfig.java

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

118
src/main/java/com/jiagutech/common/thread/ThreadStatistics.java

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

76
src/main/java/com/jiagutech/controller/RecordController.java

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

61
src/main/java/com/jiagutech/controller/UserController.java

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

38
src/main/java/com/jiagutech/dto/CreateRecordRequest.java

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

21
src/main/java/com/jiagutech/dto/LoginRequest.java

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

37
src/main/java/com/jiagutech/dto/LoginResponse.java

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

86
src/main/java/com/jiagutech/dto/LoginUser.java

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

10
src/main/java/com/jiagutech/dto/ModelPredictData.java

@ -0,0 +1,10 @@
package com.jiagutech.dto;
import lombok.Data;
import java.util.List;
@Data
public class ModelPredictData{
private List<ModelPredictResponse> recognitionData;
}

14
src/main/java/com/jiagutech/dto/ModelPredictResponse.java

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

31
src/main/java/com/jiagutech/dto/RecordContentExcel.java

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

27
src/main/java/com/jiagutech/dto/RecordDetail.java

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

31
src/main/java/com/jiagutech/dto/RecordExcel.java

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

29
src/main/java/com/jiagutech/dto/RecordItem.java

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

26
src/main/java/com/jiagutech/dto/RecordModel.java

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

43
src/main/java/com/jiagutech/dto/UserRequest.java

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

23
src/main/java/com/jiagutech/dto/common/PageRequest.java

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

33
src/main/java/com/jiagutech/dto/common/PageResult.java

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

110
src/main/java/com/jiagutech/dto/common/R.java

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

42
src/main/java/com/jiagutech/entity/RecordContentEntity.java

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

29
src/main/java/com/jiagutech/entity/RecordEntity.java

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

28
src/main/java/com/jiagutech/entity/UserEntity.java

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

26
src/main/java/com/jiagutech/handler/AddCellRangeWriteHandler.java

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

103
src/main/java/com/jiagutech/handler/ExcelFillCellMergeStrategy.java

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

27
src/main/java/com/jiagutech/handler/ModelPredictResponseListTypeHandler.java

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

13
src/main/java/com/jiagutech/mapper/RecordContentMapper.java

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

14
src/main/java/com/jiagutech/mapper/RecordMapper.java

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

10
src/main/java/com/jiagutech/mapper/UserMapper.java

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

93
src/main/java/com/jiagutech/record/RecordTaskHandler.java

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

21
src/main/java/com/jiagutech/record/RecordTaskManager.java

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

112
src/main/java/com/jiagutech/record/RecordTaskProcessor.java

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

33
src/main/java/com/jiagutech/service/RecordService.java

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

286
src/main/java/com/jiagutech/service/RecordServiceImpl.java

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

36
src/main/java/com/jiagutech/service/TaskProgressSSEService.java

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

13
src/main/java/com/jiagutech/service/UserService.java

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

71
src/main/java/com/jiagutech/service/UserServiceImpl.java

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

45
src/main/java/com/jiagutech/utils/EmailUtil.java

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

52
src/main/java/com/jiagutech/utils/ExcelBigNumberConvert.java

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

53
src/main/java/com/jiagutech/utils/ExcelUtil.java

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

300
src/main/java/com/jiagutech/utils/HuaweiObs.java

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

14
src/main/java/com/jiagutech/utils/LoginUtil.java

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

34
src/main/java/com/jiagutech/utils/Md5Util.java

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

101
src/main/resources/application.yml

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

23
src/main/resources/mapper/RecordContentMapper.xml

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

30
src/main/resources/mapper/RecordMapper.xml

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

20
src/main/resources/mapper/UserMapper.xml

@ -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…
Cancel
Save