资讯详情

二手闲置交易系统JavaWeb课设源码拆解:架构、安全与部署

📅 2026/10/31 21:53:35 | 华诺云谱 👁 阅读
二手闲置交易系统JavaWeb课设源码拆解:架构、安全与部署
简介一份面向JavaWeb期末大作业与课程设计的二手闲置物品交易系统源码适合计算机及相关专业学生直接借鉴与二次开发。项目采用Spring Boot整合前端静态页面围绕商品与用户等核心业务实现涵盖列表展示、信息维护、异步交互等典型场景代码结构规整可满足课程设计答辩展示或实训报告撰写需求。压缩包共352个文件以Java源码、XML配置、HTML/CSS/JS前端资源为主另含图片、字体图标及项目配置文件整体大小45.54MB目录层级清晰便于按Controller、Service、Dao等层次阅读。内容预览中可见GoodsController、UserController、UserServiceImpl等核心类以及DruidConfig数据库连接池配置能帮助理解请求路由、业务封装与数据持久化流程。目前已有4460人学习下载适合希望快速搭建JavaWeb项目并打通前后端逻辑的同学。1. 二手闲置物品交易系统不是玩具课设拆源码前先想清楚这三个问题「二手闲置物品交易系统」是 JavaWeb 课程设计里出现频率极高的一道题但大多数同学交上去的版本只有「能跑」的水平代码全部堆在一起业务逻辑和页面跳转搅成一团答辩被老师追问两句设计思路就露馅。我这次拆的这套源码从类文件构成看Spring Boot 启动类 Application、Controller/Service/Entity 三层结构、Druid 连接池显式配置、AjaxResult 统一返回封装、DateUtil 时间工具类全齐了几乎把 JavaWeb 课设能考的要点都串在了一个交易系统里。对正在做课设的人来说这套源码真正的价值不在于「能跑」而在于「能改」。你接手之后只需要回答三个问题分层边界怎么划、数据库表怎么设计才能支撑买卖双方的数据流转、改哪些地方能在期末答辩时讲出设计感。这篇笔记就按这个顺序拆从项目结构一路拆到部署踩坑最后给你两个可以直接写进答辩词的改造点。2. 项目骨架拆解Spring Boot Druid 三层架构的分工与边界2.1 从 class 文件反推项目结构十个类之间的调用关系拿到这套源码压缩包先别急着启动。把 class 文件列表过一遍项目长什么样基本就清楚了Application 是 Spring Boot 的启动入口GoodsController 和 UserController 是 HTTP 请求入口GoodsServiceImpl 和 UserServiceImpl 是业务逻辑落点Goods 和 User 是对应数据库表的实体类DruidConfig 专门做数据源配置AjaxResult 统一封装接口返回DateUtil 负责时间格式化。这个构成说明作者没有把所有代码塞进一个类而是按标准工程分包——这在课设评分里本身就是个隐性加分项。源码目录在 IDEA 里通常长什么样常见做法是分五个包controller 放控制器service 放接口和 impl 实现类entity 放实体config 放配置类util 放工具类。如果用了 MyBatis还会多一个 mapper 包放数据库访问接口。这套源码从类名推断大概率遵循这个结构你导入后先在 pom.xml 里确认依赖再逐个包扫一遍十分钟内就能把代码地图建起来。Controller 层的职责边界只有三条接收参数、调用 Service、把结果包成 AjaxResult 返回。Service 层管业务规则——商品发布前校验登录态、商品下架前校验归属、状态流转是否合法这些规则绝对不允许出现在 Controller 里。实体类只做数据库字段映射不写业务方法。有些同学的实体类里塞了一堆 getter/setter 之外的业务方法答辩时被问「你分层的依据是什么」回答一旦含糊前面代码写得再好也被打折扣。// 典型的三层调用Controller 尽量保持轻薄 RestController RequestMapping(/goods) public class GoodsController { private final GoodsService goodsService; public GoodsController(GoodsService goodsService) { this.goodsService goodsService; } PostMapping(/publish) public AjaxResult publish(RequestBody Goods goods, HttpSession session) { Object userId session.getAttribute(userId); if (userId null) { return AjaxResult.error(请先登录); } return goodsService.publish(goods, (Integer) userId); } }这段代码里有两个细节值得展开。第一个是构造器注入通过构造方法把 GoodsService 传进来而不是在字段上加 Autowired这样单元测试时可以直接 new 一个 mock 的 Service 传进去依赖关系在编译期就被强制约束字段注入做不到这一点。第二个是登录态处理发布商品所需的 userId 从 Session 里取而不是让前端把 userId 放在请求体里提交——如果前端能传 userId任何人都能冒充卖家发布商品这是典型的越权漏洞。2.2 DruidConfig为什么连接池配置是课设里的隐藏加分项很多课设项目图省事在配置文件里写三行 spring.datasource 就完事根本不单独定义数据源。这套源码专门写了一个 DruidConfig说明作者在这块是有意识加分的。Druid 是阿里巴巴开源的数据库连接池相比 Spring Boot 默认的 HikariCP它在课设场景里最实用的点是自带一个可视化监控页能实时看到活跃连接数、执行耗时最长的 SQL、连接池占用情况。答辩现场打开 localhost:8080/druid指着监控页说出「我发现商品列表的查询耗时最高给关键字列加了索引」这个叙述比你空口说「我做了性能优化」有说服力得多。监控数据摆在那里老师能看到系统是活的。Configuration public class DruidConfig { Bean ConfigurationProperties(prefix spring.datasource) public DataSource druidDataSource() { return new DruidDataSource(); } Bean public ServletRegistrationBeanStatViewServlet statViewServlet() { ServletRegistrationBeanStatViewServlet bean new ServletRegistrationBean(new StatViewServlet(), /druid/*); bean.addInitParameter(loginUsername, admin); bean.addInitParameter(loginPassword, 123456); return bean; } Bean public FilterRegistrationBeanWebStatFilter webStatFilter() { FilterRegistrationBeanWebStatFilter bean new FilterRegistrationBean(new WebStatFilter()); bean.addUrlPatterns(/*); bean.addInitParameter(exclusions, *.js,*.css,/druid/*); return bean; } }三个配置各有各的讲究。StatViewServlet 是监控页入口URL 映射成 /druid/* 之后浏览器就能直接访问loginUsername 和 loginPassword 是监控页登录凭证建议在交付前改成自己的账号不要留着默认的 admin。WebStatFilter 负责采集 SQL 统计里面的 exclusions 参数是排除静态资源——如果忘了排除 js、css监控页会被静态资源请求刷屏真正有价值的 SQL 统计反而找不到。还有一个容易忽略的联动如果项目里自己写了登录拦截器要在拦截器注册时把 /druid/* 排除掉否则两套过滤器规则冲突监控页会白屏。连接池参数方面initialSize 和 minIdle 建议保持一致都设 5maxActive 设 20 对课设规模绰绰有余。不要迷信大数字把 maxActive 调到 200 并不会让系统变快反而会让连接池维护一堆空闲连接启动变慢、数据库压力变大。2.3 AjaxResult 统一返回格式让前后端联调少一半口水没有统一返回结构的项目接口风格能乱到什么程度有的接口返回对象有的返回 String出错时有的返回 null、有的直接抛异常堆栈。前端拿到这些每个接口都要单独写解析逻辑联调基本靠吼。AjaxResult 就是给所有接口套一个统一的壳后端只要守住约定前端一个拦截器就能处理所有响应。public class AjaxResult extends HashMapString, Object { public static AjaxResult success(Object data) { AjaxResult result new AjaxResult(); result.put(code, 200); result.put(msg, 操作成功); result.put(data, data); return result; } public static AjaxResult error(String msg) { AjaxResult result new AjaxResult(); result.put(code, 500); result.put(msg, msg); return result; } }为什么让它继承 HashMap 而不是定义 code、msg、data 三个成员变量因为继承 HashMap 后想给返回包加一个字段直接 put 就行不用改类定义扩展成本最低。代价是没有类型约束结构全靠约定。而约定的承载者就是前端 axios 拦截器// 前端统一处理code 200 才走进成功回调 axios.interceptors.response.use( response { if (response.data.code 200) { return response.data.data; } alert(response.data.msg); return Promise.reject(response.data.msg); }, error { console.error(请求异常:, error); } );这段拦截器里藏着前后端协作的三个约定code 为 200 才进入成功分支非 200 统一弹提示提示文案由后端维护在 msg 里前端不写死任何业务提示语后端想改提示内容只动 AjaxResult.success 或 error 的 msg全站生效。拿到这套源码后第一件事就是检查是不是所有 Controller 方法都返回了 AjaxResult——漏掉一两个接口前端就要为它们单独开特别处理分支联调消耗立刻上来了。3. 数据库设计先行用户表、商品表与状态机的取舍3.1 从实体类反推表结构Goods 与 User 的核心字段数据源配置好了下一个问题是表怎么建。交易系统的核心是「一个人把闲置挂出来另一个人看到后联系或购买」数据结构落在两张主表上用户表 user 和商品表 goods。之后如果要加订单、收藏、评论每张新表都是围绕这两个主体扩展的。用户表的核心字段包括id 主键、username 登录名、password 加密后的密码、nickname 昵称、phone 联系电话——二手交易场景里买家需要联系卖家这个字段必须要有、avatar 头像地址、create_time 注册时间。商品表的核心字段是id、user_id 卖家外键、title 标题、description 描述、price 价格、images 图片列表、status 商品状态、view_count 浏览量、create_time 和 update_time。CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL COMMENT 卖家ID, title VARCHAR(100) NOT NULL COMMENT 标题, description TEXT COMMENT 商品描述, price DECIMAL(10,2) NOT NULL COMMENT 价格, images VARCHAR(500) DEFAULT COMMENT 逗号分隔的图片路径, status TINYINT DEFAULT 0 COMMENT 0在售 1已下架 2已售出, view_count INT DEFAULT 0 COMMENT 浏览量, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT二手商品表;这张表有三个设计决策值得在答辩时展开讲。第一个是 price 用 DECIMAL(10,2) 而不是 FLOAT因为浮点数在计算机里是近似存储0.1 加 0.2 会得到一长串小数涉及金额的字段必须用精确小数类型这是行业规范。第二个是 status 用 TINYINT 而不是字符串「在售」「已下架」「已售出」存成字符串不仅占空间状态迁移还不好控制下面一节细讲。第三个是 images 用逗号拼接存储多个图片路径课设阶段比单独建商品图片表简单得多等你有按图片维度做统计的需求时再拆表不迟。导入数据库时有两点提醒user 和 goods 不是 MySQL 保留字但建表语句里最好加反引号避免版本差异带来的意外问题如果库里已有同名表先 DROP 再执行否则重复建表会报错。编码必须用 utf8mb4只用 utf8 在存 emoji 表情时会报错现在的商品描述里带 emoji 太常见了。3.2 状态机设计商品四态之间的合法流转二手商品从发布到成交会先后经历几个状态。简单系统通常定义三个0 在售、1 已下架卖家主动操作、2 已售出。严谨一点的会多加一个 -1 表示管理员封禁。状态机设计的核心不是把状态定义出来而是把「哪些跳转是合法的」在代码里写死。// 合法的状态流转0在售 - 1下架/2售出1下架 - 0再次上架 // 已售出是终态不能改回任何状态 private boolean canTransit(int from, int to) { if (from 0) { return to 1 || to 2; } if (from 1) { return to 0; } return false; }为什么要单独写这个方法课设里最常见的翻车现场就是状态被乱改SQL 直接写UPDATE goods SET status 2 WHERE id ?任何一个知道接口的人都能把已售出的商品改回在售。把状态迁移规则收敛到 Service 层一个方法里所有状态变更入口都先走它再落库状态就乱不了。这个方法本身也适合在答辩时聊聊状态模式——不用真的引入二十几个类至少你能说清楚「有限状态」这个概念。状态校验之外更隐蔽的是所有权校验。修改商品、下架商品、删除商品这三个接口很容易只校验「商品存在」却漏掉「商品属于当前登录用户」。正确的接口调用链是根据 goodsId 查出商品再比对商品 user_id 与 Session 里的 userId不一致直接返回「无权操作」。这个校验在 4.2 节的代码里有完整实现到时候可以直接抄。提示状态机校验放在 Service 层而不是依赖前端按钮显隐。前端隐藏按钮只是体验优化后端校验才是安全底线。3.3 DateUtil时间字段在列表与详情里的处理DateUtil 这个工具类看着不起眼做列表页时你一定会用到它。数据库存的是 DATETIMEJava 里对应 LocalDateTime浏览器要显示成「2024-12-20 14:30」或是「3 分钟前」中间隔着两层转换。如果没有统一工具类每个页面各自格式化一遍格式迟早会乱。public class DateUtil { private static final DateTimeFormatter FORMATTER DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm); public static String format(LocalDateTime time) { if (time null) return ; return FORMATTER.format(time); } public static LocalDateTime parse(String str) { if (str null || str.isEmpty()) return null; return LocalDateTime.parse(str, FORMATTER); } public static String relativeTime(LocalDateTime time) { Duration d Duration.between(time, LocalDateTime.now()); if (d.toMinutes() 1) return 刚刚; if (d.toMinutes() 60) return d.toMinutes() 分钟前; if (d.toHours() 24) return d.toHours() 小时前; return FORMATTER.format(time); } }建议使用 JDK 8 的 java.time 包而不是 java.util.Date前者类型安全、不可变没有 Date 那一堆 deprecated 方法。format 负责标准格式输出relativeTime 用于列表页展示「刚刚/分钟前/小时前」让页面有真人使用的感觉这个细节做到了答辩观感立刻不一样。建表时 create_time 直接给 DEFAULT CURRENT_TIMESTAMPupdate_time 加上 ON UPDATE CURRENT_TIMESTAMP应用层就不用手动维护这两个字段。这样插入、更新自动处理时间也就不会出现「某个功能明显改过但 update_time 没变」的翻车现场。4. 核心功能实战从商品发布的完整链路看业务边界与安全校验4.1 登录注册Session 方案的选择与密码处理二手交易系统里商品的发布、下架、购买全部围绕「用户身份」展开登录注册是第一道闸门。UserController 里通常实现 login、register、logout 三个接口。登录成功的本质是服务端在 Session 里写入用户标识客户端拿到 Session Cookie后续每次请求都带着这个会话身份。用户注册的安全细节集中在密码上。密码不能明文存数据库这是 JavaWeb 课设老师最爱问的问题。常见的处理方向是存储前做不可逆哈希MD5 加盐或者 BCrypt。MD5 有彩虹表风险稳妥做法直接上 BCryptSpring Security 里自带 BCryptPasswordEncoder。不过课设为了轻量很多人用 MD5 加固定盐也能被接受关键在于能解释清楚「为什么服务端拿不到用户真实密码」——数据库泄露了攻击者也无法反推出明文。PostMapping(/register) public AjaxResult register(String username, String password) { if (username null || username.length() 3) { return AjaxResult.error(用户名至少 3 位); } if (password null || password.length() 6) { return AjaxResult.error(密码至少 6 位); } User exist userService.findByUsername(username); if (exist ! null) { return AjaxResult.error(用户名已存在); } User user new User(); user.setUsername(username); user.setPassword(PasswordUtil.encode(password)); userService.register(user); return AjaxResult.success(null); }这段注册代码体现三层约束参数校验前端后端都要做前端为了体验、后端为了安全——不少课设的思路是前端校验了后端就不管这是大忌因为接口是公开的绕过前端直接发请求太容易了唯一性检查放在插入之前否则数据库里出现重名用户登录逻辑就开始出乱子密码在进入 Service 之前就完成加密Controller 这一层永远接触不到明文密码后面打日志也不会把密码带进日志文件。登录接口的思路对称取到用户名密码查用户比对哈希写 Session返回 AjaxResult。比对用的加密函数必须和注册时完全一致否则每次登录都提示「密码错误」。这种「本地能跑、换个环境密码全部失效」的玄学问题根源多半就是两套代码用了不同的加密方式或者注册时每次随机盐导致哈希对不上。4.2 商品发布与修改参数校验和越权防护两手抓商品是系统的核心资产GoodsController 的接口设计直接决定系统的安全下限。发布一个二手商品的完整链路是前端提交 title、description、price、images后端从 Session 取当前用户拼装 Goods 对象落库。这段逻辑的关键点只有一个——user_id 必须从 Session 取前端传的 user_id 一律不认。真正容易出问题的是修改接口。没有经验的实现长成这样// 反面教材只校验参数不校验归属 PostMapping(/update) public AjaxResult update(Goods goods) { goodsService.update(goods); return AjaxResult.success(null); }这段代码有什么问题任何登录用户只要构造一个带id3price0.01的请求就能把别人的商品改成自己的价格。前端确实只在卖家页面显示编辑按钮但接口是公开的F12 打开控制台手动发一个请求就能绕过页面。正确实现必须加四道防御PostMapping(/update) public AjaxResult update(RequestBody Goods goods, HttpSession session) { Integer userId (Integer) session.getAttribute(userId); if (userId null) { return AjaxResult.error(请先登录); } Goods exist goodsService.findById(goods.getId()); if (exist null || !exist.getUserId().equals(userId)) { return AjaxResult.error(无权修改该商品); } if (goods.getPrice() null || goods.getPrice().compareTo(BigDecimal.ZERO) 0) { return AjaxResult.error(价格不合法); } goods.setUserId(userId); goodsService.update(goods); return AjaxResult.success(null); }四个动作缺一不可先从 Session 取 userId 确认登录态拿不到直接拒绝再根据商品 id 查原商品比对归属不是自己的商品一律返回无权操作价格做基础合法性校验杜绝负数或零元商品最后goods.setUserId(userId)一行强制覆盖——即使前端伪造了 userId 字段到这里也会被覆盖成当前登录用户越权路径彻底堵死。这套组合打完「越权修改他人商品」这个课设高频漏洞基本没有缺口。4.3 Service 层的事务与状态校验三条写操作必须同生共死Controller 层校验完参数业务规则进入 Service 层。一个商品成交的完整链路往往涉及三张表的写操作商品表更新状态为已售出、订单表插入一条订单记录、用户表给买家加交易计数。三条写操作必须以事务方式执行要么全成功、要么全回滚不然会出现「库存扣了但订单没生成」这类数据不一致。Transactional public void completeTrade(Integer goodsId, Integer buyerId) { Goods goods goodsMapper.findById(goodsId); if (goods.getStatus() ! 0) { throw new RuntimeException(商品当前不可购买); } goodsMapper.updateStatus(goodsId, 2); orderMapper.insert(new Order(goodsId, buyerId, goods.getUserId(), goods.getPrice())); userMapper.increaseTradeCount(buyerId); }这里的 Transactional 注解由 Spring 拦截方法执行进入前开启事务方法正常结束提交抛出 RuntimeException 自动回滚。这个注解有两个经典的大坑。一个是方法内部 try-catch 把异常吞掉事务管理器看不到异常就不会回滚——正确做法是 catch 后重新抛出或者干脆不 catch 让异常往外冒。第二个是同一个类里自调用带事务的方法即 Controller 调 list 方法list 方法里又调了 completeTrade事务会静默失效因为 Spring 事务依赖代理对象自调用不经过代理。状态校验放在 Service 层而不是直接写 SQL 条件原因是「可读的报错信息」。SQL 里写UPDATE goods SET status2 WHERE id? AND status0状态不对时只会返回影响行数 0前端拿到的反馈是「操作失败」却不知道具体为什么。在 Service 层预先读一次商品、判断状态、抛出明确异常前端就能把「商品已下架或已售出」直接展示给用户。如果系统里有多个入口都可能触发状态变更——发布、下架、购买、管理员操作——把这个状态机规则收敛到 Service 层一个方法里是课设能稳定运行的底气。5. 部署与避坑IDEA 启动到 MySQL 配置的五个真实踩坑从拿到源码到完整跑起来花掉的时间大概率不是业务代码而是这些不起眼的环境问题。下面五条是我拆这类 JavaWeb 课设时遇到最多的情况按出现频率排序。5.1 现象启动时报数据库连接失败或 Access denied启动 Spring Boot 项目控制台最先报错的大多是数据库连接Cannot connect to MySQL server on localhost:3306或者Access denied for user rootlocalhost。原因基本锁定三个方向本地 MySQL 没装或服务没启动application.properties 里的连接配置和当前环境不匹配MySQL 大版本和驱动版本不兼容认证协议对不上。其中第三个最隐蔽MySQL 8.0 默认使用 caching_sha2_password 认证需要 mysql-connector-java 8.0 以上的驱动才支持如果 pom 里依赖还停留在 5.1.x账号密码全对也会报 Access denied。解决路径是固定的先在命令行敲mysql -u root -p验证账号密码能连通通了再看配置文件里的地址、端口、库名是否和本地一致最后打开 IDEA 的 Maven 面板刷新依赖确认驱动版本和 MySQL 大版本匹配。配置文件里的 url 建议补全三个参数spring.datasource.urljdbc:mysql://localhost:3306/second_hand?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 spring.datasource.usernameroot spring.datasource.password你的密码useSSLfalse 关闭 SSL 告警避免控制台刷屏serverTimezoneAsia/Shanghai 解决时区导致的时间错乱characterEncodingutf8 保证中文不乱码。这三个参数值得写进你自己的标准配置模板每次复制粘贴能少踩一半数据库连接坑。5.2 现象Druid 监控页 404 或白屏Druid 依赖加了、DruidConfig 也写了访问 localhost:8080/druid 还是 404这种白屏问题的排查顺序很固定确认 pom.xml 里 druid 相关依赖真的存在——有人从网上抄了配置类却忘了加依赖这是最常见的低级翻车确认 StatViewServlet 的 url-pattern 和访问地址一致配的是 /druid/* 访问就得带分号后的斜杠最后检查项目自定义拦截器有没有把 /druid/* 给拦了。最后这条最隐蔽。Spring Boot 项目如果写了一个 WebMvcConfigurer 做登录拦截通常会用addPathPatterns(/**)把所有路径都揽进来忘了排除 /druid/*监控页就永远被登录校验挡在外面。解决方式是在拦截器注册时显式追加.excludePathPatterns(/druid/*)必要时连同静态资源一起排除否则页面样式加载不出来看着跟白屏一样。提示如果项目里没有自定义拦截器但页面仍然白屏优先检查 druid-spring-boot-starter 的版本和 Spring Boot 版本兼容性版本跨度太大的组合行为不可预期。5.3 现象登录成功但后续接口提示请先登录页面上明明跳转到了登录后的首页点「发布商品」却弹出「请先登录」。这个问题本质上是 Session 没有在第二次请求时被带回服务端。先说最常见的前端原因axios 默认不携带跨域 Cookie。如果你做的是前后端分离前端跑在 5173 或 8081 这类端口后端在 8080请求属于跨域浏览器不会自动附带 Session Cookie。解决方式是在 axios 配置里加withCredentials: true同时后端开启 CORS 时设置allowCredentials(true)并且 allowOrigin 不能是*。另一个真实场景是登录机制混用前端把 token 存到了 localStorage后端接口却在从 Session 取 userId或者后端登录接口发了 Token但没有把它放进 Session。两种机制互相打架登录状态时好时坏。课设项目建议从一而终要么全走 Session要么全走 Token不要一半一半排查成本会直线上升。5.4 现象刷新页面后商品图片全没了图片上传功能做完第一次测试就发现刷新之后图片丢了。问题出在图片存储方式数据库里存的是相对路径图片实际被写进项目的 target/classes 目录IDEA 每次 clean 构建都会清掉这个目录图片随之灰飞烟灭。另一种常见情况是上传路径写死成绝对路径比如 D:/project/upload数据库里也存绝对路径换一台电脑部署所有历史图片路径全部失效。解决思路数据库只存相对路径上传根目录指向操作系统上的独立目录比如 /home/user/uploads然后在 Spring Boot 配置项里定义一个file.upload-dir参数统一控制这样换环境时只改一个配置就能把整个图片路径拉齐。5.5 现象换个浏览器或手机访问页面样式乱掉前后端一体的课设项目JSP 或 Thymeleaf静态资源路径用相对路径是排在第一位的样式问题。场景很典型首页 index 下写link hrefcss/style.css能正常加载但页面跳到 /goods/list 之后浏览器解析相对路径的基准变成了 /goods/于是请求 /goods/css/style.css404样式全丢。解决方式统一改成根路径引用JSP 用${pageContext.request.contextPath}拼接Thymeleaf 用th:href{/css/style.css}。改完静态资源路径再检查后端有没有给静态资源放行——Spring Boot 的登录拦截器如果不排除 /css/、/js/、/images/**会出现「登录后页面正常、未登录时样式全崩」的现象这个坑在带校验拦截器的项目里几乎必踩一次。6. 拿来改造成自己的课设一个低成本高回报的页面与答辩清单6.1 加一个「我的发布」页面顺手点满三个考核点原项目可能只有商品列表、详情、发布三个页面缺一个「卖家管理自己商品」的入口。加一个 /goods/my 接口按当前登录用户查询前端复用列表页模板只是请求参数带 userId——这个改动在答辩时够讲三分钟数据范围隔离、登录态复用、页面复用。GetMapping(/my) public AjaxResult myGoods(HttpSession session) { Integer userId (Integer) session.getAttribute(userId); if (userId null) { return AjaxResult.error(请先登录); } return AjaxResult.success(goodsService.listByUserId(userId)); }配套的 Mapper SQL 记得把 status 筛选条件留出来下架的商品也要能查到不然商品下架后你就再也无法操作它了这是设计里最容易忽略的一个闭环。6.2 答辩前的最后检查清单拿到这套源码后建议你自己先在空白环境里完整走一遍下面的流程项目从零启动成功数据库脚本在 README 里写明版本和导入顺序所有接口返回统一走 AjaxResult密码不以明文存在数据库商品发布、编辑、下架三个接口都做归属校验静态资源全部根路径引用注册、登录、发布、下架完整走一遍确认 Session 不丢。如果答不上来「你为什么这么设计」多从状态机和安全校验这两个点准备它们最容易打开老师的话匣子。每次答辩前我也会拿着这套源码重跑一遍环境——不是因为它多高深而是连接池配置、图片目录、Session 传递这些坑在每个项目里都会换个样子再出现一次。从那以后我每次部署课设项目都强制走一遍上面的流程答辩前一天重点核对数据库脚本和越权接口这两项。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑