Files
mokeegateway/_backup_20260613/FileServiceImpl.java
2026-07-15 16:13:27 +08:00

179 lines
6.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.mokee.admin.service.impl;
import cn.hutool.crypto.digest.MD5;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mokee.admin.mapper.SysFileMapper;
import com.mokee.admin.service.ConfigService;
import com.mokee.admin.service.FileService;
import com.mokee.common.entity.SysFile;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
* 文件服务实现 — 文件二进制数据存储于本地磁盘
* <p>
* 磁盘路径结构:{basePath}/{yyyy-MM}/{storageFileName}
* 例:/data/mokee/gw/file/2026-06/a1b2c3d4e5f6.png
* <p>
* 向下兼容:存量 BLOB 数据file_data 不为空)仍可正常读取。
*
* @author mokee
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {
private final SysFileMapper sysFileMapper;
private final ConfigService configService;
private static final DateTimeFormatter YYYY_MM = DateTimeFormatter.ofPattern("yyyy-MM");
private static final String DEFAULT_BASE_PATH = "/data/mokee/gw/file";
/** 获取配置的存储根目录 */
private String getBasePath() {
return configService.getValue("file.storage.disk.path", DEFAULT_BASE_PATH);
}
/**
* 从原始文件名提取扩展名
*/
private String ext(String fileName) {
if (fileName == null) return "";
int i = fileName.lastIndexOf('.');
return i > 0 ? fileName.substring(i) : "";
}
@Override
public SysFile upload(MultipartFile file, String fileType, String systemId,
String userId, String uploadUserName) throws IOException {
byte[] data = file.getBytes();
// 计算 MD5
String md5 = MD5.create().digestHex(data);
// 构建实体(先生成 ID 用于存储文件名)
String fileId = cn.hutool.core.lang.UUID.randomUUID().toString().replace("-", "");
String storageFileName = fileId + ext(file.getOriginalFilename());
LocalDateTime now = LocalDateTime.now();
SysFile sf = SysFile.builder()
.id(fileId)
.systemId(systemId)
.fileName(file.getOriginalFilename())
.storageFileName(storageFileName)
.fileType(fileType != null ? fileType : "other")
.fileSize(file.getSize())
.mimeType(file.getContentType())
.md5(md5)
.uploadUserId(userId)
.uploadUserName(uploadUserName)
.fileData(null) // 新文件不写入 BLOB
.createdAt(now)
.build();
// 写入磁盘
String basePath = getBasePath();
Path dir = Paths.get(basePath, now.format(YYYY_MM));
try {
Files.createDirectories(dir);
} catch (IOException e) {
log.error("创建存储目录失败: {} — 请检查磁盘权限和路径配置", dir);
throw new IOException("存储目录创建失败: " + dir + " (请检查磁盘权限和路径配置)", e);
}
Path filePath = dir.resolve(storageFileName);
try {
Files.write(filePath, data);
} catch (IOException e) {
log.error("写入文件失败: {} — 可能磁盘空间不足或权限不足", filePath);
throw new IOException("文件写入失败: " + filePath + " (可能磁盘空间不足或权限不足)", e);
}
log.info("文件已写入磁盘: {} ({} bytes, md5={})", filePath, file.getSize(), md5);
// 存元信息到 DB
sysFileMapper.insert(sf);
return sf;
}
@Override
public IPage<SysFile> page(Integer page, Integer size, String fileType,
String fileName, String uploadUserName, String systemId) {
LambdaQueryWrapper<SysFile> w = new LambdaQueryWrapper<>();
if (fileType != null && !fileType.isEmpty()) w.eq(SysFile::getFileType, fileType);
if (fileName != null && !fileName.isEmpty()) w.like(SysFile::getFileName, fileName);
if (uploadUserName != null && !uploadUserName.isEmpty()) w.like(SysFile::getUploadUserName, uploadUserName);
if (systemId != null) w.eq(SysFile::getSystemId, systemId);
w.orderByDesc(SysFile::getCreatedAt);
w.select(SysFile.class, f -> !"file_data".equals(f.getColumn()));
return sysFileMapper.selectPage(new Page<>(page, size), w);
}
@Override
public List<SysFile> list(String fileType, String systemId) {
LambdaQueryWrapper<SysFile> w = new LambdaQueryWrapper<>();
if (fileType != null && !fileType.isEmpty()) w.eq(SysFile::getFileType, fileType);
if (systemId != null) w.eq(SysFile::getSystemId, systemId);
w.orderByDesc(SysFile::getCreatedAt);
w.select(SysFile.class, f -> !"file_data".equals(f.getColumn()));
return sysFileMapper.selectList(w);
}
@Override
public SysFile getById(String id) {
return sysFileMapper.selectById(id);
}
@Override
public SysFile getMetaById(String id) {
LambdaQueryWrapper<SysFile> w = new LambdaQueryWrapper<>();
w.eq(SysFile::getId, id);
w.select(SysFile.class, f -> !"file_data".equals(f.getColumn()));
return sysFileMapper.selectOne(w);
}
@Override
public Path getDiskPath(SysFile sf) {
String basePath = getBasePath();
// 存量兼容:旧文件 storageFileName 为空时,用 id + 扩展名兜底
String storageName = sf.getStorageFileName();
if (storageName == null || storageName.isEmpty()) {
storageName = sf.getId() + ext(sf.getFileName());
}
return Paths.get(basePath, sf.getCreatedAt().format(YYYY_MM), storageName);
}
@Override
public void delete(String id) throws IOException {
SysFile sf = sysFileMapper.selectById(id);
if (sf == null) return;
// 删除磁盘文件(失败不阻断 DB 删除)
try {
Path path = getDiskPath(sf);
if (Files.exists(path)) {
Files.delete(path);
log.info("磁盘文件已删除: {}", path);
}
} catch (Exception e) {
log.warn("删除磁盘文件失败继续删DB记录 - id: {}, error: {}", id, e.getMessage());
}
// 删除 DB 记录
sysFileMapper.deleteById(id);
}
}