feat(seal): 添加印章详情及图片预览功能
- 后端新增获取印章图片接口并设置安全响应头
- 服务层添加图片查询方法并补充对应单元测试
- 前端封装获取印章图片的API下载请求方法
- 列表页新增详情操作按钮及印章详情弹窗组件
- 实现图片异步加载、状态展示及失败重试机制
- 补充详情弹窗样式并在组件卸载时释放内存
| | |
| | | @Resource private MesPdProjectController projectController; |
| | | @GetMapping("/list") @PreAuthorize("@ss.hasPermission('mes:pd-seal:manage')") |
| | | public CommonResult<List<Map<String,Object>>> list(){ return success(service.seals(false)); } |
| | | @GetMapping("/image") @PreAuthorize("@ss.hasPermission('mes:pd-seal:manage')") |
| | | public void image(@RequestParam long id, HttpServletResponse response) throws java.io.IOException { |
| | | byte[] image = service.getImage(id); |
| | | response.setContentType("image/png"); |
| | | response.setHeader("Cache-Control", "no-store, private"); |
| | | response.setHeader("X-Content-Type-Options", "nosniff"); |
| | | response.getOutputStream().write(image); |
| | | } |
| | | @GetMapping("/available") @PreAuthorize("@ss.hasPermission('mes:pd-project:export')") |
| | | public CommonResult<List<Map<String,Object>>> available(){ return success(service.seals(true)); } |
| | | @PostMapping("/upload") @PreAuthorize("@ss.hasPermission('mes:pd-seal:manage')") |
| | |
| | | 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 { |
| | | 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印章图片"); |
| | |
| | | @InjectMocks PdSealService service; |
| | | @Mock JdbcTemplate jdbc; |
| | | @Mock BpmTaskService tasks; |
| | | @Test void imageLookupIsScopedToCurrentTenant() { |
| | | cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.setTenantId(42L); |
| | | try { |
| | | byte[] image = {1, 2}; |
| | | when(jdbc.queryForList(anyString(), any(Object[].class))) |
| | | .thenReturn(List.of(Map.of("image_data", image))); |
| | | assertArrayEquals(image, service.getImage(7)); |
| | | verify(jdbc).queryForList(contains("tenant_id=? AND deleted=0"), eq(7L), eq(42L)); |
| | | } finally { |
| | | cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder.clear(); |
| | | } |
| | | } |
| | | @Test void refusesImageWhenSealIsNotFound() { |
| | | when(jdbc.queryForList(anyString(), any(Object[].class))).thenReturn(List.of()); |
| | | assertThrows(ServiceException.class, () -> service.getImage(7)); |
| | | } |
| | | @Test void refusesDownloadWithoutApprovedApplication() { |
| | | when(jdbc.queryForList(anyString(), any(Object[].class))).thenReturn(List.of()); |
| | | assertThrows(ServiceException.class, () -> service.download(1, 7)); |