| | |
| | | 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 cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; |
| | | import jakarta.annotation.Resource; |
| | | import org.springframework.jdbc.core.JdbcTemplate; |
| | | import org.springframework.jdbc.support.GeneratedKeyHolder; |
| | |
| | | @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; |
| | | @Resource private SecurityFrameworkService security; |
| | | 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 byte[] getImage(long id) { |
| | | var rows = jdbc.queryForList("SELECT image_data FROM pd_seal WHERE id=? AND tenant_id=? AND deleted=0", id, tenant()); |
| | | if (rows.isEmpty()) throw fail("印章不存在"); |
| | | return (byte[]) rows.get(0).get("image_data"); |
| | | } |
| | | public long upload(String name, MultipartFile file, Long userId) throws Exception { |
| | | checkName(name); |
| | | byte[] data = readImage(file); |
| | | String imageHash = hash(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,imageHash); ps.setLong(4,userId); ps.setLong(5,tenant()); return ps; }, keys); |
| | | return Objects.requireNonNull(keys.getKey()).longValue(); |
| | | } |
| | | public void update(long id, String name, MultipartFile file) throws Exception { |
| | | checkName(name); |
| | | int updated; |
| | | if (file == null || file.isEmpty()) { |
| | | updated = jdbc.update("UPDATE pd_seal SET name=? WHERE id=? AND tenant_id=? AND deleted=0", name.trim(), id, tenant()); |
| | | } else { |
| | | byte[] data = readImage(file); |
| | | updated = jdbc.update("UPDATE pd_seal SET name=?,image_data=?,image_hash=? WHERE id=? AND tenant_id=? AND deleted=0", name.trim(), data, hash(data), id, tenant()); |
| | | } |
| | | if (updated != 1) throw fail("印章不存在"); |
| | | } |
| | | public void delete(long id) { |
| | | Integer pending = jdbc.queryForObject("SELECT COUNT(*) FROM pd_seal_application WHERE seal_id=? AND tenant_id=? AND status=1", Integer.class, id, tenant()); |
| | | if (pending != null && pending > 0) throw fail("该印章有进行中的用印审批申请,请待审批完成后再删除"); |
| | | if (jdbc.update("UPDATE pd_seal SET deleted=1 WHERE id=? AND tenant_id=? AND deleted=0", id, tenant()) != 1) throw fail("印章不存在"); |
| | | } |
| | | private void checkName(String name) { |
| | | if (name == null || name.isBlank() || name.length() > 100) throw fail("印章名称不能为空且不能超过100字"); |
| | | } |
| | | private byte[] readImage(MultipartFile file) throws Exception { |
| | | 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}; |
| | |
| | | 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(); |
| | | return data; |
| | | } |
| | | private String hash(byte[] data) throws Exception { |
| | | return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256").digest(data)); |
| | | } |
| | | 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("印章不存在"); |
| | |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | | * 审批详情:查询用印申请的业务信息(含使用的印章名称与申请时间等) |
| | | * |
| | | * 可见范围:申请人本人、该流程实例的审批人(含已办)、以及具备用印/项目/流程查询权限的管理人员。 |
| | | */ |
| | | public Map<String,Object> applicationDetail(long id, long userId) { |
| | | var rows = jdbc.queryForList("SELECT a.id,a.project_id,COALESCE(a.project_code,p.task_code) project_code,p.task_name project_name," |
| | | + "a.seal_id,a.seal_name,a.reason,a.status,a.process_instance_id,a.applicant_id," |
| | | + "u.nickname applicant_name,a.create_time,a.finish_time " |
| | | + "FROM pd_seal_application a " |
| | | + "LEFT JOIN mes_pd_project p ON p.id=a.project_id AND p.tenant_id=a.tenant_id " |
| | | + "LEFT JOIN system_users u ON u.id=a.applicant_id " |
| | | + "WHERE a.id=? AND a.tenant_id=?", id, tenant()); |
| | | if (rows.isEmpty()) throw fail("用印申请不存在"); |
| | | var row = rows.get(0); |
| | | if (!canViewApplication(row, userId)) throw fail("无权查看该用印申请,仅申请人、本流程审批人和用印管理人员可查看"); |
| | | return row; |
| | | } |
| | | |
| | | /** |
| | | * 审批详情:获取申请时使用的印章图片(申请提交时已快照,后续更换印章图片不影响历史记录) |
| | | */ |
| | | public byte[] applicationImage(long id, long userId) { |
| | | var rows = jdbc.queryForList("SELECT seal_image,applicant_id,process_instance_id FROM pd_seal_application WHERE id=? AND tenant_id=?", id, tenant()); |
| | | if (rows.isEmpty()) throw fail("用印申请不存在"); |
| | | var row = rows.get(0); |
| | | if (!canViewApplication(row, userId)) throw fail("无权查看该用印申请,仅申请人、本流程审批人和用印管理人员可查看"); |
| | | return (byte[]) row.get("seal_image"); |
| | | } |
| | | |
| | | private boolean canViewApplication(Map<String,Object> row, long userId) { |
| | | Object applicant = row.get("applicant_id"); |
| | | if (applicant instanceof Number number && number.longValue() == userId) return true; |
| | | // 用印申请人/审批人之外的可见范围:项目、印章、流程/待办的管理与审批权限 |
| | | if (security.hasAnyPermissions("mes:pd-project:export", "mes:pd-seal:manage", |
| | | "bpm:process-instance:query", "bpm:process-instance:manager-query", |
| | | "bpm:task:update", "bpm:task:manager-query")) return true; |
| | | Object instance = row.get("process_instance_id"); |
| | | if (instance == null || instance.toString().isBlank()) return false; |
| | | return taskService.getTaskListByProcessInstanceId(instance.toString(), true).stream() |
| | | .anyMatch(task -> task.getAssignee() != null && String.valueOf(userId).equals(task.getAssignee())); |
| | | } |
| | | @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); |