云
4 天以前 bd961491fb449a92e7c357376073e138f99c5a29
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package cn.iocoder.yudao.module.mes.service.pd.seal;
 
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import java.io.*;
import java.sql.Statement;
import java.security.MessageDigest;
import java.util.*;
import java.util.zip.*;
 
@Service
public class PdSealService {
    public static final String PROCESS_KEY = "hrm-seal-use-approval";
    @Resource private JdbcTemplate jdbc;
    @Resource private BpmProcessInstanceApi bpm;
    @Resource private cn.iocoder.yudao.module.bpm.service.definition.BpmProcessAutoApproveService autoApprove;
    @Resource private cn.iocoder.yudao.module.bpm.service.task.BpmTaskService taskService;
    private long tenant() { Long id = cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.getTenantId(); return id == null ? 0L : id; }
    private ServiceException fail(String text) { return new ServiceException(1_040_990_001, text); }
 
    public List<Map<String,Object>> seals(boolean activeOnly) {
        return jdbc.queryForList("SELECT id,name,enabled,create_time FROM pd_seal WHERE tenant_id=? AND deleted=0" + (activeOnly ? " AND enabled=1" : "") + " ORDER BY id DESC", tenant());
    }
    public long upload(String name, MultipartFile file, Long userId) throws Exception {
        if (name == null || name.isBlank() || name.length() > 100) throw fail("印章名称不能为空且不能超过100字");
        if (file == null || file.isEmpty() || file.getSize() > 2 * 1024 * 1024) throw fail("请上传不超过2MB的PNG印章图片");
        byte[] data = file.getBytes();
        byte[] sig = {(byte)137,80,78,71,13,10,26,10};
        if (data.length < 8 || !Arrays.equals(sig, Arrays.copyOf(data, 8))) throw fail("仅支持PNG图片");
        try (var input = ImageIO.createImageInputStream(new ByteArrayInputStream(data))) {
            var readers = ImageIO.getImageReaders(input);
            if (!readers.hasNext()) throw fail("图片无法读取");
            var reader = readers.next();
            try { reader.setInput(input); if (reader.getWidth(0) > 2048 || reader.getHeight(0) > 2048) throw fail("图片尺寸不能超过2048×2048"); reader.read(0); }
            finally { reader.dispose(); }
        }
        String hash = HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(data));
        var keys = new GeneratedKeyHolder();
        jdbc.update(c -> { var ps=c.prepareStatement("INSERT INTO pd_seal(name,image_data,image_hash,enabled,created_by,tenant_id) VALUES(?,?,?,1,?,?)", Statement.RETURN_GENERATED_KEYS); ps.setString(1,name.trim()); ps.setBytes(2,data); ps.setString(3,hash); ps.setLong(4,userId); ps.setLong(5,tenant()); return ps; }, keys);
        return Objects.requireNonNull(keys.getKey()).longValue();
    }
    public void setEnabled(long id, boolean enabled) {
        if (jdbc.update("UPDATE pd_seal SET enabled=? WHERE id=? AND tenant_id=? AND deleted=0", enabled, id, tenant()) != 1) throw fail("印章不存在");
    }
    @Transactional(rollbackFor=Exception.class)
    public long apply(long projectId, String projectCode, long sealId, String reason, Long userId, byte[] snapshot) {
        if (reason == null || reason.isBlank() || reason.length() > 500) throw fail("请填写不超过500字的用印事由");
        if (autoApprove.isAutoApprove(PROCESS_KEY)) throw fail("用印流程开启了免审批,请先关闭后再提交");
        List<Map<String,Object>> seals=jdbc.queryForList("SELECT name,image_data FROM pd_seal WHERE id=? AND tenant_id=? AND enabled=1 AND deleted=0 FOR UPDATE",sealId,tenant());
        if (seals.isEmpty()) throw fail("印章不存在或已停用");
        var seal=seals.get(0); var keys=new GeneratedKeyHolder();
        jdbc.update(c -> { var ps=c.prepareStatement("INSERT INTO pd_seal_application(project_id,project_code,seal_id,seal_name,seal_image,document_snapshot,reason,applicant_id,status,tenant_id) VALUES(?,?,?,?,?,?,?,?,1,?)",Statement.RETURN_GENERATED_KEYS); ps.setLong(1,projectId); ps.setString(2,projectCode); ps.setLong(3,sealId); ps.setString(4,(String)seal.get("name")); ps.setBytes(5,(byte[])seal.get("image_data")); ps.setBytes(6,snapshot); ps.setString(7,reason.trim()); ps.setLong(8,userId); ps.setLong(9,tenant()); return ps; },keys);
        long id=Objects.requireNonNull(keys.getKey()).longValue();
        var variables=new HashMap<String,Object>(); variables.put("projectCode", projectCode); variables.put("sealName",seal.get("name")); variables.put("reason",reason.trim());
        String instance=bpm.createProcessInstance(userId,new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY).setBusinessKey(String.valueOf(id)).setVariables(variables));
        jdbc.update("UPDATE pd_seal_application SET process_instance_id=? WHERE id=? AND tenant_id=? AND (process_instance_id IS NULL OR process_instance_id=?)",instance,id,tenant(),instance);
        return id;
    }
    public void status(BpmProcessInstanceStatusEvent event) {
        if (!PROCESS_KEY.equals(event.getProcessDefinitionKey()) || event.getStatus() == null || event.getStatus() < 2 || event.getStatus() > 4) return;
        try { long id=Long.parseLong(event.getBusinessKey()); jdbc.update("UPDATE pd_seal_application SET status=?,process_instance_id=?,finish_time=NOW() WHERE id=? AND tenant_id=? AND status=1 AND (process_instance_id IS NULL OR process_instance_id=?)",event.getStatus(),event.getId(),id,tenant(),event.getId()); } catch (NumberFormatException ignored) { }
    }
    public List<Map<String,Object>> applications(long userId, Long projectId) {
        return jdbc.queryForList("SELECT id,project_id,project_code,seal_name,reason,status,process_instance_id,create_time,finish_time FROM pd_seal_application WHERE tenant_id=? AND applicant_id=?"+(projectId == null ? "" : " AND project_id="+projectId)+" ORDER BY id DESC LIMIT 200",tenant(),userId);
    }
    @Transactional(rollbackFor=Exception.class)
    public byte[] download(long id,long userId) throws IOException {
        var rows=jdbc.queryForList("SELECT document_snapshot,seal_image,process_instance_id FROM pd_seal_application WHERE id=? AND tenant_id=? AND applicant_id=? AND status=2",id,tenant(),userId);
        if(rows.isEmpty()) throw fail("申请不存在、无权下载或审批尚未通过");
        String instance=(String) rows.get(0).get("process_instance_id");
        if (instance == null || instance.isBlank()) throw fail("审批记录不完整,无法下载");
        boolean humanApproval = taskService.getTaskListByProcessInstanceId(instance, true).stream().anyMatch(task -> {
            var vars = task.getTaskLocalVariables();
            if (vars == null || !Integer.valueOf(2).equals(vars.get("TASK_STATUS")) || task.getAssignee() == null) return false;
            Object reason = vars.get("TASK_REASON");
            return Arrays.stream(cn.iocoder.yudao.module.bpm.enums.task.BpmReasonEnum.values()).noneMatch(item -> Objects.equals(item.getReason(), reason));
        });
        if (!humanApproval) throw fail("未找到人工审批通过记录,无法下载");
        byte[] snapshot=(byte[])rows.get(0).get("document_snapshot"), image=(byte[])rows.get(0).get("seal_image");
        var out=new ByteArrayOutputStream(); boolean found=false;
        try(var zin=new ZipInputStream(new ByteArrayInputStream(snapshot)); var zout=new ZipOutputStream(out)) { ZipEntry entry; while((entry=zin.getNextEntry())!=null){byte[] bytes=zin.readAllBytes(); if(entry.getName().equals("word/media/seal.png")){bytes=image;found=true;} zout.putNextEntry(new ZipEntry(entry.getName())); zout.write(bytes); zout.closeEntry();} }
        if(!found) throw fail("跟踪表模板缺少印章位置");
        jdbc.update("INSERT INTO pd_seal_download_log(application_id,user_id,tenant_id) VALUES(?,?,?)",id,userId,tenant());
        return out.toByteArray();
    }
}