新增文件上传模块

🐛 修复日志记录开关以及文件上传时由于日志记录导致的文件读取异常
This commit is contained in:
b2baccline
2020-01-09 13:09:03 +08:00
parent f4ea5d5a08
commit a9490d9b63
24 changed files with 380 additions and 33 deletions

View File

@@ -29,6 +29,10 @@
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-log</artifactId>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-storage</artifactId>
</dependency>
<!-- swagger 文档聚合 -->
<dependency>
<groupId>com.hccake</groupId>

View File

@@ -54,8 +54,12 @@ public class AdminAccessLogHandlerServiceImpl implements AccessLogHandlerService
.setMatchingPattern(String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)))
.setErrorMsg(Optional.ofNullable(myThrowable).map(Throwable::getMessage).orElse(null))
.setHttpStatus(response.getStatus())
.setReqParams(JSONUtil.toJsonStr(request.getParameterMap()))
.setReqBody(LogUtils.getRequestBody(request));
.setReqParams(JSONUtil.toJsonStr(request.getParameterMap()));
// 非文件上传请求记录body
if (!LogUtils.isMultipartContent(request)){
adminAccessLog.setReqBody(LogUtils.getRequestBody(request));
}
// 如果登陆用户 则记录用户名和用户id
Optional.ofNullable(SecurityUtils.getSysUserDetails())

View File

@@ -17,14 +17,17 @@ import com.hccake.ballcat.common.core.result.R;
import com.hccake.ballcat.common.core.result.ResultStatus;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
import javax.validation.ValidationException;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -32,16 +35,14 @@ import java.util.List;
* @author
* @date 2018/12/16
*/
@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/sysuser")
@Api(value = "sysuser", tags = "用户管理模块")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysUserRoleService sysUserRoleService;
private final SysUserService sysUserService;
private final SysUserRoleService sysUserRoleService;
/**
* 分页查询用户
@@ -187,4 +188,20 @@ public class SysUserController {
R.ok() : R.failed(ResultStatus.SAVE_ERROR, "批量修改用户状态!");
}
@ApiOperation(value = "修改系统用户头像", notes = "修改系统用户头像")
@OperationLogging("修改系统用户头像")
@PreAuthorize("@per.hasPermission('sys_sysuser_edit')")
@PostMapping("/avatar")
public R<String> updateAvatar(@RequestParam("file") MultipartFile file, @RequestParam("userId") Integer userId) {
String objectName;
try {
objectName = sysUserService.updateAvatar(file, userId);
} catch (IOException e) {
log.error("修改系统用户头像异常", e);
return R.failed(ResultStatus.FILE_UPLOAD_ERROR);
}
return R.ok(objectName);
}
}

View File

@@ -0,0 +1,20 @@
package com.hccake.ballcat.admin.modules.sys.service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* @author Hccake
* @version 1.0
* @date 2020/1/8 11:15
*/
public interface FileService {
/**
* 文件上传
* @param file 待上传文件
* @param objectName 文件对象名
*
*/
void uploadFile(MultipartFile file, String objectName) throws IOException;
}

View File

@@ -7,7 +7,9 @@ import com.hccake.ballcat.admin.modules.sys.model.dto.SysUserScope;
import com.hccake.ballcat.admin.modules.sys.model.entity.SysUser;
import com.hccake.ballcat.admin.modules.sys.model.qo.SysUserQO;
import com.hccake.ballcat.admin.modules.sys.model.vo.UserInfo;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
@@ -91,4 +93,13 @@ public interface SysUserService extends IService<SysUser> {
* @return
*/
boolean updateUserStatus(List<Integer> userIds, Integer status);
/**
* 修改系统用户头像
* @param file 头像文件
* @param userId 用户ID
* @return 文件相对路径
* @throws IOException
*/
String updateAvatar(MultipartFile file, Integer userId) throws IOException;
}

View File

@@ -0,0 +1,32 @@
package com.hccake.ballcat.admin.modules.sys.service.impl;
import com.hccake.ballcat.admin.modules.sys.service.FileService;
import com.hccake.ballcat.commom.storage.FileStorageClient;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
/**
* @author Hccake
* @version 1.0
* @date 2020/1/8 11:16
*/
@Service
@AllArgsConstructor
public class FileServiceImpl implements FileService {
private final FileStorageClient fileStorageClient;
/**
* 文件上传
*
* @param file 待上传文件
* @param objectName 文件对象名
*
*/
@Override
public void uploadFile(MultipartFile file, String objectName) throws IOException {
fileStorageClient.putObject(objectName, file.getInputStream());
}
}

View File

@@ -1,6 +1,9 @@
package com.hccake.ballcat.admin.modules.sys.service.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
@@ -16,15 +19,20 @@ import com.hccake.ballcat.admin.modules.sys.model.entity.SysUser;
import com.hccake.ballcat.admin.modules.sys.model.qo.SysUserQO;
import com.hccake.ballcat.admin.modules.sys.model.vo.PermissionVO;
import com.hccake.ballcat.admin.modules.sys.model.vo.UserInfo;
import com.hccake.ballcat.admin.modules.sys.service.FileService;
import com.hccake.ballcat.admin.modules.sys.service.SysPermissionService;
import com.hccake.ballcat.admin.modules.sys.service.SysUserRoleService;
import com.hccake.ballcat.admin.modules.sys.service.SysUserService;
import com.hccake.ballcat.common.core.util.PasswordUtil;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -38,17 +46,18 @@ import java.util.stream.Collectors;
* @date 2019-09-12 20:39:31
*/
@Service
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
private final FileService fileService;
private final SysPermissionService sysPermissionService;
private final SysUserRoleService sysUserRoleService;
@Autowired
private SysPermissionService sysPermissionService;
@Autowired
private SysUserRoleService sysUserRoleService;
@Value("${password.secret-key}")
private String secretKey;
@Override
public IPage<SysUser> getUserPage(IPage<SysUser> page, SysUserQO qo) {
@@ -207,5 +216,22 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
);
}
@Override
@Transactional(rollbackFor = Exception.class)
public String updateAvatar(MultipartFile file, Integer userId) throws IOException {
// 获取系统用户头像的文件名
String objectName = "sysuser/" + userId + "/avatar/"
+ LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + StrUtil.SLASH
+ IdUtil.fastSimpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
fileService.uploadFile(file, objectName);
SysUser sysUser = new SysUser();
sysUser.setUserId(userId);
sysUser.setAvatar(objectName);
baseMapper.updateById(sysUser);
return objectName;
}
}

View File

@@ -46,7 +46,8 @@ xxl:
addresses: http://ballcat-job:8888/xxl-job-admin # xxl-job-admin 接口地址
executor:
port: 8081 #通讯端口
appName: ballcat-admin-job
appName: ballcat-admin-jobs
logretentiondays: 30 #日志保留天数
swagger:

View File

@@ -51,15 +51,12 @@ public class ApiAccessLogHandlerServiceImpl implements AccessLogHandlerService<A
.setMatchingPattern(String.valueOf(request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE)))
.setErrorMsg(Optional.ofNullable(myThrowable).map(Throwable::getMessage).orElse(null))
.setHttpStatus(response.getStatus())
.setReqParams(JSONUtil.toJsonStr(request.getParameterMap()))
.setReqBody(LogUtils.getRequestBody(request));
.setReqParams(JSONUtil.toJsonStr(request.getParameterMap()));
/*Optional.ofNullable(SecurityUtils.getSysUserDetails())
.map(SysUserDetails::getSysUser)
.ifPresent(x -> {
apiAccessLog.setUserId(x.getUserId());
apiAccessLog.setUsername(x.getUsername());
});*/
// 非文件上传请求记录body
if (!LogUtils.isMultipartContent(request)){
apiAccessLog.setReqBody(LogUtils.getRequestBody(request));
}
return apiAccessLog;
}

View File

@@ -45,6 +45,7 @@ xxl:
executor:
port: 9091 #通讯端口
appName: ballcat-api-job
logretentiondays: 30 #日志保留天数
swagger:

View File

@@ -45,7 +45,11 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-core</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -59,6 +59,11 @@ public enum ResultStatus {
*/
MALICIOUS_REQUEST(90002, "Malicious Request"),
/**
* 文件上传异常
*/
FILE_UPLOAD_ERROR(90003, "File Upload Error"),
/**
* 未知异常
*/

View File

@@ -17,10 +17,6 @@
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>

View File

@@ -2,6 +2,7 @@ package com.hccake.ballcat.commom.log.access.filter;
import cn.hutool.core.util.StrUtil;
import com.hccake.ballcat.commom.log.access.service.AccessLogHandlerService;
import com.hccake.ballcat.commom.log.util.LogUtils;
import com.hccake.ballcat.common.core.filter.RepeatBodyRequestWrapper;
import lombok.AllArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -40,8 +41,16 @@ public class AccessLogFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
return;
}
// 包装request以保证可以重复读取body
RepeatBodyRequestWrapper requestWrapper = new RepeatBodyRequestWrapper(request);
// 包装request以保证可以重复读取body 但不对文件上传请求body进行处理
HttpServletRequest requestWrapper;
if (LogUtils.isMultipartContent(request)) {
requestWrapper = request;
}else {
requestWrapper = new RepeatBodyRequestWrapper(request);
}
// 开始时间
Long startTime = System.currentTimeMillis();
@@ -68,4 +77,7 @@ public class AccessLogFilter extends OncePerRequestFilter {
}
}

View File

@@ -52,4 +52,20 @@ public class LogUtils {
return body;
}
/**
* 判断是否是multipart/form-data请求
*
* @param request
* @return
*/
public static boolean isMultipartContent(HttpServletRequest request) {
if (!HttpMethod.POST.name().equals(request.getMethod().toUpperCase())) {
return false;
}
//获取Content-Type
String contentType = request.getContentType();
return (contentType != null) && (contentType.toLowerCase().startsWith("multipart/"));
}
}

View File

@@ -1,4 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hccake.ballcat.commom.log.operation.OperationLogAutoConfiguration,\
com.hccake.ballcat.commom.log.access.AccessLogAutoConfiguration,\
com.hccake.ballcat.commom.log.error.ErrorLogAutoConfiguration

View File

@@ -0,0 +1,25 @@
<?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">
<parent>
<artifactId>ballcat-common</artifactId>
<groupId>com.hccake</groupId>
<version>0.0.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ballcat-common-storage</artifactId>
<dependencies>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,32 @@
package com.hccake.ballcat.commom.storage;
import com.hccake.ballcat.commom.storage.aliyun.AliyunOssClient;
import lombok.AllArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**
* oss 自动配置类
* @author Hccake
*/
@AllArgsConstructor
@EnableConfigurationProperties({FileStorageProperties.class})
public class FileStorageAutoConfiguration {
private final FileStorageProperties properties;
@Bean
@ConditionalOnMissingBean(FileStorageClient.class)
@ConditionalOnProperty(name = "file.storage.type", havingValue = "aliyun")
FileStorageClient aliyunOssClient() {
return new AliyunOssClient(
properties.getEndpoint(),
properties.getAccessKey(),
properties.getAccessSecret(),
properties.getBucketName()
);
}
}

View File

@@ -0,0 +1,27 @@
package com.hccake.ballcat.commom.storage;
import java.io.InputStream;
/**
* @author Hccake
* @version 1.0
* @date 2020/1/7 16:28
*/
public interface FileStorageClient {
/**
* 文件上传
* @param objectName 存储对象名称
* @param inputStream 文件输入流
* @return 文件相对路径
*/
String putObject(String objectName, InputStream inputStream);
/**
* 文件删除
* @param objectName 存储对象名称
*/
void deleteObject(String objectName);
}

View File

@@ -0,0 +1,34 @@
package com.hccake.ballcat.commom.storage;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @author Hccake
* @version 1.0
* @date 2019/7/16 15:34
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "file.storage")
public class FileStorageProperties {
/**
* endpoint 服务地址 http://oss-cn-qingdao.aliyuncs.com
*/
private String endpoint;
/**
* 密钥key
*/
private String accessKey;
/**
* 密钥Secret
*/
private String accessSecret;
/**
* bucketName
*/
private String bucketName;
}

View File

@@ -0,0 +1,69 @@
package com.hccake.ballcat.commom.storage.aliyun;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.hccake.ballcat.commom.storage.FileStorageClient;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import java.io.InputStream;
/**
* @author Hccake
* @version 1.0
* @date 2019/7/16 15:45
*/
@RequiredArgsConstructor
public class AliyunOssClient implements FileStorageClient, InitializingBean, DisposableBean {
private final String endpoint;
private final String accessKey;
private final String accessSecret;
private final String bucketName;
private OSS client;
/**
* 文件上传
*
* @param objectName 存储对象名称
* @param inputStream 文件输入流
* @return
*/
@Override
public String putObject(String objectName, InputStream inputStream) {
client.putObject(bucketName, objectName, inputStream);
return objectName;
}
/**
* 文件删除
* @param objectName 存储对象名称
*/
@Override
public void deleteObject(String objectName){
if (client.doesObjectExist(bucketName, objectName)) {
client.deleteObject(bucketName, objectName);
}
}
@Override
public void afterPropertiesSet() {
Assert.hasText(endpoint, "endpoint 为空");
Assert.hasText(accessKey, "Oss accessKey为空");
Assert.hasText(accessSecret, "Oss accessSecret为空");
client = new OSSClientBuilder().build(endpoint, accessKey, accessSecret);
}
@Override
public void destroy() {
if (this.client != null) {
this.client.shutdown();
}
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.hccake.ballcat.commom.storage.FileStorageAutoConfiguration

View File

@@ -20,6 +20,7 @@
<module>ballcat-common-conf</module>
<module>ballcat-common-job</module>
<module>ballcat-common-swagger</module>
<module>ballcat-common-storage</module>
</modules>

14
pom.xml
View File

@@ -26,7 +26,7 @@
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-boot.version>2.2.1.RELEASE</spring-boot.version>
<spring-boot.version>2.2.2.RELEASE</spring-boot.version>
<spring-platform.version>Cairo-SR8</spring-platform.version>
<org.mapstruct.version>1.3.0.Final</org.mapstruct.version>
@@ -37,6 +37,7 @@
<xxl-job.version>2.1.0</xxl-job.version>
<spring-boot-admin.version>2.2.0</spring-boot-admin.version>
<spring-security-oauth2.version>2.3.6.RELEASE</spring-security-oauth2.version>
<oss.aliyun.version>3.8.0</oss.aliyun.version>
</properties>
@@ -103,6 +104,11 @@
<artifactId>ballcat-common-swagger</artifactId>
<version>${ballcat.version}</version>
</dependency>
<dependency>
<groupId>com.hccake</groupId>
<artifactId>ballcat-common-storage</artifactId>
<version>${ballcat.version}</version>
</dependency>
<!--swagger注解-->
<dependency>
<groupId>io.swagger</groupId>
@@ -127,6 +133,12 @@
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--aliyun oss sdk-->
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>${oss.aliyun.version}</version>
</dependency>
<!-- spring-security oauth -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>