package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.device.mapper.DeviceMaintenanceFileMapper;
import com.ruoyi.device.pojo.DeviceMaintenanceFile;
import com.ruoyi.device.service.DeviceMaintenanceFileService;
import com.ruoyi.framework.config.RuoYiConfig;
import com.ruoyi.other.mapper.TempFileMapper;
import com.ruoyi.other.pojo.TempFile;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
/**
*
* 设备保养附件 服务实现类
*
*
* @author 芯导软件(江苏)有限公司
* @since 2026-01-27 09:48:09
*/
@Service
@Slf4j
public class DeviceMaintenanceFileServiceImpl extends ServiceImpl
implements DeviceMaintenanceFileService {
@Resource
private TempFileMapper tempFileMapper;
@Value("${file.upload-dir}")
private String uploadDir;
@Value("${file.temp-dir}")
private String tempDir;
@Override
public DeviceMaintenanceFile uploadFile(MultipartFile file, Integer deviceMaintenanceId) throws IOException {
if (deviceMaintenanceId == null || deviceMaintenanceId <= 0) {
throw new ServiceException("设备保养记录ID不能为空");
}
if (file == null || file.isEmpty()) {
throw new ServiceException("上传失败,文件不能为空");
}
Path formalPath = storeMultipartFile(file, deviceMaintenanceId);
return insertFileRecord(deviceMaintenanceId, file.getOriginalFilename(), formalPath, (int) file.getSize());
}
@Override
public DeviceMaintenanceFile bindFromTemp(String tempId, Integer deviceMaintenanceId, String name) throws IOException {
if (!StringUtils.hasText(tempId)) {
throw new ServiceException("临时文件ID不能为空");
}
if (deviceMaintenanceId == null || deviceMaintenanceId <= 0) {
throw new ServiceException("设备保养记录ID不能为空");
}
TempFile tempFile = tempFileMapper.selectById(tempId);
if (tempFile == null) {
throw new ServiceException("临时文件不存在或已过期");
}
Path source = Paths.get(tempFile.getTempPath());
if (!Files.exists(source)) {
tempFileMapper.deleteById(tempId);
throw new ServiceException("临时文件已失效,请重新上传");
}
Path formalPath = copyToFormalDir(source, deviceMaintenanceId, tempFile.getOriginalName());
Files.deleteIfExists(source);
tempFileMapper.deleteById(tempId);
String fileName = StringUtils.hasText(name) ? name : tempFile.getOriginalName();
return insertFileRecord(deviceMaintenanceId, fileName, formalPath, null);
}
@Override
public DeviceMaintenanceFile saveRecord(DeviceMaintenanceFile deviceMaintenanceFile) throws IOException {
if (deviceMaintenanceFile == null) {
throw new ServiceException("附件信息不能为空");
}
if (StringUtils.hasText(deviceMaintenanceFile.getTempId())) {
return bindFromTemp(
deviceMaintenanceFile.getTempId(),
deviceMaintenanceFile.getDeviceMaintenanceId(),
deviceMaintenanceFile.getName());
}
String url = deviceMaintenanceFile.getUrl();
if (isTempStoragePath(url)) {
throw new ServiceException("请勿直接保存临时目录文件,请使用上传接口或传递 tempId");
}
if (StringUtils.hasText(url) && !url.startsWith(Constants.RESOURCE_PREFIX)) {
deviceMaintenanceFile.setUrl(buildAccessLink(Paths.get(url)));
}
save(deviceMaintenanceFile);
enrichAccessUrl(deviceMaintenanceFile);
return deviceMaintenanceFile;
}
@Override
public void enrichAccessUrl(List files) {
if (files == null) {
return;
}
files.forEach(this::enrichAccessUrl);
}
private void enrichAccessUrl(DeviceMaintenanceFile file) {
if (file == null || !StringUtils.hasText(file.getUrl())) {
return;
}
String url = file.getUrl().trim();
if (url.startsWith(Constants.RESOURCE_PREFIX) || url.startsWith("http://") || url.startsWith("https://")) {
return;
}
try {
file.setUrl(buildAccessLink(Paths.get(url)));
} catch (Exception e) {
log.warn("附件URL转换失败 id={} url={}", file.getId(), url);
}
}
private Path storeMultipartFile(MultipartFile file, Integer deviceMaintenanceId) throws IOException {
Path formalDirPath = resolveFormalDirPath();
Files.createDirectories(formalDirPath);
String originalFilename = file.getOriginalFilename();
if (!StringUtils.hasText(originalFilename)) {
throw new ServiceException("文件名不能为空");
}
Path formalPath = formalDirPath.resolve(buildFormalFilename(deviceMaintenanceId, originalFilename));
file.transferTo(formalPath.toFile());
return formalPath;
}
private Path copyToFormalDir(Path source, Integer deviceMaintenanceId, String originalFilename) throws IOException {
Path formalDirPath = resolveFormalDirPath();
Files.createDirectories(formalDirPath);
String name = StringUtils.hasText(originalFilename) ? originalFilename : source.getFileName().toString();
Path formalPath = formalDirPath.resolve(buildFormalFilename(deviceMaintenanceId, name));
Files.copy(source, formalPath, StandardCopyOption.REPLACE_EXISTING);
return formalPath;
}
private Path resolveFormalDirPath() {
String dir = uploadDir;
if (!dir.endsWith("/") && !dir.endsWith("\\")) {
dir = dir + "/";
}
return Paths.get(dir + "deviceMaintenance/" + LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE));
}
private String buildFormalFilename(Integer deviceMaintenanceId, String originalFilename) {
String ext = FilenameUtils.getExtension(originalFilename);
String base = deviceMaintenanceId + "_" + System.currentTimeMillis() + "_"
+ UUID.randomUUID().toString().substring(0, 8);
return StringUtils.hasText(ext) ? base + "." + ext : base;
}
private DeviceMaintenanceFile insertFileRecord(Integer deviceMaintenanceId, String name, Path formalPath, Integer fileSize) {
DeviceMaintenanceFile record = new DeviceMaintenanceFile();
record.setName(name);
record.setUrl(buildAccessLink(formalPath));
record.setFileSize(fileSize);
record.setDeviceMaintenanceId(deviceMaintenanceId);
save(record);
return record;
}
private boolean isTempStoragePath(String url) {
if (!StringUtils.hasText(url)) {
return false;
}
String normalized = url.replace("\\", "/").toLowerCase();
return normalized.contains("/temp/") || normalized.contains("temp/uploads");
}
private String buildAccessLink(Path formalFilePath) {
String normalizedPath = formalFilePath.toString().replace("\\", "/");
String profile = RuoYiConfig.getProfile();
String normalizedProfile = profile == null ? "" : profile.replace("\\", "/");
String relativePath = normalizedPath;
if (StringUtils.hasText(normalizedProfile) && normalizedPath.startsWith(normalizedProfile)) {
relativePath = normalizedPath.substring(normalizedProfile.length());
}
if (!relativePath.startsWith("/")) {
relativePath = "/" + relativePath;
}
return Constants.RESOURCE_PREFIX + relativePath;
}
}