news 2026/9/28 8:05:41

SSM+Maven+Bootstrap+MySQL实现校内超市收银管理系统,附完整代码与部署指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SSM+Maven+Bootstrap+MySQL实现校内超市收银管理系统,附完整代码与部署指南

我平时给学生改课程设计或者看毕业设计代码时,遇到最多的项目类型就是"校内超市收银管理系统"这类JavaWeb管理系统,但说句实在话,能真正跑通、逻辑清楚、代码整洁的没有几个。今天我把这个基于SSM+Maven+Bootstrap+MySQL+JSP的校内超市收银管理系统掰开揉碎讲清楚,它不只是应付答辩的一个作业,而是一个能完整覆盖"商品管理、收银结算、库存扣减、销售统计"的真实业务闭环。如果你正在找javaweb项目练手、或者正被mysql建表、maven配置、idea运行javaweb项目折腾到头大,那这篇内容应该能替你省下不少时间。

1. 为什么是这个技术组合:项目设计与选型盘点

1.1 系统到底解决了什么问题

先搞清楚应用场景。校内超市和校外大商超不一样,它的商品种类一般几百到上千,客单价不高但客流量集中在下课时间段,人工收银容易算错账,月底盘点经常发现账实不符。更麻烦的是,商品调价了要一张张改价签,优惠活动想去掉也得手动算折扣。这套系统把这些痛点全部接住:商品信息统一录入数据库、价格修改即时生效、收银台快速结算并自动累计销售额、库存低于阈值还能给出预警。

一句话概括:它把原先写在本子上的流水账,变成了一台"有记忆的收银机器"。对学习者来说,它又是一个麻雀虽小五脏俱全的JavaWeb完整案例,前端有页面,中间有Controller处理请求,底层有MyBatis操作MySQL,所有数据流是通的。

1.2 SSM三件套各干各的活

SSM由Spring、SpringMVC、MyBatis三个框架组成,很多人学的时候弄不清它们的分工,我用大白话说一下:

  • Spring是"大管家",负责创建和管理对象(Bean),比如Service层的实例、数据库连接池,它还管着事务——下单时如果扣库存的SQL挂了,它能自动把前面执行的SQL全部回滚。
  • SpringMVC是"前台客服",所有HTTP请求先到它这里,它根据URL路由到对应的Controller方法,把前端传的参数绑定成Java对象,处理完再返回一个JSP页面给浏览器。
  • MyBatis是"仓库管理员",它不关心业务逻辑,只负责把SQL语句执行了、把数据库记录换成Java对象。你可以把SQL写在Mapper.xml里,也可以在注解里写,非常灵活。

这三个框架配合起来,就是一个标准的三层架构:Controller层接收请求,Service层写业务规则,Mapper层访问数据库。

1.3 Maven、Bootstrap、JSP在项目里扮演的角色

Maven解决的是"依赖管理+项目构建"两件事。没有Maven的时候,你得手动下载一堆jar包,拷到WEB-INF/lib目录,还得担心版本冲突;有了Maven,pom.xml里写一行依赖坐标,它自动把jar包拉下来,打包成war包部署到Tomcat也是一条命令的事。项目里用到的Spring版本、MyBatis版本、MySQL驱动版本,全部在pom.xml里统一管理。

Bootstrap则负责页面外观。收银管理系统的后台界面结构其实很常规:左侧菜单栏、顶部导航、中间内容区,Bootstrap的栅格系统能很好地完成这种布局,表格自带样式、按钮不需要自己调CSS、弹出框(Modal)做新增编辑非常顺手。你甚至不需要自己写多少CSS代码。

JSP的价值在于服务端渲染。商品列表页可以直接在JSP里写循环,把后端传来的List用JSTL标签一行行输出成表格行,不需要前端写Ajax再去拼HTML。这种模式在SSM项目中是最顺手的。

2. 数据库怎么设计:表结构从头拆解

2.1 功能模块地图

模块规划直接决定数据表结构。系统按角色划分功能,管理员和收银员看到的界面不一样:

角色可用功能
管理员商品管理(增删改查、上下架)、商品分类管理、订单查询、会员管理、销售统计、库存预警
收银员收银台结算、商品搜索、订单查询、会员积分登记

功能确定后,数据表最少要有这几张:管理员表(admin)、商品分类表(goods_category)、商品表(goods)、订单表(orders)、订单明细表(order_item)、会员表(member)。如果还要做积分和折扣,member表里加积分字段即可。

2.2 每张表的设计思路

商品表是核心表,字段包括id、分类id、商品名、条码、售价、库存、销量、状态(上架/下架)、创建时间。我特别强调一下条码字段,校内超市很多商品自带条码,收银时如果有扫码枪,直接扫条码就能定位商品,比输入关键词快得多。

订单表要记录的是"整笔交易"的信息:订单号、总金额、支付方式(现金/微信/支付宝)、收银员id、会员id、下单时间。为什么还要一个订单明细表?因为一笔订单可以包含多个商品,如果只存一张表,要么一行存不下所有商品,要么就得重复记录订单信息。明细表一行对应一个商品,包含订单id、商品id、商品名、单价、数量、小计金额。

这里有个我强烈建议的做法:订单明细表里冗余一个商品名字段。如果不冗余,商品改过名或者被删除后,历史订单查到的就是空的或者显示"无"。冗余之后,即使商品被删了,订单照样能正确显示当时的商品名和单价。

建表SQL的关键部分如下(我只截取核心,完整脚本放到项目源码即可):

CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, name VARCHAR(100) NOT NULL, barcode VARCHAR(50) DEFAULT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sales INT NOT NULL DEFAULT 0, status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, total_price DECIMAL(10,2) NOT NULL, pay_type TINYINT NOT NULL COMMENT '1现金 2微信 3支付宝', member_id INT DEFAULT NULL, cashier_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, goods_id INT NOT NULL, goods_name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, quantity INT NOT NULL, subtotal DECIMAL(10,2) NOT NULL );

2.3 字段设计的几个经验点

金额字段不要用float或double,必须用DECIMAL。浮点算钱会出现0.1+0.2不等于0.3的问题,收银系统涉及金额精度,这一点踩过坑的人都懂。

库存和销量我放在了goods表里,这对小项目来说是最简单的方案。但要注意,扣减库存时不能先查出来再减,如果两个收银员同时下单同一件商品,后写的覆盖先写的,库存就错了。正确的做法是"UPDATE goods SET stock = stock - #{quantity} WHERE id = #{id} AND stock >= #{quantity}",受影响行数为0说明库存不足,这一步后面我会展开讲。

数据库中顺手给order_no加了唯一索引。订单号用时间戳加随机数生成,比如"20250112103000012",同一个人同一秒钟不可能下两单,但不排除极端情况,唯一索引是兜底保障。

3. 收银核心流程怎么实现:关键代码逐个说

3.1 登录与权限拦截

登录模块看起来简单,但权限拦截才是重点。用户登录成功后在session里存当前管理员对象,没登录就想访问收银台、商品管理等页面是不允许的。SSM里实现这个用的是SpringMVC的拦截器(HandlerInterceptor)。

拦截器的preHandle方法里判断session有没有登录用户,没有就重定向到登录页:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Admin admin = (Admin) session.getAttribute("loginAdmin"); if (admin == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

在spring-mvc.xml里配置拦截路径:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/loginCheck"/> <mvc:exclude-mapping path="/static/**"/> <mvc:bean class="com.demo.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

有点要注意,Bootstrap的css、js、图片这些静态资源也要排除掉,否则登录页都加载不出样式,浏览器控制台会报一堆403。我第一次做的时候没排除static路径,卡了半天才发现是拦截器把静态资源拦了。

3.2 收银台实现:前端购物车与后端事务

收银台页面是使用频率最高的页面,用户体验直接决定系统好不好用。前端页面上有一个商品搜索框,输入条码或商品名关键词,页面用Ajax请求后台接口,把匹配的商品列在一个表格区,点"加入购物车"按钮,商品进入下方购物车区域并累加数量和金额。结算时把购物车商品列表按JSON格式提交到后端。

前端这块用JSP加jQuery即可,不必引入太复杂的前端框架。购物车的数据我用JavaScript的数组维护,重点代码如下:

var cart = []; // 每个元素:{goodsId, name, price, quantity} function addToCart(goodsId, name, price) { var found = cart.find(item => item.goodsId === goodsId); if (found) { found.quantity++; } else { cart.push({goodsId: goodsId, name: name, price: price, quantity: 1}); } renderCart(); } function checkout() { if (cart.length === 0) { alert("购物车为空"); return; } $.ajax({ url: "/order/create", type: "POST", contentType: "application/json", data: JSON.stringify({items: cart, payType: selectedPayType, memberId: selectedMemberId}), success: function(result) { if (result.code === 200) { // 清空购物车,显示订单金额 cart = []; renderCart(); } } }); }

后端Controller接收到JSON后,调用Service层方法。Service层是整个收银流程的心脏,它要做的事按顺序排列:生成订单号、计算订单总金额、插入订单主表、遍历明细列表插入订单明细表、扣减每个商品的库存。任何一个环节抛异常,整笔交易都应该回滚。

核心Service代码的核心结构如下:

@Transactional(rollbackFor = Exception.class) public Order createOrder(CreateOrderForm form) { // 1. 生成订单号 String orderNo = "NO" + System.currentTimeMillis() + RandomUtil.randomNumbers(4); Order order = new Order(); order.setOrderNo(orderNo); BigDecimal total = BigDecimal.ZERO; // 2. 计算总价(以数据库商品价格为准,不能相信前端传来的价格) List<OrderItem> itemList = new ArrayList<>(); for (OrderItemForm itemForm : form.getItems()) { Goods goods = goodsMapper.selectById(itemForm.getGoodsId()); if (goods == null || goods.getStatus() != 1) { throw new RuntimeException("商品不存在或已下架"); } BigDecimal subtotal = goods.getPrice().multiply(new BigDecimal(itemForm.getQuantity())); total = total.add(subtotal); // 组装OrderItem... } order.setTotalPrice(total); // 3. 插入订单 orderMapper.insert(order); // 4. 插入明细 for (OrderItem item : itemList) { orderItemMapper.insert(item); } // 5. 扣减库存 + 增加销量 for (OrderItemForm itemForm : form.getItems()) { int rows = goodsMapper.deductStock(itemForm.getGoodsId(), itemForm.getQuantity()); if (rows == 0) { throw new RuntimeException("商品库存不足"); } } return order; }

代码里有几个关键点我必须强调:

第一,计算金额时不能用前端传来的价格,要拿数据库里的商品价格重新算。否则有人抓包把价格改成1分钱,整单就废了。服务端永远不信任客户端输入,这是开发的基本素养。

第二,@Transactional是Spring的声明式事务,方法内的多个数据库操作会在同一个事务中执行。inventory扣减失败抛出RuntimeException,前面插入的订单和明细会被自动回滚,不会出现"订单有了但库存没扣"的脏数据。

第三,deductStock方法对应前面说的条件更新SQL,而不是先select再update。这段mapper代码是:

<update id="deductStock"> UPDATE goods SET stock = stock - #{quantity}, sales = sales + #{quantity} WHERE id = #{id} AND stock >= #{quantity} </update>

这种写法保证了并发情况下也不会超卖,因为数据库层面的行锁会挡住两个同时执行的更新操作,后执行的那一个会因为stock不满足条件而更新0行,从而触发那个"库存不足"异常。

3.3 销售统计的SQL怎么凑

收银数据积累起来之后,管理员最关心的几个统计:今日销售额、今日订单数、商品销量排行、库存预警。这些用SQL聚合函数,比在Java里循环计算效率高得多,而且写法很直观。

今日销售额:

SELECT IFNULL(SUM(total_price), 0) FROM orders WHERE DATE(create_time) = CURDATE();

商品销量排行榜前10:

SELECT goods_name, SUM(quantity) AS total_qty FROM order_item GROUP BY goods_id, goods_name ORDER BY total_qty DESC LIMIT 10;

库存预警(比如小于10就提示补货):

SELECT id, name, stock FROM goods WHERE stock < 10 AND status = 1;

Controller里把统计结果封装成VO返回给JSP,页面再通过JSTL或少量JS渲染成卡片和表格。整个实现下来你会发现,MyBatis写这类SQL比JPA爽太多,这也是SSM这类教学项目仍然有生命力的原因。

4. 环境搭建与部署实战:从JDK到Tomcat走一遍

4.1 开发环境版本怎么选

这个项目对环境的要求不高,但版本匹配问题可能把你卡在第一步。我推荐的组合是:JDK 1.8、Maven 3.6.3、MySQL 5.7或8.0、Tomcat 8.5或9.0、IDEA 2020以上。

JDK 1.8是目前兼容性最稳的版本,Spring 5.x、Tomcat 9都能正常跑,后面新学Spring Boot也不会觉得跳变。Maven 3.6.3是不会和IDEA内置的Maven冲突的稳妥选择,太高版本在一些老IDEA里可能初始化报错。MySQL建议5.7,它对表结构校验较松,练习项目完全够用,但你电脑里装的是8.0也没问题,只要把驱动换成mysql-connector-java 8.0.x即可。

4.2 Maven配置阿里云镜像

Maven装好之后第一步不是建项目,而是改settings.xml。默认的中央仓库在国内下载依赖非常慢,pom里几十个依赖可能拉半小时还报错。打开Maven安装目录下conf/settings.xml,在mirrors节点里加上阿里云镜像:

<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>aliyun public repository</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>

改完后到IDEA里File -> Settings -> Build Tools -> Maven,把"User settings file"指向这个settings.xml,顺便把Maven home path指到你本地安装的Maven目录,而不是用IDEA捆绑的Maven。这样依赖下载、仓库管理等行为才是可控的。

4.3 IDEA导入与Tomcat部署

用IDEA直接打开pom.xml,选择Open as Project,后面步骤是:Project Structure确认Project SDK是1.8;Settings里找到Build Tools -> Maven把Runner的JRE设为1.8;然后配置Tomcat,Run -> Edit Configurations -> 左上角加号 -> Tomcat Server -> Local,Server选项卡里Application Server选择Tomcat安装目录,Deployment选项卡加Artifact选择"xxx:war exploded"。

地址填http://localhost:8080/,然后Run。第一次启动时Idea会调用Maven下载项目依赖并编译war包,如果前面镜像配置好了,整个过程两三分钟内能完成。

4.4 数据库连接配置

项目的jdbc.properties配置文件是连接MySQL的关键,不同的MySQL版本、不同的连接方式,这个配置差异巨大,很多人的项目栽在这里。以MySQL 8.0为例,驱动类名是com.mysql.cj.jdbc.Driver,连接串必须带时区参数,否则会报"Server returns invalid timezone"错误:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/supermarket?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=你的密码

如果你是MySQL 5.7,驱动类名用com.mysql.jdbc.Driver,配置里不建议带useSSL=false以外的多余参数。补充一点,直接用Navicat或MySQL Workbench建库建表很方便,但学习阶段我强烈建议你在命令行窗口手敲一遍建库导入SQL,这样对数据库的底层行为理解更深。

5. 开发与运行中的经典问题排查

5.1 Maven依赖下载慢或找不到包

这个问题我在给同学调代码时几乎每次都遇到。典型症状是IDEA报"Could not resolve dependencies"或者红色波浪线出现在import语句上。排查顺序我建议这样:

第一步看settings.xml有没有配置阿里云镜像,没配置或配置不生效大概率就是这个原因。第二步看IDEA使用的Maven是不是本地安装的版本,如果你改了系统配置但IDEA里还是捆绑Maven,设置不会读到你的settings.xml。第三步看本地仓库目录位置,默认在用户目录/.m2/repository,如果之前有下载到一半的损坏文件,删掉对应目录重新加载即可。

还有种情况特别坑:pom.xml里依赖版本和本地仓库里已有的版本不一致,IDEA显示红色的Artifact无法解析。解决办法是在IDEA右侧Maven面板点刷新图标(Reload All Maven Projects),让它重新解析一次。

5.2 MySQL连接失败的各种姿势

连接失败是JavaWeb项目最高发的故障,错误信息五花八门。整理一个速查表:

报错关键信息原因解决
Access denied for user 'root'@'localhost'密码错误或账号无权限检查jdbc.properties里的用户名密码,确认mysql -u root -p能登录
Unknown database 'supermarket'数据库还没建执行CREATE DATABASE supermarket,再导入表脚本
Communications link failure连接串不对或服务没启动确认MySQL服务运行中,URL无拼写错误
Server returns invalid timezoneMySQL 8时区未设置URL加serverTimezone=Asia/Shanghai
Public Key Retrieval is not allowedMySQL 8.0.12及以上版本安全限制URL加allowPublicKeyRetrieval=true
Port 3306连接超时MySQL端口被占用或防火墙拦截查看netstat -ano

5.3 Tomcat部署后端口占用与404

端口占用是启动阶段的常见问题。IDEA启动Tomcat时左下角报"Port 8080 was already in use",说明有别的进程占了8080。可以换端口,也可以找到占用进程关掉。在Windows上命令是netstat -ano | findstr "8080",找到PID后在任务管理器里结束它。Linux/macOS用lsof -i:8080,然后kill进程。

部署成功后访问页面404,优先检查部署Artifact配置和项目访问路径。使用war exploded方式部署,IDEA右上角的Application context默认是"/xxx_war_exploded",如果你的Controller里写的是redirect:/login,可能因为上下文路径不对导致跳转失败,建议把Application context改成"/"。另外也要确认项目的web.xml存在,并且DispatcherServlet配置的url-pattern没有错映射掉JSP请求。

5.4 中文乱码问题

JSP页面上中文商品名变成问号,或者MySQL里存的数据出现乱码,基本可以确认是编码环节没统一。要端到端检查,从浏览器到Java再到MySQL一条链路全部设置UTF-8:

  • JSP页面顶部加<%@ page contentType="text/html;charset=UTF-8" %>
  • 页面form表单提交时method="post"并让过滤器处理编码,web.xml里配置Spring的CharacterEncodingFilter,它专门把request的编码设置为UTF-8:
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
  • MySQL连接URL里带characterEncoding=utf8
  • 建库时指定utf8mb4字符集:CREATE DATABASE supermarket DEFAULT CHARSET utf8mb4;

这几个全部一致之后,乱码问题基本绝迹。

5.5 常见问题速查表(经验总结)

场景现象我常用的排查命令/工具
依赖问题红色import、reload无效检查settings.xml和localRepository路径
数据库问题连接超时、时区报错先命令行登录sql验证账号,再看URL
权限问题某些页面直接跳登录页看拦截器exclude-mapping是否把静态资源漏掉
事务问题订单出现在但库存未扣检查Service方法上是否有@Transactional
页面问题表单提交后后台报空指针检查Controller方法参数名是否与input的name一致,必要时加@RequestParam
数据精度金额计算多出几分钱检查是否用了float/double,统一BigDecimal和DECIMAL

6. 做完这个项目的几点个人体会

做这个校内超市收银管理系统,最大的收获不是写完了多少行代码,而是把"数据从页面到数据库再回到页面"的完整链路彻底打通了。你会真正理解Maven管理依赖的价值,理解为什么MySQL要设计成事务型数据库,理解自研系统里"金额计算绝不能信前端"这种红线原则。如果你是用它当课程设计或者毕业设计,强烈建议在答辩前自己亲手把Recovery流程跑一遍:编译、部署、初始化数据库、连续下几单、查看库存变化和日报表。这个过程能帮你把项目真正装进脑子里,不会被老师的随机提问打懵。

踩过坑之后,我现在做一切JavaWeb项目都养成了三个习惯:先画数据表关系再写业务逻辑、写任何金额计算先声明BigDecimal、改配置前先备份一份原文件。这套系统如果你愿意,后续还可以往微信小程序点单、刷脸支付的方向扩展,但先把这套SSM的地基打扎实,比什么都值。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 8:05:39

企业管理系统表单为何不能直接套Element UI?动态表单配置方案实战

最近团队在重构一个老旧的 OA 系统&#xff0c;老板看完原型丢给我一句&#xff1a;“表单直接用 Element UI 套上去不就行了&#xff0c;控件不是现成的吗&#xff1f;”这句话差点把我噎住。做过企业管理系统前端的同学都知道&#xff0c;这话听着省事&#xff0c;真正落地的…

作者头像 李华
网站建设 2026/9/28 8:05:36

C#+EF构建生产管理系统:源码架构、核心模块与性能实战

做一个制造企业的生产管理系统&#xff0c;C#配上EF&#xff08;Entity Framework&#xff09;这套技术栈&#xff0c;在业内其实非常常见。我自己前几年就接手过一个类似的源码项目——一套基于C# EF架构搭建的离散制造业生产管理系统&#xff0c;功能覆盖工单管理、物料追溯…

作者头像 李华
网站建设 2026/9/28 8:02:39

大模型与3D渲染协同架构:硬件约束下的实时互动系统设计

1. 这不是一张“示意图”&#xff0c;而是一份可执行的3D渲染系统蓝图你点开过多少张标着“大模型3D渲染”的架构图&#xff1f;是不是大多停留在“输入文本→大模型理解→生成3D网格→渲染显示”这种四步流程图上&#xff1f;线条很酷&#xff0c;箭头很顺&#xff0c;但当你真…

作者头像 李华
网站建设 2026/9/28 8:02:20

OpenClaw爆火背后:AI Agent安全风险与防护清单

说实话&#xff0c;OpenClaw这阵子火得有点出乎我的意料。各大技术社区里&#xff0c;安装教程一篇接一篇&#xff0c;有人拿它配合阿里云服务器做个人Agent&#xff0c;有人接入Microsoft Teams让机器人在群里干活&#xff0c;还有人干脆把它当成本地浏览器指挥中枢&#xff0…

作者头像 李华
网站建设 2026/9/28 8:01:28

深入理解AQS:从源码到实战,掌握Java并发编程的核心

1. 为什么说AQS是并发包的“珠穆朗玛峰”搞Java并发编程的人&#xff0c;迟早会撞上AQS这个名字。它全称是AbstractQueuedSynchronizer&#xff0c;中文叫抽象队列同步器&#xff0c;在java.util.concurrent.locks包下面。我见过不少工作三五年的开发&#xff0c;能熟练使用Ree…

作者头像 李华
网站建设 2026/9/28 8:01:27

串口不够用?ESP32外扩CH432/CH438/CH9434选型与实战

做嵌入式这些年&#xff0c;“串口不够用”这个问题几乎每做一个新项目都要碰上一次。ESP32看似给了三个UART&#xff0c;实际上Serial0被烧录和调试占用&#xff0c;留给外设的往往只有一两个&#xff0c;而一个稍复杂的IoT设备里&#xff0c;GPS模块要串口、LoRa模块要串口、…

作者头像 李华