ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot + Vue前后端分离:彩票销售管理系统毕业设计全解析

Spring Boot + Vue前后端分离:彩票销售管理系统毕业设计全解析 很多 Java 方向的毕业生在准备毕业设计时都会遇到同一个尴尬局面框架基础学了大半年Spring Boot 的 HelloWorld 能跑起来MyBatis 的增删改查也写过但真要独立做一个“前后端分离 管理系统 权限控制”的完整项目却不知道从哪下手。下载了别人的源码打开 IDEA 一看几十个类文件互相调用启动一遍报一堆错最后连项目结构都没看明白就放弃了。彩票销售管理系统是近年 Java 方向毕业设计里出现频率很高的一类选题。它的业务边界清楚、角色分明、数据流转直观而且天然包含“用户端销售操作”和“管理端后台维护”两条主线非常适合用来完整展示 Spring Boot Vue 前后端分离项目的工程能力。很多同学选这个题是觉得“彩票系统”听起来有业务亮点但真正开始做之后才发现难点根本不在彩票业务本身而在整个系统的工程整合JWT 登录认证怎么做、数据库表怎么设计、前后端跨域怎么处理、项目怎么打包部署。这些问题没有现成答案只能靠一个个踩坑去试。这篇文章不打算只贴一堆源码文件而是从“一个完整的前后端分离毕设项目应该怎么落地”这个角度把彩票销售管理系统拆开讲清楚。你会看到它的功能模块划分、表结构设计、Spring Boot 后端核心代码、Vue 前端联调思路、以及最常见的报错排查方法。读完后你不仅能理解这个项目是怎么运行的还能把自己的毕设项目按同样的工程标准做出来。需要提前说明的是本文讨论的是用于课程设计、毕业设计等教学场景的演示系统。我国彩票由国家特许发行任何个人或组织未经许可不得从事彩票销售业务。本文所有代码和设计仅用于技术学习与教学演示请勿用于真实业务场景。1. 这篇文章真正要解决的问题先说结论彩票销售管理系统作为毕业设计真正的技术价值不在于“彩票”这个业务而在于它覆盖了 Java 后端开发最常用的一整套工程链路。如果用一句话概括这个项目的核心难点那就是如何用 Spring Boot 构建一套带身份认证的 RESTful API并同时提供一个可运行的 Vue 管理前端让用户在浏览器里完成从登录到下单的全流程操作。很多同学做这个题目时会遇到三种典型困境。第一种困境是“只做后端不会前端”。后端接口写好了用 Postman 测试能通但一问前端页面怎么对接就卡住了。毕业设计答辩时老师要求现场演示系统运行效果结果打开浏览器一片空白。第二种困境是“代码能跑但讲不清设计”。项目是从网上找的源码能启动但问到自己负责哪个模块、表结构为什么这样设计、JWT Token 过期了怎么办完全答不上来。这类项目在答辩时往往会被追问到无法自圆其说。第三种困境是“前后端割裂不知道怎么联调”。后端接口返回的数据结构不统一前端拿不到想要的数据格式跨域配置没做Ajax 请求直接被浏览器拦截。这类问题不解决项目功能再完整也演示不了。这篇文章要做的就是帮你绕过这些坑。我从头梳理这个项目的功能边界、技术选型、数据库设计、核心代码逻辑以及最常见的启动和联调错误。读完以后你应该能独立完成这套系统的搭建也能在答辩时把自己的设计思路讲清楚。如果用一个标准来衡量你是否学会了这个项目那就是能从零开始建一个 Spring Boot 项目和一个 Vue 项目把数据库初始化好让前端页面通过 API 完成用户登录、彩种列表展示、创建订单和管理员审核这一整条流程。2. 彩票销售管理系统的功能模块与技术方向彩票销售管理系统的业务模型并不复杂但它的模块划分很有代表性。几乎所有“XX管理系统”类毕业设计都可以套用类似的结构。2.1 系统角色与功能边界从用户角色来看系统通常分为两类角色角色核心权限典型操作普通用户浏览彩种、创建订单、查看个人订单注册登录、选号下单、查看开奖信息系统管理员后台管理所有数据管理彩种、审核订单、发布公告、管理用户如果项目定位是前后端分离管理系统那么在功能设计上还要考虑用户端和管理端是否分开展示。常见的做法是普通用户端使用独立的页面风格管理员通过后台管理界面进行数据维护。两个端共用同一套后端接口只是权限不同。2.2 功能模块拆解一个功能完整但不过度复杂的管理系统通常包含以下模块用户认证模块用户注册、登录、退出基于 JWT 的身份校验密码加密存储。彩种管理模块管理员维护彩种信息。彩种名称、玩法说明、单注金额、开奖规则、状态启用/停用。订单管理模块用户选择彩种并填写号码生成投注订单管理员在后台查看订单列表可根据订单号或用户查询详情。开奖信息模块管理员录入每期开奖号码用户可以查看历史开奖记录。公告管理模块管理员发布系统公告用户端展示最新公告。用户管理模块管理员查看注册用户列表可以启用或禁用账号。这六个模块基本覆盖了“销售类管理系统”的核心流程用户从看到商品彩种到购买下订单到消费后查看结果开奖信息再到管理员对整个流程进行数据维护。2.3 适合什么技术路线的项目选题方向的判断也很重要。同样是彩票销售系统你可以做成纯后端项目、混合式单体项目或者前后端分离项目。从技术发展趋势和面试/答辩的加分角度来看前后端分离方案在当前环境下的收益最高前端使用 Vue后端使用 Spring Boot通过 RESTful API 通信。这个方案的好处是职责划分清晰——前端专注于页面交互和数据展示后端专注于业务逻辑和数据处理。对毕业设计来说它还可以展示你在“接口设计”和“前端联调”上的工程能力这是很多纯后端项目做不到的。如果只使用 JSP 做服务端渲染开发周期会短一些但项目的技术层次感会弱很多也缺少 Vue 生命周期、Axios 请求封装、跨域处理这些前端技术要点。3. Spring Boot Vue 技术栈里的关键概念在进入代码之前有几个核心概念需要先理清楚。很多同学做项目中途卡住不是不会写代码而是对这些概念的边界理解得太模糊。3.1 Spring Boot 到底解决了什么问题Spring Boot 的核心价值是“简化 Spring 应用的搭建和开发”。它通过自动配置把以前需要大量 XML 配置的 Spring 项目变成了约定大于配置的工程。你不需要自己决定 Bean 怎么装配、数据源怎么配置、消息转换器怎么注册Spring Boot 会根据依赖和应用场景自动完成大部分工作。在彩票销售管理系统里你需要在 pom.xml 中引入的依赖包括 Spring Web、MyBatis 或 Spring Data JPA、MySQL 驱动、JWT 相关工具库等。Spring Boot 的自动配置会帮你完成内嵌 Tomcat 的启动、数据源的初始化、JSON 消息转换器的注册等底层工作。3.2 什么是 RESTful API前后端分离的核心是接口通信。后端提供 URL 地址前端通过 HTTP 请求访问这些地址获得 JSON 数据。RESTful 是一种接口设计风格它把业务操作映射到 HTTP 方法上HTTP 方法典型含义系统中的应用场景GET查询资源获取彩种列表、获取订单详情POST新增资源用户注册、创建订单PUT修改资源更新彩种信息、修改公告DELETE删除资源删除用户、删除公告这种设计方式的好处是接口语义清晰一个 URL 配合不同的请求方法就能完成不同的操作。3.3 JWT 身份认证的基本流程用户登录后后端生成一个加密的 Token 字符串返回给前端。前端在后续请求的请求头中携带这个 Token后端通过拦截器解析 Token判断用户是谁、是否登录、有没有权限。JWT 的三段式结构Header.Payload.Signature本身并不难理解。真正容易出错的地方在于Token 过期时间怎么设置、拦截器要放行哪些接口、登录接口和受保护接口怎么区分、前端拿到 401 状态码后如何处理。3.4 Vue 前端在系统中的职责Vue 在前后端分离项目中扮演的是“视图层 交互层”的角色。它负责渲染页面、接收用户的点击和输入、通过 Axios 调用后端 API、把返回的 JSON 数据展示在界面上。Vue 不直接操作数据库也不处理业务规则它只做两件事情展示数据和提交操作请求。对这个项目而言Vue 端的典型工作包括登录/注册页面的表单校验、彩种列表的渲染、下单页面的号码选择交互、个人中心订单状态的展示。这些功能用 Vue Router 做页面跳转、用 Vuex/Pinia 管理登录状态、用 Axios 统一发起 HTTP 请求。4. 环境准备与前置条件很多项目跑不起来不是代码问题而是环境不一致导致的。做这个项目之前建议先确认你的开发环境满足下面这些基础条件。类别推荐配置说明JDKJDK 8 或 JDK 11Spring Boot 2.x 项目通常使用 JDK 8/11版本以你的项目实际配置为准构建工具Maven 3.6依赖管理和项目打包数据库MySQL 5.7 或 8.0存储用户、订单、彩种等业务数据Node.jsNode 14运行 Vue 项目npm 管理前端依赖IDEIDEA 或 Eclipse后端开发推荐 IDEA前端开发VS Code配合 Volar 插件开发 Vue这里要特别提一个容易踩坑的地方Spring Boot 版本和 JDK 版本必须匹配。Spring Boot 2.7 的默认构建和部署方式适合 JDK 8/11Spring Boot 3.x 要求 JDK 17 及以上。如果你下载的项目是基于 Spring Boot 2.x 写的却用 JDK 17 去运行很可能出现一些奇怪的兼容性问题。如果遇到“Unsupported class file major version”之类的报错就要优先检查 JDK 版本和 pom.xml 中的 Java 版本配置是否一致。如果你打算自行新建工程数据库操作使用 MyBatis-Plus 会省掉大量重复的 SQL 编写工作。但如果你准备在此基础上学习和理解原生 SQL使用 MyBatis 也是可以的。两者在接口风格上有差异不影响整体项目结构。5. 数据库设计与初始化数据库设计是毕业设计答辩时老师非常关注的部分也是项目能够顺利运行的前提。设计原则很简单表结构能清楚地表达业务关系不冗余、不混乱。5.1 核心数据表一个彩票销售管理系统至少需要以下核心表。表名用途核心字段sys_user用户表id、username、password、real_name、phone、status、create_timelottery_type彩种表id、type_name、price、rule_desc、status、create_timelottery_order订单表id、order_no、user_id、lottery_id、bet_numbers、amount、status、create_timelottery_open开奖表id、lottery_id、open_issue、open_numbers、open_timesys_notice公告表id、title、content、status、create_time从角色与表的关系来看一个用户可以下多个订单一个彩种可以对应多个订单一个彩种可以有多期开奖记录。这些是一对多的关系。用户表和订单表通过 user_id 关联订单表和彩种表通过 lottery_id 关联。这样设计的好处是数据查询路径清晰例如“查询某个用户最近 10 笔订单”“查询某个彩种的所有投注订单”都能通过外键字段快速完成。5.2 订单状态设计订单状态是这个系统的核心业务字段建议使用整数表示状态值含义说明0待支付用户已创建订单但未完成支付1已支付订单支付成功等待开奖2已开奖对应期次已开奖结果已公布3已撤销订单被用户取消或管理员撤销状态字段用数字存储的好处是扩展方便前端通过映射表把数字翻译成对应的中文状态图标。答辩时可以说明这个设计简化了数据库字段也便于后续扩展更多状态。5.3 初始化 SQL 示例下面给出一份精简版的建表 SQL你可以在此基础上按自己的业务字段扩展。-- 用户表 CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录用户名, password varchar(100) NOT NULL COMMENT 密码BCrypt加密存储, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, status tinyint(4) DEFAULT 1 COMMENT 状态0禁用1启用, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; -- 彩种表 CREATE TABLE lottery_type ( id bigint(20) NOT NULL AUTO_INCREMENT, type_name varchar(50) NOT NULL COMMENT 彩种名称, price decimal(10,2) NOT NULL COMMENT 单注金额, rule_desc varchar(255) DEFAULT NULL COMMENT 玩法说明, status tinyint(4) DEFAULT 1 COMMENT 状态0停用1启用, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT彩种表; -- 订单表 CREATE TABLE lottery_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单编号唯一, user_id bigint(20) NOT NULL COMMENT 下单用户ID, lottery_id bigint(20) NOT NULL COMMENT 购买的彩种ID, bet_numbers varchar(200) NOT NULL COMMENT 投注号码, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint(4) DEFAULT 0 COMMENT 订单状态0待支付1已支付2已开奖3已撤销, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT彩票订单表; -- 开奖信息表 CREATE TABLE lottery_open ( id bigint(20) NOT NULL AUTO_INCREMENT, lottery_id bigint(20) NOT NULL COMMENT 彩种ID, open_issue varchar(30) NOT NULL COMMENT 期号, open_numbers varchar(100) NOT NULL COMMENT 开奖号码, open_time datetime DEFAULT NULL COMMENT 开奖时间, PRIMARY KEY (id), KEY idx_lottery_id (lottery_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT开奖信息表;在设计这张表时有两点值得注意。第一密码字段长度设置为 100因为 BCrypt 加密后的字符串比较长。如果字段长度不够注册时就会报数据库异常。第二订单表冗余了 lottery_name 之类的字段也可以但为了保持表结构精简推荐通过关联查询获取彩种信息。这样做的好处是彩种名称修改后不需要更新历史订单。6. 后端工程搭建与核心代码实现后端工程是整个系统的逻辑核心。我们先从工程结构和配置入手然后再写具体的代码。6.1 后端项目结构使用 Maven 构建的 Spring Boot 项目核心结构如下src/main/java/com/example/lottery/ │ ├── LotteryApplication.java // 启动类 ├── config/ │ ├── CorsConfig.java // 跨域配置 │ └── WebMvcConfig.java // 拦截器注册 ├── controller/ │ ├── AuthController.java // 登录、注册 │ ├── LotteryTypeController.java // 彩种管理 │ ├── OrderController.java // 订单操作 │ └── AdminController.java // 管理员管理 ├── service/ │ ├── UserService.java │ ├── LotteryTypeService.java │ ├── OrderService.java │ └── NoticeService.java ├── mapper/ │ ├── UserMapper.java │ ├── LotteryTypeMapper.java │ └── OrderMapper.java ├── entity/ │ ├── SysUser.java │ ├── LotteryType.java │ └── LotteryOrder.java ├── dto/ │ ├── LoginRequest.java │ ├── LoginResponse.java │ └── OrderCreateRequest.java ├── common/ │ ├── Result.java // 统一返回结果 │ └── ResultCode.java // 状态码定义 ├── exception/ │ └── BusinessException.java // 自定义业务异常 ├── utils/ │ ├── JwtUtils.java // JWT 生成与解析 │ └── OrderNoUtils.java // 订单号生成 └── interceptor/ └── JwtInterceptor.java // JWT 拦截器这个结构是典型的 Spring Boot 分层结构Controller 负责接收请求Service 负责业务逻辑Mapper 负责数据库操作entity 对应数据库表dto 用于前后端数据传输common 存放通用返回类型。分层清楚的好处是出现问题时能快速定位到对应层。6.2 核心 Maven 依赖在 pom.xml 中核心依赖如下dependencies !-- Spring Boot Web 依赖 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 依赖 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- JWT 工具库 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- BCrypt 密码加密 -- dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency !-- Lombok 简化实体类 -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies如果你不需要 MyBatis-Plus 的代码生成能力只想用原生 MyBatis 也完全可行。MyBatis-Plus 在单表操作上的优势非常明显比如用户插入、订单分页查询这种常见操作它内置的 BaseMapper 方法可以直接复用减少大量重复代码。对于毕业设计项目MyBatis-Plus 是性价比很高的选择。6.3 配置文件 application.yml后端配置文件是项目能正常连接数据库的前提核心内容和解释如下server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/lottery_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 jwt: secret: your-secret-key-please-change-in-production expire: 86400000这里有几个容易出错的地方需要单独说明。第一数据库连接字符串中的 serverTimezoneAsia/Shanghai 不能省略。MySQL 8.x 版本如果不指定时区会在启动时报告时间相关的异常。第二map-underscore-to-camel-case 配置表示数据库字段 user_id 自动映射为 Java 属性 userId。如果关闭这个配置MyBatis-Plus 的自动映射可能失效。第三jwt.secret 在真实项目中应当使用足够长的随机字符串。如果你用源码演示时用的是默认 secret记得在部署前修改否则存在安全风险。6.4 统一返回结果封装前后端分离项目一定要有统一的响应结构。这样前端只需要处理一种固定格式的数据而不是不同接口返回不同结构。// 文件路径src/main/java/com/example/lottery/common/Result.java public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } // getter/setter 略 }前端后续可以约定当 code 为 200 时表示请求成功code 为 401 时表示未登录或 Token 失效code 为 500 时表示服务器内部错误。这样 Axios 的响应拦截器就能根据 code 做统一处理而不是对每个接口单独判断。6.5 JWT 工具类与拦截器用户登录成功后需要生成 Token后续请求需要验证 Token这部分的实现是整个系统安全的基石。// 文件路径src/main/java/com/example/lottery/utils/JwtUtils.java public class JwtUtils { private static final String SECRET your-secret-key-please-change-in-production; private static final long EXPIRE 86400000L; public static String generateToken(Long userId, String username) { Date now new Date(); Date expireDate new Date(now.getTime() EXPIRE); return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }拦截器负责拦截需要认证的请求接口解析请求头中的 Token// 文件路径src/main/java/com/example/lottery/interceptor/JwtInterceptor.java public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (handler instanceof HandlerMethod) { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { token token.substring(7); } if (token ! null token.length() 0) { try { JwtUtils.parseToken(token); return true; } catch (Exception e) { // Token 无效或过期 } } response.setStatus(401); response.setContentType(application/json;charsetUTF-8); response.getWriter().write({\code\:401,\message\:\未登录或Token已过期\}); return false; } return true; } }然后在 WebMvcConfig 中注册拦截器并配置放行规则// 文件路径src/main/java/com/example/lottery/config/WebMvcConfig.java Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new JwtInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login, /api/auth/register); } Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }跨域配置是前后端分离项目里最容易踩坑的地方之一。前端项目运行在 8081 端口后端运行在 8080 端口两者端口不同浏览器的同源策略就会拦截请求报出 “Access-Control-Allow-Origin” 相关错误。上面的 addCorsMappings 配置就是为了解决这个问题。在实际开发中也可以在前端 Vue 项目根目录配置代理解决联调跨域问题但有两种方式建议至少掌握一种。更推荐在后端配置 CORS因为这样不依赖前端开发服务器的转发放行后前后端可独立部署。6.6 用户登录与注册登录接口的逻辑是接收用户名和密码从数据库查询用户用 BCrypt 校验密码密码正确则生成 Token 返回。// 文件路径src/main/java/com/example/lottery/controller/AuthController.java RestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; PostMapping(/login) public ResultLoginResponse login(RequestBody LoginRequest request) { LoginResponse response userService.login(request.getUsername(), request.getPassword()); return Result.success(response); } PostMapping(/register) public ResultVoid register(RequestBody RegisterRequest request) { userService.register(request); return Result.success(null); } }UserService 中对应的实现逻辑重点是密码校验和加密存储// 文件路径src/main/java/com/example/lottery/service/impl/UserServiceImpl.java Service public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override public LoginResponse login(String username, String password) { SysUser user userMapper.selectByUsername(username); if (user null) { throw new BusinessException(用户名或密码错误); } if (user.getStatus() ! null user.getStatus() 0) { throw new BusinessException(账号已被禁用); } BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); if (!encoder.matches(password, user.getPassword())) { throw new BusinessException(用户名或密码错误); } String token JwtUtils.generateToken(user.getId(), user.getUsername()); LoginResponse response new LoginResponse(); response.setToken(token); response.setUsername(user.getUsername()); response.setRealName(user.getRealName()); return response; } Override public void register(RegisterRequest request) { SysUser user new SysUser(); BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); user.setUsername(request.getUsername()); user.setPassword(encoder.encode(request.getPassword())); user.setRealName(request.getRealName()); user.setPhone(request.getPhone()); user.setStatus(1); userMapper.insert(user); } }6.7 用户下订单业务用户下单是整个系统最重要的业务接口。它的核心逻辑包括生成唯一订单号、查询彩种信息、计算金额、保存订单。// 文件路径src/main/java/com/example/lottery/controller/OrderController.java RestController RequestMapping(/api/order) public class OrderController { Autowired private OrderService orderService; PostMapping(/create) public ResultLotteryOrder createOrder(RequestBody OrderCreateRequest request, HttpServletRequest httpRequest) { String token httpRequest.getHeader(Authorization).substring(7); Claims claims JwtUtils.parseToken(token); Long userId claims.get(userId, Long.class); LotteryOrder order orderService.createOrder(userId, request.getLotteryId(), request.getBetNumbers()); return Result.success(order); } GetMapping(/my) public ResultListLotteryOrder myOrders(HttpServletRequest httpRequest) { String token httpRequest.getHeader(Authorization).substring(7); Claims claims JwtUtils.parseToken(token); Long userId claims.get(userId, Long.class); ListLotteryOrder orders orderService.getOrdersByUserId(userId); return Result.success(orders); } }创建订单时需要注意金额计算的准确性。不要在 SQL 里拼金额而是在 Java 层根据彩种单价和注数相乘得到。用户名从 Token 中获取不能由前端传入否则用户可以伪造别人的订单。7. 前端 Vue 项目与接口联调后端接口完成后接下来就是前端页面的搭建和接口联调。这部分是很多同学觉得“心里没底”的环节。其实前端的开发路径也很固定创建项目、配置代理、封装 Axios、写页面组件、调用接口。7.1 前端项目结构使用 Vue CLI 或 Vite 创建的项目核心结构如下src/ │ ├── api/ │ ├── auth.js // 登录、注册接口 │ ├── lottery.js // 彩种接口 │ └── order.js // 订单接口 ├── router/ │ └── index.js // 路由配置 ├── store/ │ └── user.js // 用户状态管理 ├── views/ │ ├── Login.vue // 登录页 │ ├── Register.vue // 注册页 │ ├── LotteryList.vue // 彩种列表页 │ ├── OrderCreate.vue // 下单页 │ └── MyOrder.vue // 我的订单页 ├── utils/ │ └── request.js // Axios 请求封装 └── App.vue7.2 Axios 请求封装前后端分离项目一定要统一封装请求工具不要在每个页面直接调用 axios.get。封装的好处是可以在一个地方统一处理 Token 注入、401 跳转和错误提示。// 文件路径src/utils/request.js import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器注入 Token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) // 响应拦截器统一处理业务错误 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res }, error { if (error.response error.response.status 401) { ElMessage.error(登录状态已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { ElMessage.error(网络异常请稍后重试) } return Promise.reject(error) } ) export default request7.3 登录接口调用示例前端登录页面调用接口的逻辑非常简单用户输入用户名和密码点击登录按钮把数据提交给后端拿到 Token 后存到 localStorage然后跳转到首页。// 文件路径src/api/auth.js import request from ../utils/request export function login(data) { return request({ url: /auth/login, method: post, data }) } export function register(data) { return request({ url: /auth/register, method: post, data }) }!-- 文件路径src/views/Login.vue -- template div classlogin-container el-card classlogin-card h2彩票销售管理系统/h2 el-form refloginRef :modelloginForm :rulesloginRules el-form-item propusername el-input v-modelloginForm.username placeholder用户名 / /el-form-item el-form-item proppassword el-input v-modelloginForm.password typepassword placeholder密码 show-password / /el-form-item el-button typeprimary stylewidth: 100% clickhandleLogin登录/el-button div stylemargin-top: 12px; text-align: center router-link to/register没有账号去注册/router-link /div /el-form /el-card /div /template script setup import { ref, reactive } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import { login } from ../api/auth const router useRouter() const loginForm reactive({ username: , password: }) const handleLogin async () { if (!loginForm.username || !loginForm.password) { ElMessage.warning(请输入用户名和密码) return } try { const res await login(loginForm) localStorage.setItem(token, res.data.token) localStorage.setItem(username, res.data.username) ElMessage.success(登录成功) router.push(/) } catch (error) { // 错误提示已在拦截器中统一处理 } } /script前端登录接口联调成功后整个项目的核心链路就已经打通了。剩下的彩种列表、下单页面、订单查询页面整体思路都是一样的写一个 API 封装函数在 Vue 组件中调用把返回的数据渲染到页面。8. 系统运行与效果验证代码写完之后最关键的环节就是让项目跑起来。后端和前端需要分别启动下面给出完整的运行步骤和验证方法。8.1 初始化数据库使用 Navicat 或命令行工具先创建数据库再执行建表 SQLmysql -u root -p在 MySQL 中执行CREATE DATABASE IF NOT EXISTS lottery_db DEFAULT CHARACTER SET utf8mb4; USE lottery_db; -- 执行建表 SQL 和测试数据 SQL建议插入一条管理员账号和几个测试彩种数据否则前端页面没有数据可展示。管理员密码同样使用 BCrypt 格式存储。8.2 启动后端服务在 IDEA 中打开后端项目等待 Maven 下载依赖完成后运行启动类。正常启动后控制台会输出 Spring Boot 的启动日志看到类似下面的内容说明后端已经启动成功Tomcat started on port(s): 8080 (http) with context path Started LotteryApplication in 3.2 seconds用浏览器访问http://localhost:8080/api/lottery/list如果返回 JSON 数据说明后端接口正常。8.3 启动前端项目进入前端项目目录在终端中执行npm install npm run serve启动成功后Vue CLI 会输出类似下面的访问地址App running at: - Local: http://localhost:8081/在浏览器打开http://localhost:8081如果能看到系统首页或登录页面说明前端启动成功。8.4 功能验证完整流程系统是否真正跑通不要只看“页面能打开”要按下面的核心业务链路走一遍注册一个新用户填写用户名、密码提交成功后提示注册成功。用刚注册的账号登录页面跳转到首页或彩种列表。选择一个彩种填写投注号码点击“立即投注”订单创建成功。进入“我的订单”能看到刚创建的订单状态为“待支付”。在管理员端登录能看到订单列表。如果系统实现了状态修改将订单状态改为“已支付”。打开浏览器开发者工具点击一个需要登录的接口查看请求头是否带有 Authorization 字段以此确认 JWT 是否正常注入。如果以上六步全部走通说明前端、后端、数据库、JWT 认证这条完整链路没有任何问题。9. 常见问题与排查方法项目开发过程中报错是常态。下面整理一份高频问题排查清单遇到问题时按表格中的顺序逐一排查。问题现象可能原因排查方式解决方案后端启动失败提示数据库连接错误MySQL 未启动、用户名密码错误、数据库名不对检查 application.yml 中的 url、username、password确认 MySQL 已启动数据库已创建账号密码正确前端请求后端接口时控制台报 CORS 错误后端没有正确配置跨域查看浏览器 Network 面板中的跨域错误信息在后端增加 CORS 配置或配置前端代理登录接口可以访问其他接口全部返回 401JWT 拦截器放行规则配置错误查看 WebMvcConfig 中的 excludePathPatterns确认登录/注册接口已放行其他 API 正常拦截登录成功但获取用户信息失败Token 解析逻辑有问题或 userId 类型转换错误在拦截器中打印解析出的 Claims 内容检查 generateToken 中 claim 的类型是 Long 还是 Integer前端 npm install 报错Node 版本过高或过低执行 node -v 查看版本切换 Node 版本到 14~18 之间或删除 node_modules 重新安装前端可以访问但列表页面空白接口返回的字段名和前端取的不一致在浏览器 Network 面板查看接口返回 JSON 结构核对后端实体字段与前端页面取值字段是否一致MyBatis-Plus 插入数据时主键冲突数据库表的主键没有设置为自增检查表的 AUTO_INCREMENT 设置调整建表 SQL 或修改实体类主键生成策略这里单独把“跨域问题”再强调一遍。前后端分离项目最常见的第一道坎就是 CORS。如果前端页面在 8081 端口后端在 8080 端口前端直接通过 axios 请求后端地址浏览器会拦截响应。解决思路只有两个方向后端开启 CORS 允许跨域或前端通过 devServer 的 proxy 把请求代理转发到后端。建议两种方式都掌握因为面试时很可能被问到“如果前后端部署在不同域名下如何解决跨域问题”。另一个常见的坑是 Lombok 依赖引起的编译失败。如果你在使用 IDEA 时发现实体类的 getter/setter 方法找不到先检查是否安装了 Lombok 插件同时确认 pom.xml 中 Lombok 依赖没有被标为 optional 后导致编译时不可用。数据库连接时区问题也是高频报错。如果启动时出现The server time zone value Öйú±ê׼ʱ¼ä is unrecognized之类的提示说明 MySQL 时区未设置。在连接字符串中加上serverTimezoneAsia/Shanghai即可解决。10. 毕业设计答辩与优化方向项目做完不是终点答辩才是成败的关键。很多同学项目做得不错但答辩时只会说“这个功能是怎么实现的”讲不出“为什么这么设计”。这里给你几个答辩时可以重点讲的观点。10.1 从项目中学到的工程经验租借管理系统也好彩票销售管理系统也好这类项目的核心价值在于从前到后完整走通了一遍软件工程流程需求分析、数据库设计、接口设计、前后端开发、联调测试。在答辩时你可以从下面几个角度举例说明数据库设计时考虑了唯一约束和索引比如订单号唯一索引、用户 ID 索引。JWT Token 认证解决了 HTTP 无状态下的会话管理问题登录状态不需要在服务端存储天然适合分布式部署。前后端分离让后端只关注数据接口前端只关注页面交互这个架构思路和真实企业开发是一致的。10.2 可以继续完善的优化方向如果时间允许以下方向可以作为项目的加分项优化方向技术方案增加验证码登录使用 Hutool 的验证码工具或集成 Google Kaptcha增加订单支付模拟在订单创建后模拟支付流程增加支付时间、支付流水号等字段使用 Redis 缓存彩种列表把热点数据放到 Redis减少数据库压力增加 Excel 导出功能使用 EasyExcel 导出订单列表和用户列表增加接口参数校验使用 Validation 注解对请求参数进行合法性校验增加管理员操作日志记录管理员增删改操作的日志便于追溯以上优化方向不是必须全部实现但要能讲清楚“如果继续开发你会怎么做”。这比单纯堆功能更能体现你的工程思维。11. 总结与学习建议彩票销售管理系统作为一个前后端分离的 Java 毕业设计项目覆盖了 Spring Boot、MyBatis-Plus、MySQL、JWT 认证、Vue 前端、RESTful API、跨域处理这些核心技术点。它不像高并发秒杀系统那样有复杂的架构设计也不像微服务项目那样需要容器编排但它的优势在于功能完整、链路清晰、技术栈通用适合用来检验你对 Java Web 开发全流程的掌握程度。如果你正在做这个题目我的建议是不要只满足于“把源码跑起来”而是用一个晚上把项目里每一步的逻辑理清楚。从数据库的表结构开始画一张数据流转图标注出用户从注册登录到下订单经过了哪些接口、哪些表、哪些状态变化。这个过程会帮你把零散的知识点串联成一个完整的体系。这套系统的核心代码并不复杂真正值钱的是你理解了每一行配置背后的原因、每一个接口设计的目的、每一个状态字段变化的时机。这些东西比源码本身更能代表你的真实水平。建议收藏这篇文章搭建项目时按照环境准备、数据库初始化、后端启动、前端启动、业务联调、问题排查的顺序一步步来。如果卡在某个环节重新阅读对应章节大部分问题都能找到答案。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进