ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于微信小程序与SpringBoot的农产品追溯系统设计与实现

基于微信小程序与SpringBoot的农产品追溯系统设计与实现 1. 从扫个码查产地说起农产品追溯到底在追什么去年接了个农产品溯源的项目需求方的原话是我们想让消费者扫一下包装上的二维码就能看到这箱苹果是哪片园子摘的、什么时候打的药、质检报告长什么样。听起来很简单做起来才发现一套真正能跑起来的农产品质量追溯系统从前端到后端、从数据模型到扫码链路涉及的东西比想象中多得多。我也是完整走了一遍基于微信小程序 SpringBoot uniapp的实现方案之后才把追溯这两个字背后的业务逻辑彻底理清楚。这篇文章就围绕这个系统把我的设计思路、核心实现和踩过的坑一次性讲透给准备做类似毕设或者实际项目的人一个可以直接参考的版本。1.1 消费者端的真实使用流程先站在用户视角走一遍流程你就知道这个系统到底要做什么了。消费者在超市或电商渠道买到一盒贴了追溯码的草莓包装上印着一个二维码旁边写着扫码溯源。用户打开微信扫一扫进入小程序直接跳转到这盒草莓的追溯详情页。页面上展示的信息包括产品名称、品牌、规格产地哪个省哪个市哪个合作社生产批次哪一天采摘、哪一条产线包装种植记录播种时间、施肥记录、农药使用记录加工记录分拣、清洗、包装的时间和负责人检测报告检测机构、检测项目、检测结论物流信息出库时间、运输方式、收货地再往下滑可能还有企业的资质证书、生产基地的实景照片、以及一个投诉举报的入口。整个链路下来用户不需要下载额外的App扫个码就能看到从田间到餐桌的关键信息。这个体验的底层支撑就是一个像样的追溯系统。1.2 三种角色对追溯系统的诉求差异做系统之前最先要搞明白的是谁在用这个系统不同角色要的东西完全不一样。消费者要的是可信。他们扫完码最关心的是信息全不全、能不能证明这产品是安全的。所以消费者端的核心是展示页面要清晰、信息要完整、来源要可查。企业/农户要的是省事。录入环节信息的操作员可能是合作社里四五十岁的大叔如果你给他们一个复杂的电脑后台他们根本不会用。所以企业端必须有一个操作门槛低、录入流程简单的工具手机能拍照、能填几个下拉框就行。监管方/平台方要的是可控。他们需要看到所有入驻企业的数据能做抽查、能追溯问题批次、能定位到具体环节责任人。所以在后端需要保留完整的数据链路每个环节的记录操作人和操作时间都不能丢。这三角色诉求的平衡决定了系统的功能模块划分也直接决定了技术选型的方向。1.3 追溯系统的业务闭环录入、查询、监管从业务流程上说一套追溯系统要跑通这样一个闭环企业入驻平台通过审核后获得录入权限企业在后台创建产品比如红富士苹果再创建批次比如2024年10月12日采摘的第3批在这个批次下分步骤录入从种植到销售各个环节的信息每一步都可以附加图片和操作人批次录入完成后系统生成一个追溯码企业把它打印出来贴到包装上消费者扫码看到整个批次的所有环节信息监管方发现问题产品时通过追溯码定位到对应批次反向查询到具体环节这个闭环里批次是核心实体追溯码是入口。后面所有的数据库设计和接口设计都是围绕这个闭环展开的。2. 技术栈选型uniapp SpringBoot 微信小程序的组合为什么合理技术选型这个环节很多新手容易一上来就开写写到一半才发现选错了路。我在这个项目里的选型逻辑是这样的你可以参考。2.1 为什么不用原生小程序而是选择uniapp单纯做微信小程序原生WXML WXSS确实够用。但有一个非常现实的问题这套系统的企业端用户很多情况下需要用到App——因为企业在田间地头、仓库车间里操作手机让员工都装微信然后用小程序录入其实并不方便App体验更好、更顺手。如果用原生小程序微信端做一套App端又得用Flutter或者Android原生再写一套人力直接翻倍。这时候uniapp的价值就体现出来了一套Vue代码编译成微信小程序、AppAndroid/iOS和H5三个端。我在这个项目里就是写了一份uniapp代码编译出微信小程序给消费者同时打包了一个Android的App给企业员工录入数据用H5版本留给管理后台做快速预览。这就是选uniapp最核心的理由一码多端覆盖不同角色的使用场景。另外uniapp的生态也帮了忙比如uview-plus这类组件库页面和交互组件都能直接拿来用。微信小程序端特有的APIuniapp基本都做了封装比如uni.scanCode对应小程序的wx.scanCode写法上统一成uni.xxx跨端迁移成本低。2.2 SpringBoot后端的模块划分与接口设计后端选SpringBoot理由很直接生态成熟、上手快、招人也好招特别适合中小型项目和毕设项目。Java本身在企业级应用里的稳定性对于这种涉及交易可信数据的系统还是有必要的。我的后端按这样的模块划分用户模块企业员工账号、微信用户openid绑定、角色权限产品与批次模块产品管理、批次创建、批次状态流转追溯环节模块各环节信息的录入、修改带审计、查询追溯码模块码的生成、打印、查询扫码记录模块消费端扫码日志、查询频率控制文件模块图片上传、访问对接MinIO接口统一返回JSON格式封装统一的返回体分页参数、错误码、异常处理都做成公共的。用MyBatis-Plus做ORM单表CRUD几乎不用手写SQL联表查询自己写XML即可。一个典型接口比如查询追溯详情返回的数据结构是这样的{ code: 0, data: { traceCode: TB202410120001, productName: 红富士苹果, batchName: 2024年秋第3批, origin: 山东省烟台市XX镇XX合作社, records: [ { type: planting, time: 2024-04-10, content: 施用有机肥每亩200kg, operator: 王师傅, images: [http://minio.example.com/xxx.jpg] } ] } }2.3 存储方案MySQL、Redis、MinIO各司其职三个存储组件的分工是这样的MySQL存核心业务数据包括用户、产品、批次、环节记录、追溯码、扫码日志。数据库表设计下一节专门讲。对于SpringBoot项目数据库连接池用默认的HikariCP就够了单机MySQL在这个量级下完全够用。Redis主要干两件事。一是做缓存扫码详情页的高频查询可以直接缓存热点数据节省数据库压力二是做扫码频率控制同一个追溯码在短时间内被反复扫不能每次请求都打数据库用Redis的过期计数器很容易实现。MinIO用于图片文件存储。环节录入时上传的实拍图片、检测报告文件都在MinIO里走公开访问链接给前端展示。MinIO的好处是开源、私有化部署便宜按S3接口兼容以后要切到云厂商的OSS成本也不高。对于入门项目其实不用过度设计。单机MySQL Redis MinIO已经覆盖了绝大多数中小型追溯系统的量级需求。3. 核心表结构设计一张批次表串起整条追溯链数据库设计是整个系统最关键的环节因为追溯链条的完整性依赖表结构的严谨性。我最终设计下来核心表就这么几张但每一张的字段和关联关系都经过了反复斟酌。3.1 从产品到批次再到环节记录数据如何流转关系链是这样的产品product→批次batch→环节记录trace_record→追溯码trace_code产品是稳定的基础档案比如红富士苹果章丘大葱。批次是动态的生产单位比如2024年10月12日采摘的500箱苹果就是一个批次。一个产品下面可以有很多批次。批次是追溯的核心因为追溯的本质就是追批次。消费者扫一个码实际上看到的是这个码绑定批次的所有环节记录。环节记录挂靠在批次下面一条批次有N条环节记录每条记录有类型、时间、内容、操作人、图片。这就是追溯详情页的主要数据来源。追溯码单独建表管理一个批次可以生成多个追溯码每个码对应同一批产品。现实中一个批次的苹果可能装了几万箱每箱的追溯码可以不同——如果码相同消费者扫出来的信息一样没问题但企业要精准定位到哪一箱出了问题还是应该一箱一码。这里我采用的是一个批次生成多个追溯码每一个独立入库的方案。3.2 环节记录表的设计要点与类型枚举环节记录表是我花心思最多的表直接贴建表SQL来看更清楚CREATE TABLE trace_record ( id bigint NOT NULL AUTO_INCREMENT, batch_id bigint NOT NULL COMMENT 批次ID, record_type varchar(20) NOT NULL COMMENT 环节类型planting/growing/harvest/processing/quality/logistics, record_name varchar(100) DEFAULT NULL COMMENT 环节名称, record_time datetime DEFAULT NULL COMMENT 环节发生时间, content text COMMENT 环节描述, operator_name varchar(50) DEFAULT NULL COMMENT 操作人, operator_user_id bigint DEFAULT NULL COMMENT 操作人用户ID, image_urls text COMMENT 图片地址逗号分隔, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT NULL, PRIMARY KEY (id), KEY idx_batch_id (batch_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;record_type用字符串枚举而不是数字原因很简单查日志和写代码的时候一眼就能看懂planting是什么环节不用再对着一张数字映射表翻半天。不同类型环节的差异化数据我用统一的content字段文本描述 image_urls图片来承载不单独做子表。这样做的取舍是查询简单、列表展示统一代价是不同环节的字段差异无法在数据库层强约束。对于追溯系统来说环节内容是人工录入的文本图片这个灵活度更重要。3.3 追溯码和批次的关系一对多还是一对一这块单独拿出来说因为它直接影响后续的扫码查询逻辑。追溯码表trace_code的核心字段CREATE TABLE trace_code ( id bigint NOT NULL AUTO_INCREMENT, code varchar(64) NOT NULL COMMENT 追溯码, batch_id bigint NOT NULL COMMENT 批次ID, status tinyint NOT NULL DEFAULT 1 COMMENT 1有效 0作废, print_count int NOT NULL DEFAULT 0 COMMENT 打印次数, create_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_code (code) ) ENGINEInnoDB;一个批次生成多个追溯码每个追溯码都指向同一个批次。消费者扫到任何一个码查询到的都是同一批次的环节记录。这里有个容易出现误解的点追溯码不是唯一ID它是入口令牌。它不需要编码所有信息只需要能定位到对应的批次即可。所以查询链是追溯码 → 批次ID → 产品信息 环节记录列表。三步搞定。4. 追溯码生成与扫码查询的完整链路追溯码是整个系统最能直观感知的部分。这一节我完整讲解码的生成规则、二维码生成方式、以及小程序扫码到详情页的完整链路。4.1 追溯码编码规则与防伪设计追溯码的编码规则我参考了常见的溯源平台做法用一个不重复、可快速查询的字符串作为码值TB 年月日时分秒 4位随机数示例TB202410121530256847为什么这么设计前缀TBTraceability标识业务类型避免和其他码混淆时间戳保证码的全局唯一性4位随机数增加不可猜测性防止别人批量踩点真实场景中防伪需求其实有个矛盾点码太规律容易被伪造码完全随机又没法在特殊情况下人工识别。我最终的方案是时间戳随机数的组合生产环境下可以在数据库层面加UNIQUE约束兜底万一生成冲突直接重新生成一次。生成追溯码的Java代码public String generateTraceCode() { // 时间戳部分yyyyMMddHHmmss String timePart DateTimeFormatter.ofPattern(yyyyMMddHHmmss) .format(LocalDateTime.now()); // 随机部分4位随机数字 String randomPart String.valueOf((int)((Math.random() * 9 1) * 1000)); String code TB timePart randomPart; // 如果数据库已存在则重新生成 while (traceCodeMapper.selectByCode(code) ! null) { randomPart String.valueOf((int)((Math.random() * 9 1) * 1000)); code TB timePart randomPart; } return code; }4.2 SpringBoot集成ZXing生成二维码后端生成二维码用的是Google的ZXing库。在SpringBoot项目中引入依赖dependency groupIdcom.google.zxing/groupId artifactIdcore/artifactId version3.5.3/version /dependency生成二维码的核心逻辑把追溯码字符串作为二维码内容生成300×300的PNG图片。ZXing的QRCodeWriter是常用的生成器public byte[] generateQrCode(String content, int width, int height) { QRCodeWriter qrCodeWriter new QRCodeWriter(); BitMatrix bitMatrix qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, width, height); ByteArrayOutputStream pngOutputStream new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(bitMatrix, PNG, pngOutputStream); return pngOutputStream.toByteArray(); }这里注意一个细节二维码的内容就是纯字符串追溯码不拼接业务参数。这样设计的好处是业务端查询接口变化时不用重新生成所有码。比如以后详情页地址变了扫码后小程序里可以根据码值自己决定跳转到哪个页面。我把生成的二维码保存到MinIO中返回给前端一个图片地址。前端打印标签时直接拉取这个图片地址即可。4.3 小程序扫码跳转与详情页数据组装小程序端的扫码逻辑在uniapp里封装成通用方法scanCode() { uni.scanCode({ success: (res) { const traceCode res.result // 跳转到追溯详情页带上追溯码 uni.navigateTo({ url: /pages/trace/detail?code${traceCode} }) }, fail: (err) { uni.showToast({ title: 扫码失败请重试, icon: none }) } }) }消费者从微信扫一扫扫到二维码时会先打开小程序需要在微信公众平台配置二维码跳转规则然后小程序拿到二维码里的字符串做参数跳转到详情页。详情页的数据组装逻辑在后端public TraceDetailVO getDetail(String traceCode) { // 1. 根据追溯码查到批次ID TraceCode byCode traceCodeMapper.selectByCode(traceCode); if (byCode null) { throw new BizException(追溯码不存在); } // 2. 根据批次ID查产品与批次信息 Batch batch batchMapper.selectById(byCode.getBatchId()); Product product productMapper.selectById(batch.getProductId()); // 3. 根据批次ID查环节记录列表按时间排序 ListTraceRecord records traceRecordMapper .selectList(new LambdaQueryWrapperTraceRecord() .eq(TraceRecord::getBatchId, byCode.getBatchId()) .orderByAsc(TraceRecord::getRecordTime)); // 4. 组装返回 return assembleVo(product, batch, records); }在组装之前先插入一条扫码记录——这个动作我在项目里单独做了一个方法在查询详情之前调用记录扫码人的openid如果有、追溯码、IP和时间为之后可能的溯源倒查留数据。5. 小程序端页面实现从登录到扫码详情的实战细节前端我用uniapp实现这套代码同时编译出了微信小程序和Android App。这一节讲页面结构和关键实现细节。5.1 页面结构与角色差异化处理小程序端的页面结构我分为两类消费者端和企业端通过登录时的角色字段做差异化展示。消费者端页面首页品牌宣传、扫码入口、溯源产品列表追溯详情页扫码后跳转的核心页面扫码记录页历史扫码记录个人中心我的登录状态、意见反馈企业端多几个页面工作台今日待办、最近录入批次管理创建批次、批次列表环节录入针对某个批次录入种植/检测/加工等记录我的产品产品管理在路由和菜单配置上我用uniapp的页面配置文件pages.json把企业端页面做成独立分包这样消费者端的主包体积可以控制得比较小小程序加载更快。分包加载这个优化点微信小程序审核时也会看主包过大容易被打回。5.2 登录、扫码与缓存设计登录逻辑是微信小程序的经典流程uni.login({ provider: weixin, success: async (loginRes) { const code loginRes.code // 后端用code换openid并签发JWT const res await request.post(/api/auth/login, { code }) uni.setStorageSync(token, res.data.token) } })一步到位返回token前端全局请求拦截器统一在header里带上token。后端通过JWT解析出用户身份再决定返回的内容是否包含企业端入口。扫码记录的缓存我做了两个层面的处理第一层是uniapp端的内存/本地缓存。扫码历史记录存到uni.setStorageSync里key是scan_historyvalue是一个数组同时在每条记录里带上时间戳。读取历史时先按时间排序再判断是否超过7天超过就直接不展示了避免本地存储越来越大。第二层是服务端Redis缓存。热点追溯详情的接口结果缓存5分钟同一个追溯码5分钟内的重复查询直接走缓存既能防刷也能大幅降低数据库压力。实现用的SpringCache注解式缓存Cacheable(value trace_detail, key #traceCode, unless #result null) public TraceDetailVO getDetail(String traceCode) { // ... 上面的查询逻辑 }这里有个坑要提醒SpringCache默认是用SimpleCacheManager单机内存缓存重启就没了。生产环境要配置RedisCacheManager缓存才靠谱。5.3 顶部导航栏适配与分享功能微信小程序顶部导航栏的适配是每个前端都要过一遍的坎。iPhone的刘海屏、不同安卓厂商的挖孔屏、安卓/iOS的状态栏高度差异都会导致自定义导航栏被刘海遮挡或者高度不一致。我的做法是在uniapp里封装一个自定义导航组件统一处理// 获取系统状态栏高度 const systemInfo uni.getSystemInfoSync() const statusBarHeight systemInfo.statusBarHeight || 44 // 导航栏高度 状态栏高度 44px默认导航栏高度 const navHeight statusBarHeight 44所有页面的顶部自定义导航栏都统一用这个高度计算配合uni.getMenuButtonBoundingClientRect()获取胶囊按钮位置做适配至少在iOS和主流安卓机上是没问题的。分享功能在uniapp里也简单设置onShareAppMessage即可生成转发卡片。我额外做了一个细节分享出去的卡片带上追溯码参数用户从卡片点进来直接打开对应产品的追溯详情页。这个分享路径的追踪本质上和扫码是同一个入口逻辑所以后端不用做额外区分。onShareAppMessage() { return { title: 我查到了一款可以溯源的农产品, path: /pages/trace/detail?code${this.traceCode} } }6. 后端接口与鉴权逻辑微信登录、JWT与环节录入后端服务是整个系统的中枢。接口设计、权限模型和文件上传这几块我把实现思路完整讲一遍。6.1 code2session换取openid再签发JWT微信小程序登录的标准流程是前端uni.login拿到临时code后端拿这个code去微信服务器换openid和session_key。拿到openid后查一下用户表如果openid已存在直接登录如果不存在注册一个新用户默认角色是消费者如果填写了企业入驻申请管理员审核后升级为企业员工角色然后后端生成JWT返回给前端。JWT的载荷里有userId和role两个关键字段后续接口通过JWT解析拿到当前用户。这里要注意一个安全问题前端不能信任自己传来的userId所有身份都要从token里解。我封装了一个RequireRole注解来做角色鉴权只允许指定角色访问的接口加上注解即可RequireRole({ADMIN, ENTERPRISE}) PostMapping(/api/batch) public Result addBatch(RequestBody BatchDTO dto) { // 只有管理员和企业员工能创建批次 }拦截器里统一解析token把当前用户信息放入ThreadLocal业务代码里可以随时取用。6.2 环节信息录入接口的设计环节录入是操作最频繁的接口。企业员工进入某个批次然后连续录入种植记录施肥记录检测记录等。接口设计上我采用的是一次提交一条环节记录的方式而不是一次提交整个批次的所有环节。原因很实际田间的网络不稳定一次性提交一大堆数据很容易因为超时丢数据。分成多条提交每一条都有独立的成功/失败反馈用户体验好得多。录入接口的核心逻辑PostMapping(/api/trace/record) public Result addTraceRecord(RequestBody TraceRecordDTO dto) { // 1. 校验批次是否存在且属于当前企业 Batch batch batchMapper.selectById(dto.getBatchId()); Assert.isTrue(batch.getEnterpriseId().equals(currentUser.getEnterpriseId()), 无权操作该批次); // 2. 组装实体并入库 TraceRecord record new TraceRecord(); BeanUtils.copyProperties(dto, record); record.setOperatorUserId(currentUser.getId()); record.setOperatorName(currentUser.getRealName()); traceRecordMapper.insert(record); // 3. 删除缓存保证下次查询能拿到最新数据 cacheManager.getCache(trace_detail).evict(batch.getTraceCode()); return Result.success(); }两个明显的细节一是操作人信息从token里取不允许前端传保证记录可信二是修改数据后主动清缓存避免用户录入了新记录但扫码还是老数据。6.3 图片上传与MinIO的集成环节记录一般都要配图片比如果园实拍、检测报告照片。图片上传我用了MinIO集成方式不复杂先在SpringBoot的配置文件里加MinIO连接参数minio: endpoint: http://127.0.0.1:9000 access-key: minioadmin secret-key: minioadmin bucket: trace-images再写一个配置类初始化MinIO Client然后封装文件上传服务public String uploadImage(MultipartFile file, String folder) { // 生成唯一的对象名避免覆盖 String objectName folder / System.currentTimeMillis() _ file.getOriginalFilename(); try { // 检查bucket是否存在不存在则创建 boolean exists minioClient.bucketExists(BucketExistsArgs .builder().bucket(bucketName).build()); if (!exists) { minioClient.makeBucket(MakeBucketArgs .builder().bucket(bucketName).build()); } // 上传文件 minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); // 拼接访问地址 return endpoint / bucketName / objectName; } catch (Exception e) { throw new BizException(文件上传失败: e.getMessage()); } }前端拿到这张图片的URL后随环节数据一起提交后续在小程序和App里直接展示。MinIO的上传和访问都在局域网内响应很快线上部署时把这个endpoint换成内网地址外网代理就搞定了。7. 部署上线与踩坑实录含jar反编译教训最后一部分讲实战最容易翻车的环节。我把项目部署和上线过程中遇到的坑整理出来这些大部分是文档里查不到的。7.1 SpringBoot打包与版本兼容问题SpringBoot打包成可执行jar常规操作就是mvn package。但我在这个项目里遇到一个典型的坑开发环境用SpringBoot 2.7没问题换成3.x之后MyBatis-Plus和部分工具类的兼容性直接出问题。SpringBoot 3.x基于Jakarta EE很多javax包名变成了jakarta。如果你的项目用了MyBatis-Plus 3.5.3之前的版本和一些老工具类在SpringBoot 3.x下直接编译不过。我的建议是不要盲目追求新版SpringBoot。对于这类中小型追溯系统SpringBoot 2.7足够稳各种中间件的兼容性资料也最多踩坑面小。如果你只是学习项目直接用2.7.x省去大量环境调优的时间。另外一个关于jar包的经验教训务必把源码纳入版本管理并定期推送远程仓库。我之前接过一个外包项目对方只给了一个编译好的jar包说是源码丢了让我直接反编译jar来改功能。反编译工具虽然能还原大部分代码但注释、部分泛型信息、以及多模块项目的构建配置会丢失改起来效率极低。吃了一次亏之后我现在所有项目都强制要求源码进Git仓库这个习惯救过我好几次。7.2 uniapp的manifest配置与真机调试uniapp项目打包成微信小程序前必须正确配置manifest.json。新手最容易漏掉的地方微信小程序AppID必须填真实的小程序AppID测试号在部分API能力上受限权限声明如果要用扫码能力需要在mp-weixin节点下配置权限相关字段基础库版本设置一个合适的微信基础库最低版本太低会导致新API不可用太高会流失一部分老版本用户我调试时还踩过一个uniapp的经典坑H5端和App端的console.log正常打印但微信小程序端一言不发。排查了半天才发现微信开发者工具默认会过滤掉console信息需要在工具的控制台面板里选择Info级别并且确认是真机调试还是模拟器调试两者日志行为不一样。这类问题不是说代码有问题而是你根本看不到日志非常容易让人误判。真机调试的另一个建议是首次登录、扫码、支付这类涉及微信原生能力的操作一定用真机测模拟器在很多API上有兼容性差异会出现模拟器正常、真机白屏的诡异问题。7.3 微信小程序年审与审核注意事项微信小程序从注册到上线有几道流程必须走。我在实际操作中总结出来的经验类目选择做农产品追溯小程序类目建议选电商平台或生活服务同时准备对应的资质材料。农产品销售还需要考虑食品经营许可等相关证件的上传不同类目要求的资质不同提前在微信公众平台的文档里确认清楚。审核期间的表现微信审核人员会测试你的核心流程。建议准备一个测试账号录好一批测试数据确保扫码能看到完整的追溯信息。我在审核时被拒了一次原因是扫码后展示信息不完整后来补录了一条完整的测试产品批次才通过。年审微信小程序每年要做一次年审需要重新提交主体信息、资质文件并缴纳审核费用。这件事容易忘记一旦过期没年审小程序会被限制搜索和部分能力。建议把年审时间记到日历里提前一个月准备材料。这些流程听起来琐碎但任何一个环节卡住整个上线进度都会受到影响。我自己的体会是做这类系统代码写出来只是一半剩下的一半是流程和数据。追溯系统的价值不在技术有多炫而在于每一环的数据是不是真的、能不能查、能不能信。所以不管是用uniapp做扫码端、SpringBoot做接口还是MySQL存数据链路最后回归到的核心问题都是消费者扫一个码看到的信息能否让他放心。把这个想清楚你的项目就不会跑偏。
返回列表