1. oss 完善 minio

2. todo 待添加水印 以及 权限控制, 同时 log 日志待完善
This commit is contained in:
ant 2024-06-04 15:47:01 +08:00
parent 7ee125c479
commit 88b5dfc714
29 changed files with 620 additions and 307 deletions

View File

@ -32,6 +32,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>

View File

@ -0,0 +1,27 @@
package com.chushang.common.core.config;
import cn.hutool.core.io.FileUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.MultipartConfigFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.MultipartConfigElement;
@Configuration
public class MultipartConfig {
@Value("${spring.servlet.multipart.location:/tmp}")
private String tmpLocation;
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
// 若没有该目录则创建此处使用了第三方插件的FileUtil各位可以使用原生File实现该逻辑
if (!FileUtil.exist(tmpLocation)) {
FileUtil.mkdir(tmpLocation);
}
factory.setLocation(tmpLocation);
return factory.createMultipartConfig();
}
}

View File

@ -118,21 +118,17 @@ public class DateUtils {
return format(localDateTime, dtf);
}
/** 时间格式(yyyy-MM-dd) */
public final static String DATE_PATTERN = "yyyy-MM-dd";
public static LocalDate toLocalDate(long secondTimestamp){
return Instant.ofEpochSecond(secondTimestamp).atZone(ZoneId.systemDefault()).toLocalDate();
}
public static LocalDate parseDate(String date)
{
return parseDate(date,DATE_PATTERN);
return parseDate(date,DatePattern.NORM_DATE_PATTERN);
}
public static String format(LocalDate date){
return format(date,DATE_PATTERN);
return format(date,DatePattern.NORM_DATE_PATTERN);
}
public static String format(LocalDate date,String pattern){
@ -157,7 +153,7 @@ public class DateUtils {
* @return 返回yyyy-MM-dd格式日期
*/
public static String format(Date date) {
return format(date, DATE_PATTERN);
return format(date, DatePattern.NORM_DATE_PATTERN);
}
/**
@ -193,10 +189,10 @@ public class DateUtils {
ZonedDateTime endDateTime = parseDate(endDateStr).atStartOfDay().atZone(clock.getZone());
Set<String> timeSet = new HashSet<>();
for (ZonedDateTime start = startDateTime; endDateTime.isAfter(start); start = start.plusDays(1L)){
timeSet.add(format(start, DateUtils.DATE_PATTERN));
timeSet.add(format(start, DatePattern.NORM_DATE_PATTERN));
}
// 最后需要添加一下 结束日期
timeSet.add(format(endDateTime, DateUtils.DATE_PATTERN));
timeSet.add(format(endDateTime, DatePattern.NORM_DATE_PATTERN));
return timeSet;
}
@ -261,11 +257,9 @@ public class DateUtils {
return date.format(DateTimeFormatter.ofPattern("yyyy_MM_dd"));
}
public final static String ISO_DATE_TIME_PATTERN = "yyyy-MM-dd'T'HH:mm:ss'Z'";
public final static String DATE_PATTERN_2 = "yyyyMMdd";
public static String isoFormat(LocalDateTime dateTime)
{
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(ISO_DATE_TIME_PATTERN);
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(DatePattern.UTC_PATTERN);
return dateTime.format(dtf);
}
@ -284,7 +278,7 @@ public class DateUtils {
* @param day 日期
*/
private static Date getWeekDate(String day) {
Date date = stringToDate(day, DATE_PATTERN);
Date date = stringToDate(day, DatePattern.NORM_DATE_PATTERN);
Calendar cal = Calendar.getInstance();
cal.setTime(date);
// 获得当前日期是一个星期的第几天

View File

@ -1,7 +1,11 @@
package com.chushang.common.core.util;
import cn.hutool.core.io.FileUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.digest.MD5;
import lombok.SneakyThrows;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -9,6 +13,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
@ -149,38 +154,22 @@ public class FileUtils extends FileUtil {
}
public static String getMd5(File f) {
try (FileInputStream fis = new FileInputStream(f)){
try (FileInputStream fis = new FileInputStream(f)) {
return getMd5(fis);
}catch (Exception e){
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public static String getMd5(InputStream is) throws IOException, NoSuchAlgorithmException {
MessageDigest mdInst = MessageDigest.getInstance("MD5");
byte[] buffer = new byte[1024];
while (is.read(buffer) != -1) {
mdInst.update(buffer);
}
return binaryToHexString(mdInst.digest());
}
private static String binaryToHexString(byte[] result) {
StringBuilder digestHexStr = new StringBuilder();
for (int i = 0; i < 16; ++i) {
char[] ob = new char[]{Digit[result[i] >>> 4 & 15], Digit[result[i] & 15]};
String s = new String(ob);
digestHexStr.append(s);
}
return digestHexStr.toString();
public static String getMd5(InputStream is) {
MD5 md5 = SecureUtil.md5();
return md5.digestHex(is);
}
public static boolean isText(File file) {
boolean isText = true;
try (FileInputStream fin = new FileInputStream(file)){
try (FileInputStream fin = new FileInputStream(file)) {
long len = file.length();
for (int j = 0; j < (int) len; ++j) {
int t = fin.read();
@ -189,18 +178,44 @@ public class FileUtils extends FileUtil {
break;
}
}
}catch (Exception e){
} catch (Exception e) {
log.error("isText", e);
}
return isText;
}
public static boolean isText(InputStream is) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
// 读取一行,, 判断这一行是否有 不可打印字符
String line = reader.readLine();
while (line != null) {
for (char c : line.toCharArray()) {
// 检查是否包含 可打印字符, 包含时, 就代表其是 text
return !(!Character.isISOControl(c) && !Character.isWhitespace(c));
}
line = reader.readLine();
}
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static String getFileCharset(File sourceFile) {
String charset = "GBK";
try (InputStream is = new FileInputStream(sourceFile)) {
charset = getStreamCharset(is);
} catch (Exception e) {
log.error("", e);
return charset;
}
return charset;
}
public static String getStreamCharset(InputStream is) {
String charset = "GBK";
byte[] first3Bytes = new byte[3];
String var7;
try (FileInputStream fis = new FileInputStream(sourceFile);
BufferedInputStream bis = new BufferedInputStream(fis)){
try (BufferedInputStream bis = new BufferedInputStream(is)) {
boolean checked = false;
bis.mark(0);
int read = bis.read(first3Bytes, 0, 3);
@ -228,7 +243,7 @@ public class FileUtils extends FileUtil {
read = bis.read();
continue label248;
}
} while(224 > read || read > 239);
} while (224 > read || read > 239);
read = bis.read();
if (128 <= read && read <= 191) {
@ -238,19 +253,21 @@ public class FileUtils extends FileUtil {
}
}
break;
} while(128 <= read && read <= 191);
} while (128 <= read && read <= 191);
}
bis.close();
return charset;
}
var7 = charset;
} catch (Exception e) {
e.printStackTrace();
return charset;
}
return var7;
return charset;
}
public static String readFileToString(File file, Charset charsetName) throws IOException {
return IOUtils.toString(() -> Files.newInputStream(file.toPath()), Charsets.toCharset(charsetName));
}
}

View File

@ -1,3 +1,4 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.chushang.common.core.util.SpringUtils, \
com.chushang.common.core.handler.GlobalExceptionHandler
com.chushang.common.core.handler.GlobalExceptionHandler, \
com.chushang.common.core.config.MultipartConfig

View File

@ -121,7 +121,7 @@ public class SysLogAspect {
// 将导出的response 过滤掉
List<Object> argList =
Arrays.stream(args)
.filter(arg -> !(arg instanceof MultipartFile) && !(arg instanceof HttpServletResponse) && !(arg instanceof HttpServletRequest))
.filter(arg -> !(arg instanceof MultipartFile) && !(arg instanceof HttpServletResponse) && !(arg instanceof HttpServletRequest) && !(arg instanceof MultipartFile[]))
.collect(Collectors.toList());
sysLogEntity.setParams(JacksonUtils.toJSONString(argList));
}

View File

@ -1,8 +1,11 @@
package com.chushang.common.mybatis.enums;
import lombok.Getter;
/**
* 比较符
*/
@Getter
public enum Operator {
/**
@ -35,9 +38,13 @@ public enum Operator {
LIMIT_ONE("limit 1")
;
/**
* 比较符
* -- GETTER --
* 获取比较符
*
* @return {@link String}
*/
private final String character;
@ -45,11 +52,4 @@ public enum Operator {
this.character = character;
}
/**
* 获取比较符
* @return {@link String}
*/
public String getCharacter() {
return character;
}
}

View File

@ -29,5 +29,10 @@
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
<!-- 权限控制 -->
<dependency>
<groupId>com.chushang</groupId>
<artifactId>chushang-common-data-scope</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -9,7 +9,7 @@ import java.io.Serial;
import java.io.Serializable;
@Configuration
@ConfigurationProperties(prefix = "oss")
@ConfigurationProperties(prefix = "config.oss")
@Data
public class UploadConfig implements Serializable {
@Serial

View File

@ -2,16 +2,21 @@ package com.chushang.oss.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.chushang.common.mybatis.base.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@EqualsAndHashCode(callSuper = true)
@Data
@TableName("tb_oss_source_info")
@TableName("tb_file_source_info")
@NoArgsConstructor
@AllArgsConstructor
public class FileSourceInfo extends BaseEntity{
@TableId(value = "fid", type = IdType.INPUT)
private String fid;
/**
@ -65,13 +70,33 @@ public class FileSourceInfo extends BaseEntity{
*/
@TableField("storage_region")
private String storageRegion;
/**
* 文件存储位置, minio ali local
*/
@TableField("storage")
private String storage;
/**
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
protected String createBy;
private String createBy;
/**
* 部门Id, 只能看到权限下的图片
*/
@TableField(value = "dept_id")
private Long deptId;
/**
* 修改人
*/
@TableField(
value = "update_by"
)
private String updateBy;
public FileSourceInfo(String fid){
this.fid = fid;
}
}

View File

@ -17,6 +17,11 @@
<groupId>com.chushang</groupId>
<artifactId>chushang-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId>
<version>2.8.0</version>
</dependency>
</dependencies>
<build>

View File

@ -2,11 +2,14 @@ package com.chushang.oss;
import com.chushang.common.core.enums.AppStartType;
import com.chushang.common.feign.annotation.EnableOnnFeignClients;
import com.chushang.common.feign.annotation.EnableTransferFeign;
import com.chushang.security.annotation.EnableCustomConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @auther: zhao
@ -14,8 +17,10 @@ import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
*/
@EnableDiscoveryClient
@EnableOnnFeignClients
@SpringBootApplication
//@EnableTransferFeign
@SpringBootApplication(scanBasePackages = {"com.chushang.**"})
@EnableTransferFeign
@EnableCustomConfig
@EnableTransactionManagement
public class OssApplication {
private final static Logger log = LoggerFactory.getLogger(OssApplication.class);

View File

@ -1,62 +1,92 @@
package com.chushang.oss.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import com.chushang.common.core.util.FileUtils;
import com.chushang.common.core.web.AjaxResult;
import com.chushang.common.core.web.Result;
import com.chushang.common.log.annotation.SysLog;
import com.chushang.common.log.enums.BusinessType;
import com.chushang.oss.entity.FileSourceInfo;
import com.chushang.oss.entity.dto.OcrDTO;
import com.chushang.oss.entity.vo.FileSourceVo;
import com.chushang.oss.enums.OcrTypeEnum;
import com.chushang.oss.service.FileSourceService;
import com.chushang.oss.service.OcrService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RestController
@RequestMapping(value = "/file")
public class FileController {
@Resource
FileSourceService fileSourceService;
// @Resource
// OcrService ocrService;
@Resource
OcrService ocrService;
/**
* todo
* 文件上传
*/
@SysLog(value = "文件上传", businessType = BusinessType.INSERT)
@PostMapping(value = "/upload")
public AjaxResult uploadFile(@RequestParam(value = "file") MultipartFile file,
@RequestParam(value = "ocrType", required = false) String ocrType) {
FileSourceVo vo = BeanUtil.copyProperties(fileSourceService.addFile(file, "ip"), FileSourceVo.class);
// if (StrUtil.isNotEmpty(ocrType)) {
// OcrDTO ocr = new OcrDTO();
// OcrTypeEnum ocrTypeEnum = OcrTypeEnum.findByCode(ocrType);
// if (ocrTypeEnum != OcrTypeEnum.NONE) {
// ocr.setOcrType(ocrTypeEnum);
// ocr.setImgPath(vo.getFilePath());
//// Result<JSONObject> ocrInfo = ocrService.ocr(ocr);
//// if (ocrInfo.isSuccess()) {
//// vo.setOcr(ocrInfo.getData());
//// }
// }
// }
return AjaxResult.success(vo);
public AjaxResult uploadFile(@RequestParam(value = "files") MultipartFile[] files,
@RequestParam(value = "ocrType", required = false) String ocrType)
{
List<FileSourceVo> result = new ArrayList<>();
for (MultipartFile file : files) {
FileSourceVo info = fileSourceService.addFile(file);
if (StrUtil.isNotEmpty(ocrType)) {
OcrDTO ocr = new OcrDTO();
OcrTypeEnum ocrTypeEnum = OcrTypeEnum.findByCode(ocrType);
if (ocrTypeEnum != OcrTypeEnum.NONE) {
ocr.setOcrType(ocrTypeEnum);
ocr.setImgPath(info.getFilePath());
Result<JSONObject> ocrInfo = ocrService.ocr(ocr);
if (ocrInfo.isSuccess()) {
info.setOcr(ocrInfo.getData());
}
}
}
result.add(info);
}
return AjaxResult.success(result);
}
@GetMapping(value="/{fid}/preview")
public void preview(@PathVariable String fid, HttpServletResponse response)
{
log.info("[OSS]preview Request --> param:{}",fid);
fileSourceService.getFileStream(fid, response, true);
}
@SysLog(value = "文件下载", businessType = BusinessType.OTHER)
@GetMapping(value = "/download/{fid}")
public AjaxResult downloadFile(@PathVariable String fid) {
return AjaxResult.success();
public void downloadFile(@PathVariable String fid, HttpServletResponse response) {
log.info("[OSS]preview Request --> param:{}",fid);
fileSourceService.getFileStream(fid, response, false);
}
@SysLog(value = "文件", businessType = BusinessType.DELETE)
@DeleteMapping(value = "/del/{fid}")
public AjaxResult delFile() {
public AjaxResult delFile(@PathVariable String fid) {
fileSourceService.delFile(fid);
return AjaxResult.success();
}
@SysLog(value = "文件", businessType = BusinessType.DELETE)
@DeleteMapping(value = "/del/batch")
public AjaxResult delFile(@RequestBody List<String> fids) {
fileSourceService.delFileBatch(fids);
return AjaxResult.success();
}

View File

@ -1,15 +1,39 @@
package com.chushang.oss.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chushang.common.core.exception.ResultException;
import com.chushang.common.core.util.DateUtils;
import com.chushang.common.core.util.FileUtils;
import com.chushang.common.core.util.IPUtils;
import com.chushang.common.core.util.ServletUtils;
import com.chushang.common.mybatis.enums.Operator;
import com.chushang.oss.config.UploadConfig;
import com.chushang.oss.entity.FileSourceInfo;
import com.chushang.oss.entity.vo.FileSourceVo;
import com.chushang.oss.mapper.FileSourceMapper;
import com.chushang.oss.service.OssService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.tika.Tika;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* @auther: zhao
@ -19,14 +43,123 @@ import javax.annotation.Resource;
@Service
public class FileSourceService
extends ServiceImpl<FileSourceMapper, FileSourceInfo>
implements IService<FileSourceInfo>
{
implements IService<FileSourceInfo> {
@Resource
OssService ossService;
@Resource
RedissonClient redissonClient;
@Value("${config.oss.storage}")
private String storage;
private String generateFid() {
String uuid = IdUtil.simpleUUID();
uuid = Integer.toHexString(Integer.parseInt(DateUtils.format(ZonedDateTime.now(), DatePattern.PURE_DATE_PATTERN))) + uuid;
return uuid;
}
/**
* 上传 到minio 或者 oss 服务中
*
* @param file 文件
*/
public FileSourceVo addFile(MultipartFile file) {
String ip = IPUtils.clientIp(ServletUtils.getRequest());
String fid = generateFid();
String fName = file.getOriginalFilename();
Tika t = new Tika();
String mimetype = null;
try {
mimetype = t.detect(file.getBytes());
if (fName.indexOf(".") < 0) {//如果文件名字没有后缀
String ext = MimeTypes.getDefaultMimeTypes().forName(mimetype).getExtension();
fName += ext;
}
} catch (IOException ignore) {
} catch (MimeTypeException e) {
log.error("文件后缀识别失败");
}
FileSourceInfo info = new FileSourceInfo();
try {
info.setFid(fid);
info.setName(fName);
// mimeType 还不是 后缀 类型
info.setMimeType(mimetype);
info.setSize(file.getSize());
String md5 = FileUtils.getMd5(file.getInputStream());
info.setMd5(md5);
String path = ossService.getPrefixPath(md5,fName);
// 不带https 的路径
info.setPath(path);
info.setStorage(storage);
info.setUploadIp(ip);
// 上传到 oss 或者 minio
String realPath = ossService.upload(file.getInputStream(), info);
// 带https 的路径
info.setRealPath(realPath);
RMap<String, FileSourceInfo> map = redissonClient.getMap("OSS-Cache");
map.put(fid, info);
// 入库
save(info);
map.remove(info.getFid());
} catch (Exception e) {
log.error("上传文件失败", e);
}
FileSourceVo vo = new FileSourceVo();
vo.setFid(info.getFid());
vo.setFilePath(info.getRealPath());
vo.setName(fName);
vo.setSize(info.getSize());
return vo;
}
public FileSourceInfo addFile(MultipartFile file, String ip) {
return null;
public void getFileStream(String fid, HttpServletResponse response, boolean convertText) {
// 文件信息
FileSourceInfo info = getOne(new LambdaQueryWrapper<FileSourceInfo>()
.eq(FileSourceInfo::getFid, fid)
.last(Operator.LIMIT_ONE.getCharacter()));
if (ObjUtil.isEmpty(info)) return;
InputStream fileStream = ossService.getFileStream(info);
response.setContentType(info.getMimeType());
try (OutputStream os = response.getOutputStream()){
if (FileUtils.isText(fileStream) && convertText){
//是文本则读取内容输出
OutputStreamWriter osw = new OutputStreamWriter(os);
osw.write(IOUtils.toString(fileStream, FileUtils.getStreamCharset(fileStream)));
osw.flush();
}else {
byte[] readByte = new byte[1024];
int len = 0;
while ((len = fileStream.read(readByte, 0, readByte.length)) != -1) {
os.write(readByte, 0, len);
}
os.flush();
}
}catch (Exception e){
log.error("文件流获取失败");
}
}
public void delFile(String fid) {
// 文件信息
FileSourceInfo info = getOne(new LambdaQueryWrapper<FileSourceInfo>()
.eq(FileSourceInfo::getFid, fid)
.last(Operator.LIMIT_ONE.getCharacter()));
if (ObjUtil.isEmpty(info)) return;
ossService.delFile(info.getPath());
}
public void delFileBatch(List<String> fids) {
// 文件信息
List<FileSourceInfo> list = list(new LambdaQueryWrapper<FileSourceInfo>()
.in(FileSourceInfo::getFid, fids));
if (CollectionUtil.isEmpty(list)) return;
List<String> strings
= ossService.delFileBatch(list.stream().map(FileSourceInfo::getPath).toList());
if (CollectionUtil.isNotEmpty(strings)){
String key = String.join(",", strings);
throw new ResultException("以下文件未删除: [" + key + "].");
}
}
}

View File

@ -0,0 +1,9 @@
package com.chushang.oss.service;
import com.alibaba.fastjson2.JSONObject;
import com.chushang.common.core.web.Result;
import com.chushang.oss.entity.dto.OcrDTO;
public interface OcrService {
Result<JSONObject> ocr(OcrDTO ocr);
}

View File

@ -1,71 +1,37 @@
package com.chushang.oss.service;
import com.chushang.common.core.util.IdUtils;
import com.chushang.oss.entity.FileSourceInfo;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
public interface OssService {
default String getSuffixPath(String suffix){
default String getPrefixPath(String fileName){
String nanoID = IdUtils.getId(32);
return getSuffixPath(suffix, nanoID);
}
/**
* 文件路径
* @param filename 文件名称
* @param suffix 文件类型
* @return 返回上传路径
*/
default String getSuffixPath(String suffix, String filename) {
return filename + suffix;
return getPrefixPath(nanoID, fileName);
}
default String getPrefixPath(String prefix, String filename){
return prefix + "/" + filename;
}
default String getSuffixPath(String prefix, String suffix, String filename)
{
//文件路径
return prefix + "/" + filename + suffix;
}
/**
* 文件上传
* @param data 文件字节数组
* @param path 文件路径包含文件名
* @param info 文件信息
* @return 返回http地址
*/
String upload(byte[] data, String path);
default String upload(byte[] data, FileSourceInfo info){
return upload(new ByteArrayInputStream(data), info);
}
String getPrefixPath(String prefix, String filename);
/**
* 文件上传
* @param data 文件字节数组
* @param suffix 文件类型
* @return 返回http地址
* @param inputStream 文件字节数组
* @param info 文件信息
* @return 返回http地址
*/
String uploadSuffix(byte[] data, String suffix);
String uploadSuffix(byte[] data, String suffix, String filename);
String uploadPrefix(byte[] data,String prefix, String suffix, String filename);
String uploadPrefix(byte[] data,String prefix, String filename);
/**
* 文件上传
* @param inputStream 字节流
* @param path 文件路径包含文件名
* @return 返回http地址
*/
String upload(InputStream inputStream, String path);
/**
* 文件上传
* @param inputStream 字节流
* @param suffix 文件类型
* @return 返回http地址
*/
String uploadSuffix(InputStream inputStream, String suffix);
String upload(InputStream inputStream, FileSourceInfo info);
/**
* 删除 cloud 中的文件
@ -78,4 +44,6 @@ public interface OssService {
*/
List<String> delFileBatch(List<String> keys);
InputStream getFileStream(FileSourceInfo info);
}

View File

@ -5,6 +5,7 @@ import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.DeleteObjectsRequest;
import com.aliyun.oss.model.DeleteObjectsResult;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.Payer;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chushang.common.core.exception.ResultException;
@ -21,7 +22,9 @@ import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
@ -51,54 +54,25 @@ public class AliServiceImpl implements OssService {
oss.setBucketRequestPayment(config.getBucketName(), payer);
}
@Override
public String upload(byte[] data, String path) {
public String getPrefixPath(String prefix, String filename){
String aliyunPrefix = config.getPrefix();
if (StringUtils.isNotEmpty(aliyunPrefix)){
path = aliyunPrefix + "/" + path;
prefix = aliyunPrefix + "/" + prefix + "/" + filename;
}else {
prefix = prefix + "/" + filename;
}
return upload(new ByteArrayInputStream(data), path);
return prefix;
}
/**
* path
* @param inputStream 字节流
* @param path 文件路径包含文件名
*/
@Override
public String upload(InputStream inputStream, String path) {
public String upload(InputStream inputStream, FileSourceInfo info) {
String path = info.getPath();
try {
oss.putObject(config.getBucketName(), path, inputStream);
} catch (Exception e){
throw new ResultException("上传文件失败,请检查配置信息", e);
}
return UploadConfig.getDomain() + "/" + path;
}
@Override
public String uploadSuffix(byte[] data, String suffix) {
return upload(data, getSuffixPath(suffix));
}
@Override
public String uploadSuffix(byte[] data, String suffix, String filename) {
return upload(data, getSuffixPath(suffix, filename));
}
@Override
public String uploadPrefix(byte[] data, String prefix, String suffix, String filename) {
return upload(data, getSuffixPath(prefix, suffix, filename));
}
@Override
public String uploadPrefix(byte[] data, String prefix, String filename) {
return upload(data, getPrefixPath(prefix, filename));
}
@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return upload(inputStream, getSuffixPath(suffix));
return path;
}
@Override
@ -111,6 +85,7 @@ public class AliServiceImpl implements OssService {
}
public List<String> delFileBatch(List<String> keys){
if (CollectionUtil.isEmpty(keys)) return new ArrayList<>();
try {
// 批量删除
DeleteObjectsRequest objectsRequest = new DeleteObjectsRequest(config.getBucketName());
@ -120,10 +95,23 @@ public class AliServiceImpl implements OssService {
objectsRequest.setEncodingType("url");
DeleteObjectsResult deleteObjectsResult = oss.deleteObjects(objectsRequest);
// 这些是成功删除对象的key
List<String> deletedObjects = deleteObjectsResult.getDeletedObjects();
if (CollectionUtil.isNotEmpty(deletedObjects)){
log.error("以下文件未从oss 中成功删除 : {}", deletedObjects);
return deletedObjects;
Iterator<String> iterator = keys.iterator();
List<String> results = new ArrayList<>();
while (iterator.hasNext()){
String key = iterator.next();
if (deletedObjects.contains(key)){
iterator.remove();
}else {
results.add(key);
}
}
if (CollectionUtil.isNotEmpty(results)){
log.error("以下文件未从oss 中成功删除 : {}", results);
return results;
}
} catch (Exception e){
throw new ResultException("删除文件失败,请检查配置信息", e);
@ -131,5 +119,11 @@ public class AliServiceImpl implements OssService {
return new ArrayList<>();
}
@Override
public InputStream getFileStream(FileSourceInfo info) {
OSSObject object = oss.getObject(config.getBucketName(), info.getPath());
return object.getObjectContent();
}
}

View File

@ -1,61 +0,0 @@
package com.chushang.oss.service.impl;
import com.chushang.oss.service.OssService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.util.List;
/**
* @auther: zhao
* @date: 2024/4/28 19:53
*/
@Service
@ConditionalOnExpression("'${config.oss.storage}'.equals('local')")
public class LocalServiceImpl implements OssService {
@Override
public String upload(byte[] data, String path) {
return "";
}
@Override
public String uploadSuffix(byte[] data, String suffix) {
return "";
}
@Override
public String uploadSuffix(byte[] data, String suffix, String filename) {
return "";
}
@Override
public String uploadPrefix(byte[] data, String prefix, String suffix, String filename) {
return "";
}
@Override
public String uploadPrefix(byte[] data, String prefix, String filename) {
return "";
}
@Override
public String upload(InputStream inputStream, String path) {
return "";
}
@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return "";
}
@Override
public void delFile(String path) {
}
@Override
public List<String> delFileBatch(List<String> keys) {
return List.of();
}
}

View File

@ -1,61 +1,215 @@
package com.chushang.oss.service.impl;
import cn.hutool.core.util.StrUtil;
import com.chushang.common.core.exception.ResultException;
import com.chushang.common.core.util.StringUtils;
import com.chushang.oss.config.UploadConfig;
import com.chushang.oss.entity.FileSourceInfo;
import com.chushang.oss.service.OssService;
import io.minio.*;
import io.minio.errors.*;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.module.ResolutionException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
* @auther: zhao
* @date: 2024/4/28 19:52
*/
@Slf4j
@Service
@ConditionalOnExpression("'$config.{oss.storage}'.equals('minio')")
@ConditionalOnExpression("'${config.oss.storage}'.equals('minio')")
public class MinioServiceImpl implements OssService {
private MinioClient minio;
@Resource
private UploadConfig config;
@PostConstruct
public void init() {
minio = MinioClient.builder().endpoint(config.getEndPoint())
.credentials(config.getAccessKey(), config.getSecretKey()).build();
try {
if (!minio.bucketExists(BucketExistsArgs.builder().bucket(config.getBucketName()).build())) {
createBucket();
}
} catch (Exception e) {
log.error("MinIO Storage Connection Fail");
}
}
@SneakyThrows
private void createBucket(){
String bucketName = config.getBucketName();
// 注意 minio通过nginx代理后无法判断桶是否存在 需要通过查询所有桶的形式
List<Bucket> buckets = minio.listBuckets();
if (buckets != null) {
Bucket bucket = buckets.stream().filter(b -> config.getBucketName().equals(b.name()))
.findAny().orElse(null);
if (bucket != null) {
log.info("------------- 初始化 minio 配置 -------------");
return;
}
}
log.warn("【{}】桶不存在,开始创建", bucketName);
minio.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
// 设置默认桶策略
String policy = StrUtil.format("{\n" +
" \"Version\": \"2012-10-17\",\n" +
" \"Statement\": [\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": {\n" +
" \"AWS\": [\n" +
" \"*\"\n" +
" ]\n" +
" },\n" +
" \"Action\": [\n" +
" \"s3:GetBucketLocation\"\n" +
" ],\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::{}\"\n" +
" ]\n" +
" },\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": {\n" +
" \"AWS\": [\n" +
" \"*\"\n" +
" ]\n" +
" },\n" +
" \"Action\": [\n" +
" \"s3:ListBucket\"\n" +
" ],\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::{}\"\n" +
" ],\n" +
" \"Condition\": {\n" +
" \"StringEquals\": {\n" +
" \"s3:prefix\": [\n" +
" \"*\"\n" +
" ]\n" +
" }\n" +
" }\n" +
" },\n" +
" {\n" +
" \"Effect\": \"Allow\",\n" +
" \"Principal\": {\n" +
" \"AWS\": [\n" +
" \"*\"\n" +
" ]\n" +
" },\n" +
" \"Action\": [\n" +
" \"s3:GetObject\"\n" +
" ],\n" +
" \"Resource\": [\n" +
" \"arn:aws:s3:::{}/**\"\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}", bucketName, bucketName, bucketName);
SetBucketPolicyArgs bucketPolicyArgs = SetBucketPolicyArgs.builder().bucket(bucketName).config(policy).build();
minio.setBucketPolicy(bucketPolicyArgs);
log.warn("【{}】桶创建完成", bucketName);
log.info("------------- 初始化 minio 配置 -------------");
}
@Override
public String upload(byte[] data, String path) {
return "";
public String getPrefixPath(String prefix, String filename) {
String minioPrefix = config.getPrefix();
if (StringUtils.isNotEmpty(minioPrefix)) {
prefix = minioPrefix + "/" + prefix + "/" + filename;
} else {
prefix = prefix + "/" + filename;
}
return prefix;
}
@Override
public String uploadSuffix(byte[] data, String suffix) {
return "";
public String upload(InputStream inputStream, FileSourceInfo info) {
try {
minio.putObject(PutObjectArgs.builder().bucket(config.getBucketName())
// 路径 object 为路径地址
.object(info.getPath())
.stream(inputStream, inputStream.available(), -1)
.contentType(info.getMimeType())
.build());
} catch (Exception e) {
log.error("上传MINio 异常", e);
}
// 需要添加上 bucket
return config.getEndPoint() + "/" + config.getBucketName() + "/" + info.getPath();
}
@Override
public String uploadSuffix(byte[] data, String suffix, String filename) {
return "";
}
@Override
public String uploadPrefix(byte[] data, String prefix, String suffix, String filename) {
return "";
}
@Override
public String uploadPrefix(byte[] data, String prefix, String filename) {
return "";
}
@Override
public String upload(InputStream inputStream, String path) {
return "";
}
@Override
public String uploadSuffix(InputStream inputStream, String suffix) {
return "";
}
@Override
public void delFile(String path) {
public void delFile(String fid) {
try {
minio.removeObject(RemoveObjectArgs.builder()
.bucket(config.getBucketName())
.object(fid)
.build());
} catch (Exception ig) {
log.error("删除失败");
}
}
@Override
public List<String> delFileBatch(List<String> keys) {
return List.of();
Iterable<Result<DeleteError>> results
= minio.removeObjects(RemoveObjectsArgs.builder()
.bucket(config.getBucketName())
.objects(keys.stream().map(DeleteObject::new).toList())
.build());
List<String> resultKey = new ArrayList<>();
results.forEach(result -> {
try {
DeleteError deleteError = result.get();
String s = deleteError.objectName();
resultKey.add(s);
} catch (Exception e) {
log.error("删除result error", e);
}
});
return resultKey;
}
@Override
public InputStream getFileStream(FileSourceInfo info) {
try {
GetObjectResponse object = minio.getObject(GetObjectArgs.builder()
.bucket(config.getBucketName())
.object(info.getPath())
.build());
return new ByteArrayInputStream(object.readAllBytes());
} catch (Exception e) {
log.error("获取minio object error", e);
throw new ResultException("获取文件失败");
}
}
}

View File

@ -0,0 +1,21 @@
package com.chushang.oss.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.chushang.common.core.web.Result;
import com.chushang.oss.entity.dto.OcrDTO;
import com.chushang.oss.service.OcrService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Slf4j
@Service
public class OcrServiceImpl implements OcrService {
/**
* 尚未接入ocr
*/
@Override
public Result<JSONObject> ocr(OcrDTO ocr) {
log.error("尚未接入ocr");
return Result.failed("尚未接入ocr");
}
}

View File

@ -16,9 +16,9 @@ spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
username: ${conf.jdbc.master.oss.username}
password: ${conf.jdbc.master.oss.password}
url: jdbc:mysql://${conf.jdbc.master.oss.host}:${conf.jdbc.master.oss.port}/${conf.jdbc.master.oss.database}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
username: ${config.jdbc.master.oss.username}
password: ${config.jdbc.master.oss.password}
url: jdbc:mysql://${config.jdbc.master.oss.host}:${config.jdbc.master.oss.port}/${config.jdbc.master.oss.database}?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
hikari:
# 最大线程池数量
maximum-pool-size: 30
@ -88,4 +88,3 @@ management:
logging:
config: classpath:logback-nacos.xml

View File

@ -12,7 +12,6 @@ spring:
config:
import:
-: "classpath:application.yml"
application:
name: @artifactId@
cloud:
@ -34,10 +33,8 @@ spring:
refresh-enabled: true
shared-configs:
# 此处不应当 走common 了, 每个羡慕应该有自己单独的 db.yaml 文件 redis 可以使用公共
- dataId: db-common.${spring.cloud.nacos.config.file-extension}
- dataId: application-common.${spring.cloud.nacos.config.file-extension}
group: ${spring.cloud.nacos.discovery.group}
refresh: ${spring.cloud.nacos.config.refresh-enabled}
profiles:
active: @profiles.active@
main:
allow-bean-definition-overriding: true

View File

@ -55,8 +55,7 @@ public class SysConfig extends BaseEntity
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
@ExcelProperty(value = "创建人", index = 5)
private String createBy;
@ -64,8 +63,7 @@ public class SysConfig extends BaseEntity
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
@ExcelProperty(value = "修改人", index = 6)
private String updateBy;

View File

@ -60,16 +60,14 @@ public class SysDept extends BaseEntity
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
private String createBy;
/**
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
private String updateBy;

View File

@ -79,16 +79,14 @@ public class SysDictData extends BaseEntity {
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
protected String createBy;
/**
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
protected String updateBy;

View File

@ -52,8 +52,7 @@ public class SysDictType extends BaseEntity
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
@ExcelProperty(value = "创建人", index = 4)
protected String createBy;
@ -61,8 +60,7 @@ public class SysDictType extends BaseEntity
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
@ExcelProperty(value = "修改人", index = 5)
protected String updateBy;

View File

@ -97,16 +97,14 @@ public class SysMenu extends BaseEntity {
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
protected String createBy;
/**
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
protected String updateBy;

View File

@ -56,8 +56,7 @@ public class SysPost extends BaseEntity
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
@ExcelProperty(value = "创建人", index = 4)
protected String createBy;
@ -65,8 +64,7 @@ public class SysPost extends BaseEntity
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
@ExcelProperty(value = "修改人", index = 5)
protected String updateBy;

View File

@ -91,16 +91,14 @@ public class SysRole extends BaseEntity {
* 创建人
*/
@TableField(
value = "create_by",
updateStrategy = FieldStrategy.NEVER
value = "create_by"
)
protected String createBy;
/**
* 修改人
*/
@TableField(
value = "update_by",
insertStrategy = FieldStrategy.NEVER
value = "update_by"
)
protected String updateBy;
/**