From f8a8474f0f422f6ad8e584391c58de5c0e2fdafd Mon Sep 17 00:00:00 2001 From: zhugongping Date: Tue, 29 Sep 2026 09:55:45 +0800 Subject: [PATCH] =?UTF-8?q?OSS=EF=BC=9A=E5=9B=BE=E7=89=87=E3=80=81?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=85=A5=E5=AF=B9?= =?UTF-8?q?=E8=B1=A1=E5=AD=98=E5=82=A8minio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/common/CommonController.java | 86 ++++-- .../system/SysProfileController.java | 12 +- .../common/utils/file/FileUploadUtils.java | 18 +- fastbee-open-api/pom.xml | 6 + .../goview/GoviewProjectController.java | 29 +- .../controller/media/SipDeviceController.java | 4 +- .../controller/tool/ToolController.java | 36 +-- .../java/com/fastbee/oss/core/OssClient.java | 266 ------------------ .../com/fastbee/oss/entity/OssProperties.java | 58 ---- .../com/fastbee/oss/enums/OssBizPath.java | 76 +++++ .../com/fastbee/oss/service/OssClient.java | 24 +- .../oss/service/OssStorageService.java | 203 +++++++++++++ .../report/executor/ReportAsyncExecutor.java | 3 +- .../com/fastbee/sip/util/ZlmApiUtils.java | 3 +- sql/fastbee-report.sql | 7 +- yack-web/src/components/FileUpload/index.vue | 5 +- yack-web/src/components/ImageUpload/index.vue | 9 +- yack-web/src/store/modules/user.js | 6 +- yack-web/src/utils/resource-url.js | 36 +++ .../views/system/user/profile/userAvatar.vue | 4 +- 20 files changed, 475 insertions(+), 416 deletions(-) delete mode 100644 fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/core/OssClient.java delete mode 100644 fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/entity/OssProperties.java create mode 100644 fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/enums/OssBizPath.java create mode 100644 fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssStorageService.java create mode 100644 yack-web/src/utils/resource-url.js diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java index 733e3549..aed485f5 100644 --- a/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/common/CommonController.java @@ -1,5 +1,6 @@ package com.fastbee.web.controller.common; +import cn.hutool.core.io.IoUtil; import com.fastbee.common.config.RuoYiConfig; import com.fastbee.common.constant.Constants; import com.fastbee.common.core.domain.AjaxResult; @@ -7,8 +8,11 @@ import com.fastbee.common.utils.MessageUtils; import com.fastbee.common.utils.StringUtils; import com.fastbee.common.utils.file.FileUploadUtils; import com.fastbee.common.utils.file.FileUtils; +import com.fastbee.common.utils.file.MimeTypeUtils; import com.fastbee.common.utils.file.QRCodeUtils; -import com.fastbee.framework.config.ServerConfig; +import com.fastbee.oss.entity.UploadResult; +import com.fastbee.oss.enums.OssBizPath; +import com.fastbee.oss.service.OssStorageService; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import io.swagger.annotations.Api; @@ -26,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; @@ -41,13 +46,14 @@ import java.util.List; public class CommonController { private static final Logger log = LoggerFactory.getLogger(CommonController.class); private static final String FILE_DELIMETER = ","; + @Autowired - private ServerConfig serverConfig; + private OssStorageService ossStorageService; /** * 通用下载请求 * - * @param fileName 文件名称 + * @param fileName 文件名称,可为对象存储地址或本地相对路径 * @param delete 是否删除 */ @ApiOperation("文件下载") @@ -57,13 +63,23 @@ public class CommonController { if (!FileUtils.checkAllowDownload(fileName)) { throw new Exception(StringUtils.format(MessageUtils.message("download.filename.not.valid"), fileName)); } - String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); - String filePath = RuoYiConfig.getDownloadPath() + fileName; - + String realFileName = resolveDownloadName(fileName); response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); FileUtils.setAttachmentResponseHeader(response, realFileName); + + if (ossStorageService.isOssUrl(fileName)) { + try (InputStream inputStream = ossStorageService.open(fileName)) { + IoUtil.copy(inputStream, response.getOutputStream()); + } + if (Boolean.TRUE.equals(delete)) { + ossStorageService.delete(fileName); + } + return; + } + // 历史遗留的本地磁盘资源,保留原读取链路 + String filePath = RuoYiConfig.getDownloadPath() + fileName; FileUtils.writeBytes(filePath, response.getOutputStream()); - if (delete) { + if (Boolean.TRUE.equals(delete)) { FileUtils.deleteFile(filePath); } } catch (Exception e) { @@ -78,15 +94,14 @@ public class CommonController { @PostMapping("/upload") public AjaxResult uploadFile(MultipartFile file) throws Exception { try { - // 上传文件路径 - String filePath = RuoYiConfig.getUploadPath(); - // 上传并返回新文件名称 - String fileName = FileUploadUtils.upload(filePath, file); - String url = serverConfig.getUrl() + fileName; + // 沿用原有的体积与扩展名白名单校验;切到对象存储后不能丢掉这层门禁 + FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + UploadResult upload = ossStorageService.upload(file, OssBizPath.UPLOAD.dir()); AjaxResult ajax = AjaxResult.success(); - ajax.put("url", url); - ajax.put("fileName", fileName); - ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("url", upload.getUrl()); + // fileName 语义为“落库并用于前端回显的可访问地址”,OSS 模式下即完整对象地址 + ajax.put("fileName", upload.getUrl()); + ajax.put("newFileName", StringUtils.substringAfterLast(upload.getFilename(), "/")); ajax.put("originalFilename", file.getOriginalFilename()); return ajax; } catch (Exception e) { @@ -101,20 +116,22 @@ public class CommonController { @PostMapping("/uploads") public AjaxResult uploadFiles(List files) throws Exception { try { - // 上传文件路径 - String filePath = RuoYiConfig.getUploadPath(); List urls = new ArrayList(); List fileNames = new ArrayList(); List newFileNames = new ArrayList(); List originalFilenames = new ArrayList(); + // 先整体校验再上传,避免中途因某个文件不合法而留下已上传的孤儿对象 for (MultipartFile file : files) { - // 上传并返回新文件名称 - String fileName = FileUploadUtils.upload(filePath, file); - String url = serverConfig.getUrl() + fileName; - urls.add(url); - fileNames.add(fileName); - newFileNames.add(FileUtils.getName(fileName)); - originalFilenames.add(file.getOriginalFilename()); + FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + // 批量上传复用同一存储客户端,避免每个文件都读一次 OSS 配置 + List uploads = ossStorageService.upload(files, OssBizPath.UPLOAD.dir()); + for (int i = 0; i < uploads.size(); i++) { + UploadResult upload = uploads.get(i); + urls.add(upload.getUrl()); + fileNames.add(upload.getUrl()); + newFileNames.add(StringUtils.substringAfterLast(upload.getFilename(), "/")); + originalFilenames.add(files.get(i).getOriginalFilename()); } AjaxResult ajax = AjaxResult.success(); ajax.put("urls", StringUtils.join(urls, FILE_DELIMETER)); @@ -138,6 +155,15 @@ public class CommonController { if (!FileUtils.checkAllowDownload(resource)) { throw new Exception(StringUtils.format(MessageUtils.message("download.resource.not.valid"), resource)); } + if (ossStorageService.isOssUrl(resource)) { + // 对象存储资源:直接从 OSS 拉取字节流写出 + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, StringUtils.substringAfterLast(resource, "/")); + try (InputStream inputStream = ossStorageService.open(resource)) { + IoUtil.copy(inputStream, response.getOutputStream()); + } + return; + } // 本地资源路径 String localPath = RuoYiConfig.getProfile(); // 数据库资源地址 @@ -173,4 +199,16 @@ public class CommonController { //以流的形式输出到前端 MatrixToImageWriter.writeToStream(bitMatrix, "jpg", stream); } + + /** + * 解析下载时展示给用户的文件名 + * + *

对象存储地址取对象 KEY 末段;本地资源沿用原有的 "时间戳 + 下划线后段" 规则。 + */ + private String resolveDownloadName(String fileName) { + if (ossStorageService.isOssUrl(fileName)) { + return StringUtils.substringAfterLast(fileName, "/"); + } + return System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + } } diff --git a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java index 0fe8a082..0863979d 100644 --- a/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java +++ b/fastbee-admin/src/main/java/com/fastbee/web/controller/system/SysProfileController.java @@ -1,7 +1,6 @@ package com.fastbee.web.controller.system; import com.fastbee.common.annotation.Log; -import com.fastbee.common.config.RuoYiConfig; import com.fastbee.common.constant.UserConstants; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; @@ -17,6 +16,9 @@ import com.fastbee.common.utils.file.MimeTypeUtils; import com.fastbee.framework.web.service.TokenService; import com.fastbee.iot.domain.UserSocialProfile; import com.fastbee.iot.service.IUserSocialProfileService; +import com.fastbee.oss.entity.UploadResult; +import com.fastbee.oss.enums.OssBizPath; +import com.fastbee.oss.service.OssStorageService; import com.fastbee.system.service.ISysUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -45,6 +47,9 @@ public class SysProfileController extends BaseController { @Autowired private IUserSocialProfileService iUserSocialProfileService; + @Autowired + private OssStorageService ossStorageService; + /** * 个人信息 */ @@ -132,7 +137,10 @@ public class SysProfileController extends BaseController { public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception { if (!file.isEmpty()) { LoginUser loginUser = getLoginUser(); - String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION); + // 沿用原有的图片类型与体积白名单校验,仅存储介质由本地磁盘切换为对象存储 + FileUploadUtils.assertAllowed(file, MimeTypeUtils.IMAGE_EXTENSION); + UploadResult upload = ossStorageService.upload(file, OssBizPath.AVATAR.dir()); + String avatar = upload.getUrl(); if (userService.updateUserAvatar(loginUser.getUsername(), avatar)) { AjaxResult ajax = AjaxResult.success(); ajax.put("imgUrl", avatar); diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java index 42b67ecb..d4073f3e 100644 --- a/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java +++ b/fastbee-common/src/main/java/com/fastbee/common/utils/file/FileUploadUtils.java @@ -52,7 +52,7 @@ public class FileUploadUtils { * @return 文件名称 * @throws Exception */ - public static final String upload(MultipartFile file) throws IOException { + public static String upload(MultipartFile file) throws IOException { try { return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { @@ -68,7 +68,7 @@ public class FileUploadUtils { * @return 文件名称 * @throws IOException */ - public static final String upload(String baseDir, MultipartFile file) throws IOException { + public static String upload(String baseDir, MultipartFile file) throws IOException { try { return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); } catch (Exception e) { @@ -88,7 +88,7 @@ public class FileUploadUtils { * @throws IOException 比如读写文件出错时 * @throws InvalidExtensionException 文件校验异常 */ - public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + public static String upload(String baseDir, MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, InvalidExtensionException { int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length(); @@ -108,12 +108,12 @@ public class FileUploadUtils { /** * 编码文件名 */ - public static final String extractFilename(MultipartFile file) { + public static String extractFilename(MultipartFile file) { return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); } - public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException { + public static File getAbsoluteFile(String uploadDir, String fileName) { File desc = new File(uploadDir + File.separator + fileName); if (!desc.exists()) { @@ -124,7 +124,7 @@ public class FileUploadUtils { return desc; } - public static final String getPathFileName(String uploadDir, String fileName) throws IOException { + public static String getPathFileName(String uploadDir, String fileName) { int dirLastIndex = RuoYiConfig.getProfile().length() + 1; String currentDir = StringUtils.substring(uploadDir, dirLastIndex); return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; @@ -138,7 +138,7 @@ public class FileUploadUtils { * @throws FileSizeLimitExceededException 如果超出最大大小 * @throws InvalidExtensionException */ - public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + public static void assertAllowed(MultipartFile file, String[] allowedExtension) throws FileSizeLimitExceededException, InvalidExtensionException { long size = file.getSize(); if (size > DEFAULT_MAX_SIZE) { @@ -173,7 +173,7 @@ public class FileUploadUtils { * @param allowedExtension * @return */ - public static final boolean isAllowedExtension(String extension, String[] allowedExtension) { + public static boolean isAllowedExtension(String extension, String[] allowedExtension) { for (String str : allowedExtension) { if (str.equalsIgnoreCase(extension)) { return true; @@ -188,7 +188,7 @@ public class FileUploadUtils { * @param file 表单文件 * @return 后缀名 */ - public static final String getExtension(MultipartFile file) { + public static String getExtension(MultipartFile file) { String extension = FilenameUtils.getExtension(file.getOriginalFilename()); if (StringUtils.isEmpty(extension)) { extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); diff --git a/fastbee-open-api/pom.xml b/fastbee-open-api/pom.xml index 840118e2..7d493f86 100644 --- a/fastbee-open-api/pom.xml +++ b/fastbee-open-api/pom.xml @@ -13,6 +13,12 @@ controller层接口 + + + com.fastbee + fastbee-oss + + com.fastbee mqtt-broker diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/goview/GoviewProjectController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/goview/GoviewProjectController.java index c957685e..74ad1cc4 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/goview/GoviewProjectController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/goview/GoviewProjectController.java @@ -1,7 +1,6 @@ package com.fastbee.controller.goview; import com.fastbee.common.annotation.Log; -import com.fastbee.common.config.RuoYiConfig; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; import com.fastbee.common.core.domain.entity.SysUser; @@ -15,13 +14,15 @@ import com.fastbee.iot.domain.GoviewProjectData; import com.fastbee.iot.model.goview.GoviewProjectVo; import com.fastbee.iot.service.IGoviewProjectDataService; import com.fastbee.iot.service.IGoviewProjectService; +import com.fastbee.oss.entity.UploadResult; +import com.fastbee.oss.enums.OssBizPath; +import com.fastbee.oss.service.OssStorageService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -43,6 +44,9 @@ public class GoviewProjectController extends BaseController { @Autowired private IGoviewProjectDataService goviewProjectDataService; + @Autowired + private OssStorageService ossStorageService; + /** * 查询项目列表 */ @@ -161,23 +165,14 @@ public class GoviewProjectController extends BaseController { @ApiOperation("文件上传") public AjaxResult uploadFile(@RequestBody MultipartFile object) throws Exception { try { - String filePath = RuoYiConfig.getProfile(); - // 获取文件名和文件类型 + // 获取文件名 String fileName = object.getOriginalFilename(); - fileName = "/goview/" + getLoginUser().getUserId().toString() + "/" + fileName; - //创建目录 - File desc = new File(filePath + File.separator + fileName); - if (!desc.exists()) { - if (!desc.getParentFile().exists()) { - desc.getParentFile().mkdirs(); - } - } - // 存储文件-覆盖存储(一个文件一个图,防止过多) - object.transferTo(desc); - String url = "/profile" + fileName; + // 同一业务对象使用固定 KEY 覆盖存储,避免重复上传在对象存储里持续堆积 + String objectKey = OssBizPath.GOVIEW.of(getLoginUser().getUserId(), fileName); + UploadResult upload = ossStorageService.uploadAs(object, objectKey); Map map = new HashMap(2); - map.put("fileName", url); - map.put("url", url); + map.put("fileName", upload.getUrl()); + map.put("url", upload.getUrl()); return AjaxResult.success("上传成功", map); } catch (Exception e) { return AjaxResult.error(e.getMessage()); diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/media/SipDeviceController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/media/SipDeviceController.java index 05b412f9..50a6c6ac 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/media/SipDeviceController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/media/SipDeviceController.java @@ -7,6 +7,7 @@ import com.fastbee.common.core.page.TableDataInfo; import com.fastbee.common.enums.BusinessType; import com.fastbee.common.utils.file.FileUploadUtils; import com.fastbee.common.utils.poi.ExcelUtil; +import com.fastbee.oss.enums.OssBizPath; import com.fastbee.sip.domain.SipDevice; import com.fastbee.sip.service.ISipDeviceService; import io.swagger.annotations.Api; @@ -125,7 +126,8 @@ public class SipDeviceController extends BaseController { @GetMapping("/snap/{deviceId}/{channelId}") public void getSnap(HttpServletResponse resp, @PathVariable String deviceId, @PathVariable String channelId) { try { - final InputStream in = Files.newInputStream(new File(FileUploadUtils.getDefaultBaseDir() + File.separator + "snap" + File.separator + deviceId + "_" + channelId + ".jpg").toPath()); + final InputStream in = Files.newInputStream(new File(FileUploadUtils.getDefaultBaseDir() + File.separator + + OssBizPath.SNAP.dir() + File.separator + deviceId + "_" + channelId + ".jpg").toPath()); resp.setContentType(MediaType.IMAGE_PNG_VALUE); IOUtils.copy(in, resp.getOutputStream()); } catch (IOException e) { diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java index 7726cf06..a97cf50c 100644 --- a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java +++ b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java @@ -7,7 +7,6 @@ import com.dtflys.forest.http.ForestRequestType; import com.dtflys.forest.http.ForestResponse; import com.fastbee.common.ProtocolDeCodeService; import com.fastbee.common.annotation.Log; -import com.fastbee.common.config.RuoYiConfig; import com.fastbee.common.constant.Constants; import com.fastbee.common.core.controller.BaseController; import com.fastbee.common.core.domain.AjaxResult; @@ -36,6 +35,9 @@ import com.fastbee.iot.util.VelocityInitializer; import com.fastbee.iot.util.VelocityUtils; import com.fastbee.mqttclient.MqttClientConfig; import com.fastbee.mqttclient.PubMqttClient; +import com.fastbee.oss.entity.UploadResult; +import com.fastbee.oss.enums.OssBizPath; +import com.fastbee.oss.service.OssStorageService; import com.fastbee.system.service.ISysConfigService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; @@ -57,17 +59,12 @@ import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayOutputStream; -import java.io.File; import java.io.IOException; import java.io.StringWriter; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -import static com.fastbee.common.utils.file.FileUploadUtils.getExtension; - /** * 产品分类Controller * @@ -106,6 +103,9 @@ public class ToolController extends BaseController { @Value("${server.broker.enabled}") private boolean brokerEnabled; + @Autowired + private OssStorageService ossStorageService; + /** * 用户注册 */ @@ -370,7 +370,6 @@ public class ToolController extends BaseController { @ApiOperation("文件上传") public AjaxResult uploadFile(MultipartFile file) throws Exception { try { - String filePath = RuoYiConfig.getProfile(); // 文件名长度限制 int fileNamelength = file.getOriginalFilename().length(); if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) { @@ -379,26 +378,11 @@ public class ToolController extends BaseController { // 文件类型限制 // assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); - // 获取文件名和文件类型 - String fileName = file.getOriginalFilename(); - String extension = getExtension(file); - //设置日期格式 - SimpleDateFormat df = new SimpleDateFormat("yyyy-MMdd-HHmmss"); - fileName = "/iot/" + getLoginUser().getUserId().toString() + "/" + df.format(new Date()) + "." + extension; - //创建目录 - File desc = new File(filePath + File.separator + fileName); - if (!desc.exists()) { - if (!desc.getParentFile().exists()) { - desc.getParentFile().mkdirs(); - } - } - // 存储文件 - file.transferTo(desc); - - String url = "/profile" + fileName; + // 上传至对象存储,按用户隔离业务目录 + UploadResult upload = ossStorageService.upload(file, OssBizPath.IOT.of(getLoginUser().getUserId())); AjaxResult ajax = AjaxResult.success(); - ajax.put("fileName", url); - ajax.put("url", url); + ajax.put("fileName", upload.getUrl()); + ajax.put("url", upload.getUrl()); return ajax; } catch (Exception e) { return AjaxResult.error(e.getMessage()); diff --git a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/core/OssClient.java b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/core/OssClient.java deleted file mode 100644 index 287b77bb..00000000 --- a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/core/OssClient.java +++ /dev/null @@ -1,266 +0,0 @@ -package com.fastbee.oss.core; - -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.util.IdUtil; -import com.amazonaws.ClientConfiguration; -import com.amazonaws.HttpMethod; -import com.amazonaws.Protocol; -import com.amazonaws.auth.AWSCredentials; -import com.amazonaws.auth.AWSCredentialsProvider; -import com.amazonaws.auth.AWSStaticCredentialsProvider; -import com.amazonaws.auth.BasicAWSCredentials; -import com.amazonaws.client.builder.AwsClientBuilder; -import com.amazonaws.services.s3.AmazonS3; -import com.amazonaws.services.s3.AmazonS3Client; -import com.amazonaws.services.s3.AmazonS3ClientBuilder; -import com.amazonaws.services.s3.model.*; -import com.fastbee.common.exception.GlobalException; -import com.fastbee.common.utils.DateUtils; -import com.fastbee.common.utils.StringUtils; -import com.fastbee.oss.entity.OssConstant; -import com.fastbee.oss.entity.OssProperties; -import com.fastbee.oss.entity.UploadResult; -import com.fastbee.oss.enums.AccessPolicyType; -import com.fastbee.oss.enums.PolicyType; - -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.InputStream; -import java.net.URL; -import java.util.Date; - -/** - * S3 存储协议 所有兼容S3协议的云厂商均支持 - * 阿里云 腾讯云 七牛云 minio - */ -public class OssClient { - - private final String configKey; - - private final OssProperties properties; - - private final AmazonS3 client; - - public OssClient(String configKey, OssProperties ossProperties) { - this.configKey = configKey; - this.properties = ossProperties; - try { - AwsClientBuilder.EndpointConfiguration endpointConfig = - new AwsClientBuilder.EndpointConfiguration(properties.getEndpoint(), properties.getRegion()); - - AWSCredentials credentials = new BasicAWSCredentials(properties.getAccessKey(), properties.getSecretKey()); - AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(credentials); - ClientConfiguration clientConfig = new ClientConfiguration(); - if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) { - clientConfig.setProtocol(Protocol.HTTPS); - } else { - clientConfig.setProtocol(Protocol.HTTP); - } - AmazonS3ClientBuilder build = AmazonS3Client.builder() - .withEndpointConfiguration(endpointConfig) - .withClientConfiguration(clientConfig) - .withCredentials(credentialsProvider) - .disableChunkedEncoding(); - if (!StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE)) { - // minio 使用https限制使用域名访问 需要此配置 站点填域名 - build.enablePathStyleAccess(); - } - this.client = build.build(); - - createBucket(); - } catch (Exception e) { - if (e instanceof GlobalException) { - throw e; - } - throw new GlobalException("配置错误! 请检查系统配置:[" + e.getMessage() + "]"); - } - } - - private static String getPolicy(String bucketName, PolicyType policyType) { - StringBuilder builder = new StringBuilder(); - builder.append("{\n\"Statement\": [\n{\n\"Action\": [\n"); - switch (policyType) { - case WRITE: - builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucketMultipartUploads\"\n"); - case READ_WRITE: - builder.append("\"s3:GetBucketLocation\",\n\"s3:ListBucket\",\n\"s3:ListBucketMultipartUploads\"\n"); - default: - builder.append("\"s3:GetBucketLocation\"\n"); - } - - builder.append("],\n\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("\"\n},\n"); - if (policyType == PolicyType.READ) { - builder.append("{\n\"Action\": [\n\"s3:ListBucket\"\n],\n\"Effect\": \"Deny\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("\"\n},\n"); - } - builder.append("{\n\"Action\": "); - switch (policyType) { - case WRITE: - builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"); - case READ_WRITE: - builder.append("[\n\"s3:AbortMultipartUpload\",\n\"s3:DeleteObject\",\n\"s3:GetObject\",\n\"s3:ListMultipartUploadParts\",\n\"s3:PutObject\"\n],\n"); - default: - builder.append("\"s3:GetObject\",\n"); - } - builder.append("\"Effect\": \"Allow\",\n\"Principal\": \"*\",\n\"Resource\": \"arn:aws:s3:::"); - builder.append(bucketName); - builder.append("/*\"\n}\n],\n\"Version\": \"2012-10-17\"\n}\n"); - return builder.toString(); - } - - public void createBucket() { - try { - String bucketName = properties.getBucketName(); - if (client.doesBucketExistV2(bucketName)) { - return; - } - CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName); - AccessPolicyType accessPolicy = getAccessPolicy(); - createBucketRequest.setCannedAcl(accessPolicy.getAcl()); - client.createBucket(createBucketRequest); - client.setBucketPolicy(bucketName, getPolicy(bucketName, accessPolicy.getPolicyType())); - } catch (Exception e) { - throw new GlobalException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]"); - } - } - - public UploadResult upload(byte[] data, String path, String contentType) { - return upload(new ByteArrayInputStream(data), path, contentType); - } - - public UploadResult upload(InputStream inputStream, String path, String contentType) { - if (!(inputStream instanceof ByteArrayInputStream)) { - inputStream = new ByteArrayInputStream(IoUtil.readBytes(inputStream)); - } - try { - ObjectMetadata metadata = new ObjectMetadata(); - metadata.setContentType(contentType); - metadata.setContentLength(inputStream.available()); - PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata); - // 设置上传对象的 Acl 为公共读 - putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); - client.putObject(putObjectRequest); - } catch (Exception e) { - throw new GlobalException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); - } - return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); - } - - public UploadResult upload(File file, String path) { - try { - PutObjectRequest putObjectRequest = new PutObjectRequest(properties.getBucketName(), path, file); - // 设置上传对象的 Acl 为公共读 - putObjectRequest.setCannedAcl(getAccessPolicy().getAcl()); - client.putObject(putObjectRequest); - } catch (Exception e) { - throw new GlobalException("上传文件失败,请检查配置信息:[" + e.getMessage() + "]"); - } - return UploadResult.builder().url(getUrl() + "/" + path).filename(path).build(); - } - - public void delete(String path) { - path = path.replace(getUrl() + "/", ""); - try { - client.deleteObject(properties.getBucketName(), path); - } catch (Exception e) { - throw new GlobalException("删除文件失败,请检查配置信息:[" + e.getMessage() + "]"); - } - } - - public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) { - return upload(data, getPath(properties.getPrefix(), suffix), contentType); - } - - public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) { - return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType); - } - - public UploadResult uploadSuffix(File file, String suffix) { - return upload(file, getPath(properties.getPrefix(), suffix)); - } - - /** - * 获取文件元数据 - * - * @param path 完整文件路径 - */ - public ObjectMetadata getObjectMetadata(String path) { - path = path.replace(getUrl() + "/", ""); - S3Object object = client.getObject(properties.getBucketName(), path); - return object.getObjectMetadata(); - } - - public InputStream getObjectContent(String path) { - path = path.replace(getUrl() + "/", ""); - S3Object object = client.getObject(properties.getBucketName(), path); - return object.getObjectContent(); - } - - public String getUrl() { - String domain = properties.getDomain(); - String endpoint = properties.getEndpoint(); - String header = OssConstant.IS_HTTPS.equals(properties.getIsHttps()) ? "https://" : "http://"; - // 云服务商直接返回 - if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { - if (StringUtils.isNotBlank(domain)) { - return header + domain; - } - return header + properties.getBucketName() + "." + endpoint; - } - // minio 单独处理 - if (StringUtils.isNotBlank(domain)) { - return header + domain + "/" + properties.getBucketName(); - } - return header + endpoint + "/" + properties.getBucketName(); - } - - public String getPath(String prefix, String suffix) { - // 生成uuid - String uuid = IdUtil.fastSimpleUUID(); - // 文件路径 - String path = DateUtils.datePath() + "/" + uuid; - if (StringUtils.isNotBlank(prefix)) { - path = prefix + "/" + path; - } - return path + suffix; - } - - public String getConfigKey() { - return configKey; - } - - /** - * 获取私有URL链接 - * - * @param objectKey 对象KEY - * @param second 授权时间 - */ - public String getPrivateUrl(String objectKey, Integer second) { - GeneratePresignedUrlRequest generatePresignedUrlRequest = - new GeneratePresignedUrlRequest(properties.getBucketName(), objectKey) - .withMethod(HttpMethod.GET) - .withExpiration(new Date(System.currentTimeMillis() + 1000L * second)); - URL url = client.generatePresignedUrl(generatePresignedUrlRequest); - return url.toString(); - } - - /** - * 检查配置是否相同 - */ - public boolean checkPropertiesSame(OssProperties properties) { - return this.properties.equals(properties); - } - - /** - * 获取当前桶权限类型 - * - * @return 当前桶权限类型code - */ - public AccessPolicyType getAccessPolicy() { - return AccessPolicyType.getByType(properties.getAccessPolicy()); - } - -} diff --git a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/entity/OssProperties.java b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/entity/OssProperties.java deleted file mode 100644 index 73da50cc..00000000 --- a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/entity/OssProperties.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.fastbee.oss.entity; - -import lombok.Data; - -@Data -public class OssProperties { - - /** - * 租户id - */ - private String tenantId; - - /** - * 访问站点 - */ - private String endpoint; - - /** - * 自定义域名 - */ - private String domain; - - /** - * 前缀 - */ - private String prefix; - - /** - * ACCESS_KEY - */ - private String accessKey; - - /** - * SECRET_KEY - */ - private String secretKey; - - /** - * 存储空间名 - */ - private String bucketName; - - /** - * 存储区域 - */ - private String region; - - /** - * 是否https(Y=是,N=否) - */ - private String isHttps; - - /** - * 桶权限类型(0private 1public 2custom) - */ - private String accessPolicy; - -} diff --git a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/enums/OssBizPath.java b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/enums/OssBizPath.java new file mode 100644 index 00000000..bf734248 --- /dev/null +++ b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/enums/OssBizPath.java @@ -0,0 +1,76 @@ +package com.fastbee.oss.enums; + +import com.fastbee.common.utils.StringUtils; + +/** + * 对象存储业务目录(对象 KEY 的一级前缀) + * + *

所有上传动作都必须从这里取目录,业务代码禁止再硬编码 upload/avatar/iot 等字面量: + * 前缀一旦散落各处,同桶对象的归属就无法统一盘点,迁移、清理、容量统计都会失控。 + * + *

用法: + *

    + *
  • 由 {@code OssClient} 生成随机对象名时传目录 —— {@code OssBizPath.AVATAR.dir()}
  • + *
  • 需要业务维度隔离时追加片段 —— {@code OssBizPath.IOT.of(userId)}
  • + *
  • 需要固定 KEY(覆盖写)时末位带文件名 —— {@code OssBizPath.GOVIEW.of(userId, fileName)}
  • + *
+ */ +public enum OssBizPath { + + /** 通用文件上传 */ + UPLOAD("upload"), + + /** 用户头像 */ + AVATAR("avatar"), + + /** IoT 工具箱上传 */ + IOT("iot"), + + /** 数据大屏资源 */ + GOVIEW("goview"), + + /** 设备监测报告 */ + REPORT("report"), + + /** 视频设备截图(ZLM 抓图,命名 {deviceId}_{channelId}.jpg) */ + SNAP("snap"), + + ; + + private final String dir; + + OssBizPath(String dir) { + this.dir = dir; + } + + /** + * 取纯目录名,用于由 {@code OssClient} 自动生成 uuid 对象名的场景 + */ + public String dir() { + return dir; + } + + /** + * 在业务目录下追加子路径片段 + * + * @param segments 依次拼接的片段(可为 userId 等业务标识;末位也可为带后缀的文件名,此时结果即完整对象 KEY) + * @return 形如 {@code avatar/1001} 的路径 + */ + public String of(Object... segments) { + StringBuilder key = new StringBuilder(dir); + if (segments == null) { + return key.toString(); + } + for (Object segment : segments) { + if (segment == null) { + continue; + } + String part = String.valueOf(segment).trim(); + if (StringUtils.isEmpty(part) || "null".equalsIgnoreCase(part)) { + continue; + } + key.append('/').append(part); + } + return key.toString(); + } +} diff --git a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssClient.java b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssClient.java index a82e62e1..dffcdb3e 100644 --- a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssClient.java +++ b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssClient.java @@ -208,15 +208,31 @@ public class OssClient { // 云服务商直接返回 if (StringUtils.containsAny(endpoint, OssConstant.CLOUD_SERVICE)) { if (StringUtils.isNotBlank(domain)) { - return header + domain; + return appendScheme(domain, header); } return header + properties.getBucketName() + "." + endpoint; } // minio 单独处理 - if (StringUtils.isNotBlank(domain)) { - return header + domain + "/" + properties.getBucketName(); + String host = StringUtils.isNotBlank(domain) ? domain : endpoint; + return appendScheme(host, header) + "/" + properties.getBucketName(); + } + + /** + * 补全省略的协议头 + * + *

oss_config 的 endpoint/domain 常被填成带协议的完整地址(如 {@code http://host:9000}), + * 此时若再拼一次 header 会得到 {@code http://http://host:9000} 这类畸形地址, + * 前端拿到后必然裂图。故 host 已自带协议时原样返回。 + */ + private String appendScheme(String host, String header) { + if (StringUtils.isEmpty(host)) { + return ""; } - return header + endpoint + "/" + properties.getBucketName(); + String trimmed = host.trim(); + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + return trimmed; + } + return header + trimmed; } public String getPath(String prefix, String suffix) { diff --git a/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssStorageService.java b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssStorageService.java new file mode 100644 index 00000000..d70007ab --- /dev/null +++ b/fastbee-plugs/fastbee-oss/src/main/java/com/fastbee/oss/service/OssStorageService.java @@ -0,0 +1,203 @@ +package com.fastbee.oss.service; + +import com.fastbee.common.core.redis.RedisCache; +import com.fastbee.common.exception.GlobalException; +import com.fastbee.common.utils.StringUtils; +import com.fastbee.common.utils.file.MimeTypeUtils; +import com.fastbee.oss.entity.UploadResult; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * 对象存储统一入口 + * + *

业务层通过本服务完成 OSS 读写,屏蔽 {@link OssFactory} / {@link OssClient} 的构造细节与 + * 配置来源差异。所有原先落本地磁盘的上传链路(通用上传、头像、大屏资源等)应统一改走本服务。 + * + *

对象命名规则由 {@link OssClient#getPath(String, String)} 生成:{@code [bizPrefix]/yyyy/MM/dd/uuid.suffix}, + * 其中 bizPrefix 由调用方按业务域指定(如 avatar、goview/12),避免所有对象平铺在日期目录下。 + */ +@Service +public class OssStorageService { + + @Autowired + private RedisCache redisCache; + + /** + * 获取默认存储客户端 + */ + public OssClient client() { + return OssFactory.instance(redisCache); + } + + /** + * 上传 MultipartFile 到指定业务目录 + * + * @param file 上传文件 + * @param bizPrefix 业务目录,为空则直接归档到日期目录 + * @return 可直接访问的上传结果(私有桶由调用方另行生成签名地址) + */ + public UploadResult upload(MultipartFile file, String bizPrefix) { + if (file == null || file.isEmpty()) { + throw new GlobalException("上传文件不能为空"); + } + return uploadWith(client(), file, bizPrefix); + } + + /** + * 批量上传到指定业务目录 + * + *

与循环调用 {@link #upload(MultipartFile, String)} 的区别:只取一次存储客户端。 + * {@code OssFactory#instance} 每次会读两次 Redis(默认配置 key + 配置详情), + * 逐个调用会产生 2N 次读;批量场景统一复用同一实例。 + * + * @param files 上传文件列表 + * @param bizPrefix 业务目录 + * @return 与入参顺序一致的上传结果 + */ + public List upload(List files, String bizPrefix) { + if (files == null || files.isEmpty()) { + return Collections.emptyList(); + } + OssClient storage = client(); + List results = new ArrayList<>(files.size()); + for (MultipartFile file : files) { + results.add(uploadWith(storage, file, bizPrefix)); + } + return results; + } + + /** + * 推断对象后缀:优先取文件名后缀,退化按 Content-Type + * + *

不直接复用 {@code FileUploadUtils#getExtension},因其内部对 contentType 做了 + * {@code requireNonNull},contentType 为空时会 NPE。 + */ + private String resolveSuffix(MultipartFile file) { + String original = file.getOriginalFilename(); + if (StringUtils.isNotEmpty(original)) { + String suffix = FilenameUtils.getExtension(original); + if (StringUtils.isNotEmpty(suffix)) { + return suffix; + } + } + String contentType = file.getContentType(); + return StringUtils.isEmpty(contentType) ? null : MimeTypeUtils.getExtension(contentType); + } + + /** + * 上传字节数据到指定业务目录 + * + * @param data 数据 + * @param bizPrefix 业务目录 + * @param suffix 文件后缀,不含点号;为空时对象不带后缀 + * @param contentType 内容类型 + * @return 上传结果 + */ + public UploadResult upload(byte[] data, String bizPrefix, String suffix, String contentType) { + if (data == null || data.length == 0) { + throw new GlobalException("上传数据不能为空"); + } + OssClient storage = client(); + String path = storage.getPath(bizPrefix, normalizeSuffix(suffix)); + return storage.upload(data, path, contentType); + } + + /** + * 以指定对象 KEY 上传(同 KEY 覆盖) + * + *

适用于“一个业务实体固定对应一个资源”的场景,例如大屏封面:本地时代靠同名文件覆盖来避免堆积, + * 对象存储若仍用随机对象名会持续沉淀无用对象,故此处保留按 KEY 覆盖的能力。 + * + * @param file 上传文件 + * @param objectKey 完整对象 KEY(含业务目录与后缀) + * @return 上传结果 + */ + public UploadResult uploadAs(MultipartFile file, String objectKey) { + if (file == null || file.isEmpty()) { + throw new GlobalException("上传文件不能为空"); + } + if (StringUtils.isEmpty(objectKey)) { + throw new GlobalException("对象KEY不能为空"); + } + try { + return client().upload(file.getBytes(), objectKey, file.getContentType()); + } catch (IOException e) { + throw new GlobalException("读取上传文件失败: " + e.getMessage()); + } + } + + /** + * 删除对象,入参可为完整 URL 或对象 KEY + */ + public void delete(String urlOrKey) { + if (StringUtils.isEmpty(urlOrKey)) { + return; + } + client().delete(urlOrKey); + } + + /** + * 读取对象内容流,入参可为完整 URL 或对象 KEY + * + * @return 对象内容流,调用方负责关闭 + */ + public InputStream open(String urlOrKey) { + if (StringUtils.isEmpty(urlOrKey)) { + throw new GlobalException("文件路径不能为空"); + } + return client().getObjectContent(urlOrKey); + } + + /** + * 获取当前存储的对外访问根地址(含 bucket) + */ + public String getBaseUrl() { + return client().getUrl(); + } + + /** + * 判断路径是否为 OSS 地址,用于区分历史本地资源与新对象存储资源 + */ + public boolean isOssUrl(String path) { + if (StringUtils.isEmpty(path)) { + return false; + } + return path.startsWith("http://") || path.startsWith("https://"); + } + + /** + * 使用给定客户端上传,供单文件与批量两条路径复用 + */ + private UploadResult uploadWith(OssClient storage, MultipartFile file, String bizPrefix) { + if (file == null || file.isEmpty()) { + throw new GlobalException("上传文件不能为空"); + } + // 文件名无后缀时按 Content-Type 推断(前端裁剪上传的文件名常是 blob), + // 否则落库对象没有后缀,会被 FileUtils#checkAllowDownload 的后缀白名单拦下 + String path = storage.getPath(bizPrefix, normalizeSuffix(resolveSuffix(file))); + try { + return storage.upload(file.getBytes(), path, file.getContentType()); + } catch (IOException e) { + throw new GlobalException("读取上传文件失败: " + e.getMessage()); + } + } + + /** + * 统一后缀格式为空或以点号开头,保持与 {@link OssClient#getPath} 的拼接语义一致 + */ + private String normalizeSuffix(String suffix) { + if (StringUtils.isEmpty(suffix)) { + return ""; + } + return suffix.startsWith(".") ? suffix : "." + suffix; + } +} diff --git a/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java b/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java index b9124add..366ca77c 100644 --- a/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java +++ b/fastbee-report/src/main/java/com/fastbee/report/executor/ReportAsyncExecutor.java @@ -5,6 +5,7 @@ import com.fastbee.common.core.redis.RedisCache; import com.fastbee.common.enums.MonitorCategory; import com.fastbee.common.utils.StringUtils; import com.fastbee.oss.entity.UploadResult; +import com.fastbee.oss.enums.OssBizPath; import com.fastbee.oss.service.OssFactory; import com.fastbee.report.analyzer.ReportAnalyzer; import com.fastbee.report.analyzer.ReportAnalyzerFactory; @@ -316,6 +317,6 @@ public class ReportAsyncExecutor { private String buildObjectPath(Long deviceId, String reportNo) { LocalDate today = LocalDate.now(ZoneOffset.ofHours(8)); - return "report/" + today.format(PATH_DATE) + "/device/" + deviceId + "/" + reportNo + ".docx"; + return OssBizPath.REPORT.of(today.format(PATH_DATE), "device", deviceId, reportNo + ".docx"); } } \ No newline at end of file diff --git a/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java b/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java index ec892a34..5fedcdc6 100644 --- a/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java +++ b/fastbee-server/sip-server/src/main/java/com/fastbee/sip/util/ZlmApiUtils.java @@ -6,6 +6,7 @@ import com.alibaba.fastjson2.JSONObject; import com.dtflys.forest.http.ForestRequest; import com.dtflys.forest.http.ForestResponse; import com.fastbee.common.utils.file.FileUploadUtils; +import com.fastbee.oss.enums.OssBizPath; import com.fastbee.sip.client.ZlmForestClient; import com.fastbee.sip.domain.MediaServer; import com.fastbee.sip.model.ZlmMediaServer; @@ -251,7 +252,7 @@ public class ZlmApiUtils { ForestResponse res = request.execute(ForestResponse.class); if (res.isSuccess()) { try { - File snapFolder = new File(FileUploadUtils.getDefaultBaseDir() + File.separator + "snap"); + File snapFolder = new File(FileUploadUtils.getDefaultBaseDir() + File.separator + OssBizPath.SNAP.dir()); if (!snapFolder.exists()) { if (!snapFolder.mkdirs()) { log.warn("{}路径创建失败", snapFolder.getAbsolutePath()); diff --git a/sql/fastbee-report.sql b/sql/fastbee-report.sql index 0ac47a75..2631f484 100644 --- a/sql/fastbee-report.sql +++ b/sql/fastbee-report.sql @@ -181,7 +181,12 @@ INSERT INTO `oss_config` VALUES (1, 'admin', 'minio', 'supermeminio', 'minio20260909', 'fastbee', 'report', 'http://47.121.180.229:9000', '', 'N', '', '0', '0', '0', 'admin', NOW(), 'admin', NOW(), 'MinIO默认存储'); - +INSERT INTO fastbee.oss_config +(tenant_id, tenant_name, config_key, access_key, secret_key, bucket_name, prefix, + endpoint, `domain`, is_https, region, access_policy, status, ext1, del_flag, create_by, create_time, update_by, update_time, remark) +VALUES +(1, 'admin', 'minio', 'IQJELEHGNX45V1LS7TZY', 'VnnQhFXwUIQ19qT4PKvlIAQxSH9Rru0IzTiyZHg3', 'fastbee', '', + 'http://47.121.180.229:9000', '', 'N', '', '1', '0', '', '0', 'admin', NOW(), 'admin', NOW(), 'MinIO默认存储'); -- ------------------------------------------------------------ -- 4. 历史报表菜单与权限(数据中心 3343 下新增) -- ------------------------------------------------------------ diff --git a/yack-web/src/components/FileUpload/index.vue b/yack-web/src/components/FileUpload/index.vue index 6908cf52..b282659d 100644 --- a/yack-web/src/components/FileUpload/index.vue +++ b/yack-web/src/components/FileUpload/index.vue @@ -20,7 +20,7 @@

  • - + {{ getFileName(file.name) }}
    @@ -35,6 +35,7 @@