毕业设计实战从选题到部署的完整技术闭环最近和几位学弟学妹聊起毕业设计发现大家普遍面临一个困境选题听起来高大上但实际做起来要么功能单薄得像课程作业要么技术栈堆砌了一堆却跑不通最后只能草草了事。其实一个好的毕业设计不在于用了多少新技术而在于能否构建一个完整、可运行、有实际业务逻辑的技术闭环。今天我就以Java技术栈为例分享如何从零开始打造一个合格的毕业设计项目。1. 背景痛点为什么你的毕业设计总是不尽如人意在开始技术细节之前我们先分析一下毕业设计中常见的几个问题功能单薄缺乏业务深度很多项目只实现了基础的增删改查CRUD没有状态流转、权限分层或业务流程看起来像个“玩具系统”。技术选型盲目堆砌为了显得“技术先进”盲目引入Redis、MQ、Elasticsearch等中间件却没有合理的应用场景反而增加了复杂度。代码结构混乱Controller里写业务逻辑、Service里直接操作数据库、没有分层概念导致后期维护和扩展困难。缺乏工程化意识没有单元测试、接口文档不完善、部署流程依赖手动操作项目离“生产可用”还有很大距离。前后端分离不彻底仍采用JSP等模板技术或前后端耦合过紧不利于独立开发和部署。2. 技术选型为什么是Spring Boot MyBatis-Plus Vue面对众多的Java框架我推荐Spring Boot MyBatis-Plus Vue这个组合原因如下2.1 Spring Boot vs 传统SSM传统SSMSpring Spring MVC MyBatis配置繁琐需要大量的XML配置而Spring Boot带来了革命性的改变约定优于配置Spring Boot提供了默认配置只需少量配置即可快速启动项目。内嵌容器内置Tomcat、Jetty等Web容器无需单独部署WAR包。起步依赖通过starter依赖简化Maven配置避免版本冲突。生产就绪提供健康检查、指标监控等生产级特性。2.2 MyBatis-Plus的核心优势MyBatis-Plus在MyBatis基础上做了大量增强特别适合快速开发强大的CRUD操作内置通用Mapper和Service单表CRUD无需编写SQL。Lambda表达式支持类型安全的查询条件构造避免硬编码字段名。代码生成器一键生成Entity、Mapper、Service、Controller层代码。分页插件内置分页功能支持多种数据库。2.3 前后端分离架构采用Vue作为前端框架的优势组件化开发提高代码复用性和可维护性。生态丰富Element UI、Vant等UI库可以快速搭建美观界面。独立部署前后端可以独立开发、测试和部署。3. 实战案例校园二手交易平台核心实现下面以“校园二手交易平台”为例展示一个完整业务系统的核心模块设计。这个项目包含用户认证、商品管理、订单交易等典型业务场景。3.1 项目架构设计校园二手交易平台 ├── 用户模块注册、登录、个人信息 ├── 商品模块发布、查询、修改、下架 ├── 订单模块创建、支付、发货、确认收货 ├── 消息模块站内信、通知 └── 后台管理数据统计、用户管理3.2 数据库设计要点-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像, status tinyint(1) 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 product ( id bigint(20) NOT NULL COMMENT 主键ID, user_id bigint(20) NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 商品标题, description text COMMENT 商品描述, price decimal(10,2) NOT NULL COMMENT 价格, category varchar(50) DEFAULT NULL COMMENT 分类, status tinyint(1) DEFAULT 1 COMMENT 状态1-上架0-下架2-已售出, view_count int(11) DEFAULT 0 COMMENT 浏览数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 订单表 CREATE TABLE order ( id varchar(32) NOT NULL COMMENT 订单号, product_id bigint(20) NOT NULL COMMENT 商品ID, buyer_id bigint(20) NOT NULL COMMENT 买家ID, seller_id bigint(20) NOT NULL COMMENT 卖家ID, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint(1) DEFAULT 1 COMMENT 状态1-待支付2-已支付3-已发货4-已完成5-已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, pay_time datetime DEFAULT NULL COMMENT 支付时间, PRIMARY KEY (id), KEY idx_buyer_id (buyer_id), KEY idx_seller_id (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;3.3 用户认证模块实现用户认证采用JWTJSON Web Token方案避免Session带来的服务器存储压力。JWT配置类Configuration public class JwtConfig { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; Bean public JwtTokenUtil jwtTokenUtil() { return new JwtTokenUtil(secret, expiration); } } Component public class JwtTokenUtil { private final String secret; private final Long expiration; public JwtTokenUtil(String secret, Long expiration) { this.secret secret; this.expiration expiration; } /** * 生成token */ public String generateToken(String username) { MapString, Object claims new HashMap(); claims.put(username, username); Date now new Date(); Date expiryDate new Date(now.getTime() expiration * 1000); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 验证token */ public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } /** * 从token中获取用户名 */ public String getUsernameFromToken(String token) { Claims claims Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } }登录接口实现RestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; Autowired private JwtTokenUtil jwtTokenUtil; /** * 用户登录 * param loginRequest 登录请求体 * return 登录结果包含token */ PostMapping(/login) public ResultLoginResponse login(Valid RequestBody LoginRequest loginRequest) { // 1. 验证用户名密码 User user userService.findByUsername(loginRequest.getUsername()); if (user null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 2. 检查用户状态 if (user.getStatus() 0) { throw new BusinessException(账号已被禁用); } // 3. 生成token String token jwtTokenUtil.generateToken(user.getUsername()); // 4. 返回登录结果 LoginResponse response new LoginResponse(); response.setToken(token); response.setUserInfo(userService.convertToVO(user)); return Result.success(response); } /** * 用户注册 */ PostMapping(/register) public ResultVoid register(Valid RequestBody RegisterRequest registerRequest) { // 检查用户名是否已存在 if (userService.existsByUsername(registerRequest.getUsername())) { throw new BusinessException(用户名已存在); } // 创建用户 User user new User(); user.setUsername(registerRequest.getUsername()); user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); user.setPhone(registerRequest.getPhone()); userService.save(user); return Result.success(注册成功); } }3.4 商品模块实现商品模块需要处理商品的发布、查询、修改和下架等操作。商品实体类Data TableName(product) ApiModel(商品实体) public class Product { TableId(type IdType.ASSIGN_ID) ApiModelProperty(商品ID) private Long id; ApiModelProperty(发布者ID) private Long userId; NotBlank(message 商品标题不能为空) ApiModelProperty(商品标题) private String title; ApiModelProperty(商品描述) private String description; NotNull(message 价格不能为空) DecimalMin(value 0.01, message 价格必须大于0) ApiModelProperty(价格) private BigDecimal price; ApiModelProperty(分类) private String category; ApiModelProperty(状态1-上架0-下架2-已售出) private Integer status; ApiModelProperty(浏览数) private Integer viewCount; TableField(fill FieldFill.INSERT) ApiModelProperty(创建时间) private Date createTime; TableField(fill FieldFill.INSERT_UPDATE) ApiModelProperty(更新时间) private Date updateTime; }商品服务层Service public class ProductServiceImpl extends ServiceImplProductMapper, Product implements ProductService { Autowired private RedisTemplateString, Object redisTemplate; /** * 发布商品 */ Override Transactional(rollbackFor Exception.class) public Long publishProduct(ProductPublishRequest request, Long userId) { // 1. 参数校验 if (request.getPrice().compareTo(BigDecimal.ZERO) 0) { throw new BusinessException(价格必须大于0); } // 2. 创建商品 Product product new Product(); product.setUserId(userId); product.setTitle(request.getTitle()); product.setDescription(request.getDescription()); product.setPrice(request.getPrice()); product.setCategory(request.getCategory()); product.setStatus(1); // 上架状态 // 3. 保存到数据库 this.save(product); // 4. 清除商品列表缓存如果有的话 String cacheKey products:list: request.getCategory(); redisTemplate.delete(cacheKey); return product.getId(); } /** * 查询商品详情带浏览计数 */ Override public ProductVO getProductDetail(Long productId) { // 1. 从缓存查询如果热点商品可以加缓存 String cacheKey product:detail: productId; ProductVO cached (ProductVO) redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { return cached; } // 2. 从数据库查询 Product product this.getById(productId); if (product null) { throw new BusinessException(商品不存在); } // 3. 浏览数1使用Redis原子操作避免并发问题 String viewKey product:view: productId; redisTemplate.opsForValue().increment(viewKey, 1); // 4. 定期同步浏览数到数据库可以定时任务处理 if (redisTemplate.opsForValue().get(viewKey) ! null ((Long) redisTemplate.opsForValue().get(viewKey)) % 10 0) { // 每10次浏览同步一次到数据库 product.setViewCount(product.getViewCount() 10); this.updateById(product); } // 5. 转换为VO并缓存 ProductVO vo convertToVO(product); redisTemplate.opsForValue().set(cacheKey, vo, 30, TimeUnit.MINUTES); return vo; } /** * 分页查询商品列表 */ Override public PageResultProductVO queryProducts(ProductQueryRequest request) { // 构建查询条件 LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(request.getKeyword())) { wrapper.like(Product::getTitle, request.getKeyword()) .or() .like(Product::getDescription, request.getKeyword()); } if (StringUtils.isNotBlank(request.getCategory())) { wrapper.eq(Product::getCategory, request.getCategory()); } if (request.getMinPrice() ! null) { wrapper.ge(Product::getPrice, request.getMinPrice()); } if (request.getMaxPrice() ! null) { wrapper.le(Product::getPrice, request.getMaxPrice()); } // 只查询上架商品 wrapper.eq(Product::getStatus, 1); // 排序 if (price_asc.equals(request.getSort())) { wrapper.orderByAsc(Product::getPrice); } else if (price_desc.equals(request.getSort())) { wrapper.orderByDesc(Product::getPrice); } else { wrapper.orderByDesc(Product::getCreateTime); } // 分页查询 PageProduct page new Page(request.getPageNum(), request.getPageSize()); PageProduct productPage this.page(page, wrapper); // 转换为VO ListProductVO voList productPage.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult(voList, productPage.getTotal()); } private ProductVO convertToVO(Product product) { // 使用BeanUtils或MapStruct进行对象转换 ProductVO vo new ProductVO(); BeanUtils.copyProperties(product, vo); return vo; } }3.5 订单模块与状态机设计订单状态流转是交易系统的核心需要保证状态变更的准确性和一致性。订单状态枚举public enum OrderStatus { WAIT_PAY(1, 待支付), PAID(2, 已支付), SHIPPED(3, 已发货), COMPLETED(4, 已完成), CANCELLED(5, 已取消); private final Integer code; private final String desc; OrderStatus(Integer code, String desc) { this.code code; this.desc desc; } // 状态流转校验 public static boolean canChangeTo(Integer fromStatus, Integer toStatus) { // 定义允许的状态转换 MapInteger, ListInteger allowedTransitions new HashMap(); allowedTransitions.put(WAIT_PAY.code, Arrays.asList(PAID.code, CANCELLED.code)); allowedTransitions.put(PAID.code, Arrays.asList(SHIPPED.code, CANCELLED.code)); allowedTransitions.put(SHIPPED.code, Arrays.asList(COMPLETED.code)); ListInteger allowed allowedTransitions.get(fromStatus); return allowed ! null allowed.contains(toStatus); } }订单服务层含乐观锁Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { Autowired private ProductService productService; /** * 创建订单使用乐观锁防止超卖 */ Override Transactional(rollbackFor Exception.class) public String createOrder(OrderCreateRequest request, Long buyerId) { // 1. 查询商品信息 Product product productService.getById(request.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } // 2. 使用乐观锁更新商品状态 boolean updateSuccess productService.update() .setSql(status 2, version version 1) // 标记为已售出 .eq(id, product.getId()) .eq(status, 1) // 只有上架状态才能购买 .eq(version, product.getVersion()) // 乐观锁 .update(); if (!updateSuccess) { throw new BusinessException(商品状态已变更请重新尝试); } // 3. 生成订单号雪花算法或时间戳随机数 String orderNo generateOrderNo(); // 4. 创建订单 Order order new Order(); order.setId(orderNo); order.setProductId(product.getId()); order.setBuyerId(buyerId); order.setSellerId(product.getUserId()); order.setAmount(product.getPrice()); order.setStatus(OrderStatus.WAIT_PAY.getCode()); this.save(order); return orderNo; } /** * 支付订单 */ Override Transactional(rollbackFor Exception.class) public void payOrder(String orderNo) { // 1. 查询订单 Order order this.getById(orderNo); if (order null) { throw new BusinessException(订单不存在); } // 2. 校验订单状态 if (!OrderStatus.WAIT_PAY.getCode().equals(order.getStatus())) { throw new BusinessException(订单状态不正确无法支付); } // 3. 更新订单状态使用乐观锁 boolean updateSuccess this.update() .set(status, OrderStatus.PAID.getCode()) .set(pay_time, new Date()) .eq(id, orderNo) .eq(status, OrderStatus.WAIT_PAY.getCode()) .update(); if (!updateSuccess) { throw new BusinessException(订单状态已变更请刷新后重试); } // 4. 发送支付成功通知可以异步处理 sendPaymentNotification(order); } /** * 取消订单 */ Override Transactional(rollbackFor Exception.class) public void cancelOrder(String orderNo, Long userId) { Order order this.getById(orderNo); if (order null) { throw new BusinessException(订单不存在); } // 校验操作权限 if (!order.getBuyerId().equals(userId)) { throw new BusinessException(无权操作此订单); } // 校验状态流转 if (!OrderStatus.canChangeTo(order.getStatus(), OrderStatus.CANCELLED.getCode())) { throw new BusinessException(当前状态无法取消订单); } // 更新订单状态 order.setStatus(OrderStatus.CANCELLED.getCode()); this.updateById(order); // 如果商品已标记为售出需要恢复为上架状态 if (OrderStatus.PAID.getCode().equals(order.getStatus()) || OrderStatus.SHIPPED.getCode().equals(order.getStatus())) { productService.update() .set(status, 1) // 恢复为上架 .eq(id, order.getProductId()) .update(); } } private String generateOrderNo() { // 时间戳 随机数 SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String timeStr sdf.format(new Date()); String randomStr String.valueOf((int)((Math.random() * 9 1) * 1000)); return timeStr randomStr; } private void sendPaymentNotification(Order order) { // 异步发送通知可以使用消息队列 // 这里简单记录日志 log.info(订单支付成功通知订单号{}, 金额{}, 买家{}, 卖家{}, order.getId(), order.getAmount(), order.getBuyerId(), order.getSellerId()); } }4. 性能与安全考量4.1 SQL注入防护MyBatis-Plus默认使用预编译语句可以有效防止SQL注入。但需要注意不要使用${}进行字符串拼接而应该使用#{}复杂查询使用QueryWrapper避免手写SQL字符串拼接定期进行安全扫描检查SQL注入漏洞4.2 并发控制策略乐观锁如上文商品购买示例通过version字段控制并发更新分布式锁对于高并发场景可以使用Redis分布式锁队列削峰使用消息队列处理高并发写操作4.3 JWT安全策略设置合理的过期时间access token建议30分钟refresh token建议7天令牌刷新机制提供refresh token接口避免用户频繁登录黑名单机制用户登出时将token加入黑名单Redis实现Service public class TokenBlacklistService { Autowired private RedisTemplateString, Object redisTemplate; private static final String BLACKLIST_KEY token:blacklist:; /** * 将token加入黑名单 */ public void addToBlacklist(String token, long expireTime) { // token剩余有效时间 long ttl expireTime - System.currentTimeMillis(); if (ttl 0) { redisTemplate.opsForValue().set(BLACKLIST_KEY token, 1, ttl, TimeUnit.MILLISECONDS); } } /** * 检查token是否在黑名单中 */ public boolean isInBlacklist(String token) { return redisTemplate.hasKey(BLACKLIST_KEY token); } }5. 生产环境避坑指南5.1 MySQL时区配置Spring Boot连接MySQL时时区问题很常见spring: datasource: url: jdbc:mysql://localhost:3306/campus_trade?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai # 注意serverTimezone参数同时在MySQL配置中设置SET GLOBAL time_zone 8:00;5.2 Swagger生产环境关闭开发环境开启Swagger方便调试但生产环境一定要关闭Configuration ConditionalOnProperty(name swagger.enabled, havingValue true) public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage(com.campus.trade)) .paths(PathSelectors.any()) .build(); } }在application-prod.yml中swagger: enabled: false5.3 前端跨域处理Spring Boot中配置CORSConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080) // 前端地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }5.4 日志配置生产环境需要合理的日志配置logging: level: com.campus.trade: DEBUG org.springframework.web: INFO file: name: logs/campus-trade.log logback: rollingpolicy: max-file-size: 10MB max-history: 305.5 Docker容器化部署创建Dockerfile# 使用OpenJDK作为基础镜像 FROM openjdk:11-jre-slim # 设置工作目录 WORKDIR /app # 复制jar包 COPY target/campus-trade-0.0.1-SNAPSHOT.jar app.jar # 暴露端口 EXPOSE 8080 # 设置JVM参数 ENV JAVA_OPTS-Xms256m -Xmx512m -Dspring.profiles.activeprod # 启动应用 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: campus_trade ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./config/mysql.cnf:/etc/mysql/conf.d/custom.cnf redis: image: redis:6-alpine ports: - 6379:6379 volumes: - redis_data:/data app: build: . ports: - 8080:8080 depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/campus_trade?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai SPRING_REDIS_HOST: redis volumes: mysql_data: redis_data:6. 项目扩展建议完成基础功能后可以考虑以下扩展方向引入Redis缓存缓存热点商品、用户信息减少数据库压力添加全文搜索使用Elasticsearch实现商品全文检索消息推送集成WebSocket实现实时聊天功能支付集成对接支付宝/微信支付沙箱环境数据统计使用ECharts展示交易数据可视化微服务拆分将用户服务、商品服务、订单服务拆分为独立微服务写在最后通过这个完整的校园二手交易平台项目我们不仅实现了基本的CRUD功能更重要的是构建了一个包含用户认证、业务状态流转、并发控制、安全防护的完整系统。毕业设计不是技术的简单堆砌而是对软件工程全流程的实践。建议大家在实现基础功能后选择1-2个扩展方向深入探索比如引入Redis优化性能或者实现Elasticsearch搜索功能。这样既能展示你的技术深度也能让项目更加完整。记住好的毕业设计应该是一个可以运行、有实际价值、代码规范的系统。不要追求大而全而要追求精而美。从这个小项目出发逐步扩展你会发现自己对Java生态和软件工程的理解会更加深刻。最后所有代码都已在GitHub上开源这里不贴具体链接大家可以在GitHub搜索类似项目参考希望能为你的毕业设计提供一些思路和帮助。祝你毕业设计顺利
基于Java的毕业设计实战:从选题到部署的完整技术闭环
毕业设计实战从选题到部署的完整技术闭环最近和几位学弟学妹聊起毕业设计发现大家普遍面临一个困境选题听起来高大上但实际做起来要么功能单薄得像课程作业要么技术栈堆砌了一堆却跑不通最后只能草草了事。其实一个好的毕业设计不在于用了多少新技术而在于能否构建一个完整、可运行、有实际业务逻辑的技术闭环。今天我就以Java技术栈为例分享如何从零开始打造一个合格的毕业设计项目。1. 背景痛点为什么你的毕业设计总是不尽如人意在开始技术细节之前我们先分析一下毕业设计中常见的几个问题功能单薄缺乏业务深度很多项目只实现了基础的增删改查CRUD没有状态流转、权限分层或业务流程看起来像个“玩具系统”。技术选型盲目堆砌为了显得“技术先进”盲目引入Redis、MQ、Elasticsearch等中间件却没有合理的应用场景反而增加了复杂度。代码结构混乱Controller里写业务逻辑、Service里直接操作数据库、没有分层概念导致后期维护和扩展困难。缺乏工程化意识没有单元测试、接口文档不完善、部署流程依赖手动操作项目离“生产可用”还有很大距离。前后端分离不彻底仍采用JSP等模板技术或前后端耦合过紧不利于独立开发和部署。2. 技术选型为什么是Spring Boot MyBatis-Plus Vue面对众多的Java框架我推荐Spring Boot MyBatis-Plus Vue这个组合原因如下2.1 Spring Boot vs 传统SSM传统SSMSpring Spring MVC MyBatis配置繁琐需要大量的XML配置而Spring Boot带来了革命性的改变约定优于配置Spring Boot提供了默认配置只需少量配置即可快速启动项目。内嵌容器内置Tomcat、Jetty等Web容器无需单独部署WAR包。起步依赖通过starter依赖简化Maven配置避免版本冲突。生产就绪提供健康检查、指标监控等生产级特性。2.2 MyBatis-Plus的核心优势MyBatis-Plus在MyBatis基础上做了大量增强特别适合快速开发强大的CRUD操作内置通用Mapper和Service单表CRUD无需编写SQL。Lambda表达式支持类型安全的查询条件构造避免硬编码字段名。代码生成器一键生成Entity、Mapper、Service、Controller层代码。分页插件内置分页功能支持多种数据库。2.3 前后端分离架构采用Vue作为前端框架的优势组件化开发提高代码复用性和可维护性。生态丰富Element UI、Vant等UI库可以快速搭建美观界面。独立部署前后端可以独立开发、测试和部署。3. 实战案例校园二手交易平台核心实现下面以“校园二手交易平台”为例展示一个完整业务系统的核心模块设计。这个项目包含用户认证、商品管理、订单交易等典型业务场景。3.1 项目架构设计校园二手交易平台 ├── 用户模块注册、登录、个人信息 ├── 商品模块发布、查询、修改、下架 ├── 订单模块创建、支付、发货、确认收货 ├── 消息模块站内信、通知 └── 后台管理数据统计、用户管理3.2 数据库设计要点-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像, status tinyint(1) 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 product ( id bigint(20) NOT NULL COMMENT 主键ID, user_id bigint(20) NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 商品标题, description text COMMENT 商品描述, price decimal(10,2) NOT NULL COMMENT 价格, category varchar(50) DEFAULT NULL COMMENT 分类, status tinyint(1) DEFAULT 1 COMMENT 状态1-上架0-下架2-已售出, view_count int(11) DEFAULT 0 COMMENT 浏览数, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), KEY idx_user_id (user_id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; -- 订单表 CREATE TABLE order ( id varchar(32) NOT NULL COMMENT 订单号, product_id bigint(20) NOT NULL COMMENT 商品ID, buyer_id bigint(20) NOT NULL COMMENT 买家ID, seller_id bigint(20) NOT NULL COMMENT 卖家ID, amount decimal(10,2) NOT NULL COMMENT 订单金额, status tinyint(1) DEFAULT 1 COMMENT 状态1-待支付2-已支付3-已发货4-已完成5-已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, pay_time datetime DEFAULT NULL COMMENT 支付时间, PRIMARY KEY (id), KEY idx_buyer_id (buyer_id), KEY idx_seller_id (seller_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;3.3 用户认证模块实现用户认证采用JWTJSON Web Token方案避免Session带来的服务器存储压力。JWT配置类Configuration public class JwtConfig { Value(${jwt.secret}) private String secret; Value(${jwt.expiration}) private Long expiration; Bean public JwtTokenUtil jwtTokenUtil() { return new JwtTokenUtil(secret, expiration); } } Component public class JwtTokenUtil { private final String secret; private final Long expiration; public JwtTokenUtil(String secret, Long expiration) { this.secret secret; this.expiration expiration; } /** * 生成token */ public String generateToken(String username) { MapString, Object claims new HashMap(); claims.put(username, username); Date now new Date(); Date expiryDate new Date(now.getTime() expiration * 1000); return Jwts.builder() .setClaims(claims) .setSubject(username) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 验证token */ public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(secret).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } /** * 从token中获取用户名 */ public String getUsernameFromToken(String token) { Claims claims Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); return claims.getSubject(); } }登录接口实现RestController RequestMapping(/api/auth) public class AuthController { Autowired private UserService userService; Autowired private JwtTokenUtil jwtTokenUtil; /** * 用户登录 * param loginRequest 登录请求体 * return 登录结果包含token */ PostMapping(/login) public ResultLoginResponse login(Valid RequestBody LoginRequest loginRequest) { // 1. 验证用户名密码 User user userService.findByUsername(loginRequest.getUsername()); if (user null || !passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) { throw new BusinessException(用户名或密码错误); } // 2. 检查用户状态 if (user.getStatus() 0) { throw new BusinessException(账号已被禁用); } // 3. 生成token String token jwtTokenUtil.generateToken(user.getUsername()); // 4. 返回登录结果 LoginResponse response new LoginResponse(); response.setToken(token); response.setUserInfo(userService.convertToVO(user)); return Result.success(response); } /** * 用户注册 */ PostMapping(/register) public ResultVoid register(Valid RequestBody RegisterRequest registerRequest) { // 检查用户名是否已存在 if (userService.existsByUsername(registerRequest.getUsername())) { throw new BusinessException(用户名已存在); } // 创建用户 User user new User(); user.setUsername(registerRequest.getUsername()); user.setPassword(passwordEncoder.encode(registerRequest.getPassword())); user.setPhone(registerRequest.getPhone()); userService.save(user); return Result.success(注册成功); } }3.4 商品模块实现商品模块需要处理商品的发布、查询、修改和下架等操作。商品实体类Data TableName(product) ApiModel(商品实体) public class Product { TableId(type IdType.ASSIGN_ID) ApiModelProperty(商品ID) private Long id; ApiModelProperty(发布者ID) private Long userId; NotBlank(message 商品标题不能为空) ApiModelProperty(商品标题) private String title; ApiModelProperty(商品描述) private String description; NotNull(message 价格不能为空) DecimalMin(value 0.01, message 价格必须大于0) ApiModelProperty(价格) private BigDecimal price; ApiModelProperty(分类) private String category; ApiModelProperty(状态1-上架0-下架2-已售出) private Integer status; ApiModelProperty(浏览数) private Integer viewCount; TableField(fill FieldFill.INSERT) ApiModelProperty(创建时间) private Date createTime; TableField(fill FieldFill.INSERT_UPDATE) ApiModelProperty(更新时间) private Date updateTime; }商品服务层Service public class ProductServiceImpl extends ServiceImplProductMapper, Product implements ProductService { Autowired private RedisTemplateString, Object redisTemplate; /** * 发布商品 */ Override Transactional(rollbackFor Exception.class) public Long publishProduct(ProductPublishRequest request, Long userId) { // 1. 参数校验 if (request.getPrice().compareTo(BigDecimal.ZERO) 0) { throw new BusinessException(价格必须大于0); } // 2. 创建商品 Product product new Product(); product.setUserId(userId); product.setTitle(request.getTitle()); product.setDescription(request.getDescription()); product.setPrice(request.getPrice()); product.setCategory(request.getCategory()); product.setStatus(1); // 上架状态 // 3. 保存到数据库 this.save(product); // 4. 清除商品列表缓存如果有的话 String cacheKey products:list: request.getCategory(); redisTemplate.delete(cacheKey); return product.getId(); } /** * 查询商品详情带浏览计数 */ Override public ProductVO getProductDetail(Long productId) { // 1. 从缓存查询如果热点商品可以加缓存 String cacheKey product:detail: productId; ProductVO cached (ProductVO) redisTemplate.opsForValue().get(cacheKey); if (cached ! null) { return cached; } // 2. 从数据库查询 Product product this.getById(productId); if (product null) { throw new BusinessException(商品不存在); } // 3. 浏览数1使用Redis原子操作避免并发问题 String viewKey product:view: productId; redisTemplate.opsForValue().increment(viewKey, 1); // 4. 定期同步浏览数到数据库可以定时任务处理 if (redisTemplate.opsForValue().get(viewKey) ! null ((Long) redisTemplate.opsForValue().get(viewKey)) % 10 0) { // 每10次浏览同步一次到数据库 product.setViewCount(product.getViewCount() 10); this.updateById(product); } // 5. 转换为VO并缓存 ProductVO vo convertToVO(product); redisTemplate.opsForValue().set(cacheKey, vo, 30, TimeUnit.MINUTES); return vo; } /** * 分页查询商品列表 */ Override public PageResultProductVO queryProducts(ProductQueryRequest request) { // 构建查询条件 LambdaQueryWrapperProduct wrapper new LambdaQueryWrapper(); if (StringUtils.isNotBlank(request.getKeyword())) { wrapper.like(Product::getTitle, request.getKeyword()) .or() .like(Product::getDescription, request.getKeyword()); } if (StringUtils.isNotBlank(request.getCategory())) { wrapper.eq(Product::getCategory, request.getCategory()); } if (request.getMinPrice() ! null) { wrapper.ge(Product::getPrice, request.getMinPrice()); } if (request.getMaxPrice() ! null) { wrapper.le(Product::getPrice, request.getMaxPrice()); } // 只查询上架商品 wrapper.eq(Product::getStatus, 1); // 排序 if (price_asc.equals(request.getSort())) { wrapper.orderByAsc(Product::getPrice); } else if (price_desc.equals(request.getSort())) { wrapper.orderByDesc(Product::getPrice); } else { wrapper.orderByDesc(Product::getCreateTime); } // 分页查询 PageProduct page new Page(request.getPageNum(), request.getPageSize()); PageProduct productPage this.page(page, wrapper); // 转换为VO ListProductVO voList productPage.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); return new PageResult(voList, productPage.getTotal()); } private ProductVO convertToVO(Product product) { // 使用BeanUtils或MapStruct进行对象转换 ProductVO vo new ProductVO(); BeanUtils.copyProperties(product, vo); return vo; } }3.5 订单模块与状态机设计订单状态流转是交易系统的核心需要保证状态变更的准确性和一致性。订单状态枚举public enum OrderStatus { WAIT_PAY(1, 待支付), PAID(2, 已支付), SHIPPED(3, 已发货), COMPLETED(4, 已完成), CANCELLED(5, 已取消); private final Integer code; private final String desc; OrderStatus(Integer code, String desc) { this.code code; this.desc desc; } // 状态流转校验 public static boolean canChangeTo(Integer fromStatus, Integer toStatus) { // 定义允许的状态转换 MapInteger, ListInteger allowedTransitions new HashMap(); allowedTransitions.put(WAIT_PAY.code, Arrays.asList(PAID.code, CANCELLED.code)); allowedTransitions.put(PAID.code, Arrays.asList(SHIPPED.code, CANCELLED.code)); allowedTransitions.put(SHIPPED.code, Arrays.asList(COMPLETED.code)); ListInteger allowed allowedTransitions.get(fromStatus); return allowed ! null allowed.contains(toStatus); } }订单服务层含乐观锁Service public class OrderServiceImpl extends ServiceImplOrderMapper, Order implements OrderService { Autowired private ProductService productService; /** * 创建订单使用乐观锁防止超卖 */ Override Transactional(rollbackFor Exception.class) public String createOrder(OrderCreateRequest request, Long buyerId) { // 1. 查询商品信息 Product product productService.getById(request.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品不存在或已下架); } // 2. 使用乐观锁更新商品状态 boolean updateSuccess productService.update() .setSql(status 2, version version 1) // 标记为已售出 .eq(id, product.getId()) .eq(status, 1) // 只有上架状态才能购买 .eq(version, product.getVersion()) // 乐观锁 .update(); if (!updateSuccess) { throw new BusinessException(商品状态已变更请重新尝试); } // 3. 生成订单号雪花算法或时间戳随机数 String orderNo generateOrderNo(); // 4. 创建订单 Order order new Order(); order.setId(orderNo); order.setProductId(product.getId()); order.setBuyerId(buyerId); order.setSellerId(product.getUserId()); order.setAmount(product.getPrice()); order.setStatus(OrderStatus.WAIT_PAY.getCode()); this.save(order); return orderNo; } /** * 支付订单 */ Override Transactional(rollbackFor Exception.class) public void payOrder(String orderNo) { // 1. 查询订单 Order order this.getById(orderNo); if (order null) { throw new BusinessException(订单不存在); } // 2. 校验订单状态 if (!OrderStatus.WAIT_PAY.getCode().equals(order.getStatus())) { throw new BusinessException(订单状态不正确无法支付); } // 3. 更新订单状态使用乐观锁 boolean updateSuccess this.update() .set(status, OrderStatus.PAID.getCode()) .set(pay_time, new Date()) .eq(id, orderNo) .eq(status, OrderStatus.WAIT_PAY.getCode()) .update(); if (!updateSuccess) { throw new BusinessException(订单状态已变更请刷新后重试); } // 4. 发送支付成功通知可以异步处理 sendPaymentNotification(order); } /** * 取消订单 */ Override Transactional(rollbackFor Exception.class) public void cancelOrder(String orderNo, Long userId) { Order order this.getById(orderNo); if (order null) { throw new BusinessException(订单不存在); } // 校验操作权限 if (!order.getBuyerId().equals(userId)) { throw new BusinessException(无权操作此订单); } // 校验状态流转 if (!OrderStatus.canChangeTo(order.getStatus(), OrderStatus.CANCELLED.getCode())) { throw new BusinessException(当前状态无法取消订单); } // 更新订单状态 order.setStatus(OrderStatus.CANCELLED.getCode()); this.updateById(order); // 如果商品已标记为售出需要恢复为上架状态 if (OrderStatus.PAID.getCode().equals(order.getStatus()) || OrderStatus.SHIPPED.getCode().equals(order.getStatus())) { productService.update() .set(status, 1) // 恢复为上架 .eq(id, order.getProductId()) .update(); } } private String generateOrderNo() { // 时间戳 随机数 SimpleDateFormat sdf new SimpleDateFormat(yyyyMMddHHmmss); String timeStr sdf.format(new Date()); String randomStr String.valueOf((int)((Math.random() * 9 1) * 1000)); return timeStr randomStr; } private void sendPaymentNotification(Order order) { // 异步发送通知可以使用消息队列 // 这里简单记录日志 log.info(订单支付成功通知订单号{}, 金额{}, 买家{}, 卖家{}, order.getId(), order.getAmount(), order.getBuyerId(), order.getSellerId()); } }4. 性能与安全考量4.1 SQL注入防护MyBatis-Plus默认使用预编译语句可以有效防止SQL注入。但需要注意不要使用${}进行字符串拼接而应该使用#{}复杂查询使用QueryWrapper避免手写SQL字符串拼接定期进行安全扫描检查SQL注入漏洞4.2 并发控制策略乐观锁如上文商品购买示例通过version字段控制并发更新分布式锁对于高并发场景可以使用Redis分布式锁队列削峰使用消息队列处理高并发写操作4.3 JWT安全策略设置合理的过期时间access token建议30分钟refresh token建议7天令牌刷新机制提供refresh token接口避免用户频繁登录黑名单机制用户登出时将token加入黑名单Redis实现Service public class TokenBlacklistService { Autowired private RedisTemplateString, Object redisTemplate; private static final String BLACKLIST_KEY token:blacklist:; /** * 将token加入黑名单 */ public void addToBlacklist(String token, long expireTime) { // token剩余有效时间 long ttl expireTime - System.currentTimeMillis(); if (ttl 0) { redisTemplate.opsForValue().set(BLACKLIST_KEY token, 1, ttl, TimeUnit.MILLISECONDS); } } /** * 检查token是否在黑名单中 */ public boolean isInBlacklist(String token) { return redisTemplate.hasKey(BLACKLIST_KEY token); } }5. 生产环境避坑指南5.1 MySQL时区配置Spring Boot连接MySQL时时区问题很常见spring: datasource: url: jdbc:mysql://localhost:3306/campus_trade?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai # 注意serverTimezone参数同时在MySQL配置中设置SET GLOBAL time_zone 8:00;5.2 Swagger生产环境关闭开发环境开启Swagger方便调试但生产环境一定要关闭Configuration ConditionalOnProperty(name swagger.enabled, havingValue true) public class SwaggerConfig { Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage(com.campus.trade)) .paths(PathSelectors.any()) .build(); } }在application-prod.yml中swagger: enabled: false5.3 前端跨域处理Spring Boot中配置CORSConfiguration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:8080) // 前端地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }5.4 日志配置生产环境需要合理的日志配置logging: level: com.campus.trade: DEBUG org.springframework.web: INFO file: name: logs/campus-trade.log logback: rollingpolicy: max-file-size: 10MB max-history: 305.5 Docker容器化部署创建Dockerfile# 使用OpenJDK作为基础镜像 FROM openjdk:11-jre-slim # 设置工作目录 WORKDIR /app # 复制jar包 COPY target/campus-trade-0.0.1-SNAPSHOT.jar app.jar # 暴露端口 EXPOSE 8080 # 设置JVM参数 ENV JAVA_OPTS-Xms256m -Xmx512m -Dspring.profiles.activeprod # 启动应用 ENTRYPOINT [sh, -c, java $JAVA_OPTS -jar app.jar]docker-compose.ymlversion: 3.8 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: campus_trade ports: - 3306:3306 volumes: - mysql_data:/var/lib/mysql - ./config/mysql.cnf:/etc/mysql/conf.d/custom.cnf redis: image: redis:6-alpine ports: - 6379:6379 volumes: - redis_data:/data app: build: . ports: - 8080:8080 depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/campus_trade?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai SPRING_REDIS_HOST: redis volumes: mysql_data: redis_data:6. 项目扩展建议完成基础功能后可以考虑以下扩展方向引入Redis缓存缓存热点商品、用户信息减少数据库压力添加全文搜索使用Elasticsearch实现商品全文检索消息推送集成WebSocket实现实时聊天功能支付集成对接支付宝/微信支付沙箱环境数据统计使用ECharts展示交易数据可视化微服务拆分将用户服务、商品服务、订单服务拆分为独立微服务写在最后通过这个完整的校园二手交易平台项目我们不仅实现了基本的CRUD功能更重要的是构建了一个包含用户认证、业务状态流转、并发控制、安全防护的完整系统。毕业设计不是技术的简单堆砌而是对软件工程全流程的实践。建议大家在实现基础功能后选择1-2个扩展方向深入探索比如引入Redis优化性能或者实现Elasticsearch搜索功能。这样既能展示你的技术深度也能让项目更加完整。记住好的毕业设计应该是一个可以运行、有实际价值、代码规范的系统。不要追求大而全而要追求精而美。从这个小项目出发逐步扩展你会发现自己对Java生态和软件工程的理解会更加深刻。最后所有代码都已在GitHub上开源这里不贴具体链接大家可以在GitHub搜索类似项目参考希望能为你的毕业设计提供一些思路和帮助。祝你毕业设计顺利