ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

JDK17+Spring Boot3采购管理系统:从环境搭建到订单审核完整实战

JDK17+Spring Boot3采购管理系统:从环境搭建到订单审核完整实战 采购管理系统是 JavaWeb 方向最常被选为毕业设计和简历项目的题目之一。很多人拿到这类需求时第一反应是找一份现成源码但真正做起来才发现难点并不在源码多少而在于 JDK 17 和 Spring Boot 之间的版本匹配、数据库表如何设计、登录权限如何拦截、订单数据如何保证完整。这篇文章以一个典型的采购管理系统为主线从环境准备、数据库设计、接口实现到启动验证把整条开发链路完整过一遍。文章会先讲清楚这套系统包含哪些业务模块再解释为什么选择 Spring Boot 3 JDK 17然后进入建库建表、后端代码、接口测试和常见问题排查。文中的表结构、包名、端口和密码都是示例实际项目里需要结合自己的环境调整但整体思路可以直接复用。1. 采购管理系统到底要做什么技术栈为什么这样选1.1 系统功能边界要在一开始就定清楚采购管理系统在毕业设计里最常见的功能是供应商管理、采购订单创建、订单审核、采购入库。不同学校的题目可能还会要求加上库存查询、统计报表、部门审批、Excel 导入导出但核心业务基本是围绕“供应商”和“采购订单”展开的。以“供应商 采购订单”作为系统主线的理由是边界清晰。供应商是基础资料采购订单是业务单据订单状态变化就是系统核心流程。用户登录后进入管理界面维护供应商信息创建一份包含多条采购明细的订单提交后由有审核权限的用户处理审核通过后订单状态变为已通过后续可以继续扩展入库逻辑。从项目管理的角度看把需求收缩到这个范围适合做毕业设计表结构不超过 6 张后端接口不超过 20 个前端页面能完整展示增删改查和状态流转代码量控制在可解释的范围内。如果一开始就把预算、库存、财务、消息通知全都加进去系统会变得很复杂反而不利于把核心链路做扎实。1.2 为什么 JDK 17 和 Spring Boot 3 是当前更合理的选择很多旧教程还在使用 JDK 8 Spring Boot 2.x但新项目已经不适合继续沿用这套组合。JDK 17 是当前 Java 长期支持版本LTSSpring Framework 6 和 Spring Boot 3.x 也明确要求 JDK 17 作为最低版本。对于毕业设计和简历项目来说使用 JDK 17 Spring Boot 3.x 一方面能避免“技术栈太旧”的质疑另一方面在面试时可以解释清楚版本升级带来的变化。Spring Boot 3.x 相比 2.x 有一个非常明显的改动Java EE 包名从javax.*迁移到了jakarta.*。这意味着很多在网络上找到的 Spring Boot 2.x 代码不能直接复制到 3.x 项目里尤其是HttpServletRequest、HttpSession、Valid、NotNull这些类的 import 路径全部发生了变化。第一次接触 Spring Boot 3 的人很容易在这一步卡住。另外JDK 17 在安装后默认是长期支持版本Oracle 和 OpenJDK 社区都会持续提供更新。选择 JDK 17 不是追求最新而是选择一个稳定且生命周期更长的版本符合企业项目选型的常见做法。1.3 JDK 版本和框架版本之间的对应关系版本匹配是这类项目最容易踩坑的地方。JDK、Spring Boot、MyBatis-Plus、MySQL 驱动之间的版本如果不匹配经常会出现编译错误、启动失败或者运行时异常。以本文采用的组合为例组件推荐版本说明JDK17长期支持版本满足 Spring Boot 3.x 要求Spring Boot3.1.x需要 JDK 17使用 Jakarta EE 包名MyBatis-Plus3.5.4 及以上必须使用mybatis-plus-spring-boot3-starterMySQL8.0驱动类名是com.mysql.cj.jdbc.DriverIDEIntelliJ IDEA 2022对 JDK 17 和 Maven 支持较好需要特别注意的是MyBatis-Plus 老版本中的mybatis-plus-boot-starter是为 Spring Boot 2 设计的如果直接引入到 Spring Boot 3 项目里启动时会报ClassNotFoundException或其他依赖冲突。正确做法是使用mybatis-plus-spring-boot3-starter。注意在实际开发前先核对 JDK、Maven、Spring Boot、MyBatis-Plus 和 MySQL 驱动之间的版本关系比急着写代码更重要。版本不对后面每一步都可能被带偏。2. 环境准备先把 JDK17 装对项目骨架才稳2.1 JDK 17 安装与 JAVA_HOME 配置Windows 环境下下载 JDK 17 安装包后按提示安装即可。安装完成后需要配置JAVA_HOME环境变量并把%JAVA_HOME%\bin加入Path。配置完成后打开命令行窗口执行java -version javac -version正常情况下会看到openjdk version 17.0.x之类的输出。这里有一个容易忽略的坑如果电脑上同时安装了多个 JDK 版本java -version显示的版本可能不是刚安装的 JDK 17而是被其他软件改写了Path后的旧版本。检查时需要确认JAVA_HOME指向的是 JDK 17 的安装目录并且Path里%JAVA_HOME%\bin出现在其他 Java 路径之前。macOS 和 Linux 环境可以使用包管理器安装。macOS 上也可以使用jenv管理多个 JDK 版本但这里不展开核心目标是保证java -version和javac -version都能显示 JDK 17。2.2 IDE 与 Maven 参数对齐IntelliJ IDEA 中需要做三件事在Project Structure里把 Project SDK 设置为 JDK 17。在Settings - Build Tools - Maven - Runner里把 JRE 设置为 JDK 17。在Settings - Build Tools - Maven - Importing里确认 JDK for importer 也是 JDK 17。如果 IDE 中的 Project SDK 和 Maven Runner 使用的 JDK 不一致Maven 编译时可能仍然使用旧版本导致编译失败或代码提示不正确。命令行构建时则依赖JAVA_HOME环境变量所以安装完 JDK 17 后应重新打开命令行和 IDE确保环境变量已经生效。Maven 本身不需要额外下载IDEA 自带 Maven 也可以但建议使用 Maven 3.8 以上版本。Spring Boot 3.x 项目对 Maven 版本没有强制要求但过低版本可能在解析依赖时出现问题。2.3 生成 Spring Boot 项目骨架项目骨架可以直接使用 Spring Initializr 生成也可以手动创建 Maven 项目。使用 Spring Initializr 的好处是不用自己配pom.xml的基础结构。生成项目时需要注意Java版本选 17。Spring Boot版本选 3.1.x。Dependencies至少添加Spring Web。构建工具选择 Maven。项目名称建议用purchase-management。生成后手动往pom.xml中加入 MyBatis-Plus、MySQL、Validation、Lombok 依赖。完整的pom.xml核心依赖部分如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.1.5/version relativePath/ /parent properties java.version17/java.version project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.4/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这段配置里最关键的依赖是mybatis-plus-spring-boot3-starter。如果写成mybatis-plus-boot-starterSpring Boot 3 项目会在启动阶段因为包名不兼容而报错。2.4 环境检查清单进入编码前建议先按下面的清单确认环境检查项检查方式预期结果JDK 版本命令行执行java -version显示 17.xJAVA_HOME查看环境变量指向 JDK 17 安装目录Maven JDKIDEA Maven Runner 设置使用 JDK 17MySQL 连接使用客户端连接localhost:3306可以正常登录数据库编码查看数据库字符集utf8mb4项目编码IDEA 右下角 File EncodingUTF-8环境检查这一环节看似琐碎但能过滤掉后面大部分启动问题。很多人项目写完了却启动失败回头排查才发现是 JDK 版本不对或数据库连接串少了编码参数。3. 数据库设计四张核心表跑通采购主流程3.1 建库建表 SQL采购管理系统采用四张核心表用户表、供应商表、采购订单表、采购订单明细表。SQL 脚本如下CREATE DATABASE IF NOT EXISTS purchase_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE purchase_db; CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT 密码, nickname VARCHAR(50) COMMENT 昵称, role VARCHAR(20) NOT NULL DEFAULT USER COMMENT 角色, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) COMMENT 用户表; CREATE TABLE supplier ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, supplier_name VARCHAR(100) NOT NULL COMMENT 供应商名称, contact_person VARCHAR(50) COMMENT 联系人, contact_phone VARCHAR(20) COMMENT 联系电话, address VARCHAR(200) COMMENT 地址, status TINYINT DEFAULT 1 COMMENT 状态1启用0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) COMMENT 供应商表; CREATE TABLE purchase_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, order_no VARCHAR(32) NOT NULL COMMENT 订单编号, supplier_id BIGINT NOT NULL COMMENT 供应商ID, total_amount DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT 总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待审核1已通过2已驳回, remark VARCHAR(500) COMMENT 备注, create_by BIGINT COMMENT 创建人, audit_by BIGINT COMMENT 审核人, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, audit_time DATETIME COMMENT 审核时间 ) COMMENT 采购订单表; CREATE TABLE purchase_order_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, order_id BIGINT NOT NULL COMMENT 订单ID, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, quantity INT NOT NULL DEFAULT 1 COMMENT 数量, price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT 单价 ) COMMENT 采购订单明细表;3.2 表关系与字段含义supplier与purchase_order是一对多关系一个供应商可以有多张采购订单。purchase_order与purchase_order_item是一对多关系一张订单包含多条明细。purchase_order中status字段表示订单状态这是采购流程中最核心的字段。status 值含义说明0待审核采购员提交后进入待审核状态1已通过审核人通过订单生效2已驳回审核人驳回采购员可修改后重新提交total_amount不需要用户手工录入而是在保存订单时根据明细中的数量和单价计算。这样可以避免订单总金额与明细不一致的问题。计算逻辑放到后端 Service 里处理前端只负责展示。3.3 为什么这里不建物理外键很多教程会在建表时加上FOREIGN KEY外键约束但在实际项目特别是毕业设计演示项目中物理外键会带来一些麻烦删除供应商时如果存在关联订单数据库会直接拒绝删除导入数据时如果顺序不对外键校验也会失败。所以这套表结构只保留逻辑关联不建物理外键。数据一致性靠后端事务和代码校验来保证。比如删除供应商前先查询是否存在未完成的订单如果有关联订单则提示不允许删除。注意不建物理外键不等于不做关联查询而是把关联规则放到应用层控制这样更符合常见项目实践也方便通过接口测试业务逻辑。3.4 初始化一条管理员账号登录功能需要一个初始账号。密码建议使用 BCrypt 加密存储但为了演示简化这里先插入一条明文密码记录正式项目里需要改成加密存储。INSERT INTO sys_user (username, password, nickname, role) VALUES (admin, 123456, 管理员, ADMIN);后端在做登录接口时需要将前端传来的密码与数据库中的密码比对。本文示例为了便于学习先使用明文生产环境必须使用BCryptPasswordEncoder或类似方案。这一点会在后续内容中再强调。4. 后端接口实现从登录到采购订单的完整链路4.1 项目包结构与核心依赖项目的包结构建议按职责分层方便后续维护和面试讲解com.example.purchase ├── PurchaseApplication.java ├── common │ ├── Result.java │ └── LoginInterceptor.java ├── config │ └── WebConfig.java ├── controller │ ├── AuthController.java │ ├── SupplierController.java │ └── PurchaseOrderController.java ├── entity │ ├── User.java │ ├── Supplier.java │ ├── PurchaseOrder.java │ └── PurchaseOrderItem.java ├── mapper │ ├── UserMapper.java │ ├── SupplierMapper.java │ ├── PurchaseOrderMapper.java │ └── PurchaseOrderItemMapper.java ├── dto │ ├── LoginDTO.java │ ├── SupplierDTO.java │ └── PurchaseOrderDTO.java └── service ├── AuthService.java ├── SupplierService.java └── PurchaseOrderService.java包结构清晰的好处是Controller 层只负责参数接收和结果返回Service 层负责业务规则Mapper 层负责数据库访问。面试时能解释清楚分层职责比堆代码更有说服力。4.2 application.yml 配置项说明src/main/resources/application.yml是 Spring Boot 项目的主要配置文件内容如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/purchase_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: auto这里有几个配置点需要理解characterEncodingutf8解决中文写入乱码问题。serverTimezoneAsia/Shanghai解决 MySQL 和 Java 时区不一致导致的日期偏移问题。map-underscore-to-camel-case开启下划线转驼峰映射数据库字段supplier_name会自动映射到 Java 属性supplierName。log-impl配置为StdOutImpl可以在控制台看到 MyBatis 执行的 SQL方便调试。id-type: auto让 MyBatis-Plus 使用数据库自增主键。4.3 登录会话与拦截器登录功能使用 Session 保存登录状态并配合拦截器实现接口访问控制。虽然 JWT 更流行但 Session 方案更简单适合毕业设计演示也方便初学者理解认证的基本原理。先编写统一返回对象ResultData public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(String message) { ResultT result new Result(); result.code 500; result.message message; return result; } }登录接口RestController RequestMapping(/api/auth) public class AuthController { Resource private UserMapper userMapper; PostMapping(/login) public ResultUser login(RequestBody LoginDTO loginDTO, HttpSession session) { User user userMapper.selectOne( new LambdaQueryWrapperUser() .eq(User::getUsername, loginDTO.getUsername()) ); if (user null || !user.getPassword().equals(loginDTO.getPassword())) { return Result.error(用户名或密码错误); } session.setAttribute(loginUser, user); return Result.success(user); } PostMapping(/logout) public ResultVoid logout(HttpSession session) { session.invalidate(); return Result.success(null); } }LoginDTO是一个简单的参数对象包含username和password字段并可以用 Validation 注解做非空校验。拦截器public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); if (loginUser null) { response.setStatus(401); return false; } return true; } }注册拦截器并排除登录接口Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/auth/login); } }这里的关键点是excludePathPatterns必须把登录接口排除在外否则用户还没登录就无法访问登录接口形成死循环。4.4 供应商管理 CRUD供应商管理是最典型的单表 CRUD适合作为核心代码示例。使用 MyBatis-Plus 后Mapper 接口不需要写 SQLMapper public interface SupplierMapper extends BaseMapperSupplier { }Controller 层提供分页查询、新增、修改、删除接口RestController RequestMapping(/api/supplier) public class SupplierController { Resource private SupplierMapper supplierMapper; GetMapping(/page) public ResultIPageSupplier page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String supplierName) { LambdaQueryWrapperSupplier wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(supplierName), Supplier::getSupplierName, supplierName) .orderByDesc(Supplier::getId); PageSupplier page supplierMapper.selectPage(new Page(pageNum, pageSize), wrapper); return Result.success(page); } PostMapping public ResultVoid add(RequestBody Supplier supplier) { if (StringUtils.hasText(supplier.getSupplierName())) { supplier.setCreateTime(LocalDateTime.now()); supplierMapper.insert(supplier); } return Result.success(null); } PutMapping public ResultVoid update(RequestBody Supplier supplier) { supplierMapper.updateById(supplier); return Result.success(null); } DeleteMapping(/{id}) public ResultVoid delete(PathVariable Long id) { supplierMapper.deleteById(id); return Result.success(null); } }分页查询中的like条件是动态拼接的当supplierName为空时不加条件。这是 MyBatis-PlusLambdaQueryWrapper的常见用法写法上比拼接 SQL 更安全不会出现 SQL 注入问题。4.5 采购订单创建与审核采购订单是系统的核心业务难点在于“订单 明细”需要一起保存并且状态流转要符合业务规则。这里采用 Spring 事务来保证数据一致性。订单 Service 的核心方法Service public class PurchaseOrderServiceImpl implements PurchaseOrderService { Resource private PurchaseOrderMapper orderMapper; Resource private PurchaseOrderItemMapper orderItemMapper; Override Transactional(rollbackFor Exception.class) public PurchaseOrder createOrder(PurchaseOrderDTO orderDTO, Long userId) { PurchaseOrder order new PurchaseOrder(); order.setOrderNo(generateOrderNo()); order.setSupplierId(orderDTO.getSupplierId()); order.setStatus(0); order.setCreateBy(userId); order.setRemark(orderDTO.getRemark()); BigDecimal total BigDecimal.ZERO; ListPurchaseOrderItem items orderDTO.getItems(); for (PurchaseOrderItem item : items) { BigDecimal amount item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); total total.add(amount); } order.setTotalAmount(total); orderMapper.insert(order); for (PurchaseOrderItem item : items) { item.setOrderId(order.getId()); orderItemMapper.insert(item); } return order; } Override Transactional(rollbackFor Exception.class) public void auditOrder(Long orderId, Long auditUserId, Integer status) { PurchaseOrder order orderMapper.selectById(orderId); if (order null) { throw new RuntimeException(订单不存在); } if (!order.getStatus().equals(0)) { throw new RuntimeException(当前订单状态不允许审核); } order.setStatus(status); order.setAuditBy(auditUserId); order.setAuditTime(LocalDateTime.now()); orderMapper.updateById(order); } private String generateOrderNo() { return PO System.currentTimeMillis(); } }这里有两个关键点第一订单创建和明细保存必须处于同一个事务中。如果订单插入成功但明细插入失败事务回滚后订单数据不会残留在数据库里。第二审核操作要做状态校验。已经审核过的订单不能重复审核这个校验避免了并发情况下状态被覆盖的问题。4.6 参数校验规则和返回格式采购订单新增接口接收的 DTOData public class PurchaseOrderDTO { NotNull(message 供应商不能为空) private Long supplierId; NotBlank(message 采购明细不能为空) private ListPurchaseOrderItem items; private String remark; }在 Controller 中通过Valid触发校验PostMapping public ResultPurchaseOrder create(RequestBody Valid PurchaseOrderDTO orderDTO, HttpSession session) { User loginUser (User) session.getAttribute(loginUser); PurchaseOrder order purchaseOrderService.createOrder(orderDTO, loginUser.getId()); return Result.success(order); }返回格式统一使用Result包装前端判断code是否为 200不需要针对每个接口单独处理不同的返回结构。统一返回格式还有一个好处拦截器被触发时也可以返回同样的 JSON 错误结构。5. 启动、测试与验证不要只确认控制台没报错5.1 启动项目与观察日志在 IDEA 中运行启动类PurchaseApplication观察控制台输出。正常启动时会出现类似下面的日志Tomcat started on port(s): 8080 (http) Started PurchaseApplication in 3.12 seconds如果端口被占用会出现端口绑定失败的错误需要修改application.yml中的端口或者关闭占用 8080 端口的进程。启动成功后不能直接认为是“可完美运行”还要通过接口请求验证业务逻辑。因为很多问题只有在接口调用时才会暴露比如 SQL 写错、参数绑定错误、事务未生效等。5.2 用 curl 验证登录和接口登录接口验证curl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}响应中应包含管理员用户信息和统一的Result结构。未登录访问供应商接口时因为拦截器会返回 401所以可以用下面的命令验证拦截是否生效curl -X GET http://localhost:8080/api/supplier/page如果返回 401 状态码说明拦截器生效如果返回了正常数据说明拦截器配置有问题需要检查WebConfig中的路径拦截配置。5.3 验证订单审批和数据落库创建一份包含两条明细的采购订单然后分别验证待审核、通过、驳回三种状态。调用创建订单接口后到数据库执行SELECT * FROM purchase_order; SELECT * FROM purchase_order_item;确认purchase_order中只出现一条订单记录total_amount等于两条明细金额之和purchase_order_item中出现两条明细记录并且order_id指向正确的订单。重复提交审核时第二次审核应该返回错误信息说明状态校验逻辑生效。5.4 验证异常分支异常分支经常被忽略但它恰恰是面试时最容易展开聊的点。以下场景建议都验证一遍场景预期结果登录密码错误返回错误信息不写入 Session未登录访问业务接口返回 401创建订单时明细为空参数校验失败返回校验错误审核不存在的订单返回“订单不存在”重复审核同一订单返回“当前订单状态不允许审核”异常分支验证通过后才能说系统具备基本的业务健壮性。6. 常见问题排查版本、乱码、依赖冲突是重灾区6.1 高频问题速查表问题现象常见原因检查方式处理建议项目启动报ClassNotFoundException: javax.servlet.*Spring Boot 3 使用jakarta.*包名老代码或旧依赖不兼容查看堆栈中的类名把代码中的javax改成jakarta并升级依赖MyBatis-Plus 相关类找不到引入了mybatis-plus-boot-starter而不是mybatis-plus-spring-boot3-starter查看pom.xml替换成 Boot3 专用 starter写入数据库中文乱码数据库连接串缺少编码参数或数据库字符集不是 utf8mb4查看数据库表字符集修改连接串重建数据库日期字段和数据库时间相差 8 小时时区配置不一致查看 URL 中的serverTimezone统一使用Asia/Shanghai接口返回 401未登录或被拦截器拦截查看是否携带 Session先登录再访问业务接口Bean 无法注入缺少Mapper注解或启动类没扫描到 Mapper检查 Mapper 接口和启动类添加Mapper或MapperScan6.2 启动失败排查路径项目启动失败时不要直接百度报错信息先按下面的顺序检查看控制台第一行异常类型是ClassNotFoundException、BeanCreationException还是PortInUseException。如果是ClassNotFoundException优先检查pom.xml中的依赖版本和包名。如果是BeanCreationException检查 Mapper 是否被扫描到Service 是否加上了Service注解。如果是数据库连接异常先检查 MySQL 是否启动、账号密码是否正确、数据库是否存在。检查数据源 URL 最后有没有characterEncodingutf8和serverTimezoneAsia/Shanghai。启动失败时日志非常关键。很多同学看到异常堆栈就直接慌其实只要找到Caused by:那一行基本就能定位到根因。6.3 中文乱码和时区问题中文乱码在 JavaWeb 项目里非常常见。检查顺序如下数据库连接串是否包含characterEncodingutf8。数据库字符集是否为utf8mb4。IDEA 的文件编码是否为 UTF-8。前端请求是否设置了Content-Type: application/json; charsetutf-8。时区问题可以通过两个方向验证一是查询数据库中的时间字段是否和 Java 传入的时间一致二是检查连接串中的serverTimezone参数。如果出现数据库时间和系统时间相差 8 小时优先修改连接串参数而不是写代码做时间转换。6.4 接口 404 或 405 的检查顺序接口访问出现 404 时按下面的顺序排查检查路径是否正确特别是RequestMapping和GetMapping的拼写。检查 Controller 类有没有加RestController或Controller。检查启动类所在的包路径是否包含了 Controller 所在的包路径。检查项目是否重新编译成功。出现 405 时通常是请求方法不对。比如接口是PostMapping却用 GET 请求访问此时会返回 405 Method Not Allowed。确认接口注解和请求方式是否一致即可。7. 从毕设到简历项目完善方向和面试讲解7.1 学习环境与生产环境的差异这个系统在本地学习环境里可以正常跑通但离生产环境还有一段距离。区别主要体现在以下几个方面维度学习环境生产环境密码存储明文或简单加密BCrypt 加密存储登录状态SessionJWT 或 Session Redis日志控制台输出文件日志 日志平台数据库单机 MySQL主从或云数据库权限控制简单拦截器Spring Security异常处理简单返回提示统一异常处理器 告警部署IDEA 运行Linux 服务 发布脚本面试时如果能主动指出这些差异说明不只是写了 CRUD还考虑过真实项目的复杂度。7.2 可以继续扩展的功能点这个系统可以横向扩展的方向很多挑选几个比较自然的入库管理采购订单审核通过后生成入库单更新库存表。统计报表按月统计采购金额、供应商采购次数。Excel 导出把订单列表导出为 Excel 文件。权限控制引入 Spring Security区分管理员和普通采购员。操作日志用 AOP 记录用户操作行为。建议优先做入库管理和统计报表因为它们与采购订单是直接关联的不会显得功能突兀。7.3 面试时怎么讲这个项目面试时讲解这个项目不要上来就讲 Spring Boot 多方便、MyBatis-Plus 多好用而是按业务主线讲项目背景这是一个采购管理系统核心流程是供应商管理、采购订单创建、订单审核。数据库设计四张表订单和明细一对多订单状态字段控制流程。权限控制登录后写入 Session拦截器校验登录状态。事务处理创建订单时订单主表和明细表必须在同一事务中保存。遇到的问题比如 Spring Boot 3 使用jakarta.*包名导致老代码不兼容以及怎么定位解决。把技术点放到业务场景里讲比单纯背概念更有说服力。8. 练习顺序和发布前检查清单8.1 建议按这个顺序练习如果是从零开始手写这个系统建议按下面的顺序来每完成一个阶段就验证一次搭好 Spring Boot 项目骨架配置数据源启动空项目。编写用户登录接口验证 Session 是否写入。配置拦截器验证未登录拦截是否生效。完成供应商表的 CRUD验证分页查询。完成采购订单创建重点理解事务和总金额计算。完成订单审核验证状态流转。整理接口文档和项目笔记。这个顺序的好处是每一步都建立在上一步的基础上出问题时定位范围小。不要一口气写完所有代码再统一调试那样问题会堆叠得非常严重。8.2 发布前检查清单项目完成并准备作为毕业设计或简历项目提交前建议按下面的清单做一次完整检查检查项完成状态JDK 17 环境配置正确项目可在本机重新启动是/否MySQL 初始化脚本可以在新环境直接执行是/否数据库连接密码在配置文件中已调整不是写死的内网地址是/否登录拦截器已排除登录接口未出现循环拦截是/否创建订单接口具备事务控制明细失败时主表不会残留是/否中文数据写入和读取无乱码是/否订单状态字段流转符合业务规则是/否提供了数据库初始化脚本和运行说明文档是/否这个清单也可以放到项目README中作为项目的运行说明和验收标准。面试展示时能拿出一个有条理的检查清单比只扔一个源码压缩包给人的印象要深刻得多。采购管理系统的核心不在于框架是否新潮而在于业务流程是否完整、数据是否可靠、权限和事务处理是否合理。用 JDK 17 和 Spring Boot 3 作为底层本质上是为了站在一个面向未来几年的技术基线上把精力放在理解业务规则和工程化细节上。把这套代码跑通只是第一步能讲清楚订单状态为什么这样设计、事务为什么必须加、拦截器为什么排除登录接口才能真正让项目变成简历上的加分项。
返回列表