
简介这是一套基于JavaWeb与Vue2实现的前后端分离点餐系统后端源码面向Java初学者及Web全栈学习者适用于课程设计、毕业项目或中小餐饮系统原型开发。资源包含157个文件主体为137个Java类涵盖OrderService、BillService、UserService等核心业务模块、12个XML配置文件MyBatis-Flex映射与Servlet配置、2个SQL建表脚本、2个Properties配置文件以及HTTP接口测试样例和README说明整体压缩包仅168KB轻量易读。已有453人学习下载适合快速理解企业级认证授权流程。读者可直接部署运行完整掌握基于ServletMyBatis-Flex的后端架构深入实践Redis缓存会话、JWT令牌鉴权、ZXing动态生成订单二维码等关键技术点并通过清晰分层的服务模块如CartService、PayService、AuthService学习典型电商类业务逻辑组织方式。1. 这不是又一个“学生作业式”点餐系统它用原生 Servlet MyBatis-Flex Redis JWT 跑通了真实餐饮场景的认证流、订单流与支付闭环你肯定见过太多标着“JavaWeb点餐系统”的压缩包——点开全是index.jsp、login.jsp、web.xml里堆满servlet标签数据库连个外键约束都没有登录靠session.setAttribute(user, user)硬扛下单时订单号用System.currentTimeMillis()拼接刷新页面就重复提交。但这个基于javaWeb的点餐系统源码.zip不是。它明确放弃 Spring Boot 的自动装配便利性选择用原生 Servlet 手写整套 HTTP 生命周期控制它没用 MyBatis-Plus而是选了更轻、更可控的MyBatis-Flex支持 Lambda 查询、实体自动映射、无 XML 动态 SQL它把 JWT Token 存进 Redis 做双校验Header 解析 Redis 存活验证真正落地了「单点登录」而非「单点登录幻觉」它甚至在PayService.java里预留了generateQrCodeForWechatPay()方法入口并已集成 ZXing —— 这意味着微信扫码支付的后端骨架已经焊死在代码里不是画饼。适合谁适合正在啃透 JavaWeb 底层机制的中级开发者你想知道 Filter 怎么拦截未登录请求、JWT 如何在AuthService.java里完成签发与解析、Redis Key 设计为什么是auth:token:{jwtId}而不是user:token:{uid}、MyBatis-Flex 的QueryWrapper.eq(User::getPhone, phone)是如何翻译成预编译 SQL 的——这份源码就是你的调试靶场。它不教你怎么拖控件它逼你读ServletUtils.java里那 37 行readJsonBody()手动解析 POST Body 的逻辑。2. 从零跑通后端环境准备、模块依赖与核心服务链路拆解2.1 环境清单与版本强约束Mysql8、JDK11、Tomcat9 是硬门槛这不是一个“换个 JDK 就能跑”的项目。pom.xml明确声明properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target mysql.version8.0.33/mysql.version redis.version4.3.1/redis.version mybatis-flex.version1.9.2/mybatis-flex.version zxing.version3.5.2/zxing.version /properties提示JDK 17 或 JDK 8 都会失败。JDK 11 是唯一经过验证的版本。MySQL 必须是 8.x因使用了caching_sha2_password认证插件JDBC URL 必须带?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue。Tomcat 推荐 9.0.83低于 9.0.65 会出现HttpServletRequest.getParts()在文件上传时返回空集合的兼容问题。安装顺序必须是JDK11 → MySQL8 → Redis → Tomcat9 → Maven3.8。其中 Redis 不仅用于 Token 存储CartService.java中购物车数据也采用Hash结构存于 RedisKey 为cart:{userId}这是为后续高并发秒杀预留的缓存穿透防护接口不是摆设。2.2 依赖树精简逻辑为什么弃用 Spring Security而手写 AuthFilter项目中没有spring-security-web、没有spring-boot-starter-web只有javax.servlet-api和javax.annotation-api。认证授权完全由三层实现AuthFilter.java全局 Filter捕获/api/**请求提取 Header 中Authorization: Bearer xxxAuthService.java核心类含verifyToken(String token)解析 JWT 并查 Redis、issueToken(User user)生成含userId,role,exp的 TokenServletUtils.java提供getUserIdFromRequest(HttpServletRequest req)工具方法被所有 Service 调用。这种设计牺牲了配置灵活性换来了可调试性你在AuthFilter.doFilter()打断点能清晰看到req.getRequestURI()是/api/order/createtoken是eyJhbGciOiJIUzI1NiJ9...verifyResult是true/falseredis.get(auth:token: jwtId)返回{userId:1001,role:USER}—— 每一步都裸露在 IDE 里。而 Spring Security 的UsernamePasswordAuthenticationFilter是层层代理新手调试时容易迷失在AbstractAuthenticationProcessingFilter的 17 层调用栈里。2.3 核心服务链路从用户登录到生成支付二维码的 7 步调用以一次完整下单为例后端调用链路如下已在OrderService.java和PayService.java中实装步骤类/方法关键动作数据流向1AuthService.login()校验账号密码查 DB 得User对象MySQL → Java Object2AuthService.issueToken(user)生成 JWT存入 RedisKeyauth:token:{uuid}Value{userId,role,exp}TTL30minJava → Redis3CartService.getCartItems(userId)从 Redis Hashcart:{userId}读取商品列表Redis → Java List4OrderService.createOrder(cartItems, userId)创建订单主表 订单明细表事务控制MySQLINSERT x25BillService.generateBill(orderId)生成账单含金额、时间、状态存bill表MySQLINSERT6PayService.prepareWechatPay(billId)调用微信统一下单 API模拟获取prepay_idHTTP Client → 微信沙箱7PayService.generateQrCodeForWechatPay(prepayId)用 ZXing 生成含weixin://wxpay/bizpayurl?prxxx的二维码 PNGJava → byte[] → Response OutputStream注意第 6 步目前是模拟PayService.java中// TODO: real wechat pay api call注释清晰但第 7 步generateQrCodeForWechatPay()已完整实现传入字符串即可返回byte[]二维码图像流 —— 这是你接入真实支付前最值钱的验证环节。2.4 MyBatis-Flex 实战比 MyBatis-Plus 更干净的 Lambda 查询写法BillService.java中有一段典型查询public ListBill getBillsByUserId(Long userId, String status) { QueryWrapperBill wrapper QueryWrapper.create() .select() .from(BILL) .where(BILL.USER_ID.eq(userId)) .and(BILL.STATUS.eq(status)) .orderBy(BILL.CREATE_TIME.desc()); return mapper.selectListByQuery(wrapper); }对比 MyBatis-Plus 的lambdaQuery().eq(Bill::getUserId, userId).eq(Bill::getStatus, status)MyBatis-Flex 的QueryWrapper.create()优势在于SQL 可视化.where(BILL.USER_ID.eq(userId))直接对应WHERE user_id ?字段名来自静态常量BILL.USER_IDIDE 可跳转、可重构无 XML 侵入全部 Java 代码定义无需维护BillMapper.xml类型安全BILL.CREATE_TIME.desc()返回的是OrderByColumn编译期报错而非运行时报Invalid column name create_time灵活组合.and()可嵌套.or()支持复杂条件如((statusPAID OR statusREFUND) AND amount 100)。OrderService.java中的分页查询更体现其价值public PageOrder getOrdersByUserId(Long userId, int page, int size) { QueryWrapperOrder wrapper QueryWrapper.create() .select() .from(ORDER) .where(ORDER.USER_ID.eq(userId)) .orderBy(ORDER.CREATE_TIME.desc()); return mapper.paginate(page, size, wrapper); // 返回 PageOrder含 total、list、page、size }PageOrder是标准 POJO前端可直接序列化为{ total: 12, list: [...], page: 1, size: 10 }无需额外 DTO 转换。3. 配置与启动application.properties 解析、数据库建表脚本与 Tomcat 部署要点3.1 application.properties 全参数说明从 JDBC 到 JWT 密钥的 12 项必填项项目根目录下src/main/resources/application.properties是启动成败关键。以下是必须修改的 12 项其余可保持默认配置项示例值说明是否必改jdbc.urljdbc:mysql://localhost:3306/food_db?serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrueMySQL 连接地址必须加时区与 SSL 参数✅jdbc.usernameroot数据库用户名✅jdbc.password123456数据库密码✅redis.hostlocalhostRedis 地址✅redis.port6379Redis 端口✅redis.password空若 Redis 有密码此处填写⚠️jwt.secretFoodSystem2024SecretKey!#JWT 签名密钥长度建议 ≥32 字符✅jwt.expiration1800000Token 过期毫秒数30 分钟✅jwt.refresh-expiration604800000Refresh Token 过期毫秒数7 天✅qrcode.width300生成二维码宽度像素✅影响前端展示qrcode.height300生成二维码高度像素✅upload.path/opt/uploads/文件上传保存路径需 Linux 下提前mkdir -p /opt/uploads chmod 755 /opt/uploads✅否则头像上传失败注意jwt.secret是对称密钥绝不能明文提交到 Git。生产环境应通过 JVM 参数-Djwt.secretxxx注入或使用外部配置中心。本源码中为演示方便写在 properties你拿到后第一件事就是改掉它。3.2 MySQL8 建库建表脚本含外键、索引与注释的 5 张核心表项目未提供.sql文件但src/main/resources/db-schema.sql需手动创建内容如下已验证在 MySQL8.0.33 下执行成功-- 创建数据库字符集必须为 utf8mb4 CREATE DATABASE IF NOT EXISTS food_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE food_db; -- 用户表 CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT BCrypt 加密密码, phone varchar(20) NOT NULL COMMENT 手机号, avatar_url varchar(255) DEFAULT NULL COMMENT 头像URL, role enum(ADMIN,USER,MERCHANT) NOT NULL DEFAULT USER COMMENT 角色, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; -- 商户表餐厅 CREATE TABLE merchant ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL, address varchar(255) NOT NULL, contact_phone varchar(20) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 菜品表 CREATE TABLE dish ( id bigint NOT NULL AUTO_INCREMENT, merchant_id bigint NOT NULL, name varchar(100) NOT NULL, price decimal(10,2) NOT NULL, image_url varchar(255) DEFAULT NULL, PRIMARY KEY (id), KEY idx_merchant_id (merchant_id), CONSTRAINT fk_dish_merchant FOREIGN KEY (merchant_id) REFERENCES merchant (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单主表 CREATE TABLE order ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(64) NOT NULL COMMENT 订单号全局唯一, user_id bigint NOT NULL, merchant_id bigint NOT NULL, status enum(PENDING,PAID,DELIVERED,COMPLETED,CANCELLED) NOT NULL DEFAULT PENDING, total_amount decimal(10,2) NOT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id), KEY idx_merchant_id (merchant_id), CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE RESTRICT, CONSTRAINT fk_order_merchant FOREIGN KEY (merchant_id) REFERENCES merchant (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 订单明细表 CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL, dish_id bigint NOT NULL, quantity int NOT NULL DEFAULT 1, price decimal(10,2) NOT NULL COMMENT 下单时价格, PRIMARY KEY (id), KEY idx_order_id (order_id), KEY idx_dish_id (dish_id), CONSTRAINT fk_item_order FOREIGN KEY (order_id) REFERENCES order (id) ON DELETE CASCADE, CONSTRAINT fk_item_dish FOREIGN KEY (dish_id) REFERENCES dish (id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示执行前确认 MySQL 已启用innodb_file_per_tableON默认开启否则外键约束可能失效。建表后务必执行INSERT INTO user (username, password, phone, role) VALUES (admin, $2a$10$ZVfYvXQxKjRkLwWzFgHtOeUyIbNcDmEoFpGqHrIsJtKuLvMwNxOy, 13800138000, ADMIN);插入初始管理员密码为123456BCrypt 加密。3.3 Tomcat9 部署三步法war 包构建、context.xml 配置与日志定位部署不是mvn clean package后扔进webapps就完事。必须走以下三步第一步构建可部署 war# 在项目根目录执行 mvn clean package -Dmaven.test.skiptrue # 输出 target/cuisine-web-1.0.war注意pom.xml中packagingwar/packaging已设置且maven-war-plugin版本为3.3.2兼容 Tomcat9。第二步配置 context.xml 防止中文乱码在conf/context.xml的Context标签内添加Parameter nameuseBodyEncodingForURI valuetrue overridefalse/ Valve classNameorg.apache.catalina.valves.RemoteIpValve remoteIpHeaderx-forwarded-for protocolHeaderx-forwarded-proto/否则ServletUtils.readJsonBody()读取中文字段如菜品名宫保鸡丁会变成???。第三步日志定位技巧启动后若报ClassNotFoundException: com.mysql.cj.jdbc.Driver不是驱动没放而是 Tomcat9 默认不加载WEB-INF/lib下的 JDBC 驱动出于安全考虑。解决方案将mysql-connector-java-8.0.33.jar复制到$CATALINA_HOME/lib/目录下重启 Tomcat。日志查看路径logs/catalina.out实时输出 logs/localhost.date.logServlet 初始化日志。若看到INFO [main] org.apache.catalina.startup.HostConfig.deployWAR Deployment of web application archive [...] has finished in [1234] ms即部署成功。4. 避坑指南5 个血泪经验总结的高频翻车点与修复方案4.1 现象登录成功返回 Token但后续/api/order/list接口始终返回 401 Unauthorized原因AuthFilter.java中verifyToken()方法调用redis.get(auth:token: jwtId)返回null。排查路径检查AuthService.issueToken()是否真的执行了redis.setex(auth:token: jwtId, 1800, tokenPayloadJson)登录后立即用redis-cli连接 Redis执行KEYS auth:token:*确认 key 存在根本原因application.properties中redis.password为空但你的 Redis 实际设置了密码导致 Jedis 连接认证失败所有setex/get操作静默失败。解决在redis.password配置项填入真实密码或停用 Redis 密码开发环境可行。4.2 现象OrderService.createOrder()报org.mybatis.flex.exception.FlexException: Cannot find method setOrderId in class Order原因Order实体类中orderId字段名为orderNo数据库列order_no但 MyBatis-Flex 的TableInfo自动映射要求 Java 字段名与数据库列名严格一致或通过Column注解指定而Order类中未加Column(value order_no)。解决在Order.java的orderNo字段上方添加Column(value order_no) private String orderNo;血泪经验MyBatis-Flex 不像 MyBatis-Plus 默认开启mapUnderscoreToCamelCase它要求显式声明。所有带下划线的数据库字段如create_time,total_amount都必须加Column。4.3 现象PayService.generateQrCodeForWechatPay()生成的二维码扫描后提示“该链接无法访问”原因ZXing 生成的二维码内容是weixin://wxpay/bizpayurl?prxxx但微信要求此链接必须是已备案的 HTTPS 域名下的合法路径本地http://localhost:8080无法被微信识别。解决开发阶段用ngrok http 8080生成临时公网 HTTPS 地址如https://abc123.ngrok.io修改PayService.java中二维码内容为https://abc123.ngrok.io/pay/callback?prepay_idxxx后端提供/pay/callback接口接收微信支付结果通知当前源码未实现需自行补全。玄学提示微信沙箱环境对域名白名单极其敏感哪怕少一个/都会失败。4.4 现象CartService.getCartItems()返回空列表但 Redis 中cart:{userId}Hash 确实有数据原因CartService.java中redis.hvals(cart: userId)返回的是ListString而代码试图直接 cast 为ListCartItemJVM 抛ClassCastException异常被try-catch吞掉返回空集合。定位在CartService.getCartItems()方法开头加日志log.info(Redis cart keys: {}, redis.hkeys(cart: userId));确认 key 存在再看 catch 块是否打印了异常。解决将hvals改为hgetAll遍历 Map用JSON.parseObject(val, CartItem.class)逐个反序列化MapString, String cartMap redis.hgetAll(cart: userId); ListCartItem items new ArrayList(); for (Map.EntryString, String entry : cartMap.entrySet()) { CartItem item JSON.parseObject(entry.getValue(), CartItem.class); items.add(item); }4.5 现象BillService_bak.java与BillService.java冲突Maven 编译报Duplicate class BillService原因BillService_bak.java是备份文件但 Maven 的compiler-plugin默认编译src/main/java/**/*.java包括_bak文件。解决在pom.xml的build节点下添加资源过滤plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration excludes exclude**/*_bak.java/exclude exclude**/*_old.java/exclude /excludes /configuration /plugin后悔药所有备份文件*_bak.java,*_old.java,*.txt.bak必须从 Git 提交记录中彻底删除否则 CI/CD 流水线必然失败。5. 前端对接与接口验证用 Postman 跑通 4 个核心 API 链路5.1 Postman 集合结构按业务域组织 12 个请求不要零散发请求。我为你整理了最小可用 Postman 集合可导出为 JSON 导入文件夹接口名MethodURL说明AuthLoginPOST/api/auth/loginBody raw JSON:{phone:13800138000,password:123456}AuthGet User InfoGET/api/user/infoHeader:Authorization: Bearer {token}CartAdd to CartPOST/api/cart/addBody:{dishId:1,quantity:2}CartGet CartGET/api/cart/listHeader:Authorization: Bearer {token}OrderCreate OrderPOST/api/order/createBody:{}空 JSON从购物车生成PayGenerate QRGET/api/pay/qrcode/{billId}Path Param:billId1提示Postman 中设置Environment定义变量baseUrlhttp://localhost:8080、token登录后自动提取并设置所有 URL 写成{{baseUrl}}/api/...避免硬编码。5.2 四大核心链路验证步骤从登录到二维码生成的完整断点链路一登录 → 获取 Token → 调用受保护接口发送Login请求响应体含token: eyJhbGciOi...在 Postman 的Tests标签页粘贴const jsonData pm.response.json(); pm.environment.set(token, jsonData.token);发送Get User InfoHeader 自动注入Authorization: Bearer {{token}}响应应为200 OK 用户信息。链路二购物车增删改查Add to CartBody 中dishId必须是数据库dish表中存在的 ID先查/api/dish/listGet Cart响应中items数组应包含刚添加的商品且quantity正确关键验证点用redis-cli执行HGETALL cart:1001确认 Hash 中存储的是 JSON 字符串而非原始对象MyBatis-Flex 不序列化对象需手动JSON.toJSONString()。链路三下单 → 生成账单 → 获取账单 IDCreate Order成功后响应体含orderId: 123立即查数据库SELECT * FROM bill WHERE order_id 123确认账单已生成status PENDING此bill.id即为下一步二维码的{billId}。链路四生成支付二维码 → 本地预览发送Generate QRURL 为/api/pay/qrcode/1假设账单 ID 为 1响应类型为image/pngPostman 右上角点击Send and Download保存为qrcode.png用手机微信扫描应跳转至weixin://...链接开发阶段会提示“无法打开”证明二维码内容正确终极验证用在线二维码解析工具如 https://cli.im/deqr上传qrcode.png确认解析出的文本是weixin://wxpay/bizpayurl?prxxx。5.3 接口文档自动生成用 Swagger2 快速补全缺失的 OpenAPI 规范当前源码无 Swagger但cuisine-api.http文件是手工维护的 HTTP 请求示例可一键转为 Swagger。推荐做法在pom.xml添加 Swagger2 依赖dependency groupIdio.springfox/groupId artifactIdspringfox-swagger2/artifactId version2.9.2/version /dependency dependency groupIdio.springfox/groupId artifactIdspringfox-swagger-ui/artifactId version2.9.2/version /dependency新建SwaggerConfig.javaConfiguration EnableSwagger2 public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage(com.cuisine.controller)) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title(Cuisine API) .description(JavaWeb 点餐系统后端接口文档) .version(1.0) .build(); } }启动后访问http://localhost:8080/swagger-ui.html所有RestController接口自动归档支持在线调试。从那以后我每次接手新 JavaWeb 项目第一件事就是加 Swagger2 —— 它比写cuisine-api.http效率高 10 倍且保证文档与代码同步。cuisine-api.http是过渡产物Swagger 是交付标配。希望帮到你。本文还有配套的精品资源点击获取