From f05838a4ed4595a944281bdd27b740b83317e3d4 Mon Sep 17 00:00:00 2001 From: zhangyeguang Date: Fri, 22 Nov 2024 14:57:21 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=92=E6=9E=9C=E8=AF=86=E5=88=AB=E5=90=8E?= =?UTF-8?q?=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 156 +-------- pom.xml | 199 ++++++++++++ .../java/com/jiagutech/RecognitionRun.java | 15 + .../client/RecognitionModelClient.java | 61 ++++ .../java/com/jiagutech/common/HttpStatus.java | 93 ++++++ .../com/jiagutech/common/Knife4jConfig.java | 50 +++ .../jiagutech/common/MybatisPlusConfig.java | 23 ++ .../java/com/jiagutech/common/ObsConfig.java | 39 +++ .../jiagutech/common/RestTemplateConfig.java | 41 +++ .../common/RestTemplateInterceptor.java | 33 ++ .../com/jiagutech/common/UserConstants.java | 143 +++++++++ .../com/jiagutech/common/WebCrosConfig.java | 32 ++ .../jiagutech/common/exception/BizCode.java | 63 ++++ .../common/exception/BusinessException.java | 53 ++++ .../common/exception/CodeInterface.java | 22 ++ .../exception/GlobalExceptionHandler.java | 80 +++++ .../jiagutech/common/sa/SaPermissionImpl.java | 38 +++ .../jiagutech/common/sa/SaTokenConfig.java | 30 ++ .../jiagutech/common/sa/SaTokenConfigure.java | 34 ++ .../thread/MonitorThreadPoolExecutor.java | 105 ++++++ .../common/thread/MonitoredRunnable.java | 40 +++ .../common/thread/NameThreadFactory.java | 21 ++ .../common/thread/ThreadExecutorConfig.java | 31 ++ .../common/thread/ThreadPoolConfig.java | 29 ++ .../common/thread/ThreadStatistics.java | 118 +++++++ .../controller/RecordController.java | 76 +++++ .../jiagutech/controller/UserController.java | 61 ++++ .../jiagutech/dto/CreateRecordRequest.java | 38 +++ .../java/com/jiagutech/dto/LoginRequest.java | 21 ++ .../java/com/jiagutech/dto/LoginResponse.java | 37 +++ .../java/com/jiagutech/dto/LoginUser.java | 86 +++++ .../com/jiagutech/dto/ModelPredictData.java | 10 + .../jiagutech/dto/ModelPredictResponse.java | 14 + .../com/jiagutech/dto/RecordContentExcel.java | 31 ++ .../java/com/jiagutech/dto/RecordDetail.java | 27 ++ .../java/com/jiagutech/dto/RecordExcel.java | 31 ++ .../java/com/jiagutech/dto/RecordItem.java | 29 ++ .../java/com/jiagutech/dto/RecordModel.java | 26 ++ .../java/com/jiagutech/dto/UserRequest.java | 43 +++ .../com/jiagutech/dto/common/PageRequest.java | 23 ++ .../com/jiagutech/dto/common/PageResult.java | 33 ++ src/main/java/com/jiagutech/dto/common/R.java | 110 +++++++ .../jiagutech/entity/RecordContentEntity.java | 42 +++ .../com/jiagutech/entity/RecordEntity.java | 29 ++ .../java/com/jiagutech/entity/UserEntity.java | 28 ++ .../handler/AddCellRangeWriteHandler.java | 26 ++ .../handler/ExcelFillCellMergeStrategy.java | 103 ++++++ .../ModelPredictResponseListTypeHandler.java | 27 ++ .../jiagutech/mapper/RecordContentMapper.java | 13 + .../com/jiagutech/mapper/RecordMapper.java | 14 + .../java/com/jiagutech/mapper/UserMapper.java | 10 + .../jiagutech/record/RecordTaskHandler.java | 93 ++++++ .../jiagutech/record/RecordTaskManager.java | 21 ++ .../jiagutech/record/RecordTaskProcessor.java | 112 +++++++ .../com/jiagutech/service/RecordService.java | 33 ++ .../jiagutech/service/RecordServiceImpl.java | 286 +++++++++++++++++ .../service/TaskProgressSSEService.java | 36 +++ .../com/jiagutech/service/UserService.java | 13 + .../jiagutech/service/UserServiceImpl.java | 71 +++++ .../java/com/jiagutech/utils/EmailUtil.java | 45 +++ .../utils/ExcelBigNumberConvert.java | 52 +++ .../java/com/jiagutech/utils/ExcelUtil.java | 53 ++++ .../java/com/jiagutech/utils/HuaweiObs.java | 300 ++++++++++++++++++ .../java/com/jiagutech/utils/LoginUtil.java | 14 + .../java/com/jiagutech/utils/Md5Util.java | 34 ++ src/main/resources/application.yml | 101 ++++++ .../resources/mapper/RecordContentMapper.xml | 23 ++ src/main/resources/mapper/RecordMapper.xml | 30 ++ src/main/resources/mapper/UserMapper.xml | 20 ++ 69 files changed, 3734 insertions(+), 140 deletions(-) create mode 100644 pom.xml create mode 100644 src/main/java/com/jiagutech/RecognitionRun.java create mode 100644 src/main/java/com/jiagutech/client/RecognitionModelClient.java create mode 100644 src/main/java/com/jiagutech/common/HttpStatus.java create mode 100644 src/main/java/com/jiagutech/common/Knife4jConfig.java create mode 100644 src/main/java/com/jiagutech/common/MybatisPlusConfig.java create mode 100644 src/main/java/com/jiagutech/common/ObsConfig.java create mode 100644 src/main/java/com/jiagutech/common/RestTemplateConfig.java create mode 100644 src/main/java/com/jiagutech/common/RestTemplateInterceptor.java create mode 100644 src/main/java/com/jiagutech/common/UserConstants.java create mode 100644 src/main/java/com/jiagutech/common/WebCrosConfig.java create mode 100644 src/main/java/com/jiagutech/common/exception/BizCode.java create mode 100644 src/main/java/com/jiagutech/common/exception/BusinessException.java create mode 100644 src/main/java/com/jiagutech/common/exception/CodeInterface.java create mode 100644 src/main/java/com/jiagutech/common/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/jiagutech/common/sa/SaPermissionImpl.java create mode 100644 src/main/java/com/jiagutech/common/sa/SaTokenConfig.java create mode 100644 src/main/java/com/jiagutech/common/sa/SaTokenConfigure.java create mode 100644 src/main/java/com/jiagutech/common/thread/MonitorThreadPoolExecutor.java create mode 100644 src/main/java/com/jiagutech/common/thread/MonitoredRunnable.java create mode 100644 src/main/java/com/jiagutech/common/thread/NameThreadFactory.java create mode 100644 src/main/java/com/jiagutech/common/thread/ThreadExecutorConfig.java create mode 100644 src/main/java/com/jiagutech/common/thread/ThreadPoolConfig.java create mode 100644 src/main/java/com/jiagutech/common/thread/ThreadStatistics.java create mode 100644 src/main/java/com/jiagutech/controller/RecordController.java create mode 100644 src/main/java/com/jiagutech/controller/UserController.java create mode 100644 src/main/java/com/jiagutech/dto/CreateRecordRequest.java create mode 100644 src/main/java/com/jiagutech/dto/LoginRequest.java create mode 100644 src/main/java/com/jiagutech/dto/LoginResponse.java create mode 100644 src/main/java/com/jiagutech/dto/LoginUser.java create mode 100644 src/main/java/com/jiagutech/dto/ModelPredictData.java create mode 100644 src/main/java/com/jiagutech/dto/ModelPredictResponse.java create mode 100644 src/main/java/com/jiagutech/dto/RecordContentExcel.java create mode 100644 src/main/java/com/jiagutech/dto/RecordDetail.java create mode 100644 src/main/java/com/jiagutech/dto/RecordExcel.java create mode 100644 src/main/java/com/jiagutech/dto/RecordItem.java create mode 100644 src/main/java/com/jiagutech/dto/RecordModel.java create mode 100644 src/main/java/com/jiagutech/dto/UserRequest.java create mode 100644 src/main/java/com/jiagutech/dto/common/PageRequest.java create mode 100644 src/main/java/com/jiagutech/dto/common/PageResult.java create mode 100644 src/main/java/com/jiagutech/dto/common/R.java create mode 100644 src/main/java/com/jiagutech/entity/RecordContentEntity.java create mode 100644 src/main/java/com/jiagutech/entity/RecordEntity.java create mode 100644 src/main/java/com/jiagutech/entity/UserEntity.java create mode 100644 src/main/java/com/jiagutech/handler/AddCellRangeWriteHandler.java create mode 100644 src/main/java/com/jiagutech/handler/ExcelFillCellMergeStrategy.java create mode 100644 src/main/java/com/jiagutech/handler/ModelPredictResponseListTypeHandler.java create mode 100644 src/main/java/com/jiagutech/mapper/RecordContentMapper.java create mode 100644 src/main/java/com/jiagutech/mapper/RecordMapper.java create mode 100644 src/main/java/com/jiagutech/mapper/UserMapper.java create mode 100644 src/main/java/com/jiagutech/record/RecordTaskHandler.java create mode 100644 src/main/java/com/jiagutech/record/RecordTaskManager.java create mode 100644 src/main/java/com/jiagutech/record/RecordTaskProcessor.java create mode 100644 src/main/java/com/jiagutech/service/RecordService.java create mode 100644 src/main/java/com/jiagutech/service/RecordServiceImpl.java create mode 100644 src/main/java/com/jiagutech/service/TaskProgressSSEService.java create mode 100644 src/main/java/com/jiagutech/service/UserService.java create mode 100644 src/main/java/com/jiagutech/service/UserServiceImpl.java create mode 100644 src/main/java/com/jiagutech/utils/EmailUtil.java create mode 100644 src/main/java/com/jiagutech/utils/ExcelBigNumberConvert.java create mode 100644 src/main/java/com/jiagutech/utils/ExcelUtil.java create mode 100644 src/main/java/com/jiagutech/utils/HuaweiObs.java create mode 100644 src/main/java/com/jiagutech/utils/LoginUtil.java create mode 100644 src/main/java/com/jiagutech/utils/Md5Util.java create mode 100644 src/main/resources/application.yml create mode 100644 src/main/resources/mapper/RecordContentMapper.xml create mode 100644 src/main/resources/mapper/RecordMapper.xml create mode 100644 src/main/resources/mapper/UserMapper.xml diff --git a/.gitignore b/.gitignore index bb2b050..0f4c0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,144 +22,20 @@ # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 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/ - -# 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 diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f1d8fac --- /dev/null +++ b/pom.xml @@ -0,0 +1,199 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.6.RELEASE + + + com.jiagutech + siliqua_recognition + 0.0.1-SNAPSHOT + siliqua_recognition + 角果识别 + + + + 8 + 8 + 8 + UTF-8 + 1.6.1 + 1.18.30 + 5.8.22 + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-web + + + + + + + + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + org.mybatis.spring.boot + mybatis-spring-boot-starter + 2.3.2 + + + + com.baomidou + mybatis-plus-boot-starter + 3.5.0 + + + org.mybatis + mybatis + + + + + com.mysql + mysql-connector-j + 8.2.0 + runtime + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.projectlombok + lombok + true + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + com.alibaba.fastjson2 + fastjson2 + 2.0.43 + + + + org.apache.commons + commons-lang3 + 3.12.0 + + + com.google.code.gson + gson + 2.10.1 + + + + + com.alibaba + easyexcel + 4.0.2 + + + commons-io + commons-io + 2.16.1 + + + + cn.dev33 + sa-token-spring-boot-starter + 1.37.0 + + + cn.dev33 + sa-token-jwt + 1.37.0 + + + cn.dev33 + sa-token-redis-jackson + 1.37.0 + + + + + + cn.hutool + hutool-bom + ${hutool.version} + pom + import + + + com.huaweicloud + esdk-obs-java + 3.20.6.1 + compile + + + + org.apache.logging.log4j + log4j-core + 2.23.1 + + + org.apache.logging.log4j + log4j-api + 2.23.1 + + + io.springfox + springfox-swagger2 + 2.7.0 + + + + io.springfox + springfox-swagger-ui + 2.7.0 + + + com.github.xiaoymin + knife4j-spring-boot-starter + 2.0.8 + + + org.springframework.boot + spring-boot-starter-mail + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + + diff --git a/src/main/java/com/jiagutech/RecognitionRun.java b/src/main/java/com/jiagutech/RecognitionRun.java new file mode 100644 index 0000000..3e8d5b1 --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/com/jiagutech/client/RecognitionModelClient.java b/src/main/java/com/jiagutech/client/RecognitionModelClient.java new file mode 100644 index 0000000..0be6b68 --- /dev/null +++ b/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 predict(File file, String modeName, float lou, float conf) { + MultiValueMap 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> requestEntity = new HttpEntity<>(body, headers); + ResponseEntity 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); + } + + +} diff --git a/src/main/java/com/jiagutech/common/HttpStatus.java b/src/main/java/com/jiagutech/common/HttpStatus.java new file mode 100644 index 0000000..1fa257e --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/jiagutech/common/Knife4jConfig.java b/src/main/java/com/jiagutech/common/Knife4jConfig.java new file mode 100644 index 0000000..f0f6341 --- /dev/null +++ b/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(); + } +} + diff --git a/src/main/java/com/jiagutech/common/MybatisPlusConfig.java b/src/main/java/com/jiagutech/common/MybatisPlusConfig.java new file mode 100644 index 0000000..3a78489 --- /dev/null +++ b/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; + } + +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/ObsConfig.java b/src/main/java/com/jiagutech/common/ObsConfig.java new file mode 100644 index 0000000..b566b6b --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/RestTemplateConfig.java b/src/main/java/com/jiagutech/common/RestTemplateConfig.java new file mode 100644 index 0000000..8774730 --- /dev/null +++ b/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; + } +} + diff --git a/src/main/java/com/jiagutech/common/RestTemplateInterceptor.java b/src/main/java/com/jiagutech/common/RestTemplateInterceptor.java new file mode 100644 index 0000000..4922d98 --- /dev/null +++ b/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; + + } +} diff --git a/src/main/java/com/jiagutech/common/UserConstants.java b/src/main/java/com/jiagutech/common/UserConstants.java new file mode 100644 index 0000000..bcb634d --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/jiagutech/common/WebCrosConfig.java b/src/main/java/com/jiagutech/common/WebCrosConfig.java new file mode 100644 index 0000000..392aee8 --- /dev/null +++ b/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); + } + +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/exception/BizCode.java b/src/main/java/com/jiagutech/common/exception/BizCode.java new file mode 100644 index 0000000..1ebe4a3 --- /dev/null +++ b/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; + } + +} diff --git a/src/main/java/com/jiagutech/common/exception/BusinessException.java b/src/main/java/com/jiagutech/common/exception/BusinessException.java new file mode 100644 index 0000000..ac9e0a7 --- /dev/null +++ b/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; + } +} diff --git a/src/main/java/com/jiagutech/common/exception/CodeInterface.java b/src/main/java/com/jiagutech/common/exception/CodeInterface.java new file mode 100644 index 0000000..5f8e091 --- /dev/null +++ b/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(); + +} diff --git a/src/main/java/com/jiagutech/common/exception/GlobalExceptionHandler.java b/src/main/java/com/jiagutech/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..043a399 --- /dev/null +++ b/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 handleBindExceptions(BindException ex) { + log.error("参数绑定失败", ex); + Map 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 handleValidationExceptions(MethodArgumentNotValidException ex) { + log.error("参数校验失败", ex); + Map 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 handleBusinessException(BusinessException ex) { + return new ResponseEntity<>(R.fail(ex.getCode(), ex.getMessage()), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { + log.error("参数类型错误", ex); + return new ResponseEntity<>(R.fail(new BusinessException("参数类型错误")), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(NotLoginException.class) + public ResponseEntity 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 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 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 handleCustomException(Exception ex) { + log.error("系统异常", ex); + return new ResponseEntity<>(R.fail(BizCode.General_Failure), HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/com/jiagutech/common/sa/SaPermissionImpl.java b/src/main/java/com/jiagutech/common/sa/SaPermissionImpl.java new file mode 100644 index 0000000..b2014a2 --- /dev/null +++ b/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 getPermissionList(Object loginId, String loginType) { + + return new ArrayList<>(); + } + + /** + * 获取角色权限列表 + */ + @Override + public List getRoleList(Object loginId, String loginType) { + LoginUser loginUser = LoginUtil.getLoginUser(); + if (loginUser == null) { + return new ArrayList<>(); + } + return Collections.emptyList(); + } +} diff --git a/src/main/java/com/jiagutech/common/sa/SaTokenConfig.java b/src/main/java/com/jiagutech/common/sa/SaTokenConfig.java new file mode 100644 index 0000000..05048dd --- /dev/null +++ b/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(); + } + + +} diff --git a/src/main/java/com/jiagutech/common/sa/SaTokenConfigure.java b/src/main/java/com/jiagutech/common/sa/SaTokenConfigure.java new file mode 100644 index 0000000..3436490 --- /dev/null +++ b/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/**" + ); + } +} diff --git a/src/main/java/com/jiagutech/common/thread/MonitorThreadPoolExecutor.java b/src/main/java/com/jiagutech/common/thread/MonitorThreadPoolExecutor.java new file mode 100644 index 0000000..8f857d5 --- /dev/null +++ b/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 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 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 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 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 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(); + } + } + } + + +} diff --git a/src/main/java/com/jiagutech/common/thread/MonitoredRunnable.java b/src/main/java/com/jiagutech/common/thread/MonitoredRunnable.java new file mode 100644 index 0000000..3e039a8 --- /dev/null +++ b/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 threadLocal; + private final long inQueueNanoTime; + + public MonitoredRunnable(Runnable runnable, ThreadLocal threadLocal) { + this.runnable = runnable; + this.threadLocal = threadLocal; + this.inQueueNanoTime = System.nanoTime(); + } + + + public ThreadLocal getThreadLocal() { + return threadLocal; + } + + public void setThreadLocal(ThreadLocal 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(); + } +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/thread/NameThreadFactory.java b/src/main/java/com/jiagutech/common/thread/NameThreadFactory.java new file mode 100644 index 0000000..dd183e4 --- /dev/null +++ b/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()); + } + + } \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/thread/ThreadExecutorConfig.java b/src/main/java/com/jiagutech/common/thread/ThreadExecutorConfig.java new file mode 100644 index 0000000..c98d965 --- /dev/null +++ b/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); + } + + +} diff --git a/src/main/java/com/jiagutech/common/thread/ThreadPoolConfig.java b/src/main/java/com/jiagutech/common/thread/ThreadPoolConfig.java new file mode 100644 index 0000000..85ae211 --- /dev/null +++ b/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; + + +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/common/thread/ThreadStatistics.java b/src/main/java/com/jiagutech/common/thread/ThreadStatistics.java new file mode 100644 index 0000000..bd7d1ad --- /dev/null +++ b/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 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 + + '}'; + } +} diff --git a/src/main/java/com/jiagutech/controller/RecordController.java b/src/main/java/com/jiagutech/controller/RecordController.java new file mode 100644 index 0000000..a7ef1f2 --- /dev/null +++ b/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 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> 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 getModels() { + return R.ok(recordService.getModels()); + } +} diff --git a/src/main/java/com/jiagutech/controller/UserController.java b/src/main/java/com/jiagutech/controller/UserController.java new file mode 100644 index 0000000..5deb9c0 --- /dev/null +++ b/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 login(@Validated @RequestBody LoginRequest loginBody) { + LoginResponse loginVo = userService.login(loginBody); + return R.ok(loginVo); + } + + @ApiOperation(value = "登出") + @GetMapping("/logout") + @PostMapping("/logout") + public R logout() { + StpUtil.logout(); + return R.ok("logout"); + } + + @ApiOperation(value = "注册") + @PostMapping(value = "/register", consumes = "application/json") + public R register(@Validated @RequestBody UserRequest user) { + userService.register(user); + return R.ok(); + } + + + @ApiOperation(value = "审批") + @PutMapping(value = "/approval/{userId}") + public R approval(@PathVariable Long userId) { + userService.approval(userId); + return R.ok(); + } + +} diff --git a/src/main/java/com/jiagutech/dto/CreateRecordRequest.java b/src/main/java/com/jiagutech/dto/CreateRecordRequest.java new file mode 100644 index 0000000..812083e --- /dev/null +++ b/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; + + +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/dto/LoginRequest.java b/src/main/java/com/jiagutech/dto/LoginRequest.java new file mode 100644 index 0000000..3210b3b --- /dev/null +++ b/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"; +} diff --git a/src/main/java/com/jiagutech/dto/LoginResponse.java b/src/main/java/com/jiagutech/dto/LoginResponse.java new file mode 100644 index 0000000..536dd54 --- /dev/null +++ b/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; + + +} diff --git a/src/main/java/com/jiagutech/dto/LoginUser.java b/src/main/java/com/jiagutech/dto/LoginUser.java new file mode 100644 index 0000000..e841a5a --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/jiagutech/dto/ModelPredictData.java b/src/main/java/com/jiagutech/dto/ModelPredictData.java new file mode 100644 index 0000000..bf05656 --- /dev/null +++ b/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 recognitionData; +} diff --git a/src/main/java/com/jiagutech/dto/ModelPredictResponse.java b/src/main/java/com/jiagutech/dto/ModelPredictResponse.java new file mode 100644 index 0000000..514387c --- /dev/null +++ b/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; +} + diff --git a/src/main/java/com/jiagutech/dto/RecordContentExcel.java b/src/main/java/com/jiagutech/dto/RecordContentExcel.java new file mode 100644 index 0000000..15d26fa --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/jiagutech/dto/RecordDetail.java b/src/main/java/com/jiagutech/dto/RecordDetail.java new file mode 100644 index 0000000..a3988cd --- /dev/null +++ b/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 content; +} diff --git a/src/main/java/com/jiagutech/dto/RecordExcel.java b/src/main/java/com/jiagutech/dto/RecordExcel.java new file mode 100644 index 0000000..269bdb5 --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/jiagutech/dto/RecordItem.java b/src/main/java/com/jiagutech/dto/RecordItem.java new file mode 100644 index 0000000..4162674 --- /dev/null +++ b/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; +} diff --git a/src/main/java/com/jiagutech/dto/RecordModel.java b/src/main/java/com/jiagutech/dto/RecordModel.java new file mode 100644 index 0000000..d381ed6 --- /dev/null +++ b/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 loaded_models; + + private List models; + + @Data + public static class ModelDetail { + + private float conf; + + private float iou; + + private String name; + + private String desc; + + private String file; + } +} diff --git a/src/main/java/com/jiagutech/dto/UserRequest.java b/src/main/java/com/jiagutech/dto/UserRequest.java new file mode 100644 index 0000000..6cb2188 --- /dev/null +++ b/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; + + +} diff --git a/src/main/java/com/jiagutech/dto/common/PageRequest.java b/src/main/java/com/jiagutech/dto/common/PageRequest.java new file mode 100644 index 0000000..e285dcf --- /dev/null +++ b/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 implements java.io.Serializable { + private static final long serialVersionUID = 1L; + + private int pageNum; + private int pageSize; + private String oderByColumn; + + private T request; + + +} diff --git a/src/main/java/com/jiagutech/dto/common/PageResult.java b/src/main/java/com/jiagutech/dto/common/PageResult.java new file mode 100644 index 0000000..53f8bd2 --- /dev/null +++ b/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 { + private int total; + private int pageSize; + private int pageNum; + private int pages; + private List records; + + public static PageResult of(int total, int pageSize, int pageNum, List records) { + PageResult 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; + } +} diff --git a/src/main/java/com/jiagutech/dto/common/R.java b/src/main/java/com/jiagutech/dto/common/R.java new file mode 100644 index 0000000..d7a0f34 --- /dev/null +++ b/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 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 R ok() { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(String msg) { + return restResult(null, SUCCESS, msg); + } + + public static R ok(String msg, T data) { + return restResult(data, SUCCESS, msg); + } + + public static R fail() { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(String msg, T data) { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) { + return restResult(null, code, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static R warn(String msg) { + return restResult(null, HttpStatus.WARN, msg); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static R warn(String msg, T data) { + return restResult(data, HttpStatus.WARN, msg); + } + + private static R restResult(T data, int code, String msg) { + R r = new R<>(); + r.setCode(code); + r.setData(data); + r.setMsg(msg); + return r; + } + + public static Boolean isError(R ret) { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/src/main/java/com/jiagutech/entity/RecordContentEntity.java b/src/main/java/com/jiagutech/entity/RecordContentEntity.java new file mode 100644 index 0000000..c958476 --- /dev/null +++ b/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 recognitionData; + + private int siliquaNum; + + private int handleNum; + + private int beakNum; + + private int size; + //比例 + private int scale; + + private float conf; + + private float iou; +} diff --git a/src/main/java/com/jiagutech/entity/RecordEntity.java b/src/main/java/com/jiagutech/entity/RecordEntity.java new file mode 100644 index 0000000..0d5aefd --- /dev/null +++ b/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; + +} diff --git a/src/main/java/com/jiagutech/entity/UserEntity.java b/src/main/java/com/jiagutech/entity/UserEntity.java new file mode 100644 index 0000000..4063f7c --- /dev/null +++ b/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; + + +} diff --git a/src/main/java/com/jiagutech/handler/AddCellRangeWriteHandler.java b/src/main/java/com/jiagutech/handler/AddCellRangeWriteHandler.java new file mode 100644 index 0000000..416c290 --- /dev/null +++ b/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 rangeCellList; + + public AddCellRangeWriteHandler(List 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/handler/ExcelFillCellMergeStrategy.java b/src/main/java/com/jiagutech/handler/ExcelFillCellMergeStrategy.java new file mode 100644 index 0000000..0cb83bf --- /dev/null +++ b/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> 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 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); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/handler/ModelPredictResponseListTypeHandler.java b/src/main/java/com/jiagutech/handler/ModelPredictResponseListTypeHandler.java new file mode 100644 index 0000000..d77d2a8 --- /dev/null +++ b/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>(){}); + } catch (Exception e) { + throw new SQLException("Error deserializing recognition_data", e); + } + } +} diff --git a/src/main/java/com/jiagutech/mapper/RecordContentMapper.java b/src/main/java/com/jiagutech/mapper/RecordContentMapper.java new file mode 100644 index 0000000..70b63f8 --- /dev/null +++ b/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 { + + List selectListByRecordId(@Param("recordId") Long recordId); +} diff --git a/src/main/java/com/jiagutech/mapper/RecordMapper.java b/src/main/java/com/jiagutech/mapper/RecordMapper.java new file mode 100644 index 0000000..4fbbde0 --- /dev/null +++ b/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 { + + RecordDetail getRecordDetail(long id); + + Page pageList(Page page, Object o); +} diff --git a/src/main/java/com/jiagutech/mapper/UserMapper.java b/src/main/java/com/jiagutech/mapper/UserMapper.java new file mode 100644 index 0000000..7259442 --- /dev/null +++ b/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 { + LoginUser selectLoginUserByPhone(String phone); +} diff --git a/src/main/java/com/jiagutech/record/RecordTaskHandler.java b/src/main/java/com/jiagutech/record/RecordTaskHandler.java new file mode 100644 index 0000000..9dae20d --- /dev/null +++ b/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; + + } + + +} diff --git a/src/main/java/com/jiagutech/record/RecordTaskManager.java b/src/main/java/com/jiagutech/record/RecordTaskManager.java new file mode 100644 index 0000000..4352fdb --- /dev/null +++ b/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 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); + } + +} diff --git a/src/main/java/com/jiagutech/record/RecordTaskProcessor.java b/src/main/java/com/jiagutech/record/RecordTaskProcessor.java new file mode 100644 index 0000000..234325f --- /dev/null +++ b/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 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(); + + + } + +} diff --git a/src/main/java/com/jiagutech/service/RecordService.java b/src/main/java/com/jiagutech/service/RecordService.java new file mode 100644 index 0000000..bb8c631 --- /dev/null +++ b/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 getPages(PageRequest pageRequest); + + void updateRecordContent(RecordContentEntity recordContentEntity); + + RecordModel getModels(); + +} diff --git a/src/main/java/com/jiagutech/service/RecordServiceImpl.java b/src/main/java/com/jiagutech/service/RecordServiceImpl.java new file mode 100644 index 0000000..1dc0c4a --- /dev/null +++ b/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 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 avgBeakLen = new ArrayList<>(); + List avgRapeLen = new ArrayList<>(); + List avgHandleLen = new ArrayList<>(); + List recordContentExcelList = new ArrayList<>(); + List 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 rapeLenList = new ArrayList<>(); + List beakLenList = new ArrayList<>(); + List handleLenList = new ArrayList<>(); + + List 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 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 getPages(PageRequest pageRequest) { + Page page = new Page(pageRequest.getPageNum(), pageRequest.getPageSize()); + Page 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(); + } +} diff --git a/src/main/java/com/jiagutech/service/TaskProgressSSEService.java b/src/main/java/com/jiagutech/service/TaskProgressSSEService.java new file mode 100644 index 0000000..9064543 --- /dev/null +++ b/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 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); + } + } +} diff --git a/src/main/java/com/jiagutech/service/UserService.java b/src/main/java/com/jiagutech/service/UserService.java new file mode 100644 index 0000000..2292e55 --- /dev/null +++ b/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); +} diff --git a/src/main/java/com/jiagutech/service/UserServiceImpl.java b/src/main/java/com/jiagutech/service/UserServiceImpl.java new file mode 100644 index 0000000..2543568 --- /dev/null +++ b/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); + } +} diff --git a/src/main/java/com/jiagutech/utils/EmailUtil.java b/src/main/java/com/jiagutech/utils/EmailUtil.java new file mode 100644 index 0000000..952eb29 --- /dev/null +++ b/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("【用户待审核】现有%s用户%s(%s)
已注册成为角果识别系统的新用户" + + ",请您尽快审核!

", unitName, realName, phone); + helper.setText(text, true); // true 表示 HTML 格式 + + // 发送邮件 + mailSender.send(message); + log.info("邮件发送成功: {}", receiver); + + } catch (MessagingException e) { + log.error("邮件发送失败: ", e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/jiagutech/utils/ExcelBigNumberConvert.java b/src/main/java/com/jiagutech/utils/ExcelBigNumberConvert.java new file mode 100644 index 0000000..b2f5504 --- /dev/null +++ b/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 { + + @Override + public Class 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 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 cellData = new WriteCellData<>(new BigDecimal(object)); + cellData.setType(CellDataTypeEnum.NUMBER); + return cellData; + } + +} diff --git a/src/main/java/com/jiagutech/utils/ExcelUtil.java b/src/main/java/com/jiagutech/utils/ExcelUtil.java new file mode 100644 index 0000000..5832190 --- /dev/null +++ b/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 void exportExcel(List list, String sheetName, Class 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异常"); + } + } +} diff --git a/src/main/java/com/jiagutech/utils/HuaweiObs.java b/src/main/java/com/jiagutech/utils/HuaweiObs.java new file mode 100644 index 0000000..09f95ae --- /dev/null +++ b/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 headers = new HashMap(); + 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; + } + } +} + diff --git a/src/main/java/com/jiagutech/utils/LoginUtil.java b/src/main/java/com/jiagutech/utils/LoginUtil.java new file mode 100644 index 0000000..f226e57 --- /dev/null +++ b/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); + } + +} diff --git a/src/main/java/com/jiagutech/utils/Md5Util.java b/src/main/java/com/jiagutech/utils/Md5Util.java new file mode 100644 index 0000000..431f008 --- /dev/null +++ b/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(); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..825cdb9 --- /dev/null +++ b/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 diff --git a/src/main/resources/mapper/RecordContentMapper.xml b/src/main/resources/mapper/RecordContentMapper.xml new file mode 100644 index 0000000..274b36a --- /dev/null +++ b/src/main/resources/mapper/RecordContentMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/RecordMapper.xml b/src/main/resources/mapper/RecordMapper.xml new file mode 100644 index 0000000..55aae55 --- /dev/null +++ b/src/main/resources/mapper/RecordMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..f51b2ec --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file