尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot校园二手交易系统毕业设计实战指南

SpringBoot校园二手交易系统毕业设计实战指南 做毕业设计选“基于SpringBoot的校园二手交易系统”这个题目说实话很聪明。它既有完整的业务闭环——注册登录、发布商品、浏览搜索、下单交易、评价互动又把SpringBoot框架的常见技术点全串了一遍无论是给答辩老师演示还是自己以后找工作写进简历都能拿得出手。哪怕你现在对SpringBoot还停留在“能跑通HelloWorld”的阶段照着这篇整理走一遍也完全能做出一个能演示、能答辩、能部署的项目。这篇内容我会从选题拆解开始讲到项目结构、核心模块、数据库设计、关键配置最后把实际开发中踩过的坑和排查思路一并列出来。我尽量按实际开发流程来讲而不是按教科书目录讲毕竟毕业设计的核心是“你能把它做出来还讲得清楚为什么这么做”。1. 项目整体设计与思路拆解1.1 为什么选校园二手交易作为毕业设计主题每年毕业设计选题季软件工程、计算机科学专业的学生最头疼的就是选题。系统管理、图书管理、成绩管理这类老掉牙题目答辩老师一眼就能看出是“增删改查”很难拿高分。而校园二手交易系统这个方向第一有明确的痛点毕业生离校时书籍、电器、生活用品大量闲置新生入学又有购买需求校内交易需求真实存在第二有足够的业务复杂度商品发布、图片上传、订单状态流转、搜索筛选、用户身份区分这些模块既能体现工作量又不会难到做不完。从技术教学角度讲这个题目正好覆盖了SpringBoot框架最常用的能力Web接口开发、数据持久化、文件上传、权限校验、异常处理。你做完它等于把SpringBoot入门到进阶的常用姿势都练了一遍。银行、电商、仓储这些系统太大不适合毕设节奏校园二手系统体量适中两个多月时间完全能做出一个像样的成品。1.2 整体架构和技术路线选择我推荐的架构是前后端分离但为了兼顾答辩演示的便利性也可以做成SpringBoot Thymeleaf传统服务端渲染。两种方案各有利弊做之前要想清楚。前后端分离方案SpringBoot Vue更贴近企业实际开发模式数据接口化和RESTful风格也是近年面试常问的点。但它有个隐形门槛——你得额外处理跨域问题、开发时得同时启动前端和后端两个服务答辩现场的部署和演示复杂度会高一些。如果前端基础较弱我建议直接放弃Vue用SpringBoot默认集成的Thymeleaf模板引擎。Thymeleaf模版里可以直接写HTML和CSS写一些简单的前端交互数据从Controller往Model里塞页面能正常工作。它的好处是项目结构简单打包后一个JAR文件就能跑非常适合毕业答辩场景。技术选型上我建议使用Spring Boot 2.7.x版本原因很简单3.x版本要求JDK17而且很多第三方集成组件对3.x的兼容性还不够完善。2.7.x配上JDK8是经过大量项目验证的稳定组合。持久层框架我推荐MyBatis-Plus它可以在MyBatis基础上提供单表CRUD的通用方法省去大量手写XML的时间对于毕业设计这种需要快速出成果的场景非常实用。数据库选MySQL 5.7或8.0都行重点是字符集必须统一设为utf8mb4否则后面存商品描述时遇到表情符号就会乱码掉。1.3 功能模块划分与用户角色我按实际业务流把系统拆成了三大角色游客未登录用户、普通用户学生、管理员。游客能浏览商品列表、搜索商品、查看商品详情但要想发布商品或下单必须登录。普通用户可以发布闲置商品填写标题、描述、价格、分类、图片、管理自己发布的商品上下架、修改、删除、搜索自己想要的商品、对商品下单、查看订单状态还能对交易过的商品进行评价。管理员负责用户管理封禁/解封、商品审核下架违规商品、分类管理增删分类标签、查看全站统计数据商品总量、订单总量、用户注册量。这个角色划分覆盖了毕设答辩时老师常问的问题——权限怎么控制的、订单状态怎么流转的、违规内容怎么治理的。1.4 为什么选择“先画数据库再写代码”的开发顺序很多同学拿到课题就开始写Controller写到哪算哪最后数据库表结构被代码牵着走反复改来改去效率极低。我的实际经验是先用一整天时间把数据库表设计清楚后面所有业务逻辑都有据可依。以二手交易系统为例核心表至少有这几张用户表user、商品表goods、商品分类表category、订单表orders、评价表comment、系统公告表notice。先把这六张表的关系理清比如一条商品记录归属一个用户、一条订单记录同时关联用户和商品然后再去写实体类、写Mapper、写Service速度会快一倍。表结构是项目的骨架骨架稳定了填肉才有方向。2. SpringBoot核心技术点解析2.1 自动装配原理与项目启动过程SpringBoot之所以能让开发大幅提速核心在于自动装配。你得能一句话讲明白它的原理答辩时老师必问。SpringBoot的启动类上有个SpringBootApplication注解它其实是一个组合注解包含SpringBootConfiguration、EnableAutoConfiguration和ComponentScan。关键在于EnableAutoConfiguration它会通过spring.factories或AutoConfiguration.imports文件加载所有AutoConfiguration类。这些配置类上都有条件装配注解比如ConditionalOnClass、ConditionalOnMissingBean、ConditionalOnProperty。当classpath下存在你引入的依赖时对应的默认配置就会生效。你引入spring-boot-starter-web自动配置DispatcherServlet和嵌入式Tomcat引入spring-boot-starter-jdbc自动配置数据源。给答辩的说辞是这样“SpringBoot启动时扫描类路径下的依赖根据条件注解决定是否加载对应的自动配置类从而完成默认初始化。如果开发者自己声明了相关Bean自动配置通过ConditionalOnMissingBean检测到冲突则以开发者的配置为准因此它既智能又可以被覆盖。”这一段能讲明白老师就知道你真看懂了框架。2.2 项目分层结构与包组织规范我没见过哪个企业项目把代码全写在一个类里毕业设计也一样要分层。我习惯把包结构组织为com.campus.market ├── controller // 接口层接收前端请求 ├── service // 业务逻辑层处理核心业务 │ └── impl // 业务接口实现 ├── mapper // 数据访问层对接MySQL或dao ├── entity // 数据库实体类 ├── dto // 数据传输对象封装前端传入参数 ├── vo // 视图对象封装返回给前端的结构 ├── config // 配置类跨域、拦截器、文件上传 ├── common // 公共类统一返回结果、异常处理、常量 └── utils // 工具类JWT工具、日期工具等Controller只做参数接收和返回结果封装不写业务逻辑。Service层写核心逻辑比如下单时校验商品状态、扣减库存、创建订单记录。Mapper层只做数据库操作MyBatis-Plus的BaseMapper能让日常CRUD缩短到不用写SQL。分层做清楚的另一个好处是答辩时可以引导老师看代码你说“业务在service层您看这里校验了商品是否已售出”老师顺着你指的方向一看就明白而不是在Controller里翻几百行逻辑。2.3 统一响应结构与全局异常处理这部分是毕设项目中你一开始懒得写、到后面到处补代码的环节但既然做了就狠下心一次到位。我习惯定义一个ResultT类包含code、message、data三个字段。Controller的每个接口都返回Result.success(data)或Result.error(xxx)这样前端无论用Thymeleaf还是Vue都能统一判断请求状态。全局异常处理用RestControllerAdvice配合ExceptionHandler捕获业务异常、参数校验异常、系统异常。比如用户传一个商品ID数据库中不存在Service层抛出new BusinessException(商品不存在)全局异常处理器统一捕获后返回code500、message“商品不存在”的JSON。前端拿到这个结构就能弹出提示后端也避免了把堆栈信息直接暴露给用户这是企业开发的标准做法放到毕设里是明显的加分项。2.4 SpringBoot配置文件的坑与技巧application.yml是项目的命门。我见过不少同学把数据库连接信息直接写死然后项目拿到别人电脑上跑就报错。实际开发中建议用多环境配置application.yml放公共配置application-dev.yml放本地数据库application-prod.yml放服务器参数。启动时通过spring.profiles.activedev切换绝对不要在代码里硬编码数据库账号密码。我常用的配置片段如下server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/campus_market?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB max-request-size: 10MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true配置文件写完第一步要检查的就是MySQL连接串serverTimezoneAsia/Shanghai要加否则数据库和JVM的时区不一致查询时间字段会差8小时。map-underscore-to-camel-case这个配置也重要它让数据库字段user_name自动映射为Java属性userName能省掉大量“手动起别名”的无聊工作。3. 数据库设计与核心功能实现3.1 数据库表结构设计详解表设计是我最花心思的部分因为二手交易涉及的不只是商品和用户还有订单状态、交易流程、评价关联。下面这几张表我按实际开发经验把关键字段都列出来你可以直接拿去建库。用户表user字段类型说明idbigint主键自增usernamevarchar(50)登录用户名唯一索引passwordvarchar(255)加密后的密码BCryptnicknamevarchar(50)显示昵称avatarvarchar(255)头像URLphonevarchar(20)联系电话statustinyint状态1正常 0封禁roletinyint角色1用户 2管理员create_timedatetime注册时间商品表goods字段类型说明idbigint主键自增user_idbigint发布者ID外键关联usercategory_idbigint分类ID外键关联categorytitlevarchar(100)商品标题descriptiontext商品描述pricedecimal(10,2)单价imagesvarchar(1000)图片路径多张用逗号分隔statustinyint状态1在售 2已售 3下架view_countint浏览量create_timedatetime发布时间订单表orders字段类型说明idbigint主键自增order_novarchar(32)订单编号全局唯一goods_idbigint商品IDseller_idbigint卖家IDbuyer_idbigint买家IDpricedecimal(10,2)成交价格statustinyint状态1待付款 2待发货 3待收货 4已完成 5已取消create_timedatetime下单时间pay_timedatetime付款时间评价表comment、分类表category、公告表notice结构相对简单。评价表关联订单ID、被评价用户ID、评分和内容分类表只需要id和name公告表包含标题、内容、发布时间。我特别在订单表中加了order_no字段这是个容易被忽略的细节。如果主键使用自增ID订单号很容易被猜到也不方便后续查询项目里生成一段20位左右的时间戳随机数订单号会更专业。这张表设计出来后你写业务代码时思路会非常清晰卖家订单查seller_id买家订单查buyer_id状态流转就是update status。3.2 用户注册登录与密码安全登录是入口功能但很多毕设只做了最简单的明文密码校验。答辩时有经验的老师看到你没做任何加密就会问“用户的密码在数据库里存明文”这一问很多人就慌了。我用Spring Security体系的BCryptPasswordEncoder。简单说就是每次对密码进行单向哈希加盐数据库里永远不存明文密码。注册时存的是加密后的字符串登录时调用matches(rawPassword, encodedPassword)比对。代码很简单但安全性完全不一样。登录成功后我用JWT生成一个token返回给前端前端存入localStorage之后每次请求在HTTP头部加Authorization: Bearer 你的token。后端写一个拦截器HandlerInterceptor在preHandle里校验token并解析出用户ID存入ThreadLocal或请求对象中后续业务直接取当前登录用户。这样写的好处是Controller里不需要反复从参数里取userId也保证了接口的访问控制。你不会写拦截器也没关系哪怕只把用户信息塞在session里也比明文密码强出一大截。3.3 商品发布与图片上传实现商品发布是整个系统最核心的写入场景。前端弹出表单包括标题、描述、价格、分类、图片文件。后端接口接收GoodsDTO里面有个MultipartFile[]文件数组。我坚持图片处理单独写一个上传接口发布接口里只接收图片的URL数组这样职责更单一也方便日后防盗链或做CDN。文件上传有两个注意点。第一是路径问题本地开发时我统一存在项目的upload/目录下但打成JAR包后这个相对路径会失效所以生产环境应该配一个绝对路径或者用本机上一个固定的目录如/home/user/campus-market/upload/。第二是文件名问题用户上传的原始名称要过滤掉我用UUID重命名String fileName UUID.randomUUID().toString().replace(-, ) ext保存在/upload/goods/20250218/这样带日期的子目录里按天分目录避免单目录文件过多。商品发布后默认状态是在售。买家下单那一刻得先判断该商品状态是否仍为在售status1避免两个人同时下单都成功。这个判断在Service层完成先用商品ID查商品检查卖家不是自己不能买自己的东西再更新状态为已售最后创建订单。三个操作必须放在同一个事务里。SpringBoot的Transactional注解解决问题但要注意它默认只处理RuntimeException如果你try-catch吞掉了异常事务就失效了。我常跟别人讲事务方法里别自己catch异常让异常抛出去交给上层处理。3.4 商品搜索与筛选逻辑搜索功能难讲深但也不能只做一个like title。我用MyBatis-Plus的QueryWrapper动态构造查询条件。前端传入四个可选参数keyword、categoryId、minPrice、maxPrice。查询条件按需追加——keyword非空就对title做like查询categoryId非空就等值筛选minPrice和maxPrice非空就做区间过滤。同时按create_time desc排序让最新发布的商品排前面。还有一个隐藏的坑列表查询必须排除掉已下架状态。游客访问系统只应看到status1的在售商品管理员才看得到所有状态。所以查询条件里默认带上eq(status, 1)。很多新手写完列表接口结果把自己的已下架商品也当成正常商品展示这种低级问题答辩时会被当场抓到。分页我用MyBatis-Plus的分页插件PageGoods page new Page(pageNum, pageSize); PageGoods result goodsMapper.selectPage(page, queryWrapper);记得在配置类里注册PaginationInnerInterceptor否则分页失效这是最常见的“配置了没生效”问题。3.5 订单流程与状态流转订单是我认为整个系统最能体现业务深度的部分。我把它设计成五个状态待付款、待发货、待收货、已完成、已取消。校园二手交易不一定需要支付集成做虚拟支付即可所以流程可以简化成买家下单直接创建订单并进入“待发货”卖家在“我的卖出的订单”里看到新订单点击发货后订单变成“待收货”买家确认收货后变成“已完成”任何一方在未完成前想取消都可以取消订单并恢复商品状态为在售。这个状态机做得好不好反映在代码里就是状态枚举和流转判断。我定义一个OrderStatus枚举类写清楚每个状态的数值和描述然后Service里做判断if (order.getStatus() ! OrderStatus.WAIT_SHIP.getCode()) { throw new BusinessException(当前订单状态不允许发货); } order.setStatus(OrderStatus.WAIT_RECEIVE.getCode());这套写法你自己答辩时能说清楚状态只有流转路径正确才允许推进非法状态操作一律拒绝。4. 实操过程从零搭建到能跑通的完整流程4.1 IDEA创建SpringBoot项目与初始化配置如果你用IDEA Ultimate直接在File - New - Project里选择Spring Initializr。如果只有社区版去Spring官网的 start.spring.io 页面下载压缩包再导入效果一样。依赖选择上我只建议第一个项目勾这几样Spring WebThymeleaf如果用前后端分离就不勾Spring Data JDBC或MyBatis框架先选MyBatis再用MyBatis-Plus的starterMySQL DriverLombok必须勾能省掉大量getter/setter项目结构生成后第一步肯定要跑起来验证环境而不是急着写代码。直接点击SpringbootMarketApplication类运行main方法浏览器访问localhost:8080能看到SpringBoot默认错误页就说明环境正常。这一步排查的意义在于分清是开发环境问题还是业务代码问题。如果连启动都报错后面全是无用功。4.2 引入MyBatis-Plus与写第一个接口引入MyBatis-Plus很简单在pom.xml添加依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency启动类上加MapperScan(com.campus.market.mapper)这一步漏了的话你的Mapper Bean无法注入。然后建一个实体类Goods字段对应表列名再用TableName(goods)标明表名TableId(type IdType.AUTO)注明主键自增。接下来写Mapper接口public interface GoodsMapper extends BaseMapperGoods { }测试第一个接口时在Controller里注入GoodsMapper写一个/api/goods/list接口直接用selectList(null)返回全部商品。这一步的目的不是实现功能而是验证从Controller到Mapper再到MySQL整条链路是通的。链路通了后面再按业务拆分模块。4.3 用户登录与拦截器实现细节拦截器很多人只在配置文件里写了一遍其实拦截器需要两步定义拦截器类然后在配置类里注册。定义拦截器类public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token null || token.isEmpty()) { response.setStatus(401); return false; } // 解析token把用户信息放入request Long userId JwtUtil.parseToken(token); request.setAttribute(userId, userId); return true; } }注册拦截器Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/**) .excludePathPatterns(/api/user/login, /api/user/register, /api/goods/list, /api/goods/detail/**); } }这里有个细节需要特别注意放行的路径要完整。有一次我只放行了/api/user/login结果注册接口也被拦截前端一直报401排查了半天才发现漏了注册路径。拦截器黑白名单必须写准确定义完至少仔细读两遍每个路径。4.4 前后端交互与跨域问题解决如果你做了前后端分离Vue前端开发服务器默认运行在5173端口SpringBoot在8080端口跨域是必然的。后端用CrossOrigin注解只能局部解决全局方案是WebMvcConfigurer里加CORS配置registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600);同时要注意预检请求OPTIONS也会被拦截器拦住所以拦截器里通常把请求方法为OPTIONS的直接放行否则前端每次跨域AJAX都会被401很典型的坑。我没少吃这个亏现在会在前后端分离项目里提前把OPTIONS排除掉。4.5 打包部署与答辩演示准备开发完成后打包也有讲究。如果你用的是前后端分离前端build后的dist目录可以直接扔到SpringBoot项目的src/main/resources/static下或者单独部署到Nginx。我更推荐把所有内容打成一个JAR包答辩的时候一台电脑带个演示环境就能跑。在项目根目录执行mvn clean package -DskipTests然后运行java -jar target/campus-market-0.0.1-SNAPSHOT.jar如果用了多环境配置记得带参数java -jar campus-market.jar --spring.profiles.activeprod部署时常见问题是连不上MySQL。我一般会先检查服务器上MySQL服务是否启动、远程访问权限是否开放、防火墙是否拦住3306端口。还有一个高频坑JDK版本不匹配。打包机用JDK8编出来的JAR运行环境也必须大于等于JDK8。如果运行时报UnsupportedClassVersionError多数是JDK版本问题重装或切换JDK就行。5. 常见问题与排查技巧实录5.1 数据库连接报错Access denied或Communications link failure这两个报错出现频率极高。Access denied for user rootlocalhost意思是密码错误或账号不允许指定主机登录先检查数据库密码和用户名。Communications link failure是网络层面的本地开发基本可以断定是MySQL服务没启动或者url里主机端口写错。还有一种情况url里的mysql.cj.jdbc.Driver是项目里MySQL驱动包版本过低导致的可以更新驱动版本。我的排查顺序永远是服务是否活着、账号密码是否正确、url拼写是否准确、防火墙是否拦截绝大多数问题出在这四步里。5.2 中文乱码问题定位与解决中文乱码分为两类前端页面乱码和数据库数据乱码。页面乱码一般是响应头没有声明UTF-8SpringBoot里在配置文件中加入server: servlet: encoding: charset: UTF-8 force: true数据库乱码几乎可以断定是建表时字符集不是utf8mb4。建库语句前面写CREATE DATABASE IF NOT EXISTS campus_market DEFAULT CHARSET utf8mb4;连接串里也要带characterEncodingutf8两头都对齐才能彻底解决。还有个容易忽略的点MySQL 8.0里utf8mb4和utf8之间转换兼容性没问题但存储emoji只能靠utf8mb4如果你在建一个二手交易系统的公告标题里加emoji插不进去多半就是字符集问题。5.3 上传图片失败或访问404上传失败先看控制台报什么。MaxUploadSizeExceededException表示超过了spring.servlet.multipart.max-file-size上限调大配置即可。如果上传成功却访问不到图片百分之九十是路径没对上。我把图片统一放在/upload/目录又通过配置类暴露静态资源映射Configuration public class ResourceConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }这段代码中uploadPath是绝对路径写错的话即使图片在磁盘上浏览器也访问不了。建议开发时直接把uploadPath打印出来确保file:/home/xxx/campus-market/upload/最后带个斜杠少了斜杠也会404。5.4 端口被占用与端口随机配置在IDEA里启动项目时提示Port 8080 was already in use说明8080被别的进程占用了。Windows上执行netstat -ano | findstr 8080Linux或macOS上执行lsof -i:8080找到PID后直接杀掉对应进程。如果不想每次换端口也可以把server.port配置成0让SpringBoot自动随机选择端口——但这样你每次启动端口都不同对调试反而增加负担不建议毕设演示时用。演示前最好确认端口没有被占用把固定端口写死答辩时稳定性最高。5.5 SpringBoot项目反编译与源码阅读建议热词里有个“怎么将springboot jar反编译成项目”这背后是很多同学遇到一个能跑的项目但看不到源码时的困惑。当别人给你一个编译好的JAR包你可以用jd-gui或luyten工具打开JAR包查看class文件里的反编译源码也可以用IDEA自带反编译功能打开。这个技巧的实用场景是你拿到一个网上找的二手交易系统源码编译出的JAR包和源码对不上或源码丢了那就只能靠反编译来还原结构。但从做毕设的角度我建议你把主要精力放在自己写代码和理解逻辑上反编译可以当作辅助工具不要把别人JAR包里的代码直接粘贴到自己的项目里那样不仅不懂原理答辩也容易露馅。5.6 其他容易被忽略的高频问题时间字段差8小时的问题前面提过连接串加serverTimezoneAsia/Shanghai即可。还有个关于Lombok的问题明明在实体类写了Data但调用getter/setter还是报错多半是IDEA没安装Lombok插件或者没有开启Annotation Processing。这个问题折腾的人很多要先检查插件再检查Settings - Build - Compiler - Annotation Processors是否勾选了Enable annotation processing。6. 做这个项目后我总结出的几点经验最后聊点个人体会是我自己做这个系统以及辅导其他同学时的总结。第一毕设项目最忌讳“贪多”。二手交易系统真正把核心交易闭环做好就够了没必要硬凑一堆用不上的模块像秒杀、优惠券、IM聊天。功能堆得多每个功能都是浅层次实现答辩老师问深一点你就慌。把商品、订单、搜索、评价这几条主链路做扎实让代码结构清晰比多塞五个花哨页面更有价值。第二自己在上手之前一定先把SpringBoot的运行机制、MyBatis-Plus的CRUD流程、拦截器是怎么生效的这三个点彻底搞明白。我见过很多同学代码都能跑但被老师问到“你这里的Autowired是按什么规则注入的”就答不上来。代码是结果原理才是你答辩时的护身符。第三项目里保留一个“亮点功能”会非常吃香。比如我在这个系统里给每个商品做了按浏览量自动排序的热门推荐其实实现就一行sorder(view_count, desc)但演示的时候提到它比单纯说增删改查有说服力得多。你也可以做一个简单的Redis缓存热门商品列表或者做一个Excel导出订单报表的功能成本不高但答辩效果完全不同。第四遇到问题记个排查笔记。像跨域、字符集、端口占用这些坑网上随便一搜答案满天飞但你自己踩一遍记录下来下次再遇到会非常快。我在项目文档里专门建了一个“开发日志”章节记录每天解决了什么问题这个文档毕业答辩时也能拿给老师看证明你的开发过程是完整的不是花钱买来或者网上拼凑的项目。如果你正在为这个题目忙得焦头烂额照着这篇里的思路重新审视一遍自己的项目数据库表是否完整、状态流转是否清晰、异常处理是否统一、配置是否规范。这些点补上了你的毕设就已经超过相当一部分人了。
返回列表