功能:在保险、规则、交易、积分和短信模块中实现初始功能和基础组件。

This commit is contained in:
abcv7
2026-03-03 04:04:22 +08:00
parent e050eb3317
commit e156d625bf
1999 changed files with 146080 additions and 63 deletions
+21
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
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>
@@ -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);
}
}
@@ -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;
}
@@ -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);
}
}
@@ -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 {
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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;
}
@@ -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));
}
}
@@ -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;
}
@@ -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";
}
}
@@ -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));
}
}
@@ -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;
}
}
}
@@ -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> {
}
@@ -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> {
}
@@ -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;
}
@@ -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;
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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)
:: 献给可爱的传智人 ::
@@ -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
@@ -0,0 +1,53 @@
#服务配置
server:
#端口
port: 7075
#服务编码
tomcat:
uri-encoding: UTF-8
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 20MB
profiles:
active: dev
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: 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-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
logging:
config: classpath:logback.xml
@@ -0,0 +1,47 @@
#\u6570\u636E\u5E93\u5730\u5740
url=jdbc:mysql://192.168.12.129:3306/restkeeper-file?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&tinyInt1isBit=false
#\u6570\u636E\u5E93\u8D26\u53F7
userName=root
#\u6570\u636E\u5E93\u5BC6\u7801
password=pass
#services\u7684src\u8DEF\u5F84
serviceProjectPath=E:/itheima-project-parent/itheima-project-cloud/itheima-module-file/module-file-web
#VO\u7684src\u8DEF\u5F84
voProjectPath=E:/itheima-project-parent/itheima-project-cloud/itheima-component/component-vo
#\u4F5C\u8005
author=Admin
#\u5305\u540D\u79F0
parent=com.itheima
#\u6A21\u5757\u540D\u79F0
moduleName=project
#\u6570\u636E\u5E93\u8868\u524D\u7F00
tablePrefix =tab_
#\u9700\u8981\u751F\u6210\u7684\u8868\u540D\uFF0C\u4F7F\u7528\uFF0C\u5206\u5272
tableName= tab_file_part
#\u9700\u8981\u751F\u6210\u9644\u4EF6\u76F8\u5173\u7684\u8868
tablesFile=
#pojo\u7684\u7236\u7C7B
SuperEntityClass = com.itheima.easy.basic.BasicPojo
#pojo\u7684\u901A\u7528\u5B57\u6BB5
superEntityColumns = id,created_time,updated_time,sharding_id,enable_flag
#\u751F\u6210\u89C4\u5219
constant=true
constant.ftl.path=/templates/constant.java
enums=true
enums.ftl.path=/templates/enums.java
vo=true
vo.ftl.path=/templates/vo.java
entity=true
entity.ftl.path=/templates/entity.java
mapper=true
mapper.ftl.path=/templates/mapper.java
service=true
service.ftl.path=/templates/service.java
serviceImpl=true
serviceImpl.ftl.path=/templates/serviceImpl.java
face=false
face.ftl.path=/templates/face.java
faceImpl=false
faceImpl.ftl.path=/templates/faceImpl.java
controller=false
controller.ftl.path=/templates/controller.java
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 -->
<property name="LOG_HOME" value="/data/logs/file-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}/file-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>
@@ -0,0 +1,24 @@
package com.itheima.easy.constant;
/**
* @Description${table.comment!}缓存常量
*/
public class ${entity}CacheConstant extends CacheConstant {
//缓存父包
public static final String PREFIX= "${controllerMappingHyphen}:";
//缓存父包
public static final String BASIC= PREFIX+"basic";
//分布式锁前缀
public static final String LOCK_PREFIX = PREFIX+"lock:";
//page分页
public static final String PAGE= PREFIX+"page";
//list下拉框
public static final String LIST= PREFIX+"list";
}
@@ -0,0 +1,128 @@
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.sfbx.framework.commons.enums.trade.${entity}Enum;
import com.itheima.easy.face.${entity}Face;
import com.itheima.sfbx.framework.commons.utils.ResponseResultBuild;
import com.itheima.sfbx.framework.commons.dto.trade.${entity}Vo;
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;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
/**
* @Description${table.comment!}前端控制器
*/
@Slf4j
@Api(tags = "${table.comment!}controller")
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#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>
@Autowired
${entity}Face ${entity?uncap_first}Face;
/***
* @description 多条件查询${table.comment!}分页列表
* @param ${entity?uncap_first}Vo ${table.comment!}Vo查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return: Page<${entity}Vo>
*/
@PostMapping("page/{pageNum}/{pageSize}")
@ApiOperation(value = "${table.comment!}分页",notes = "${table.comment!}分页")
@ApiImplicitParams({
@ApiImplicitParam(name = "${entity?uncap_first}Vo",value = "${table.comment!}Vo对象",required = true,dataType = "${entity}Vo"),
@ApiImplicitParam(paramType = "path",name = "pageNum",value = "页码",example = "1",dataType = "Integer"),
@ApiImplicitParam(paramType = "path",name = "pageSize",value = "每页条数",example = "10",dataType = "Integer")
})
public ResponseResult<Page<${entity}Vo>> find${entity}VoPage(
@RequestBody ${entity}Vo ${entity?uncap_first}Vo,
@PathVariable("pageNum") int pageNum,
@PathVariable("pageSize") int pageSize) {
Page<${entity}Vo> ${entity?uncap_first}VoPage = ${entity?uncap_first}Face.find${entity}VoPage(${entity?uncap_first}Vo, pageNum, pageSize);
return ResponseResultBuild.build(${entity}Enum.SUCCEED,${entity?uncap_first}VoPage);
}
/**
* @Description 保存${table.comment!}
* @param ${entity?uncap_first}Vo ${table.comment!}Vo对象
* @return ${entity}Vo
*/
@PutMapping
@ApiOperation(value = "添加${entity}",notes = "添加${entity}")
@ApiImplicitParam(name = "${entity?uncap_first}Vo",value = "${table.comment!}Vo对象",required = true,dataType = "${entity}Vo")
public ResponseResult<${entity}Vo> create${entity}(@RequestBody ${entity}Vo ${entity?uncap_first}Vo) {
${entity}Vo ${entity?uncap_first}VoResult = ${entity?uncap_first}Face.create${entity}Vo(${entity?uncap_first}Vo);
return ResponseResultBuild.build(${entity}Enum.SUCCEED,${entity?uncap_first}VoResult);
}
/**
* @Description 修改${table.comment!}
* @param ${entity?uncap_first}Vo ${table.comment!}Vo对象
* @return Boolean 是否修改成功
*/
@PatchMapping
@ApiOperation(value = "修改${table.comment!}",notes = "修改${table.comment!}")
@ApiImplicitParam(name = "${entity?uncap_first}Vo",value = "${table.comment!}Vo对象",required = true,dataType = "${entity}Vo")
public ResponseResult<Boolean> update${entity}(@RequestBody ${entity}Vo ${entity?uncap_first}Vo) {
Boolean flag = ${entity?uncap_first}Face.update${entity}Vo(${entity?uncap_first}Vo);
return ResponseResultBuild.build(${entity}Enum.SUCCEED,flag);
}
/**
* @Description 删除${table.comment!}
* @param ${entity?uncap_first}Vo 刪除条件:checkedIds 不可为空
* @return
*/
@DeleteMapping
@ApiOperation(value = "删除${table.comment!}",notes = "删除${table.comment!}")
@ApiImplicitParam(name = "${entity?uncap_first}Vo",value = "${table.comment!}Vo对象",required = true,dataType = "${entity}Vo")
public ResponseResult<Boolean> delete${entity}(@RequestBody ${entity}Vo ${entity?uncap_first}Vo) {
Boolean flag = ${entity?uncap_first}Face.delete${entity}Vo(${entity?uncap_first}Vo.getCheckedIds());
return ResponseResultBuild.build(${entity}Enum.SUCCEED,flag);
}
/***
* @description 多条件查询${table.comment!}列表
* @param ${entity?uncap_first}Vo ${table.comment!}Vo对象
* @return List<${entity}Vo>
*/
@PostMapping("list")
@ApiOperation(value = "多条件查询${table.comment!}列表",notes = "多条件查询${table.comment!}列表")
@ApiImplicitParam(name = "${entity?uncap_first}Vo",value = "${table.comment!}Vo对象",required = true,dataType = "${entity}Vo")
public ResponseResult<List<${entity}Vo>> ${entity?uncap_first}List(@RequestBody ${entity}Vo ${entity?uncap_first}Vo) {
List<${entity}Vo> ${entity?uncap_first}VoList = ${entity?uncap_first}Face.find${entity}VoList(${entity?uncap_first}Vo);
return ResponseResultBuild.build(${entity}Enum.SUCCEED,${entity?uncap_first}VoList);
}
}
</#if>
@@ -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>
}
@@ -0,0 +1,38 @@
package com.itheima.easy.enums;
import com.itheima.easy.basic.IBasicEnum;
/**
* @ClassName ${entity}Enum.java
* @Description ${table.comment!}枚举
*/
public enum ${entity}Enum implements IBasicEnum {
SUCCEED("200","操作成功"),
FAIL("1000","操作失败"),
PAGE_FAIL("53001", "查询${table.comment!}列表失败"),
SAVE_FAIL("53002", "保存${table.comment!}失败"),
UPDATE_FAIL("53003", "修改${table.comment!}失败"),
DEL_FAIL("53004", "删除${table.comment!}失败"),
LIST_FAIL("53005", "查询${table.comment!}失败")
;
private String code;
private String msg;
${entity}Enum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
@@ -0,0 +1,53 @@
package com.itheima.easy.face;
import com.itheima.sfbx.framework.commons.dto.trade.${entity}Vo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* @Description${table.comment!}Face服务类
*/
<#if kotlin>
interface ${entity}Face
<#else>
public interface ${entity}Face {
/**
* @Description 多条件查询${table.comment!}分页列表
* @param ${entity?uncap_first}Vo 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return Page<${entity}>
*/
Page<${entity}Vo> find${entity}VoPage(${entity}Vo ${entity?uncap_first}Vo, int pageNum, int pageSize);
/**
* @Description 创建${table.comment!}
* @param ${entity?uncap_first}Vo 对象信息
* @return ${entity}
*/
${entity}Vo create${entity}Vo(${entity}Vo ${entity?uncap_first}Vo);
/**
* @Description 修改${table.comment!}
* @param ${entity?uncap_first}Vo 对象信息
* @return Boolean
*/
Boolean update${entity}Vo(${entity}Vo ${entity?uncap_first}Vo);
/**
* @Description 删除${table.comment!}
* @param checkedIds 选择中对象Ids
* @return Boolean
*/
Boolean delete${entity}Vo(String[] checkedIds);
/**
* @description 多条件查询${table.comment!}列表
* @param ${entity?uncap_first}Vo 查询条件
* @return: List<${entity}>
*/
List<${entity}Vo> find${entity}VoList(${entity}Vo ${entity?uncap_first}Vo);
}
</#if>
@@ -0,0 +1,188 @@
package com.itheima.easy.face.impl;
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import com.itheima.sfbx.framework.commons.dto.trade.${entity}Vo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.easy.face.${entity}Face;
import org.springframework.beans.factory.annotation.Autowired;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import com.itheima.sfbx.framework.commons.utils.ExceptionsUtil;
import com.itheima.sfbx.framework.commons.exception.ProjectException;
import com.itheima.sfbx.framework.commons.constant.trade.${entity}CacheConstant;
import com.itheima.sfbx.framework.commons.enums.trade.${entity}Enum;
import org.springframework.transaction.annotation.Transactional;
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
import java.util.List;
import java.util.ArrayList;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.itheima.sfbx.framework.commons.basic.ResponseResult;
import com.itheima.easy.feign.FileFeign;
</#if>
</#list>
import java.util.List;
/**
* @Description${table.comment!}Face服务实现类
*/
<#if kotlin>
open class ${entity}FaceImpl : ${entity}Face {
}
<#else>
@Slf4j
@Component
public class ${entity}FaceImpl implements ${entity}Face {
@Autowired
I${entity}Service ${entity?uncap_first}Service;
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
@Autowired
FileFeign fileFeign;
</#if>
</#list>
@Override
@Cacheable(value = ${entity}CacheConstant.PAGE,key ="#pageNum+'-'+#pageSize+'-'+#${entity?uncap_first}Vo.hashCode()")
public Page<${entity}Vo> find${entity}VoPage(${entity}Vo ${entity?uncap_first}Vo, int pageNum, int pageSize) {
try {
Page<${entity}> page = ${entity?uncap_first}Service.find${entity}Page(${entity?uncap_first}Vo, pageNum, pageSize);
Page<${entity}Vo> pageVo =BeanConv.toPage(page,${entity}Vo.class);
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
//查询记录结果
List<${entity}Vo> records = pageVo.getRecords();
//处理图片
if (!EmptyUtil.isNullOrEmpty(records)){
records.forEach(n->{
ResponseResult<List<FileVO>> responseWrap = fileFeign
.findFileVOByBusinessId(FileVO.builder().businessId(n.getId()).build());
if (!EmptyUtil.isNullOrEmpty(responseWrap.getDatas())){
n.setFileVOs(responseWrap.getDatas());
}else {
n.setFileVOs(new ArrayList<>());
}
});
}
</#if>
</#list>
return pageVo;
}catch (Exception e){
log.error("${table.comment!}列表异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(${entity}Enum.PAGE_FAIL);
}
}
@Transactional
@Caching(evict = {@CacheEvict(value = ${entity}CacheConstant.PAGE,allEntries = true),
@CacheEvict(value = ${entity}CacheConstant.LIST,allEntries = true)},
put={@CachePut(value =${entity}CacheConstant.BASIC,key = "#result.id")})
@Override
public ${entity}Vo create${entity}Vo(${entity}Vo ${entity?uncap_first}Vo) {
try {
${entity}Vo ${entity?uncap_first}VoResult = BeanConv.toBean(${entity?uncap_first}Service.create${entity}(${entity?uncap_first}Vo), ${entity}Vo.class);
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
//绑定附件
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}VoResult)&&!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.getFileVOs())){
${entity?uncap_first}Vo.getFileVOs().forEach(n->{n.setBusinessId(${entity?uncap_first}VoResult.getId());});
//绑定图片
ResponseResult<List<FileVO>> listResponseResult = fileFeign.bindBatchBusinessId(${entity?uncap_first}Vo.getFileVOs());
${entity?uncap_first}VoResult.setFileVOs(listResponseResult.getDatas());
}
</#if>
</#list>
return ${entity?uncap_first}VoResult;
} catch (Exception e) {
log.error("保存${table.comment!}异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(${entity}Enum.SAVE_FAIL);
}
}
@Transactional
@Caching(evict = {@CacheEvict(value = ${entity}CacheConstant.PAGE,allEntries = true),
@CacheEvict(value = ${entity}CacheConstant.LIST,allEntries = true),
@CacheEvict(value =${entity}CacheConstant.BASIC,key = "#${entity?uncap_first}Vo.id")})
@Override
public Boolean update${entity}Vo(${entity}Vo ${entity?uncap_first}Vo) {
try {
Boolean flag = ${entity?uncap_first}Service.update${entity}(${entity?uncap_first}Vo);
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
if (flag){
//移除业务原图片,并绑定新的图片到业务上
fileFeign.replaceBindBatchBusinessId(${entity?uncap_first}Vo.getFileVOs());
}
</#if>
</#list>
return flag;
} catch (Exception e) {
log.error("修改${table.comment!}异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(${entity}Enum.UPDATE_FAIL);
}
}
@Transactional
@Caching(evict = {@CacheEvict(value = ${entity}CacheConstant.PAGE,allEntries = true),
@CacheEvict(value = ${entity}CacheConstant.LIST,allEntries = true)})
@Override
public Boolean delete${entity}Vo(String[] checkedIds) {
try {
Boolean flag = ${entity?uncap_first}Service.delete${entity}(checkedIds);
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
if (flag){
//删除图片
for (String checkedId : checkedIds) {
fileFeign.deleteFileVOByBusinessId(FileVO.builder()
.businessId(Long.valueOf(checkedId))
.build());
}
}
</#if>
</#list>
return flag;
} catch (Exception e) {
log.error("删除${table.comment!}异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(${entity}Enum.DEL_FAIL);
}
}
@Override
@Cacheable(value = ${entity}CacheConstant.LIST,key ="#${entity?uncap_first}Vo.hashCode()")
public List<${entity}Vo> find${entity}VoList(${entity}Vo ${entity?uncap_first}Vo) {
try {
List<${entity}Vo> records = BeanConv.toBeanList(${entity?uncap_first}Service.find${entity}List(${entity?uncap_first}Vo),${entity}Vo.class);
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
//处理图片
if (!EmptyUtil.isNullOrEmpty(records)){
records.forEach(n->{
ResponseResult<List<FileVO>> responseWrap = fileFeign
.findFileVOByBusinessId(FileVO.builder().businessId(n.getId()).build());
if (!EmptyUtil.isNullOrEmpty(responseWrap.getDatas())){
n.setFileVOs(responseWrap.getDatas());
}else {
n.setFileVOs(new ArrayList<>());
}
});
}
</#if>
</#list>
return records;
} catch (Exception e) {
log.error("删除${table.comment!}异常:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(${entity}Enum.LIST_FAIL);
}
}
}
</#if>
@@ -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>
@@ -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>
@@ -0,0 +1,55 @@
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
import com.itheima.sfbx.framework.commons.dto.trade.${entity}Vo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
/**
* @Description${table.comment!}服务类
*/
<#if kotlin>
interface ${table.serviceName} : ${superServiceClass}<${entity}>
<#else>
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {
/**
* @Description 多条件查询${table.comment!}分页列表
* @param ${entity?uncap_first}Vo 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return Page<${entity}>
*/
Page<${entity}> find${entity}Page(${entity}Vo ${entity?uncap_first}Vo, int pageNum, int pageSize);
/**
* @Description 创建${table.comment!}
* @param ${entity?uncap_first}Vo 对象信息
* @return ${entity}
*/
${entity} create${entity}(${entity}Vo ${entity?uncap_first}Vo);
/**
* @Description 修改${table.comment!}
* @param ${entity?uncap_first}Vo 对象信息
* @return Boolean
*/
Boolean update${entity}(${entity}Vo ${entity?uncap_first}Vo);
/**
* @Description 删除${table.comment!}
* @param checkedIds 选择中对象Ids
* @return Boolean
*/
Boolean delete${entity}(String[] checkedIds);
/**
* @description 多条件查询${table.comment!}列表
* @param ${entity?uncap_first}Vo 查询条件
* @return: List<${entity}>
*/
List<${entity}> find${entity}List(${entity}Vo ${entity?uncap_first}Vo);
}
</#if>
@@ -0,0 +1,100 @@
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
import com.itheima.sfbx.framework.commons.dto.trade.${entity}Vo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.sfbx.framework.commons.utils.BeanConv;
import com.itheima.sfbx.framework.commons.utils.EmptyUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @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} {
@Override
public Page<${entity}> find${entity}Page(${entity}Vo ${entity?uncap_first}Vo, int pageNum, int pageSize) {
//构建分页对象
Page<${entity}> page = new Page<>(pageNum,pageSize);
//构建查询条件
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
//构建多条件查询,代码生成后自己可自行调整
<#list table.fields as field>
//${field.comment}查询
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.get${field.propertyName?cap_first}())) {
queryWrapper.lambda().eq(${entity}::get${field.propertyName?cap_first},${entity?uncap_first}Vo.get${field.propertyName?cap_first}());
}
</#list>
//状态查询
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.getEnableFlag())) {
queryWrapper.lambda().eq(${entity}::getEnableFlag,${entity?uncap_first}Vo.getEnableFlag());
}
//按创建时间降序
queryWrapper.lambda().orderByDesc(${entity}::getCreatedTime);
//执行分页查询
return page(page, queryWrapper);
}
@Override
public ${entity} create${entity}(${entity}Vo ${entity?uncap_first}Vo) {
//转换${entity}Vo为${entity}
${entity} ${entity?uncap_first} = BeanConv.toBean(${entity?uncap_first}Vo, ${entity}.class);
boolean flag = save(${entity?uncap_first});
if (flag){
return ${entity?uncap_first};
}
return null;
}
@Override
public Boolean update${entity}(${entity}Vo ${entity?uncap_first}Vo) {
//转换${entity}Vo为${entity}
${entity} ${entity?uncap_first} = BeanConv.toBean(${entity?uncap_first}Vo, ${entity}.class);
return updateById(${entity?uncap_first});
}
@Override
public Boolean delete${entity}(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<${entity}> find${entity}List(${entity}Vo ${entity?uncap_first}Vo) {
//构建查询条件
QueryWrapper<${entity}> queryWrapper = new QueryWrapper<>();
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.getId())) {
queryWrapper.lambda().eq(${entity}::getId,${entity?uncap_first}Vo.getId());
}
<#list table.fields as field>
//${field.comment}查询
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.get${field.propertyName?cap_first}())) {
queryWrapper.lambda().eq(${entity}::get${field.propertyName?cap_first},${entity?uncap_first}Vo.get${field.propertyName?cap_first}());
}
</#list>
//状态查询
if (!EmptyUtil.isNullOrEmpty(${entity?uncap_first}Vo.getEnableFlag())) {
queryWrapper.lambda().eq(${entity}::getEnableFlag,${entity?uncap_first}Vo.getEnableFlag());
}
return list(queryWrapper);
}
}
</#if>
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover">
<title>店铺商品详情</title>
</head>
<body>
<div id="app">
<h1>商品信息json</h1>
<div>
${shopGoods}
</div>
<#-- <div>-->
<#-- <h1>-->
<#-- 封面图片列表-->
<#-- </h1>-->
<#-- <ul>-->
<#-- <li>路径: <a href=""> </a></li>-->
<#-- </ul>-->
<#-- </div>-->
<#-- <div>-->
<#-- <h1>-->
<#-- 商品基本信息-->
<#-- </h1>-->
<#-- <p> 优惠价: <span> </span> 原价: <span> </span> </p>-->
<#-- -->
<#-- <p>标题: <span> </span></p>-->
<#-- <p>当前规格: </p>-->
<#-- -->
<#-- <p>所有规格: </p>-->
<#-- <p>服务规则列表: </p>-->
<#-- </div>-->
<#-- <div>-->
<#-- <h1>-->
<#-- 商品详情图片列表-->
<#-- </h1>-->
<#-- <ul>-->
<#-- <li>路径: <a href=""> </a></li>-->
<#-- </ul>-->
<#-- </div>-->
<p>参数列表: 未提供</p>
<p>评价列表: 异步ajax查询</p>
<p>优惠券: 异步查询</p>
</div>
</body>
</html>
@@ -0,0 +1,75 @@
package com.itheima.easy.vo;
import com.itheima.easy.basic.BasicVo;
<#if swagger2>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
</#if>
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
import java.util.List;
import com.itheima.sfbx.framework.commons.dto.file.FileVO;
</#if>
</#list>
/**
* @Description${table.comment!}
*/
<#if entityLombokModel>
@Data
@NoArgsConstructor
</#if>
public class ${entity}Vo extends BasicVo {
@Builder
public ${entity}Vo(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.propertyType=='Long'>
@JsonFormat(shape = JsonFormat.Shape.STRING)
</#if>
<#if field.propertyType=='Date'>
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
@ApiModelProperty(value = "选中节点")
private String[] checkedIds;
<#list cfg.tablesFile as tableFiile>
<#if tableFiile==table.name>
<#if swagger2>
@ApiModelProperty(value = "文件Vo对象")
</#if>
private List<FileVO> fileVOs;
</#if>
</#list>
}