ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

高校汉服租赁网站系统:SpringBoot2+Vue3+MyBatis-Plus实战详解

高校汉服租赁网站系统:SpringBoot2+Vue3+MyBatis-Plus实战详解 直接上一个校园场景的Java Web项目SpringBoot2 Vue3 MyBatis-Plus MySQL8.0这套组合在找工作阶段实在见得太多但真把前后端串联起来、还能跑通的成品项目并不算多。最近整理了一份高校汉服租赁网站系统源码后端用的SpringBoot2前端用的是Vue3全家桶数据库操作交给MyBatis-Plus数据存储落在MySQL8.0上还附带完整的项目文档。这篇文章把项目从设计思路、数据库建模、接口逻辑到前端页面的落地方案都拆开讲适合正在准备毕业设计、或者想从前端转全栈的Java开发者参考拿来练手和改造成自己项目都很合适。1. 项目全景汉服租赁网站到底在做什么很多同学拿到这种校园项目思路的第一反应是小瞧它觉得不过是个增删改查。但真正动手把“租赁”这个业务抽象成数据模型和状态流转时细节比想象中要多。汉服租赁不像普通电商买东西一笔订单里涉及押金、租赁天数、逾期归还、服装状态变更这些都是实体店租赁业务在线上系统的核心映射。1.1 为什么是“高校汉服租赁”这个组合高校场景选得很有代表性。校园里的汉服社、文艺晚会、毕业照拍摄、成人礼仪式都催生了对汉服租赁的真实需求。学生客户群体集中租借周期短流转频率高还涉及社团集体租借和单件租借两类场景。这在业务建模上比普通电商多了两个特殊对象租赁时长和押金规则。从系统设计的角度看高校场景还有一个天然优势用户范围可控。系统不需要做开放注册用户可以限定为在校学生和社团管理员相比之下权限模型会简单一些但又不能完全没有。这在做技术方案时是一个很舒服的复杂度档位比纯电商系统上手门槛低但又比学生管理系统多了业务深度。另一个值得说的是文化层面的因素。汉服租赁系统的核心价值不只是服装信息管理它天然带有文化活动属性传统节日活动、社团招新、摄影采风都会用到。因此系统里可以扩展活动公告、文化科普这类内容板块让系统不只是一个“租衣服的工具”这也是这类项目在答辩时能讲出亮点的关键。1.2 系统角色与核心业务流程这套系统的角色拆分不复杂一共三个学生用户、社团管理员后台运营、系统管理员超级管理员。三者权限逐级递进前端页面也根据角色动态渲染。核心业务链条是“汉服上架 - 用户浏览 - 提交租借申请 - 管理员确认 - 用户取衣 - 归还验收 - 订单完结”这套完整闭环。其中管理员确认这个环节特别重要因为汉服租赁属于实物交割业务管理员需要线下确认服装状态后才能把订单状态推进到“租用中”不然会出现系统显示可租、线下实际已经被借走的库存不一致问题。还有一个重要的点是逾期处理。汉服租赁天然有时效性比如客户预约了周六的活动周五就必须拿到衣服。如果周六活动结束没有按时归还系统需要自动计算逾期天数并在归还验收时生成违约记录。我在台账表里专门设计了逾期罚金字段由管理员操作归还时手工录入或系统根据规则自动计算这样可以保留一定的人工裁量空间。1.3 项目文件结构导览拿到源码之后建议先看整体目录结构不必急着跑起来。项目分成backend和frontend两个大目录外加一个doc目录放配套文档。后端的包结构按模块划分比较清晰config包放配置类controller包放接口层service和serviceImpl放业务逻辑mapper放MyBatis-Plus的持久层接口entity放数据库实体映射common里放统一返回类和全局异常处理。前端用Vite初始化的Vue3工程src下分api、assets、components、router、store、utils、views这七个标准目录。前端没有采用网上那种过度封装的结构api目录下每个接口文件对应一个业务模块比如hanfu.js、order.js、user.js。这样开发时找接口很直接维护成本也低。views目录下再按页面划分用户端和管理后台通过路由分成两个layout布局权限控制由路由守卫统一拦截。2. 技术选型背后的取舍SpringBoot2Vue3MyBatis-PlusMySQL8.0这套组合现在几乎是Java Web全栈项目的标配但每选一个技术都有背后的考量。不是最新的技术就一定适合项目关键是团队熟悉度、生态成熟度和部署环境的兼容性。2.1 后端为什么选 SpringBoot2 而不是 SpringBoot3这里很多初学者会有疑问现在Spring Boot 3都出来了为什么还要用SpringBoot2最现实的答案是兼容性。SpringBoot3的底层从Java EE迁移到了Jakarta EE规范很多老版本的第三方库和封装都需要跟着升级而SpringBoot2无论是资料数量还是第三方整合的成熟度都处在最稳定的位置。本项目用的SpringBoot2.7.x是2.x系列里生命周期最长的维护分支包含了很多安全补丁和功能升级。JDK方面选择8或11都可以我这里用的是JDK8因为大部分高校的实验环境和在线判题环境仍然默认JDK8部署到学校服务器的时候不容易出问题。说句实在话毕设场景和中小型项目用SpringBoot2完全够用。除非你在简历上明确写着“熟悉SpringBoot3微服务生态”否则这个版本差异在面试官眼里不构成难点。反过来如果你能说清楚SpringBoot2和3在启动原理、自动装配机制上的变化反而是加分项。2.2 MyBatis-Plus 带来的开发效率提升MyBatis-Plus本质上是MyBatis的增强工具它只做增强不做改变所以原生MyBatis的所有特性都能用。项目里主要用了它的三块能力通用CRUD、条件构造器、分页插件。通用CRUD帮我们省掉了大量重复的XML文件。以往写一个用户表的增删改查需要写四五个Mapper方法再加对应的XML mapping。现在BaseMapper里已经内置了insert、deleteById、selectById、updateById等方法大部分单表操作零SQL解决。条件构造器是我觉得最舒服的功能。比如搜索汉服列表时需要按照名称模糊查询、分类筛选、状态筛选还要按价格排序。放在以前得在XML里拼接动态SQL代码多不说还容易写漏空格或逗号。现在用LambdaQueryWrapper几行代码就搞定类型安全字段名不会写错编译期就能发现错误。分页插件的配置也值得一提。项目里所有分页查询都通过MyBatis-Plus的分页插件实现只需要在config包下注册一个MybatisPlusInterceptor添加PaginationInnerInterceptor即可MySQL方言指定为DbType.MYSQL。前端传pageNum和pageSize两个参数后端统一封装成IPage返回。2.3 前端 Vue3 组合式 API 与生态对比前端用Vue3而不是Vue2最核心的差异在组合式API。Vue3里把一个页面的所有逻辑按功能去组织比如一个汉服详情页把“获取详情数据”、“尺寸选择”“加入租借清单”这几个逻辑函数放在一个setup里相关代码聚在一起比Vue2时代的data、computed、methods分散写法要清晰得多。项目在构建工具上选择Vite开发环境下启动速度比Webpack快一个量级热更新也是毫秒级的。Vite按需编译和原生ESM的特性让开发体验好很多调试时基本不需要长时间等待。状态管理用的是Pinia。它是Vue3官方推荐的状态管理库比Vuex轻量去掉了很多繁琐的mutations概念。直接把state和action写在同一个store文件里在组件里useStore之后就能正常访问代码量比Vuex少三分之一。UI库用的是Element Plus。表格、表单、弹窗、分页这些后台管理常用的组件都有现成的样式统一不用额外写大量CSS。用户端页面如果嫌弃Element Plus风格偏后台可以自己写一些卡片式布局组件库主要用来支撑管理后台页面。2.4 MySQL8.0 的部署选择MySQL8.0相比5.7最大的变化是默认字符集改为utf8mb4底层存储引擎默认InnoDB还加入了窗口函数、公共表表达式等新特性。这才是真正能存emoji和生僻字的方案比如服装名称里可能出现的生僻字在5.7的老版本utf8编码下会有乱码风险。数据库连接驱动方面MySQL8.0使用的驱动类改为com.mysql.cj.jdbc.DriverURL需要添加serverTimezone参数指定时区。这一点是新手最容易踩的坑报错内容通常是java.sql.SQLException: The server time zone value is unrecognized。解决办法是在JDBC连接串里加上serverTimezoneAsia/Shanghai并且建议把useSSL参数设置为false。本地开发直接用Navicat管理数据库就能做但部署到服务器时选择Docker来装MySQL8.0会方便很多。Docker安装MySQL8.0的步骤不复杂拉镜像、跑容器、映射端口、设置挂载目录几分钟就能得到一个干净的数据库实例。需要注意的是一定要做数据目录的挂载启动容器时用-v参数把容器内/var/lib/mysql映射到宿主机目录不然容器一删数据全没。docker run -d --name mysql8 -p 3306:3306 -e MYSQL_ROOT_PASSWORD你的密码 -v /usr/local/mysql/data:/var/lib/mysql -v /usr/local/mysql/conf:/etc/mysql/conf.d mysql:8.0如果你所在环境网络受限也可以直接用系统包管理工具安装MySQL8.0本质上差别不大。项目的数据库脚本在doc目录下有单独SQL文件导入后即可看到完整的表结构。3. 后端核心实现与数据库设计细节后端部分是这个项目最值得研究的地方。既要保证业务逻辑严谨又要体现SpringBoot2和MyBatis-Plus的整合能力。这一章重点拆解数据库设计和核心接口逻辑。3.1 数据库表结构与字段设计设计数据库表之前我画了简单的实体关系图一个用户对应多张租赁订单一张订单包含多个租赁条目可以同时租多件汉服每个租赁条目对应一个款式的汉服。此外还有独立的服装分类表、归还记录表。用户表设计的时候除了常规的id、username、password、phone之外加了role字段区分角色用Integer类型存储0是学生1是管理员2是超级管理员。加了一个status字段做禁用逻辑管理员可以把违规用户禁用禁用后该用户无法登录和下单。汉服表是核心表字段包含name、categoryId、size、color、price租赁单价、deposit押金、stock库存、coverImage封面图、description描述、status0在架可租、1已租出、2维护中。库存字段的引入是为了支持同款式多件同时出租而不是一件衣服对应一条记录这样数据维护成本低很多。订单表的设计需要关注订单号、用户ID、订单状态、租赁天数、起始日期、结束日期、租金总额、押金总额、实付金额、违约定金、备注。订单号我采用时间戳加随机数的方式生成保证并发时不会重复。租金总额的计算方式是单价乘天数再乘数量押金总额是单件押金乘数量。归还记录表记录每次订单的归还信息包括订单编号、归还时间、是否逾期、逾期天数、罚金金额、验收管理员ID、备注。这张表是后期统计逾期率、管理纠纷的重要依据。3.2 租赁核心流程的接口设计接口设计上遵循RESTful风格资源名是复数用名词不用动词。比如获取汉服列表是GET /api/hanfu/page创建订单是POST /api/orders确认订单是PUT /api/orders/{id}/confirm。创建订单这个接口是核心链路。用户从前端提交租借表单时携带的是汉服ID数组和各自的数量、租赁天数。后端收到请求后需要做四件事校验用户是否登录、校验服装是否在架可租、校验库存是否充足、计算租金总额和押金总额。任何一步校验失败都要返回明确的错误提示。校验通过后就往order表和order_item表各插一条数据订单状态为待确认。这里要注意事务控制用Transactional注解包住整个方法任何时候抛出异常都会回滚避免出现订单主表有了数据但明细表丢失的脏数据。确认订单接口是管理员操作的。管理员从前端列表看到待确认状态的订单点确认后系统会锁定对应的库存把汉服状态改为已租出同时把订单状态推进到租用中。这一步相当于线下“锁库存”防止同一件衣服被重复确认。归还接口的逻辑更复杂一些。管理员操作归还时系统会比对其中的结束日期和当前日期判断是否逾期。如果逾期则计算逾期天数按每天罚金规则累加罚金。同时把汉服状态改回可租订单状态改为待归还后续的已完成。3.3 权限控制与统一返回体后端权限部分没有引入Spring Security这类重型框架而是选择轻量级的拦截器加JWT方案。这个选择很务实Spring Security的过滤器链配置门槛高学起来耗时而校园项目的权限模型本来就简单用拦截器完全能覆盖。具体方案是用户登录成功后签发一个JWT令牌令牌里包含用户ID和角色信息。前端在请求头里带Authorization字段后端写一个LoginInterceptor拦截所有需要身份的接口从请求头解析令牌并校验合法性。如果校验失败直接返回401状态码并抛出未登录异常。对于管理员接口在拦截器里再判断用户的角色值非管理员角色直接返回403。拦截器的注册通过WebMvcConfigurer的addInterceptors方法实现排除掉登录、注册、汉服列表浏览等公开接口即可。只要做了这个访问控制前端就算怎么调整路由也是安全的。统一返回体也花了不少心思。后端所有controller的返回值都封装成统一的Result对象包含code、msg、data三个字段。code为200是成功非200是失败前端通过code判断和拦截器统一处理错误提示。这样好处是前端axios响应拦截器只需要判断code不需要区分各种HTTP状态码简化了错误处理逻辑。3.4 关键业务代码实践片段创建订单接口的事务控制直接贴出来重点看一眼Transactional和库存扣减这段逻辑。Transactional(rollbackFor Exception.class) public Result createOrder(OrderCreateRequest request) { User user UserContext.get(); ListOrderItemRequest items request.getItems(); // 遍历租借条目校验库存并计算费用 BigDecimal totalRent BigDecimal.ZERO; BigDecimal totalDeposit BigDecimal.ZERO; ListOrderItem orderItems new ArrayList(); for (OrderItemRequest item : items) { Hanfu hanfu hanfuMapper.selectById(item.getHanfuId()); if (hanfu null || hanfu.getStatus() ! 0) { throw new BizException(汉服不存在或不可租借); } if (hanfu.getStock() item.getQuantity()) { throw new BizException(《 hanfu.getName() 》库存不足); } // 扣库存 hanfu.setStock(hanfu.getStock() - item.getQuantity()); hanfuMapper.updateById(hanfu); totalRent totalRent.add(hanfu.getRentPrice() .multiply(BigDecimal.valueOf(item.getQuantity())) .multiply(BigDecimal.valueOf(request.getLeaseDays()))); totalDeposit totalDeposit.add(hanfu.getDeposit() .multiply(BigDecimal.valueOf(item.getQuantity()))); OrderItem orderItem new OrderItem(); orderItem.setHanfuId(hanfu.getId()); orderItem.setHanfuName(hanfu.getName()); orderItem.setPrice(hanfu.getRentPrice()); orderItem.setQuantity(item.getQuantity()); orderItems.add(orderItem); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(user.getId()); order.setTotalAmount(totalRent); order.setDepositAmount(totalDeposit); order.setStatus(0); order.setLeaseDays(request.getLeaseDays()); order.setStartDate(request.getStartDate()); order.setEndDate(request.getEndDate()); orderMapper.insert(order); for (OrderItem orderItem : orderItems) { orderItem.setOrderId(order.getId()); orderItemMapper.insert(orderItem); } return Result.success(order); }这段代码注释了两行关键逻辑一个是库存预校验一个是费用计算。库存扣减这里有一个并发隐患两个用户同时租同一件衣服的最后一库存理论上会有超卖风险。课程设计阶段不引入Redis分布式锁这种重型方案真正上线的话可以在扣库存SQL里加stock 0条件做原子更新。分页查询的逻辑也要注意。MyBatis-Plus的Page对象分页查询返回结果是IPage类型前端要的往往是records列表和total总条数。为了避免直接把实体类暴露给前端我在service层做了一次属性裁剪把汉服实体中不需要展示的字段隐藏掉返回干净的数据对象。4. 前端核心实现与技术要点前端部分最大的价值在于Vue3工程化实践。不是简单堆组件而是从项目初始化、路由设计、状态管理到接口封装的完整链路。4.1 项目初始化与目录规划Vue3项目的初始化我用的是Vite官方创建命令比Webpack版的vue-cli启动速度快很多。执行npm create vitelatest frontend选择Vue框架加JavaScript模板也可以选TypeScript但考虑到毕设上手难度JavaScript更轻快。初始化完成后第一件事是把项目目录按业务模块划分好。src/router下配置前端路由src/store下配Pinia的storesrc/api下按后端接口模块拆分请求文件。这样后期加一个功能模块只需要新建一个api文件加一个views目录下的页面结构非常清晰。有一点要提醒Element Plus的安装不要用全量引入几十个组件全注册到应用里会增加首屏加载体积。按需引入的配置方式比全量引入更有技术含量写起来也就多几行代码。使用unplugin-vue-components插件加自动导入配置这样在模板里用到的组件会被自动按需加载样式也不用手动引入。4.2 前端状态管理与请求封装状态管理这一块用Pinia。封装了一个userStore用来管理用户登录状态state里有token和userInfoaction里有login方法和logout方法。登录成功后调用localStorage存一份token刷新页面后通过初始化逻辑重新加载用户信息用户刷新页面不会丢登录态。请求封装是前端工程质量的关键。我在src/utils/request.js里封装了axios实例设置baseURL为后端接口地址请求拦截器统一在header上加Authorization字段响应拦截器统一处理错误码和HTTP异常。这样做的好处是所有业务页面里只需要直接调用封装的api函数代码简洁统一。对于列表类页面还封装了一个通用的分页混入逻辑。因为管理后台里所有列表页的结构都很相似搜索区加表格加分页器。把这一套逻辑抽成组合式函数useTable接收api函数和查询参数返回表格数据和分页数据页面里只需要调用useTable并绑定组件能省掉大量重复代码。4.3 关键页面落地产出用户端首页的设计思路是拖拽展示加分类导航。顶部导航栏放Logo和登录状态中间大块区域用轮播图推荐热门汉服下面按分类展示不同风格的服装卡片。汉服卡片组件是复用度最高的组件属性接收服装信息对象点击卡片跳转到详情路由。汉服详情页用了动态路由和页面传参。路由配置成/hanfu/:id进入页面后在onMounted生命周期里调用后端详情接口获取该汉服的全部信息。页面上展示商品图片、价格、押金、库存用户选择尺码和租借天数后点击“立即租借”。租借按钮的点击逻辑是先调用登录状态检查未登录直接跳转登录页已登录则跳转到租借确认页。管理后台页面都用Element Plus组件搭建。汉服管理页的表格包含封面小图、名称、分类、价格、库存、状态、操作按钮。操作按钮里的上架/下架是个很实用的功能点击后调更新状态接口状态的切换在前端通过el-switch组件绑定用户体验很直观。订单管理页用el-tabs做订单状态筛选待确认、租用中、已归还等状态入口一目了然。5. 本地运行全流程实操记录这一部分记录我拿到这套源码后从零到跑通的完整过程包括环境准备和每一步的具体操作照着走一遍基本不会卡壳。5.1 环境准备JDK、Maven、Node、MySQL后端要准备JDK8、Maven3.6以上版本前端要准备Node16以上版本数据库端准备MySQL8.0。这里有一个版本匹配的细节重点提醒Node版本过旧会导致Vite启动报错建议直接用Node18 LTS版本JDK8对应的是SpringBoot2.x最佳兼容版本不需要强行上JDK17。为了统一开发环境建议Maven配置阿里云镜像仓库不然下载依赖时去中央仓库的网速会让人很崩溃。配置方式很简单在Maven的settings.xml里加一个mirror节点镜像地址填阿里的仓库地址。前端npm也建议设置国内镜像源执行npm config set registry https://registry.npmmirror.com即可。MySQL我用Docker方式启动的整个过程记录如下拉取mysql:8.0镜像、创建并启动容器、设置root密码和端口映射、验证连接、导入项目SQL脚本。如果你本机已经安装了MySQL8.0直接创建数据库后导入SQL也可以项目脚本在doc目录下。5.2 从导入到跑通的具体步骤整个导入过程按顺序如下第一步导入SQL。用Navicat连接本地MySQL8.0新建一个数据库hanfu_rental字符集选择utf8mb4然后运行项目根目录下的hanfu_rental.sql脚本。导入完成后能看到十几张业务表说明脚本没有报错。第二步改后端配置。打开backend目录下的application.yml修改数据库的用户名、密码和连接URL。重点检查driver-class-name是否包含cj字段url是否带serverTimezone参数。配好后在Idea里打开backend项目点击Maven面板的刷新按钮等依赖下载完成。第三步启动后端。找到主类也就是带SpringBootApplication注解的类直接右键运行。控制台出现Started Application in X seconds的字样以及Tomcat started on port 8080的日志说明后端启动成功。第四步启动前端。在终端进入frontend目录npm install装依赖启动时直接用npm run dev命令。Vite默认端口5173浏览器打开localhost:5173就能看到系统首页。首次加载如果看到跨域报错那是因为前端页面调用后端8080端口接口需要先配置Vite代理。我在Vite的vite.config.js里配置了devServer.proxy把/api前缀的请求都代理到localhost:8080这样前端请求GET /api/hanfu/page等价于请求http://localhost:8080/api/hanfu/page。配置完重启Vite跨域问题就解决了。5.3 联调常见问题速查前后端联调阶段的报错大多集中在几个固定的坑位上列出我在实操时遇到的真实问题连接数据库报Public Key Retrieval is not allowed。这是MySQL8.0推出的一种安全机制在JDBC连接串里加上allowPublicKeyRetrievaltrue即可解决配合useSSLfalse使用。常见问题一大堆但不要慌。前端页面报404先检查后端接口路径是否匹配再看看代理配置是否生效。页面数据没有渲染打开浏览器F12看Network面板直接看接口返回的数据结构和预期是否一致。后端启动报Bean无法创建大概率是配置文件里数据库连接信息错误检查yml里密码是否含特殊字符有特殊字符的话需要加引号包裹。还有一坑是前端请求成功了但页面没有内容检查返回结果里data字段是否为空。这通常是数据库表里没有任何测试数据导入SQL后先手工插几条汉服记录和用户记录页面马上有数据展示。6. 二次开发扩展方向与踩坑记录这套系统跑通之后只能算完成基础需求离一个真正能用于社团运营的平台还有不少距离。后续的扩展空间其实很大这里列几个方向同时记录我在开发过程中踩过的坑。6.1 可扩展的方向支付和信用体系是最直接的扩展方向。现在的租赁流程是线下交接后续可以接入虚拟支付比如对接校园一卡通或模拟支付。还可以增加用户信用积分按时归还加分逾期扣分信用分高的用户可以享受免押金租借信用分过低则限制租借。这套体系的引入能大幅提升系统的完整度放在简历上也是很能打的亮点。消息通知和活动运营的方向同样值得做。可以增加一个公告模块发布传统节日活动和社团招新信息还可以在归还日前一天给用户发站内信提醒降低逾期率。这些功能的注册量不大但能把系统的交互体验拉高一个档次。从技术架构层面看如果想把技术亮点做得更足可以引入Redis做缓存热点汉服列表放到缓存里减少数据库压力还可以用Elasticsearch做服装搜索支持品牌名模糊搜索和规格筛选甚至可以通过Docker Compose把前端、后端、数据库打包成多容器一键部署方案。这些都可以在现有代码基础上逐步演进不必推倒重写。6.2 我踩过的一些坑第一个坑是登录接口的请求体格式。前端axios默认用JSON提交而后端我当时没有加RequestBody注解结果参数接收全是null。排查半天之后发现SpringMVC的普通参数接收方式默认不支持JSON请求体解决办法是在登录接口的参数上加RequestBody注解并把前端请求头的Content-Type设置为application/json。这个坑不大但是很典型。第二个坑是日期传参格式问题。创建订单时前端传起始日期和结束日期我用的是字符串格式yyyy-MM-dd后端LocalDate类型解析正常。但如果前端传的是带时分秒的格式后端就会因为格式不匹配而报解析异常。需要在后端增加DateTimeFormat注解并指定模式或者统一用JsonFormat注解做全局日期格式约定。第三个坑和MyBatis-Plus的字段映射有关。数据库表字段用了下划线命名实体类用了驼峰命名。MyBatis-Plus默认开启了下划线到驼峰的自动映射但前提是数据库表字段名大写或下划线规则要一致确保settings里map-underscore-to-camel-case为true不然后端查询返回的实体类字段全是null前端页面一片空白。第四个坑是前端代理失效的问题。本地调试的时候Vite代理是正常的但部署到服务器后前端页面请求still走前端静态资源的地址后端接口访问不到。部署时最省事的方式是后端配置CORS跨域过滤器放行前端域名或者直接把前后端部署到同一个域名下用Nginx做路径转发。这一块建议在部署文档里多写几句能帮别人省去很多试错时间。最后一个小技巧是关于文档的。项目配套文档除了常规的需求说明书和数据库设计文档之外建议再加一份部署手册把环境准备、数据库初始化、后端启动、前端构建、Nginx配置这些步骤全部用截图和命令记录下来。这份文档无论是答辩还是交给社团运营都是很拿得出手的交付物。
返回列表