资讯动态

基于HTML5的网上团购系统:从数据库设计到答辩避坑全流程

发布时间:2026/10/13 5:05:10 来源:尧图企业网站定制
如果你在毕业设计选题里搜过“团购”多半见过“基于HTML5的网上团购系统设计与实现”这个题目它每年都在清单里每年也都有人做到一半卡在数据库设计或者订单状态上。我当时选这个题就是看中它的业务链路完整但复杂度可控浏览商品、加入购物车、提交订单、模拟支付、商家核销整条链路走通之后论文、PPT、演示视频的素材全都有了。这篇文章按我实际做完的顺序把需求拆分、技术选型、数据库设计、核心代码、避坑记录以及后面怎么把它变成论文和答辩材料的经验一起讲清楚。适合正在做毕设、课程设计或者想拿一个完整电商类项目练手的同学参考。1. 业务链路与整体方案先想清楚这个系统到底在解决什么问题1.1 网上团购的核心业务链路从哪里开始拆团购系统的本质不复杂商家发布一个团购套餐或代金券用户以相对优惠的价格购买到店后出示凭证给商家核销。围绕这条线系统的角色只有三种普通用户、商家、系统管理员。我从用户视角把核心流程过了一遍注册登录、浏览商品、按分类或关键词筛选、加入购物车、提交订单、模拟支付、在“我的订单”里查看状态、消费后评论。商家视角要简单些管理自家的商品、查看订单、核销用户的购买凭证。管理员则是管人和管商品用户管理、商家管理、商品上下架、订单统计。这三个角色加起来就是一个非常标准的电商管理系统既不会像纯商城那样大而全又比“增删改查”的练习项目有内容。这里有个经验做毕设不要一上来就堆功能。我见过有人把秒杀、优惠券、拼团、满减全塞进去结果两个月过去接口都没写完。正确做法是先固定最小可用版本也就是上面说的“下单-支付-核销-评论”这条主干其他都是加分项。主干跑通了论文的“核心功能实现”章节才有东西写演示视频也才有完整故事线。1.2 前端HTML5和后端框架怎么搭配才算稳妥技术选型是很多同学纠结的地方。我最终用的是前端HTML5 CSS3 原生JavaScript部分页面用了一点Vue后端Spring Boot 2.x MyBatis MySQL。理由说得很直接这套组合是当下高校毕设里最稳的方案资料多、老师熟、跑起来简单。为什么前端坚持用HTML5原生能力而不是直接上一整套Vue/React因为这个题目的题目里就写着“基于HTML5”你要让答辩老师在这套系统里看到HTML5的痕迹语义化标签、本地存储、表单验证、Canvas绘图、媒体元素、响应式布局这些原生能力和Spring Boot配合得很好。如果直接做成前后端分离的Vue项目反而跑题答辩时很难把“基于HTML5”这个点讲透。后端用Spring Boot的原因更实际它把Tomcat内嵌了打包成jar后一条命令就能启动。现场演示最怕环境问题Spring Boot恰恰能把环境问题压缩到最小。MyBatis则让SQL保持完全可控尤其在做库存扣减、订单分组统计这类操作时写SQL比用JPA的自动方法踏实得多。不建议选SSH老框架也不建议纯PHP裸写——不是不能用是答辩时技术含量容易被质疑。1.3 功能模块划分与页面清单功能模块划分直接影响后面开发速度和论文结构。我按角色把系统拆成三个管理端加一个数据统计模块然后映射成具体的页面。前台用户端有首页、商品列表、商品详情、购物车、用户登录注册、个人中心、订单确认。商家和管理员共用一套后台商品管理、订单管理、核销、用户管理、统计看板。页面数量我控制在14个左右这里有一个很实用的原则一个页面只干一件事。商品列表页就做筛选和分页详情页就做展示和加购购物车页就做数量调整和结算。这样前端代码结构清楚写论文时可以一个页面一截图一个功能点不会出现“一个页面截图里堆了五个功能”的尴尬局面。我建议你在动手之前就把页面清单和功能对照表列出来。这个表后面可以直接放进论文需求分析章节省掉很多返工时间。我那会儿就是先画了这个表格开发时照着页面清单走做完之后论文里的功能结构图基本就是照着表格画的效率很高。2. 数据库和前端交互最容易翻车的两个环节2.1 核心表怎么设计字段类型为什么这样定数据库设计是整个系统最不能偷懒的部分。我最终设计了6张表用户表t_user、商家表t_business、商品表t_product、订单表t_order、订单明细表t_order_item虽然系统可能一次只买一个商品但保留这个表结构更规范、评论表t_comment。电商里常见的购物车表我特意没建因为前端用localStorage存储购物车数据这正是体现HTML5特性的地方后面会细说。先从用户表开始建表SQL大概是这个样子CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(128) NOT NULL, nickname VARCHAR(50) DEFAULT NULL, phone VARCHAR(11) DEFAULT NULL, avatar VARCHAR(200) DEFAULT NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;密码字段我特意设计为VARCHAR(128)因为我们用BCrypt加密加密后的密文比MD5长很多很多同学用VARCHAR(32)存密码注册一调通就报数据库报错。另外用户名加了唯一索引这是登录类系统必须有的约束。商品表是业务核心字段类型这里要先讲一个关键点——金额字段绝对不能用float或doubleCREATE TABLE t_product ( id INT NOT NULL AUTO_INCREMENT, business_id INT NOT NULL, name VARCHAR(100) NOT NULL, category VARCHAR(50) DEFAULT NULL, original_price DECIMAL(10,2) NOT NULL, team_price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, sold_count INT NOT NULL DEFAULT 0, image VARCHAR(200) DEFAULT NULL, detail TEXT, status TINYINT NOT NULL DEFAULT 1 COMMENT 1上架 0下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_business (business_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么价格用DECIMAL(10,2)因为float和double在计算机里是二进制近似存储1.1加2.2可能算出3.3000000000000003结算差额虽然小但答辩时老师问“订单金额为什么有误差”你就下不来台。DECIMAL是字符串存储的定点数精度完全可控这是财务数据的标准选择。订单表设计要特别关注状态字段和业务唯一性CREATE TABLE t_order ( id INT NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, user_id INT NOT NULL, product_id INT NOT NULL, pay_amount DECIMAL(10,2) NOT NULL, count INT NOT NULL DEFAULT 1, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待支付 1已支付待核销 2已完成 3已取消 4已退款, verify_code VARCHAR(12) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;订单号order_no不是自增ID而是用时间戳加随机数生成的唯一字符串并加了唯一索引。这样设计有两个原因第一订单号会展示给用户自增ID暴露业务规模第二下单时先插入订单再根据订单号做后续支付唯一索引能防止并发重复提交。status用TINYINT加注释每个数字代表什么状态一目了然比用字符串“PAID”之类更省空间也更规范。verify_code字段是核销码支付成功后生成用户到店报给商家商家后台输入核销码完成核销这一条链路就是团购系统区别于普通商城的地方。外键这块我的建议是逻辑外键就够了不要建物理外键约束。原因很实际毕设数据量小物理外键看起来严谨但测试时插入数据顺序错误就会卡住删数据也麻烦。表之间通过business_id、user_id这些字段维持关系在SQL查询里做JOIN代码里体现关联逻辑答辩时也能讲清楚。2.2 HTML5在页面里的几个关键落地技巧这个项目的亮点要落在HTML5上所以得真有东西能拿出来讲。我实际用到了四个比较有代表性的特性每个都在答辩时能给老师演示。第一是语义化标签。页面结构不再是一堆div而是header、nav、main、section、footer。这个改变不只是代码好看语义化标签对SEO友好浏览器和读屏软件能正确识别页面结构配合CSS布局也清爽。首页结构大致是这样的header nav a hrefindex.html首页/a a hreflist.html团购套餐/a a hrefcart.html购物车/a /nav /header main section classbanner !-- 轮播图 -- /section section classhot-products !-- 热门商品列表 -- /section /main footer关于我们 联系方式/footer第二是localStorage实现购物车。传统方案是把购物车数据存后端数据库但这里我把购物车放在浏览器本地仅在下单时提交到后端。逻辑是购物车是临时性的用户可能只是随便逛逛放本地能减少数据库写入也省去购物车表的维护。localStorage的API很简单// 用 userId 做 key 前缀防止换账号后购物车串数据 function getCartKey() { return cart_ currentUserId; } function getCart() { var cart localStorage.getItem(getCartKey()); return cart ? JSON.parse(cart) : []; } function addToCart(product, count) { var cart getCart(); var existed cart.find(function (item) { return item.productId product.id; }); if (existed) { existed.count count; } else { cart.push({ productId: product.id, name: product.name, price: product.teamPrice, count: count }); } localStorage.setItem(getCartKey(), JSON.stringify(cart)); }要注意localStorage只能存字符串所有对象都要JSON.stringify之后再存读取时JSON.parse回来这个细节第一次写很容易漏。另外localStorage单域名下大概能存5MB放购物车绰绰有余但如果你把商品图片也塞进去就会爆所以只存关键字段就够了。第三是HTML5表单验证。手机号、密码、邮箱这些校验原生input属性就能搞定不用引任何校验插件input typetel namephone pattern^1[3-9][0-9]{9}$ placeholder请输入11位手机号 required input typepassword namepassword minlength6 maxlength20 required input typeemail nameemail placeholder请输入邮箱 required加上CSS的:invalid和:valid伪类还能在输入不合法时直接显示红色边框。这让前端校验代码量减少了大概一半后端再接一层兜底校验就够了。这个点答辩时可以提说明你了解前端验证的目的只是提升体验真正安全还是靠后端。第四是Canvas画数据统计图。管理员后台需要一个订单统计看板我一开始想引ECharts后来发现题目强调HTML5干脆用Canvas画了三个简单图表近七日订单量柱状图、商品分类销售占比饼图、商家订单数排名。原理不难拿到Canvas上下文后用循环画矩形和扇形var canvas document.getElementById(orderChart); var ctx canvas.getContext(2d); // 拿到近7天订单数据后按数量换算柱高 ctx.fillStyle #4a90d9; for (var i 0; i data.length; i) { var barHeight data[i].count / maxCount * 200; ctx.fillRect(40 i * 50, 240 - barHeight, 30, barHeight); }用原生Canvas画图有明显的副作用代码量比ECharts大但答辩时你能讲清楚绘图原理老师会认为你有真功夫而不是什么都是框架拼出来的。2.3 前后端接口格式先定好能少改一半代码前后端联调最怕的就是接口各写各的。我一开始先把接口返回格式统一了所有后端接口都返回同一个Result对象public class Result { private int code; private String msg; private Object data; public static Result success(Object data) { Result r new Result(); r.code 0; r.msg success; r.data data; return r; } public static Result error(int code, String msg) { Result r new Result(); r.code code; r.msg msg; return r; } }code为0表示成功非0表示失败前端只用判断code就能决定是跳转还是弹提示。接口名称也统一用/api前缀比如/api/login、/api/product/list、/api/order/create。前端用fetch发送请求POST接口传JSONfetch(/api/login, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ username: username, password: password }) }) .then(function (res) { return res.json(); }) .then(function (data) { if (data.code 0) { window.location.href index.html; } else { alert(data.msg); } });商品列表的分页和筛选也一并在接口层约定/api/product/list?page1size8keyword火锅category美食。前端把参数拼在URL上后端接收后组装SQL条件。这样定好之后前后端可以并行开发我先把接口文档写在Markdown里前端页面照着文档调后端照着文档实现联调阶段几乎没有返工。3. 从零到验收核心功能的关键实现记录3.1 环境准备和项目骨架搭建搭建这个系统需要的环境非常基础JDK 8或11、Maven 3.6以上、MySQL 5.7或8.0、IDEA。Spring Boot版本我用的是2.7.x这个版本稳定各种资料好找。创建项目时通过Spring Initializr勾选Web、MyBatis、MySQL Driver就可以省去手动引依赖的麻烦。项目目录结构建议按职责分包这样论文写“系统架构”时能直接贴图src/main/java/com/example/team/ controller/ # 接口层只做参数接收和结果包装 service/ # 业务逻辑层订单状态、库存扣减都在这里 mapper/ # MyBatis接口对应SQL映射 entity/ # 数据库实体类 config/ # 拦截器、跨域配置 src/main/resources/ mapper/ # XML文件也可以全用注解SQL static/ html/ # 前端页面 css/ js/ images/ sql/ init.sql # 建库建表语句application.yml里最值得注意的就是数据源配置和上传大小配置。数据源如果乱码通常就是连接URL没加编码参数spring: datasource: url: jdbc:mysql://localhost:3306/team_buy?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: yourpassword mvc: static-path-pattern: /static/** servlet: multipart: max-file-size: 10MB max-request-size: 10MB上传大小的配置是我踩坑之后才加上的。Spring Boot默认文件上传限制只有1MB商品图片稍微大一点就报错不提前配好演示视频录到上传图片那一段直接翻车。3.2 登录、下单、支付模拟和核销四个关键点登录注册这里有一个安全细节值得写进论文。密码不能明文存数据库也不建议用简单MD5。我直接用Spring Security里的BCryptPasswordEncoder// 注册时加密 user.setPassword(new BCryptPasswordEncoder().encode(password)); // 登录时校验 public User login(String username, String password) { User user userMapper.findByUsername(username); if (user null) { return null; } BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); if (!encoder.matches(password, user.getPassword())) { return null; } return user; }登录接口设置SessionPostMapping(/api/login) public Result login(String username, String password, HttpSession session) { User user userService.login(username, password); if (user null) { return Result.error(1001, 用户名或密码错误); } session.setAttribute(user, user); return Result.success(user); }后面再用拦截器统一校验Session未登录访问购物车或订单接口就返回提示。这样既实现了登录态管理答辩时也能说清楚权限控制怎么做的。下单是整个系统最需要严谨对待的功能因为涉及库存扣减和订单创建的一致性。我用了事务加SQL条件更新保证不会超卖Transactional public Order createOrder(Long userId, Long productId, Integer count) { // 条件更新只有库存足够时才会扣减成功 int rows productMapper.deductStock(productId, count); if (rows 0) { throw new RuntimeException(库存不足); } // 生成订单号 String orderNo TB System.currentTimeMillis() RandomUtil.randomNumbers(4); // 查商品价格计算支付金额插入订单表 Product product productMapper.findById(productId); Order order new Order(); order.setOrderNo(orderNo); order.setUserId(userId); order.setProductId(productId); order.setPayAmount(product.getTeamPrice().multiply(new BigDecimal(count))); order.setStatus(0); orderMapper.insert(order); return order; }关键就在那句SQLUPDATE t_product SET stock stock - #{count} WHERE id #{productId} AND stock #{count}这个SQL的意思是库存充足才扣减扣减后返回受影响行数如果返回0说明库存不够直接抛异常让事务回滚。相比“先查库存再减库存”的两步操作这样不会有并发超卖风险。这个方法代码量不大但是在答辩时回答“高并发下怎么保证数据一致”非常管用。支付这块我没有接真实支付而是模拟了支付流程。前端弹出一个支付窗口展示二维码图案二维码是用纯前端JS生成的模拟码不涉及真实支付通道点击“确认已支付”后请求一个模拟回调接口// 模拟第三方支付回调实际项目中这是微信/支付宝服务器调用 PostMapping(/api/pay/notify) public Result payNotify(RequestBody PayNotifyDTO dto) { Order order orderMapper.findByOrderNo(dto.getOrderNo()); if (order ! null order.getStatus() 0) { order.setStatus(1); order.setPayTime(LocalDateTime.now()); // 生成核销码比如6位数字 order.setVerifyCode(RandomUtil.randomNumbers(6)); orderMapper.update(order); } return Result.success(null); }这里有个重要的设计思路让支付状态变更走独立的“回调接口”而不是用户在前端点按钮直接把订单改成已支付。这样系统结构是模仿真实支付流程的答辩时老师问“支付安全性怎么考虑”你能说出回调验签、幂等判断这些概念虽然模拟版本没有真实签名但思路是对的。核销功能是在商家后台完成的。商家登录后看到待核销的订单列表输入用户报的6位核销码核销成功订单状态变为已完成。给用户的体验是购买、到店、报码、核销整个团购闭环就完整了。3.3 移动端适配和演示视频的准备团购系统的用户经常在手机上操作所以响应式适配必须先做。viewport标签是基础meta nameviewport contentwidthdevice-width, initial-scale1.0样式上用flex布局加media query。断点设置我用了两档宽度小于768px时导航栏折叠成菜单商品卡片从4列变成2列宽度大于1200px时容器宽度固定为1140px居中。这种方案代码量小演示时拿浏览器开发者工具切换设备模拟器就能直观展示。关于演示视频我建议在开发接近尾声时就准备一个单独的录屏脚本不要等项目“完全做完”再录。我的脚本只有七步首页轮播、用户登录、商品筛选、加入购物车、提交订单、模拟支付、商家核销。每步在视频里控制在20到30秒总时长控制在3到5分钟。录制时用Win10自带的录屏或者OBS分辨率调到1920乘1080关键操作步骤鼠标点击位置提前想好别录到一半发现字体太小看不清。我先用测试账号完整走了一遍流程录到第二遍就是全部一次过的成片。4. 从开发到答辩常见问题与避坑实录4.1 我真实踩过的六个坑整个开发过程我最想分享的就是这些坑每个都是自己在日志里翻了半天才解决的。问题现象根本原因解决办法页面中文全部变成问号数据库连接URL没设置UTF-8连接URL加characterEncodingutf8上传商品图片报错Spring Boot默认真实上传大小上限1MBapplication.yml里配置max-file-size与max-request-size订单支付时间显示成“2025-01-01T10:30”LocalDateTime默认序列化格式不是预期格式配置Jackson的日期格式patternlocalStorage购物车刷新后还在但换账号还在购物车key没区分用户key加userId前缀退出登录时清除对应key前端传了JSON但后端取不到值POST没有指定Content-Type为application/jsonfetch里显式设置Content-Type请求头演示时MySQL端口被占用本地装多个MySQL或MariaDB统一改端口并写进README启动说明举一个最典型的展开一下中文乱码。我曾经在Windows上开发一切正常换到演示用的电脑后页面突然乱码。排查后发现是MySQL连接URL漏了characterEncoding参数。数据库本身是utf8mb4但连接层没有指定客户端编码MySQL就按系统默认的latin1处理中文自然就乱了。这个问题的解决办法很简单但排查过程很折磨人。建议在项目初始化时就把URL写全不要等出现乱码再补。还有一个隐藏比较深的问题日期序列化。Spring Boot默认会把LocalDateTime转成ISO格式也就是“2025-06-15T14:30:00”中间那个T让页面展示很丑。我加了一个Jackson配置类Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss) )); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss) )); }; } }这个问题不解决订单列表页面的时间显示会非常影响观感答辩截图也会难看。4.2 论文、PPT和演示视频怎么组织才不慌论文我按学校模板走的标准结构摘要、绪论、相关技术、需求分析、概要设计、详细设计、系统实现、系统测试、总结展望。比较关键的是要把每一个章节和实际代码对应上不要出现需求分析里写了A功能详细设计里却没有A的映射。我把功能模块表、页面清单、ER图、接口列表整理成一个文档写论文时直接引用章节之间逻辑很顺。ER图建议用PowerDesigner或者draw.io画数据库表结构要和实际SQL完全一致。我见过不少论文里ER图和真实表对不上老师现场查一下就能发现这种低级错误很伤印象分。核心的流程图不要用网上模板就画“用户下单支付商家核销”这一条主线我用了draw.io手绘风格的流程看起来清爽答辩时也容易讲。PPT控制在14页以内讲述时长控制在5到7分钟。我实际用的PPT结构是背景与意义1页、技术选型1页、系统架构图1页、数据库ER图1页、用户端核心页面截图4到5页、商家端和管理员端截图2到3页、测试结果1页、总结1页。不要贴大段代码PPT上只放关键方法名和思路代码在论文里演示视频里才展示跑起来的效果。演示视频的素材来源我在开发过程中就留意留存了。每做完一个功能就截一张图没做好的地方记录一下当时的状态最后统一整理到一个截图文件夹里。视频则单独录流程脚本就是上面说的七步。视频最后展示一下源码目录结构和启动方式这样老师看着会觉得项目完整可交付。4.3 答辩现场高频问题与回答思路答辩前我把老师可能会问的问题都列了出来每个都写了一两句话的回答思路。最常被问的有四个。第一个问题为什么用HTML5而不是原生App或者微信小程序回答思路是HTML5跨平台免安装一套代码适配PC和手机浏览器结合CSS3可以做响应式布局localStorage、Canvas这些新特性正好覆盖了系统的购物车和统计需求。小程序固然适合团购场景但题目限定的是Web技术栈HTML5是最直接且完整的方案。第二个问题如何防止库存超卖回答思路是不采用先查库存再扣减的方式而是用UPDATE语句带stock #{count}条件进行原子扣减受影响行数为0就说明库存不足整个下单操作放在事务里任何一步失败都会回滚。再补充一句如果业务量更大可以引入Redis预扣库存和消息队列削峰说明你有扩展意识。第三个问题支付安全怎么考虑回答思路要小心不要吹牛。我这个系统是模拟支付所以如实说支付环节模拟了真实的回调机制回调接口对订单状态做了幂等判断只有待支付状态的订单才会被更新重复通知不会产生重复操作如果是生产环境必须使用微信/支付宝官方SDK用回调验签和金额比对来保证安全性。这样既站得住又不夸大。第四个问题如果用户量变大系统怎么扩展回答思路从三个层次说先从数据库层面优化高频表的查询字段已经建了索引再引入Redis缓存商品信息和验证码减少数据库压力最后做水平扩展把静态资源放到Nginx或CDN应用服务多节点部署数据库做主从。不要展开太深点到为止老师清楚这是毕设能说出方向就够了。这套问题准备下来答辩基本不会慌。我现在回想整个项目最庆幸的是当初没有拼命堆功能而是把小而完整的链路打磨清楚。如果你也正卡在这个题上建议先把下单、支付、核销这条主链路跑通再回头补细节——给自己留一个至少完整的演示版本这才是最重要的。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑