尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Spring Boot + uniapp 汽车销售库存管理系统设计与完整实现

发布时间:2026/9/24 19:01:17

资讯中心
01
ARTICLE

Spring Boot + uniapp 汽车销售库存管理系统设计与完整实现

Spring Boot + uniapp 汽车销售库存管理系统设计与完整实现
做汽车销售这块的朋友应该都有感触库存管理看着简单实际一上手全是坑。展厅里几十台车哪台在途、哪台已订、哪台在库销售顾问问库管库管翻Excel客户在边上等着场面一度非常尴尬。我之前接过一个4S店的内部项目需求就是“让销售在手机上随时能查库存、开单、看出入库记录”最后落地成了微信小程序端 Spring Boot后端的汽车销售库存管理系统。这套组合在中小型经销商、二手车行里非常典型代码仓库名字里那个785h00gj就是当时项目的唯一标识。今天把这套系统的设计思路、核心实现和上线过程中踩过的坑完整梳理一遍给正要动手做类似项目的同学一个能直接参考的范本。这套系统解决的核心问题很明确把“车”从到店入库到销售出库的全生命周期管起来同时让销售员不需要回办公室就能完成查询和开单操作。前端用 uniapp 开发一套代码同时输出微信小程序、H5和Android App后端用 Spring Boot 提供接口数据库选 MySQL。整个项目适合有 Java 基础、正在做毕业设计或公司内部工具开发的同学参考也适合产品经理用来理解汽车经销行业的信息化逻辑。1. 项目整体设计与技术选型思路1.1 业务场景与系统定位汽车销售库存管理和普通商品库存管理最大的区别在于车的单品价值高、SKU属性复杂、流转环节多。一台车从厂家发运、到店验收、入库展示、客户试驾、销售预订、财务收款、办理手续到最后交付中间涉及多个角色。如果只用一张Excel表格记录“库存数量”根本没法回答“展厅里那台白色2.0T顶配卖出去没有”这种问题。所以在设计这个系统时我没有把车辆当成普通商品去做简单的数量增减而是采用“单车档案”模式每一台车在系统里都有一条独立的记录用车辆识别码VIN作为唯一标识它所在的库位、当前状态、停放天数、关联的销售订单全部挂在同一条数据上。核心状态机分为在途、在库、已预订、已售、已提车五个状态任何时刻打开系统管理层都能知道每一台车在哪里、处于什么状态。系统的用户角色分成三类管理员负责基础数据维护和所有记录查看库管负责车辆入库、出库和库存盘点销售顾问负责查询库存和创建销售订单。早期版本还加了一个财务只读账号用于核对车款和库存金额后来因为客户觉得权限太细反而麻烦就合并成了三类角色。1.2 为什么选择 uniapp Spring Boot 组合选 uniapp 的理由其实很实际客户既有微信小程序的需求又不想放弃未来做App的可能性。如果原生开发微信小程序将来上架App Store还要另写一套iOS代码成本直接翻倍。uniapp 基于 Vue 语法写一遍代码可以编译到微信小程序、H5和Android/iOS App虽然性能和原生有差距但对于库存查询、扫码、表单提交这种轻交互场景完全够用。开发效率上uniapp 配 HBuilderX 的体验也算顺畅内置的uni.request、uni.scanCode、uni.uploadFile这些API封装好了跨端差异不用自己操心微信小程序的wx.request和其他平台的差异问题。项目里还用到了uni-ui组件库的uni-list和uni-badge列表页和状态标签直接开箱即用。后端选 Spring Boot 是因为团队熟悉 Java 技术栈而且库存管理这类系统对事务一致性要求高Spring 的声明式事务Transactional实现起来非常可靠。数据库访问层用 MyBatis-Plus它的LambdaQueryWrapper写查询条件比手写XML省很多事分页插件也内置好了。登录鉴权这块用 JWT 做无状态token微信小程序登录后拿到openid后端签发token后续请求在拦截器里统一校验避免每次请求都去查数据库。1.3 项目模块划分与目录结构前后端分离的项目最怕的就是目录结构混乱。这个项目的代码组织是严格按业务模块拆分的后端分为controller、service、mapper、entity四层按stock、order、user、car等业务域分包。前端 uniapp 项目在pages下按功能模块建子目录api目录统一存放接口调用封装utils目录放token管理、日期格式化等工具函数。这种拆分的好处是加一个新功能时不用在几百个文件里找入口团队成员各自负责一个业务域互不干扰。比如我要加一个车辆保险到期提醒功能只需要在car包下面加InsuranceController和对应service小程序端在pages/car下新增一个提醒页面。2. 核心功能模块拆解与数据库设计2.1 库存管理业务流从入库到出库的完整闭环整个系统最核心的业务流是两条采购入库流和销售出库流。采购入库的起点是仓库收到厂家发运的车辆库管在小程序端选择“新车入库”通过扫描VIN条码或手动输入车架号系统自动带出车型信息库管补充颜色、配置、指导价、入库库位后提交。此时车辆状态变为“在库”同时系统自动生成一条库存流水记录包含操作人、操作类型、操作时间。销售出库流则要复杂一些销售顾问在客户确定购买意向后先在系统里把目标车辆状态改为“已预订”录入客户信息和定金金额。等到车款到账、办完手续后库管或销售再执行“确认出库”车辆状态变为“已售”关联的销售订单状态同步更新为“已完成”同时库存流水再记一条出库记录。这样设计的好处是每一台车从进到出都有完整的审计痕迹月底对账时只要把流水表拉出来就能说清楚这段时间每台车经历了什么。盘点功能是我后来补的一个模块起因是客户发现展厅的车和系统对不上查了半天发现是两辆同款同色的车被人为换过库位。盘点功能让库管选择某个库位系统列出该库位应有的车辆清单库管逐台扫码核对缺失或多余的车辆自动生成差异记录并允许一键修正库位信息。2.2 数据库表设计与关键字段说明库存管理系统的表设计不算复杂但有几个字段必须设计好不然后期业务扩展会很痛苦。核心表包括用户表、车辆信息表、客户表、销售订单表、库存流水表、库位表。车辆信息表是最重要的我列出关键字段供参考字段名类型说明idbigint主键vin_codevarchar(32)车辆识别码全局唯一brandvarchar(32)品牌奥迪、宝马等seriesvarchar(64)车系A6L、3系等model_yearvarchar(16)年款2024款colorvarchar(16)外观颜色config_levelvarchar(32)配置级别低/中/顶配guide_pricedecimal(10,2)指导价cost_pricedecimal(10,2)成本价财务可看sell_pricedecimal(10,2)成交价待售为空statustinyint1在途 2在库 3已预订 4已售 5已提车storage_locationvarchar(32)库位编码A-01-03inbound_timedatetime入库时间shelf_daysint在库天数冗余字段定时任务更新create_bybigint创建人create_timedatetime创建时间库存流水表stock_record记录所有变动字段包括car_id、record_type1入库 2出库 3盘点调整 4库位变更、before_status、after_status、operator_id、remark。之所以要把变更前后的状态都存下来是为了在出问题时能追溯当时的现场这是我在一个二手车项目里被坑过一次之后长记性加上的。销售订单表sale_order关联车辆ID、客户ID、销售员ID、成交价格、定金、尾款、合同编号、订单状态。客户表单独建字段包括姓名、电话、身份证号、地址因为一个客户可能买多台车老带新、增购客户和车辆是一对多关系不能把客户信息直接冗余在订单表里。2.3 库存扣减的并发处理方案汽车库存因为采用“单车档案”模式天然避开了普通商品库存的超卖问题——每台车是一条独立记录销售出库不是把库存数量减一而是把这条记录的status从“在库”改成“已售”。这个设计让并发安全变得非常简单不需要加库存数量的锁。真正需要注意的并发场景是“同一台车被两个销售同时下单”。试想两个销售顾问同时看到一台在库车辆都点了“预订”如果不加控制就会产生两个订单都关联到同一台车。解决办法有两种一种是在车辆表上加一个locked_by字段预订时用UPDATE car_info SET status 3, locked_by #{salesmanId} WHERE id #{carId} AND status 2这种带条件更新的SQL数据库行锁会串行化这个操作第二个人的UPDATE影响行数为0说明车已被锁定前端提示“车辆已被预订”。另一种是用 Redis 分布式锁适合微服务多实例部署的情况单体应用用数据库就完全够了。我最终采用的是第一种方案因为实现简单、性能足够。关键代码在CarService.lockCar()里用 MyBatis-Plus 的UpdateWrapper拼接带状态条件的更新然后判断返回值Transactional(rollbackFor Exception.class) public boolean lockCar(Long carId, Long salesId) { LambdaUpdateWrapperCarInfo wrapper new LambdaUpdateWrapper(); wrapper.eq(CarInfo::getId, carId) .eq(CarInfo::getStatus, 2) // 只有在库状态才能锁定 .set(CarInfo::getStatus, 3) // 改为已预订 .set(CarInfo::getLockedBy, salesId); return carInfoMapper.update(null, wrapper) 0; }这段代码的价值在于数据库层面保证了“状态变更”操作的原子性两个并发请求同时执行时只有一个能成功更新。比先查后改的方式安全得多也不会出现高并发场景下的脏数据。3. 后端Spring Boot核心实现要点3.1 微信小程序登录与JWT鉴权微信小程序的登录流程和后端普通账号密码登录完全不一样。前端调用uni.login获取临时code后端拿着这个code去微信接口https://api.weixin.qq.com/sns/jscode2session换取openid。这个openid是用户在某个小程序下的唯一标识整个流程不需要用户输入账号密码体验很顺畅。Spring Boot 端的 Controller 实现很简单用RestTemplate或OkHttp请求微信接口拿到返回的 JSON 解析出 openid。要注意微信接口偶尔会不稳定建议加上超时设置和失败重试机制。如果是开发测试没有真实的小程序AppID可以先用 mock 接口返回固定openid把流程先跑通。获取到 openid 后判断用户是否已存在于sys_user表不存在则自动创建一条默认记录昵称取“微信用户手机号后四位”。然后生成 JWT token把userId和roleCode放进 token 的 payload设置过期时间我设的是7天方便销售长期免登录返回给前端存储到uni.setStorageSync。后续每次请求前端在拦截器里带上Authorization: Bearer token后端写一个JwtInterceptor实现HandlerInterceptor接口在preHandle里校验token有效性解析出用户信息存入ThreadLocal。需要当前用户的地方直接从 ThreadLocal 取避免每个接口都传userId参数。3.2 车辆图片与文件上传的存储方案汽车库存系统里车辆图片是刚需销售在外给客户发车源信息没有照片根本聊不下去。图片上传流程是小程序端调用uni.chooseImage选择图片然后通过uni.uploadFile把文件以multipart/form-data形式POST到后端的/api/upload接口。Spring Boot 用MultipartFile接收然后决定存储到本地磁盘还是对象存储。考虑到这个系统的部署环境只是一个云服务器我用的是本地磁盘存储方案配置一个upload.path属性指定文件保存目录如/data/upload按日期建子目录防止单个目录文件过多文件名用 UUID 重命名避免中文名和重复名问题。返回给前端的是/upload/2025/07/21/uuid.jpg这种相对路径前端拼接服务器域名得到完整URL。这里有个必须注意的坑微信小程序里的图片域名必须在后台配置为“downloadFile合法域名”否则用户在小程序里能看到首页轮播图却看不到车辆详情页的图片因为图片请求被微信拦截了。我在开发时被这个问题卡了一上午一度以为是后端接口返回数据有问题后来查了小程序后台的域名配置才发现是这个原因。3.3 库存预警与统计报表实现库存预警模块解决了“哪些车压库太久需要处理”的问题。每家店对库存天数的容忍度不一样所以我把预警阈值做成了可配置的管理员在设置页维护一个“库龄预警天数”参数后端每天通过定时任务扫描车辆表把在库天数超过阈值的车标记为预警状态并给销售主管推送微信订阅消息。实际运行时发现定时任务每天扫一次不够及时因为车是随时入库的后来改成每次查询列表时动态计算库龄超过阈值就在前端显示红色标签。定时任务只用来做汇总统计两件事解耦。销售统计报表则用了简单的 SQL 聚合查询按天、按周、按月统计成交台数、销售额、单车均价再按销售员分组看个人业绩。这里没有引入复杂的报表引擎客户主要是在手机上看数字一个简单的柱状图页面就够了。前端用ucharts组件渲染图表后端返回经过聚合的 List 数据例如当月每天成交量的结构是[{date: 2025-07-01, count: 3}, ...]。4. 前端uniapp小程序端实现细节4.1 HBuilderX创建项目与页面结构开发工具用 HBuilderX新建项目时选择“uni-app”模板框架选 Vue 3因为 Vue 3 的组合式API写起来更清爽而且微信小程序基础库已经支持。项目创建后最优先做的是在manifest.json里配置微信小程序AppID。很多新手在这里卡住如果没有注册小程序账号可以先点“测试号”申请一个测试AppID功能不受影响只是不能发布上线。页面结构按照“底部Tab 功能子页”的方式组织。底部Tab有四个首页、库存、订单、我的。首页展示库存概览数据包括总库存、在库数量、今日成交、预警车辆数用卡片式布局放四个数字下面放最近入库车辆列表。库存页是核心页面顶部是搜索框支持按品牌、车型、状态筛选下面是车辆卡片列表卡片上显示车辆照片、车型名称、颜色、状态标签和库龄。订单页展示销售订单列表点击进入订单详情。我的页面放用户信息和系统功能入口比如库存流水查询、盘点工具、预警设置。每个页面在pages.json里注册路由注意小程序页面路径不需要带.vue后缀。tabBar 页面必须在pages.json的tabBar.list中配置pagePath和iconPathicon 图片需要准备 PNG 格式建议用 81x81 像素否则小程序会报警告。4.2 小程序登录态保持与请求统一封装微信小程序的请求封装是整个前端项目的地基封装好了能避免大量重复代码。我在utils/request.js里封装了一个request函数内部用uni.request发送请求。核心逻辑是每次请求前从 storage 里取token有则加到 header收到 401 状态码时自动清除token并跳转到登录页面网络错误时统一弹出uni.showToast提示。登录和 token 刷新机制也值得细说。小程序获取到后端返回的token后不是只存起来就完了还需要把用户的基本信息userName、roleName一并存储方便页面直接展示。token 过期后不能再要求用户重新走一遍微信授权用户体验太差而应该静默重新调用uni.login获取新code再调/api/auth/wxLogin换新token。我的实现里做了一个请求队列的雏形收到401时先暂停所有后续请求刷新token成功后再继续执行。这样做的效果是操作过程中用户完全感知不到登录已过期。4.3 扫码入库与列表渲染注意事项扫码功能是小程序端的亮点也是微信生态的天然优势。实现很简单调用uni.scanCode打开摄像头扫描VIN码拿到结果后调用后端接口查询车辆信息。VIN码一般是17位字母和数字组合扫码时要注意条形码的格式如果扫出来包含空格或换行符要先去除再提交。我在测试时扫过几次二维码发现微信的scanCode对二维码和条形码都支持但解析结果偶尔会带不可见字符所以后端接口里也做了 trim 处理双保险。车辆列表的渲染要注意性能问题。小程序setData的数据量过大会导致页面卡顿特别是在车辆数据超过几百台时。我的做法是利用 uniapp 的分页加载能力每次只加载20条滚动到底部时自动加载下一页。另外车辆状态这种枚举值不要存中文在数据库里前端通过filter函数映射成对应的颜色和标签文案例如状态值1映射为橙色“在途”2映射为蓝色“在库”3映射为紫色“已预订”4映射为灰色“已售”。这样后端返回的数据简洁前端展示统一。还有一个移动端常见的坑iPhone 上底部 tab 栏会遮挡最后一条数据列表页需要给onReachBottom做页面底部安全距离适配。uniapp 提供了uni.getSystemInfoSync获取safeAreaInsets.bottom的值在计算滚动加载位置时要加上这个值不然列表最后几项总是被挡住。5. 前后端联调、打包发布与部署5.1 微信小程序端发行流程与域名白名单联调阶段的第一个关键点是网络环境。开发环境下微信开发者工具可以勾选“不校验合法域名、web-view业务域名、TLS版本以及HTTPS证书”这样就能直接访问本地电脑的后端接口IP用http://192.168.x.x:8080的形式。如果后端跑在云服务器上开发阶段也可以直接用服务器IP加端口访问但要注意服务器安全组必须放行对应端口。到发行阶段HBuilderX 的操作为菜单栏“发行”-“小程序-微信”输入小程序AppID后点击发行。等编译完成后HBuilderX 会自动打开微信开发者工具并加载编译产物到dist/dev/mp-weixin目录。此时项目代码已经变成了微信小程序的 WXML/WXSS/JS 格式不能再直接改前端Vue代码要改就回到 HBuilderX 里改源码再重新发行。上线前必须在小程序后台配置合法域名request合法域名配后端 HTTPS 接口地址如https://api.example.comuploadFile合法域名配图片上传接口域名downloadFile合法域名配图片访问域名。这里有个容易被忽略的点如果后端接口和图片服务是用 Nginx 代理的三个域名可以同一个根域名但路径前缀可能不同需要分别配置。域名配置后不是即时生效一般要等几分钟到几十分钟。5.2 Spring Boot服务端部署与数据库初始化后端部署我推荐用最稳妥的 jar 包 Nginx 反代方案不推荐用 Spring Boot 内置的容器直接暴露端口供外网访问。具体步骤是本地用 Maven 执行mvn clean package -DskipTests打出 jar 包上传到服务器的/opt/app目录用nohup java -jar stock-system.jar --spring.profiles.activeprod app.log 21 启动。Nginx 里配置location /api/转发到http://127.0.0.1:8080即可这样小程序端请求https://api.example.com/api/xxxNginx 实际转发到http://127.0.0.1:8080/api/xxx。数据库初始化我用的是项目启动时自动执行schema.sql的方式Spring Boot 的spring.sql.init.modealways配合schema.sql建表和data.sql写入初始管理员账号。生产环境需要把这个开关关掉改用手动执行SQL脚本避免每次重启都重置数据。这里的教训是开发环境自动初始化确实方便但生产环境一旦忘了关重启一次所有数据就没了虽然对开发团队是常识但对不太熟悉Spring Boot的维护人员来说是个大坑。服务器配置建议至少2核4G内存系统的负载主要集中在图片存储和报表查询上如果并发量不大1核2G也能跑但图片预览会慢。5.3 上线前自测清单上线前最后一天我把整个系统的核心链路逐条走了一遍整理了一份自测清单分享出来你们可以参考新用户微信登录后能否自动注册并正常访问首页库管能否扫码或手动输入VIN完成入库状态是否正确变为“在库”销售能否通过筛选找到目标车辆并成功锁定其他人再操作会不会被拦截订单创建后车辆状态是否更新订单详情是否展示正确的车辆和客户信息车辆出库后库存流水是否多了一条出库记录流水详情操作人与时间是否正确在库车辆超过预警阈值后列表是否显示红色预警标签销售统计报表本月数据是否等于订单已完成数量弱网环境下图片加载是否会出现长时间空白占位列表刷新是否卡顿换个非管理员账号登录能否正确隐藏无权访问的菜单入口每个问题测完我都会记录在测试表中打勾确认后再测下一个。这套流程虽然土但确实能拦截掉大部分低级问题。比如我第一次自检时就发现了运营账号能看到财务成本价字段的问题这个属于权限配置漏项在客户面前演示时被发现就太尴尬了。6. 常见问题与排查技巧实录在项目开发和上线初期我记录了一份问题排查清单挑几个最有代表性的实际问题分享给你包括具体现象、原因和解决方案。问题现象根本原因解决办法小程序请求接口报request:fail url not in domain listrequest合法域名未配置或开发工具未勾选跳过校验开发阶段勾选“不校验合法域名”上线前在后台配置HTTPS合法域名并等待生效真机预览能打开首页但登录失败云服务器安全组未放行8080端口或后端绑定的是127.0.0.1而非0.0.0.0检查安全组入方向规则Spring Boot 在服务器上启动时加参数--server.address0.0.0.0图片上传后小程序里无法显示图片域名未加入downloadFile合法域名在微信公众平台后台添加图片服务器域名到downloadFile合法域名两个销售同时点击预订锁车未生效没有使用带状态条件的UPDATE而是先SELECT再UPDATE改用UPDATE ... WHERE id? AND status2依靠数据库行锁原子操作上传图片大小超过2MB报错Nginx 默认client_max_body_size 1m在Nginx配置文件server块中添加client_max_body_size 10m;列表页数据量大时明显卡顿一次性将所有车辆数据setData改为分页查询每页20条滚动到底部再加载部署后重启后端数据被清空spring.sql.init.modealways每次启动执行建表脚本生产环境改为never数据库结构变更用Flyway或手动执行SQL这些问题里最让我记忆深刻的是第一个域名问题。那天我打包发了预览版给客户验收结果客户打开小程序后所有接口全部报url not in domain list我当时的第一反应是后端接口写错了对着代码查了半天没发现问题。后来突然想起微信小程序的域名限制登录小程序后台一看果然只配置了 request 合法域名uploadFile 和 downloadFile 都漏了。从那以后我每次上线前都会做一遍完整的“类生产环境”测试关闭开发工具的跳过校验开关模拟真实用户的使用路径。另一个值得单独说的是后端跨域配置。前后端分离开发时前端跑在http://localhost:8081HBuilderX内置浏览器后端跑在http://localhost:8080如果不在后端配置跨域H5端调试会报CORS错误。我在 Spring Boot 里写了一个全局CorsFilter放行所有来源、所有请求头、所有HTTP方法。但要注意配了CorsFilter之后拦截器里的OPTIONS预检请求不能直接返回404或者拦截必须放行否则前端依然会报跨域错误。还有一个数据库层面的细节车辆表的vin_code字段一定要建唯一索引。虽然业务上已经按VIN作为主逻辑判断但没有唯一索引兜底的话并发或人工重复录入时可能插入两条VIN相同的记录。我给这个字段加了唯一索引后重复录入直接报 DuplicateKeyException配合全局异常处理器返回“该车辆已存在”的提示从根本上杜绝了重复数据。写在最后的一点经验这套系统从需求确认到上线试运行前后大概花了三周时间其中需求沟通和原型确认占了一周开发占了一周半测试修bug占了半天。真正上线后我才发现最有价值的不是那些车辆CRUD接口而是库存流水表里沉淀下来的数据。有了流水数据客户每个月底都能自动生成对账报表哪台车亏了、哪台车赚了一眼就能看明白。如果你正在做类似的项目我的建议是先别急着写代码把业务状态机梳理清楚。车辆的状态流转是这个系统的灵魂状态定义清楚了后面的数据库设计、接口设计、前端页面都会顺理成章。反过来如果状态机一开始就含糊后面每个功能都会变得拧巴。另外一个很实用的扩展方向是给系统加一个简易的财务模块记录每台车的成本、运费、整备费用和最终成交价这样能自动算出单台车的毛利。客户后来专门要求加这个功能因为对老板来说库存管理最终还是要落到赚不赚钱这件事上。如果你准备在毕业设计里用这个题目把毛利率统计加进去答辩时会是一个不错的加分项。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。