拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ERP生产单图片文档管理:技术实现与最佳实践

今天我们来解决一个企业信息化中的常见需求如何在ERP软件的生产单模块中实现图片和文档的添加功能。对于制造企业来说生产单是核心业务单据传统ERP系统往往只支持文本信息录入但在实际生产过程中产品图纸、工艺文件、质检照片等非结构化数据的关联管理同样重要。这个功能实现的核心价值在于将生产相关的所有资料集中管理避免信息孤岛提高生产执行效率。无论是机械加工的产品图纸还是食品行业的质检照片或是电子行业的BOM配图都能与生产单建立直接关联。1. 核心能力速览能力项技术实现说明图片支持格式JPG、PNG、GIF、BMP等常见图片格式文档支持类型PDF、Word、Excel、TXT等办公文档存储方式数据库BLOB存储或文件服务器存储前端展示图片预览、文档在线查看/下载权限控制按角色设置上传、查看、下载权限批量操作支持多文件同时上传批量关联接口支持提供REST API供其他系统调用2. 适用场景与使用边界适用场景机械制造业产品图纸、工艺卡片与生产单关联食品行业原料检验报告、成品质检照片上传电子行业PCB设计文件、BOM配图管理服装行业样衣照片、面料样板图片管理所有需要将非结构化数据与结构化业务单据关联的场景使用边界文件大小限制通常单文件不超过10MB避免影响系统性能格式兼容性需明确支持的格式范围避免用户上传无法预览的文件存储容量根据企业数据量规划存储空间定期清理无用文件版权合规上传的文件必须确保有合法授权避免侵权风险3. 技术架构设计3.1 数据库设计扩展在生产单主表的基础上需要新增附件关联表CREATE TABLE production_order_attachments ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL COMMENT 生产单ID, file_name VARCHAR(255) NOT NULL COMMENT 原始文件名, file_path VARCHAR(500) NOT NULL COMMENT 存储路径, file_type VARCHAR(50) COMMENT 文件类型image/doc/pdf等, file_size INT COMMENT 文件大小(字节), upload_time DATETIME DEFAULT CURRENT_TIMESTAMP, upload_user_id INT COMMENT 上传用户ID, description VARCHAR(500) COMMENT 文件描述, FOREIGN KEY (order_id) REFERENCES production_orders(id) );3.2 存储方案选择方案一数据库BLOB存储// 适用于小文件存储管理简单 Column(name file_content) Lob private byte[] fileContent;方案二文件系统存储// 适用于大文件性能更好 public String saveToFileSystem(MultipartFile file, String orderNo) { String filePath /erp/files/production/ orderNo /; File directory new File(filePath); if (!directory.exists()) { directory.mkdirs(); } // 保存文件逻辑 }方案三云存储集成// 适用于分布式部署扩展性强 public String uploadToCloudStorage(MultipartFile file) { // 阿里云OSS、腾讯云COS等云存储集成 }4. 前端界面设计与实现4.1 图片上传组件使用现代前端框架实现拖拽上传和预览功能template div classupload-container el-upload action/api/production/upload :data{ orderId: currentOrderId } :on-successhandleSuccess :before-uploadbeforeUpload multiple list-typepicture-card i classel-icon-plus/i /el-upload /div /template script export default { methods: { beforeUpload(file) { const isImage /\.(jpg|jpeg|png|gif)$/i.test(file.name); const isLt10M file.size / 1024 / 1024 10; if (!isImage) { this.$message.error(只能上传图片文件!); return false; } if (!isLt10M) { this.$message.error(图片大小不能超过10MB!); return false; } return true; }, handleSuccess(response, file) { this.$emit(upload-success, response.data); } } } /script4.2 文档管理界面实现文档列表展示和操作功能div classdocument-list div v-fordoc in documents :keydoc.id classdocument-item div classdoc-icon i :classgetFileIcon(doc.fileType)/i /div div classdoc-info div classdoc-name{{ doc.fileName }}/div div classdoc-meta span{{ formatFileSize(doc.fileSize) }}/span span{{ formatTime(doc.uploadTime) }}/span /div /div div classdoc-actions button clickpreviewDoc(doc)预览/button button clickdownloadDoc(doc)下载/button button clickdeleteDoc(doc) v-ifhasDeletePermission删除/button /div /div /div5. 后端接口实现5.1 文件上传接口RestController RequestMapping(/api/production) public class ProductionAttachmentController { PostMapping(/upload) public ResponseEntityUploadResult uploadFile( RequestParam(file) MultipartFile file, RequestParam(orderId) Long orderId, RequestParam(value description, required false) String description) { try { // 验证文件大小和类型 if (file.isEmpty()) { return ResponseEntity.badRequest().body(UploadResult.error(文件为空)); } if (file.getSize() 10 * 1024 * 1024) { return ResponseEntity.badRequest().body(UploadResult.error(文件大小超过限制)); } // 保存文件 String savedPath fileStorageService.store(file, orderId); // 保存到数据库 ProductionAttachment attachment new ProductionAttachment(); attachment.setOrderId(orderId); attachment.setFileName(file.getOriginalFilename()); attachment.setFilePath(savedPath); attachment.setFileType(getFileType(file.getOriginalFilename())); attachment.setFileSize(file.getSize()); attachment.setDescription(description); attachmentService.save(attachment); return ResponseEntity.ok(UploadResult.success(attachment)); } catch (Exception e) { return ResponseEntity.status(500).body(UploadResult.error(上传失败)); } } }5.2 文件预览接口GetMapping(/preview/{attachmentId}) public void previewFile(PathVariable Long attachmentId, HttpServletResponse response) { ProductionAttachment attachment attachmentService.findById(attachmentId); if (attachment null) { response.setStatus(404); return; } try { String filePath attachment.getFilePath(); File file new File(filePath); if (!file.exists()) { response.setStatus(404); return; } // 设置响应头 response.setContentType(getContentType(attachment.getFileType())); response.setHeader(Content-Disposition, inline; filename\ URLEncoder.encode(attachment.getFileName(), UTF-8) \); // 输出文件流 Files.copy(file.toPath(), response.getOutputStream()); response.flushBuffer(); } catch (IOException e) { response.setStatus(500); } }6. 批量任务处理6.1 批量上传实现对于需要批量关联图片文档的场景实现批量上传功能Service public class BatchAttachmentService { Async(taskExecutor) public CompletableFutureBatchUploadResult batchUpload( Long orderId, ListMultipartFile files) { BatchUploadResult result new BatchUploadResult(); ListProductionAttachment successList new ArrayList(); ListUploadError errorList new ArrayList(); for (int i 0; i files.size(); i) { MultipartFile file files.get(i); try { ProductionAttachment attachment processSingleFile(orderId, file); successList.add(attachment); } catch (Exception e) { errorList.add(new UploadError(file.getOriginalFilename(), e.getMessage())); } } result.setSuccessCount(successList.size()); result.setErrorCount(errorList.size()); result.setSuccessAttachments(successList); result.setErrors(errorList); return CompletableFuture.completedFuture(result); } }6.2 定时清理任务设置定时任务清理临时文件和过期附件Component public class AttachmentCleanupTask { Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void cleanupTempFiles() { // 清理超过30天的临时文件 File tempDir new File(/erp/temp/); cleanupDirectory(tempDir, 30); // 清理标记为删除的附件文件 ListProductionAttachment deletedAttachments attachmentService.findDeletedAttachments(); for (ProductionAttachment attachment : deletedAttachments) { deletePhysicalFile(attachment.getFilePath()); attachmentService.physicalDelete(attachment.getId()); } } }7. 性能优化策略7.1 图片压缩与缩略图Service public class ImageProcessingService { public void generateThumbnail(String originalPath, String thumbnailPath, int width, int height) { try { BufferedImage originalImage ImageIO.read(new File(originalPath)); BufferedImage thumbnail new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d thumbnail.createGraphics(); g2d.drawImage(originalImage.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null); g2d.dispose(); ImageIO.write(thumbnail, JPEG, new File(thumbnailPath)); } catch (IOException e) { throw new RuntimeException(生成缩略图失败, e); } } public void compressImage(String sourcePath, String targetPath, float quality) { // 图片质量压缩实现 } }7.2 文件存储优化分级存储策略热数据最近3个月的生产单附件使用SSD存储温数据3个月到1年的附件使用普通硬盘存储冷数据1年以上的附件归档到低成本存储CDN加速对于多分支机构的企业可以使用CDN加速附件访问public String getFileUrl(ProductionAttachment attachment) { if (isImageFile(attachment.getFileType())) { // 图片文件使用CDN加速 return cdnConfig.getImageBaseUrl() attachment.getFilePath(); } else { // 文档文件直接访问 return fileServerConfig.getBaseUrl() /download/ attachment.getId(); } }8. 安全与权限控制8.1 文件上传安全Component public class FileSecurityValidator { public void validateFile(MultipartFile file) { // 检查文件类型 String originalFilename file.getOriginalFilename(); if (!isAllowedFileType(originalFilename)) { throw new SecurityException(不支持的文件类型); } // 检查文件内容 if (!isSafeFileContent(file)) { throw new SecurityException(文件内容不安全); } // 检查文件大小 if (file.getSize() maxFileSize) { throw new SecurityException(文件大小超过限制); } } private boolean isSafeFileContent(MultipartFile file) { // 实际项目中应使用专业的文件内容检测库 // 这里简化为检查文件头信息 try { byte[] header new byte[4]; file.getInputStream().read(header); return isValidFileHeader(header); } catch (IOException e) { return false; } } }8.2 访问权限控制基于RBAC模型的权限控制PreAuthorize(hasPermission(#orderId, PRODUCTION_ORDER, READ)) GetMapping(/{orderId}/attachments) public ListProductionAttachment getOrderAttachments(PathVariable Long orderId) { return attachmentService.findByOrderId(orderId); } PreAuthorize(hasPermission(#orderId, PRODUCTION_ORDER, UPLOAD)) PostMapping(/{orderId}/upload) public ResponseEntity? uploadToOrder(PathVariable Long orderId, RequestParam MultipartFile file) { // 上传逻辑 }9. 移动端适配9.1 移动端上传优化针对移动设备的特点进行优化// 移动端图片压缩 function compressImageForMobile(file, quality 0.7) { return new Promise((resolve) { const canvas document.createElement(canvas); const ctx canvas.getContext(2d); const img new Image(); img.onload function() { // 根据设备屏幕尺寸调整图片大小 const maxWidth 1024; const maxHeight 1024; let { width, height } calculateSize(img, maxWidth, maxHeight); canvas.width width; canvas.height height; ctx.drawImage(img, 0, 0, width, height); canvas.toBlob(resolve, image/jpeg, quality); }; img.src URL.createObjectURL(file); }); }9.2 离线缓存策略对于移动端离线使用的场景// 离线缓存生产单附件 function cacheAttachmentsForOffline(orderId, attachments) { if (caches in window) { caches.open(production-order-${orderId}).then(cache { attachments.forEach(attachment { cache.add(attachment.url); }); }); } }10. 系统集成与API扩展10.1 第三方系统集成提供标准的REST API供其他系统调用RestController RequestMapping(/api/external) public class ExternalAttachmentApi { PostMapping(/v1/production-orders/{orderId}/attachments) public ResponseEntityApiResponse addAttachment( PathVariable String orderId, Valid RequestBody AttachmentCreateRequest request) { // 验证API密钥 if (!apiKeyService.isValid(request.getApiKey())) { return ResponseEntity.status(401).build(); } // 处理文件上传 ProductionAttachment attachment attachmentService.addExternalAttachment( orderId, request.getFileUrl(), request.getFileName()); return ResponseEntity.ok(ApiResponse.success(attachment)); } }10.2 消息通知集成文件上传后的消息通知机制Service public class AttachmentNotificationService { EventListener Async public void handleAttachmentUploaded(AttachmentUploadedEvent event) { ProductionAttachment attachment event.getAttachment(); // 发送内部通知 notificationService.sendToProductionManager( attachment.getOrderId(), 新的生产单附件已上传, buildAttachmentMessage(attachment)); // 如果需要发送邮件通知 if (shouldSendEmailNotification(attachment)) { emailService.sendAttachmentNotification(attachment); } } }11. 监控与日志管理11.1 操作日志记录记录所有附件相关操作Aspect Component public class AttachmentLogAspect { AfterReturning(execution(* com.erp.attachment..*.*(..)) annotation(loggable)) public void logOperation(JoinPoint joinPoint, Loggable loggable) { Object[] args joinPoint.getArgs(); String operation loggable.value(); AttachmentLog log new AttachmentLog(); log.setOperation(operation); log.setOperator(getCurrentUser()); log.setOperationTime(new Date()); log.setDetails(buildLogDetails(args)); attachmentLogService.save(log); } }11.2 性能监控监控附件上传下载性能RestController RequestMapping(/api/attachment) public class AttachmentMetricsController { GetMapping(/metrics) public AttachmentMetrics getMetrics() { Metrics metrics new Metrics(); metrics.setUploadCount(attachmentService.getTodayUploadCount()); metrics.setTotalStorage(attachmentService.getTotalStorageUsage()); metrics.setAvgUploadTime(attachmentService.getAverageUploadTime()); metrics.setErrorRate(attachmentService.getErrorRate()); return metrics; } }通过以上完整的技术方案企业可以在现有ERP系统的基础上快速实现生产单图片和文档的添加功能。这个方案考虑了性能、安全、移动端适配等实际需求提供了从数据库设计到前端展示的完整实现思路。在实际实施过程中建议先在小范围试点验证系统稳定性和用户体验然后逐步推广到全公司使用。同时要建立完善的文件管理规范确保系统长期稳定运行。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门