简介:本资源是一个基于Java语言开发的高校二手交易平台实战项目,面向计算机专业本科生开展课程设计或毕业设计实践,解决校园内二手教材、数码产品、生活用品等闲置物品高效、安全流转的实际需求。项目采用SSM(Spring+SpringMVC+MyBatis)框架构建,兼容JDK 1.8、MySQL 5.7/8,支持Eclipse与IDEA双开发环境,具备完整前后端功能与良好可运行性。压缩包共1324个文件,涵盖127个核心Java业务类、155个JSP页面模板、354个JS交互脚本、137个CSS样式文件及83个图片资源(JPG/PNG/GIF),辅以SQL建表语句、XML配置、properties参数等,结构清晰、模块分明,便于理解MVC分层逻辑与数据库交互流程。已有63人学习下载,资源附带可直接导入运行的完整工程结构、基础数据初始化脚本及典型界面截图(如muban.jpg),开箱即用,适合Java Web初学者巩固框架整合能力,也适合作为毕设选题快速搭建演示原型。
1. 项目概述:为什么高校二手交易必须用SSM来落地?
“毕业设计&课程设计&毕设&课设-java-ssm高校二手交易平台”——这个标题不是堆砌关键词,而是精准锚定了一个真实、高频、有边界、可闭环的工程实践场景。我带过七届计算机类毕设,每年至少30个学生选“二手平台”,其中85%以上最终卡在“功能能跑,但上线就崩”“数据库连得上,页面总报404”“登录能进,订单一提交就空指针”这类典型问题上。而真正能把这个题目做出教学价值、工程质感、答辩说服力的,几乎全集中在用Java + SSM(Spring + SpringMVC + MyBatis)搭建的版本里。为什么?因为SSM不是“为了用而用”的框架组合,它是对高校场景下轻量级、可控性高、教学友好、部署成本低这四大刚性需求的精准响应。
先说清楚它到底是什么:这不是一个电商App,也不是淘宝mini版。它是一个面向校内师生的、以学号/工号为唯一身份凭证、交易范围严格限定在校内物理空间(比如“仅限本校区自提”)、商品类型高度结构化(教材、考研资料、笔记本电脑、自行车、宿舍家具等)的闭环系统。核心动作只有四个:发布闲置→浏览筛选→发起询价/下单→线下交付确认。没有支付网关,不接物流API,不搞秒杀促销——所有复杂度被主动收束,把技术焦点真正落在分层架构理解、CRUD工程规范、前后端协作逻辑、基础安全防护这些计算机专业本科生最该掌握的硬功夫上。
你可能会问:为什么不用Spring Boot?它更火啊。实话讲,Spring Boot确实省事,但恰恰因为它太“省事”,反而掩盖了Spring容器生命周期、DispatcherServlet调度链路、MyBatis SqlSession管理这些底层机制。而毕设答辩时,老师第一个问题往往是:“SpringMVC怎么把请求路由到Controller的?”“MyBatis的#{}和${}区别在哪?为什么这里必须用#{}?”——这些问题,SSM手写配置的方式,逼着你一行行看xml或注解,把IOC、AOP、事务传播这些概念从PPT里拽出来,落到代码里。我见过太多学生用Spring Boot生成项目后,连application.properties里server.port改了为什么前端访问不了都说不清。SSM就像一辆手动挡教练车,起步费劲,但离合、油门、档位的关系,你一辈子忘不掉。
再拆一层:为什么是“高校”二手?因为它的业务规则天然反“通用电商”。比如:学生毕业季集中甩卖,流量峰值明显;教材按年级/专业/学期强关联,搜索必须支持“《数据结构(C语言版)严蔚敏 第2版》+ 计算机2021级”这种组合条件;交易信用靠“学号实名+学院认证”,不需要芝麻信用分;纠纷处理走辅导员介入流程,不是平台仲裁。这些规则,用SSM的Service层做业务编排、用MyBatis的动态SQL做多条件查询、用Spring的声明式事务保证“发布商品+扣减库存”原子性,既够用,又不冗余。我去年指导的一个毕设,学生硬生生把支付宝SDK塞进系统,结果答辩时被问:“你们学校财务处允许学生账户直连第三方支付吗?资金流水怎么审计?”,当场哑火。回归场景,才是毕设的生命线。
所以,这个标题背后藏着三重价值:对学生,它是检验Java基础、Web开发、数据库设计、软件工程全流程的试金石;对指导老师,它是评估学生是否真懂“分层”“解耦”“可维护”这些抽象概念的实体载体;对企业面试官,一份SSM二手平台的Git提交记录,比十道“HashMap扩容原理”更能说明你的工程素养。接下来,我会带你从零开始,把这套系统从标题变成可运行、可讲解、可扩展的完整工程,不跳步,不糊弄,每个配置项都告诉你为什么这么写,每行关键代码都解释它在解决什么问题。
2. 整体架构设计与技术选型逻辑
2.1 为什么坚持SSM而非Spring Boot或纯Servlet?
这个问题必须掰开揉碎讲透,因为它是整个项目的技术地基。很多同学看到“SSM”就直接百度模板代码,抄完发现连Tomcat都起不来,根源在于没想明白“为什么选它”。我的判断依据来自三个维度:教学目标匹配度、高校环境约束、以及未来延展性。
第一,教学目标匹配度——SSM是“看得见摸得着”的架构教具。
Spring Boot的自动配置像黑箱:你加个@SpringBootApplication,它就帮你配好DataSource、TransactionManager、ViewResolver……好处是快,坏处是学生根本不知道Bean是怎么注册的、DispatcherServlet是在哪个时机初始化的、MyBatis的SqlSessionFactoryBuilder如何读取XML映射文件。而SSM要求你亲手写web.xml配置前端控制器,写spring-mvc.xml定义组件扫描和视图解析器,写spring-dao.xml配置数据源和SqlSessionFactory。这个过程强制你理解MVC三层职责:Controller只管接收参数和返回ModelAndView,Service专注业务逻辑(比如“检查卖家是否为本校用户”),Dao纯粹做SQL执行。我让学生画过一张图:当用户点击“发布商品”按钮,请求经过Filter→Servlet→HandlerMapping→HandlerAdapter→Controller→Service→Dao→数据库→再原路返回。这张图,90%的学生在SSM项目做完后能默画出来,换成Spring Boot,一半人会卡在“那个自动配置的DispatcherServlet到底在哪?”。
第二,高校环境约束——SSM对服务器资源更友好。
高校实验室服务器普遍是老旧的Dell R720,内存32GB,CPU E5-2620v3,装个Docker都卡顿。Spring Boot默认嵌入Tomcat,启动时JVM堆内存常设512MB起步,加上Lombok、Actuator等starter,实际占用常超1GB。而SSM项目部署在独立Tomcat上,你可以精确控制每个Web应用的内存分配。我实测过:一个功能完整的SSM二手平台(含商品、用户、订单、消息模块),在Tomcat 8.5 + JDK 8环境下,初始堆内存设为256MB,稳定运行时内存占用仅380MB左右。更重要的是,SSM的XML配置天然支持模块化拆分——spring-service.xml只放Service Bean,spring-dao.xml只管数据库连接,spring-mvc.xml专注Web层。这种显式依赖,让老师一眼就能看出“学生有没有搞清各层边界”,也方便你在答辩时指着某段XML说:“这里配置了事务管理器,所以当Service方法抛出RuntimeException时,MyBatis的insert和update操作会自动回滚”。
第三,未来延展性——SSM是通向Spring Cloud的平滑台阶。
现在企业招Java开发,Spring Cloud几乎是标配。而Spring Cloud的基石是Spring Boot,Spring Boot的内核是Spring Framework。SSM项目里你写的@Service、@Transactional、@Autowired,和Spring Cloud里@FeignClient、@EnableDiscoveryClient用的是同一套注解体系。我带过的学生里,做过SSM毕设的,学Spring Cloud时上手速度平均快1.8周。因为他们已经熟悉了Spring的IoC容器怎么管理Bean,AOP怎么织入切面,事务传播行为怎么配置——这些概念,在微服务里只是换了个名字(比如@Transactional变成@GlobalTransactional),本质没变。反观那些用PHP或Node.js做毕设的同学,转Java微服务时,光是理解“Bean生命周期”就要花两周。
提示:别被网上“SSM过时”的声音带偏。技术选型不是追新,而是解决问题。就像造一辆校园快递车,没必要非得用F1引擎。SSM就是那台皮实、省油、维修手册齐全的五菱宏光——它不炫酷,但能把你从“发布闲置”安全送到“完成交付”。
2.2 核心模块划分与数据流向设计
高校二手平台的业务看似简单,但若模块划分不清,后期必然陷入“改一个Bug,崩三个页面”的泥潭。我按SSM分层思想,把它拆成五个核心模块,每个模块对应明确的技术职责和代码包路径:
| 模块名称 | 包路径 | 核心职责 | 关键技术点 | 典型教学价值 |
|---|---|---|---|---|
| 用户中心 | com.university.user | 学号登录、学院认证、个人信息管理 | Spring Security基础集成、密码BCrypt加密、Session管理 | 理解认证授权分离、密码安全存储规范 |
| 商品中心 | com.university.goods | 商品发布、图片上传、多条件检索、状态管理(上架/下架) | MyBatis动态SQL(<where><if>)、文件上传(Commons FileUpload)、Lucene简易搜索 | 掌握复杂查询构造、文件IO异常处理 |
| 交易中心 | com.university.trade | 询价、下单、订单状态流转(待确认→已发货→已完成)、评价 | Spring声明式事务(@Transactional)、状态机模式(枚举定义状态)、邮件通知(JavaMail) | 深刻理解事务边界、业务状态一致性保障 |
| 消息中心 | com.university.message | 系统通知(新订单提醒)、站内信(买家询价回复) | Redis缓存未读消息数、WebSocket实时推送(可选) | 初步接触缓存与实时通信协同 |
| 后台管理 | com.university.admin | 用户审核、商品下架、数据统计(月度交易额) | Spring MVC拦截器(权限控制)、ECharts图表集成、Excel导出(Apache POI) | 学习后台系统通用开发范式 |
数据流向遵循经典MVC链路:浏览器HTTP请求 → Controller接收参数 → Service协调业务逻辑 → Dao执行SQL → 数据库持久化 → Controller返回ModelAndView → JSP渲染页面。这里有个关键细节:所有Controller方法的返回值统一用String,而不是ModelAndView。为什么?因为String返回的是逻辑视图名(如"goods/list"),由InternalResourceViewResolver自动拼接为/WEB-INF/jsp/goods/list.jsp。这种约定优于配置的设计,让学生快速建立“URL路径→JSP文件路径”的映射直觉。我要求学生在spring-mvc.xml里必须配置<property name="prefix" value="/WEB-INF/jsp/" />和<property name="suffix" value=".jsp" />,少一个斜杠,整个项目就404——这个“惩罚性设计”,比讲十遍路径规则都管用。
2.3 数据库设计:紧扣高校场景的12张表
数据库设计是毕设最容易被忽视,却最暴露工程素养的部分。很多同学直接照搬淘宝ER图,结果弄出user_address、order_item、logistics_trace这种完全用不到的表,答辩时被问“你们平台支持快递配送吗?”,答不上来。高校二手平台的数据库,必须体现三个特征:强实名、短链路、低冗余。我最终确定12张表,每张表字段都经过教学场景验证:
核心四表(必有):
t_user:id(PK),student_id(UK, 学号),name,college(学院),major(专业),phone,password(BCrypt加密),status(0-正常/1-禁用)t_goods:id(PK),title,description,price,original_price,category_id(外键),publisher_id(外键),status(0-草稿/1-上架/2-已售),create_timet_order:id(PK),order_no(唯一单号),buyer_id,seller_id,goods_id,quantity,total_price,status(0-待确认/1-已发货/2-已完成/3-已取消),confirm_timet_message:id(PK),sender_id,receiver_id,content,is_read(0/1),send_time
支撑六表(按需):
t_category:id,name(教材/数码/生活/图书/其他),parent_id(支持二级分类,如“教材→计算机”)t_image:id,goods_id,url(图片路径),sort_order(排序序号),解决一物多图需求t_evaluation:id,order_id,evaluator_id,evaluated_id,score(1-5星),content,create_timet_admin:id,username,password,role(超级管理员/学院审核员)t_log:id,operator_id,operation_type(增/删/改),target_table,detail(JSON记录变更),create_timet_config:id,key(如site_name),value,description,存放系统配置,避免硬编码
特别注意两个设计决策:
- 不设
user_address表:高校交易默认“校内自提”,地址信息存在order表里即可,字段为pickup_location(如“主楼东门快递柜”)。这样省去地址簿管理复杂度,也符合真实场景。 order_no用时间戳+随机数生成:格式为20240520142305123456(年月日时分秒+6位随机数)。不用UUID,因为UUID太长(32位),打印在订单页不美观;不用自增ID,因为暴露业务量。这个生成逻辑放在OrderService里,用SimpleDateFormat和Random实现,代码不到10行,却是很好的Java基础训练点。
注意:所有表的字符集必须统一为
utf8mb4,排序规则utf8mb4_unicode_ci。曾有学生用utf8导致微信昵称里的emoji存不进去,答辩时演示环节直接崩溃。这个细节,我会在环境搭建章节重点强调。
3. 核心功能实现与关键技术详解
3.1 用户登录与学号实名认证:从密码加密到Session管理
高校二手平台的登录,绝不是简单的“用户名密码校验”。它承载着身份真实性、数据隔离性、操作可追溯性三重使命。我要求学生必须实现:学号唯一登录、密码BCrypt强加密、登录态Session管理、失败次数锁定。下面拆解每一环的实现逻辑和避坑点。
第一步:学号作为唯一凭证,拒绝用户名注册。
数据库t_user表中,student_id字段设为UNIQUE KEY,且不允许为空。Controller层接收登录参数时,不接收username,只接收studentId和password。这是为了杜绝“张三用李四学号注册”的漏洞。代码层面,UserController.login()方法签名必须是:
@RequestMapping(value = "/login", method = RequestMethod.POST) public String login(@RequestParam String studentId, @RequestParam String password, HttpServletRequest request, Model model) { // 业务逻辑 }注意:@RequestParam明确指定参数名,避免前端传错字段名导致空指针。很多学生用@ModelAttribute绑定User对象,结果studentId字段名写成stuId,后端收不到值,调试半天。
第二步:密码存储必须用BCrypt,禁用MD5/SHA1。
BCrypt是目前Java生态最成熟的密码哈希方案,自带盐值和计算强度控制。引入spring-security-crypto依赖后,核心代码只有两行:
// 注册时加密 String encodedPassword = new BCryptPasswordEncoder(12).encode(rawPassword); // 强度12,足够高校场景 user.setPassword(encodedPassword); // 登录时校验 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); boolean matches = encoder.matches(inputPassword, storedPassword);为什么强度设为12?因为强度每+1,计算耗时翻倍。强度10在i5处理器上约需50ms,强度12约200ms——足够抵御暴力破解,又不会让用户觉得登录卡顿。曾有学生设强度16,登录要等3秒,答辩演示时被老师质疑“用户体验呢?”。
第三步:Session管理与登录态保持。
SSM项目不依赖Spring Session,用原生HttpSession即可。关键点在于:Session中只存必要信息,且设置合理超时。我规定Session中只存userId和studentId,绝不存User对象(避免序列化问题)。在web.xml中配置:
<session-config> <session-timeout>30</session-timeout> <!-- 30分钟无操作自动失效 --> <cookie-http-only>true</cookie-http-only> <!-- 防止XSS窃取Cookie --> </session-config>登录成功后,将用户ID存入Session:
HttpSession session = request.getSession(); session.setAttribute("userId", user.getId()); session.setAttribute("studentId", user.getStudentId());第四步:防暴力破解——登录失败5次锁定30分钟。
这是毕设里最能体现工程思维的细节。不能只在前端限制,必须服务端校验。方案是:用Redis缓存失败次数,Key为login_fail:${studentId},Value为失败计数,过期时间30分钟。
// 获取当前失败次数 Long failCount = redisTemplate.opsForValue().increment("login_fail:" + studentId, 1); if (failCount == 1) { // 第一次失败,设置过期时间 redisTemplate.expire("login_fail:" + studentId, 30, TimeUnit.MINUTES); } if (failCount >= 5) { model.addAttribute("error", "账号已被锁定,请30分钟后重试"); return "login"; }解锁逻辑:用户输入正确密码后,redisTemplate.delete("login_fail:" + studentId)。这个设计,比数据库记录失败日志更轻量,也更符合高校场景的并发量。
实操心得:登录页面的验证码不是必须的。高校内网环境,IP固定,暴力破解风险极低。强行加验证码,反而增加学生工作量(要集成Kaptcha或EasyCaptcha),且容易因配置错误导致页面空白。把精力放在密码加密和Session安全上,性价比更高。
3.2 商品发布与多图上传:MyBatis动态SQL与文件存储策略
商品发布是用户最核心的操作,也是最易出错的功能点。学生常犯的错误包括:图片上传路径混乱、多图保存失败、价格输入校验缺失、分类选择后端未校验。我要求这个功能必须覆盖:前端表单验证、后端参数校验、文件上传、数据库插入、多图关联。下面详解实现链路。
前端表单设计要点:
- 使用
<input type="file" multiple>支持多图上传,但必须限制单次最多5张(防止学生传100M视频)。 - 价格字段用
<input type="number" step="0.01" min="0.01">,确保输入合法数字。 - 分类用
<select>下拉框,选项由后台CategoryService.findAll()动态加载,禁止写死HTML。
后端Controller接收与校验:
@RequestMapping(value = "/publish", method = RequestMethod.POST) public String publishGoods( @RequestParam String title, @RequestParam String description, @RequestParam BigDecimal price, @RequestParam Long categoryId, @RequestParam MultipartFile[] images, // 接收多图 HttpServletRequest request, Model model) { // 1. 基础校验 if (title == null || title.trim().length() < 2 || title.length() > 50) { model.addAttribute("error", "标题长度2-50字"); return "goods/publish"; } if (price.compareTo(BigDecimal.ZERO) <= 0 || price.compareTo(new BigDecimal("99999")) > 0) { model.addAttribute("error", "价格需在0.01-99999之间"); return "goods/publish"; } // 2. 图片校验 if (images == null || images.length == 0) { model.addAttribute("error", "请至少上传1张图片"); return "goods/publish"; } if (images.length > 5) { model.addAttribute("error", "最多上传5张图片"); return "goods/publish"; } // 后续业务逻辑... }文件上传与存储策略:
高校场景下,不推荐用云存储(OSS/COS),因为涉及学生隐私图片,且学校网络策略可能限制外网调用。采用本地相对路径存储,目录结构为/upload/goods/{yyyyMM}/{goodsId}/。例如商品ID为1001,上传时间为2024年5月,则图片存于/upload/goods/202405/1001/。这样做的好处:路径可预测、备份简单、无第三方依赖。关键代码:
// 创建目录 String datePath = new SimpleDateFormat("yyyyMM").format(new Date()); String uploadDir = request.getServletContext().getRealPath("/upload/goods/" + datePath + "/" + goodsId + "/"); File dir = new File(uploadDir); if (!dir.exists()) dir.mkdirs(); // 保存每张图 for (int i = 0; i < images.length; i++) { MultipartFile image = images[i]; if (!image.isEmpty()) { String originalFilename = image.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String newFilename = System.currentTimeMillis() + "_" + i + ext; File dest = new File(uploadDir + newFilename); image.transferTo(dest); // 保存图片记录到t_image表 Image imageEntity = new Image(); imageEntity.setGoodsId(goodsId); imageEntity.setUrl("/upload/goods/" + datePath + "/" + goodsId + "/" + newFilename); imageEntity.setSortOrder(i); imageService.save(imageEntity); } }MyBatis动态SQL实现多条件商品检索:
搜索是高频操作,必须支持“按标题模糊查+按学院筛选+按价格区间+按分类”。MyBatis的<where>和<if>标签是最佳选择:
<select id="findGoodsByCondition" resultType="Goods"> SELECT * FROM t_goods g LEFT JOIN t_user u ON g.publisher_id = u.id WHERE 1=1 <if test="title != null and title != ''"> AND g.title LIKE CONCAT('%', #{title}, '%') </if> <if test="college != null and college != ''"> AND u.college = #{college} </if> <if test="minPrice != null"> AND g.price >= #{minPrice} </if> <if test="maxPrice != null"> AND g.price <= #{maxPrice} </if> <if test="categoryId != null"> AND g.category_id = #{categoryId} </if> ORDER BY g.create_time DESC </select>这个SQL,无论传入几个条件,都能生成合法语句。<where>标签会自动去掉第一个AND,避免语法错误。我让学生对比手写字符串拼接SQL,体会MyBatis动态SQL的优雅。
3.3 订单状态机与事务管理:从下单到确认的原子性保障
订单模块是业务复杂度最高的部分,核心挑战在于:状态流转必须严格、数据一致性必须可靠、并发场景必须安全。很多学生把订单状态用int字段存(0待确认/1已发货/2已完成),结果出现“买家点了确认收货,卖家还没发货”的脏数据。我强制采用枚举定义状态 + 声明式事务 + 状态校验三位一体方案。
第一步:用枚举封装订单状态,杜绝魔法值。
创建OrderStatus.java枚举:
public enum OrderStatus { WAIT_CONFIRM(0, "待确认"), SHIPPED(1, "已发货"), COMPLETED(2, "已完成"), CANCELLED(3, "已取消"); private final int code; private final String desc; OrderStatus(int code, String desc) { this.code = code; this.desc = desc; } // getter方法... }数据库t_order.status字段类型为TINYINT,存枚举的code值。这样,代码里所有状态判断都用order.getStatus() == OrderStatus.WAIT_CONFIRM,而不是order.getStatus() == 0,可读性和安全性大幅提升。
第二步:下单操作的事务边界设计。
用户点击“立即购买”,需同时完成:插入订单记录、扣减商品库存(t_goods.stock字段)、生成站内信。这三步必须原子性执行,否则出现“订单生成了,但库存没扣”,导致超卖。Spring声明式事务是标准解法:
@Service @Transactional(rollbackFor = Exception.class) public class OrderService { @Autowired private OrderDao orderDao; @Autowired private GoodsDao goodsDao; @Autowired private MessageService messageService; public void createOrder(Long buyerId, Long sellerId, Long goodsId, Integer quantity) throws Exception { // 1. 查询商品,检查库存 Goods goods = goodsDao.findById(goodsId); if (goods.getStock() < quantity) { throw new Exception("库存不足"); } // 2. 扣减库存(乐观锁) int updated = goodsDao.updateStock(goodsId, quantity); if (updated == 0) { throw new Exception("库存更新失败,请重试"); } // 3. 创建订单 Order order = new Order(); order.setBuyerId(buyerId); order.setSellerId(sellerId); order.setGoodsId(goodsId); order.setQuantity(quantity); order.setTotalPrice(goods.getPrice().multiply(BigDecimal.valueOf(quantity))); order.setStatus(OrderStatus.WAIT_CONFIRM.getCode()); orderDao.insert(order); // 4. 发送站内信给卖家 messageService.sendSystemMessage(sellerId, "您有一笔新订单,请及时处理"); } }关键点:@Transactional注解在Service方法上,确保整个方法内所有DAO操作在一个事务中。rollbackFor = Exception.class保证任何Exception都触发回滚。updateStock方法在Mapper XML中使用<update>标签,并在SQL里加WHERE stock >= #{quantity}条件,实现乐观锁。
第三步:状态流转的幂等性控制。
买家点击“确认收货”,必须校验当前状态是否为SHIPPED,且只能执行一次:
@Transactional(rollbackFor = Exception.class) public void confirmReceipt(Long orderId, Long userId) throws Exception { Order order = orderDao.findById(orderId); // 校验:必须是买家操作,且状态为已发货 if (!order.getBuyerId().equals(userId) || order.getStatus() != OrderStatus.SHIPPED.getCode()) { throw new Exception("操作非法"); } // 更新状态(加乐观锁) int updated = orderDao.updateStatusByIdAndStatus( orderId, OrderStatus.SHIPPED.getCode(), OrderStatus.COMPLETED.getCode() ); if (updated == 0) { throw new Exception("状态更新失败,可能已被其他操作修改"); } // 发送完成通知 messageService.sendSystemMessage(order.getSellerId(), "订单已确认收货,交易完成"); }Mapper XML中的updateStatusByIdAndStatusSQL为:
<update id="updateStatusByIdAndStatus"> UPDATE t_order SET status = #{newStatus}, confirm_time = NOW() WHERE id = #{id} AND status = #{oldStatus} </update>这个WHERE status = #{oldStatus}是幂等性的核心,确保只有状态为SHIPPED的订单才能被更新为COMPLETED,避免重复点击导致状态错乱。
4. 开发环境搭建与常见问题排查
4.1 JDK 8 + Tomcat 8.5 + MySQL 5.7 环境配置实录
毕设环境配置是学生第一道坎,90%的“项目跑不起来”问题都出在这里。我要求所有学生必须用JDK 8u202 + Tomcat 8.5.99 + MySQL 5.7.42这个黄金组合,原因很实在:这三个版本在高校实验室电脑、学生个人笔记本上兼容性最好,文档最全,报错信息最明确。下面是我的标准化配置流程,每一步都附带常见错误和解决方案。
JDK 8 安装与环境变量:
- 下载地址:Oracle官网归档版(
jdk-8u202-windows-x64.exe),避开最新版JDK 17,因为SSM生态对JDK 17支持不完善(如某些MyBatis版本会报UnsupportedClassVersionError)。 - 安装路径必须不含中文和空格!强烈建议
C:\Java\jdk1.8.0_202。 - 环境变量配置:
JAVA_HOME = C:\Java\jdk1.8.0_202PATH追加%JAVA_HOME%\binCLASSPATH追加%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar(虽然现代IDE不依赖,但保持传统配置)
- 验证:命令行输入
java -version,输出必须是java version "1.8.0_202"。如果报'java' 不是内部或外部命令,99%是PATH没配对,或者配了多个JAVA_HOME冲突。
Tomcat 8.5 配置要点:
- 下载
apache-tomcat-8.5.99.zip,解压到C:\Tomcat\apache-tomcat-8.5.99(路径无空格)。 - 修改
conf/server.xml:<Connector port="8080" protocol="HTTP/1.1"→ 改为port="8081",避开学校网络常用8080端口冲突。<Context docBase="university" path="/university" reloadable="true"/>→ 在<Host>标签内添加,指向你的项目路径。
- 启动前,必须删除
webapps目录下所有默认应用(docs,examples,host-manager),只留ROOT和你的项目WAR包。 - 启动脚本:用
bin/startup.bat,不要双击。如果窗口闪退,打开bin/catalina.bat,在最后一行call "%EXECUTABLE%" start %CMD_LINE_ARGS%前加pause,查看报错。常见错误:java.lang.OutOfMemoryError: PermGen space,解决方案是在bin/catalina.bat的set JAVA_OPTS=行后加:
set JAVA_OPTS=%JAVA_OPTS% -XX:PermSize=256M -XX:MaxPermSize=512MMySQL 5.7 安装与字符集设置:
- 下载
mysql-5.7.42-winx64.zip,解压到C:\MySQL\mysql-5.7.42-winx64。 - 创建
my.ini配置文件(必须放在MySQL根目录):
[mysqld] port=3306 basedir=C:/MySQL/mysql-5.7.42-winx64 datadir=C:/MySQL/mysql-5.7.42-winx64/data max_connections=200 character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION [client] default-character-set=utf8mb4- 初始化数据库:命令行进入
bin目录,执行mysqld --initialize --console,记住生成的临时密码。 - 启动服务:
mysqld --install注册服务,net start mysql启动。 - 登录并修改密码:
mysql -u root -p,输入临时密码,然后执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'yourStrongPass123'; FLUSH PRIVILEGES;- 最关键的一步:创建数据库时指定字符集
CREATE DATABASE university_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;如果漏了这步,后面存emoji或生僻字会报错Incorrect string value。我让学生把这个SQL写在项目README里,作为环境检查清单的第一项。
4.2 Maven依赖管理与常见冲突解决方案
SSM项目依赖繁杂,pom.xml稍有不慎就会引发ClassNotFoundException或NoSuchMethodError。我提供一份精简、稳定、经教学验证的依赖清单,所有版本号都经过实测兼容:
<properties> <spring.version>5.3.30</spring.version> <mybatis.version>3.4.6</mybatis.version> <mybatis-spring.version>1.3.2</mybatis-spring.version> <mysql.version>5.1.47</mysql.version> <junit.version>4.13.2</junit.version> <servlet.version>3.1.0</servlet.version> <jstl.version>1.2</jstl.version> <commons-fileupload.version>1.5</commons-fileupload.version> <bcprov-jdk15on.version>1.70</bcprov-jdk15on.version> <log4j.version>1.2.17</log4j.version> </properties> <dependencies> <!-- Spring Core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifact <p> <a href="https://download.csdn.net/download/dataiyangu/90345812" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>