ARTICLE DETAIL

资讯详情

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

Spring Boot校园二手书交易平台毕设全流程实战与避坑指南

Spring Boot校园二手书交易平台毕设全流程实战与避坑指南 每年的毕设季Spring Boot 校园二手书交易平台的提问量都会准时涨起来。这个题目看着不起眼却是把 JavaEE 方向后端开发的核心技能串得最完整的一个场景用户注册登录、旧书发布、分类检索、下单、订单状态流转、文件上传、后台管理一圈做下来基本等于把 Web 开发的地基重新打了一遍。我最早接触这个项目是帮学弟改代码——他自己照着视频敲了个 Spring Boot JSP 的版本跑是能跑但一问到怎么防止两个人同时买到同一本书就卡住了。这也是我想写这篇东西的原因网上大部分教程只讲怎么把功能做出来很少讲为什么这么做、上线会踩哪些坑。这篇文章按我从需求分析到部署上线的完整链路来写适合准备拿它做毕业设计、或者刚学完 JavaWeb 想找个完整项目练手的人。顺便说一句标题里那个11764其实是项目编号不用太在意重要的是我们把功能边界和技术选型想清楚。1. 这个题目真正要交付的东西核心流程与角色拆解1.1 从卖书到买书的一条完整链路有人看到交易平台就往淘宝方向想上来就设计购物车、优惠券、在线支付结果把自己坑了。真实校园二手书场景没这么复杂也不太需要在线支付——学生之间通常是平台撮合线下见面交易或约好地点自提。所以核心链路其实只有一条卖家上传旧书 → 买家搜索浏览 → 买家下单 → 双方约定线下交易 → 订单完成这条链路看似简单但每一环都有对应的后端知识点。上传旧书涉及图片上传和表单校验搜索浏览涉及分页和条件组合查询下单涉及事务和并发控制订单完成涉及状态流转。如果你把在线支付当作必做项就得额外考虑商家接入、回调验签这已经不是小项目的范畴了。我建议明确一点这是一个信息撮合平台不是支付平台专注把书、人、订单三个对象管好就够了。1.2 角色与权限的最小集合这个项目里最常见的角色划分就是两种学生用户和管理员。游客可以浏览但要下单、发布图书就必须登录。角色核心权限典型功能游客浏览图书列表、查看图书详情搜索、翻页、看封面和描述学生用户发布、修改、下架自己的书上传图书、下单、确认完成、查看订单管理员管理全部内容分类维护、用户冻结、图书下架、数据统计很多同学一开始会纠结要不要做收藏留言举报这些额外模块。我的看法是如果课程设计有时间收藏可以加它会自然引出一个关联表的设计但如果能稳定跑通核心链路先不要贪多。答辩时老师更关心的是你有没有把基础链路讲清楚而不是功能清单有多长。1.3 为什么说它是麻雀虽小五脏俱全这个题目之所以在毕业设计里长盛不衰正是因为它把教师想考察的技能点全部装进了一个相对容易理解的业务场景。RESTful 接口设计、JWT 或 Session 鉴权、MyBatis 或 JPA 操作、事务注解、文件读写、异常处理这些都是 JavaEE 范畴内最实用的东西。你把这个项目做完再去写商城、论坛、预约类系统骨架基本是通用的——无非换一批表和接口。2. 技术栈复盘Spring Boot 和 JavaEE 的边界别再搞混2.1 为什么选 Spring Boot标题里写着基于 JavaEE但实现上几乎清一色是 Spring Boot。这是因为 Spring Boot 本身建立在 Java Servlet、JDBC 这些 JavaEE 规范之上它把传统 JavaEE 项目里繁琐的 XML 配置、应用服务器部署和依赖管理全部简化了。你不再需要折腾 Tomcat 安装目录也不需要手写一堆 web.xml一个内嵌 Tomcat 就能让项目跑起来。对课程设计和毕设来说效率是第一位的。Spring Boot 也意味着你默认拥抱了约定大于配置的开发方式。比如application.yml里写数据源、Redis、文件上传大小框架会自动装配成对应的 Bean。面试时经常被问的自动装配原理底层是SpringBootApplication组合注解里的EnableAutoConfiguration再通过spring.factories加载一堆配置类。项目做完了这部分原理最好也去翻翻源码答辩被问到的时候不至于只说用了 Spring Boot。2.2 建议的完整技术栈清单拿我帮人改过的几个版本来看最稳的组合是层次选型说明开发语言Java 8 或 11兼容性最好避免高版本 JDK 的编译坑框架Spring Boot 2.7.x稳定资料多尽量别一上来就上 3.x持久层MyBatis-Plus省去大量重复 SQL分页好用数据库MySQL 5.7 / 8.0学生项目的主流选择鉴权JWT 拦截器前后端分离场景最方便密码加密BCrypt比简单的 MD5 更安全前端Vue 3 Element Plus 或 Thymeleaf取决于你是否愿意写前后端分离文件存储本地磁盘或 MinIO开发用本地部署用 MinIO部署Docker Nginx够用且演示效果好这个清单里没有 Redis、没有消息队列。不是说它们不重要而是对一个交易平台来说如果实际业务并没有高并发和异步解耦需求硬加技术栈反而会让答辩变成灾难——老师问一个你怎么答得上来一个。真要有加分需求可以加一个用户登录后把 token 加入 Redis 做黑名单的小功能或者用 Redis 存验证码这种结合点才是自然的。2.3 JavaEE 在毕设标题里的真实含义这里说一个容易被误解的点。严格来说JavaEE 现在叫 Jakarta EE指的是 Servlet、JSP、EJB、JMS 等一系列企业级规范而 Spring Boot 是一个基于这些规范的框架。毕设标题写基于 JavaEE更多是表达这是一个企业级 Web 方向项目的意思你不用真的去把 EJB 容器跑起来。真正落到代码里你和 JavaEE 打交道最多的地方其实是内嵌 Tomcat 处理 Servlet 请求以及javax.servlet这个命名空间下的HttpServletRequest、MultipartFile等 API。所以答辩时如果有人问你用的 JavaEE 技术体现在哪你就从 Servlet 生命周期、Tomcat 内嵌机制、HTTP 会话这几个角度去讲完全站得住。2.4 开发环境的小细节IDEA 与 VSCode 各有各的坑大部分学校机房装的是 IDEA。如果你用的是 Community 版注意它不直接支持 Spring Initializr 和 Tomcat 集成视图但用 Maven 命令行mvn spring-boot:run也能跑。社区里很多人问VSCode 配置 JavaEE 语言环境——其实就是三件事装 JDK、装 Extension Pack for Java、装 Maven。VSCode 下按CtrlShiftP选择 JDK 路径然后把项目用pom.xml导入等右下角 Maven 依赖加载完就可以启动。唯一难受的是断点调试配置略微繁琐建议主要用 IDEA。另外一个容易被忽略的小事JDK 版本。Spring Boot 2.7 在 JDK 8 和 JDK 11 上最稳如果你装了 JDK 17配合旧版 Lombok 会出现com.sun.tools.javac相关的编译错误解决办法是把 Lombok 升到 1.18.30 以上。遇到这种莫名其妙的编译问题先检查版本组合而不是去改代码。3. 数据库建模先解决一本书不能卖两次的问题3.1 核心表结构二手书平台的表其实不多核心就四张用户表、图书表、订单表、分类表。再往后可以加收藏表、留言表。这里给你一份比较标准的 DDL 参考。CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(32) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt 密文, nickname varchar(32) DEFAULT NULL, phone varchar(20) DEFAULT NULL, avatar varchar(255) DEFAULT NULL, role tinyint(4) NOT NULL DEFAULT 1 COMMENT 0管理员 1学生用户, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1正常 0冻结, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;CREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT, book_name varchar(64) NOT NULL, author varchar(32) DEFAULT NULL, publisher varchar(64) DEFAULT NULL, isbn varchar(32) DEFAULT NULL, category_id bigint(20) DEFAULT NULL, original_price decimal(10,2) DEFAULT NULL, sell_price decimal(10,2) NOT NULL, degree tinyint(4) NOT NULL DEFAULT 0 COMMENT 0全新 1九成新 2七成新 3有笔记, description varchar(500) DEFAULT NULL, cover varchar(255) DEFAULT NULL, seller_id bigint(20) NOT NULL, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0在售 1锁定 2已售出 3已下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_status (category_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;CREATE TABLE book_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL, book_id bigint(20) NOT NULL, book_name varchar(64) DEFAULT NULL, cover varchar(255) DEFAULT NULL, buyer_id bigint(20) NOT NULL, seller_id bigint(20) NOT NULL, price decimal(10,2) NOT NULL, contact_phone varchar(20) DEFAULT NULL, remark varchar(255) DEFAULT NULL, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待联系 1交易中 2已完成 3已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, finish_time datetime DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意我把订单表取名book_order而不是order。order在 MySQL 里是排序相关的保留字直接用会给你带来一堆 SQL 的引号地狱。这是新手最容易踩的坑提前规避掉。3.2 图书状态与订单状态的设计二手书交易的难点不在增删改查而在状态机。一本书从挂出来到最终成交至少要经过在售→锁定→已售出这几个阶段。为什么要锁定因为在买家下单到双方真正见面的这段时间里这本书不能被其他人再买走。这里我设计了一套双状态机图书状态book.status0 在售、1 锁定、2 已售出、3 下架订单状态book_order.status0 待联系、1 交易中、2 已完成、3 已取消流转规则很简单。买家下单时系统把图书从 0 变成 1同时创建一条状态为 0 的订单双方联系上并开始线下交易后订单变成 1确认交易完成订单变 2图书变 2。如果买家取消或者超时未联系订单变 3图书从 1 回到 0重新上架。这套设计最大的好处是任何时刻你都能回答这本书现在处于什么状态这个问题。而且管理员后台做统计也很直观——在售多少本、成交多少本、取消订单多少笔。答辩时画一张状态流转图用文本描述清楚即可比甩出一堆功能截图更有说服力。3.3 几个建模时容易犯的错第一价格字段用double而不是decimal。浮点数在金融计算里会有精度问题虽然二手书场景不太涉及复杂金额计算但养成用decimal(10,2)的习惯总没错。第二把图片存成 base64 塞进数据库。这是我最常见到的新手写法最后表体积爆炸查询也慢。正确做法是数据库里只存图片 URL 或相对路径文件本身放磁盘或对象存储。第三不加创建时间。这个项目所有列表几乎都要按时间倒序排列如果没有create_time字段且没有默认值写排序时只能干瞪眼。第四忽略了订单快照。订单表里记录book_name和cover看起来冗余但这叫快照哪怕卖家后续修改了图书信息或删除了图书历史订单仍然能正确显示当时成交的书名和封面。这个细节虽然简单但能体现你有没有真实业务经验。4. 核心业务代码从登录鉴权到订单状态机4.1 密码加密与 JWT 鉴权用户模块是第一个要写的接口。密码一定不能明文入库也不要只用简单 MD5。推荐引入spring-security-crypto里的BCryptPasswordEncoder它自带随机盐同密码每次生成的密文都不一样安全性好很多。Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 注册时加密 String encoded passwordEncoder.encode(rawPassword); // 登录时校验 boolean ok passwordEncoder.matches(rawPassword, user.getPassword()); if (!ok) { throw new BizException(用户名或密码错误); }登录成功之后前后端分离的情况下用 JWT 最省事。生成 tokenString token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 7200_000L)) .signWith(SignatureAlgorithm.HS256, secretKey) .compact();然后写一个HandlerInterceptor在preHandle里读请求头的Authorization解析失败就返回 401。这里有个版本坑老版本的 jjwt 0.9.1 在 JDK 9 以上会报ClassNotFoundException: javax.xml.bind.DatatypeConverter因为 JAXB 被移除了。要么用 jjwt 0.11.5 配合Keys.hmacShaKeyFor(secret.getBytes())签名要么在 pom 里补 JAXB 依赖。我建议直接用 0.11.5API 变化不算大。鉴权拦截器只解决你是谁的问题还要解决你能干什么——管理员接口要做角色校验。简单的做法是在拦截器里把解析出来的 userId 和 role 塞进ThreadLocal或request属性然后在管理员接口里用自定义注解RequireAdmin或者直接判断角色值。别小看这一步答辩时老师很可能会问普通用户能不能调管理员接口你只要演示给他看会被拦截这个分数就到手了。4.2 发布图书上传和表单的顺序问题发布图书有两个常见做法一种是前端先调上传接口拿到图片 URL再和表单一起提交另一种是后端用一个接口同时接MultipartFile和表单字段。两种都行我更推荐第一种因为它的步骤清晰出错时更容易排查而且以后如果需要审核图片可以只在文件层面做处理。无论哪种方式后端都要做文件类型白名单校验。很多人直接信任前端传的文件名这是安全隐患。服务端更新一点判断即可PostMapping(/upload) public R upload(RequestParam(file) MultipartFile file) { String original file.getOriginalFilename(); String ext StringUtils.getFilenameExtension(original); if (!Arrays.asList(jpg, jpeg, png, gif, webp).contains(ext.toLowerCase())) { throw new BizException(图片格式不支持); } String filename UUID.randomUUID().toString().replace(-, ) . ext; file.transferTo(new File(uploadPath filename)); return R.ok(/files/ filename); }我见过有人用时间戳做文件名并发一高就撞名用 UUID 是最省心的。上传成功返回一个相对路径前端把它放进表单一起提交后端只需把 URL 存进book.cover字段。4.3 检索与分页别写一坨冗长的 if else图书列表页通常需要支持按关键词搜索书名、按分类筛选、按价格排序。很多初学者会写出一长串if (xxx ! null) { sql and name like ... }这样拼接 SQL 既容易出错又难维护。如果用 MyBatis-PlusLambdaQueryWrapper直接解决LambdaQueryWrapperBook wrapper new LambdaQueryWrapper(); wrapper.eq(Book::getStatus, status) .eq(categoryId ! null, Book::getCategoryId, categoryId) .like(StringUtils.isNotBlank(keyword), Book::getBookName, keyword) .orderByDesc(Book::getCreateTime); PageBook page bookMapper.selectPage(new Page(pageNum, pageSize), wrapper);这里有个关键点eq和like的第一个参数是条件。条件不成立时MyBatis-Plus 会自动跳过这个查询条件你的categoryId为空、keyword为空都不会出问题。这就是动态 SQL的核心思想。还有一个容易忽略的细节查询在售图书时千万不要在Wrapper里忘了status 0否则你已经锁定的书会被用户看到下单时又报错体验很差。宁可让前端传 status也不能默认查出全部。4.4 下单与并发处理如何防止一本书被两个人同时买这是整个项目最值得讲清楚的地方也是答辩时最容易问倒人的点。场景是这样的A、B 两个学生同时看中一本书几乎同时点击下单。如果代码只做了先查书判断 status 是 0插入订单那么两个请求可能都通过了检查都创建了订单——一本书卖了两遍。解决办法很多最简单可靠的是用一行带条件的更新语句让数据库帮我们保证原子性Transactional(rollbackFor Exception.class) public OrderVO createOrder(Long bookId, Long buyerId) { Book book bookMapper.selectById(bookId); if (book null || book.getStatus() ! 0) { throw new BizException(这本书暂不可购买); } if (book.getSellerId().equals(buyerId)) { throw new BizException(不能购买自己发布的图书); } // 关键把 status0 作为更新条件影响行数为 0 说明被别人抢了 int rows bookMapper.lockBook(bookId); if (rows 0) { throw new BizException(手慢了图书已被其他同学买走); } // 插入订单返回订单编号 }对应的 Mapper 方法Update(UPDATE book SET status 1 WHERE id #{bookId} AND status 0) int lockBook(Param(bookId) Long bookId);这个设计为什么能防并发因为UPDATE语句会对命中的行加锁两个事务同时执行时只有一个能把status从 0 改成 1另一个影响行数是 0直接抛出业务异常。同时Transactional保证了下单过程中任何一个异常都会回滚不会出现订单创建成功但书没锁住、或者书锁了订单没建成的中间状态。额外提醒一句Transactional默认只回滚RuntimeException如果你用了自定义异常请指定rollbackFor Exception.class否则事务可能悄悄不生效。这是事务注解使用中非常经典的一个坑。5. 图片上传方案本地存储和对象存储的取舍5.1 最省事的本地磁盘存储课程设计阶段图片直接存本地磁盘是最快的方式。你只需要在application.yml里配置上传根目录然后写一个WebMvcConfigurer把本地路径映射成静态资源 URL。spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB file: upload-path: ./uploads/ access-path: /files/**Configuration public class WebFileConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file: uploadPath); } }这样前端img src/files/xxx.jpg就能直接访问到磁盘上的图片。注意addResourceLocations里必须写file:前缀Windows 和 Linux 的路径分隔符框架会自动处理但根目录建议统一用绝对路径否则 jar 包在不同目录启动时会找不到文件。5.2 生产上接入 MinIO如果要把项目放到云服务器上演示或者老师明确要求分布式文件存储本地磁盘就不太够用了——你把 jar 部署到服务器图片存在服务器某个目录一旦迁移或者多实例部署图片就各管各的。这时引入 MinIO 是性价比最高的方案。MinIO 兼容 S3 协议私有部署社区也活跃。核心代码如下Value(${minio.endpoint}) private String endpoint; Value(${minio.access-key}) private String accessKey; Value(${minio.secret-key}) private String secretKey; Value(${minio.bucket}) private String bucket; PostConstruct public void init() throws Exception { MinioClient client MinioClient.builder() .endpoint(endpoint) .credentials(accessKey, secretKey) .build(); if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) { client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build()); } } public String upload(MultipartFile file) throws Exception { String object UUID.randomUUID().toString().replace(-, ); client.putObject(PutObjectArgs.builder() .bucket(bucket) .object(object) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); return endpoint / bucket / object; }把endpoint、access-key、secret-key配在application-prod.yml和开发环境区分开。上传接口返回的 URL 直接存进数据库。如果 bucket 没设置为公开浏览器加载图片时会有访问权限问题需要在 MinIO 控制台给 bucket 设置匿名只读策略或者在后端额外生成带签名的临时 URL。对毕设演示来说大部分人会选择公开读注意设置里要限制只能读不能开放写权限。5.3 上传模块的三个常见坑第一个坑是没调大spring.servlet.multipart的默认大小。Spring Boot 默认单文件 1MB请求 10MB学生拍的书本照片随便一张就超过 1MB前端报错后你往往还以为是接口的问题。直接在上面的 yml 里调大一劳永逸。第二个坑是富文本内容里的图片。如果你的图书描述使用了富文本编辑器编辑器会把图片转成 base64 塞进 HTML一个长描述可能让请求体膨胀到几兆。所以要让前端走上传接口拿到 URL再以img src形式插入富文本。第三个坑是删除文件。用户下架图书或删除图书时如果在服务端执行new File(path).delete()在 Windows 下经常遇到文件被占用删不掉结果页面显示 404。我的处理方式是删除逻辑里不要做强依赖数据库记录删掉即可图片文件用定时任务每周清理一次孤儿文件。省心也不会阻塞业务。6. 打包部署与答辩现场最容易踩的坑6.1 从 IDEA 里的能用到 jar 包能跑很多同学在 IDEA 里跑得好好的一打包就出各种问题。常见的原因有几个。第一个是静态资源配置写成了相对路径。比如上面file.upload-path: ./uploads/你在 IDEA 里启动路径是项目根目录没问题但用java -jar部署时工作目录取决于你从哪里执行命令。如果不在预期目录图片路径全乱。所以部署时要么写绝对路径要么在启动脚本里先cd到固定目录。第二个是mvn clean package -DskipTests打包完发现 target 里没有 jar。先检查pom.xml有没有配 Spring Boot Maven 插件build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build没有这个插件打出来的 jar 只是一个普通 jar启动会报no main manifest attribute。这是最经典的问题没有之一。第三个是端口被占用。如果你用了8080云服务器上又装了 Nginx、Tomcat 或者别的服务就可能冲突。我习惯在application.yml里把端口设成可配的server.port: ${PORT:8080}这样部署时用java -jar xxx.jar --server.port8081就能覆盖。社区里很多人搜springboot yml 随机端口开发时确实可以配random但那只是临时调试用线上一定要固定。6.2 MySQL 的时区与关键字MySQL 8 对时区很敏感。你用 IDEA 连接数据库时一般会主动配serverTimezoneAsia/Shanghai一旦忘了查询时间字段会少 8 小时或直接报The server time zone value 乱码 is unrecognized。在数据库连接串里统一写死jdbc:mysql://localhost:3306/book_trade? useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai还有一个关键字问题前文提醒过表名不要叫order字段名也不要叫desc、remark如果跟某些数据库版本冲突建议加反引号或改名。desc是排序关键字有人给字段起名desc表示描述结果 SQL 直接报错。统一的命名习惯能帮你避免大量低级错误表名用复数或加前缀字段名都用_分隔的英文不要用中文和拼音缩写。6.3 Docker 部署与配置分离展示给老师和答辩评委看Docker 部署很加分。最简单的 Dockerfile 长这样FROM openjdk:8-jre-alpine WORKDIR /app COPY target/book-trade-1.0.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar, --spring.profiles.activeprod]然后docker build -t book-trade . docker run -d -p 8080:8080 -v /data/uploads:/app/uploads book-trade。把本地上传目录挂载到容器里的/app/uploads数据不丢。MySQL 和 MinIO 也可以用docker-compose一起编排这里不展开但至少你有一个可以一键启动的完整环境比在联网机房现场装数据库稳妥得多。配置分离的原则也在这里体现开发用application-dev.yml连本地数据库生产用application-prod.yml连云服务器数据库启动参数通过--spring.profiles.activeprod指定。不要把生产库密码写到代码里提交到仓库那属于基础安全素养。6.4 关于反编译别人项目的一点个人建议很多人手里会拿到一个编译好的xxx.jar想把它还原成可以继续改代码的项目。搜索热度也证明这是个刚需。我的做法是用 IDEA 直接把 jar 拖进编辑器它会自动反编译成可读的 class 源码或者用jd-gui打开 jar 查看类的结构。你能看到 Controller、Service、Mapper 接口但不能指望反编译出完整的 Maven 工程结构——注释、配置文件、测试代码这些信息已经丢失了。所以我的建议是反编译适合用来看思路不适合直接拿来交作业。看别人怎么设计表、怎么处理状态然后自己重写一遍学到的东西才真正是你的。如果只是把反编译出来的代码一交了之老师随便问一个细节你就会卡壳。最后再分享一个我个人比较看重的细节给项目加一个自定义启动 Banner。社区里有现成的 springboot banner 生成器把 ASCII 艺术字复制到banner.txt里项目启动时打印出来答辩演示的观感会好不少。这个行为虽然和业务无关但会让评委觉得你对项目有热情、有完成度。技术上也是一样越是这种不起眼的小地方越能看出一个人是不是真把项目当成自己的作品在做。如果你正要开始做这个题目我的建议是先把数据库表和状态流转想清楚再写代码先跑通发布→搜索→下单→完成的闭环再加花活。这套骨架一旦稳定后续扩展收藏、留言、数据报表甚至管理后台的图表统计都会非常顺手。
返回列表