maven
2025-09-19 2cfd0045a109b01ac890f0f2e968cbb44415d0ac
yys  博达商贸-巡检管理
已添加33个文件
已修改9个文件
2332 ■■■■■ 文件已修改
pom.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/pojo/StorageBlob.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/common/utils/MinioUtils.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 348 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysUserMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -307,6 +307,14 @@
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>16</source>
                    <target>16</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
@@ -6,4 +6,6 @@
@Data
public class StorageBlobDTO extends StorageBlob {
    private String url;
    private String downloadUrl;
}
src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
@@ -64,4 +64,10 @@
     */
    @TableField(value = "byte_size")
    private Long byteSize;
    /**
     * 0生产前 1生产后 2生产问题
     */
    @TableField(value = "type")
    private Long type;
}
src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -34,6 +34,9 @@
     */
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType);
    /**
     * åˆ é™¤é€šç”¨æ–‡ä»¶ä¸Šä¼ çš„附件信息
     * @param storageAttachment æ–‡ä»¶ä¿¡æ¯
src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -78,6 +78,22 @@
    }
    @Override
    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType) {
        // åˆ é™¤æ—§å›¾
        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
        for (StorageAttachment attachment : attachments) {
            // èŽ·å–å…³è”è®°å½•
            StorageBlob storageBlob = attachment.getStorageBlobDTO();
            attachment.setName(fileType.toString());
            attachment.setRecordType((long) recordType.ordinal());
            attachment.setRecordId(recordId);
            attachment.setStorageBlobId(storageBlob.getId());
            storageAttachmentMapper.insert(attachment);
        }
    }
    @Override
    public int deleteStorageAttachment(StorageAttachment storageAttachment) {
        // å…ˆåˆ é™¤æ˜Žç»†è¡¨
        storageBlobService.deleteStorageBlobs(storageAttachment);
src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
@@ -9,7 +9,10 @@
@AllArgsConstructor
public enum StorageAttachmentRecordType {
    // ä¾‹å­ å®žé™…开发请删除
    Template("Template","范例");
    Template("Template","范例"),
    Archives("Archives","文档管理"),
    InspectionTasks("InspectionTasks","生产巡检"),
    QrCodeScanRecords("QrCodeScanRecords","二维码扫码记录文件");
    private final String code;
src/main/java/com/ruoyi/common/utils/MinioUtils.java
@@ -21,12 +21,10 @@
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -306,4 +304,86 @@
        return null;
    }
    /**
     * ç”Ÿæˆé¢„览URL
     * @param bucketFilename æ–‡ä»¶åœ¨MinIO中的唯一标识
     * @param bucketName å­˜å‚¨æ¡¶åç§°
     * @param useDefaultExpiry æ˜¯å¦ä½¿ç”¨é»˜è®¤è¿‡æœŸæ—¶é—´ï¼ˆtrue=使用默认过期时间,false=永久有效)
     * @return é¢„览URL
     */
    public String getPreviewUrls(String bucketFilename, String bucketName, boolean useDefaultExpiry) {
        if (StringUtils.isBlank(bucketFilename)) {
            return null;
        }
        try {
            // éªŒè¯æ–‡ä»¶å­˜åœ¨æ€§
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(bucketFilename)
                    .build());
            GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(bucketFilename);
            // è®¾ç½®è¿‡æœŸæ—¶é—´ï¼šuseDefaultExpiry=true ä½¿ç”¨é…ç½®çš„过期时间
            if (useDefaultExpiry) {
                builder.expiry(previewExpiry, TimeUnit.HOURS);
            }
            return minioClient.getPresignedObjectUrl(builder.build());
        } catch (Exception e) {
            throw new UtilException("生成预览URL失败: " + e.getMessage(), e);
        }
    }
    /**
     * ç”Ÿæˆä¸‹è½½URL(强制浏览器下载)
     * @param bucketFilename æ–‡ä»¶åœ¨MinIO中的唯一标识
     * @param bucketName å­˜å‚¨æ¡¶åç§°
     * @param originalFileName åŽŸå§‹æ–‡ä»¶åï¼ˆç”¨äºŽä¸‹è½½æ—¶æ˜¾ç¤ºï¼‰
     * @param useDefaultExpiry æ˜¯å¦ä½¿ç”¨é»˜è®¤è¿‡æœŸæ—¶é—´ï¼ˆtrue=使用默认,false=无过期时间)
     * @return ä¸‹è½½URL
     */
    public String getDownloadUrls(String bucketFilename, String bucketName, String originalFileName, boolean useDefaultExpiry) {
        if (StringUtils.isBlank(bucketFilename)) {
            return null;
        }
        try {
            // éªŒè¯æ–‡ä»¶å­˜åœ¨æ€§
            minioClient.statObject(StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(bucketFilename)
                    .build());
            // æ­£ç¡®ç¼–码文件名:替换 + ä¸º %20
            String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8)
                    .replace("+", "%20");
            Map<String, String> reqParams = new HashMap<>();
            reqParams.put("response-content-disposition",
                    "attachment; filename=\"" + encodedFileName + "\"");
            GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(bucketName)
                    .object(bucketFilename)
                    .extraQueryParams(reqParams);
            // æ ¹æ®å‚数决定是否设置过期时间
            if (useDefaultExpiry) {
                // ä½¿ç”¨é»˜è®¤è¿‡æœŸæ—¶é—´ï¼ˆä»Žé…ç½®è¯»å–)
                builder.expiry(previewExpiry, TimeUnit.HOURS);
            } else {
                // ä¸è®¾ç½®è¿‡æœŸæ—¶é—´ï¼ˆMinIO é»˜è®¤7天)
            }
            return minioClient.getPresignedObjectUrl(builder.build());
        } catch (Exception e) {
            throw new UtilException("生成下载URL失败: " + e.getMessage(), e);
        }
    }
}
src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.inspectiontask;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
 * é™„件记录类型枚举
 *
 */
@Getter
@AllArgsConstructor
public enum StorageAttachmentRecordType {
    // ä¾‹å­ å®žé™…开发请删除
    Template("Template","范例"),
    Archives("Archives","文档管理"),
    InspectionTasks("InspectionTasks","生产巡检"),
    QrCodeScanRecords("QrCodeScanRecords","二维码扫码记录文件");
    private final String code;
    private final String info;
}
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.inspectiontask.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.service.InspectionTaskService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
/**
 * @author :yys
 * @date : 2025/9/19 10:52
 */
@RestController
@Api(tags = "巡检任务管理")
@RequestMapping("/inspectionTask")
public class InspectionTaskController extends BaseController {
    @Autowired
    private InspectionTaskService inspectionTaskService;
    /**
     * å·¡æ£€ä»»åŠ¡è¡¨è¡¨æŸ¥è¯¢
     */
    @GetMapping("/list")
    @ApiOperation("巡检任务表表查询")
    public R<IPage<InspectionTaskDto>> list(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
        IPage<InspectionTaskDto> list = inspectionTaskService.selectInspectionTaskList(page,inspectionTaskDto);
        return R.ok(list);
    }
    /**
     * å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
     */
    @PostMapping("/addOrEditInspectionTask")
    @ApiOperation("巡检任务表新增修改")
    @Transactional(rollbackFor = Exception.class)
    public R addOrEditInspectionTask(@RequestBody InspectionTaskDto inspectionTaskDto) {
        return R.ok(inspectionTaskService.addOrEditInspectionTask(inspectionTaskDto));
    }
    /**
     * å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
     */
    @DeleteMapping("/delInspectionTask")
    @ApiOperation("巡检任务表删除")
    @Transactional(rollbackFor = Exception.class)
    public R remove(@RequestBody Long[] ids) {
        return R.ok(inspectionTaskService.delByIds(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.ruoyi.inspectiontask.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.inspectiontask.dto.QrCodeDto;
import com.ruoyi.inspectiontask.pojo.QrCode;
import com.ruoyi.inspectiontask.service.QrCodeService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @author :yys
 * @date : 2025/9/19 10:52
 */
@RestController
@Api(tags = "二维码管理")
@RequestMapping("/qrCode")
public class QrCodeController extends BaseController {
    @Autowired
    private QrCodeService qrCodeService;
    /**
     * äºŒç»´ç ç®¡ç†è¡¨æŸ¥è¯¢
     */
    @GetMapping("/list")
    @ApiOperation("二维码管理表查询")
    public R<IPage<QrCode>> list(Page page, QrCodeDto qrCodeDto) {
        IPage<QrCode> list = qrCodeService.selectQrCodeList(page, qrCodeDto);
        return R.ok(list);
    }
    /**
     * äºŒç»´ç ç®¡ç†è¡¨æ–°å¢žä¿®æ”¹
     */
    @PostMapping("/addOrEditQrCode")
    @ApiOperation("二维码管理表新增修改")
    public R<Long> addOrEditQrCode(@RequestBody QrCodeDto qrCodeDto) {
        return R.ok(qrCodeService.addOrEditQrCode(qrCodeDto));
    }
    /**
     * äºŒç»´ç ç®¡ç†è¡¨åˆ é™¤
     */
    @DeleteMapping("/delQrCode")
    @ApiOperation("二维码管理表删除")
    public R remove(@RequestBody Long[] ids) {
        return R.ok(qrCodeService.delByIds(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,56 @@
package com.ruoyi.inspectiontask.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
import com.ruoyi.inspectiontask.service.QrCodeScanRecordService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @author :yys
 * @date : 2025/9/19 10:53
 */
@RestController
@Api(tags = "二维码扫描记录管理")
@RequestMapping("/qrCodeScanRecord")
public class QrCodeScanRecordController extends BaseController {
    @Autowired
    private QrCodeScanRecordService qrCodeScanRecordService;
    /**
     * äºŒç»´ç æ‰«ç è®°å½•表查询
     */
    @GetMapping("/list")
    @ApiOperation("二维码扫码记录表查询")
    public R<IPage<QrCodeScanRecordDto>> list(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
        IPage<QrCodeScanRecordDto> list = qrCodeScanRecordService.selectQrCodeScanRecordList(page, qrCodeScanRecordDto);
        return R.ok(list);
    }
    /**
     * äºŒç»´ç æ‰«ç è®°å½•表新增修改
     */
    @PostMapping("/addOrEditQrCodeRecord")
    @ApiOperation("二维码扫码记录表新增修改")
    public R addOrEditQrCodeRecord(@RequestBody QrCodeScanRecordDto qrCodeScanRecordDto) {
        return R.ok(qrCodeScanRecordService.addOrEditQrCodeRecord(qrCodeScanRecordDto));
    }
    /**
     * äºŒç»´ç æ‰«ç è®°å½•表删除
     */
    @DeleteMapping("/delSalesRecord")
    @ApiOperation("二维码扫码记录表删除")
    public R remove(@RequestBody Long[] ids) {
        return R.ok(qrCodeScanRecordService.delByIds(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,58 @@
package com.ruoyi.inspectiontask.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.inspectiontask.dto.TimingTaskDto;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import com.ruoyi.inspectiontask.service.TimingTaskService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
 * @author :yys
 * @date : 2025/9/19 10:53
 */
@RestController
@Api(tags = "定时任务管理")
@RequestMapping("/timingTask")
public class TimingTaskController extends BaseController {
    @Autowired
    private TimingTaskService timingTaskService;
    /**
     * å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æŸ¥è¯¢
     */
    @GetMapping("/list")
    @ApiOperation(value = "定时任务列表")
    public R<IPage<TimingTaskDto>> list(Page<TimingTask> page, TimingTask timingTask) {
        IPage<TimingTaskDto> list = timingTaskService.selectTimingTaskList(page,timingTask);
        return R.ok(list);
    }
    /**
     * å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨æ–°å¢žä¿®æ”¹
     */
    @PostMapping("/addOrEditTimingTask")
    @ApiOperation(value = "新增修改定时任务")
    public R addOrEditTimingTask(@RequestBody TimingTaskDto timingTaskDto) throws SchedulerException {
        return R.ok(timingTaskService.addOrEditTimingTask(timingTaskDto));
    }
    /**
     * å®šæ—¶å·¡æ£€ä»»åŠ¡è¡¨åˆ é™¤
     */
    @DeleteMapping("/delTimingTask")
    @ApiOperation(value = "删除定时任务")
    public R remove(@RequestBody Long[] ids) {
        return R.ok(timingTaskService.delByIds(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.inspectiontask.dto;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import lombok.Data;
import java.util.List;
@Data
public class InspectionTaskDto extends InspectionTask {
    private List<StorageBlobDTO> storageBlobDTO;
    private List<StorageBlobDTO> beforeProduction;
    private List<StorageBlobDTO> afterProduction;
    private List<StorageBlobDTO> productionIssues;
    private List<StorageAttachment> attachments;
}
src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.inspectiontask.dto;
import com.ruoyi.inspectiontask.pojo.QrCode;
import lombok.Data;
@Data
public class QrCodeDto extends QrCode {
}
src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.inspectiontask.dto;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.inspectiontask.pojo.QrCode;
import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
import lombok.Data;
import java.util.List;
@Data
public class QrCodeScanRecordDto extends QrCodeScanRecord {
    private QrCode qrCode;
    private String scanner;
    private List<StorageBlobDTO> storageBlobDTO;
}
src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,12 @@
package com.ruoyi.inspectiontask.dto;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import lombok.Data;
import java.util.List;
@Data
public class TimingTaskDto extends TimingTask {
    private List<String> inspector;
}
src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.inspectiontask.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
/**
 * @author :yys
 * @date : 2025/9/19 10:46
 */
public interface InspectionTaskMapper extends BaseMapper<InspectionTask> {
}
src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.inspectiontask.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspectiontask.pojo.QrCode;
/**
 * @author :yys
 * @date : 2025/9/19 10:46
 */
public interface QrCodeMapper extends BaseMapper<QrCode> {
}
src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.inspectiontask.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
/**
 * @author :yys
 * @date : 2025/9/19 10:46
 */
public interface QrCodeScanRecordMapper extends BaseMapper<QrCodeScanRecord> {
}
src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package com.ruoyi.inspectiontask.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspectiontask.pojo.TimingTask;
/**
 * @author :yys
 * @date : 2025/9/19 10:47
 */
public interface TimingTaskMapper extends BaseMapper<TimingTask> {
}
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
package com.ruoyi.inspectiontask.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/19 10:25
 */
@Data
@ApiModel
@TableName("inspection_task")
public class InspectionTask {
    private static final long serialVersionUID = 1L;
    /**
     * å·¡æ£€ä»»åŠ¡å”¯ä¸€æ ‡è¯†
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "巡检任务名称")
    private String taskName;
    @ApiModelProperty(value = "巡检人ID")
    private String inspectorId;
    @ApiModelProperty(value = "执行巡检的人员姓名")
    private String inspector;
    @ApiModelProperty(value = "任务附加说明或特殊情况记录")
    private String remarks;
    @ApiModelProperty(value = "任务登记人ID")
    private Long registrantId;
    @ApiModelProperty(value = "任务登记人姓名")
    private String registrant;
    @ApiModelProperty(value = "频次")
    private String frequencyType;
    @ApiModelProperty(value = "时间细节")
    private String frequencyDetail;
    @ApiModelProperty(value = "巡检地点详细描述")
    private String inspectionLocation;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    private Integer deleted;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private String createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private String updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "租户")
    @TableField(fill = FieldFill.INSERT)
    private Integer tenantId;
}
src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,63 @@
package com.ruoyi.inspectiontask.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/19 10:29
 */
@Data
@ApiModel
@TableName("qr_code")
public class QrCode {
    private static final long serialVersionUID = 1L;
    /**
     * äºŒç»´ç å”¯ä¸€æ ‡è¯†
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "设备名称")
    private String deviceName;
    @ApiModelProperty(value = "所在位置描述")
    private String location;
    @ApiModelProperty(value = "租户ID,用于多租户隔离")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer tenantId;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    private Integer deleted;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,68 @@
package com.ruoyi.inspectiontask.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/19 10:29
 */
@Data
@TableName("qr_code_scan_record")
@ApiModel
public class QrCodeScanRecord {
    private static final long serialVersionUID = 1L;
    /**
     * æ‰«ç è®°å½•唯一标识
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "关联的二维码ID")
    private Long qrCodeId;
    @ApiModelProperty(value = "扫码人用户ID")
    private Long scannerId;
    @ApiModelProperty(value = "实际扫码时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime scanTime;
    @ApiModelProperty(value = "租户ID,用于多租户隔离")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer tenantId;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    private Integer deleted;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private Integer updateBy;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package com.ruoyi.inspectiontask.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/19 10:27
 */
@Data
@ApiModel
@TableName("timing_task")
public class TimingTask {
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "任务名称")
    private String taskName;
    @ApiModelProperty(value = "巡检人")
    private String inspectorIds;
    @ApiModelProperty(value = "巡检地点")
    private String inspectionLocation;
    @ApiModelProperty(value = "频次")
    private String frequencyType;
    @ApiModelProperty(value = "频次详情")
    private String frequencyDetail;
    @ApiModelProperty(value = "下次执行时间")
    private LocalDateTime nextExecutionTime;
    @ApiModelProperty(value = "最后执行时间")
    private LocalDateTime lastExecutionTime;
    @ApiModelProperty(value = "是否激活")
    private boolean isActive;
    @ApiModelProperty(value = "备注")
    private String remarks;
    @ApiModelProperty(value = "登记人id")
    private Long registrantId;
    @ApiModelProperty(value = "登记人")
    private String registrant;
    @ApiModelProperty(value = "登记日期")
    private LocalDate registrationDate;
    @ApiModelProperty(value = "状态")
    private String status;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    private Integer deleted;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    private Integer tenantId;
}
src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.inspectiontask.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
/**
 * @author :yys
 * @date : 2025/9/19 10:49
 */
public interface InspectionTaskService extends IService<InspectionTask> {
    IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto);
    int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto);
    int delByIds(Long[] ids);
}
src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.inspectiontask.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
/**
 * @author :yys
 * @date : 2025/9/19 10:50
 */
public interface QrCodeScanRecordService extends IService<QrCodeScanRecord> {
    IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto);
    int addOrEditQrCodeRecord(QrCodeScanRecordDto qrCodeScanRecordDto);
    int delByIds(Long[] ids);
}
src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.inspectiontask.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspectiontask.dto.QrCodeDto;
import com.ruoyi.inspectiontask.pojo.QrCode;
/**
 * @author :yys
 * @date : 2025/9/19 10:50
 */
public interface QrCodeService extends IService<QrCode>{
    IPage<QrCode> selectQrCodeList(Page page, QrCodeDto qrCodeDto);
    Long addOrEditQrCode(QrCodeDto qrCodeDto);
    int delByIds(Long[] ids);
}
src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,23 @@
package com.ruoyi.inspectiontask.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.inspectiontask.dto.TimingTaskDto;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import org.quartz.SchedulerException;
/**
 * @author :yys
 * @date : 2025/9/19 10:51
 */
public interface TimingTaskService extends IService<TimingTask> {
    IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask);
    int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException;
    int delByIds(Long[] ids);
    void updateTaskExecutionTime(Long taskId);
}
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,257 @@
package com.ruoyi.inspectiontask.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.common.utils.MinioUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.service.InspectionTaskService;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.ruoyi.common.constant.StorageAttachmentConstants.StorageAttachmentFile;
import static com.ruoyi.common.enums.StorageAttachmentRecordType.InspectionTasks;
/**
 * @author :yys
 * @date : 2025/9/19 10:54
 */
@Service
@Slf4j
public class InspectionTaskServiceImpl extends ServiceImpl<InspectionTaskMapper, InspectionTask> implements InspectionTaskService {
    @Autowired
    private InspectionTaskMapper inspectionTaskMapper;
    @Autowired
    private StorageAttachmentService storageAttachmentService;
    @Autowired
    private StorageBlobMapper storageBlobMapper;
    @Autowired
    private StorageAttachmentMapper storageAttachmentMapper;
    @Autowired
    private MinioUtils minioUtils;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
        LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
        //  æ— æ•°æ®æå‰è¿”回
        if (CollectionUtils.isEmpty(entityPage.getRecords())) {
            return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
        }
        // èŽ·å–id集合
        List<Long> ids = entityPage.getRecords().stream().map(InspectionTask::getId).toList();
        //登记人ids
        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).toList();
        // æ‰¹é‡æŸ¥è¯¢ç™»è®°äºº
        Map<Long, SysUser> sysUserMap;
        if (!registrantIds.isEmpty()) {
            List<SysUser> sysUsers = sysUserMapper.selectList(registrantIds);
            sysUserMap = sysUsers.stream().collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
        } else {
            sysUserMap = new HashMap<>();
        }
        //巡检人ids
        List<String> inspectorIds = entityPage.getRecords().stream().map(InspectionTask::getInspectorId).toList();
        //获取所有不重复的用户ID
        Set<Long> allUserIds = entityPage.getRecords().stream()
                .map(InspectionTask::getInspectorId) // èŽ·å–"2,3"这样的字符串
                .filter(StringUtils::isNotBlank)
                .flatMap(idsStr -> Arrays.stream(idsStr.split(",")))
                .map(idStr -> {
                    try {
                        return Long.parseLong(idStr.trim());
                    } catch (NumberFormatException e) {
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // ä½¿ç”¨SQL批量查询用户信息
        Map<Long, String> userIdToNameMap = allUserIds.isEmpty()
                ? Collections.emptyMap()
                : sysUserMapper.selectUsersByIds(new ArrayList<>(allUserIds))
                .stream()
                .collect(Collectors.toMap(
                        SysUser::getUserId,
                        SysUser::getNickName,
                        (existing, replacement) -> existing));
        //处理附件
        Map<Long, List<StorageAttachment>> attachmentsMap = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>().in(StorageAttachment::getRecordId, ids)
                        .eq(StorageAttachment::getRecordType, InspectionTasks.ordinal()))
                .stream()
                .collect(Collectors.groupingBy(StorageAttachment::getRecordId));
        //  æ‰¹é‡æŸ¥è¯¢æ‰€æœ‰éœ€è¦çš„æ–‡ä»¶æ•°æ®
        Set<Long> blobIds = attachmentsMap.values()
                .stream()
                .flatMap(List::stream)
                .map(StorageAttachment::getStorageBlobId)
                .collect(Collectors.toSet());
        Map<Long, StorageBlob> blobMap = blobIds.isEmpty()
                ? Collections.emptyMap()
                : storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>().in(StorageBlob::getId, blobIds))
                .stream()
                .collect(Collectors.toMap(StorageBlob::getId, Function.identity()));
        List<InspectionTaskDto> dtoList = entityPage.getRecords().stream().map(inspectionTask -> {
            InspectionTaskDto dto = new InspectionTaskDto();
            BeanUtils.copyProperties(inspectionTask, dto);  // å¤åˆ¶ä¸»å¯¹è±¡å±žæ€§
            // è®¾ç½®ç™»è®°äºº
            SysUser sysUser = sysUserMap.get(inspectionTask.getRegistrantId());
            if (sysUser != null) {
                dto.setRegistrant(sysUser.getNickName());
            }
            // å¤„理巡检人名称
            if (StringUtils.isNotBlank(inspectionTask.getInspectorId())) {
                String inspectorNames = Arrays.stream(inspectionTask.getInspectorId().split(","))
                        .map(String::trim)
                        .map(idStr -> {
                            try {
                                Long userId = Long.parseLong(idStr);
                                return userIdToNameMap.getOrDefault(userId, "未知用户(" + idStr + ")");
                            } catch (NumberFormatException e) {
                                return "无效ID(" + idStr + ")";
                            }
                        })
                        .collect(Collectors.joining(","));
                dto.setInspector(inspectorNames);
            }
            // åˆå§‹åŒ–三个附件列表
            dto.setBeforeProduction(new ArrayList<>());
            dto.setAfterProduction(new ArrayList<>());
            dto.setProductionIssues(new ArrayList<>());
            // å¤„理附件分类
            Optional.ofNullable(attachmentsMap.get(inspectionTask.getId()))
                    .orElse(Collections.emptyList())
                    .forEach(attachment -> {
                        StorageBlob blob = blobMap.get(attachment.getStorageBlobId());
                        if (blob != null) {
                            // åˆ›å»ºé™„ä»¶DTO
                            StorageBlobDTO blobDto = createBlobDto(blob);
                            // æ ¹æ®type分类
                            switch ((int) blob.getType().longValue()) {
                                case 0:
                                    dto.getBeforeProduction().add(blobDto);
                                    break;
                                case 1:
                                    dto.getAfterProduction().add(blobDto);
                                    break;
                                case 2:
                                    dto.getProductionIssues().add(blobDto);
                                    break;
                                default:
                                    // å¯é€‰ï¼šè®°å½•未分类类型
                                    break;
                            }
                        }
                    });
            return dto;
        }).collect(Collectors.toList());
        // 7. æž„建返回分页对象
        IPage<InspectionTaskDto> resultPage = new Page<>();
        BeanUtils.copyProperties(entityPage, resultPage);
        resultPage.setRecords(dtoList);
        return resultPage;
    }
    // æå–创建BlobDTO的公共方法
    private StorageBlobDTO createBlobDto(StorageBlob blob) {
        StorageBlobDTO dto = new StorageBlobDTO();
        BeanUtils.copyProperties(blob, dto);
        // è®¾ç½®URL
        dto.setUrl(minioUtils.getPreviewUrls(
                blob.getBucketFilename(),
                blob.getBucketName(),
                true
        ));
        // è®¾ç½®ä¸‹è½½URL
        dto.setDownloadUrl(minioUtils.getDownloadUrls(
                blob.getBucketFilename(),
                blob.getBucketName(),
                blob.getOriginalFilename(),
                true
        ));
        return dto;
    }
    @Override
    public int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto) {
        InspectionTask inspectionTask = new InspectionTask();
        BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
        inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
        inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
        int i;
        if (Objects.isNull(inspectionTaskDto.getId())) {
            i = inspectionTaskMapper.insert(inspectionTask);
        } else {
            i = inspectionTaskMapper.updateById(inspectionTask);
        }
        if (inspectionTaskDto.getStorageBlobDTO() != null && !inspectionTaskDto.getStorageBlobDTO().isEmpty()) {
            List<StorageAttachment> attachments = new ArrayList<>();
            for (StorageBlobDTO storageBlobDTO : inspectionTaskDto.getStorageBlobDTO()) {
                StorageAttachment storageAttachment = new StorageAttachment(
                        StorageAttachmentFile,
                        (long) InspectionTasks.ordinal(),
                        inspectionTask.getId()
                );
                storageAttachment.setStorageBlobDTO(storageBlobDTO);
                attachments.add(storageAttachment);
            }
            storageAttachmentService.saveStorageAttachment(attachments, inspectionTask.getId(), InspectionTasks, StorageAttachmentFile);
        }
        return i;
    }
    @Override
    public int delByIds(Long[] ids) {
        // æ£€æŸ¥å‚æ•°
        if (ids == null || ids.length == 0) {
            return 0;
        }
        return inspectionTaskMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
package com.ruoyi.inspectiontask.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.StorageBlobDTO;
import com.ruoyi.basic.mapper.StorageAttachmentMapper;
import com.ruoyi.basic.mapper.StorageBlobMapper;
import com.ruoyi.basic.pojo.StorageAttachment;
import com.ruoyi.basic.pojo.StorageBlob;
import com.ruoyi.basic.service.StorageAttachmentService;
import com.ruoyi.common.constant.StorageAttachmentConstants;
import com.ruoyi.common.utils.MinioUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
import com.ruoyi.inspectiontask.mapper.QrCodeScanRecordMapper;
import com.ruoyi.inspectiontask.pojo.QrCode;
import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
import com.ruoyi.inspectiontask.service.QrCodeScanRecordService;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static com.ruoyi.common.constant.StorageAttachmentConstants.StorageAttachmentFile;
import static com.ruoyi.common.enums.StorageAttachmentRecordType.QrCodeScanRecords;
/**
 * @author :yys
 * @date : 2025/9/19 10:54
 */
@Service
@Slf4j
public class QrCodeScanRecordServiceImpl extends ServiceImpl<QrCodeScanRecordMapper, QrCodeScanRecord> implements QrCodeScanRecordService {
    @Autowired
    private QrCodeScanRecordMapper qrCodeScanRecordMapper;
    @Autowired
    private QrCodeMapper qrCodeMapper;
    @Autowired
    private StorageAttachmentService storageAttachmentService;
    @Autowired
    private StorageBlobMapper storageBlobMapper;
    @Autowired
    private StorageAttachmentMapper storageAttachmentMapper;
    @Autowired
    private MinioUtils minioUtils;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
        // 1. æž„建基础查询条件
        LambdaQueryWrapper<QrCodeScanRecord> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(QrCodeScanRecord::getCreateTime);
        // 2. æ‰§è¡Œåˆ†é¡µæŸ¥è¯¢
        IPage<QrCodeScanRecord> scanRecordIPage = qrCodeScanRecordMapper.selectPage(page, queryWrapper);
        // 3. æ— æ•°æ®æå‰è¿”回
        if (CollectionUtils.isEmpty(scanRecordIPage.getRecords())) {
            return new Page<>(scanRecordIPage.getCurrent(), scanRecordIPage.getSize(), scanRecordIPage.getTotal());
        }
        // 4. æ‰¹é‡èŽ·å–æ‰€æœ‰è®°å½•ID和二维码ID
        List<Long> recordIds = scanRecordIPage.getRecords().stream()
                .map(QrCodeScanRecord::getId)
                .collect(Collectors.toList());
        Set<Long> qrCodeIds = scanRecordIPage.getRecords().stream()
                .map(QrCodeScanRecord::getQrCodeId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // 5. æ‰¹é‡æŸ¥è¯¢å…³è”数据(使用最新API)
        // 5.1 æŸ¥è¯¢äºŒç»´ç ä¿¡æ¯ï¼ˆæ›¿æ¢selectBatchIds为selectByIds)
        Map<Long, QrCode> qrCodeMap = qrCodeIds.isEmpty()
                ? Collections.emptyMap()
                : qrCodeMapper.selectBatchIds(qrCodeIds).stream()
                .collect(Collectors.toMap(QrCode::getId, Function.identity()));
        // 5.2 æŸ¥è¯¢é™„件关联关系
        Map<Long, List<StorageAttachment>> attachmentsMap = storageAttachmentMapper
                .selectList(new LambdaQueryWrapper<StorageAttachment>()
                        .in(StorageAttachment::getRecordId, recordIds)
                        .eq(StorageAttachment::getRecordType, QrCodeScanRecords.ordinal()))
                .stream()
                .collect(Collectors.groupingBy(StorageAttachment::getRecordId));
        // 5.3 æŸ¥è¯¢æ–‡ä»¶æ•°æ®ï¼ˆä½¿ç”¨selectByIds)
        Set<Long> blobIds = attachmentsMap.values().stream()
                .flatMap(List::stream)
                .map(StorageAttachment::getStorageBlobId)
                .collect(Collectors.toSet());
        Map<Long, StorageBlob> blobMap = blobIds.isEmpty()
                ? Collections.emptyMap()
                : storageBlobMapper.selectBatchIds(blobIds).stream()
                .collect(Collectors.toMap(StorageBlob::getId, Function.identity()));
        // 6. ç»„装DTO数据
        List<QrCodeScanRecordDto> dtoList = scanRecordIPage.getRecords().stream().map(record -> {
            QrCodeScanRecordDto dto = new QrCodeScanRecordDto();
            BeanUtils.copyProperties(record, dto);
            SysUser sysUser = sysUserMapper.selectUserById(record.getScannerId());
            dto.setScanner(sysUser.getNickName());
            // 6.1 è®¾ç½®äºŒç»´ç ä¿¡æ¯
            Optional.ofNullable(qrCodeMap.get(record.getQrCodeId()))
                    .ifPresent(qrCode -> {
                        BeanUtils.copyProperties(qrCode, dto); // å¤åˆ¶åˆ°çˆ¶ç±»
                        dto.setQrCode(qrCode); // è®¾ç½®å®Œæ•´å¯¹è±¡
                    });
            // 6.2 è®¾ç½®é™„件信息
            dto.setStorageBlobDTO(
                    Optional.ofNullable(attachmentsMap.get(record.getId()))
                            .orElse(Collections.emptyList())
                            .stream()
                            .map(att -> {
                                StorageBlobDTO blobDTO = new StorageBlobDTO();
                                Optional.ofNullable(blobMap.get(att.getStorageBlobId()))
                                        .ifPresent(blob -> {
                                            BeanUtils.copyProperties(blob, blobDTO);
                                            blobDTO.setUrl(minioUtils.getPreviewUrls(blob.getBucketFilename(), blob.getBucketName(), true));
                                            blobDTO.setDownloadUrl(minioUtils.getDownloadUrls(blob.getBucketFilename(),blob.getBucketName(),blob.getOriginalFilename(),true));
                                        });
                                return blobDTO;
                            })
                            .filter(blobDTO -> blobDTO.getId() != null) // è¿‡æ»¤æ— æ•ˆé™„ä»¶
                            .collect(Collectors.toList())
            );
            return dto;
        }).collect(Collectors.toList());
        // 7. æž„建返回分页对象
        IPage<QrCodeScanRecordDto> resultPage = new Page<>();
        BeanUtils.copyProperties(scanRecordIPage, resultPage);
        resultPage.setRecords(dtoList);
        return resultPage;
    }
    @Override
    public int addOrEditQrCodeRecord(QrCodeScanRecordDto qrCodeScanRecordDto) {
        QrCodeScanRecord qrCodeScanRecord = new QrCodeScanRecord();
        BeanUtils.copyProperties(qrCodeScanRecordDto, qrCodeScanRecord);
        int i;
        if (Objects.isNull(qrCodeScanRecordDto.getId())) {
            i = qrCodeScanRecordMapper.insert(qrCodeScanRecord);
        } else {
            i = qrCodeScanRecordMapper.updateById(qrCodeScanRecord);
        }
        if (qrCodeScanRecordDto.getStorageBlobDTO() != null && !qrCodeScanRecordDto.getStorageBlobDTO().isEmpty()) {
            List<StorageAttachment> attachments = new ArrayList<>();
            for (StorageBlobDTO storageBlobDTO : qrCodeScanRecordDto.getStorageBlobDTO()) {
                StorageAttachment storageAttachment = new StorageAttachment(
                        StorageAttachmentFile,
                        (long) QrCodeScanRecords.ordinal(),
                        qrCodeScanRecord.getId()
                );
                storageAttachment.setStorageBlobDTO(storageBlobDTO);
                attachments.add(storageAttachment);
            }
            storageAttachmentService.saveStorageAttachment(attachments, qrCodeScanRecord.getId(), QrCodeScanRecords, StorageAttachmentFile);
        }
        return i;
    }
    @Override
    public int delByIds(Long[] ids) {
        return qrCodeScanRecordMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,54 @@
package com.ruoyi.inspectiontask.service.impl;
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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.inspectiontask.dto.QrCodeDto;
import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
import com.ruoyi.inspectiontask.pojo.QrCode;
import com.ruoyi.inspectiontask.service.QrCodeService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Objects;
/**
 * @author :yys
 * @date : 2025/9/19 10:55
 */
@Service
@Slf4j
public class QrCodeServiceImpl extends ServiceImpl<QrCodeMapper, QrCode> implements QrCodeService {
    @Autowired
    private QrCodeMapper qrCodeMapper;
    @Override
    public IPage<QrCode> selectQrCodeList(Page page, QrCodeDto qrCodeDto) {
        LambdaQueryWrapper<QrCode> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(QrCode::getCreateTime);
        return qrCodeMapper.selectPage(page, queryWrapper);
    }
    @Override
    public Long addOrEditQrCode(QrCodeDto qrCodeDto) {
        QrCode qrCode = new QrCode();
        BeanUtils.copyProperties(qrCodeDto, qrCode);
        if (Objects.isNull(qrCodeDto.getId())) {
            qrCodeMapper.insert(qrCode);
        } else {
            qrCodeMapper.updateById(qrCode);
        }
        return qrCode.getId();
    }
    @Override
    public int delByIds(Long[] ids) {
        return qrCodeMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,65 @@
package com.ruoyi.inspectiontask.service.impl;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import com.ruoyi.inspectiontask.service.TimingTaskService;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
public class TimingTaskJob implements Job {
    @Autowired
    private
    TimingTaskService timingTaskService;
    @Autowired
    private InspectionTaskMapper inspectionTaskMapper;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        Long taskId = jobDataMap.getLong("taskId");
        try {
            // 1. èŽ·å–å®šæ—¶ä»»åŠ¡è¯¦æƒ…
            TimingTask timingTask = timingTaskService.getById(taskId);
            if (timingTask == null || !timingTask.isActive()) {
                return;
            }
            // 2. åˆ›å»ºå¹¶ä¿å­˜å·¡æ£€ä»»åŠ¡è®°å½• - è¿™å°±æ˜¯æ‚¨æä¾›çš„代码应该放的位置
            InspectionTask inspectionTask = createInspectionTask(timingTask);
            inspectionTaskMapper.insert(inspectionTask);
            // 3. æ›´æ–°å®šæ—¶ä»»åŠ¡çš„æ‰§è¡Œæ—¶é—´
            timingTaskService.updateTaskExecutionTime(taskId);
            // 4. è®°å½•执行日志
//            timingTaskService.recordExecutionLog(taskId, true, "任务执行成功,生成巡检任务ID: " + inspectionTask.getId());
        } catch (Exception e) {
//            timingTaskService.recordExecutionLog(taskId, false, "任务执行失败: " + e.getMessage());
            throw new JobExecutionException(e);
        }
    }
    // è¿™å°±æ˜¯æ‚¨æä¾›çš„代码封装成的方法
    private InspectionTask createInspectionTask(TimingTask timingTask) {
        InspectionTask inspectionTask = new InspectionTask();
        // å¤åˆ¶åŸºæœ¬å±žæ€§
        inspectionTask.setTaskName(timingTask.getTaskName());
        inspectionTask.setInspectorId(timingTask.getInspectorIds());
        inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
        inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId());
        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
        return inspectionTask;
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,234 @@
package com.ruoyi.inspectiontask.service.impl;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import org.quartz.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Date;
import java.util.stream.Collectors;
@Service
public class TimingTaskScheduler {
    @Autowired
    private Scheduler scheduler;
    /**
     * æ·»åŠ æ–°ä»»åŠ¡åˆ°è°ƒåº¦å™¨
     */
    public void scheduleTimingTask(TimingTask task) throws SchedulerException {
        JobDetail jobDetail = buildJobDetail(task);
        Trigger trigger = buildJobTrigger(task, jobDetail);
        scheduler.scheduleJob(jobDetail, trigger);
    }
    /**
     * æ›´æ–°å·²æœ‰ä»»åŠ¡
     */
    public void rescheduleTimingTask(TimingTask task) throws SchedulerException {
        TriggerKey triggerKey = new TriggerKey("trigger_" + task.getId());
        // èŽ·å–çŽ°æœ‰è§¦å‘å™¨å¹¶è½¬æ¢ä¸º CronTrigger
        Trigger oldTrigger = scheduler.getTrigger(triggerKey);
        if (!(oldTrigger instanceof CronTrigger)) {
            throw new SchedulerException("Existing trigger is not a CronTrigger");
        }
        // æž„建新触发器
        Trigger newTrigger = TriggerBuilder.newTrigger()
                .withIdentity(triggerKey)
                .withDescription(task.getTaskName())
                .withSchedule(CronScheduleBuilder.cronSchedule(convertToCronExpression(task)))
                .startAt(Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()))
                .forJob(oldTrigger.getJobKey())
                .build();
        scheduler.rescheduleJob(triggerKey, newTrigger);
    }
    /**
     * æš‚停任务
     */
    public void pauseTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.pauseJob(jobKey);
    }
    /**
     * æ¢å¤ä»»åŠ¡
     */
    public void resumeTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.resumeJob(jobKey);
    }
    /**
     * åˆ é™¤ä»»åŠ¡
     */
    public void unscheduleTimingTask(Long taskId) throws SchedulerException {
        JobKey jobKey = new JobKey("timingTask_" + taskId);
        scheduler.deleteJob(jobKey);
    }
    private JobDetail buildJobDetail(TimingTask task) {
        JobDataMap jobDataMap = new JobDataMap();
        jobDataMap.put("taskId", task.getId());
        return JobBuilder.newJob(TimingTaskJob.class)
                .withIdentity("timingTask_" + task.getId())
                .withDescription(task.getTaskName())
                .usingJobData(jobDataMap)
                .storeDurably()
                .build();
    }
    private Trigger buildJobTrigger(TimingTask task, JobDetail jobDetail) {
        String cronExpression = convertToCronExpression(task);
            TriggerBuilder<CronTrigger> triggerBuilder = TriggerBuilder.newTrigger()
                .withIdentity("trigger_" + task.getId())
                .withDescription(task.getTaskName())
                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression));
        if (jobDetail != null) {
            triggerBuilder.forJob(jobDetail);
        }
        if (task.getNextExecutionTime() != null) {
            triggerBuilder.startAt(Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()));
        }
        return triggerBuilder.build();
    }
    private String convertToCronExpression(TimingTask task) {
        // å‚数校验
        if (task == null || task.getFrequencyType() == null || task.getFrequencyDetail() == null) {
            throw new IllegalArgumentException("任务参数不能为空");
        }
        // ä½¿ç”¨switch确保条件互斥
        return switch (task.getFrequencyType().toUpperCase()) { // ç»Ÿä¸€è½¬ä¸ºå¤§å†™æ¯”较
            case "DAILY" -> convertDailyToCron(task.getFrequencyDetail());
            case "WEEKLY" -> convertWeeklyToCron(task.getFrequencyDetail());
            case "MONTHLY" -> convertMonthlyToCron(task.getFrequencyDetail());
            case "QUARTERLY" -> convertQuarterlyToCron(task.getFrequencyDetail());
            default -> throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        };
    }
    // æ¯æ—¥ä»»åŠ¡è½¬æ¢
    private String convertDailyToCron(String frequencyDetail) {
        LocalTime time = parseTime(frequencyDetail);
        return String.format("0 %d %d * * ?", time.getMinute(), time.getHour());
    }
    // æ¯å‘¨ä»»åŠ¡è½¬æ¢
    private String convertWeeklyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
        String daysOfWeek = convertDayNamesToCron(parts[0]);
        LocalTime time = parseTime(parts[1]);
        return String.format("0 %d %d ? * %s", time.getMinute(), time.getHour(), daysOfWeek);
    }
    // æ¯æœˆä»»åŠ¡è½¬æ¢
    private String convertMonthlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
        int day = validateDayOfMonth(parts[0]);
        LocalTime time = parseTime(parts[1]);
        return String.format("0 %d %d %d * ?", time.getMinute(), time.getHour(), day);
    }
    // æ¯å­£åº¦ä»»åŠ¡è½¬æ¢
    private String convertQuarterlyToCron(String frequencyDetail) {
        String[] parts = validateAndSplit(frequencyDetail, ",", 3);
        int month = validateMonth(parts[0]);  // éªŒè¯æœˆä»½(1-12)
        int day = validateDayOfMonth(parts[1]);  // éªŒè¯æ—¥æœŸ
        LocalTime time = parseTime(parts[2]);  // è§£æžæ—¶é—´
        // è®¡ç®—季度起始月份(1月=1, 4月=4, 7月=7, 10月=10)
        int quarterStartMonth = ((month - 1) / 3) * 3 + 1;
        return String.format("0 %d %d %d %d/3 ?",
                time.getMinute(),
                time.getHour(),
                day,
                quarterStartMonth);
    }
    // æ–°å¢žéªŒè¯æœˆä»½çš„æ–¹æ³•(1-12)
    private int validateMonth(String monthStr) {
        try {
            int month = Integer.parseInt(monthStr);
            if (month < 1 || month > 12) {
                throw new IllegalArgumentException("月份必须在1-12之间");
            }
            return month;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("无效的月份格式");
        }
    }
    // è¾…助方法:解析时间
    private LocalTime parseTime(String timeStr) {
        try {
            return LocalTime.parse(timeStr);
        } catch (DateTimeParseException e) {
            throw new IllegalArgumentException("时间格式必须为HH:mm", e);
        }
    }
    // è¾…助方法:验证并分割字符串
    private String[] validateAndSplit(String input, String delimiter, int expectedParts) {
        String[] parts = input.split(delimiter);
        if (parts.length != expectedParts) {
            throw new IllegalArgumentException(
                    String.format("格式错误,应为%d部分用'%s'分隔", expectedParts, delimiter));
        }
        return parts;
    }
    // è¾…助方法:验证月份中的日
    private int validateDayOfMonth(String dayStr) {
        int day = Integer.parseInt(dayStr);
        if (day < 1 || day > 31) {
            throw new IllegalArgumentException("日期必须在1-31之间");
        }
        return day;
    }
    // è¾…助方法:验证季度中的月
    private int validateMonthInQuarter(String monthStr) {
        int month = Integer.parseInt(monthStr);
        if (month < 1 || month > 3) {
            throw new IllegalArgumentException("季度月份必须是1、2或3");
        }
        return month;
    }
    // è½¬æ¢æ˜ŸæœŸå‡ åç§°
    private String convertDayNamesToCron(String dayNames) {
        return Arrays.stream(dayNames.split("\\|"))
                .map(this::convertSingleDayName)
                .collect(Collectors.joining(","));
    }
    // è½¬æ¢å•个星期几名称
    private String convertSingleDayName(String dayName) {
        switch (dayName.toUpperCase()) {
            case "MON": return "MON";
            case "TUE": return "TUE";
            case "WED": return "WED";
            case "THU": return "THU";
            case "FRI": return "FRI";
            case "SAT": return "SAT";
            case "SUN": return "SUN";
            default: throw new IllegalArgumentException("无效的星期几: " + dayName);
        }
    }
}
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,348 @@
package com.ruoyi.inspectiontask.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.inspectiontask.dto.TimingTaskDto;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import com.ruoyi.inspectiontask.service.TimingTaskService;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import lombok.extern.slf4j.Slf4j;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.*;
import java.util.*;
import java.util.stream.Collectors;
/**
 * @author :yys
 * @date : 2025/9/19 10:55
 */
@Service
@Slf4j
public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService {
    @Autowired
    private TimingTaskMapper timingTaskMapper;
    @Autowired
    private InspectionTaskMapper inspectionTaskMapper;
    @Autowired
    private TimingTaskScheduler timingTaskScheduler;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
        // 1. å…ˆåˆ†é¡µæŸ¥è¯¢å®šæ—¶ä»»åŠ¡æ•°æ®
        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null);
        // 2. å¦‚果没有数据,直接返回空分页
        if (taskPage.getRecords().isEmpty()) {
            return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        }
        // 3. æ”¶é›†æ‰€æœ‰éœ€è¦æŸ¥è¯¢çš„用户ID
        Set<Long> userIds = new HashSet<>();
        // æ”¶é›†ç™»è®°äººID
        taskPage.getRecords().forEach(task -> {
            if (task.getRegistrantId() != null) {
                userIds.add(task.getRegistrantId());
            }
        });
        // æ”¶é›†å·¡æ£€äººID(多个ID以逗号分隔)
        taskPage.getRecords().forEach(task -> {
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                Arrays.stream(task.getInspectorIds().split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(Long::valueOf)
                        .forEach(userIds::add);
            }
        });
        // 4. æ‰¹é‡æŸ¥è¯¢ç”¨æˆ·ä¿¡æ¯
        Map<Long, String> userNickNameMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<SysUser> users = sysUserMapper.selectUserByIds((List<Long>) userIds);
            users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
        }
        // 5. è½¬æ¢ä¸ºDTO
        List<TimingTaskDto> dtoList = taskPage.getRecords().stream().map(task -> {
            TimingTaskDto dto = new TimingTaskDto();
            // å¤åˆ¶åŸºæœ¬å±žæ€§
            BeanUtils.copyProperties(task, dto);
            // è®¾ç½®ç™»è®°äººæ˜µç§°
            if (task.getRegistrantId() != null) {
                dto.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
            }
            // è®¾ç½®å·¡æ£€äººæ˜µç§°åˆ—表
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                List<String> inspectorNickNames = Arrays.stream(task.getInspectorIds().split(","))
                        .filter(StringUtils::isNotBlank)
                        .map(idStr -> {
                            Long id = Long.valueOf(idStr);
                            return userNickNameMap.getOrDefault(id, "未知用户");
                        })
                        .toList();
                dto.setInspector(inspectorNickNames);
            }
            return dto;
        }).collect(Collectors.toList());
        // 6. æž„建返回的分页对象
        Page<TimingTaskDto> resultPage = new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        resultPage.setRecords(dtoList);
        return resultPage;
    }
    @Override
    @Transactional
    public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
        TimingTask timingTask = new TimingTask();
        BeanUtils.copyProperties(timingTaskDto, timingTask);
        // è®¾ç½®åˆ›å»ºäººä¿¡æ¯å’Œé»˜è®¤å€¼
        if (Objects.isNull(timingTaskDto.getId())) {
            timingTask.setRegistrationDate(LocalDate.now());
            timingTask.setActive(true);
            // è®¡ç®—首次执行时间
            LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
            timingTask.setNextExecutionTime(firstExecutionTime);
            int result = timingTaskMapper.insert(timingTask);
            if (result > 0) {
                // æ–°å¢žæˆåŠŸåŽæ·»åŠ åˆ°è°ƒåº¦å™¨
                timingTaskScheduler.scheduleTimingTask(timingTask);
            }
            return result;
        } else {
            int result = timingTaskMapper.updateById(timingTask);
            if (result > 0) {
                // æ›´æ–°æˆåŠŸåŽé‡æ–°è°ƒåº¦ä»»åŠ¡
                timingTaskScheduler.rescheduleTimingTask(timingTask);
            }
            return result;
        }
    }
    private LocalDateTime calculateFirstExecutionTime(TimingTask task) {
        // æ ¹æ®é¢‘率类型和详情计算首次执行时间
        return switch (task.getFrequencyType()) {
            case "DAILY" ->
                    // å¦‚果是每天执行,计算今天或明天的具体时间
                    calculateDailyFirstExecution(task.getFrequencyDetail());
            case "WEEKLY" ->
                    // å¦‚果是每周执行,计算下周的具体星期几
                    calculateWeeklyFirstExecution(task.getFrequencyDetail());
            case "MONTHLY" ->
                    // å¦‚果是每月执行,计算下个月的具体日期
                    calculateMonthlyFirstExecution(task.getFrequencyDetail());
            case "QUARTERLY" ->
                    // è‡ªå®šä¹‰é¢‘率,如每小时、每30分钟等
                    calculateCustomFirstExecution(task.getFrequencyDetail());
            default -> throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
        };
    }
    private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
        // frequencyDetail可能是具体时间,如 "14:30"
        LocalTime executionTime = LocalTime.parse(frequencyDetail);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayExecution = LocalDateTime.of(now.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天执行
        return now.isBefore(todayExecution) ? todayExecution : todayExecution.plusDays(1);
    }
    private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
        return null;
    }
    private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
        return null;
    }
    private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
        return null;
    }
    @Override
    @Transactional
    public void updateTaskExecutionTime(Long taskId) {
        TimingTask task = timingTaskMapper.selectById(taskId);
        if (task == null) {
            throw new RuntimeException("定时任务不存在,ID: " + taskId);
        }
        // æ›´æ–°æœ€åŽæ‰§è¡Œæ—¶é—´ä¸ºå½“前时间
        task.setLastExecutionTime(LocalDateTime.now());
        // è®¡ç®—下次执行时间
        LocalDateTime nextExecutionTime = calculateNextExecutionTime(
                task.getFrequencyType(),
                task.getFrequencyDetail(),
                LocalDateTime.now()
        );
        task.setNextExecutionTime(nextExecutionTime);
        // æ›´æ–°æ•°æ®åº“
        timingTaskMapper.updateById(task);
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType,
                                                     String frequencyDetail,
                                                     LocalDateTime currentTime) {
        try {
            return switch (frequencyType) {
                case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
                case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
                case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
                case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
                default -> throw new IllegalArgumentException("不支持的频率类型: " + frequencyType);
            };
        } catch (Exception e) {
            throw new RuntimeException("计算下次执行时间失败: " + e.getMessage(), e);
        }
    }
    /**
     * è®¡ç®—每日任务的下次执行时间
     */
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr); // è§£æžæ ¼å¼ "HH:mm"
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    /**
     * è®¡ç®—每周任务的下次执行时间
     */
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];  // å¦‚ "MON" æˆ– "MON|WED|FRI"
        LocalTime time = LocalTime.parse(parts[1]); // æ—¶é—´éƒ¨åˆ†
        // è§£æžæ˜ŸæœŸå‡ (支持多个星期)
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        // ä»Žå½“前时间开始找下一个符合条件的星期几
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
            // é˜²æ­¢æ— é™å¾ªçޝ(理论上不会发生)
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
    /**
     * è®¡ç®—每月任务的下次执行时间
     */
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        // ä»Žä¸‹ä¸ªæœˆå¼€å§‹è®¡ç®—
        LocalDateTime nextTime = current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
        return nextTime;
    }
    /**
     * è®¡ç®—每季度任务的下次执行时间
     */
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        // è®¡ç®—当前季度
        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            // æœ¬å­£åº¦å†…还有执行机会
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            // éœ€è¦åˆ°ä¸‹ä¸ªå­£åº¦
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        // å¤„理月末日期
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(
                targetYearMonth.getYear(),
                targetYearMonth.getMonthValue(),
                adjustedDay,
                time.getHour(),
                time.getMinute()
        );
    }
    /**
     * è§£æžæ˜ŸæœŸå‡ å­—符串
     */
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
        for (String dayStr : dayStrs) {
            switch (dayStr) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
                default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
            }
        }
        return days;
    }
    @Override
    public int delByIds(Long[] ids) {
        return timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
    }
}
src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,67 @@
package com.ruoyi.procurementrecord.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
import com.ruoyi.procurementrecord.service.ProcurementPlanService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/9/18 16:13
 */
@RestController
@Api(tags = "采购计划")
@RequestMapping("/procurementPlan")
public class ProcurementPlanController extends BaseController {
    @Autowired
    private ProcurementPlanService procurementPlanService;
    @RequestMapping("/listPage")
    @ApiOperation("采购计划-查询")
    public AjaxResult listPage(Page page, ProcurementPlan procurementPlan){
        IPage<ProcurementPlan> result = procurementPlanService.listPage(page, procurementPlan);
        return AjaxResult.success(result);
    }
    @PostMapping("/add")
    @ApiOperation("采购计划-添加")
    public AjaxResult add(@RequestBody ProcurementPlan procurementPlan){
        boolean result = procurementPlanService.save(procurementPlan);
        return result ? AjaxResult.success() : AjaxResult.error();
    }
    @PostMapping("/update")
    @ApiOperation("采购计划-修改")
    public AjaxResult update(@RequestBody ProcurementPlan procurementPlan){
        boolean result = procurementPlanService.updateById(procurementPlan);
        return result ? AjaxResult.success() : AjaxResult.error();
    }
    @DeleteMapping("/del")
    @ApiOperation("采购计划-删除")
    public AjaxResult del(@RequestBody List<Long> ids){
        boolean result = procurementPlanService.removeByIds(ids);
        return result ? AjaxResult.success() : AjaxResult.error();
    }
    /**
     * å¯¼å‡º
     * @param response
     */
    @PostMapping("/export")
    public void export(HttpServletResponse response) {
        procurementPlanService.export(response);
    }
}
src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package com.ruoyi.procurementrecord.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
import org.apache.ibatis.annotations.Param;
/**
 * @author :yys
 * @date : 2025/9/18 16:10
 */
public interface ProcurementPlanMapper extends BaseMapper<ProcurementPlan> {
    /**
     * æŸ¥è¯¢é‡‡è´­è®¡åˆ’列表
     *
     * @param page
     * @param procurementPlan
     * @return
     */
    IPage<ProcurementPlan> listPage(Page page,@Param("req") ProcurementPlan procurementPlan);
}
src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
package com.ruoyi.procurementrecord.pojo;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/18 16:00
 */
@Data
@TableName("procurement_plan")
@ApiModel
public class ProcurementPlan {
    private static final long serialVersionUID = 1L;
    /**
     * åºå·
     */
    @TableId(type = IdType.AUTO)
    private Long id;
    @ApiModelProperty(value = "编码")
    @Excel(name = "编码")
    private String code;
    @ApiModelProperty(value = "名称")
    @Excel(name = "名称")
    private String planName;
    @ApiModelProperty(value = "描述")
    @Excel(name = "描述")
    private String description;
    @ApiModelProperty(value = "状态")
    @Excel(name = "状态", readConverterExp = "disabled=禁用,active=启用")
    private String status;
    @ApiModelProperty(value = "是否系统预置")
    private Boolean isSystemPreset;
    @ApiModelProperty(value = "考虑现有库存")
    private Boolean considerExistingStock;
    @ApiModelProperty(value = "仓库运行MRP的控制")
    private Boolean warehouseControl;
    @ApiModelProperty(value = "计算总需求")
    private Boolean calculateTotalDemand;
    @ApiModelProperty(value = "考虑安全库存")
    private Boolean considerSafetyStock;
    @ApiModelProperty(value = "考虑锁库")
    private Boolean considerLockedStock;
    @ApiModelProperty(value = "不考虑物料辅助属性")
    private Boolean notConsiderMaterialAux;
    @ApiModelProperty(value = "负库存作为需求")
    private Boolean negativeStockAsDemand;
    @ApiModelProperty(value = "物料")
    private Boolean summaryMaterial;
    @ApiModelProperty(value = "辅助属性")
    private Boolean summaryAuxAttributes;
    @ApiModelProperty(value = "需求日期")
    private Boolean summaryDemandDate;
    @ApiModelProperty(value = "计算公式")
    @Excel(name = "计算公式")
    private String formula;
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "修改时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "最后计算时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "修改用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.procurementrecord.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
import javax.servlet.http.HttpServletResponse;
/**
 * @author :yys
 * @date : 2025/9/18 16:11
 */
public interface ProcurementPlanService extends IService<ProcurementPlan> {
    /**
     * æŸ¥è¯¢
     * @param page
     * @param procurementPlan
     * @return
     */
    IPage<ProcurementPlan> listPage(Page page, ProcurementPlan procurementPlan);
    void export(HttpServletResponse response);
}
src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.procurementrecord.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.procurementrecord.mapper.ProcurementPlanMapper;
import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
import com.ruoyi.procurementrecord.pojo.ProcurementPriceManagement;
import com.ruoyi.procurementrecord.service.ProcurementPlanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/9/18 16:12
 */
@Service
@Slf4j
public class ProcurementPlanServiceImpl extends ServiceImpl<ProcurementPlanMapper, ProcurementPlan> implements ProcurementPlanService {
    @Autowired
    private ProcurementPlanMapper procurementPlanMapper;
    @Override
    public IPage<ProcurementPlan> listPage(Page page, ProcurementPlan procurementPlan) {
        IPage<ProcurementPlan> result = procurementPlanMapper.listPage(page, procurementPlan);
        return result;
    }
    @Override
    public void export(HttpServletResponse response) {
        List<ProcurementPlan> procurementPriceManagements = procurementPlanMapper.selectList(null);
        ExcelUtil<ProcurementPlan> util = new ExcelUtil<ProcurementPlan>(ProcurementPlan.class);
        util.exportExcel(response, procurementPriceManagements, "采购计划");
    }
}
src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java
@@ -1,10 +1,13 @@
package com.ruoyi.project.system.mapper;
import java.util.ArrayList;
import java.util.List;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.project.system.domain.SysUser;
import org.springframework.beans.PropertyValues;
/**
 * ç”¨æˆ·è¡¨ æ•°æ®å±‚
@@ -134,4 +137,8 @@
     * @return ç»“æžœ
     */
    public SysUser checkEmailUnique(String email);
    List<SysUser> selectList(List<Long> registrantIds);
    List<SysUser> selectUsersByIds(@Param("userIds") List<Long> userIds);
}
src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
<?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.ruoyi.procurementrecord.mapper.ProcurementPlanMapper">
    <select id="listPage" resultType="com.ruoyi.procurementrecord.pojo.ProcurementPlan">
        SELECT * FROM procurement_plan
        <where>
            <if test="req.status != null and req.status != ''">
                AND status = #{req.status}
            </if>
            <if test="req.planName != null and req.planName != ''">
                AND plan_name LIKE CONCAT('%',#{req.planName},'%')
            </if>
        </where>
    </select>
</mapper>
src/main/resources/mapper/system/SysUserMapper.xml
@@ -158,6 +158,28 @@
             #{item}
        </foreach>
    </select>
    <select id="selectList" resultType="com.ruoyi.project.system.domain.SysUser">
        SELECT user_id, nick_name FROM sys_user
        <where>
            <if test="list != null and list.size() > 0">
                user_id IN
                <foreach item="id" collection="list" open="(" separator="," close=")">
                    #{id}
                </foreach>
            </if>
            <if test="list == null or list.size() == 0">
                1=0  <!-- ç©ºåˆ—表时返回空结果 -->
            </if>
        </where>
    </select>
    <select id="selectUsersByIds" resultType="com.ruoyi.project.system.domain.SysUser">
        SELECT user_id, nick_name
        FROM sys_user
        WHERE user_id IN
        <foreach collection="userIds" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>
    <insert id="insertUser" parameterType="com.ruoyi.project.system.domain.SysUser" useGeneratedKeys="true" keyProperty="userId">
         insert into sys_user(