feat:设备日志模块

This commit is contained in:
dzc committed 2026-07-30 13:15:45 +08:00
1 parent 41fa28775b
commit 06330260e5
18 files changed
+1217 -5

No files matched your search

@@ -0,0 +1,77 @@
-- 终端日志文件管理端权限补充。
-- 当前 sys_menu.permission 长度为 32,下载权限使用 down,避免 33 位的 download 标识被数据库截断。
-- 本脚本只维护权限元数据,不写 sys_role_menu;新按钮由管理员按角色显式授权,避免扩大现有角色权限。
SET @terminal_log_menu_seed := (SELECT COALESCE(MAX(menu_id), 0) FROM sys_menu);
INSERT INTO sys_menu
(menu_id, name, permission, path, component, parent_id, icon, visible, sort_order, keep_alive, embedded,
menu_type, create_by, create_time, update_by, update_time, del_flag, tenant_id)
SELECT (@terminal_log_menu_seed := @terminal_log_menu_seed + 1), '日志预览',
'hard_hardTerminalLogFile_preview', NULL, NULL, view_menu.parent_id, NULL, '0', 1, '0', '0', '1',
'admin', NOW(), 'admin', NOW(), '0', view_menu.tenant_id
FROM sys_menu view_menu
WHERE view_menu.permission = 'hard_hardTerminalLogFile_view'
AND view_menu.del_flag = '0'
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_id <=> view_menu.tenant_id
AND existing.permission = 'hard_hardTerminalLogFile_preview'
);
INSERT INTO sys_menu
(menu_id, name, permission, path, component, parent_id, icon, visible, sort_order, keep_alive, embedded,
menu_type, create_by, create_time, update_by, update_time, del_flag, tenant_id)
SELECT (@terminal_log_menu_seed := @terminal_log_menu_seed + 1), '日志下载',
'hard_hardTerminalLogFile_down', NULL, NULL, view_menu.parent_id, NULL, '0', 2, '0', '0', '1',
'admin', NOW(), 'admin', NOW(), '0', view_menu.tenant_id
FROM sys_menu view_menu
WHERE view_menu.permission = 'hard_hardTerminalLogFile_view'
AND view_menu.del_flag = '0'
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_id <=> view_menu.tenant_id
AND existing.permission = 'hard_hardTerminalLogFile_down'
);
INSERT INTO sys_menu
(menu_id, name, permission, path, component, parent_id, icon, visible, sort_order, keep_alive, embedded,
menu_type, create_by, create_time, update_by, update_time, del_flag, tenant_id)
SELECT (@terminal_log_menu_seed := @terminal_log_menu_seed + 1), '立即拉取日志',
'hard_hardTerminalLogFile_pull', NULL, NULL, view_menu.parent_id, NULL, '0', 3, '0', '0', '1',
'admin', NOW(), 'admin', NOW(), '0', view_menu.tenant_id
FROM sys_menu view_menu
WHERE view_menu.permission = 'hard_hardTerminalLogFile_view'
AND view_menu.del_flag = '0'
AND NOT EXISTS (
SELECT 1
FROM sys_menu existing
WHERE existing.tenant_id <=> view_menu.tenant_id
AND existing.permission = 'hard_hardTerminalLogFile_pull'
);
UPDATE sys_menu
SET del_flag = '0',
visible = '0',
update_by = 'admin',
update_time = NOW()
WHERE permission IN (
'hard_hardTerminalLogFile_preview',
'hard_hardTerminalLogFile_down',
'hard_hardTerminalLogFile_pull'
);
-- 生成器默认的写操作和导出按钮不再对管理端开放,保留记录便于审计和重复执行。
UPDATE sys_menu
SET del_flag = '1',
visible = '0',
update_by = 'admin',
update_time = NOW()
WHERE permission IN (
'hard_hardTerminalLogFile_add',
'hard_hardTerminalLogFile_edit',
'hard_hardTerminalLogFile_del',
'hard_hardTerminalLogFile_export'
);
@@ -0,0 +1,47 @@
package com.bsd.pjcloud.hard.api.dto.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 终端日志管理端查询条件。
*/
@Data
@Schema(description = "终端日志文件查询条件")
public class HardTerminalLogFileQueryDto {
@Schema(description = "设备ID")
private Long hardId;
@Schema(description = "设备名称")
private String hardName;
@Schema(description = "设备唯一编号")
private String uniqueCode;
@Schema(description = "商户ID")
private Long merchantId;
@Schema(description = "部门ID")
private Long deptId;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Schema(description = "日志日期开始")
private LocalDate logDateStart;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Schema(description = "日志日期结束")
private LocalDate logDateEnd;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Schema(description = "上传时间开始")
private LocalDateTime uploadTimeStart;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Schema(description = "上传时间结束")
private LocalDateTime uploadTimeEnd;
}
@@ -0,0 +1,32 @@
package com.bsd.pjcloud.hard.api.dto.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 终端日志上传预检参数。
*/
@Data
@Schema(description = "终端日志上传预检参数")
public class HardTerminalLogPreflightDto {
@NotBlank(message = "日志相对路径不能为空")
@Size(max = 255, message = "日志相对路径不能超过255个字符")
@Schema(description = "终端日志根目录下的相对路径")
private String relativePath;
@NotNull(message = "文件大小不能为空")
@Min(value = 0, message = "文件大小不能小于0")
@Schema(description = "脱敏文件大小")
private Long fileSize;
@NotBlank(message = "文件哈希不能为空")
@Pattern(regexp = "(?i)^[0-9a-f]{32}$", message = "文件哈希必须为32位MD5")
@Schema(description = "脱敏文件MD5")
private String fileHash;
}
@@ -0,0 +1,43 @@
package com.bsd.pjcloud.hard.api.dto.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 终端日志上传元数据。设备快照由服务端根据请求头中的设备ID填充。
*/
@Data
@Schema(description = "终端日志上传元数据")
public class HardTerminalLogUploadDto {
@NotBlank(message = "日志相对路径不能为空")
@Size(max = 255, message = "日志相对路径不能超过255个字符")
@Schema(description = "终端日志根目录下的相对路径")
private String relativePath;
@NotNull(message = "日志日期不能为空")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
@Schema(description = "日志所属日期")
private LocalDate logDate;
@NotNull(message = "本地修改时间不能为空")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@Schema(description = "终端本地文件最后修改时间")
private LocalDateTime fileModifiedTime;
@Size(max = 64, message = "应用版本不能超过64个字符")
@Schema(description = "应用版本")
private String appVersion;
@NotBlank(message = "脱敏规则版本不能为空")
@Size(max = 32, message = "脱敏规则版本不能超过32个字符")
@Schema(description = "脱敏规则版本")
private String sanitizeVersion;
}
@@ -0,0 +1,191 @@
package com.bsd.pjcloud.hard.api.entity.manager;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 终端日志文件
*/
@Data
@TenantTable
@TableName("hard_terminal_log_file")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "终端日志文件")
public class HardTerminalLogFileEntity extends Model<HardTerminalLogFileEntity> {
/**
* 编号
*/
@TableId(type = IdType.AUTO)
@Schema(description = "编号")
private Long id;
/**
* 设备ID(hard_manager.id)
*/
@Schema(description = "设备ID(hard_manager.id)")
private Long hardId;
/**
* 上传时设备名称快照
*/
@Schema(description = "上传时设备名称快照")
private String hardName;
/**
* 上传时设备唯一编号快照
*/
@Schema(description = "上传时设备唯一编号快照")
private String uniqueCode;
/**
* 上传时设备类型快照
*/
@Schema(description = "上传时设备类型快照")
private String hardType;
/**
* 上传时商户ID快照
*/
@Schema(description = "上传时商户ID快照")
private Long merchantId;
/**
* 上传时部门ID快照
*/
@Schema(description = "上传时部门ID快照")
private Long deptId;
/**
* 日志所属日期
*/
@Schema(description = "日志所属日期")
private LocalDate logDate;
/**
* 终端日志根目录下的相对路径
*/
@Schema(description = "终端日志根目录下的相对路径")
private String relativePath;
/**
* 原日志文件名
*/
@Schema(description = "原日志文件名")
private String originalName;
/**
* 文件大小(字节)
*/
@Schema(description = "文件大小(字节)")
private Long fileSize;
/**
* 文件内容哈希(当前使用MD5)
*/
@Schema(description = "文件内容哈希(当前使用MD5)")
private String fileHash;
/**
* 终端本地文件最后修改时间
*/
@Schema(description = "终端本地文件最后修改时间")
private LocalDateTime fileModifiedTime;
/**
* 文件中心sys_file记录ID
*/
@Schema(description = "文件中心sys_file记录ID")
private Long sysFileId;
/**
* OSS对象文件名
*/
@Schema(description = "OSS对象文件名")
private String storageFileName;
/**
* 文件访问相对地址
*/
@Schema(description = "文件访问相对地址")
private String storageUrl;
/**
* 上传时应用版本
*/
@Schema(description = "上传时应用版本")
private String appVersion;
/**
* 日志脱敏规则版本
*/
@Schema(description = "日志脱敏规则版本")
private String sanitizeVersion;
/**
* 最近成功上传时间
*/
@Schema(description = "最近成功上传时间")
private LocalDateTime uploadTime;
/**
* 成功上传次数
*/
@Schema(description = "成功上传次数")
private Integer uploadCount;
/**
* 备注
*/
@Schema(description = "备注")
private String remark;
/**
* 创建人
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private LocalDateTime createTime;
/**
* 修改人
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改人")
private String updateBy;
/**
* 修改时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "修改时间")
private LocalDateTime updateTime;
/**
* 删除标记
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description = "删除标记")
private String delFlag;
/**
* 租户ID
*/
@Schema(description = "租户ID")
private Long tenantId;
}
@@ -0,0 +1,23 @@
package com.bsd.pjcloud.hard.api.vo.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 终端日志设备聚合摘要。
*/
@Data
@Schema(description = "终端日志设备摘要")
public class HardTerminalLogDeviceVo {
private Long hardId;
private String hardName;
private String uniqueCode;
private String hardType;
private Long merchantId;
private Long deptId;
private Long fileCount;
private LocalDateTime latestUploadTime;
}
@@ -0,0 +1,27 @@
package com.bsd.pjcloud.hard.api.vo.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* 终端日志文件树节点。
*/
@Data
@Schema(description = "终端日志文件树节点")
public class HardTerminalLogFileTreeVo {
private String key;
private String label;
private String relativePath;
private Boolean directory;
private Long fileId;
private Long fileSize;
private LocalDate logDate;
private LocalDateTime uploadTime;
private List<HardTerminalLogFileTreeVo> children = new ArrayList<>();
}
@@ -0,0 +1,19 @@
package com.bsd.pjcloud.hard.api.vo.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
/**
* 终端日志上传预检结果。
*/
@Data
@Builder
@Schema(description = "终端日志上传预检结果")
public class HardTerminalLogPreflightVo {
private Long id;
private Boolean needUpload;
private String normalizedPath;
private String message;
}
@@ -0,0 +1,22 @@
package com.bsd.pjcloud.hard.api.vo.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
/**
* 终端日志尾部预览。
*/
@Data
@Builder
@Schema(description = "终端日志预览")
public class HardTerminalLogPreviewVo {
private Long id;
private String originalName;
private String relativePath;
private String content;
private Integer lineCount;
private Long fileSize;
private Boolean truncated;
}
@@ -0,0 +1,21 @@
package com.bsd.pjcloud.hard.api.vo.manager;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
/**
* 终端日志上传结果。
*/
@Data
@Builder
@Schema(description = "终端日志上传结果")
public class HardTerminalLogUploadResultVo {
private Long id;
private Boolean uploaded;
private Boolean changed;
private String relativePath;
private Integer uploadCount;
private String message;
}
@@ -0,0 +1,112 @@
package com.bsd.pjcloud.hard.controller.hard;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogFileQueryDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogPreflightDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogUploadDto;
import com.bsd.pjcloud.hard.api.entity.manager.HardTerminalLogFileEntity;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogDeviceVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogFileTreeVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreflightVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreviewVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogUploadResultVo;
import com.bsd.pjcloud.hard.service.HardTerminalLogFileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
/**
* 终端日志文件。
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/hardTerminalLogFile")
@Tag(description = "hardTerminalLogFile", name = "终端日志文件管理")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class HardTerminalLogFileController {
private final HardTerminalLogFileService hardTerminalLogFileService;
@Operation(summary = "终端日志上传预检")
@PostMapping("/ad/preflight")
public R<HardTerminalLogPreflightVo> preflight(@RequestHeader("DEVICE-ID") Long hardId,
@Valid @RequestBody HardTerminalLogPreflightDto dto) {
return R.ok(hardTerminalLogFileService.preflight(hardId, dto));
}
@Operation(summary = "上传脱敏后的终端日志")
@PostMapping(value = "/ad/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<HardTerminalLogUploadResultVo> upload(@RequestHeader("DEVICE-ID") Long hardId,
@Valid @ModelAttribute HardTerminalLogUploadDto dto,
@RequestPart("file") MultipartFile file) {
return R.ok(hardTerminalLogFileService.upload(hardId, dto, file));
}
@Operation(summary = "分页查询终端日志文件")
@GetMapping("/page")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_view')")
public R<Page<HardTerminalLogFileEntity>> page(@ParameterObject Page<HardTerminalLogFileEntity> page,
@ParameterObject HardTerminalLogFileQueryDto dto) {
return R.ok(hardTerminalLogFileService.scopePage(page, dto));
}
@Operation(summary = "按设备聚合终端日志")
@GetMapping("/device-page")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_view')")
public R<Page<HardTerminalLogDeviceVo>> devicePage(@ParameterObject Page<HardTerminalLogDeviceVo> page,
@ParameterObject HardTerminalLogFileQueryDto dto) {
return R.ok(hardTerminalLogFileService.devicePage(page, dto));
}
@Operation(summary = "查询指定设备的日志文件树")
@GetMapping("/file-tree")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_view')")
public R<List<HardTerminalLogFileTreeVo>> fileTree(@RequestParam Long hardId,
@ParameterObject HardTerminalLogFileQueryDto dto) {
return R.ok(hardTerminalLogFileService.fileTree(hardId, dto));
}
@Operation(summary = "预览终端日志尾部")
@GetMapping("/{id}/preview")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_preview')")
public R<HardTerminalLogPreviewVo> preview(@PathVariable Long id) {
return R.ok(hardTerminalLogFileService.preview(id));
}
@Operation(summary = "下载完整终端日志")
@GetMapping("/{id}/download")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_down')")
public void download(@PathVariable Long id, HttpServletResponse response) {
hardTerminalLogFileService.download(id, response);
}
@Operation(summary = "立即拉取指定终端当前日志")
@SysLog("立即拉取终端当前日志")
@PostMapping("/pull/{hardId}")
@PreAuthorize("@pms.hasPermission('hard_hardTerminalLogFile_pull')")
public R<Boolean> pull(@PathVariable Long hardId) {
return R.ok(hardTerminalLogFileService.pull(hardId));
}
}
@@ -0,0 +1,46 @@
package com.bsd.pjcloud.hard.mapper.manager;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogFileQueryDto;
import com.bsd.pjcloud.hard.api.entity.manager.HardTerminalLogFileEntity;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogDeviceVo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface HardTerminalLogFileMapper extends PjcloudBaseMapper<HardTerminalLogFileEntity> {
default LambdaQueryWrapper<HardTerminalLogFileEntity> queryWrapper(HardTerminalLogFileQueryDto dto) {
HardTerminalLogFileQueryDto query = dto == null ? new HardTerminalLogFileQueryDto() : dto;
return new LambdaQueryWrapper<HardTerminalLogFileEntity>()
.eq(ObjectUtil.isNotEmpty(query.getHardId()), HardTerminalLogFileEntity::getHardId, query.getHardId())
.like(StrUtil.isNotBlank(query.getHardName()), HardTerminalLogFileEntity::getHardName, query.getHardName())
.like(StrUtil.isNotBlank(query.getUniqueCode()), HardTerminalLogFileEntity::getUniqueCode, query.getUniqueCode())
.eq(ObjectUtil.isNotEmpty(query.getMerchantId()), HardTerminalLogFileEntity::getMerchantId, query.getMerchantId())
.eq(ObjectUtil.isNotEmpty(query.getDeptId()), HardTerminalLogFileEntity::getDeptId, query.getDeptId())
.ge(ObjectUtil.isNotEmpty(query.getLogDateStart()), HardTerminalLogFileEntity::getLogDate, query.getLogDateStart())
.le(ObjectUtil.isNotEmpty(query.getLogDateEnd()), HardTerminalLogFileEntity::getLogDate, query.getLogDateEnd())
.ge(ObjectUtil.isNotEmpty(query.getUploadTimeStart()), HardTerminalLogFileEntity::getUploadTime, query.getUploadTimeStart())
.le(ObjectUtil.isNotEmpty(query.getUploadTimeEnd()), HardTerminalLogFileEntity::getUploadTime, query.getUploadTimeEnd())
.orderByDesc(HardTerminalLogFileEntity::getUploadTime)
.orderByDesc(HardTerminalLogFileEntity::getId);
}
default Page<HardTerminalLogFileEntity> scopePage(Page<HardTerminalLogFileEntity> page, HardTerminalLogFileQueryDto dto) {
return selectPageByScope(page, queryWrapper(dto), DataScope.of());
}
default List<HardTerminalLogFileEntity> scopeList(HardTerminalLogFileQueryDto dto) {
return selectListByScope(queryWrapper(dto), DataScope.of());
}
List<HardTerminalLogDeviceVo> deviceGroups(@Param("dto") HardTerminalLogFileQueryDto dto,
@Param("dataScope") DataScope dataScope);
}
@@ -0,0 +1,36 @@
package com.bsd.pjcloud.hard.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogFileQueryDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogPreflightDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogUploadDto;
import com.bsd.pjcloud.hard.api.entity.manager.HardTerminalLogFileEntity;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogDeviceVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogFileTreeVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreflightVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreviewVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogUploadResultVo;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface HardTerminalLogFileService extends IService<HardTerminalLogFileEntity> {
HardTerminalLogPreflightVo preflight(Long hardId, HardTerminalLogPreflightDto dto);
HardTerminalLogUploadResultVo upload(Long hardId, HardTerminalLogUploadDto dto, MultipartFile file);
Page<HardTerminalLogFileEntity> scopePage(Page<HardTerminalLogFileEntity> page, HardTerminalLogFileQueryDto dto);
Page<HardTerminalLogDeviceVo> devicePage(Page<HardTerminalLogDeviceVo> page, HardTerminalLogFileQueryDto dto);
List<HardTerminalLogFileTreeVo> fileTree(Long hardId, HardTerminalLogFileQueryDto dto);
HardTerminalLogPreviewVo preview(Long id);
void download(Long id, HttpServletResponse response);
Boolean pull(Long hardId);
}
@@ -0,0 +1,461 @@
package com.bsd.pjcloud.hard.service.impl.manager;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.LockTemplate;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.feign.RemoteFileService;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogFileQueryDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogPreflightDto;
import com.bsd.pjcloud.hard.api.dto.manager.HardTerminalLogUploadDto;
import com.bsd.pjcloud.hard.api.entity.manager.HardManagerEntity;
import com.bsd.pjcloud.hard.api.entity.manager.HardTerminalLogFileEntity;
import com.bsd.pjcloud.hard.api.vo.manager.HardManagerVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogDeviceVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogFileTreeVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreflightVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogPreviewVo;
import com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogUploadResultVo;
import com.bsd.pjcloud.hard.mapper.manager.HardManagerMapper;
import com.bsd.pjcloud.hard.mapper.manager.HardTerminalLogFileMapper;
import com.bsd.pjcloud.hard.service.HardManagerService;
import com.bsd.pjcloud.hard.service.HardTerminalLogFileService;
import com.bsd.pjcloud.mqtt.api.feign.RemoteMqttEventService;
import com.bsd.pjcloud.mqtt.constant.MqttActionEnum;
import com.bsd.pjcloud.mqtt.dto.MqttBaseDto;
import feign.Response;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* 终端日志文件服务。
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class HardTerminalLogFileServiceImpl extends ServiceImpl<HardTerminalLogFileMapper, HardTerminalLogFileEntity> implements HardTerminalLogFileService {
private static final String TAG = "HardTerminalLogFileService";
private static final int PREVIEW_MAX_BYTES = 1024 * 1024;
private static final int PREVIEW_MAX_LINES = 5000;
private static final long LOCK_EXPIRE_MILLIS = 180_000L;
private static final long LOCK_WAIT_MILLIS = 5_000L;
private final HardManagerMapper hardManagerMapper;
private final HardManagerService hardManagerService;
private final RemoteFileService remoteFileService;
private final RemoteMqttEventService remoteMqttEventService;
private final LockTemplate lockTemplate;
@Override
public HardTerminalLogPreflightVo preflight(Long hardId, HardTerminalLogPreflightDto dto) {
requireTerminal(hardId);
String relativePath = normalizeRelativePath(dto.getRelativePath());
HardTerminalLogFileEntity existing = findByPath(hardId, relativePath);
boolean unchanged = existing != null
&& ObjectUtil.equals(existing.getFileSize(), dto.getFileSize())
&& StrUtil.equalsIgnoreCase(existing.getFileHash(), dto.getFileHash());
return HardTerminalLogPreflightVo.builder()
.id(existing == null ? null : existing.getId())
.needUpload(!unchanged)
.normalizedPath(relativePath)
.message(unchanged ? "日志未变化,无需重复上传" : "日志需要上传")
.build();
}
@Override
@Transactional(rollbackFor = Exception.class)
public HardTerminalLogUploadResultVo upload(Long hardId, HardTerminalLogUploadDto dto, MultipartFile file) {
HardManagerEntity hard = requireTerminal(hardId);
String relativePath = normalizeRelativePath(dto.getRelativePath());
validateUploadFile(file);
String fileHash = calculateMd5(file);
long fileSize = file.getSize();
String lockKey = String.format("hard:terminal-log:%s:%s:%s", TenantContextHolder.getTenantId(), hardId,
SecureUtil.md5(relativePath));
LockInfo lock = lockTemplate.lock(lockKey, LOCK_EXPIRE_MILLIS, LOCK_WAIT_MILLIS);
if (lock == null) {
throw new RuntimeException("该日志正在上传,请稍后重试");
}
try {
HardTerminalLogFileEntity existing = findByPath(hardId, relativePath);
if (existing != null && ObjectUtil.equals(existing.getFileSize(), fileSize)
&& StrUtil.equalsIgnoreCase(existing.getFileHash(), fileHash)) {
return uploadResult(existing, false, false, "日志未变化,无需重复上传");
}
Map<String, Object> storage = uploadToFileCenter(file);
LocalDateTime now = LocalDateTime.now();
boolean changed = existing != null;
HardTerminalLogFileEntity entity = existing == null ? new HardTerminalLogFileEntity() : existing;
fillTerminalSnapshot(entity, hard);
entity.setLogDate(dto.getLogDate());
entity.setRelativePath(relativePath);
entity.setOriginalName(fileName(relativePath));
entity.setFileSize(fileSize);
entity.setFileHash(fileHash);
entity.setFileModifiedTime(dto.getFileModifiedTime());
entity.setSysFileId(numberValue(storage.get("id")));
entity.setStorageFileName(stringValue(storage.get("fileName"), "文件中心未返回存储文件名"));
entity.setStorageUrl(stringValue(storage.get("url"), "文件中心未返回文件地址"));
entity.setAppVersion(dto.getAppVersion());
entity.setSanitizeVersion(dto.getSanitizeVersion());
entity.setUploadTime(now);
entity.setUploadCount(existing == null ? 1 : Math.max(0, existing.getUploadCount() == null ? 0 : existing.getUploadCount()) + 1);
entity.setTenantId(TenantContextHolder.getTenantId());
if (existing == null) {
save(entity);
} else {
updateById(entity);
}
return uploadResult(entity, true, changed, changed ? "日志已更新" : "日志上传成功");
} finally {
if (!lockTemplate.releaseLock(lock)) {
log.warn("|{}|终端日志锁释放失败,lockKey:|{}|", TAG, lockKey);
}
}
}
@Override
public Page<HardTerminalLogFileEntity> scopePage(Page<HardTerminalLogFileEntity> page, HardTerminalLogFileQueryDto dto) {
return baseMapper.scopePage(page, dto);
}
@Override
public Page<HardTerminalLogDeviceVo> devicePage(Page<HardTerminalLogDeviceVo> page, HardTerminalLogFileQueryDto dto) {
HardTerminalLogFileQueryDto query = dto == null ? new HardTerminalLogFileQueryDto() : dto;
List<HardTerminalLogDeviceVo> scopedGroups = baseMapper.deviceGroups(query, DataScope.of());
Map<Long, HardTerminalLogDeviceVo> deviceMap = new LinkedHashMap<>();
// SQL 先按部门和创建人分组,让数据权限拦截器过滤授权组;此处再合并同一设备,避免加载全部日志明细。
for (HardTerminalLogDeviceVo group : scopedGroups) {
HardTerminalLogDeviceVo device = deviceMap.get(group.getHardId());
if (device == null) {
group.setFileCount(group.getFileCount() == null ? 0L : group.getFileCount());
deviceMap.put(group.getHardId(), group);
continue;
}
device.setFileCount(device.getFileCount() + (group.getFileCount() == null ? 0L : group.getFileCount()));
if (device.getLatestUploadTime() == null || group.getLatestUploadTime() != null
&& group.getLatestUploadTime().isAfter(device.getLatestUploadTime())) {
device.setHardName(group.getHardName());
device.setUniqueCode(group.getUniqueCode());
device.setHardType(group.getHardType());
device.setMerchantId(group.getMerchantId());
device.setDeptId(group.getDeptId());
device.setLatestUploadTime(group.getLatestUploadTime());
}
}
List<HardTerminalLogDeviceVo> devices = new ArrayList<>(deviceMap.values());
devices.sort(Comparator.comparing(HardTerminalLogDeviceVo::getLatestUploadTime,
Comparator.nullsLast(Comparator.reverseOrder()))
.thenComparing(HardTerminalLogDeviceVo::getHardId, Comparator.nullsLast(Comparator.reverseOrder())));
page.setTotal(devices.size());
long size = page.getSize();
if (size <= 0) {
page.setRecords(devices);
return page;
}
long offset = Math.max(0, page.getCurrent() - 1) * size;
int fromIndex = (int) Math.min(offset, devices.size());
int toIndex = (int) Math.min(offset + size, devices.size());
page.setRecords(devices.subList(fromIndex, toIndex));
return page;
}
@Override
public List<HardTerminalLogFileTreeVo> fileTree(Long hardId, HardTerminalLogFileQueryDto dto) {
HardTerminalLogFileQueryDto query = dto == null ? new HardTerminalLogFileQueryDto() : dto;
query.setHardId(hardId);
List<HardTerminalLogFileEntity> files = baseMapper.scopeList(query);
List<HardTerminalLogFileTreeVo> root = new ArrayList<>();
for (HardTerminalLogFileEntity file : files) {
appendTreeNode(root, file);
}
return root;
}
@Override
public HardTerminalLogPreviewVo preview(Long id) {
HardTerminalLogFileEntity file = requireScopedFile(id);
try (Response response = remoteFileService.getFile(file.getStorageFileName())) {
requireFileResponse(response);
TailBytes tail = readTail(response.body().asInputStream(), PREVIEW_MAX_BYTES);
String content = new String(tail.bytes(), StandardCharsets.UTF_8);
if (tail.truncated()) {
int lineBreak = content.indexOf('\n');
content = lineBreak >= 0 ? content.substring(lineBreak + 1) : "";
}
List<String> lines = new ArrayList<>(Arrays.asList(content.split("\\R", -1)));
boolean truncated = tail.truncated();
if (lines.size() > PREVIEW_MAX_LINES) {
lines = lines.subList(lines.size() - PREVIEW_MAX_LINES, lines.size());
truncated = true;
}
if (!lines.isEmpty() && lines.get(lines.size() - 1).isEmpty()) {
lines = lines.subList(0, lines.size() - 1);
}
return HardTerminalLogPreviewVo.builder()
.id(file.getId())
.originalName(file.getOriginalName())
.relativePath(file.getRelativePath())
.content(String.join("\n", lines))
.lineCount(lines.size())
.fileSize(file.getFileSize())
.truncated(truncated)
.build();
} catch (IOException exception) {
throw new RuntimeException("读取日志文件失败", exception);
}
}
@Override
public void download(Long id, HttpServletResponse servletResponse) {
HardTerminalLogFileEntity file = requireScopedFile(id);
try (Response response = remoteFileService.getFile(file.getStorageFileName())) {
requireFileResponse(response);
servletResponse.setContentType("text/plain; charset=UTF-8");
servletResponse.setHeader("Content-Disposition", "attachment; filename*=UTF-8''"
+ URLEncoder.encode(file.getOriginalName(), StandardCharsets.UTF_8).replace("+", "%20"));
if (file.getFileSize() != null) {
servletResponse.setContentLengthLong(file.getFileSize());
}
IoUtil.copy(response.body().asInputStream(), servletResponse.getOutputStream());
} catch (IOException exception) {
throw new RuntimeException("下载日志文件失败", exception);
}
}
@Override
public Boolean pull(Long hardId) {
HardManagerEntity scopedHard = requireScopedHard(hardId);
HardManagerVo hard = hardManagerService.getByHardId(scopedHard.getId());
if (hard == null || hard.getHardGroupId() == null || hard.getMerchantDeptId() == null) {
throw new RuntimeException("设备消息主题配置不完整,无法下发日志拉取指令");
}
MqttBaseDto<?> dto = new MqttBaseDto<>();
dto.setHardGroupId(hard.getHardGroupId());
dto.setMerchantDeptId(hard.getMerchantDeptId());
dto.setHardwareId(hard.getId());
dto.setAction(MqttActionEnum.PULL_TERMINAL_LOG);
R<Boolean> result = remoteMqttEventService.sendDefaultMsg(dto);
if (result == null || !result.isOk() || !Boolean.TRUE.equals(result.getData())) {
throw new RuntimeException(result == null || StrUtil.isBlank(result.getMsg()) ? "日志拉取指令下发失败" : result.getMsg());
}
return true;
}
private HardManagerEntity requireTerminal(Long hardId) {
if (hardId == null) {
throw new RuntimeException("DEVICE-ID不能为空");
}
HardManagerEntity hard = hardManagerMapper.selectById(hardId);
if (hard == null) {
throw new RuntimeException("当前租户下设备不存在");
}
return hard;
}
private HardManagerEntity requireScopedHard(Long hardId) {
List<HardManagerEntity> list = hardManagerMapper.selectListByScope(
Wrappers.<HardManagerEntity>lambdaQuery().eq(HardManagerEntity::getId, hardId), DataScope.of());
if (list.isEmpty()) {
throw new RuntimeException("设备不存在或无数据权限");
}
return list.get(0);
}
private HardTerminalLogFileEntity requireScopedFile(Long id) {
List<HardTerminalLogFileEntity> files = baseMapper.selectListByScope(
Wrappers.<HardTerminalLogFileEntity>lambdaQuery().eq(HardTerminalLogFileEntity::getId, id), DataScope.of());
if (files.isEmpty()) {
throw new RuntimeException("日志文件不存在或无数据权限");
}
return files.get(0);
}
private HardTerminalLogFileEntity findByPath(Long hardId, String relativePath) {
return getOne(Wrappers.<HardTerminalLogFileEntity>lambdaQuery()
.eq(HardTerminalLogFileEntity::getHardId, hardId)
.eq(HardTerminalLogFileEntity::getRelativePath, relativePath), false);
}
private String normalizeRelativePath(String source) {
String path = StrUtil.trim(source).replace('\\', '/');
if (StrUtil.isBlank(path) || path.startsWith("/") || path.indexOf('\0') >= 0 || path.matches("^[A-Za-z]:.*")) {
throw new RuntimeException("日志相对路径不合法");
}
List<String> segments = Arrays.stream(path.split("/"))
.filter(StrUtil::isNotBlank)
.toList();
if (segments.isEmpty() || segments.stream().anyMatch(segment -> ".".equals(segment) || "..".equals(segment))) {
throw new RuntimeException("日志相对路径不合法");
}
String normalized = String.join("/", segments);
if (normalized.length() > 255 || !normalized.toLowerCase().endsWith(".log") || fileName(normalized).length() > 128) {
throw new RuntimeException("仅支持名称长度合规的.log日志文件");
}
return normalized;
}
private void validateUploadFile(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new RuntimeException("上传日志文件不能为空");
}
String originalName = file.getOriginalFilename();
if (StrUtil.isBlank(originalName) || !originalName.toLowerCase().endsWith(".log")) {
throw new RuntimeException("仅支持上传.log日志文件");
}
}
private String calculateMd5(MultipartFile file) {
try (InputStream inputStream = file.getInputStream()) {
return SecureUtil.md5(inputStream);
} catch (IOException exception) {
throw new RuntimeException("计算日志文件哈希失败", exception);
}
}
@SuppressWarnings("unchecked")
private Map<String, Object> uploadToFileCenter(MultipartFile file) {
R<?> result = remoteFileService.innerUpload(file, "terminal-log", null, "terminal-log");
if (result == null || !result.isOk() || !(result.getData() instanceof Map<?, ?> map)) {
throw new RuntimeException(result == null || StrUtil.isBlank(result.getMsg()) ? "文件中心上传失败" : result.getMsg());
}
return (Map<String, Object>) map;
}
private void fillTerminalSnapshot(HardTerminalLogFileEntity entity, HardManagerEntity hard) {
entity.setHardId(hard.getId());
entity.setHardName(hard.getName());
entity.setUniqueCode(hard.getUniqueCode());
entity.setHardType(hard.getType());
entity.setMerchantId(hard.getMerchantId());
entity.setDeptId(hard.getDeptId());
}
private HardTerminalLogUploadResultVo uploadResult(HardTerminalLogFileEntity entity, boolean uploaded, boolean changed,
String message) {
return HardTerminalLogUploadResultVo.builder()
.id(entity.getId())
.uploaded(uploaded)
.changed(changed)
.relativePath(entity.getRelativePath())
.uploadCount(entity.getUploadCount())
.message(message)
.build();
}
private void appendTreeNode(List<HardTerminalLogFileTreeVo> root, HardTerminalLogFileEntity file) {
String[] segments = file.getRelativePath().split("/");
List<HardTerminalLogFileTreeVo> level = root;
StringBuilder currentPath = new StringBuilder();
for (int index = 0; index < segments.length; index++) {
String segment = segments[index];
if (currentPath.length() > 0) {
currentPath.append('/');
}
currentPath.append(segment);
boolean leaf = index == segments.length - 1;
String nodeKey = currentPath.toString();
HardTerminalLogFileTreeVo node = level.stream()
.filter(child -> StrUtil.equals(child.getLabel(), segment))
.findFirst()
.orElse(null);
if (node == null) {
HardTerminalLogFileTreeVo value = new HardTerminalLogFileTreeVo();
value.setKey(nodeKey);
value.setLabel(segment);
value.setRelativePath(nodeKey);
value.setDirectory(!leaf);
level.add(value);
node = value;
}
if (leaf) {
node.setFileId(file.getId());
node.setFileSize(file.getFileSize());
node.setLogDate(file.getLogDate());
node.setUploadTime(file.getUploadTime());
} else {
level = node.getChildren();
}
}
}
private void requireFileResponse(Response response) {
if (response == null || response.status() < 200 || response.status() >= 300 || response.body() == null) {
throw new RuntimeException("文件中心读取日志失败");
}
}
private TailBytes readTail(InputStream inputStream, int maxBytes) throws IOException {
byte[] ring = new byte[maxBytes];
byte[] buffer = new byte[8192];
long total = 0;
int read;
while ((read = inputStream.read(buffer)) != -1) {
for (int index = 0; index < read; index++) {
ring[(int) (total % maxBytes)] = buffer[index];
total++;
}
}
int length = (int) Math.min(total, maxBytes);
ByteArrayOutputStream output = new ByteArrayOutputStream(length);
long start = Math.max(0, total - maxBytes);
for (long index = start; index < total; index++) {
output.write(ring[(int) (index % maxBytes)]);
}
return new TailBytes(output.toByteArray(), total > maxBytes);
}
private String fileName(String path) {
return path.substring(path.lastIndexOf('/') + 1);
}
private String stringValue(Object value, String message) {
if (value == null || StrUtil.isBlank(value.toString())) {
throw new RuntimeException(message);
}
return value.toString();
}
private Long numberValue(Object value) {
if (value instanceof Number number) {
return number.longValue();
}
if (value != null && StrUtil.isNotBlank(value.toString())) {
return Long.valueOf(value.toString());
}
throw new RuntimeException("文件中心未返回文件记录ID");
}
private record TailBytes(byte[] bytes, boolean truncated) {
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bsd.pjcloud.hard.mapper.manager.HardTerminalLogFileMapper">
<select id="deviceGroups" resultType="com.bsd.pjcloud.hard.api.vo.manager.HardTerminalLogDeviceVo">
select hard_id,
hard_name,
unique_code,
hard_type,
merchant_id,
dept_id,
create_by,
count(1) as file_count,
max(upload_time) as latest_upload_time
from hard_terminal_log_file
<where>
del_flag = '0'
<if test="dto != null and dto.hardId != null">
and hard_id = #{dto.hardId}
</if>
<if test="dto != null and dto.hardName != null and dto.hardName != ''">
and hard_name like concat('%', #{dto.hardName}, '%')
</if>
<if test="dto != null and dto.uniqueCode != null and dto.uniqueCode != ''">
and unique_code like concat('%', #{dto.uniqueCode}, '%')
</if>
<if test="dto != null and dto.merchantId != null">
and merchant_id = #{dto.merchantId}
</if>
<if test="dto != null and dto.deptId != null">
and dept_id = #{dto.deptId}
</if>
<if test="dto != null and dto.logDateStart != null">
and log_date <![CDATA[ >= ]]> #{dto.logDateStart}
</if>
<if test="dto != null and dto.logDateEnd != null">
and log_date <![CDATA[ <= ]]> #{dto.logDateEnd}
</if>
<if test="dto != null and dto.uploadTimeStart != null">
and upload_time <![CDATA[ >= ]]> #{dto.uploadTimeStart}
</if>
<if test="dto != null and dto.uploadTimeEnd != null">
and upload_time <![CDATA[ <= ]]> #{dto.uploadTimeEnd}
</if>
</where>
group by hard_id, hard_name, unique_code, hard_type, merchant_id, dept_id, create_by
order by latest_upload_time desc, hard_id desc
</select>
</mapper>
@@ -18,7 +18,7 @@ import java.util.List;
public interface RemoteMqttEventService {
@NoToken
@GetMapping("/mqtt/send-default-msg")
@PostMapping("/mqtt/send-default-msg")
R<Boolean> sendDefaultMsg(@RequestBody MqttBaseDto dto);
/**
@@ -18,6 +18,7 @@ public enum MqttActionEnum {
PRINT("打印变化"),
FACE("人脸变化"),
TAKE_FOOD_BROADCAST("定餐广播"),
PULL_TERMINAL_LOG("拉取终端当前日志"),
;
private final String description;
}
}
@@ -109,14 +109,16 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
@Override
public R innerUpload(MultipartFile file, String dir, Long groupId, String type) {
String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(file.getOriginalFilename());
Map<String, String> resultMap = new HashMap<>(4);
Map<String, Object> resultMap = new HashMap<>(5);
resultMap.put("bucketName", properties.getBucketName());
resultMap.put("fileName", fileName);
resultMap.put("url", String.format("/admin/sys-file/oss/file?fileName=%s", fileName));
try (InputStream inputStream = file.getInputStream()) {
fileTemplate.putObject(properties.getBucketName(), dir, fileName, inputStream, file.getContentType());
// 文件管理数据记录,收集管理追踪文件
innerFileLog(file, dir, fileName, groupId, type);
SysFile sysFile = innerFileLog(file, dir, fileName, groupId, type);
// 内部上传调用方需要文件中心主键执行后续替换、清理和保留周期管理。
resultMap.put("id", sysFile.getId());
} catch (Exception e) {
return R.failed(e.getLocalizedMessage());
}
@@ -283,7 +285,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
}
@SneakyThrows
private void innerFileLog(MultipartFile file, String dir, String fileName, Long groupId, String type) {
private SysFile innerFileLog(MultipartFile file, String dir, String fileName, Long groupId, String type) {
// 创建SysFile对象并设置相关属性
SysFile sysFile = new SysFile();
sysFile.setFileName(fileName);
@@ -304,6 +306,7 @@ public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> impl
sysFile.setUpdateBy("SYSTEM");
// 调用save方法保存SysFile对象
this.save(sysFile);
return sysFile;
}
}