最近被问得最多的一个选题,就是基于Java SpringBoot的二手图书交易系统,也就是大家常说的图书商城。这个项目在毕业设计和课程设计里都属于“万金油”类型:它不像纯商城系统那样千篇一律,又比学生管理系统多了一层真实的业务纵深,用户、图书、订单、交易状态这些环节串起来,刚好能撑起一篇像样的论文和一次完整的答辩演示。
我今天想聊的这套项目,是比较完整的一套资料,包含源码、文档、运行视频和讲解视频。很多人拿到手第一反应是“太好了,终于不用自己写了”,但我更想说的是,这种项目真正值钱的地方不在于“能跑”,而在于你能不能在答辩时把每一步讲清楚。市面上二手上交易的毕设源码很多,但大多数存在两个问题:要么代码风格混乱、没有注释,要么文档和代码对不上。这篇文章我会从项目定位、技术选型、数据库设计、核心业务实现、运行部署到答辩亮点扩展,把整个项目从头到尾拆开讲一遍,顺便把那些只有真正动手跑过才会踩到的坑也说清楚。
1. 项目定位与设计思路:为什么选这个题目,图的是什么
1.1 选题逻辑与核心需求拆解
先聊“为什么值得做”。二手图书交易系统本质上是C2C模式的二手交易平台,但业务范围收窄到了图书这个品类。它的核心流程非常清晰:用户注册登录后,可以把自己闲置的图书上架出售,也可以浏览其他用户发布的图书,看中了就加入购物车或直接下单购买,订单产生后卖家和买家之间完成交易,后台管理员负责审核图书、管理用户和处理订单。
相比纯粹的单向购买商城,二手图书系统的关键在于“每个人既是买家又是卖家”的双重身份,这让权限设计、数据归属、订单关联都变得更有讲究。答辩时老师非常喜欢问这类问题:“一个用户下单买的书,和你自己发布的图书,在数据上是怎么区分的?”“如果一本书正在交易,别人还能不能下单?”这些问题背后考察的就是你对业务模型的真正理解。
从功能模块来看,这个系统大概可以拆成两块。用户端需要注册登录、图书检索、分类浏览、发布图书、购物车、下单支付、订单管理、收藏和个人信息维护;管理端需要图书审核、分类管理、用户管理、订单管理、数据统计。有的版本还会带轮播图管理、公告发布这类扩展功能,属于锦上添花。理解了这些需求,再去看源码里的包结构、表结构和页面跳转,思路就会清晰很多,而不是对着代码一头雾水。
1.2 技术选型背后的为什么
这套项目用的是SpringBoot,这是目前Java后端最主流的选择。很多人在答辩时会被问“为什么用SpringBoot而不用传统的SSM”,这个问题其实是在考你对框架演进的理解。传统SSM项目需要大量的XML配置,整合Spring、SpringMVC和MyBatis三个框架,光配置文件就能写一箩筐;SpringBoot的核心价值是自动装配和约定优于配置,内置了Tomcat,打成一个jar包就能直接跑,开发效率高出一大截。
持久层方面,常见的方案有MyBatis和MyBatis-Plus。这套项目用MyBatis-Plus的情况比较多,因为它把单表CRUD做了极大简化,不需要手写大量重复的SQL,BaseMapper里直接提供了增删改查方法,分页插件也是一行配置的事情。不过答辩时要小心,老师如果追问“MyBatis-Plus和MyBatis的区别”,你得能说出MyBatis需要手写XML里的SQL和ResultMap,而MP在MyBatis之上做了增强,本质还是MyBatis,不是替代关系。
前端方案有两种常见形态。一种是纯粹的Thymeleaf服务端渲染,登录页、首页、后台页面都由后端模板引擎输出,数据通过Model传递,适合一个人快速搞定,部署也简单;另一种是前后端分离,前端用Vue或若依框架,后端提供RESTful接口,数据用JSON交互。这两种方案各有侧重点。如果你的毕业设计重点是后端,选Thymeleaf版本压力更小;如果你想展示自己的全栈能力,前后端分离的版本在答辩时更抓眼球。拿到源码后先确认自己的项目属于哪种形态,后面启动方式完全不同,这点非常重要。
2. 核心功能与数据库设计:表结构建好了,系统就成了一半
2.1 功能模块拆解
在真正开始看代码之前,我建议先拿出一张纸,把这个系统的功能树画出来。用户端围绕“找书—买书—卖书”这条主线,衍生出注册登录、首页展示、图书搜索、分类筛选、图书详情、发布闲置、购物车管理、下单结算、订单查询、收藏管理这些功能;管理端围绕“审核—管理—统计”这条支线,包括登录鉴权、图书审核、图书上下架、分类维护、用户禁用、订单跟踪和销售统计。
这里有一个非常容易忽略但又经常被问到的点:图书审核。二手平台不同于自营电商,用户发布的商品需要管理员审核后才能展示在前台,这个状态字段贯穿了整个流程。很多学生做项目时会漏掉这个环节,导致用户发布的任何图书直接进入在售列表,这在答辩时会被老师抓住漏洞。合格的系统里,图书状态至少应该有:待审核、在售、已下架、已售出这几种,管理员对待审核图书点击通过后,前台才可见。
功能模块可以用下面的表格快速对照检查,拿到源码后逐项验证,就知道这套项目完整度如何。
| 模块 | 用户端功能 | 管理端功能 |
|---|---|---|
| 用户模块 | 注册、登录、个人资料、我的发布 | 用户列表、禁用/启用 |
| 图书模块 | 发布图书、浏览详情、搜索筛选 | 图书审核、上下架、分类管理 |
| 交易模块 | 购物车、下单、订单查看 | 订单管理、状态跟踪 |
| 其他模块 | 收藏、评论、公告浏览 | 数据统计、公告发布 |
2.2 表结构设计的几个关键点
数据库是这个项目的骨架,表设计好不好,直接影响业务代码的复杂度。核心表基本是这几张:用户表user、图书表book、分类表category、订单表orders、订单明细表(如果订单可以包含多本书)、购物车表cart、收藏表favorite、评论表comment。如果后面扩展了公告或轮播图,会加上banner、notice这类表。
用户表字段比较常规:id、username、password、nickname、avatar、phone、email、create_time、status。密码字段这里必须多说一句,规范的实现不应该存明文,应该用BCrypt或MD5加盐加密。很多Java毕设里直接明文存密码,属于减分项,答辩时老师翻数据库一眼就能看出来。
book表是整个系统的核心,字段设计要重点看。大致包括id、seller_id(卖家用户id)、category_id、title、author、publisher、isbn、original_price、sell_price、quality(图书成色)、description、cover_image、status、view_count、create_time。其中seller_id是最关键的外键关联,它把图书和发布者绑定在一起,所有“我的发布”列表都依赖这个字段查询。封面图字段建议存URL路径而不是二进制内容,图片文件单独上传到服务器目录或对象存储,数据库只存地址。
orders表有一个很容易被忽略但很重要的设计点:商品快照。什么意思呢?用户下单时,图书的标题、价格这些信息应该被冗余复制到订单表里。因为卖家可能在下单后修改价格、下架图书甚至删除图书,如果订单表只有book_id这一个外键,订单历史数据可能跟着变没或变得对不上。保存快照后,即使图书被下架,买家依然能在订单里看到自己买的是哪本书、多少钱,这在真实电商系统里是最基本的做法,也是答辩时很加分的一个细节。
订单表建议包含:id、order_no(唯一订单号)、buyer_id、seller_id、book_id、book_title、book_cover、book_price、total_amount、status、pay_time、create_time。状态字段推荐用tinyint或varchar混合方式,代码里用枚举常量去表示,比散落的魔法数字好维护得多。关于订单状态机的问题,我在下一节专门说。
3. 关键业务流程与核心实现:下单、审核、鉴权这些硬骨头
3.1 用户发布图书与管理员审核的实现思路
发布图书是卖书路径的起点。前端页面是一个表单加图片上传,后端接收后要做两件事:校验字段和保存数据。校验包括书名不能为空、价格必须大于0、描述长度不能超限,这些用Spring的@Valid注解加上实体类上的@NotBlank、@NotNull就能搞定,比在Service里手动if判断优雅很多。
图片上传是一个高频面试点。简单的做法是配置一个本地上传路径,MultipartFile接收到文件后,用UUID重命名,保持原扩展名不变,然后写到服务器的指定目录,再把访问路径拼出来存入数据库。这里有两个坑要注意:第一是IDEA开发环境和Linux服务器的本地路径不一样,建议在application.yml里配置一个自定义的file.upload-path属性,不要硬编码;第二是域名访问路径,如果用了本地路径,图片可能因为静态资源映射没配置而加载不出来,需要在WebMvcConfigurer的addResourceHandlers方法里做映射。
管理员审核功能相对简单,本质上是对book记录做状态更新。管理员进入待审核列表,点击通过就把status从待审核改成在售,点击拒绝就改成下架并可以填一个拒绝原因。这一块的业务逻辑虽然简单,但权限控制不能少,审核接口必须放在管理员身份才能访问的接口路径下,这点是后续拦截器配置的重点。
3.2 下单流程怎么处理才稳:状态机、并发与事务
下单是整个系统业务逻辑最重的环节。正常的流程是:用户从购物车或图书详情页发起购买,系统检查图书当前状态是否在售,如果图书已经下架或者被别人抢先下单,就要返回一个友好的提示。
这里有一个比较典型的并发场景:同一本书只有一个库存,两个人同时下单怎么办。图书不同于普通商城商品,一本书就是唯一的一件。最朴素的实现是在创建订单前先查询book状态,状态为在售就更新为已售,再创建订单。但两个并发请求同时读到在售状态,就可能出现同时更新成功的问题,这个叫超卖。解决办法是对book表加一个version字段做乐观锁,或者在更新时带条件,例如比较下面这段逻辑。
@Transactional public boolean createOrder(Long buyerId, Long bookId) { // 1. 查出当前图书信息 Book book = bookMapper.selectById(bookId); if (book == null || !BookStatus.ON_SALE.equals(book.getStatus())) { throw new BizException("图书已下架或已售出"); } // 2. 使用条件更新,防止并发重复购买 int rows = bookMapper.updateStatusByIdAndStatus(bookId, BookStatus.ON_SALE.getCode(), BookStatus.SOLD_OUT.getCode()); if (rows == 0) { throw new BizException("手慢了,图书刚刚被买走了"); } // 3. 创建订单,记录商品快照 Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setBuyerId(buyerId); order.setSellerId(book.getSellerId()); order.setBookId(bookId); order.setBookTitle(book.getTitle()); order.setBookCover(book.getCover()); order.setBookPrice(book.getSellPrice()); order.setStatus(OrderStatus.WAIT_PAY.getCode()); orderMapper.insert(order); return true; }这个方法上加了@Transactional,保证“更新图书状态”和“插入订单记录”要么同时成功,要么同时回滚。条件更新本身就是一个行级锁,只有图书状态仍为在售时才能更新成已售,这样并发请求只能有一个成功,省掉了手写乐观锁的复杂度。订单号建议用时间戳加随机数生成,或者用雪花ID,注意前端展示时Long类型精度丢失问题,后续部署部分我会说。
订单状态机是另一个重点。一个二手图书订单的完整生命周期大概是:待付款、已付款(待发货)、已发货、确认收货、已完成,以及旁边的取消、退款。状态流转可以用一个枚举类统一管理,例如OrderStatus里定义常量,禁止在代码里散写数字。管理端和用户端列表页都依赖状态字段做筛选,设计清楚了这个字段,后面写查询会省很多事。
3.3 检索与分类筛选的实现
图书检索这个功能看起来简单,却是日常使用频率最高的入口,也是MyBatis-Plus体现优势的地方。搜索页通常支持按书名或作者做模糊匹配,叠加分类、价格区间、图书成色和排序方式。用LambdaQueryWrapper可以很优雅地拼条件。
public PageResult<Book> searchBook(String keyword, Long categoryId, BigDecimal minPrice, BigDecimal maxPrice, String sortType, int page, int size) { Page<Book> pageParam = new Page<>(page, size); LambdaQueryWrapper<Book> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Book::getStatus, BookStatus.ON_SALE.getCode()); if (StringUtils.hasText(keyword)) { wrapper.and(w -> w.like(Book::getTitle, keyword) .or().like(Book::getAuthor, keyword)); } if (categoryId != null) { wrapper.eq(Book::getCategoryId, categoryId); } wrapper.between(minPrice != null && maxPrice != null, Book::getSellPrice, minPrice, maxPrice); wrapper.orderByDesc("new".equals(sortType), Book::getCreateTime); wrapper.orderByDesc("priceDesc".equals(sortType), Book::getSellPrice); wrapper.orderByAsc("priceAsc".equals(sortType), Book::getSellPrice); return new PageResult<>(bookMapper.selectPage(pageParam, wrapper)); }需要注意一下between方法的使用,如果minPrice和maxPrice只传了一个,直接用between会查到一堆不符合预期的数据。稳妥的做法是先判断两者都非空再拼该条件,或者用ge和le分别追加,这样条件拼接更灵活。模糊查询里关键字前后带%会导致索引失效,但在这种数据量并不大的毕业设计场景下完全够用,答辩时如果被问到性能问题,可以顺着往Elasticsearch搜索方案上引,这会成为一个很好的扩展点。
3.4 登录鉴权与安全细节
登录鉴权有两条技术路线:Session方式和JWT方式。早期SSM项目大多用Session,现在SpringBoot项目普遍倾向用JWT无状态鉴权。JWT的思路是:用户登录成功后,后端把用户id、用户名、角色等信息加密生成一个token返回给前端,前端每次请求在Authorization头里带上这个token,后端通过拦截器解析并校验有效性。
拦截器需要区分用户端接口和管理端接口。用户端接口校验token存在且有效即可;管理端接口除了校验token,还要判断当前用户的角色是不是管理员,普通用户token访问管理接口必须直接拒绝。比如管理端拦截器里加一行role判断,这个细节在答辩时非常加分。对于静态资源和登录接口本身,要记得在拦截器配置里放行,否则会出现登录都登不进去的问题。
密码加密这块,我强烈建议使用BCryptPasswordEncoder。这个类的encode方法会生成带随机盐的加密串,同一个密码每次加密结果都不同,比MD5固定字符串抗破解能力强得多。Spring Security单独为了一个密码加密器引入可能有点杀鸡用牛刀,但Spring Boot本身已经把这个Bean内置了,直接用就行。
4. 从0到1把项目跑起来:环境、导入、配置、启动全流程
4.1 环境准备清单
拿到源码包以后,最忌讳的事情是立刻用IDEA打开,然后看着一堆红色报错干瞪眼。我建议你按照下面的清单先准备环境,花十分钟把环境理顺,后面能少掉一半的坑。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8或11 | SpringBoot 2.x配JDK8最稳,3.x需要JDK17,先确认自己的项目是哪个版本 |
| Maven | 3.6以上 | 用IDEA内置的也行,但建议改一下settings.xml里的镜像源 |
| MySQL | 5.7或8.0 | 注意8.0的驱动类和时区配置与5.7不同 |
| IDEA | 2020以上 | 社区版也能跑,但专业版对SpringBoot和SQL支持更好 |
| Navicat | 任意版本 | 导入sql脚本用 |
| Redis | 可选 | 如果项目里用到缓存或JWT黑名单则需要启动,不用可以不装 |
这里面最大的版本坑就是JDK对不上SpringBoot的大版本。你拿到项目先打开pom.xml,看spring-boot-starter-parent的版本号。如果是2.2到2.7之间,老老实实用JDK8;如果是3.x,就要用JDK17,否则项目根本编译不过。
4.2 导入项目与修改配置
用IDEA导入项目时,选择File → New → Project from Existing Sources,然后选中pom.xml,让Maven解析依赖。如果你本地没有Maven仓库,第一次下载通常很慢。这个问题的解决方法不是什么玄学,就是到Maven的conf/settings.xml里,把阿里云镜像配好,然后再让IDEA重新加载。源码里如果自带mvnw,那直接执行打包命令也可以,我不多展开,但配置文件这一步一定要弄明白。
配置文件是本地的关键入口。SpringBoot项目里最常改的是application.yml或application.properties。你重点检查这几项:第一,spring.datasource.url里的数据库地址、端口、库名要与你的环境一致;第二,username和password要换成你本地数据库的账号密码;第三,如果项目里配置了Redis地址,把host和port调整好;第四,文件上传路径和端口号,默认端口一般是8080,如果被占用,可以改成8081或你自己习惯的端口。修改完成后,用Navicat连接数据库,创建一个新的数据库,字符集选择utf8mb4,然后执行源码里带的那份sql脚本,导入完成后检查一下表和数据,确认自动生成的admin账号之类初始数据都在。
4.3 启动过程与常见启动信息解读
启动项目其实就是一个操作:运行主类里带@SpringBootApplication注解的main方法。你会在控制台看到SpringBoot的启动日志,一个绿色的Spring标志,一批自动配置的打印信息,最后看到Tomcat started on port 8080,说明后端已经起来了。
如果你拿到的是前后端分离版本,那还要启动前端。以Vue项目为例,先用命令行进入前端目录,执行npm install安装依赖,再执行npm run dev,编译成功后再访问本地端口。这里有个特别常见的坑:前端接口请求地址写的是localhost:8080,但前端页面跑在另一个端口,浏览器就会因为跨域被拦截。解决办法有三种:后端加CorsFilter、前端在vue.config.js里配代理、或者直接用nginx转发。源码里一般已经处理过,如果你遇到跨域问题,优先检查后端的跨域配置类是否被组件扫描覆盖到。
启动成功之后,别急着说“项目跑通了”。你应该从头到尾走一遍核心流程:注册一个账号、登录、发布一本图书、去后台审核通过、再模拟另一个用户下单、最后查看订单状态。这套流程走顺了,你才算是真正理解了系统的运行逻辑,后面扩展和答辩才有底气。
4.4 源码包里的文档和视频怎么用
这套资料里包含文档、运行视频和讲解视频,它们的用途是完全不同的。运行视频是“救命视频”,它录的是从解压到启动的完整过程,遇到环境配置问题、启动报错时,先看运行视频,因为它踩过的坑大概率你也会踩一遍。讲解视频则不是给你照着敲一遍代码用的,它更像是答辩预演,你需要边看边梳理主讲人是怎么组织语言讲清楚一个模块的,把其中讲解项目亮点、数据库设计逻辑的段落,沉淀成你自己答辩时的表达。
至于文档,重点看需求分析、数据库设计、功能模块说明这几个章节。很多小白写论文时最头疼的就是逻辑结构混乱,而这套文档本身就是很好的参考模板。但注意,直接用别人的文档是答辩事故的高发区,导师一翻就能看出来。我的建议是,以文档为骨架,结合你自己实际调试时发现的问题和改动,把每个功能模块的描述用自己的话重新写一遍,这样既省时间又不容易撞车。
5. 实战中踩过的坑与排查速查表
5.1 典型运行问题及解决方案
我帮学生调试这种SpringBoot毕设项目,遇到最多的问题集中在下面几个方向,直接做成一个速查表,方便你对着排查。
| 现象 | 最可能的原因 | 解决方法 |
|---|---|---|
| 启动报DataSource连接失败 | application.yml里的数据库账号密码或库名错误 | 核对url、username、password,确认数据库已建好且sql已导入 |
| Access denied for user | 数据库账号没权限或者密码不对 | 用Navicat测试连接,确认能连上 |
| Table doesn't exist | sql脚本没执行成功 | 重新执行sql脚本,检查是否选对了数据库 |
| 端口被占用 | 上一次启动没关掉,或者其他程序占用了8080 | 换端口或杀掉占用进程 |
| 中文乱码 | 数据库连接URL缺少characterEncoding=utf8 | 在url后加?useUnicode=true&characterEncoding=utf8&useSSL=false |
| Druid或MyBatis报驱动类找不到 | MySQL 8.0的驱动类名变了 | 使用com.mysql.cj.jdbc.Driver,同时升级mysql-connector-java版本 |
| 上传图片失败 | spring.servlet.multipart.max-file-size默认1MB太小 | 改成max-file-size=10MB、max-request-size=10MB |
| Vue页面请求后端的接口404或跨域 | 前后端路径不匹配或后端跨域配置失效 | 检查axios baseURL、后端地址是否一致,确认跨域过滤器被扫描 |
| Long型id传到前端精度丢失 | 雪花ID是19位,前端parseInt会丢精度 | 在实体类id字段上用@JsonSerialize(using = ToStringSerializer.class)或配置Long转String |
5.2 排查思路与方法
遇到报错不要慌,先看控制台最下面的几行关键错误信息,大部分问题在堆栈的前三行就暴露了。这里我提供一个自己的排查顺序:先确认环境和配置,再确认数据库连接,最后才去怀疑业务代码。因为SpringBoot毕设项目里,环境配置导致的启动失败占绝大多数,业务代码逻辑问题反而比例不高。
启动失败时,第一步看是不是数据库连不上;第二步看是不是端口被占;第三步看是不是依赖没有下载完整。运行时报错时,先看是不是请求路径或参数类型不匹配,Controller层的Mapping注解、前端传的参数名和后端DTO的字段名大小写不一致,是这类问题的高发源。如果接口返回500,去后端控制台找异常栈,里面有具体是哪个文件的哪一行出的问题,定位会快很多。
我多说一句,尽量不要一上来就去网上复制一堆奇怪的解决方案。SpringBoot的报错信息已经足够清楚,把英文错误信息放到搜索引擎里搜,十有八九能找到同类问题。很多人最后栽的不是技术,而是浮躁,这个心态调整好了,项目调试效率能翻倍。
6. 源码拿到手之后,怎么做才算真正“会了”
6.1 学习路径建议:跑通、改通、讲通
我对拿到源码的同学一直有一个固定的三步建议。第一步是“跑通”,就是把它当作一个黑盒,完整地走一遍所有用户流程,记录下每一步的界面、数据变化和订单状态变化。第二步是“改通”,挑一个你感兴趣的模块,做一些不影响大局的小改动,比如把排序规则改一下、给图书详情页加一个浏览量累计、给注册功能加一个邮箱校验。这个步骤的意义不在于改动本身多重要,而在于你被迫去读代码、改代码、理解代码之间的调用关系。第三步才是“讲通”,把每个模块的结构、数据流向、关键逻辑整理成自己的话,能不看稿子讲出来,到这一步才是真正吃透了。
如果你的时间非常有限,我建议把精力集中在三个模块上:用户登录鉴权的拦截器实现、发布图书和审核流程的状态流转、下单时的事务控制和防并发逻辑。这三个模块恰好是整套系统里技术含量最高、答辩时最容易问到的部分,把这几个点吃透了,比把一百个小功能背下来都有用。
6.2 可以加的三个亮点功能
想拿高分,只跑通基础功能是不够的。这里给你三个性价比比较高的扩展方向,每个都花不了太多时间,但放简历上和答辩演示中都很加分。
第一个是引入Redis做缓存与热点处理。把首页图书列表、商品详情这些高频访问的数据放入Redis缓存,首次访问查数据库回填缓存,后续直接走缓存,后台更新图书时手动淘汰对应缓存。这一改动只需要几十行代码,但老师一定会感兴趣。第二个是把本地图片存储替换成MinIO对象存储,MinIO是开源的,可以部署在自己电脑上,接口与对象存储的语义接近,答辩时解释“图片和服务分离部署”会非常流畅。第三个是在订单状态变更的关键节点,接入支付宝沙箱支付或者加一个WebSocket消息通知,用户下单后自动通知卖家处理订单。支付宝沙箱环境根本不需要真实商户号,注册一个开发者账号就能用,属于那种演示效果特别炸的扩展功能。
6.3 答辩时最容易问到的点
根据我带过这么多学生做毕设的经验,老师看到这种SpringBoot项目,提问方向基本就锁定在四个问题:为什么选SpringBoot、事务是怎么控制的、一本书被人买走之后如何防止别人再买、图片存在哪里。这四个问题的标准答案,在你的项目里都能找到实际代码支撑,就看你有没有认真梳理过。
回答的时候有个小技巧:先一句话说结论,再结合代码讲具体实现。比如事务控制问题,你要说“我在下单Service方法上用@Transactional声明了事务,并且使用了条件更新来防止并发状态下同一本书被重复下单”,然后再指出哪一行代码是关键,展示你对代码有真实理解,而不是背概念。
我个人在实际操作中的体会是,源码和视频本质上都是“别人嚼过的饭”,真正的能力一定来自你自己动手改过的每一行代码。花一个晚上的时间,把这个项目跑通、把核心流程走一遍、把下单那段代码的前因后果梳理清楚,你得到的东西会远远超过这个标题本身。后续如果你想让它看起来更有竞争力,完全可以按照我上面说的方向加一个亮点功能,那就不再是拿别人的项目应付答辩,而是你自己的作品了。