尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
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系统的基础上快速实现生产单图片和文档的添加功能。这个方案考虑了性能、安全、移动端适配等实际需求提供了从数据库设计到前端展示的完整实现思路。在实际实施过程中建议先在小范围试点验证系统稳定性和用户体验然后逐步推广到全公司使用。同时要建立完善的文件管理规范确保系统长期稳定运行。
RELATED

相关推荐

H5页面一键分层实战:用AI Skill提取图层树与设计资源

H5页面一键分层实战:用AI Skill提取图层树与设计资源

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/5 19:24:16
阿里云Qoder黑客松实战:从代码生成到开发流程嵌入

阿里云Qoder黑客松实战:从代码生成到开发流程嵌入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/26 4:22:43
RISC-V入门指南:开放指令集架构的原理、生态与实战

RISC-V入门指南:开放指令集架构的原理、生态与实战

1. RISC-V为什么突然这么火:从一段往事说起大概七八年前,我在一个嵌入式技术交流群里看到有人转发一条新闻,说加州大学伯克利分校推出了一套全新的开源指令集架构,叫RISC-V。当时群里反应很冷淡,有人觉得这只是又一个学…

📅 2026/11/1 22:23:00
MORE NEWS

更多资讯

📰

API-first设计提升SaaS开发者体验的关键实践

1. 项目背景与行业痛点在当前的SaaS行业生态中,开发者体验正成为产品竞争力的关键指标。过去三年间,我们观察到头部SaaS产品的API调用量年均增长达到217%,但仍有67%的集成项目因接口设计问题导致交付延期。典型的开发者痛点包括:接…

📰

Forest Pack 7 文档精读:3ds Max 大场景植被散布与农场部署

简介:Forest Pack 7帮助文档PDF面向3ds Max建筑可视化、游戏开发与影视特效场景制作者,聚焦森林树木植物及大规模物体散布场景的快速搭建与问题排查。包内仅含1个PDF文件,约9.82MB,内容为插件帮助文档,目录按主题编排&…

📰

Forest Pack 7帮助文档:3ds Max散布调参与渲染农场避坑

简介:面向3ds Max建筑可视化、游戏场景与影视特效制作者的Forest Pack 7英文帮助文档,集中解决森林树木、草地、人群、岩石及地面覆盖物等大规模散布场景的学习与查证问题,适合已具备基础建模操作、希望系统掌握插件参数与工作流的中级用户。…

📰

零基础IDA反汇编入门:从字符串定位到结构体识别

简介:《IDA简易教程共十四章(图)》是一份面向逆向工程初学者与安全分析入门者的图文教程,围绕 IDA 反汇编器的基础使用展开,帮助读者从简单 C 程序入手理解 IDB 分析、二进制代码查看与数据结构识别。教程按章节讲解基…

📰

大数据架构下时间序列数据处理与优化实践

1. 时间序列分析在大数据架构中的核心价值 时间序列数据是大数据生态中最常见的数据类型之一。从物联网设备传感器读数、金融交易记录到服务器监控指标,这类按时间顺序排列的数据点构成了企业数据资产的重要部分。传统的关系型数据库在处理高频时间序列数据时往往面…

📰

RAG翻车后,我总结了AI数据治理的四个关键落点

1. 从一次RAG翻车现场说起:数据治理到底卡在哪去年帮一个团队调一套RAG知识库,场景不复杂——把公司内部几百份产品文档、运维手册、客服话术灌进去,让大模型能回答一线同事的问题。模型选的是当时口碑不错的中文大模型,向量库用的…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬