This commit is contained in:
cuijiawang
2025-09-20 15:49:47 +08:00
commit 013433f136
1995 changed files with 144238 additions and 0 deletions

76
.gitignore vendored Normal file
View File

@@ -0,0 +1,76 @@
# Compiled class file
*.class
# Log file
*.log
*.imi
*.lst
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea
*.iml
.settings/
.project
.classpath
target/
logs/
.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
**/*.log
tests/**/coverage/
tests/e2e/reports
selenium-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.local
package-lock.json
yarn.lock
# Build and Release Folders
bin-debug/
bin-release/
[Oo]bj/
[Bb]in/
# Other files and folders
.settings/
# Executables
*.swf
*.air
*.ipa
*.apk
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
# should NOT be excluded as they contain compiler settings and other important
# information for Eclipse / Flash Builder.

36
README.en.md Normal file
View File

@@ -0,0 +1,36 @@
# sfbx-cloud
#### Description
四方保险
#### Software Architecture
Software architecture description
#### Installation
1. xxxx
2. xxxx
3. xxxx
#### Instructions
1. xxxx
2. xxxx
3. xxxx
#### Contribution
1. Fork the repository
2. Create Feat_xxx branch
3. Commit your code
4. Create Pull Request
#### Gitee Feature
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
4. The most valuable open source project [GVP](https://gitee.com/gvp)
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# sfbx-cloud
#### 介绍
四方保险
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)

125
docker-compose.yml Normal file
View File

@@ -0,0 +1,125 @@
version: "3.8"
services:
#mysql数据库脚本
mysql:
image: mysql:5.7
container_name: mysql
restart: always
ports:
- 3306:3306
volumes:
- ./mysql/mydir:/mydir
- ./mysql/data:/var/lib/mysql
- ./mysql/conf/my.cnf:/etc/my.cnf
- ./mysql/source:/docker-entrypoint-initdb.d/
environment:
MYSQL_ROOT_PASSWORD: pass
networks:
extnetwork:
ipv4_address: 172.21.0.2
#nacos服务脚本
nacos:
image: nacos/nacos-server:2.0.2
container_name: nacos
restart: always
ports:
- "8848:8848"
environment:
SPRING_DATASOURCE_PLATFORM: mysql #数据源平台 仅支持mysql或不保存empty
MODE: standalone
MYSQL_SERVICE_HOST: mysql
MYSQL_SERVICE_DB_NAME: nacos
MYSQL_SERVICE_PORT: 3306
MYSQL_SERVICE_USER: root
MYSQL_SERVICE_PASSWORD: pass
NACOS_APPLICATION_PORT: 8848
JVM_XMS: 512m
JVM_MMS: 256m
JVM_XMN: 128m
networks:
extnetwork:
ipv4_address: 172.21.0.3
depends_on:
- mysql
#seata服务脚本
seata-server:
image: seataio/seata-server:1.5.2
container_name: seata-server
restart: always
ports:
- "9200:9200"
- "7091:7091"
- "8091:8091"
volumes:
- ./seata-server:/seata-server/resources
environment:
SEATA_IP: 192.168.12.129
SEATA_PORT: 9200
networks:
extnetwork:
ipv4_address: 172.21.0.4
depends_on:
- nacos
#rabbitmq脚本
rabbitmq:
image: rabbitmq:3.8.3-management
container_name: rabbitmq
restart: always
ports:
- 15672:15672
- 5672:5672
volumes:
- ./rabbitmq/data:/var/lib/rabbitmq
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: pass
networks:
extnetwork:
ipv4_address: 172.21.0.5
xxl-job:
image: xuxueli/xxl-job-admin:2.1.2
container_name: xxl-job-admin
restart: always
ports:
- 8280:8080
volumes:
- ./xxl-job/data:/data/applogs
environment:
PARAMS: "--spring.datasource.url=jdbc:mysql://mysql:3306/xxl-job?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&serverTimezone=Asia/Shanghai --spring.datasource.username=root --spring.datasource.password=pass"
networks:
extnetwork:
ipv4_address: 172.21.0.9
depends_on:
- mysql
redis:
image: redis:5.0.0
container_name: redis
restart: always
command: redis-server --requirepass pass
ports:
- 6379:6379
volumes:
- ./redis/data:/data
networks:
extnetwork:
ipv4_address: 172.21.0.10
influxdb:
image: influxdb:1.8.0
container_name: influxdb
restart: always
ports:
- 9083:8083
- 8086:8086
- 8088:8088
privileged: true
volumes:
- ./influxdb/data/influxdb:/var/lib/influxdb
- ./influxdb/config/influxdb.conf:/etc/influxdb/influxdb.conf
# docker容器内网地址
networks:
extnetwork:
name: extnetwork
ipam:
config:
- subnet: 172.21.0.0/16

473
pom.xml Normal file
View File

@@ -0,0 +1,473 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-cloud</artifactId>
<version>2.0-SNAPSHOT</version>
<modules>
<!--数据字典模块-->
<module>sfbx-dict</module>
<!--基础骨架模块-->
<module>sfbx-framework</module>
<!--文件处理模块-->
<module>sfbx-file</module>
<!--埋点处理模块-->
<module>sfbx-points</module>
<!--交易处理模块-->
<module>sfbx-trade</module>
<!--权限处理模块-->
<module>sfbx-security</module>
<!--短信处理模块-->
<module>sfbx-sms</module>
<!--任务监听处理模块-->
<module>sfbx-task</module>
<!--保险业务模块-->
<module>sfbx-insurance</module>
<module>sfbx-gateway</module>
<module>sfbx-apache-httpclient</module>
<!--规则引擎模块-->
<module>sfbx-rule</module>
</modules>
<packaging>pom</packaging>
<name>sfbx-cloud</name>
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!--easy.version-->
<sfbx-cloud.version>2.0-SNAPSHOT</sfbx-cloud.version>
<!--spring-cloud版本-->
<spring-cloud.version>2021.0.6</spring-cloud.version>
<!--spring-cloud-alibaba版本-->
<spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
<!--spring-cloud-stream版本-->
<stream-rabbit.version>3.1.0</stream-rabbit.version>
<!--spring-boot版本-->
<spring.boot.version>2.7.10</spring.boot.version>
<!--mybatis-plus版本-->
<mybatis-plus-boot-starter.version>3.4.0</mybatis-plus-boot-starter.version>
<!--druid的springboot版本配置-->
<druid-spring-boot-starter>1.1.20</druid-spring-boot-starter>
<!--druid版本配置-->
<druid.version>1.1.22</druid.version>
<!--knife4j版本支持-->
<knife4j.version>3.0.3</knife4j.version>
<!--orika 拷贝工具 -->
<orika-core.version>1.5.4</orika-core.version>
<!--hutool工具 -->
<hutool.version>5.8.10</hutool.version>
<!--lang3-->
<commons.lang3.version>3.8.1</commons.lang3.version>
<!--kryo-->
<kryo.version>4.0.2</kryo.version>
<!--阿里easy支付-->
<alipay.easysdk.version>2.2.0</alipay.easysdk.version>
<!--阿里通用支付-->
<alipay-sdk-java.version>4.38.61.ALL</alipay-sdk-java.version>
<!--微信支付-->
<wechatpay.version>0.4.8</wechatpay.version>
<!--guava版本 -->
<guava.version>23.0</guava.version>
<!--fastjson版本-->
<fastjson.version>1.2.73</fastjson.version>
<!--sharding-jdbc版本-->
<sharding-jdbc.version>4.1.1</sharding-jdbc.version>
<!--redisson版本-->
<redisson-spring-boot>3.11.2</redisson-spring-boot>
<!--hessian协议支持-->
<hessian.version>4.0.7</hessian.version>
<!--xxl-job-->
<xxl-job-core.version>2.1.2</xxl-job-core.version>
<!--阿里当前线程,解决父子线程间参数传递问题-->
<transmittable.version>2.12.2</transmittable.version>
<!-- 七牛 sdk 版本 -->
<qiniu.version>7.7.0</qiniu.version>
<!-- mysql数据库版本 -->
<mysql.version>8.0.19</mysql.version>
</properties>
<!--声明jar-->
<dependencyManagement>
<dependencies>
<!--基础模块-工具包-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-commons</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-redis支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-redis</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!-- 外部数据源 -->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-out-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!-- 数据字典 -->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>dict-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!-- ai模块 -->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-ai</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!-- rule模块 -->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-rule-base</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-knife4j-web支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-knife4j-web</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-apache-httpclient</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-knife4j-gateway支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-knife4j-gateway</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-gateway支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-gateway</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-web支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-web</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-feign支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-feign</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-seata支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-seata</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-rabbitmq支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-rabbitmq</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-mybatis支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-mybatis-plus</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-task-executor支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-task-executor</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-xxl-job支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-xxl-job</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-influxdb支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-influxdb</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-权限接口支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>security-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>rule-client</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-文件接口支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>file-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-短信接口支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sms-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-数据埋点支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>points-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-交易模块支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>trade-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--保险模块-service支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>insurance-service</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!--基础模块-保险模块支持-->
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>insurance-interface</artifactId>
<version>${sfbx-cloud.version}</version>
</dependency>
<!---springcould主配置-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!---spring-cloud-alibaba主配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!---springboot主配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- sharding-jdbc -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<!-- 使用XA事务时需要引入此模块 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<!-- 使用BASE事务时需要引入此模块 -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-base-seata-at</artifactId>
<version>${sharding-jdbc.version}</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons.lang3.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>${orika-core.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!--knife4j版接口文档 访问/doc.html -->
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency>
<!--Mysql支持-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--druid的springboot配置-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid-spring-boot-starter}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!--springboot关于mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!--代码生成器模板引擎 相关依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
</dependency>
<!-- 阿里easy支付 -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>${alipay.easysdk.version}</version>
</dependency>
<!-- 阿里通用版本支付- -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>${alipay-sdk-java.version}</version>
</dependency>
<!-- 微信支付 -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>${wechatpay.version}</version>
</dependency>
<!--redis缓存客户端-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson-spring-boot}</version>
</dependency>
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>${xxl-job-core.version}</version>
</dependency>
<!--使用spring-cloud-starter-stream对rabbitmq的支持-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
<version>${stream-rabbit.version}</version>
</dependency>
<!--百度云短信-->
<dependency>
<groupId>com.baidubce</groupId>
<artifactId>bce-java-sdk</artifactId>
<version>0.10.119</version>
</dependency>
<!--阿里云短信-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>dysmsapi20170525</artifactId>
<version>2.0.24</version>
</dependency>
<!--腾讯云短信-->
<dependency>
<groupId>com.tencentcloudapi</groupId>
<artifactId>tencentcloud-sdk-java</artifactId>
<version>3.1.270</version>
</dependency>
<!-- 阿里当前线程,解决父子线程间参数传递问题 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>${transmittable.version}</version>
</dependency>
<!-- 七牛 sdk -->
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>
<!---spring-cloud-alibaba-oss配置-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
<version>2.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.10.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!--jdk插件-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
<!--springboot的打包插件-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.0.RELEASE</version>
</plugin>
<!-- maven-surefire-plugin 测试包 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<!-- 全局是否执行maven生命周期中的测试是否跳过测试 -->
<skipTests>true</skipTests>
<!-- 解决测试中文乱码-->
<forkMode>once</forkMode>
<argLine>-Dfile.encoding=UTF-8</argLine>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>sfbx-cloud</artifactId>
<groupId>com.itheima.sfbx</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--三方sdk接口封装-->
<artifactId>sfbx-apache-httpclient</artifactId>
<name>sfbx-apache-httpclient</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,58 @@
package com.itheima.sfbx;
import com.itheima.sfbx.auth.*;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.execchain.ClientExecChain;
/**
* @ClassName BoleeHttpClientBuilder.java
* @Description 保乐三方请求HttpClient对象构建
*/
public class BoleeHttpClientBuilder extends HttpClientBuilder {
//系统相关属性
private static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
private static final String VERSION = System.getProperty("java.version");
//凭证构建
private Credentials credentials;
//校验者
private Validator validator;
public static BoleeHttpClientBuilder create() {
return new BoleeHttpClientBuilder();
}
private BoleeHttpClientBuilder() {
String userAgent = String.format("sfbx-Apache-HttpClient/%s (%s) Java/%s", this.getClass().getPackage().getImplementationVersion(), OS, VERSION == null ? "Unknown" : VERSION);
this.setUserAgent(userAgent);
}
public BoleeHttpClientBuilder withCredentials(String appId,String privateKey) {
this.credentials = new BoleeCredentials(appId, new PrivateKeySigner(privateKey));
return this;
}
public BoleeHttpClientBuilder withValidator(String publicKey) {
this.validator = new BoleeValidator(new PublicKeyVerifier(publicKey));
return this;
}
public CloseableHttpClient build() {
if (this.credentials == null) {
throw new IllegalArgumentException("缺少身份认证信息");
} else if (this.validator == null) {
throw new IllegalArgumentException("缺少签名验证信息");
} else {
return super.build();
}
}
protected ClientExecChain decorateProtocolExec(ClientExecChain requestExecutor) {
return new DecorateClientExecChain(this.credentials, this.validator, requestExecutor);
}
}

View File

@@ -0,0 +1,19 @@
package com.itheima.sfbx;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
/**
* @ClassName Credentials.java
* @Description 认证凭证创建处理
*/
public interface Credentials {
/***
* @description 创建请求认证凭证
* @param request 请求对象
* @return: java.lang.String 凭证字符串
*/
String createCredentials(HttpRequestWrapper request) throws IOException;
}

View File

@@ -0,0 +1,75 @@
package com.itheima.sfbx;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpExecutionAware;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.execchain.ClientExecChain;
import org.apache.http.util.EntityUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import static org.apache.http.HttpHeaders.AUTHORIZATION;
import static org.apache.http.HttpStatus.SC_MULTIPLE_CHOICES;
import static org.apache.http.HttpStatus.SC_OK;
/**
* @ClassName SignatureExec.java
* @Description 请求执行者
*/
@Slf4j
public class DecorateClientExecChain implements ClientExecChain {
private final ClientExecChain mainExec;
private final Credentials credentials;
private final Validator validator;
protected DecorateClientExecChain(Credentials credentials, Validator validator, ClientExecChain mainExec) {
this.credentials = credentials;
this.validator = validator;
this.mainExec = mainExec;
}
protected void convertToRepeatableResponseEntity(CloseableHttpResponse response) throws IOException {
HttpEntity entity = response.getEntity();
if (entity != null) {
response.setEntity(new BufferedHttpEntity(entity));
}
}
@Override
public CloseableHttpResponse execute(HttpRoute httpRoute, HttpRequestWrapper httpRequestWrapper,
HttpClientContext httpClientContext, HttpExecutionAware httpExecutionAware) throws IOException, HttpException {
//请求头添加认证令牌
httpRequestWrapper.addHeader(AUTHORIZATION,credentials.createCredentials(httpRequestWrapper));
//执行请求
CloseableHttpResponse response = mainExec.execute(httpRoute, httpRequestWrapper, httpClientContext, httpExecutionAware);
//对成功应答验签
StatusLine statusLine = response.getStatusLine();
if (statusLine.getStatusCode() >= SC_OK && statusLine.getStatusCode() < SC_MULTIPLE_CHOICES) {
convertToRepeatableResponseEntity(response);
if (!validator.validate(response)) {
throw new HttpException("应答的签名验证失败");
}
}
return response;
}
}

View File

@@ -0,0 +1,16 @@
package com.itheima.sfbx;
import com.itheima.sfbx.dto.EncodeDTO;
import java.io.IOException;
public interface Encryptor {
/***
* @description 加密body体消息的方法
* @param body 请求消息体
* @return: boolean 校验结果
*/
boolean encode(EncodeDTO body) throws IOException;
}

View File

@@ -0,0 +1,163 @@
package com.itheima.sfbx;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itheima.sfbx.auth.BoleeEncryptor;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.CredentialsDTO;
import com.itheima.sfbx.dto.EncodeDTO;
import com.itheima.sfbx.utils.SecurityUtil;
import lombok.Builder;
import lombok.Data;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
/**
* RequestTemplate
*
* @author: wgl
* @describe: 对外统一发送模板--组装各个模组
* 加密模组
* 解密模组组装
* @date: 2022/12/28 10:10
*/
@Data
public class RequestTemplate {
private String privateKey;
private String publicKey;
private String appId;
private URIBuilder uriBuilder;
private RequestConfig requestConfig;
@Builder
public RequestTemplate(String privateKey, String publicKey, String appId, URIBuilder uriBuilder,RequestConfig requestConfig) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.appId = appId;
this.uriBuilder = uriBuilder;
this.requestConfig = requestConfig;
}
public <T> T doRequest(Object params,Class<T> t) throws URISyntaxException, IOException {
CloseableHttpClient httpclient = BoleeHttpClientBuilder.create()
.withCredentials(appId, privateKey)
.withValidator(publicKey)
.build();
//=============基础配置===================
RequestConfig requestConfig = null;
if(ObjectUtil.isNull(requestConfig)) {
requestConfig = RequestConfig.custom()
.setSocketTimeout(100000)
.setConnectTimeout(100000)
.build();
}else{
requestConfig = this.requestConfig;
}
try {
URI url = uriBuilder.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
EncodeDTO encodeDTO = new EncodeDTO();
JSONObject body = JSONUtil.parseObj(params);
encodeDTO.setBody(body.toString());
if(new BoleeEncryptor(privateKey).encode(encodeDTO)){
StringEntity stringEntity = new StringEntity(encodeDTO.getEncodeBody(), "utf-8");
httpPost.setEntity(stringEntity);
httpPost.addHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY,encodeDTO.getHeadAESSecurityKey());
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
CloseableHttpResponse apiRes = httpclient.execute(httpPost);
//验签完毕后需要对数据进行解密
HttpEntity entity = apiRes.getEntity();
String content = EntityUtils.toString(entity);
String responseJson = decryptRequestBody(content, apiRes.getFirstHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY).getValue());
if(StrUtil.isEmpty(responseJson)){
throw new RuntimeException("参数解析为空");
}
return JSONUtil.toBean(responseJson, t);
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return null;
}
/**
* 解密请求body体
*
* @param dody 返回数据对象
* @param aesKeyRSA 获取请求头中加密后的AES密钥
* @return
*/
private String decryptRequestBody(String dody, String aesKeyRSA) {
try {
//先使用rsa解密aes秘钥
RSA rsa = SecureUtil.rsa(null, publicKey);
String aesKey = SecurityUtil.decryptFromStringRSAPublicKey(rsa, aesKeyRSA);
//使用aes进行解密
// 使用解密后的AES密钥进行AES解密请求体数据
String requestBody = SecurityUtil.decryptFromStringAES(dody, Mode.CBC, Padding.ZeroPadding,aesKey);
return requestBody;
} catch (Exception e) {
e.printStackTrace();
// 解密失败,可以根据你的需求进行异常处理
return null;
}
}
/**
* 构建请求参数
* 1694953272
* xHct36JgHbj6tXBx
* Modified: {"msg":"ok","code":"100","data":null}
*
* @param authorization
* @param response
* @return
*/
private CredentialsDTO buildRequestParam(String authorization, CloseableHttpResponse response) {
try {
authorization = authorization.replaceAll("\"", "");
String[] authorizations = authorization.split(",");
Map<String, String> authMap = new HashMap<>();
for (String authorizationIndex : authorizations) {
String[] split = authorizationIndex.split("=");
authMap.put(split[0], split[1].substring(0, split[1].length()));
}
CredentialsDTO credentialsDTO = new CredentialsDTO();
credentialsDTO.setNonce(authMap.get("nonce_str"));
credentialsDTO.setTimestamp(authMap.get("timestamp"));
credentialsDTO.setSignature(authMap.get("signature"));
// credentialsDTO.setSecurityBody(getPostData(req));
return credentialsDTO;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@@ -0,0 +1,11 @@
package com.itheima.sfbx;
/**
* @ClassName Signer.java
* @Description 签名加密者
*/
public interface Signer {
String sign(byte[] message);
}

View File

@@ -0,0 +1,27 @@
package com.itheima.sfbx;
import com.itheima.sfbx.notify.NotifyRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import java.io.IOException;
/**
* @ClassName Validator.java
* @Description 签名验证处理
*/
public interface Validator {
/***
* @description 验证应答签名
* @param response 响应结果
* @return: boolean 校验结果
*/
boolean validate(CloseableHttpResponse response) throws IOException;
/***
* @description 验证通知请求签名
* @param notifyRequest 响应结果
* @return: boolean 校验结果
*/
boolean validateNotify(NotifyRequest notifyRequest);
}

View File

@@ -0,0 +1,17 @@
package com.itheima.sfbx;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName Verifier.java
* @Description 验签解密者
*/
public interface Verifier {
boolean verify(byte[] data,byte[] signature);
}

View File

@@ -0,0 +1,49 @@
package com.itheima.sfbx.auth;
import com.itheima.sfbx.Credentials;
import com.itheima.sfbx.Signer;
import com.itheima.sfbx.utils.ClientUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
/**
* @ClassName BoleeCredentials.java
* @Description 认证凭证创建处理实现
*/
@Slf4j
public class BoleeCredentials implements Credentials {
//应用id
protected final String appId;
//签名者
protected final Signer signer;
public BoleeCredentials(String appId, Signer signer) {
this.appId = appId;
this.signer = signer;
}
@Override
public String createCredentials(HttpRequestWrapper request) throws IOException {
//临时字符串
String nonceStr = ClientUtils.generateNonceStr();
//时间戳
long timestamp = ClientUtils.generateTimestamp();
//请求数据
String message = ClientUtils.requestMessage(nonceStr, timestamp, request);
log.debug("authorization data:{}", message);
//生成签名字符串
String signature = signer.sign(Base64.decodeBase64(message));
//返回认证令牌对象
String credentials = "appId=\"" + this.appId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "signature=\"" + signature + "\"";
log.debug("authorization credentials=[{}]", credentials);
return credentials;
}
}

View File

@@ -0,0 +1,59 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import com.itheima.sfbx.Encryptor;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.EncodeDTO;
import com.itheima.sfbx.utils.ClientUtils;
import com.itheima.sfbx.utils.SecurityUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.HttpRequestWrapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* BoleeEncryptor
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Slf4j
public class BoleeEncryptor implements Encryptor {
//私钥签名
protected String privateKeyBase64;
public BoleeEncryptor(String privateKey) {
this.privateKeyBase64 = privateKey;
}
/**
* 对数据进行加密
* @param body 请求消息体
* @return
* @throws IOException
*/
@Override
public boolean encode(EncodeDTO body){
//获取AES加密秘钥
String bodySecurity = ClientUtils.generateNonceStr();
//使用AES加密 body体里的数据
String aesSecurity = SecurityUtil.encryptFromStringAES(body.getBody(), Mode.CBC, Padding.ZeroPadding,bodySecurity);
body.setEncodeBody(aesSecurity);
//使用RSA加密AES的对称加密秘钥
RSA rsa = new RSA(privateKeyBase64, null);
String securityToken = SecurityUtil.encryptFromStringRSAPrivateKey(rsa, bodySecurity);
body.setHeadAESSecurityKey(securityToken);
//将新生产的AES的body返回
return true;
}
}

View File

@@ -0,0 +1,48 @@
package com.itheima.sfbx.auth;
import com.itheima.sfbx.Validator;
import com.itheima.sfbx.Verifier;
import com.itheima.sfbx.notify.NotifyRequest;
import com.itheima.sfbx.utils.ClientUtils;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* @ClassName BoleeValidator.java
* @Description 签名验证处理实现
*/
public class BoleeValidator implements Validator {
protected final Verifier verifier;
public BoleeValidator(Verifier verifier) {
this.verifier = verifier;
}
@Override
public boolean validate(CloseableHttpResponse response) throws IOException {
//校验请求参数
ClientUtils.validateParameters(response);
//构建响应体信息
String message = ClientUtils.responseMessage(response);
//获得签名信息
String signature = response.getFirstHeader("signature").getValue();
//验证签名
return verifier.verify(message.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(signature));
}
@Override
public boolean validateNotify(NotifyRequest notifyRequest) {
//校验请求参数
ClientUtils.validateParametersNotify(notifyRequest);
//构建响应体信息
String message = ClientUtils.notifyMessage(notifyRequest);
//获得签名信息
String signature = notifyRequest.getSignature();
//验证签名
return verifier.verify(message.getBytes(StandardCharsets.UTF_8), Base64.decodeBase64(signature));
}
}

View File

@@ -0,0 +1,29 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.itheima.sfbx.Signer;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName PrivateKeySigner.java
* @Description TODO
*/
@Slf4j
public class PrivateKeySigner implements Signer {
protected final String privateKey;
public PrivateKeySigner(String privateKey) {
this.privateKey = privateKey;
}
@Override
public String sign(byte[] message) {
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,Base64.decodeBase64(privateKey),null);
return Base64.encodeBase64String(sign.sign(message));
}
}

View File

@@ -0,0 +1,26 @@
package com.itheima.sfbx.auth;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import com.itheima.sfbx.Verifier;
import org.apache.commons.codec.binary.Base64;
/**
* @ClassName PublicKeyVerifier.java
* @Description 公钥验证
*/
public class PublicKeyVerifier implements Verifier {
protected final String publicKey;
public PublicKeyVerifier(String publicKey) {
this.publicKey = publicKey;
}
@Override
public boolean verify(byte[] data,byte[] signature){
Sign sign = SecureUtil.sign(SignAlgorithm.SHA256withRSA,null, Base64.decodeBase64(publicKey));
return sign.verify(data,signature);
}
}

View File

@@ -0,0 +1,36 @@
package com.itheima.sfbx.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* SecurityConfig
*
* @author: wgl
* @describe: 客户端秘钥配置
* @date: 2022/12/28 10:10
*/
@ConfigurationProperties(prefix = "itheima.security")
@Data
public class SecurityConfig {
/**
* 客户appId
*/
@Value("${itheima.security.client.appid}")
private String appId;
/**
* 客户端私钥
*/
@Value("${itheima.security.client.privateKey}")
private String privateKey;
/**
* 服务端公钥
*/
@Value("${itheima.security.server.publicKey}")
private String publicKey;
}

View File

@@ -0,0 +1,16 @@
package com.itheima.sfbx.constants;
/**
* BoleeSecurityConstant
*
* @author: wgl
* @describe: 宝乐保险秘钥常量
* @date: 2022/12/28 10:10
*/
public class BoleeSecurityConstant {
/**
* 宝乐保险中body体中的信息
*/
public static String HEAD_NAME_BODY_KEY = "bolee_security_key";
}

View File

@@ -0,0 +1,65 @@
package com.itheima.sfbx.dto;
import lombok.Data;
/**
* RequestParamDTO
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Data
public class CredentialsDTO {
/**
* 请求方式
*/
private String methodd;
/**
* 接口路径
*/
private String uriPath;
/**
* 时间戳
*/
private String timestamp;
/**
* 签名数据
*/
private String signature;
/**
* 密文数据
*/
private String securityBody;
/**
* 随机字符串
*/
private String nonce;
/**
* 构建签名字符串
* return request.getRequestLine().getMethod() + "\n"
* + canonicalUrl + "\n"
* + timestamp + "\n"
* + nonce + "\n"
* + body + "\n";
* @return
*/
public String buildSignBody(){
StringBuilder sb = new StringBuilder();
sb.append(methodd + "\n");
sb.append(uriPath + "\n");
sb.append(timestamp + "\n");
sb.append(nonce + "\n");
sb.append(securityBody + "\n");
return sb.toString();
}
}

View File

@@ -0,0 +1,29 @@
package com.itheima.sfbx.dto;
import lombok.Data;
/**
* EncodeDTO
*
* @author: wgl
* @describe: TODO
* @date: 2022/12/28 10:10
*/
@Data
public class EncodeDTO {
/**
* 原始body参数
*/
private String body;
/**
* 加密后的密文
*/
private String encodeBody;
/**
* head中rsa加密后的aes密钥
*/
private String headAESSecurityKey;
}

View File

@@ -0,0 +1,22 @@
package com.itheima.sfbx.dto;
import lombok.Data;
import java.util.Map;
/**
* ResponseDTO
*
* @author: wgl
* @describe: 返回结果对象
* @date: 2022/12/28 10:10
*/
@Data
public class ResponseDTO {
private String msg;
private String code;
private Map data;
}

View File

@@ -0,0 +1,64 @@
package com.itheima.sfbx.notify;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectReader;
import com.itheima.sfbx.Validator;
import com.itheima.sfbx.Verifier;
import com.itheima.sfbx.auth.BoleeValidator;
import com.itheima.sfbx.auth.PublicKeyVerifier;
import java.io.IOException;
/**
* @ClassName NotifiyHandler.java
* @Description 通知验证及解析出来
*/
public class NotifiyHandler {
//校验者
private Validator validator;
private static final ObjectMapper objectMapper = new ObjectMapper();
public NotifiyHandler(String publicKey) {
this.validator = new BoleeValidator(new PublicKeyVerifier(publicKey));
}
/**
* 解析通知请求结果
* @param notifyRequest 微信支付通知请求
* @return 微信支付通知报文解密结果@StreamListener(FileSink.FILE_INPUT)
* public void onMessage(@Payload MqMessage message,
* @Header(AmqpHeaders.CHANNEL) Channel channel,
* @Header(AmqpHeaders.DELIVERY_TAG) Long deliveryTag) throws IOException {
* String jsonConten = message.getcontent();
* log.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
* FileVO fileVO= JSONObject.parseObject(jsonConten,FileVO.class);
* Boolean responseWrap = fileBusinessFeign.clearFileById(fileVO.getId());
* channel.basicAck(deliveryTag,false);
*
* }
*/
public NotifyResponse parse(NotifyRequest notifyRequest) throws IOException {
// 验签
boolean validate = validator.validateNotify(notifyRequest);
if (!validate){
throw new RuntimeException("验证签名失败");
}
// 解析请求体
return parseBody(notifyRequest.getBody());
}
/**
* 解析请求体
*
* @param body 请求体
* @return 解析结果
* @throws IOException 解析body失败
*/
private NotifyResponse parseBody(String body) throws IOException {
ObjectReader objectReader = objectMapper.reader();
NotifyResponse notifyResponse = objectReader.readValue(body, NotifyResponse.class);
return notifyResponse;
}
}

View File

@@ -0,0 +1,31 @@
package com.itheima.sfbx.notify;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @ClassName NotificationRequest.java
* @Description 通知请求对象
*/
@Data
@NoArgsConstructor
public class NotifyRequest {
//时间戳
private String timestamp;
//随机字符串
private String nonce;
//签名
private String signature;
//请求体
private String body;
@Builder
public NotifyRequest(String timestamp, String nonce, String signature, String body) {
this.timestamp = timestamp;
this.nonce = nonce;
this.signature = signature;
this.body = body;
}
}

View File

@@ -0,0 +1,117 @@
package com.itheima.sfbx.notify;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* @ClassName NotifyResponse.java
* @Description TODO
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class NotifyResponse {
@JsonProperty("id")
private String id;
@JsonProperty("create_time")
private String createTime;
@JsonProperty("event_type")
private String eventType;
@JsonProperty("resource_type")
private String resourceType;
@JsonProperty("summary")
private String summary;
@JsonProperty("")
private Resource resource;
private String decryptData;
@Override
public String toString() {
return "Notification{" +
"id='" + id + '\'' +
", createTime='" + createTime + '\'' +
", eventType='" + eventType + '\'' +
", resourceType='" + resourceType + '\'' +
", decryptData='" + decryptData + '\'' +
", summary='" + summary + '\'' +
", resource=" + resource +
'}';
}
public String getId() {
return id;
}
public String getCreateTime() {
return createTime;
}
public String getEventType() {
return eventType;
}
public String getDecryptData() {
return decryptData;
}
public String getSummary() {
return summary;
}
public String getResourceType() {
return resourceType;
}
public Resource getResource() {
return resource;
}
public void setDecryptData(String decryptData) {
this.decryptData = decryptData;
}
@JsonIgnoreProperties(ignoreUnknown = true)
public class Resource {
@JsonProperty("algorithm")
private String algorithm;
@JsonProperty("ciphertext")
private String ciphertext;
@JsonProperty("associated_data")
private String associatedData;
@JsonProperty("nonce")
private String nonce;
@JsonProperty("original_type")
private String originalType;
public String getAlgorithm() {
return algorithm;
}
public String getCiphertext() {
return ciphertext;
}
public String getAssociatedData() {
return associatedData;
}
public String getNonce() {
return nonce;
}
public String getOriginalType() {
return originalType;
}
@Override
public String toString() {
return "Resource{" +
"algorithm='" + algorithm + '\'' +
", ciphertext='" + ciphertext + '\'' +
", associatedData='" + associatedData + '\'' +
", nonce='" + nonce + '\'' +
", originalType='" + originalType + '\'' +
'}';
}
}
}

View File

@@ -0,0 +1,153 @@
package com.itheima.sfbx.utils;
import com.itheima.sfbx.notify.NotifyRequest;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
/**
* @ClassName ClientUtils.java
* @Description 客户端工具类
*/
public class ClientUtils {
//随机字符串处理
protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
//随机函数
protected static final SecureRandom RANDOM = new SecureRandom();
/***
* @description 时间戳创建
* @return: long 返回时间戳
*/
public static long generateTimestamp() {
return System.currentTimeMillis() / 1000;
}
/***
* @description 随机字符串
* @return: java.lang.String 字符串
*/
public static String generateNonceStr() {
char[] nonceChars = new char[16];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
/***
* @description 构建请求信息: 请求方式+请求路径+请求时间戳+随机字符串+请求体
* @param nonce 随机字符串
* @param timestamp 时间戳
* @param request 请求对象
* @return: java.lang.String 请求信息
*/
public static String requestMessage(String nonce, long timestamp, HttpRequestWrapper request) throws IOException {
URI uri = request.getURI();
String canonicalUrl = uri.getRawPath();
if (uri.getQuery() != null) {
canonicalUrl += "?" + uri.getRawQuery();
}
String body = EntityUtils.toString(((HttpEntityEnclosingRequest) request).getEntity(), StandardCharsets.UTF_8);
return request.getRequestLine().getMethod() + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
/***
* @description 检测应答参数
* @param response 应答对象
*/
public static final void validateParameters(CloseableHttpResponse response) {
//返回头中存在的内容:签名字符串,随机字符串,时间戳
String[] headers = {"signature","nonce_str","timestamp"};
Header header = null;
for (String headerName : headers) {
header = response.getFirstHeader(headerName);
if (header == null) {
throw new RuntimeException("验证返回参数"+headerName+"不全");
}
}
//获得最后时间戳判断应答时间戳到当前时间超过5分钟则认为应答失败
String timestampStr = header.getValue();
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestampStr));
// 拒绝过期应答
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new RuntimeException("应答超时");
}
} catch (DateTimeException | NumberFormatException e) {
throw new RuntimeException("应答时间处理异常");
}
}
/***
* @description 构建应答信息
* @param response 应答结果
* @return: java.lang.String 应答信息
*/
public static String responseMessage(CloseableHttpResponse response) throws IOException {
String timestamp = response.getFirstHeader("timestamp").getValue();
String nonce = response.getFirstHeader("nonce_str").getValue();
HttpEntity entity = response.getEntity();
String body = (entity != null && entity.isRepeatable()) ? EntityUtils.toString(entity) : "";;
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
/***
* @description 构建notify信息
* @param notifyRequest 推送请求结果
* @return: java.lang.String 应答信息
*/
public static String notifyMessage(NotifyRequest notifyRequest) {
String timestamp = notifyRequest.getTimestamp();
String nonce = notifyRequest.getNonce();
String body = notifyRequest.getBody();
return timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
public static void validateParametersNotify(NotifyRequest notifyRequest) {
//请求中必须存在的内容:签名字符串,随机字符串,时间戳
String signature = notifyRequest.getSignature();
if (signature==null){
throw new RuntimeException("签名字符串为空");
}
String nonce = notifyRequest.getNonce();
if (nonce==null){
throw new RuntimeException("随机字符串为空");
}
String timestamp = notifyRequest.getTimestamp();
if (timestamp==null){
throw new RuntimeException("时间戳为空");
}
//获得最后时间戳判断应答时间戳到当前时间超过5分钟则认为应答失败
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒绝过期应答
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new RuntimeException("应答超时");
}
} catch (DateTimeException | NumberFormatException e) {
throw new RuntimeException("应答时间处理异常");
}
}
}

View File

@@ -0,0 +1,123 @@
package com.itheima.sfbx.utils;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.symmetric.AES;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
/**
* @ClassName SymmetricCryptoUtil.java
* @Description TODO
*/
public class SecurityUtil {
/**
* 16字节
*/
private static final String ENCODE_KEY = "1234567812345678";
private static final String IV_KEY = "0000000000000000";
public static void main(String[] args) {
String password = "zdm321123.";
String encryptData = encryptFromStringAES(password, Mode.CBC, Padding.ZeroPadding);
System.out.println("AES加密" + encryptData);
String decryptData = decryptFromStringAES(encryptData, Mode.CBC, Padding.ZeroPadding);
System.out.println("AES解密" + decryptData);
RSA rsa = new RSA();
encryptData = encryptFromStringRSA(rsa,password);
System.out.println("RSA加密"+encryptData);
System.out.println("================用于网络传输===========");
decryptData = decryptFromStringRSA(rsa,encryptData);
//解密内容生成字符串
System.out.println("RSA解密" + decryptData);
}
public static String encryptFromStringAES(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
return aes.encryptBase64(data, StandardCharsets.UTF_8);
}
public static String encryptFromStringAES(String data, Mode mode, Padding padding,String securityKey) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(securityKey.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
return aes.encryptBase64(data, StandardCharsets.UTF_8);
}
public static String encryptFromStringRSA(RSA rsa,String password) {
//byte[]内容加密
byte[] encrypt = rsa.encrypt(StrUtil.bytes(password, CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey);
//加密后base64处理用于网络传输
return Base64.encodeBase64String(encrypt);
}
public static String encryptFromStringRSAPrivateKey(RSA rsa,String password) {
//byte[]内容加密
byte[] encrypt = rsa.encrypt(StrUtil.bytes(password, CharsetUtil.CHARSET_UTF_8), KeyType.PrivateKey);
//加密后base64处理用于网络传输
return Base64.encodeBase64String(encrypt);
}
public static String decryptFromStringAES(String data, Mode mode, Padding padding) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(IV_KEY.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
byte[] decryptDataBase64 = aes.decrypt(data);
return new String(decryptDataBase64, StandardCharsets.UTF_8);
}
public static String decryptFromStringAES(String data, Mode mode, Padding padding,String aesKey) {
AES aes;
if (Mode.CBC == mode) {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"),
new IvParameterSpec(aesKey.getBytes()));
} else {
aes = new AES(mode, padding,
new SecretKeySpec(ENCODE_KEY.getBytes(), "AES"));
}
byte[] decryptDataBase64 = aes.decrypt(data);
return new String(decryptDataBase64, StandardCharsets.UTF_8);
}
public static String decryptFromStringRSA(RSA rsa, String encryptData) {
//对byte数组进行解密
byte[] decrypt = rsa.decrypt(Base64.decodeBase64(encryptData), KeyType.PrivateKey);
return new String(decrypt, StandardCharsets.UTF_8);
}
public static String decryptFromStringRSAPublicKey(RSA rsa, String encryptData) {
//对byte数组进行解密
byte[] decrypt = rsa.decrypt(Base64.decodeBase64(encryptData), KeyType.PublicKey);
return new String(decrypt, StandardCharsets.UTF_8);
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.sfbx.config.SecurityConfig

View File

@@ -0,0 +1,104 @@
package com.itheima.sfbx;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.itheima.sfbx.auth.BoleeEncryptor;
import com.itheima.sfbx.auth.PrivateKeySigner;
import com.itheima.sfbx.auth.PublicKeyVerifier;
import com.itheima.sfbx.constants.BoleeSecurityConstant;
import com.itheima.sfbx.dto.EncodeDTO;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName BoleeHttpClientTest.java
* @Description TODO
*/
public class BoleeHttpClientTest {
private static String privateKeyBase64 = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAILdGdmrbwdn0tkD5I1RYB/whGEUsAOYprsy/F0cCdRFcUfNT6Q4BcErwcJAmdXPDuSYSbPsImKA0EbLN+pGh/wwlrYtBueItE4zGV2GdMNhAU3P6CquIAm5H58UFeDmVEUnE81laylXjaiaZduXBVTiLgTPVcpSxus70ZJQQirBAgMBAAECgYAg0sBHHn7MxrfWAunyoDSSDkvF5eB4JnO7hIBUAlJc0cYmElMlh3+6AfWpeXaccED2CVSDMnk1Z8XV2+b8dhBpTo3IHG+IQwZvz89bfMcwhhpRT9eXpC5YkG5UHlh11Ftm7byAo7s0HG/JynquxWZDRifWnvrA2bYE2sk6oN2zuQJBAMxlxcDDcSIdsZbXH7zTFJ6c0WEYlsXndRJhT1OfmxzwNitN+8PicbyJh6GfDcM+PR+13rqGZ4x+yGyKSjsp9XMCQQCj5tRLINjRUcNalPrJnvvUMhlJ0yrxsKReX81L1lwDXjbFpcMsy9eZddgdLjGXRgH7K9tYmLGZ6hflRGTcbrH7AkEAt/HDFOYOU1iLsKbrDgCcJt4T5CC/11ykVCU0wZn6ewGGjlRBBhksqDLQ19ePCC1jzrzas9wvJhYXAu81PKdXFwJAUftq4u1aJlFcgtmUG/efBUPN7GRozZ3Kib4nxTBCtBiTEwfX+Xc4r3UHlYj+mykUYptMSyONamxyaWZtgOkJswJBAMLjyUR61F4UPjoyGGg5G4eRTM3fJ1w7ulHYYxlEy5u2UAC/jBtd07wv030lsqPSA7abTdK+6iNM2v0gA7KODKk=";
private static String publicKeyBase64 = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCC3RnZq28HZ9LZA+SNUWAf8IRhFLADmKa7MvxdHAnURXFHzU+kOAXBK8HCQJnVzw7kmEmz7CJigNBGyzfqRof8MJa2LQbniLROMxldhnTDYQFNz+gqriAJuR+fFBXg5lRFJxPNZWspV42ommXblwVU4i4Ez1XKUsbrO9GSUEIqwQIDAQAB";
@Test
public void testTemplate() throws URISyntaxException, IOException {
URIBuilder urlBuilder = new URIBuilder()
.setScheme("http")
.setHost("localhost:8080")
.setPath("/insure");
RequestTemplate requestTemplate = RequestTemplate.builder()
.appId("10001")
.privateKey(privateKeyBase64)
.publicKey(publicKeyBase64)
.uriBuilder(urlBuilder)
.build();
Map<String, Object> params = new HashMap<>();
params.put("name","张三");
params.put("age","18");
params.put("sex","");
Map map = requestTemplate.doRequest(params, Map.class);
System.out.println(map);
}
@Test
public void BoleeHttpClientBuilderTest(){
CloseableHttpClient httpclient = BoleeHttpClientBuilder.create()
.withCredentials("10001", privateKeyBase64)
.withValidator(publicKeyBase64)
.build();
//=============基础配置===================
URIBuilder urlBuilder = new URIBuilder()
.setScheme("http")
.setHost("localhost:8080")
.setPath("/insure");
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(1000)
.setConnectTimeout(1000)
.build();
try {
URI url = urlBuilder.build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(requestConfig);
EncodeDTO encodeDTO = new EncodeDTO();
JSONObject body = JSONUtil.parseObj(new HashMap<>() {
{
put("age", 19);
}
});
encodeDTO.setBody(body.toString());
if(new BoleeEncryptor(privateKeyBase64).encode(encodeDTO)){
StringEntity stringEntity = new StringEntity(encodeDTO.getEncodeBody(), "utf-8");
httpPost.setEntity(stringEntity);
httpPost.addHeader(BoleeSecurityConstant.HEAD_NAME_BODY_KEY,encodeDTO.getHeadAESSecurityKey());
httpPost.addHeader("Content-Type","application/json;charset=utf-8");
CloseableHttpResponse apiRes = httpclient.execute(httpPost);
HttpEntity entity = apiRes.getEntity();
String content = EntityUtils.toString(entity);
System.out.println("接收到了返回信息:"+content);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-dict</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--数字字典接口模块-->
<artifactId>dict-interface</artifactId>
<name>dict-interface</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-feign</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.dict.config;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName DictFeignConfig.java
* @Description feign的最优化配置
*/
@Configuration
@EnableFeignClients(basePackages = "com.itheima.sfbx.dict")
public class DictFeignConfig {
}

View File

@@ -0,0 +1,42 @@
package com.itheima.sfbx.dict.feign;
import com.itheima.sfbx.dict.hystrix.DataDictHystrix;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName DataDictFace.java
* @Description数字字典feign接口类
*/
@FeignClient(value = "dict-web",fallback = DataDictHystrix.class)
public interface DataDictFeign {
/**
* @Description 根据parentKey获取value
* @return
*/
@PostMapping("data-dict-feign/parent-key/{parentKey}")
List<DataDictVO> findDataDictVOByParentKey(@PathVariable("parentKey") String parentKey);
/**
* @Description 根据dataKey获取DataDictVO
* @return
*/
@PostMapping("data-dict-feign/data-key/{dataKey}")
DataDictVO findDataDictVOByDataKey(@PathVariable("dataKey") String dataKey) ;
/**
* @Description 根据dataKey获取DataDictVO集合
* @return
*/
@PostMapping("data-dict-feign/findValueByDataKeys")
List<DataDictVO> findValueByDataKeys(@RequestBody ArrayList<String> dataKeys);
}

View File

@@ -0,0 +1,25 @@
package com.itheima.sfbx.dict.feign;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import java.util.List;
/**
* @ClassName PlacesFeign.java
* @Description 地区feign服务
*/
@FeignClient(value = "dict-web")
public interface PlacesFeign {
/***
* @description 查询下级
* @param parentId 父层Id
* @return
*/
@PostMapping("places-fegin/{parentId}")
public List<PlacesVO> findPlacesVOListByParentId(@PathVariable("parentId") Long parentId);
}

View File

@@ -0,0 +1,32 @@
package com.itheima.sfbx.dict.hystrix;
import com.itheima.sfbx.dict.feign.DataDictFeign;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName DataDictHystrix.java
* @Description DataDictFeign的Hystrix处理
*/
@Component
public class DataDictHystrix implements DataDictFeign {
@Override
public List<DataDictVO> findDataDictVOByParentKey(String parentKey) {
return null;
}
@Override
public DataDictVO findDataDictVOByDataKey(String dataKey) {
return null;
}
@Override
public List<DataDictVO> findValueByDataKeys(ArrayList<String> dataKeys) {
return null;
}
}

View File

@@ -0,0 +1,20 @@
package com.itheima.sfbx.dict.hystrix;
import com.itheima.sfbx.dict.feign.PlacesFeign;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @ClassName PlacesHystrix.java
* @Description PlacesFeign的Hystrix
*/
@Component
public class PlacesHystrix implements PlacesFeign {
@Override
public List<PlacesVO> findPlacesVOListByParentId(Long parentId) {
return null;
}
}

View File

@@ -0,0 +1,21 @@
FROM openjdk:11-jdk
LABEL maintainer="研究院研发组 <research@itcast.cn>"
# 时区修改为东八区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /dict-web
ARG PACKAGE_PATH=./target/dict-web.jar
ADD ${PACKAGE_PATH:-./} dict-web.jar
EXPOSE 8080
ENV JAVA_OPTS="\
-server \
-Xms256m \
-Xmx512m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m\
-Dspring.profiles.active=test"
ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom -jar $JAVA_OPTS dict-web.jar"]

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-dict</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--数字字典web模块-->
<artifactId>dict-web</artifactId>
<name>dict-web</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-web</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-seata</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-task-executor</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-knife4j-web</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yaml</include>
<include>**/*.txt</include>
</includes>
</resource>
</resources>
<finalName>dict-web</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package com.itheima.sfbx.dict;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 后台管理中的 系统管理-数字字典
*/
@SpringBootApplication(scanBasePackages = {"com.itheima.sfbx"})
public class DictWebStart {
public static void main(String[] args) {
SpringApplication.run(DictWebStart.class, args);
}
}

View File

@@ -0,0 +1,50 @@
package com.itheima.sfbx.dict.feign;
import com.itheima.sfbx.dict.service.IDataDictService;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @ClassName DataDictController.java
* @Description 数字字典controller
*/
@Slf4j
@RestController
@RequestMapping("data-dict-feign")
@Api(tags = "数字字典feign")
public class DataDictFeignController {
@Autowired
IDataDictService dataDictService;
/**
* @Description 父项键查询
* @return List<DataDictVO>
*/
@PostMapping("parent-key/{parentKey}")
@ApiOperation(value = "父项键查询",notes = "父项键查询")
@ApiImplicitParam(paramType = "path",name = "parentKey",value = "字典parentKey",example = "URGE_TYPE",dataType = "String")
List<DataDictVO> findDataDictVOByParentKey(@PathVariable("parentKey") String parentKey) {
return dataDictService.findDataDictVOByParentKey(parentKey);
}
/**
* @Description 子项键查询
* @return DataDictVO
*/
@PostMapping("data-key/{dataKey}")
@ApiOperation(value = "子项键查询",notes = "子项键查询")
@ApiImplicitParam(paramType = "path",name = "dataKey",value = "字典dataKey",example = "URGE_TYPE",dataType = "String")
DataDictVO findDataDictVOByDataKey(@PathVariable("dataKey")String dataKey){
return dataDictService.findDataDictVOByDataKey(dataKey);
}
}

View File

@@ -0,0 +1,35 @@
package com.itheima.sfbx.dict.feign;
import com.itheima.sfbx.dict.service.IPlacesService;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @ClassName PlacesFeignController.java
* @Description 地区controller
*/
@RestController
@RequestMapping("places-fgign")
@Api(tags = "地区fegin")
public class PlacesFeignController {
@Autowired
IPlacesService placesService;
/**
* @Description 保存字典数据
* @return
*/
@PostMapping("{parentId}")
@ApiOperation(value = "地区下拉框",notes = "地区下拉框")
@ApiImplicitParam(name = "parentId",value = "父层id",required = true,dataType = "Long")
public List<PlacesVO> findPlacesVOListByParentId(@PathVariable("parentId") Long parentId) {
return placesService.findPlacesVOListByParentId(parentId);
}
}

View File

@@ -0,0 +1,61 @@
package com.itheima.sfbx.dict.init;
import com.itheima.sfbx.dict.service.IDataDictService;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
/**
* @ClassName InitDataDict.java
* @Description 热加载数字字典
*/
@Component
public class InitDataDict {
@Autowired
IDataDictService dataDictService;
@Async
@PostConstruct
public void initDataDict(){
Timer timer = new Timer();
timer.schedule(new InitTask(timer),10*1000);
}
class InitTask extends TimerTask{
private Timer timer;
private InitTask(Timer timer) {
this.timer= timer;
}
@Override
public void run() {
//所有ParentKey的set集合
Set<String> parentKeyAll = dataDictService.findParentKeyAll();
if (EmptyUtil.isNullOrEmpty(parentKeyAll)){
return;
}
//初始化父亲目录下所有有效状态的数据
parentKeyAll.forEach(n->{
dataDictService.findDataDictVOByParentKey(n);
});
//所有dataKey的set集合
Set<String> dataKeyAll = dataDictService.findDataKeyAll();
if (EmptyUtil.isNullOrEmpty(parentKeyAll)){
return;
}
//初始化datakey对应有效状态的数据
dataKeyAll.forEach(n->{
dataDictService.findDataDictVOByDataKey(n);
});
}
}
}

View File

@@ -0,0 +1,61 @@
package com.itheima.sfbx.dict.init;
import com.itheima.sfbx.dict.service.IPlacesService;
import com.itheima.sfbx.framework.commons.constant.basic.SuperConstant;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
/**
* @ClassName InitPlaces.java
* @Description 热加载区域
*/
@Component
public class InitPlaces {
@Autowired
IPlacesService placesService;
@Async
@PostConstruct
public void initPlaces(){
Timer timer = new Timer();
timer.schedule(new InitTask(timer),10*1000);
}
class InitTask extends TimerTask {
private Timer timer;
private InitTask(Timer timer) {
this.timer= timer;
}
@Override
public void run() {
//初始化省列表
List<PlacesVO> provinces = placesService.findPlacesVOListByParentId(SuperConstant.CHINA_CODE);
if (EmptyUtil.isNullOrEmpty(provinces)){
return;
}
//初始化市列表
provinces.forEach(n->{
List<PlacesVO> citys = placesService.findPlacesVOListByParentId(n.getId());
if (EmptyUtil.isNullOrEmpty(citys)){
return;
}
//初始化区列表
citys.forEach(k->{
placesService.findPlacesVOListByParentId(k.getId());
});
});
}
}
}

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.dict.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.sfbx.dict.pojo.DataDict;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description数据字典表Mapper接口
*/
@Mapper
public interface DataDictMapper extends BaseMapper<DataDict> {
}

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.dict.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.sfbx.dict.pojo.Places;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description地方表Mapper接口
*/
@Mapper
public interface PlacesMapper extends BaseMapper<Places> {
}

View File

@@ -0,0 +1,46 @@
package com.itheima.sfbx.dict.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itheima.sfbx.framework.mybatisplus.basic.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Description数据字典表
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("tab_data_dict")
@ApiModel(value="DataDict对象", description="数据字典表")
public class DataDict extends BasePojo {
private static final long serialVersionUID = 1L;
@Builder
public DataDict(Long id, String dataState, String parentKey, String dataKey, String dataValue, String discription) {
super(id, dataState);
this.parentKey = parentKey;
this.dataKey = dataKey;
this.dataValue = dataValue;
this.discription = discription;
}
@ApiModelProperty(value = "父key")
private String parentKey;
@ApiModelProperty(value = "数据字典KEY")
private String dataKey;
@ApiModelProperty(value = "")
private String dataValue;
@ApiModelProperty(value = "描述")
private String discription;
}

View File

@@ -0,0 +1,38 @@
package com.itheima.sfbx.dict.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itheima.sfbx.framework.mybatisplus.basic.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Description地方表
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("tab_places")
@ApiModel(value="Places对象", description="地方表")
public class Places extends BasePojo {
private static final long serialVersionUID = 1L;
@Builder
public Places(Long id, String dataState, Long parentId, String cityName) {
super(id, dataState);
this.parentId = parentId;
this.cityName = cityName;
}
@ApiModelProperty(value = "父ID")
private Long parentId;
@ApiModelProperty(value = "名称")
private String cityName;
}

View File

@@ -0,0 +1,67 @@
package com.itheima.sfbx.dict.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.sfbx.dict.pojo.DataDict;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import java.util.List;
import java.util.Set;
/**
* @Description数据字典表 服务类
*/
public interface IDataDictService extends IService<DataDict> {
/***
* @description 数据字典列表数据
*
* @param dataDictVO 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return: Page<DataDictVO>
*/
Page<DataDictVO> findDataDictVOPage(DataDictVO dataDictVO, int pageNum, int pageSize);
/**
* @Description 检测key是否已经存在
* @return
*/
Boolean checkByDataKey(String dataKey);
/**
* @Description 保存字典数据
* @return
*/
DataDictVO saveDataDict(DataDictVO dataDictVO) ;
/**
* @Description 修改字典数据
* @return
*/
DataDictVO updateDataDict(DataDictVO dataDictVO);
/**
* @Description 根据dataKey获取value
* @return DataDictVO
*/
DataDictVO findDataDictVOByDataKey(String dataKey);
/**
* @Description 获得所有不重复的ParentKey的set集合
* @return Set<String>
*/
Set<String> findParentKeyAll();
/**
* @Description 获取父key下的数据
* @return List<DataDictVO>
*/
List<DataDictVO> findDataDictVOByParentKey(String parentKey);
/**
* @Description 获得所有不重复的DataKey的set集合
* @return Set<String>
*/
Set<String> findDataKeyAll();
}

View File

@@ -0,0 +1,23 @@
package com.itheima.sfbx.dict.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.sfbx.dict.pojo.Places;
import com.itheima.sfbx.framework.commons.constant.dict.PlacesCacheConstant;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import org.springframework.cache.annotation.Cacheable;
import java.util.List;
/**
* @Description地方表 服务类
*/
public interface IPlacesService extends IService<Places> {
/***
* @description 查询下级
* @param parentId
* @return: List<Places>
*/
List<PlacesVO> findPlacesVOListByParentId(Long parentId);
}

View File

@@ -0,0 +1,191 @@
package com.itheima.sfbx.dict.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.sfbx.dict.mapper.DataDictMapper;
import com.itheima.sfbx.dict.pojo.DataDict;
import com.itheima.sfbx.dict.service.IDataDictService;
import com.itheima.sfbx.framework.commons.constant.basic.SuperConstant;
import com.itheima.sfbx.framework.commons.constant.dict.DataDictCacheConstant;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import com.itheima.sfbx.framework.commons.enums.dict.DataDictEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.ExceptionsUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Description数据字典表 服务实现类
*/
@Slf4j
@Service
public class DataDictServiceImpl extends ServiceImpl<DataDictMapper, DataDict> implements IDataDictService {
/***
* @description 构建多条件查询条件
* @param dataDictVO 查询条件
* @return 查询条件
*/
private QueryWrapper queryWrapper(DataDictVO dataDictVO){
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
//多条件查询
if (!EmptyUtil.isNullOrEmpty(dataDictVO.getDiscription())){
queryWrapper.lambda().like(DataDict::getDiscription,dataDictVO.getDiscription());
}
if (!EmptyUtil.isNullOrEmpty(dataDictVO.getParentKey())){
queryWrapper.lambda().like(DataDict::getParentKey,dataDictVO.getParentKey());
}
if (!EmptyUtil.isNullOrEmpty(dataDictVO.getDataKey())){
queryWrapper.lambda().like(DataDict::getDataKey,dataDictVO.getDataKey());
}
if (!EmptyUtil.isNullOrEmpty(dataDictVO.getDataValue())){
queryWrapper.lambda().like(DataDict::getDataValue,dataDictVO.getDataValue());
}
if (!EmptyUtil.isNullOrEmpty(dataDictVO.getDataState())){
queryWrapper.lambda().eq(DataDict::getDataState,dataDictVO.getDataState());
}
queryWrapper.lambda().orderByDesc(DataDict::getCreateTime).orderByAsc(DataDict::getDataKey);
return queryWrapper;
}
@Override
@Cacheable(value = DataDictCacheConstant.PAGE,key ="#pageNum+'-'+#pageSize+'-'+#dataDictVO.hashCode()")
public Page<DataDictVO> findDataDictVOPage(DataDictVO dataDictVO, int pageNum, int pageSize) {
try {
Page<DataDict> page = new Page<>(pageNum, pageSize);
QueryWrapper<DataDict> queryWrapper = this.queryWrapper(dataDictVO);
return BeanConv.toPage(page(page, queryWrapper),DataDictVO.class);
}catch (Exception e){
log.error("查询数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.PAGE_FAIL);
}
}
@Override
public Boolean checkByDataKey(String dataKey) {
try {
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DataDict::getDataKey,dataKey);
return !EmptyUtil.isNullOrEmpty(getOne(queryWrapper));
}catch (Exception e){
log.error("检查数字字典重复性异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.CHECK_FALL);
}
}
@Override
@Caching(
evict={@CacheEvict(value = DataDictCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = DataDictCacheConstant.PARENT_KEY,key = "#dataDictVO.parentKey")},
put={@CachePut(value = DataDictCacheConstant.DATA_KEY,key = "#dataDictVO.dataKey")})
@Transactional
public DataDictVO saveDataDict(DataDictVO dataDictVO) {
try {
DataDict dataDict =BeanConv.toBean(dataDictVO,DataDict.class);
boolean flag = save(dataDict);
if (flag){
return BeanConv.toBean(dataDict,DataDictVO.class);
}else {
log.error("数据字典保存异常!");
throw new ProjectException(DataDictEnum.SAVE_FAIL);
}
}catch (Exception e){
log.error("数据字典保存异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.SAVE_FAIL);
}
}
@Override
@Caching(
evict={@CacheEvict(value = DataDictCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = DataDictCacheConstant.PARENT_KEY,key = "#dataDictVO.parentKey"),
@CacheEvict(value = DataDictCacheConstant.DATA_KEY,key = "#dataDictVO.dataKey")})
@Transactional
public DataDictVO updateDataDict(DataDictVO dataDictVO) {
try {
DataDict dataDict = BeanConv.toBean(dataDictVO, DataDict.class);
DataDict dataDictTemp = getById(dataDictVO.getId());
dataDict.setCreateBy(dataDictTemp.getCreateBy());
dataDict.setCreateTime(dataDictTemp.getCreateTime());
boolean flag = updateById(dataDict);
if (flag){
return BeanConv.toBean(dataDict, DataDictVO.class);
}else {
log.error("修改数据字典列表异常!");
throw new ProjectException(DataDictEnum.UPDATE_FAIL);
}
}catch (Exception e){
log.error("修改数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.UPDATE_FAIL);
}
}
@Override
public Set<String> findParentKeyAll() {
try {
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DataDict::getDataState, SuperConstant.DATA_STATE_0);
List<DataDict> list = list(queryWrapper);
return EmptyUtil.isNullOrEmpty(list)?null:list.stream().map(DataDict::getParentKey).collect(Collectors.toSet());
}catch (Exception e){
log.error("查询数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.FIND_PARENTKEY_ALL);
}
}
@Override
@Cacheable(value = DataDictCacheConstant.PARENT_KEY,key = "#parentKey")
public List<DataDictVO> findDataDictVOByParentKey(String parentKey) {
try {
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DataDict::getParentKey,parentKey)
.eq(DataDict::getDataState, SuperConstant.DATA_STATE_0);
return BeanConv.toBeanList(list(queryWrapper),DataDictVO.class);
}catch (Exception e){
log.error("查询数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.FIND_DATADICTVO_PARENTKEY);
}
}
@Override
public Set<String> findDataKeyAll() {
try {
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DataDict::getDataState, SuperConstant.DATA_STATE_0);
List<DataDict> list = list(queryWrapper);
return EmptyUtil.isNullOrEmpty(list)?null:list.stream().map(DataDict::getDataKey).collect(Collectors.toSet());
}catch (Exception e){
log.error("查询数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.FIND_DATAKEY_ALL);
}
}
@Override
@Cacheable(value = DataDictCacheConstant.DATA_KEY,key = "#dataKey")
public DataDictVO findDataDictVOByDataKey(String dataKey) {
try {
QueryWrapper<DataDict> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(DataDict::getDataKey,dataKey)
.eq(DataDict::getDataState, SuperConstant.DATA_STATE_0);
return BeanConv.toBean(getOne(queryWrapper),DataDictVO.class);
}catch (Exception e){
log.error("查询数据字典列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(DataDictEnum.FIND_DATADICTVO_DATAKEY);
}
}
}

View File

@@ -0,0 +1,40 @@
package com.itheima.sfbx.dict.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.sfbx.dict.mapper.PlacesMapper;
import com.itheima.sfbx.dict.pojo.Places;
import com.itheima.sfbx.dict.service.IPlacesService;
import com.itheima.sfbx.framework.commons.constant.dict.PlacesCacheConstant;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import com.itheima.sfbx.framework.commons.enums.dict.PlacesEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.sfbx.framework.commons.utils.ExceptionsUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Description地方表 服务实现类
*/
@Slf4j
@Service
public class PlacesServiceImpl extends ServiceImpl<PlacesMapper, Places> implements IPlacesService {
@Override
@Cacheable(value = PlacesCacheConstant.LIST,key = "#parentId")
public List<PlacesVO> findPlacesVOListByParentId(Long parentId) {
try {
QueryWrapper<Places> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Places::getParentId,parentId);
return BeanConv.toBeanList(list(queryWrapper),PlacesVO.class);
}catch (Exception e){
log.error("查询parentId下列表异常{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(PlacesEnum.LIST_FAIL);
}
}
}

View File

@@ -0,0 +1,115 @@
package com.itheima.sfbx.dict.web;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.itheima.sfbx.dict.service.IDataDictService;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.dto.dict.DataDictVO;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @ClassName DataDictController.java
* @Description 数字字典controller
*/
@Slf4j
@RestController
@RequestMapping("data-dict")
@Api(tags = "数字字典")
public class DataDictController {
@Autowired
IDataDictService dataDictService;
/***
* @description 数据字典分页
* @param dataDictVO 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return: Page<DataDictVO>
*/
@PostMapping("page/{pageNum}/{pageSize}")
@ApiOperation(value = "数据字典分页",notes = "数据字典分页")
@ApiImplicitParams({
@ApiImplicitParam(name = "dataDictVO",value = "字典查询对象",required = false,dataType = "DataDictVO"),
@ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",example = "1",dataType = "Integer"),
@ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",example = "10",dataType = "Integer")
})
@ApiOperationSupport(includeParameters ={"dataDictVO.parentKey","","dataDictVO.dataKey",
"dataDictVO.dataValue","dataDictVO.discription"} )
ResponseResult<Page<DataDictVO>> findDataDictVOPage(
@RequestBody DataDictVO dataDictVO,
@PathVariable("pageNum") int pageNum,
@PathVariable("pageSize") int pageSize) {
Page<DataDictVO> dataDictVOPage = dataDictService.findDataDictVOPage(dataDictVO, pageNum, pageSize);
return ResponseResultBuild.successBuild(dataDictVOPage);
}
/**
* @Description 保存字典数据
* @return
*/
@PostMapping
@ApiOperation(value = "数字字典添加",notes = "数字字典添加")
@ApiImplicitParam(name = "dataDictVO",value = "字典信息",required = true,dataType = "DataDictVO")
@ApiOperationSupport(ignoreParameters ={"dataDictVO.id", "dataDictVO.updateTime",
"dataDictVO.createBy","dataDictVO.updateBy", "dataDictVO.creator"} )
public ResponseResult<DataDictVO> saveDataDict(@RequestBody DataDictVO dataDictVO) {
DataDictVO dataDictVOResult = dataDictService.saveDataDict(dataDictVO);
return ResponseResultBuild.successBuild(dataDictVOResult);
}
/**
* @Description 修改字典数据
* @return
*/
@PatchMapping
@ApiOperation(value = "数字字典编辑",notes = "数字字典编辑")
@ApiImplicitParam(name = "dataDictVO",value = "字典信息",required = true,dataType = "DataDictVO")
@ApiOperationSupport(ignoreParameters ={"dataDictVO.updateTime", "dataDictVO.createBy",
"dataDictVO.updateBy", "dataDictVO.creator"})
public ResponseResult<DataDictVO> updateDataDict(@RequestBody DataDictVO dataDictVO) {
DataDictVO dataDictVOResult = dataDictService.updateDataDict(dataDictVO);
return ResponseResultBuild.successBuild(dataDictVOResult);
}
@PostMapping("data-state")
@ApiOperation(value = "数字字典状态编辑",notes = "数字字典状态编辑")
@ApiImplicitParam(name = "dataDictVO",value = "字典信息",required = true,dataType = "DataDictVO")
@ApiOperationSupport(ignoreParameters ={"dataDictVO.id", "dataDictVO.dataState"})
ResponseResult<DataDictVO> updateDataDictEnableFlag(@RequestBody DataDictVO dataDictVO) {
DataDictVO dataDictVOResult = dataDictService.updateDataDict(dataDictVO);
return ResponseResultBuild.successBuild(dataDictVOResult);
}
/**
* @Description 父项键查询
* @return List<DataDictVO>
*/
@PostMapping("parent-key/{parentKey}")
@ApiOperation(value = "父项键查询",notes = "父项键查询")
@ApiImplicitParam(paramType = "path",name = "parentKey",value = "字典parentKey",example = "URGE_TYPE",dataType = "String")
ResponseResult<List<DataDictVO>> findDataDictVOByParentKey(@PathVariable("parentKey") String parentKey) {
return ResponseResultBuild.successBuild(dataDictService.findDataDictVOByParentKey(parentKey));
}
/**
* @Description 子项键查询
* @return DataDictVO
*/
@PostMapping("data-key/{dataKey}")
@ApiOperation(value = "子项键查询",notes = "子项键查询")
@ApiImplicitParam(paramType = "path",name = "dataKey",value = "字典dataKey",example = "URGE_TYPE",dataType = "String")
ResponseResult<DataDictVO> findDataDictVOByDataKey(@PathVariable("dataKey")String dataKey){
return ResponseResultBuild.successBuild(dataDictService.findDataDictVOByDataKey(dataKey));
}
}

View File

@@ -0,0 +1,41 @@
package com.itheima.sfbx.dict.web;
import com.itheima.sfbx.dict.service.IPlacesService;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.dto.dict.PlacesVO;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @ClassName PlacesFeignController.java
* @Description 地区controller
*/
@RestController
@RequestMapping("places")
@Api(tags = "地区")
public class PlacesController {
@Autowired
IPlacesService placesService;
/**
* @Description 保存字典数据
* @return
*/
@PostMapping("{parentId}")
@ApiOperation(value = "地区下拉框",notes = "地区下拉框")
@ApiImplicitParam(name = "parentId",value = "父层id",required = true,dataType = "Long")
public ResponseResult<List<PlacesVO>> findPlacesVOListByParentId(@PathVariable("parentId") Long parentId) {
List<PlacesVO> placesVOList = placesService.findPlacesVOListByParentId(parentId);
return ResponseResultBuild.successBuild(placesVOList);
}
}

View File

@@ -0,0 +1,10 @@
_ __ __
(_) /__________ ______/ /_
/ / __/ ___/ __ `/ ___/ __/
/ / /_/ /__/ /_/ (__ ) /_
/_/\__/\___/\__,_/____/\__/
:: Spring Boot :: (v-2.7.10)
:: Spring Cloud :: (v-2021.0.6)
:: Spring Cloud Alibaba :: (v-2021.0.1.0)
:: sfbx Cloud :: (v-2.0-SNAPSHOT)
:: 献给可爱的传智人 ::

View File

@@ -0,0 +1,53 @@
#服务配置
server:
#端口
port: 7071
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
# profiles:
# active: test
config:
activate:
on-profile:
- test
main:
allow-circular-references: true
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#应用配置
application:
#应用名称
name: dict-web
cloud:
nacos:
discovery:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos注册中心
group: SEATA_GROUP
service: ${spring.application.name}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
config:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos配置中心地址
group: SEATA_GROUP
file-extension: yml
shared-configs: # 共享配置
- data-id: shared-spring-seata.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-spring-task.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-redisson.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-mybatis-plus.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
logging:
config: classpath:logback.xml

View File

@@ -0,0 +1,45 @@
#服务配置
server:
#端口
port: 7071
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
profiles:
active: dev
main:
allow-circular-references: true
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#应用配置
application:
#应用名称
name: dict-web
cloud:
nacos:
discovery:
server-addr: 192.168.12.129:8848 # nacos注册中心
group: SEATA_GROUP
service: ${spring.application.name}
config:
server-addr: 192.168.12.129:8848 # nacos配置中心地址
group: SEATA_GROUP
file-extension: yml
shared-configs: # 共享配置
- data-id: shared-spring-seata.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-spring-task.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-redisson.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-mybatis-plus.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
logging:
config: classpath:logback.xml

View File

@@ -0,0 +1,33 @@
#\u6570\u636E\u5E93\u5730\u5740
url=jdbc:mysql://192.168.12.129:3306/restkeeper-dict?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&tinyInt1isBit=false
#\u6570\u636E\u5E93\u8D26\u53F7
userName=root
#\u6570\u636E\u5E93\u5BC6\u7801
password=pass
#\u6B64\u5904\u4E3A\u672C\u9879\u76EEsrc\u6240\u5728\u8DEF\u5F84\uFF08\u4EE3\u7801\u751F\u6210\u5668\u8F93\u51FA\u8DEF\u5F84\uFF09
serviceProjectPath=D:/easy-cloud/easy-dict/dict-web
#\u8BBE\u7F6E\u4F5C\u8005
author=Admin
#\u81EA\u5B9A\u4E49\u5305\u8DEF\u5F84
parent=com.itheima
#\u88C5\u4EE3\u7801\u7684\u6587\u4EF6\u5939\u540D
moduleName=project
#\u8BBE\u7F6E\u8868\u524D\u7F00\uFF0C\u4E0D\u8BBE\u7F6E\u5219\u9ED8\u8BA4\u65E0\u524D\u7F00
tablePrefix =tab_
#\u6570\u636E\u5E93\u8868\u540D(\u6B64\u5904\u5207\u4E0D\u53EF\u4E3A\u7A7A\uFF0C\u5982\u679C\u4E3A\u7A7A\uFF0C\u5219\u9ED8\u8BA4\u8BFB\u53D6\u6570\u636E\u5E93\u7684\u6240\u6709\u8868\u540D)
tableName=tab_data_dict,tab_places
#pojo\u7684\u8D85\u7C7B
SuperEntityClass = com.itheima.sfbx.framework.commons.basic.BasicPojo
#pojo\u7684\u8D85\u7C7B\u516C\u7528\u5B57\u6BB5
superEntityColumns = id,created_time,updated_time,sharding_id,enable_flag
#\u751F\u6210\u7684\u5C42\u7EA7
entity=true
entity.ftl.path=/templates/entity.java
mapper=true
mapper.ftl.path=/templates/mapper.java
service=false
service.ftl.path=/templates/service.java
serviceImp=false
serviceImp.ftl.path=/templates/serviceImpl.java
controller=false
controller.ftl.path=/templates/controller.java

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
<property name="LOG_HOME" value="/data/logs/dict-web" />
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/dict-web-01.log.%d{yyyy-MM-dd}.log
</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符 -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50}-%msg%n</pattern>
</encoder>
<!--日志文件最大的大小 -->
<triggeringPolicy
class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
<logger name="org.hibernate.type.descriptor.sql.BasicBinder"
level="TRACE" />
<logger name="org.hibernate.type.descriptor.sql.BasicExtractor"
level="DEBUG" />
<logger name="org.hibernate.SQL" level="DEBUG" />
<logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
<logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />
<!--myibatis log configure -->
<logger name="com.apache.ibatis" level="TRACE" />
<logger name="java.sql.Connection" level="DEBUG" />
<logger name="java.sql.Statement" level="DEBUG" />
<logger name="java.sql.PreparedStatement" level="DEBUG" />
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
<!--日志异步到数据库 -->
</configuration>

View File

@@ -0,0 +1,34 @@
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* @Description${table.comment!} 前端控制器
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName??>/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
}
</#if>

View File

@@ -0,0 +1,156 @@
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.EqualsAndHashCode;
</#if>
/**
* @Description${table.comment!}
*/
<#if entityLombokModel>
@Data
@NoArgsConstructor
<#if superEntityClass??>
@EqualsAndHashCode(callSuper = true)
<#else>
@EqualsAndHashCode(callSuper = false)
</#if>
</#if>
<#if table.convert>
@TableName("${table.name}")
</#if>
<#if swagger2>
@ApiModel(value="${entity}对象", description="${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} implements Serializable {
</#if>
<#if entitySerialVersionUID>
private static final long serialVersionUID = 1L;
</#if>
@Builder
public ${entity}(Long id,<#list table.fields as field>${field.propertyType} ${field.propertyName}<#if field_has_next>,</#if></#list>){
super(id);
<#list table.fields as field>
this.${field.propertyName}=${field.propertyName};
</#list>
}
<#-- ---------- BEGIN 字段循环遍历 ---------->
<#list table.fields as field>
<#if field.keyFlag>
<#assign keyPropertyName="${field.propertyName}"/>
</#if>
<#if field.comment!?length gt 0>
<#if swagger2>
@ApiModelProperty(value = "${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.name}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.name}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.name}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.name}")
</#if>
<#-- 乐观锁注解 -->
<#if (versionFieldName!"") == field.name>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if (logicDeleteFieldName!"") == field.name>
@TableLogic
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if entityBuilderModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if entityBuilderModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
protected Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName}=" + ${field.propertyName} +
<#else>
", ${field.propertyName}=" + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}

View File

@@ -0,0 +1,15 @@
package ${package.Mapper};
import ${package.Entity}.${entity};
import ${superMapperClassPackage};
/**
* @Description${table.comment!}Mapper接口
*/
<#if kotlin>
interface ${table.mapperName} : ${superMapperClass}<${entity}>
<#else>
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {
}
</#if>

View File

@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
<#if enableCache>
<!-- 开启二级缓存 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
</#if>
<#if baseResultMap>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
<id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
</resultMap>
</#if>
<#if baseColumnList>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
<#list table.commonFields as field>
${field.name},
</#list>
${table.fieldNames}
</sql>
</#if>
</mapper>

View File

@@ -0,0 +1,15 @@
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
/**
* @Description${table.comment!} 服务类
*/
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
}
</#if>

View File

@@ -0,0 +1,21 @@
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
/**
* @Description${table.comment!} 服务实现类
*/
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>(), ${table.serviceName} {
}
<#else>
public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}> implements ${table.serviceName} {
}
</#if>

22
sfbx-dict/pom.xml Normal file
View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>sfbx-cloud</artifactId>
<groupId>com.itheima.sfbx</groupId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--数据字典模块-->
<artifactId>sfbx-dict</artifactId>
<packaging>pom</packaging>
<name>sfbx-dict</name>
<modules>
<module>dict-interface</module>
<module>dict-web</module>
</modules>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
</project>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-file</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--文件处理接口模块-->
<artifactId>file-interface</artifactId>
<name>file-interface</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-feign</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.file.config;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
/**
* @ClassName DictFenginConfig.java
* @Description feign的最优化配置
*/
@EnableFeignClients(basePackages = "com.itheima.sfbx.file.feign")
@Configuration
public class FileFeignConfig {
}

View File

@@ -0,0 +1,83 @@
package com.itheima.sfbx.file.feign;
import com.itheima.sfbx.file.hystrix.FileBusinessHystrix;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName FileFeign.java
* @Description 附件feign接口
*/
@FeignClient(value = "file-web",fallback = FileBusinessHystrix.class)
public interface FileBusinessFeign {
/**
* @Description 业务与文件绑定
* @param fileVO 附件对象
* @return com.itheima.travel.req.FileVO
*/
@PostMapping("file-feign/bind-file")
List<FileVO> bindFile(@RequestBody FileVO fileVO);
/**
* @Description 业务绑定多个附件
* @param fileVOs 相同业务的多个附件对象
* @return
*/
@PostMapping("file-feign/bind-batch-file")
List<FileVO> bindBatchFile(@RequestBody ArrayList<FileVO> fileVOs);
/**
* @Description 移除业务原附件,并绑定新的附件到业务上
* @param fileVO 附件对象
* @return
*/
@PostMapping(value = "file-feign/replace-bind-file")
Boolean replaceBindFile(@RequestBody FileVO fileVO);
/**
* @Description 移除业务原附件,并批量绑定新的附件到业务上
* @param fileVOs 附件对象
* @return
*/
@PostMapping(value = "file-feign/replace-bind-batch-file")
Boolean replaceBindBatchFile(@RequestBody ArrayList<FileVO> fileVOs);
/**
* @Description 按业务ID查询附件【对象传递参数方式】
* @param businessIds 业务IDS
* @return List<FileVO>
*/
@PostMapping("file-feign/find-in-business-ids")
List<FileVO> findInBusinessIds(@RequestBody List<Long> businessIds);
/**
* @Description 删除业务相关附件
* @param businessIds 业务IDS
* @return Boolean
*/
@DeleteMapping("file-feign/delete-by-business-ids")
Boolean deleteByBusinessIds(@RequestBody ArrayList<Long> businessIds);
/**
* @Description 定时清理文件
* @return
*/
@DeleteMapping("file-feign/clear-file")
Boolean clearFile();
/**
* @Description 延迟队列清理文件
* @return
*/
@DeleteMapping("file-feign/clear-file-id/{id}")
Boolean clearFileById(@PathVariable("id") Long id);
}

View File

@@ -0,0 +1,24 @@
package com.itheima.sfbx.file.feign;
import com.itheima.sfbx.file.hystrix.FileBusinessHystrix;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @ClassName FileFeign.java
* @Description 文件下载feign接口
*/
@FeignClient(value = "file-web",fallback = FileBusinessHystrix.class)
public interface FileDownLoadFeign {
/***
* @description 文件下载-服务远程调用-base64方式返回
* @param fileId 上传对象
* @return: com.itheima.travel.req.FileVo
*/
@PostMapping("file-feign/down-load/{fileId}")
FileVO downLoad(@PathVariable("fileId") Long fileId);
}

View File

@@ -0,0 +1,57 @@
package com.itheima.sfbx.file.hystrix;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.file.feign.FileBusinessFeign;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName FileHystrix.java
* @Description FileFeign的Hystrix
*/
@Component
public class FileBusinessHystrix implements FileBusinessFeign {
@Override
public List<FileVO> bindFile(FileVO fileVO) {
return null;
}
@Override
public List<FileVO> bindBatchFile(ArrayList<FileVO> fileVOs) {
return null;
}
@Override
public Boolean replaceBindFile(FileVO fileVO) {
return null;
}
@Override
public Boolean replaceBindBatchFile(ArrayList<FileVO> fileVOs) {
return null;
}
@Override
public List<FileVO> findInBusinessIds(List<Long> businessIds) {
return null;
}
@Override
public Boolean deleteByBusinessIds(ArrayList<Long> businessIds) {
return null;
}
@Override
public Boolean clearFile() {
return null;
}
@Override
public Boolean clearFileById(Long id) {
return null;
}
}

View File

@@ -0,0 +1,19 @@
package com.itheima.sfbx.file.hystrix;
import com.itheima.sfbx.file.feign.FileDownLoadFeign;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import org.springframework.stereotype.Component;
/**
* @ClassName FileHystrix.java
* @Description FileFeign的Hystrix
*/
@Component
public class FileDownLoadHystrix implements FileDownLoadFeign {
@Override
public FileVO downLoad(Long fileId) {
return null;
}
}

View File

@@ -0,0 +1,21 @@
FROM openjdk:11-jdk
LABEL maintainer="研究院研发组 <research@itcast.cn>"
# 时区修改为东八区
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
WORKDIR /file-web
ARG PACKAGE_PATH=./target/file-web.jar
ADD ${PACKAGE_PATH:-./} file-web.jar
EXPOSE 8080
ENV JAVA_OPTS="\
-server \
-Xms256m \
-Xmx512m \
-XX:MetaspaceSize=256m \
-XX:MaxMetaspaceSize=512m\
-Dspring.profiles.active=test"
ENTRYPOINT ["sh","-c","java -Djava.security.egd=file:/dev/./urandom -jar $JAVA_OPTS file-web.jar"]

105
sfbx-file/file-web/pom.xml Normal file
View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.itheima.sfbx</groupId>
<artifactId>sfbx-file</artifactId>
<version>2.0-SNAPSHOT</version>
</parent>
<!--文件处理web模块-->
<artifactId>file-web</artifactId>
<name>file-web</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-rabbitmq</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-seata</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-web</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-mybatis-plus</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.itheima.sfbx</groupId>
<artifactId>framework-knife4j-web</artifactId>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.yml</include>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.yaml</include>
<include>**/*.txt</include>
</includes>
</resource>
</resources>
<finalName>file-web</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package com.itheima.sfbx.file;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 文件上传处理微服务
*/
@SpringBootApplication(scanBasePackages = "com.itheima.sfbx")
public class FileWebStart {
public static void main(String[] args) {
SpringApplication.run(FileWebStart.class, args);
}
}

View File

@@ -0,0 +1,79 @@
package com.itheima.sfbx.file.adapter;
import com.itheima.sfbx.file.pojo.File;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @ClassName FileStorageAdapter.java
* @Description 文件存储适配处理
*/
public interface FileStorageAdapter {
/**
* 文件上传
* @param fileVO {@link FileVO} 文件信息对象
* @param inputStream 文件流
* @return pathUrl 全路径
*/
String uploadFile(FileVO fileVO, InputStream inputStream);
/***
* @description 分片上传-初始化分片请求
* @param fileVO {@link FileVO} 文件信息对象
* @return uploadId 文件上传id
*/
File initiateMultipartUpload(FileVO fileVO);
/***
* @description 分片上传-上传每个分片文件
* @param filePartVo {@link FilePartVO} 文件信息对象
* @param inputStream 当前分片文件流
* @return PartETag json字符串
*/
String uploadPart(FilePartVO filePartVo, InputStream inputStream);
/***
* @description 分片上传-合并所有上传文件
* @param fileVO {@link File} 文件信息对象
* @return 合并结果
*/
String completeMultipartUpload(FileVO fileVO);
/**
* @Description 下载文件
* @param storeFlag 存储源标识
* @param bucketName 资源存储区域名称
* @param pathUrl 资源文件路径地址(其中包含文件名称)
* @return
*/
InputStream downloadFile(String storeFlag,String bucketName,String pathUrl) throws IOException;
/**
* @Description 文件删除
* @param pathUrl 全路径
* @throws Exception
*/
void delete(String storeFlag,String bucketName,String pathUrl);
/**
* @Description 批量文件删除
* @param pathUrls 全路径集合
* @throws Exception
*/
void deleteBatch(String storeFlag,String bucketName,List<String> pathUrls);
/**
* @Description 获取文件文本内容
* @param pathUrl 全路径
* @return
* @throws IOException
*/
String getFileContent(String storeFlag,String bucketName,String pathUrl) throws IOException;
}

View File

@@ -0,0 +1,100 @@
package com.itheima.sfbx.file.adapter.impl;
import com.itheima.sfbx.file.adapter.FileStorageAdapter;
import com.itheima.sfbx.file.handler.FileStorageHandler;
import com.itheima.sfbx.framework.commons.constant.file.FileConstant;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.RegisterBeanHandler;
import com.itheima.sfbx.file.pojo.File;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @ClassName FileStorageAdapterImpl.java
* @Description 文件存储适配处理
*/
@Component
public class FileStorageAdapterImpl implements FileStorageAdapter {
@Autowired
RegisterBeanHandler registerBeanHandler;
private static Map<String,String> fileStorageHandlers =new HashMap<>();
static {
fileStorageHandlers.put(FileConstant.ALIYUN_OSS,"ossFileStorageHandler");
fileStorageHandlers.put(FileConstant.QINIU_KODO,"kodoFileStorageHandler");
}
@Override
public String uploadFile(FileVO fileVO, InputStream inputStream) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(fileVO.getStoreFlag())?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(fileVO.getStoreFlag());
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.uploadFile(fileVO.getSuffix(), fileVO.getFileName(), fileVO.getBucketName(), fileVO.getAutoCatalog(), inputStream);
}
@Override
public File initiateMultipartUpload(FileVO fileVO) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(fileVO.getStoreFlag())?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(fileVO.getStoreFlag());
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.initiateMultipartUpload(fileVO.getSuffix(),fileVO.getFileName(),fileVO.getBucketName(),fileVO.getAutoCatalog());
}
@Override
public String uploadPart(FilePartVO filePartVo, InputStream inputStream) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(filePartVo.getStoreFlag())?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(filePartVo.getStoreFlag());
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.uploadPart(filePartVo.getUploadId(),filePartVo.getFileName(),filePartVo.getPartNumber(),filePartVo.getPartSize(),filePartVo.getBucketName(),inputStream);
}
@Override
public String completeMultipartUpload(FileVO fileVO) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(fileVO.getStoreFlag())?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(fileVO.getStoreFlag());
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.completeMultipartUpload(fileVO.getUploadId(),fileVO.getPartETags(),fileVO.getFileName(),fileVO.getBucketName());
}
@Override
public InputStream downloadFile(String storeFlag,String bucketName,String pathUrl) throws IOException {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(storeFlag)?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(storeFlag);
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.downloadFile(bucketName,pathUrl);
}
@Override
public void delete(String storeFlag,String bucketName,String pathUrl) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(storeFlag)?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(storeFlag);
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
fileStorageHandler.delete(bucketName,pathUrl);
}
@Override
public void deleteBatch(String storeFlag,String bucketName,List<String> pathUrls) {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(storeFlag)?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(storeFlag);
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
fileStorageHandler.deleteBatch(bucketName,pathUrls);
}
@Override
public String getFileContent(String storeFlag,String bucketName,String pathUrl) throws IOException {
String fileStorageHandlerString = EmptyUtil.isNullOrEmpty(storeFlag)?
fileStorageHandlers.get(FileConstant.ALIYUN_OSS):fileStorageHandlers.get(storeFlag);
FileStorageHandler fileStorageHandler = registerBeanHandler.getBean(fileStorageHandlerString, FileStorageHandler.class);
return fileStorageHandler.getFileContent(bucketName,pathUrl);
}
}

View File

@@ -0,0 +1,12 @@
package com.itheima.sfbx.file.binding;
import com.itheima.sfbx.framework.rabbitmq.source.FileSource;
import org.springframework.cloud.stream.annotation.EnableBinding;
/**
* @ClassName Binding.java
* @Description 绑定文件发送者声明
*/
@EnableBinding({FileSource.class})
public class SourceBinding {
}

View File

@@ -0,0 +1,123 @@
package com.itheima.sfbx.file.fegin;
import com.itheima.sfbx.file.service.IFileService;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName FileController.java
* @Description 附件展示维护controller
*/
@RestController
@RequestMapping("file-feign")
@Api(tags = "附件feign-controller")
@Slf4j
public class FileBusinessFeignController {
@Autowired
IFileService fileService;
/**
* @Description 业务绑定单个附件
* @param fileVO 附件对象
* @return
*/
@PostMapping(value = "bind-file")
@ApiOperation(value = "业务绑定单文件",notes = "业务绑定单文件")
@ApiImplicitParam(name = "fileVO",value = "附件对象",required = true,dataType = "FileVO")
public FileVO bindFile(@RequestBody FileVO fileVO){
return fileService.bindFile(fileVO);
}
/**
* @Description 相同业务绑定多个附件
* @param fileVOs 相同业务的多个附件对象
* @return
*/
@PostMapping(value = "bind-batch-file")
@ApiOperation(value = "业务绑定多文件",notes = "业务绑定多文件")
@ApiImplicitParam(name = "fileVOs",value = "附件对象",required = true,dataType = "FileVO")
public List<FileVO> bindBatchFile(@RequestBody ArrayList<FileVO> fileVOs){
return fileService.bindBatchFile(fileVOs);
}
/**
* @Description 移除业务原图片,并绑定新的图片到业务上
* @param fileVO 附件对象
* @return
*/
@PostMapping(value = "replace-bind-file")
@ApiOperation(value = "移除业务原图片,并绑定新的图片到业务上",notes = "移除业务原图片,并绑定新的图片到业务上")
@ApiImplicitParam(name = "fileVO",value = "附件对象",required = true,dataType = "FileVO")
public Boolean replaceBindFile(@RequestBody FileVO fileVO){
return fileService.replaceBindFile(fileVO);
}
/**
* @Description 批量移除业务原图片,并批量绑定新的图片到业务上
* @param fileVOs 附件对象
* @return
*/
@PostMapping(value = "replace-bind-batch-file")
@ApiOperation(value = "移除业务原图片,并绑定新的图片到业务上",notes = "移除业务原图片,并绑定新的图片到业务上")
@ApiImplicitParam(name = "fileVOs",value = "附件对象",required = true,dataType = "FileVO")
public Boolean replaceBindBatchFile(@RequestBody ArrayList<FileVO> fileVOs){
return fileService.replaceBindBatchFile(fileVOs);
}
/**
* @description 按业务ID查询附件
* @param businessIds 业务ids
* @return java.util.List<com.itheima.travel.req.FileVO>
*/
@PostMapping("find-in-business-ids")
@ApiOperation(value = "查询业务对应附件",notes = "查询业务对应附件")
@ApiImplicitParam(name = "fileVO",value = "附件对象",required = true,dataType = "FileVO")
public List<FileVO> findInBusinessIds(@RequestBody ArrayList<Long> businessIds) {
return fileService.findInBusinessIds(businessIds);
}
/**
* @Description 删除业务相关附件
* @param businessIds 附件信息ids
* @return
*/
@DeleteMapping("delete-by-business-ids")
@ApiOperation(value = "删除业务对应附件",notes = "删除业务对应附件")
@ApiImplicitParam(name = "fileVO",value = "附件对象",required = true,dataType = "FileVO")
public Boolean deleteByBusinessIds(@RequestBody ArrayList<Long> businessIds) {
return fileService.deleteInBusinessIds(businessIds);
}
/**
* @Description 定时清理文件
* @return Boolean
*/
@DeleteMapping("clear-file")
@ApiOperation(value = "删除业务对应附件",notes = "删除业务对应附件")
public Boolean clearFile(){
return fileService.clearFile();
}
/**
* @Description 延迟清理文件
* @return Boolean
*/
@DeleteMapping("clear-file-id/{id}")
@ApiOperation(value = "删除业务对应附件",notes = "删除业务对应附件")
@ApiImplicitParam(name = "id",value = "业务id",required = true,dataType = "String")
public Boolean clearFileById(@PathVariable("id")String id){
return fileService.clearFileById(id);
}
}

View File

@@ -0,0 +1,44 @@
package com.itheima.sfbx.file.fegin;
import com.itheima.sfbx.file.service.IFileService;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName FileUpLoadController.java
* @Description 文件下载接口
*/
@RestController
@RequestMapping("file-feign")
@Api(tags = "附件controller")
@Slf4j
public class FileDownLoadFeignController {
@Autowired
IFileService fileService;
/***
* @description 文件下载-简单下载-图片base64Image方式展示
* @param fileId 上传对象
* @return: com.itheima.travel.req.FileVo
*/
@PostMapping(value = "down-load/{fileId}")
@ApiOperation(value = "文件下载",notes = "文件下载")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "path", name = "fileId",value = "附件Id",dataType = "Long")
})
public FileVO downLoad(@PathVariable("fileId") Long fileId){
FileVO fileVO = fileService.downLoad(fileId);
return fileVO;
}
}

View File

@@ -0,0 +1,56 @@
package com.itheima.sfbx.file.handler;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName AbsFileStorageHandler.java
* @Description 文件存储处理抽象类
*/
public abstract class AbsFileStorageHandler {
public static Map<String,String> metaMimeTypeMap = new HashMap<>();
static {
metaMimeTypeMap.put(".docx","application/vnd.openxmlformats-officedocument.wordprocessingml.document");
metaMimeTypeMap.put(".doc","application/msword");
metaMimeTypeMap.put(".ppt","application/x-ppt");
metaMimeTypeMap.put(".xls","application/vnd.ms-excel");
metaMimeTypeMap.put(".xhtml","text/html");
metaMimeTypeMap.put(".htm","text/html");
metaMimeTypeMap.put(".html","text/html");
metaMimeTypeMap.put(".jpe","image/jpg");
metaMimeTypeMap.put(".jpeg","image/jpg");
metaMimeTypeMap.put(".jpg","image/jpg");
metaMimeTypeMap.put(".png","image/jpg");
metaMimeTypeMap.put(".mp4","video/mp4");
metaMimeTypeMap.put(".wmv","video/x-ms-wmv");
metaMimeTypeMap.put(".pdf","application/pdf");
metaMimeTypeMap.put(".mp3","audio/mp3");
}
/***
* @description 文件路径生成策略
*
* @param filename
* @return
* @return: java.lang.String
*/
public String builderOssPath(String filename) {
String separator = "/";
StringBuilder stringBuilder = new StringBuilder(50);
LocalDate localDate = LocalDate.now();
String yeat = String.valueOf(localDate.getYear());
stringBuilder.append(yeat).append(separator);
String moth = String.valueOf(localDate.getMonthValue());
stringBuilder.append(moth).append(separator);
DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
String day = formatter.format(localDate);
stringBuilder.append(day).append(separator);
stringBuilder.append(filename);
return stringBuilder.toString();
}
}

View File

@@ -0,0 +1,93 @@
package com.itheima.sfbx.file.handler;
import com.itheima.sfbx.file.pojo.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @ClassName FileStorageHandler.java
* @Description 文件存储处理接口类
*/
public interface FileStorageHandler {
/**
* @Description 文件上传
* @param suffix 文件后缀
* @param filename 文件名称-可以指定存储空间下的目录例如a/b/c/filename
* @param bucketName 存储空间的名称
* @param autoCatalog 是否自动生成文件存储目录,如果在filename指定了目录此值设置为false
* @param inputStream 文件流
* @return pathUrl 全路径
*/
String uploadFile(String suffix, String filename,String bucketName,boolean autoCatalog, InputStream inputStream);
/***
* @description 分片上传-初始化分片请求
* @param suffix 文件后缀
* @param filename 文件名称-可以指定存储空间下的目录例如a/b/c/filename
* @param bucketName 存储空间的名称
* @param autoCatalog 是否自动生成文件存储目录,如果在filename指定了目录此值设置为false
* @return uploadId 文件上传id
*/
File initiateMultipartUpload(String suffix, String filename, String bucketName, boolean autoCatalog);
/***
* @description 分片上传-上传每个分片文件
* @param upLoadId 文件上传id
* @param filename 文件名称-可以指定存储空间下的目录例如a/b/c/filename
* @param partNumber 当前分片
* @param partSize 分片数
* @param bucketName 存储空间的名称
* @param inputStream 当前分片文件流
* @return PartETag json字符串
*/
String uploadPart(String upLoadId,String filename,int partNumber,long partSize,String bucketName,InputStream inputStream);
/***
* @description 分片上传-合并所有上传文件
*
* @param upLoadId 文件上传id
* @param partETags json字符串
* @param filename 文件名称-可以指定存储空间下的目录例如a/b/c/filename
* @param bucketName 存储空间的名称
* @return 合并结果
*/
String completeMultipartUpload(String upLoadId,List<String> partETags,String filename,String bucketName);
/**
* @Description 下载文件
* @param bucketName 存储空间名称
* @param pathUrl 全路径
* @return
*/
InputStream downloadFile(String bucketName,String pathUrl) throws IOException;
/**
* @Description 文件删除
* @param bucketName 存储空间名称
* @param pathUrl 全路径
* @throws Exception
*/
void delete(String bucketName,String pathUrl);
/**
* @Description 批量文件删除
* @param bucketName 存储空间名称
* @param pathUrls 全路径集合
* @throws Exception
*/
void deleteBatch(String bucketName,List<String> pathUrls);
/**
* @Description 获取文件文本内容
* @param bucketName 存储空间名称
* @param pathUrl 全路径
* @return
* @throws IOException
*/
String getFileContent(String bucketName,String pathUrl) throws IOException;
}

View File

@@ -0,0 +1,179 @@
package com.itheima.sfbx.file.handler.aliyun.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.internal.OSSConstants;
import com.aliyun.oss.internal.OSSHeaders;
import com.aliyun.oss.model.*;
import com.google.common.collect.Lists;
import com.google.common.io.ByteStreams;
import com.itheima.sfbx.framework.commons.enums.file.FileEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.file.handler.AbsFileStorageHandler;
import com.itheima.sfbx.file.handler.FileStorageHandler;
import com.itheima.sfbx.file.handler.aliyun.properties.OssAliyunConfigProperties;
import com.itheima.sfbx.file.pojo.File;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/**
* @ClassName OssFileStorageHandlerImpl.java
* @Description 阿里云文件上传
*/
@Slf4j
@Service("ossFileStorageHandler")
@EnableConfigurationProperties(OssAliyunConfigProperties.class)
public class OssFileStorageHandlerImpl extends AbsFileStorageHandler implements FileStorageHandler {
@Autowired
OSS ossClient;
@Autowired
OssAliyunConfigProperties ossAliyunConfigProperties;
/***
* @description 文件元数据处理
* @param prefix 文件后缀
* @return
*/
public ObjectMetadata fileMetaHandler(String prefix){
//元数据对象
ObjectMetadata objectMeta = new ObjectMetadata();
//文件字符集
objectMeta.setContentEncoding("UTF-8");
//文件类型匹配
objectMeta.setContentType(metaMimeTypeMap.get(prefix.toLowerCase()));
return objectMeta;
}
@Override
public String uploadFile(String suffix, String filename, String bucketName,boolean autoCatalog, InputStream inputStream) {
// 是否自动生成存储路径并设置文件路径和名称Key
String key = autoCatalog?builderOssPath(filename):filename;
log.info("OSS文件上传开始{}" ,key);
try {
//上传文件元数据处理
ObjectMetadata objectMeta = fileMetaHandler(suffix);
//文件上传请求对象
PutObjectRequest request = new PutObjectRequest(bucketName, key, inputStream,objectMeta);
//上传限流
if (ossAliyunConfigProperties.getIslimitSpeed()){
request.setTrafficLimit(ossAliyunConfigProperties.getUplimitSpeed());
}
//文件上传
PutObjectResult result = ossClient.putObject(request);
// 设置权限(公开读)
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
} catch (OSSException oe) {
log.error("OSS文件上传错误{}", oe);
throw new ProjectException(FileEnum.UPLOAD_FAIL);
} catch (ClientException ce) {
log.error("OSS文件上传客户端错误{}",ce);
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}
return key;
}
@Override
public File initiateMultipartUpload(String suffix, String filename, String bucketName, boolean autoCatalog) {
// 是否自动生成存储路径并设置文件路径和名称Key
String key = filename;
InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(bucketName, key);
// 如果需要在初始化分片时设置请求头,请参考以下示例代码。
ObjectMetadata metadata = fileMetaHandler(suffix);
metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
// 指定该Object的网页缓存行为。
metadata.setCacheControl("no-cache");
// 指定该Object被下载时的名称。
metadata.setContentDisposition("inline;filename="+key);
// 指定该Object的内容编码格式。
metadata.setContentEncoding(OSSConstants.DEFAULT_CHARSET_NAME);
//指定请求
request.setObjectMetadata(metadata);
// 初始化分片。
InitiateMultipartUploadResult upresult = ossClient.initiateMultipartUpload(request);
// 设置权限(公开读)
ossClient.setBucketAcl(bucketName, CannedAccessControlList.PublicRead);
// 返回uploadId它是分片上传事件的唯一标识。您可以根据该uploadId发起相关的操作例如取消分片上传、查询分片上传等。
return File.builder().bucketName(bucketName).pathUrl(key).fileName(key).uploadId(upresult.getUploadId()).build();
}
@Override
public String uploadPart(String upLoadId, String filename, int partNumber, long partSize, String bucketName, InputStream inputStream) {
//封装分片上传请求
UploadPartRequest uploadPartRequest = new UploadPartRequest();
uploadPartRequest.setUploadId(upLoadId);
//part大小 1-10000
uploadPartRequest.setPartNumber(partNumber);
uploadPartRequest.setPartSize(partSize);
//文件上传的bucketName
uploadPartRequest.setBucketName(bucketName);
//分片文件
uploadPartRequest.setInputStream(inputStream);
uploadPartRequest.setKey(filename);
// 每个分片不需要按顺序上传甚至可以在不同客户端上传OSS会按照分片号排序组成完整的文件。
UploadPartResult uploadPartResult = ossClient.uploadPart(uploadPartRequest);
log.info("{}文件第 {} 片上传成功,上传结果:{}", upLoadId, uploadPartRequest.getPartNumber(),JSON.toJSON(uploadPartResult));
return JSONObject.toJSONString(uploadPartResult.getPartETag());
}
@Override
public String completeMultipartUpload(String upLoadId, List<String> partETags, String filename, String bucketName) {
StopWatch st = new StopWatch();
st.start();
//转换jsonarray为list
List<PartETag> partETagList =Lists.newArrayList();
partETags.forEach(n->{
partETagList.add(JSONObject.parseObject(n,PartETag.class));
});
CompleteMultipartUploadRequest completeMultipartUploadRequest =
new CompleteMultipartUploadRequest(bucketName, filename, upLoadId, partETagList);
log.info("{}文件上传完成,开始合并,partList:{}", upLoadId, partETags);
// 完成分片上传。
CompleteMultipartUploadResult completeMultipartUploadResult = ossClient.completeMultipartUpload(completeMultipartUploadRequest);
st.stop();
log.info("{}文件上传完成,上传结果:{},耗时:{}", upLoadId, JSON.toJSON(completeMultipartUploadResult), st.getTotalTimeMillis());
return completeMultipartUploadResult.getETag();
}
@Override
public InputStream downloadFile(String bucketName,String pathUrl) throws IOException {
GetObjectRequest request = new GetObjectRequest(bucketName, pathUrl);
//下载传限流
if (ossAliyunConfigProperties.getIslimitSpeed()){
request.setTrafficLimit(ossAliyunConfigProperties.getDownlimitSpeed());
}
//ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
InputStream inputStream = ossClient.getObject(request).getObjectContent();
return inputStream;
}
@Override
public void delete(String bucketName,String pathUrl) {
// 删除Objects
ossClient.deleteObject(bucketName,pathUrl);
}
@Override
public void deleteBatch(String bucketName,List<String> pathUrls) {
// 删除Objects
ossClient.deleteObjects(new DeleteObjectsRequest(bucketName).withKeys(pathUrls));
}
@Override
public String getFileContent(String bucketName,String pathUrl) throws IOException {
InputStream inputStream = downloadFile(bucketName,pathUrl);
return new String(ByteStreams.toByteArray(inputStream));
}
}

View File

@@ -0,0 +1,43 @@
package com.itheima.sfbx.file.handler.aliyun.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Description 阿里云OSS上传配置类
*/
@Data
@NoArgsConstructor
@ConfigurationProperties("spring.cloud.alicloud.oss")
public class OssAliyunConfigProperties {
//区域
String region;
//秘钥ID
String accessKeyId;
//秘钥
String accessKeySecret;
//角色
String roleArn;
//桶名称
private String bucketName ;
//访问终端域名地址
private String endpoint;
//是否限流
private Boolean islimitSpeed = true;
//上传限流
private int uplimitSpeed = 100 * 1024 * 1024 * 2;
//下载限流
private int downlimitSpeed = 100 * 1024 * 8;
}

View File

@@ -0,0 +1,106 @@
package com.itheima.sfbx.file.handler.qiniu.config;
import com.itheima.sfbx.file.handler.qiniu.properties.QiniuProperties;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Region;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* Qiniu服务配置类
* </p>
* 声明创建Qiniu核心配置对象
*/
@Slf4j
@Configuration
@EnableConfigurationProperties(QiniuProperties.class)
public class QiniuConfig {
@Autowired
private QiniuProperties qiniuConfigProperties;
private static Map<String, Region> regions =new HashMap<>();
static {
regions.put(QiniuRegion.REGION_HUADONG,Region.huadong());
regions.put(QiniuRegion.REGION_HUABEI,Region.huabei());
regions.put(QiniuRegion.REGION_HUANAN,Region.huanan());
regions.put(QiniuRegion.REGION_BEIMEI,Region.beimei());
regions.put(QiniuRegion.REGION_DONGNANYA,Region.xinjiapo());
}
@Bean("qiniuConfiguration")
public com.qiniu.storage.Configuration qiniuConfiguration() {
// 设置存储区域
String regionString = qiniuConfigProperties.getKodo().getRegion();
Region region = regions.get(regionString);
com.qiniu.storage.Configuration configuration = new com.qiniu.storage.Configuration(region);
// 使用http协议
configuration.useHttpsDomains = false;
//上传限流,超过后上传会自动转为分片上传
configuration.resumableUploadAPIV2BlockSize = qiniuConfigProperties.getUplimitSpeed();
return configuration;
}
@Bean("qiniuAuth")
public Auth qiniuAuth() {
Auth auth = Auth.create(qiniuConfigProperties.getAccessKey(),
qiniuConfigProperties.getSecretKey());
return auth;
}
@Bean("bucketManager")
public BucketManager bucketManager(@Qualifier("qiniuConfiguration") com.qiniu.storage.Configuration configuration,
@Qualifier("qiniuAuth") Auth auth
) {
BucketManager bucketManager = new BucketManager(auth, configuration);
return bucketManager;
}
/**
* <p>
* 七牛服务区域标识
* </p>
* 此标识是由七牛的kodo对象存储中定义参考 {@link Region}
*/
public interface QiniuRegion {
/**
* z0 华东
*/
String REGION_HUADONG = "z0";
/**
* z1 华北
*/
String REGION_HUABEI = "z1";
/**
* z2 华南
*/
String REGION_HUANAN = "z2";
/**
* na0 北美
*/
String REGION_BEIMEI = "na0";
/**
* as0 东南亚
*/
String REGION_DONGNANYA = "as0";
}
}

View File

@@ -0,0 +1,169 @@
package com.itheima.sfbx.file.handler.qiniu.impl;
import com.aliyun.oss.ClientException;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.itheima.sfbx.file.handler.qiniu.properties.QiniuProperties;
import com.itheima.sfbx.framework.commons.enums.file.FileEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.file.handler.AbsFileStorageHandler;
import com.itheima.sfbx.file.handler.FileStorageHandler;
import com.itheima.sfbx.file.pojo.File;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.DownloadUrl;
import com.qiniu.storage.UploadManager;
import com.qiniu.storage.model.BatchStatus;
import com.qiniu.storage.model.DefaultPutRet;
import com.qiniu.util.Auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
/**
* @ClassName OssFileStorageHandlerImpl.java
* @Description 七牛云文件上传
*/
@Slf4j
@Service("kodoFileStorageHandler")
public class KodoFileStorageHandlerImpl extends AbsFileStorageHandler implements FileStorageHandler {
@Autowired
private Auth qiniuAuth;
@Autowired
private Configuration qiniuConfiguration;
@Autowired
private BucketManager bucketManager;
@Autowired
QiniuProperties qiniuConfigProperties;
@Override
public String uploadFile(String suffix, String filename, String bucketName, boolean autoCatalog, InputStream inputStream) {
if (EmptyUtil.isNullOrEmpty(bucketName)){
bucketName=qiniuConfigProperties.getKodo().getBucketName();
}
String pathUrl = null;
// 是否自动生成存储路径并设置文件路径和名称Key
String key = autoCatalog?builderOssPath(filename):filename;
log.info("七牛Kodo文件上传开始{}", key);
try {
String upToken = qiniuAuth.uploadToken(bucketName);
String mimeType = metaMimeTypeMap.get(suffix);
UploadManager uploadManager = new UploadManager(qiniuConfiguration);
Response response = uploadManager.put(inputStream, key, upToken, null, mimeType);
//解析上传成功的结果
DefaultPutRet putRet = new Gson().fromJson(response.bodyString(), DefaultPutRet.class);
if (!(StringUtils.isEmpty(putRet.key))) {
log.info("七牛Kodo文件上传成功{}", putRet.key);
pathUrl = putRet.key;
}
} catch (QiniuException oe) {
log.error("七牛Kodo文件上传错误{}", oe);
throw new ProjectException(FileEnum.UPLOAD_FAIL);
} catch (ClientException ce) {
log.error("七牛Kodo文件上传客户端错误{}", ce);
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}
return pathUrl;
}
@Override
public File initiateMultipartUpload(String suffix, String filename, String bucketName, boolean autoCatalog) {
return null;
}
@Override
public String uploadPart(String uploadId, String filename, int partNumber, long partSize, String bucketName, InputStream inputStream) {
return null;
}
@Override
public String completeMultipartUpload(String uploadId, List<String> partETags, String filename, String bucketName) {
return null;
}
/*
* 七牛获得文件的inputStream
* 1.官方文档只有获得文件的URL路径地址的接口没有直接获得Inputstream
* 2.URL地址需要获得bucket所属于域名
* 3.通过域名来获得文件的URL
* 4.通过URL转为InputStream
* */
@Override
public InputStream downloadFile(String bucketName, String pathUrl) throws IOException {
InputStream inputStream = null;
try {
// 默认获得指定Bucket第一个域名
String[] domainList = bucketManager.domainList(bucketName);
String domain = domainList[0];
// 获得文件的路径并转为URL对象
DownloadUrl downloadUrl = new DownloadUrl(domain, false, pathUrl);
String buildURL = downloadUrl.buildURL();
URL url = new URL(buildURL);
// URL转为InputStream
inputStream = url.openStream();
} catch (IOException e) {
log.error("七牛Kodo获得文件输入流失败{}", e);
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}
return inputStream;
}
@Override
public void delete(String bucketName, String pathUrl) {
try {
bucketManager.delete(bucketName, pathUrl);
} catch (QiniuException e) {
//如果遇到异常,说明删除失败
log.error("七牛Kodo获得文件输入流失败{}", e);
throw new ProjectException(FileEnum.DELETE_FAIL);
}
}
@Override
public void deleteBatch(String bucketName, List<String> pathUrls) {
try {
BucketManager.BatchOperations batchOperations = new BucketManager.BatchOperations();
batchOperations.addDeleteOp(bucketName, pathUrls.toArray(new String[0]));
Response response = bucketManager.batch(batchOperations);
BatchStatus[] batchStatusList = response.jsonToObject(BatchStatus[].class);
for (int i = 0; i < pathUrls.size(); i++) {
BatchStatus status = batchStatusList[i];
String key = pathUrls.get(i);
if (status.code != 200) {
log.error(status.data.error);
log.error("七牛Kodo批量删除文件失败key 为 {}", key);
throw new ProjectException(FileEnum.DELETE_FAIL);
}
}
} catch (QiniuException e) {
//如果遇到异常,说明删除失败
log.error("七牛Kodo批量删除文件失败{}", e);
throw new ProjectException(FileEnum.DELETE_FAIL);
}
}
@Override
public String getFileContent(String bucketName, String pathUrl) throws IOException {
InputStream inputStream = downloadFile(bucketName, pathUrl);
return new String(ByteStreams.toByteArray(inputStream));
}
}

View File

@@ -0,0 +1,87 @@
package com.itheima.sfbx.file.handler.qiniu.properties;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @Description 阿里云OSS上传配置类
*/
@Data
@NoArgsConstructor
@ConfigurationProperties("spring.cloud.qiniu")
public class QiniuProperties {
//访问key
private String accessKey ;
//密钥key
private String secretKey ;
//是否限流
private Boolean islimitSpeed = true;
//上传限流,超过后上传会自动转为分片上传
private int uplimitSpeed = 1024 * 1024 * 8;
private KodoProperties kodo;
public static class KodoProperties {
/**
* 存款空间区域标识,参考 {@link com.qiniu.storage.Region} 中的region属性值
*/
private String region;
/**
* 存储空间的名称
*/
private String bucketName;
/**
* 存款空间访问域名
*/
private String endpoint;
public KodoProperties() {
}
public KodoProperties(String region, String bucketName) {
this.region = region;
this.bucketName = bucketName;
}
public KodoProperties(String region, String bucketName, String endpoint) {
this.region = region;
this.bucketName = bucketName;
this.endpoint = endpoint;
}
public String getEndpoint() {
return endpoint;
}
public void setEndpoint(String endpoint) {
this.endpoint = endpoint;
}
public String getRegion() {
return region;
}
public void setRegion(String region) {
this.region = region;
}
public String getBucketName() {
return bucketName;
}
public void setBucketName(String bucketName) {
this.bucketName = bucketName;
}
}
}

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.sfbx.file.pojo.File;
import org.apache.ibatis.annotations.Mapper;
/**
* @Description附件Mapper接口
*/
@Mapper
public interface FileMapper extends BaseMapper<File> {
}

View File

@@ -0,0 +1,13 @@
package com.itheima.sfbx.file.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.sfbx.file.pojo.FilePart;
import org.apache.ibatis.annotations.Mapper;
/**
* @DescriptionMapper接口
*/
@Mapper
public interface FilePartMapper extends BaseMapper<FilePart> {
}

View File

@@ -0,0 +1,73 @@
package com.itheima.sfbx.file.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itheima.sfbx.framework.mybatisplus.basic.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Description附件
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("tab_file")
@ApiModel(value="File对象", description="附件")
public class File extends BasePojo {
private static final long serialVersionUID = 1L;
@Builder
public File(Long id, String dataState, Long businessId, String businessType, String suffix, String fileName, String pathUrl, String storeFlag, String bucketName, String uploadId, String md5, String status, String companyNo) {
super(id, dataState);
this.businessId = businessId;
this.businessType = businessType;
this.suffix = suffix;
this.fileName = fileName;
this.pathUrl = pathUrl;
this.storeFlag = storeFlag;
this.bucketName = bucketName;
this.uploadId = uploadId;
this.md5 = md5;
this.status = status;
this.companyNo = companyNo;
}
@ApiModelProperty(value = "业务ID")
private Long businessId;
@ApiModelProperty(value = "业务类型")
private String businessType;
@ApiModelProperty(value = "后缀名")
private String suffix;
@ApiModelProperty(value = "文件名")
private String fileName;
@ApiModelProperty(value = "访问路径")
private String pathUrl;
@ApiModelProperty(value = "存储源标识参考FileConstant")
private String storeFlag;
@ApiModelProperty(value = "存储空间名称")
private String bucketName;
@ApiModelProperty(value = "分片上传文件Id")
private String uploadId;
@ApiModelProperty(value = "md5值")
private String md5;
@ApiModelProperty(value = "状态上传中【sending】完成【succeed】失败【failed】")
private String status;
@ApiModelProperty(value = "企业号")
private String companyNo;
}

View File

@@ -0,0 +1,61 @@
package com.itheima.sfbx.file.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.itheima.sfbx.framework.mybatisplus.basic.BasePojo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @Description
*/
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("tab_file_part")
@ApiModel(value="FilePart对象", description="分片上传")
public class FilePart extends BasePojo {
private static final long serialVersionUID = 1L;
@Builder
public FilePart(Long id, String dataState, String uploadId, Integer partNumber, String uploadResult, String md5, String bucketName, String fileName, String storeFlag, String companyNo) {
super(id, dataState);
this.uploadId = uploadId;
this.partNumber = partNumber;
this.uploadResult = uploadResult;
this.md5 = md5;
this.bucketName = bucketName;
this.fileName = fileName;
this.storeFlag = storeFlag;
this.companyNo = companyNo;
}
@ApiModelProperty(value = "唯一上传id")
private String uploadId;
@ApiModelProperty(value = "当前片数")
private Integer partNumber;
@ApiModelProperty(value = "分片上传结果(json)")
private String uploadResult;
@ApiModelProperty(value = "md5值")
private String md5;
@ApiModelProperty(value = "存储空间名称")
private String bucketName;
@ApiModelProperty(value = "文件名")
private String fileName;
@ApiModelProperty(value = "存储源标识参考FileConstant")
private String storeFlag;
@ApiModelProperty(value = "企业号")
private String companyNo;
}

View File

@@ -0,0 +1,58 @@
package com.itheima.sfbx.file.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.sfbx.file.pojo.FilePart;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import java.util.List;
/**
* @Description服务类
*/
public interface IFilePartService extends IService<FilePart> {
/**
* @Description 多条件查询分页列表
* @param filePartVo 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return Page<FilePart>
*/
Page<FilePart> findFilePartPage(FilePartVO filePartVo, int pageNum, int pageSize);
/**
* @Description 创建
* @param filePartVo 对象信息
* @return FilePart
*/
FilePart createFilePart(FilePartVO filePartVo);
/**
* @Description 修改
* @param filePartVo 对象信息
* @return Boolean
*/
Boolean updateFilePart(FilePartVO filePartVo);
/**
* @Description 删除
* @param checkedIds 选择中对象Ids
* @return Boolean
*/
Boolean deleteFilePart(String[] checkedIds);
/**
* @description 多条件查询列表
* @param filePartVo 查询条件
* @return: List<FilePart>
*/
List<FilePart> findFilePartList(FilePartVO filePartVo);
/**
* @description 按upLoadId删除记录
* @param upLoadId 上传ID
* @return: List<FilePart>
*/
Boolean deleteFilePartByUpLoadId(String upLoadId);
}

View File

@@ -0,0 +1,149 @@
package com.itheima.sfbx.file.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.dto.file.UploadMultipartFile;
import com.itheima.sfbx.file.pojo.File;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import java.util.List;
import java.util.Set;
/**
* @Description附件 服务类
*/
public interface IFileService extends IService<File> {
/**
* @Description 按业务ID查询附件
* @param businessId 附件对象业务Id
* @return
*/
List<FileVO> findFileVoByBusinessId(Long businessId) ;
/**
* @Description 附件列表
* @param fileVO 查询条件
* @return
*/
Page<FileVO> findFileVOPage(FileVO fileVO, int pageNum, int pageSize);
/***
* @description 定时清理文件
* @return
*/
List<FileVO> needClearFile();
/***
* @description 延迟队列清理文件
* @return
*/
FileVO needClearFileById(String id);
/**
* @Description 业务绑定单个附件
* @param fileVO 附件对象
* @return
*/
FileVO bindFile(FileVO fileVO);
/**
* @Description 相同业务绑定多个附件
* @param fileVOs 相同业务的多个附件对象
* @return
*/
List<FileVO> bindBatchFile(List<FileVO> fileVOs);
/**
* @Description 移除业务原图片,并绑定新的图片到业务上
* @param fileVO 附件对象
* @return
*/
Boolean replaceBindFile(FileVO fileVO);
/**
* @Description 移除业务原图片,并批量绑定新的图片到业务上
* @param fileVOs 附件对象
* @return
*/
Boolean replaceBindBatchFile(List<FileVO> fileVOs);
/**
* @description 按业务ID查询附件
* @param businessIds 业务ids
* @return java.util.List<com.itheima.travel.req.FileVO>
*/
List<FileVO> findInBusinessIds(List<Long> businessIds);
/**
* @Description 删除业务相关附件
* @param businessIds 附件信息ids
* @return
*/
Boolean deleteInBusinessIds(List<Long> businessIds);
/**
* @Description 删除业务相关附件
* @param ids 附件信息ids
* @return
*/
Boolean deleteInIds(List<Long> ids);
/**
* @Description 定时清理文件
* @return Boolean
*/
Boolean clearFile();
/**
* @Description 定时清理文件
* @return Boolean
*/
Boolean clearFileById(String fileId);
/**
* @Description 查询所有业务对应附件
* @return Set<Long>
*/
Set<Long> findBusinessIdAll();
/***
* @description 文件简单上传
*
* @param uploadMultipartFile
* @param fileVO
* @return FileVO
*/
FileVO upLoad(UploadMultipartFile uploadMultipartFile, FileVO fileVO);
/***
* @description 初始化分片上传
*
* @param fileVO
* @return FileVO
*/
FileVO initiateMultipartUpload(FileVO fileVO);
/***
* @description 分片每个分片
* @param uploadMultipartFile
* @param filePartVo
* @return String
*/
String uploadPart(UploadMultipartFile uploadMultipartFile, FilePartVO filePartVo);
/***
* @description 合并所有分片
* @param fileVO
* @return String
*/
String completeMultipartUpload(FileVO fileVO);
/***
* @description 文件下载
* @param fileId
* @return FileVO
*/
FileVO downLoad(Long fileId);
}

View File

@@ -0,0 +1,115 @@
package com.itheima.sfbx.file.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.file.mapper.FilePartMapper;
import com.itheima.sfbx.file.pojo.FilePart;
import com.itheima.sfbx.file.service.IFilePartService;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Description服务实现类
*/
@Service
public class FilePartServiceImpl extends ServiceImpl<FilePartMapper, FilePart> implements IFilePartService {
@Override
public Page<FilePart> findFilePartPage(FilePartVO filePartVo, int pageNum, int pageSize) {
//构建分页对象
Page<FilePart> page = new Page<>(pageNum,pageSize);
//构建查询条件
QueryWrapper<FilePart> queryWrapper = new QueryWrapper<>();
//构建多条件查询,代码生成后自己可自行调整
//唯一上传id查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getUploadId())) {
queryWrapper.lambda().eq(FilePart::getUploadId,filePartVo.getUploadId());
}
//当前片数查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getPartNumber())) {
queryWrapper.lambda().eq(FilePart::getPartNumber,filePartVo.getPartNumber());
}
//分片上传结果(json)查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getUploadResult())) {
queryWrapper.lambda().eq(FilePart::getUploadResult,filePartVo.getUploadResult());
}
//状态查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getDataState())) {
queryWrapper.lambda().eq(FilePart::getDataState,filePartVo.getDataState());
}
//按创建时间降序
queryWrapper.lambda().orderByDesc(FilePart::getCreateTime);
//执行分页查询
return page(page, queryWrapper);
}
@Override
public FilePart createFilePart(FilePartVO filePartVo) {
//转换FilePartVO为FilePart
FilePart filePart = BeanConv.toBean(filePartVo, FilePart.class);
boolean flag = save(filePart);
if (flag){
return filePart;
}
return null;
}
@Override
public Boolean updateFilePart(FilePartVO filePartVo) {
//转换FilePartVO为FilePart
FilePart filePart = BeanConv.toBean(filePartVo, FilePart.class);
return updateById(filePart);
}
@Override
public Boolean deleteFilePart(String[] checkedIds) {
//转换数组为集合
List<String> ids = Arrays.asList(checkedIds);
List<Long> idsLong = new ArrayList<>();
ids.forEach(n->{
idsLong.add(Long.valueOf(n));
});
return removeByIds(idsLong);
}
@Override
public List<FilePart> findFilePartList(FilePartVO filePartVo) {
//构建查询条件
QueryWrapper<FilePart> queryWrapper = new QueryWrapper<>();
if (!EmptyUtil.isNullOrEmpty(filePartVo.getId())) {
queryWrapper.lambda().eq(FilePart::getId,filePartVo.getId());
}
//唯一上传id查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getUploadId())) {
queryWrapper.lambda().eq(FilePart::getUploadId,filePartVo.getUploadId());
}
//当前片数查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getPartNumber())) {
queryWrapper.lambda().eq(FilePart::getPartNumber,filePartVo.getPartNumber());
}
//当前片数查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getMd5())) {
queryWrapper.lambda().eq(FilePart::getMd5,filePartVo.getMd5());
}
//状态查询
if (!EmptyUtil.isNullOrEmpty(filePartVo.getDataState())) {
queryWrapper.lambda().eq(FilePart::getDataState,filePartVo.getDataState());
}
return list(queryWrapper);
}
@Override
public Boolean deleteFilePartByUpLoadId(String upLoadId) {
UpdateWrapper<FilePart> updateWrapperp = new UpdateWrapper<>();
updateWrapperp.lambda().eq(FilePart::getUploadId,upLoadId);
return remove(updateWrapperp);
}
}

View File

@@ -0,0 +1,593 @@
package com.itheima.sfbx.file.service.impl;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.oss.model.UploadPartResult;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.itheima.sfbx.file.adapter.FileStorageAdapter;
import com.itheima.sfbx.file.mapper.FileMapper;
import com.itheima.sfbx.file.pojo.File;
import com.itheima.sfbx.file.pojo.FilePart;
import com.itheima.sfbx.file.service.IFilePartService;
import com.itheima.sfbx.file.service.IFileService;
import com.itheima.sfbx.file.utils.FileUrlContext;
import com.itheima.sfbx.framework.commons.constant.basic.SuperConstant;
import com.itheima.sfbx.framework.commons.constant.file.FileCacheConstant;
import com.itheima.sfbx.framework.commons.constant.file.FileConstant;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.dto.file.UploadMultipartFile;
import com.itheima.sfbx.framework.commons.enums.file.FileEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.utils.EncodesUtil;
import com.itheima.sfbx.framework.commons.utils.ExceptionsUtil;
import com.itheima.sfbx.framework.rabbitmq.pojo.MqMessage;
import com.itheima.sfbx.framework.rabbitmq.source.FileSource;
import lombok.Cleanup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Description附件 服务实现类
*/
@Slf4j
@Service
public class FileServiceImpl extends ServiceImpl<FileMapper, File> implements IFileService {
@Value("${file-delay-time}")
Integer fileDelayTime;
@Autowired
private FileUrlContext fileUrlContext;
@Autowired
FileStorageAdapter fileStorageAdapter;
@Autowired
IdentifierGenerator identifierGenerator;
@Autowired
FileSource fileSource;
@Autowired
RedissonClient redissonClient;
@Autowired
IFilePartService filePartService;
private QueryWrapper<File> queryWrapper(FileVO fileVO){
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
if (!EmptyUtil.isNullOrEmpty(fileVO.getBusinessType())) {
queryWrapper.lambda().eq(File::getBusinessType,fileVO.getBusinessType());
}
if (!EmptyUtil.isNullOrEmpty(fileVO.getFileName())) {
queryWrapper.lambda().likeRight(File::getFileName,fileVO.getFileName());
}
if (!EmptyUtil.isNullOrEmpty(fileVO.getPathUrl())) {
queryWrapper.lambda().likeRight(File::getPathUrl,fileVO.getPathUrl());
}
if (!EmptyUtil.isNullOrEmpty(fileVO.getDataState())) {
queryWrapper.lambda().likeRight(File::getDataState,fileVO.getDataState());
}
if (!EmptyUtil.isNullOrEmpty(fileVO.getStatus())) {
queryWrapper.lambda().likeRight(File::getStatus,fileVO.getStatus());
}
queryWrapper.lambda().orderByDesc(File::getCreateTime);
return queryWrapper;
}
@Override
@Cacheable(value = FileCacheConstant.BUSINESS_KEY,key = "#businessId")
public List<FileVO> findFileVoByBusinessId(Long businessId) {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(File::getBusinessId,businessId);
List<File> files = list(queryWrapper);
if (!EmptyUtil.isNullOrEmpty(files)){
files.forEach(n->{
String fileUrl = fileUrlContext.getFileUrl(n.getStoreFlag(), n.getPathUrl());
n.setPathUrl(fileUrl);
});
}
return BeanConv.toBeanList(files,FileVO.class);
}catch (Exception e){
log.error("查询业务对应附件:{}异常:{}", businessId,ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.SELECT_FILE_BUSINESSID_FAIL);
}
}
@Override
@Cacheable(value = FileCacheConstant.PAGE,key ="#pageNum+'-'+#pageSize+'-'+#fileVO.hashCode()")
public Page<FileVO> findFileVOPage(FileVO fileVO, int pageNum , int pageSize) {
try {
Page<File> page = new Page<>(pageNum,pageSize);
QueryWrapper<File> queryWrapper = queryWrapper(fileVO);
Page<FileVO> fileVOPage = BeanConv.toPage(page(page, queryWrapper), FileVO.class);
if (!EmptyUtil.isNullOrEmpty(fileVOPage)&&!EmptyUtil.isNullOrEmpty(fileVOPage.getRecords())){
fileVOPage.getRecords().forEach(n->{
n.setPathUrl(fileUrlContext.getFileUrl(n.getStoreFlag(), n.getPathUrl()));
});
}
return fileVOPage;
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.PAGE_FAIL);
}
}
@Override
public List<FileVO> needClearFile() {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(fileDelayTime/1000);
queryWrapper.lambda().isNull(File::getBusinessId).lt(File::getCreateTime,localDateTime);
return BeanConv.toBeanList(list(queryWrapper), FileVO.class);
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.SELECT_FILE_BUSINESSID_FAIL);
}
}
@Override
public FileVO needClearFileById(String id) {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
LocalDateTime localDateTime = LocalDateTime.now().minusSeconds(fileDelayTime/1000);
queryWrapper.lambda().isNull(File::getBusinessId).lt(File::getCreateTime,localDateTime).eq(File::getId,id);
return BeanConv.toBean(getOne(queryWrapper),FileVO.class);
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.SELECT_FILE_BUSINESSID_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,key = "#fileVO.id"),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,key = "#fileVO.businessId")})
public FileVO bindFile(FileVO fileVO) {
try {
//修改file表中的businessId
File file = BeanConv.toBean(fileVO, File.class);
boolean flag = updateById(file);
//构建完整返回对象
if (flag){
QueryWrapper<File> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(File::getBusinessId,fileVO.getBusinessId());
File fileResult = getOne(queryWrapper);
if (!EmptyUtil.isNullOrEmpty(file)){
fileResult.setPathUrl(fileUrlContext.getFileUrl(fileResult.getStoreFlag(), fileResult.getPathUrl()));
}
return BeanConv.toBean(fileResult,FileVO.class);
}
return null;
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.SELECT_FILE_BUSINESSID_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,key = "#fileVOs.get(0).getBusinessId()")})
public List<FileVO> bindBatchFile(List<FileVO> fileVOs) {
Long businessId = fileVOs.get(0).getBusinessId();
if (EmptyUtil.isNullOrEmpty(businessId)) {
throw new ProjectException(FileEnum.SELECT_BUSUBBESSID_FAIL);
}
try {
//修改file表中的businessId
updateBatchById(BeanConv.toBeanList(fileVOs, File.class));
QueryWrapper<File> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(File::getBusinessId,businessId);
List<File> files = list(queryWrapper);
//构建完整返回对象
if (!EmptyUtil.isNullOrEmpty(files)){
files.forEach(n->{
String fileUrl = fileUrlContext.getFileUrl(n.getStoreFlag(), n.getPathUrl());
n.setPathUrl(fileUrl);
});
}
return BeanConv.toBeanList(files,FileVO.class);
} catch (Exception e) {
log.error("绑定业务:{}异常:{}", fileVOs.toString(),ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.BIND_FAIL);
}
}
@Override
@Transactional
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true),
@CacheEvict(value = FileCacheConstant.LIST,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,key = "#fileVO.getBusinessId()")})
public Boolean replaceBindFile(FileVO fileVO) {
try {
//删除老图片
ArrayList<Long> ids = Lists.newArrayList();
ids.add(fileVO.getId());
deleteInIds(ids);
//绑定新图片
FileVO fileVOResult = bindFile(fileVO);
return !EmptyUtil.isNullOrEmpty(fileVOResult);
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.BIND_FAIL);
}
}
@Override
@Transactional
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true),
@CacheEvict(value = FileCacheConstant.LIST,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,key = "#fileVOs.get(0).getBusinessId()")})
public Boolean replaceBindBatchFile(List<FileVO> fileVOs) {
try {
//查询当前业务图片
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(File::getBusinessId,fileVOs.get(0).getBusinessId());
List<File> oldList = list(queryWrapper);
List<Long> oldIds = oldList.stream().map(File::getId).collect(Collectors.toList());
List<Long> newIds = fileVOs.stream().map(FileVO::getId).collect(Collectors.toList());
//删除:老图片对新图片的差集
List<Long> delIds = oldIds.stream().filter(n -> {
return !newIds.contains(n);
}).collect(Collectors.toList());
if (!EmptyUtil.isNullOrEmpty(delIds)){
deleteInIds(delIds);
}
//绑定新图片
List<FileVO> newFiles = fileVOs.stream().filter(n -> {
return !oldIds.contains(n.getId());
}).collect(Collectors.toList());
if (!EmptyUtil.isNullOrEmpty(newFiles)){
List<FileVO> fileVOsResult = bindBatchFile(newFiles);
return !EmptyUtil.isNullOrEmpty(fileVOsResult);
}
return true;
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.BIND_FAIL);
}
}
@Override
@Cacheable(value = FileCacheConstant.LIST,key ="#businessIds.hashCode()")
public List<FileVO> findInBusinessIds(List<Long> businessIds) {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().in(File::getBusinessId,businessIds);
List<FileVO> fileVOList = BeanConv.toBeanList(list(queryWrapper), FileVO.class);
for (FileVO fileVO : fileVOList) {
fileVO.setPathUrl(fileUrlContext.getFileUrl(fileVO.getStoreFlag(), fileVO.getPathUrl()));
}
return fileVOList;
}catch (Exception e){
log.error("查询文件分页异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.BIND_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true),
@CacheEvict(value = FileCacheConstant.LIST,key ="#businessIds.hashCode()"),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true)})
@Transactional
public Boolean deleteInBusinessIds(List<Long> businessIds) {
try {
//删除数据库
List<FileVO> files = findInBusinessIds(businessIds);
UpdateWrapper<File> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().in(File::getBusinessId,businessIds);
Boolean flag = remove(updateWrapper);
if (!flag){
throw new ProjectException(FileEnum.DELETE_FAIL);
}
//删除OSS中的图片
if (!EmptyUtil.isNullOrEmpty(files)){
List<String> getPathUrls = files.stream().map(FileVO::getPathUrl).collect(Collectors.toList());
FileVO fileVO = files.get(0);
String bucketName = fileVO.getBucketName();
String storeFlag = fileVO.getStoreFlag();
fileStorageAdapter.deleteBatch(storeFlag,bucketName,getPathUrls);
}
return flag;
}catch (Exception e){
log.error("删除业务对应附件:{}失败",businessIds);
throw new ProjectException(FileEnum.DELETE_FILE_BUSINESSID_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true),
@CacheEvict(value = FileCacheConstant.LIST,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true)})
public Boolean deleteInIds(List<Long> ids) {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().in(File::getId,ids);
List<File> files = list(queryWrapper);
boolean flag = removeByIds(ids);
if (!flag){
throw new ProjectException(FileEnum.DELETE_FAIL);
}
//移除对象存储数据
for (File file : files) {
fileStorageAdapter.delete(file.getStoreFlag(),file.getBucketName(),file.getPathUrl());
}
return flag;
}catch (Exception e){
log.error("删除文件异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.DELETE_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,allEntries = true)})
@Transactional
public Boolean clearFile() {
try {
//查询需要清理的文件
List<FileVO> fileList = needClearFile();
if (EmptyUtil.isNullOrEmpty(fileList)){
return true;
}
List<Long> fileListIds = fileList.stream().map(FileVO::getId).collect(Collectors.toList());
//移除数据库信息
Boolean flag = removeByIds(fileListIds);
//移除对象存储数据
for (FileVO fileVO : fileList) {
fileStorageAdapter.delete(fileVO.getStoreFlag(),fileVO.getBucketName(),fileVO.getPathUrl());
}
return flag;
}catch (Exception e){
log.error("定时清理文件异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.CLEAR_FILE_TASK_FAIL);
}
}
@Override
@Caching(evict = {@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BASIC,key = "#id"),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true)})
@Transactional
public Boolean clearFileById(String id) {
try {
FileVO fileVO = needClearFileById(id);
if (EmptyUtil.isNullOrEmpty(fileVO)){
return true;
}
Boolean flag = removeById(fileVO.getId());
if (!flag){
throw new ProjectException(FileEnum.DELETE_FAIL);
}
//删除OSS中的图片
if (!EmptyUtil.isNullOrEmpty(fileVO)){
fileStorageAdapter.delete(fileVO.getStoreFlag(),fileVO.getBucketName(),fileVO.getPathUrl());
}
return flag;
}catch (Exception e){
log.error("定时清理文件异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.CLEAR_FILE_TASK_FAIL);
}
}
@Override
public Set<Long> findBusinessIdAll() {
try {
QueryWrapper<File> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(File::getDataState, SuperConstant.DATA_STATE_0).isNotNull(File::getBusinessId);
List<File> list = list(queryWrapper);
if (!EmptyUtil.isNullOrEmpty(list)){
return list.stream().map(File::getBusinessId).collect(Collectors.toSet());
}
return null;
}catch (Exception e){
log.error("查询附件列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.SELECT_BUSUBBESSID_FAIL);
}
}
@Override
@Caching(
evict = {
@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true)},
put={@CachePut(value =FileCacheConstant.BASIC,key = "#result.id")})
@Transactional
public FileVO upLoad(UploadMultipartFile multipartFile, FileVO fileVO) throws ProjectException {
//获得文件ByteArrayInputStream
ByteArrayInputStream byteArrayInputStream =new ByteArrayInputStream(multipartFile.getFileByte());
try {
//文件重命名
String filename = identifierGenerator.nextId(fileVO)+"-"+multipartFile.getOriginalFilename();;
fileVO.setFileName(filename);
//文件后缀名
String suffix = fileVO.getFileName().substring(fileVO.getFileName().lastIndexOf("."));
fileVO.setSuffix(suffix);
//调用简单上传
String pathUrl = fileStorageAdapter.uploadFile(fileVO, byteArrayInputStream);
//保存数据库
File file = BeanConv.toBean(fileVO, File.class);
file.setStatus(FileConstant.STATUS_SUCCEED);
file.setPathUrl(pathUrl);
boolean flag = save(file);
if (!flag){
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}
//补全完整路径
pathUrl = fileUrlContext.getFileUrl(fileVO.getStoreFlag(), pathUrl);
fileVO.setId(file.getId());
fileVO.setPathUrl(pathUrl);
//发送延迟信息:上传如果超过10分钟不进行文件业务绑定则会被消息队列清空
Long messageId = (Long) identifierGenerator.nextId(fileVO);
MqMessage mqMessage = MqMessage.builder()
.id(messageId)
.title("file-message")
.content(JSONObject.toJSONString(fileVO))
.messageType("file-request")
.produceTime(Timestamp.valueOf(LocalDateTime.now()))
.sender("system")
.build();
Message<MqMessage> message = MessageBuilder.withPayload(mqMessage).setHeader("x-delay", fileDelayTime).build();
fileSource.fileOutput().send(message);
return fileVO;
}catch (Exception e) {
log.error("文件上传异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}finally {
if (byteArrayInputStream != null) {
try {
byteArrayInputStream.close();
} catch (Exception e) {
log.error("文件上传操作失败:{}", ExceptionsUtil.getStackTraceAsString(e));
}
}
}
}
@Override
@Caching(evict = {
@CacheEvict(value = FileCacheConstant.PAGE,allEntries = true),
@CacheEvict(value = FileCacheConstant.BUSINESS_KEY,allEntries = true)},
put={@CachePut(value =FileCacheConstant.BASIC,key = "#result.id")})
@Transactional
public FileVO initiateMultipartUpload(FileVO fileVO) {
try {
//文件重命名
String filename = identifierGenerator.nextId(fileVO)+"-"+fileVO.getFileName();
fileVO.setFileName(filename);
//文件后缀名
String suffix = fileVO.getFileName().substring(fileVO.getFileName().lastIndexOf("."));
fileVO.setSuffix(suffix);
//分片上传-初始化
File file = fileStorageAdapter.initiateMultipartUpload(fileVO);
//保存数据库
file.setBusinessType(fileVO.getBusinessType());
file.setSuffix(suffix);
file.setStoreFlag(fileVO.getStoreFlag());
file.setMd5(fileVO.getMd5());
file.setCompanyNo(fileVO.getCompanyNo());
file.setStatus(FileConstant.STATUS_SENDING);
boolean flag = save(file);
if (!flag){
throw new ProjectException(FileEnum.UPLOAD_FAIL);
}
//补全完整路径
FileVO fileVOResult = BeanConv.toBean(file, FileVO.class);
String pathUrl = fileUrlContext.getFileUrl(fileVOResult.getStoreFlag(), fileVOResult.getPathUrl());
fileVOResult.setPathUrl(pathUrl);
//发送队列信息:上传如果超过10分钟不进行文件业务绑定则会被消息队列清空
Long messageId = (Long) identifierGenerator.nextId(fileVOResult);
MqMessage mqMessage = MqMessage.builder()
.id(messageId)
.title("file-message")
.content(JSONObject.toJSONString(fileVOResult))
.messageType("file-request")
.produceTime(Timestamp.valueOf(LocalDateTime.now()))
.sender("system")
.build();
Message<MqMessage> message = MessageBuilder.withPayload(mqMessage).setHeader("x-delay", fileDelayTime).build();
fileSource.fileOutput().send(message);
return fileVOResult;
} catch (Exception e) {
log.error("文件上传初始化异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.INIT_UPLOAD_FAIL);
}
}
@Override
@Transactional
public String uploadPart(UploadMultipartFile multipartFile, FilePartVO filePartVO) {
try {
//上传分片数据
String partETagString = fileStorageAdapter.uploadPart(filePartVO, new ByteArrayInputStream(multipartFile.getFileByte()));
//保存分片信息
FilePart filePart = BeanConv.toBean(filePartVO, FilePart.class);
filePart.setUploadResult(partETagString);
filePartService.save(filePart);
return partETagString;
}catch (Exception e) {
log.error("文件分片上传异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.UPLOAD_PART_FAIL);
}
}
@Override
@Transactional
public String completeMultipartUpload(FileVO fileVO) {
try {
//移除分片记录
Boolean flag = filePartService.deleteFilePartByUpLoadId(fileVO.getUploadId());
if (!flag){
throw new ProjectException(FileEnum.COMPLETE_PART_FAIL);
}
//修改文件记录状态
fileVO.setStatus(FileConstant.STATUS_SUCCEED);
flag = updateById(BeanConv.toBean(fileVO,File.class));
if (!flag){
throw new ProjectException(FileEnum.COMPLETE_PART_FAIL);
}
//合并结果
return fileStorageAdapter.completeMultipartUpload(fileVO);
} catch (Exception e) {
log.error("文件分片上传异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.COMPLETE_PART_FAIL);
}
}
@Override
@Cacheable(value = FileCacheConstant.BASIC,key = "#fileId")
public FileVO downLoad(Long fileId) {
try {
File file = getById(fileId);
InputStream inputStream = fileStorageAdapter
.downloadFile(file.getStoreFlag(), file.getBucketName(), file.getPathUrl());
byte[] bytes = IOUtils.toByteArray(inputStream);
String base64Image = EncodesUtil.encodeBase64(bytes);
FileVO fileVO = BeanConv.toBean(file, FileVO.class);
fileVO.setBase64Image(base64Image);
return fileVO;
} catch (Exception e) {
log.error("文件下载传异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(FileEnum.DOWNLOAD_FAIL);
}
}
}

View File

@@ -0,0 +1,60 @@
package com.itheima.sfbx.file.utils;
import com.itheima.sfbx.file.handler.aliyun.properties.OssAliyunConfigProperties;
import com.itheima.sfbx.file.handler.qiniu.properties.QiniuProperties;
import com.itheima.sfbx.framework.commons.constant.file.FileConstant;
import com.itheima.sfbx.framework.commons.enums.file.FileEnum;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 获得资源文件完整路径地址上下文对象
* 如果添加了新的对象存储资源,需要在此类中对 initMap 方法添加新的路径前缀
* </p>
*
* @Description:
*/
@Component
public class FileUrlContext {
@Autowired
private OssAliyunConfigProperties ossAliyunConfigProperties;
@Autowired
private QiniuProperties qiniuProperties;
private static Map<String,String> fileStoreUrlHandler =new HashMap<>();
@PostConstruct
public void initMap() {
// 初始化各个对象存储的文件访问路径地址
String ossPrefixUrl = "https://" + ossAliyunConfigProperties.getBucketName() + "." +
ossAliyunConfigProperties.getEndpoint() + "/";
String kodoPrefixUrl = "http://" + qiniuProperties.getKodo().getEndpoint() + "/";
fileStoreUrlHandler.put(FileConstant.ALIYUN_OSS,ossPrefixUrl);
fileStoreUrlHandler.put(FileConstant.QINIU_KODO,kodoPrefixUrl);
}
/**
* 获得资源文件的访问地址
* @param storeFlag String 对象存储标识
* @param pathUrl String 相对路径
* @return String 资源文件对应的完整路径地址
*/
public String getFileUrl(String storeFlag, String pathUrl) {
String prefix = fileStoreUrlHandler.get(storeFlag);
if (StringUtils.isEmpty(prefix)) {
throw new ProjectException(FileEnum.FILE_PREFIX_NOT_FOUND);
}
return prefix + pathUrl;
}
}

View File

@@ -0,0 +1,71 @@
package com.itheima.sfbx.file.web;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.itheima.sfbx.file.service.IFileService;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName FileController.java
* @Description 附件展示维护controller
*/
@RestController
@RequestMapping("file")
@Api(tags = "附件controller")
@Slf4j
public class FileBusinessController {
@Autowired
IFileService fileService;
/***
* @description 附件分页列表
* @param fileVO 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return: Page<FileVO>
*/
@PostMapping("page/{pageNum}/{pageSize}")
@ApiOperation(value = "查询附件分页",notes = "查询附件分页")
@ApiImplicitParams({
@ApiImplicitParam(name = "fileVO",value = "附件查询对象",required = false,dataType = "FileVO"),
@ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",example = "1",dataType = "Integer"),
@ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",example = "10",dataType = "Integer")
})
@ApiOperationSupport(includeParameters ={"fileVO.businessType","","fileVO.pathUrl",
"fileVO.dataState","fileVO.status"} )
public ResponseResult<Page<FileVO>> findFileVOPage(
@RequestBody FileVO fileVO,
@PathVariable("pageNum") int pageNum,
@PathVariable("pageSize") int pageSize) {
//查询附件分页信息
Page<FileVO> fileVOPage = fileService.findFileVOPage(fileVO, pageNum, pageSize);
return ResponseResultBuild.successBuild(fileVOPage);
}
/**
* @Description 移除业务原图片,并批量绑定新的图片到业务上
* @param fileVOs 附件对象
* @return
*/
@PutMapping(value = "replace-bind-batch-file")
@ApiOperation(value = "移除业务原图片,并绑定新的图片到业务上",notes = "移除业务原图片,并绑定新的图片到业务上")
@ApiImplicitParam(name = "fileVOs",value = "附件对象",required = true,dataType = "FileVO")
public ResponseResult<Boolean> replaceBindBatchFile(@RequestBody List<FileVO> fileVOs){
Boolean flag = fileService.replaceBindBatchFile(fileVOs);
return ResponseResultBuild.successBuild(flag);
}
}

View File

@@ -0,0 +1,104 @@
package com.itheima.sfbx.file.web;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.itheima.sfbx.file.service.IFileService;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.framework.commons.dto.file.FilePartVO;
import com.itheima.sfbx.framework.commons.dto.file.UploadMultipartFile;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import com.itheima.sfbx.framework.commons.utils.SubjectContent;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* @ClassName FileUpLoadController.java
* @Description 文件上传接口
*/
@RestController
@RequestMapping("file")
@Api(tags = "附件controller")
@Slf4j
public class FileUpLoadController {
@Autowired
IFileService fileService;
/***
* @description 文件上传-简单上传-前端直接调用
* @param file 上传对象
* @return: com.itheima.travel.req.FileVO
*/
@PostMapping(value = "up-load")
@ApiOperation(value = "文件上传-简单上传",notes = "文件上传-简单上传")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "form", name = "file", value = "文件对象", required = true, dataTypeClass = MultipartFile.class)
})
@ApiOperationSupport(includeParameters = {"fileVO.businessType","fileVO.bucketName","fileVO.storeFlag","fileVO.autoCatalog"})
public ResponseResult<FileVO> upLoad(
@RequestParam("file") MultipartFile file,
FileVO fileVO) throws IOException {
fileVO.setCompanyNo(SubjectContent.getCompanyNo());
//构建文件上传对象
UploadMultipartFile uploadMultipartFile = UploadMultipartFile
.builder()
.originalFilename(file.getOriginalFilename())
.fileByte(IOUtils.toByteArray(file.getInputStream()))
.build();
//执行文件上传
FileVO fileVOResult = fileService.upLoad(uploadMultipartFile, fileVO);
return ResponseResultBuild.successBuild(fileVOResult);
}
@PostMapping(value = "initiate-multipart-up-load")
@ApiOperation(value = "文件分片上传-初始化",notes = "文件分片上传-初始化")
@ApiImplicitParam(name = "fileVO",value = "文件对象",required = true,dataType = "FileVO")
public ResponseResult<FileVO> initiateMultipartUpload(
@RequestBody FileVO fileVO){
fileVO.setCompanyNo(SubjectContent.getCompanyNo());
//初始化上传Id
FileVO fileVOResult = fileService.initiateMultipartUpload(fileVO);
return ResponseResultBuild.successBuild(fileVOResult);
}
@PostMapping(value = "up-load-part")
@ApiOperation(value = "文件分片上传-上传分片",notes = "文件分片上传-上传分片")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "form", name = "file", value = "文件对象", required = true, dataTypeClass = MultipartFile.class)
})
public ResponseResult<String> uploadPart(
@RequestParam("file") MultipartFile file,
FilePartVO filePartVO)throws IOException {
filePartVO.setCompanyNo(SubjectContent.getCompanyNo());
//构建文件上次对象
UploadMultipartFile uploadMultipartFile = UploadMultipartFile
.builder()
.originalFilename(file.getOriginalFilename())
.fileByte(IOUtils.toByteArray(file.getInputStream()))
.build();
//上传分片返回partETagJson
String partETagJson = fileService.uploadPart(uploadMultipartFile,filePartVO);
return ResponseResultBuild.successBuild(partETagJson);
}
@PostMapping(value = "complete-multipart-up-load")
@ApiOperation(value = "文件分片上传-合并分片",notes = "文件分片上传-合并分片")
@ApiImplicitParam(name = "fileVO",value = "文件对象",required = true,dataType = "FileVO")
public ResponseResult<String> completeMultipartUpload(
@RequestBody FileVO fileVO)throws IOException {
//问上传分片返回partETagJson
String eTagJson = fileService.completeMultipartUpload(fileVO);
return ResponseResultBuild.successBuild(eTagJson);
}
}

View File

@@ -0,0 +1,10 @@
_ __ __
(_) /__________ ______/ /_
/ / __/ ___/ __ `/ ___/ __/
/ / /_/ /__/ /_/ (__ ) /_
/_/\__/\___/\__,_/____/\__/
:: Spring Boot :: (v-2.7.10)
:: Spring Cloud :: (v-2021.0.6)
:: Spring Cloud Alibaba :: (v-2021.0.1.0)
:: sfbx Cloud :: (v-2.0-SNAPSHOT)
:: 献给可爱的传智人 ::

View File

@@ -0,0 +1,54 @@
#服务配置
server:
#端口
port: 7075
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
config:
activate:
on-profile:
- test
main:
allow-circular-references: true
allow-bean-definition-overriding: true
mvc:
pathmatch:
matching-strategy: ant_path_matcher
#应用配置
application:
#应用名称
name: file-web
cloud:
nacos:
discovery:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos注册中心
group: SEATA_GROUP
service: ${spring.application.name}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
config:
server-addr: ${NACOS_ADDRESS:nacos-service.yjy-public-sfbx-java.svc.cluster.local:20015} # nacos注册中心
group: SEATA_GROUP
file-extension: yml
shared-configs: # 共享配置
- data-id: shared-spring-seata.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-redisson.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-mybatis-plus.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-stream-rabbit-basic.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
- data-id: shared-stream-rabbit-source-file.yml #配置文件名-DataId
group: SEATA_GROUP
refresh: false
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:PKsf*bxQ4;yP3a+}
logging:
config: classpath:logback.xml

Some files were not shown because too many files have changed in this diff Show More