ARTICLE DETAIL

资讯详情

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

Java+JSP+MySQL学生信息管理系统课设全攻略:从环境搭建到War部署

Java+JSP+MySQL学生信息管理系统课设全攻略:从环境搭建到War部署 简介面向Java Web初学者的完整学生信息管理系统项目基于Java、JSP与MySQL实现覆盖从需求分析、数据库设计到前后端交互的典型开发流程适合课程设计、毕业设计或自学练手。压缩包共75个文件包含28个JSP视图页面、22个class编译类、11个Java源码、10个XML配置及1个SQL脚本等整体约3.92MB目录结构清晰。开发者可对照源码理解登录与权限管理、学生信息增删改查、条件查询和报表统计等核心模块同时学习JavaBean封装数据、Servlet处理请求以及通过SQL操作MySQL数据库的完整链路。项目采用经典B/S架构内置合理的分层设计与基础安全防护思路可帮助读者规避常见SQL注入等风险并体验从编码到测试的Web开发全流程。已有8989人学习下载适合希望快速积累实战经验的初学者和需要参考完整项目的高校学生。1. 为什么是JavaJSPMySQL学生信息管理系统这个题目老但不过时每年课设答辩季学生信息管理系统都是JavaWeb方向出现频率最高的题目之一。很多人在选题时会犹豫SpringBoot都卷到3.x了再交一个JavaJSPMySQL的Web项目会不会显得过时我的看法恰好相反。这门课设的价值不是「用最潮的框架」而是把Servlet生命周期、HTTP请求响应、JDBC数据库访问、Session状态管理这条链路亲手走通。JSPServlet正是这条链路最直接的呈现方式MySQL负责把数据真正落盘三者拼起来就是一个能演示、能答辩、能二次扩展的完整Web系统。本文按我实际做这类项目的顺序展开先定环境和骨架再定表和连接参数然后写登录、增删改查和分页最后把项目打成War包部署到独立Tomcat上。适合正在做课设、毕设或想补JavaWeb基础功力的读者。2. 环境与项目骨架用IDEA把传统JSP项目跑起来的完整路径2.1 选型先说清Servlet/JSP直出还是SpringBoot套壳先纠正一个误区题目写「JavaJSPMySQL」但很多同学会偷偷用SpringBoot把JSP塞进src/main/webapp里再配一堆Resolver。这种做法不是不行而是会让答辩变得很尴尬——老师问「请求怎么到Servlet的」「Tomcat怎么处理JSP的」你答不上来。传统的JSP项目走的是最朴素的一条链浏览器请求 → Tomcat解析URL找到对应的Servlet或JSP → Servlet调用DAO访问MySQL → 结果set到request → forward到JSP渲染输出。这条链上每个环节都肉眼可见没有框架帮你隐藏细节这正是课设想要你掌握的。我一般建议直接用ServletJSPJDBC三层结构不引入Spring、MyBatis。理由有三个第一依赖少整个项目只需要一个mysql-connector-java出问题好查第二JSP页面可以直接通过% %和JSTL输出数据不用学习额外标签库第三答辩时你可以讲清楚每一行代码在干什么这是框架黑匣子给不了的。如果你的老师要求「必须用框架」那再考虑SpringBoot套壳但那就不是本文的题目了。2.2 环境版本搭配JDK、Tomcat、MySQL的兼容边界版本组合是这类项目第一个踩坑点而且坑得毫无技术含量。常见翻车现场是JDK 17 Tomcat 10 mysql-connector-java 5.1启动直接报NoClassDefFoundError: javax/servlet/...。原因是Tomcat 10把包名从javax.servlet换成了jakarta.servlet而很多课设老代码还在写import javax.servlet.*。我的推荐组合是JDK 8或11 Tomcat 9.0.x MySQL 5.7或8.0 mysql-connector-java 8.0.x。这套组合下代码里用的还是javax.servlet和绝大多数教材、老项目完全一致驱动选择8.0.x是因为它同时兼容MySQL 5.7和8.0不用区分两个驱动包。如果你机器上已经装了JDK 17能跑但没必要课设场景JDK 8最稳Tomcat 9对JDK 8和11都支持良好。MySQL方面5.7和8.0都行。唯一要注意的是8.0默认的认证插件是caching_sha2_password老驱动连不上会报Unable to load authentication plugin所以驱动必须用8.0.x。数据库编码在建库时直接指定utf8mb4避免后面中文乱码。2.3 IDEA新建JSP Web项目的最小配置常见做法是用IDEA的Maven骨架建项目而不是手动创建目录。操作路径New Project → Maven → 勾选 Create from archetype → 选 maven-archetype-webapp。这个骨架会生成标准的Maven目录结构src/main/java、src/main/resources、src/main/webapp以及pom.xml。生成后需要补几件事。第一把src/main/java标记为Sources Root第二在webapp下建WEB-INF目录并补上web.xml新版骨架可能自动生成没有就手动建第三在pom.xml里加入Servlet API和JDBC驱动的依赖下面是我常用的最小配置dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency /dependencies build finalNamestudent-management/finalName /buildjavax.servlet-api的scope设为provided很关键意思是编译时用它但打包时不打进去因为Tomcat自己已经带了Servlet实现否则部署时会和Tomcat的类冲突。finalName决定打出的War包名字也就是部署后访问路径的上下文名我习惯设为student-management这样启动后访问地址是http://localhost:8080/student-management/。设置好之后在IDEA的Run Configuration里加一个Tomcat ServerDeployment里把构建产物选成student-management:war explodedApplication context填/student-management。这一步很多人漏了导致访问地址和预期不一致。配置完成后启动浏览器访问http://localhost:8080/student-management/能看到Maven骨架自带的index.jsp就说明Tomcat和项目关联成功了。3. 数据库设计与连接池学生信息表、用户表和连接参数一起定稿3.1 三张表的设计学生表、院系表、用户表一个能过答辩的学生信息管理系统数据库至少要有三张表用户表存登录账号院系表存学院或专业信息学生表存核心业务数据。很多同学只建一张student表把学院名称直接写死在里面这会导致两个问题一是数据冗余「计算机学院」每个学生都重复一遍二是老师追问「如果学院改名了怎么办」时答不上来。学生表字段我按下面这套设计覆盖常见需求又不过度设计CREATE DATABASE student_db DEFAULT CHARACTER SET utf8mb4; USE student_db; CREATE TABLE tb_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(20) ) ENGINEInnoDB; CREATE TABLE tb_dept ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL UNIQUE ) ENGINEInnoDB; CREATE TABLE tb_student ( id INT PRIMARY KEY AUTO_INCREMENT, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(20) NOT NULL, gender TINYINT DEFAULT 1 COMMENT 1男 0女, birthday DATE, phone VARCHAR(11), email VARCHAR(50), dept_id INT, enroll_date DATE, status TINYINT DEFAULT 1 COMMENT 1在读 0休学 2毕业, FOREIGN KEY (dept_id) REFERENCES tb_dept(id) ) ENGINEInnoDB;tb_user.password用VARCHAR(64)是为了以后能存SHA-256或BCrypt的密文而不是明文。gender和status用TINYINT加注释比VARCHAR省空间也方便前端用三元表达式映射中文显示。student_no加UNIQUE约束这是学生业务上的自然主键。外键dept_id关联院系表实现一对多关系导航时用JOIN查出院系名即可。建好表之后往tb_user插入一行测试数据比如用户名admin、密码先用明文123456项目能跑通后再改加密。别把这条忘了很多人写完整套代码才发现库里连个登录账号都没有。3.2 MySQL 8.0的连接参数driver、useSSL、serverTimezone、allowPublicKeyRetrievalJSP项目里数据库连接最容易出问题的地方不是SQL而是连接串参数。MySQL 8.0 mysql-connector-java 8.0.x 的经典连接串长这样String url jdbc:mysql://localhost:3306/student_db ?useSSLfalse allowPublicKeyRetrievaltrue serverTimezoneAsia/Shanghai characterEncodingutf8; String username root; String password 你的密码; Class.forName(com.mysql.cj.jdbc.Driver); Connection conn DriverManager.getConnection(url, username, password);四个参数各有各的用途。useSSLfalse是因为本地开发不需要加密连接不关掉的话MySQL 8.0会因SSL握手失败报Communications link failureallowPublicKeyRetrievaltrue是配合8.0的caching_sha2_password认证插件使用的不设置会在连接时提示Public Key Retrieval is not allowedserverTimezoneAsia/Shanghai解决时区报错默认UTC会让birthday这类日期字段读取时差8小时characterEncodingutf8保证驱动传输层使用UTF-8编码。驱动类名也别忘了8.0.x用的com.mysql.cj.jdbc.Driver不是老版本的com.mysql.jdbc.Driver。虽然新驱动会自动注册不用写Class.forName也能连但我建议保留这行一是兼容老写法二是答辩时能讲清楚「JDBC四步加载驱动、获取连接、执行SQL、释放资源」。3.3 连接池选型和Druid参数为什么课设也要用连接池如果DAO层每次查询都DriverManager.getConnection一次功能能跑但性能和代码质量都不过关。老师常问的一个加分题就是「为什么用连接池」答案很简单建立数据库连接是重操作包括TCP握手、认证、分配资源每次创建再释放很浪费连接池提前创建一批连接放着用的时候借、用完还能大幅减少开销。常见选择有Druid、C3P0、HikariCP。传统JSP项目里Druid用得多因为它自带监控页面答辩时能演示SQL执行统计算个小亮点。Maven里加Druid依赖然后在src/main/resources下建druid.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/student_db?useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password你的密码 initialSize5 maxActive20 minIdle5 maxWait3000初始化连接池的Java代码封装成一个工具类即可public class DbUtils { private static DruidDataSource dataSource; static { try (InputStream in DbUtils.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource (DruidDataSource) DruidDataSourceFactory .createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }initialSize5是池子启动时预先创建5个连接maxActive20是最大连接数minIdle5是池内最少空闲连接数maxWait3000是当池子满时调用方最多等3000毫秒超过就抛异常。课设场景并发不高maxActive不用调太大20就够。DruidDataSourceFactory.createDataSource(props)会自动读取properties里以driverClassName、url、username、password开头的配置并创建数据源。静态块里把数据源初始化好整个项目共用一个池子DAO层每次getConnection()都是从池中借连接。4. 功能实现登录拦截、学生CRUD、分页查询的完整代码串联4.1 登录与拦截Session Filter别把权限写在每个页面里登录功能是Web系统的门面也是答辩必问点。核心逻辑是登录页提交用户名密码 → LoginServlet查询tb_user→ 成功则把用户信息放进Session并跳转到主页面 → 失败则提示并留在登录页。代码不复杂但有一个设计点必须做好对未登录访问的拦截。新手常见写法是在每个页面开头都写一遍「判断Session里有没有user没有就跳回login」这是复制粘贴式开发最后总有页面漏写。正确做法是用Filter统一拦截。先写登录ServletWebServlet(/login) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); UserDao dao new UserDao(); User user dao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /student/list); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }再写一个过滤器拦截所有除登录页、静态资源外的请求WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); if (uri.endsWith(/login) || uri.endsWith(/login.jsp) || uri.contains(/css/) || uri.contains(/js/) || uri.endsWith(.jpg) || uri.endsWith(.png)) { chain.doFilter(req, resp); return; } if (request.getSession().getAttribute(loginUser) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); } }WebServlet(/login)是Servlet 3.0以后提供的注解写法省去在web.xml里配置Servlet映射。req.getContextPath()获取的是项目部署上下文路径用它拼URL可以避免项目改名或部署路径变化时跳转失效。Filter里的白名单很重要静态资源、登录页、登录接口必须放行否则会出现「登录页都打不开」或「CSS样式全丢」的问题。Session超时时间在web.xml里配session-config session-timeout30/session-timeout /session-config这个值单位是分钟30分钟表示用户30分钟无操作后Session自动失效需要重新登录。课设演示时建议改成60避免讲到一半Session过期被强制踢回登录页。4.2 StudentDAO增删改查的SQL和参数绑定DAO把SQL和业务逻辑隔离开JSP里不写SQLServlet里不写SQLSQL只出现在DAO层。学生管理最核心的四个操作是查询列表、新增、更新、删除每个都对应一个方法。以新增和查询为例public class StudentDao { private DbUtils dbUtils new DbUtils(); PreparedStatement ps null; ResultSet rs null; Connection conn null; public ListStudent findAll(int page, int pageSize) { ListStudent list new ArrayList(); String sql SELECT s.*, d.dept_name FROM tb_student s LEFT JOIN tb_dept d ON s.dept_id d.id ORDER BY s.id DESC LIMIT ?, ?; try { conn dbUtils.getConnection(); ps conn.prepareStatement(sql); ps.setInt(1, (page - 1) * pageSize); ps.setInt(2, pageSize); rs ps.executeQuery(); while (rs.next()) { Student s new Student(); s.setId(rs.getInt(id)); s.setStudentNo(rs.getString(student_no)); s.setName(rs.getString(name)); s.setDeptName(rs.getString(dept_name)); list.add(s); } } catch (SQLException e) { e.printStackTrace(); } finally { close(rs, ps, conn); } return list; } public int insert(Student s) { String sql INSERT INTO tb_student(student_no, name, gender, birthday, phone, email, dept_id, enroll_date) VALUES(?,?,?,?,?,?,?,?); // 参数绑定、执行和关闭逻辑同上的模式 } }PreparedStatement用?占位符通过setInt、setString绑定参数相比字符串拼接SQL的好处是防止SQL注入比如用户名传 or 11时不会拼出破坏性SQL其次是MySQL会缓存预编译语句多次执行同一条SQL时性能更好。查询列表用了LEFT JOIN关联院系表这样前端可以直接取dept_name不需要再查一次院系表。LIMIT ?, ?的第一个参数是偏移量第二个是每页条数分页的核心就这两行。finally块里必须按ResultSet → PreparedStatement → Connection逆序关闭资源顺序反了可能会出现连接泄漏。连接池里的连接用close()不是真的断开而是还回池中所以更要保证每条连接都走到close否则池里的连接会被借完项目跑一段时间后卡死。4.3 分页查询LIMIT参数、页码计算和JSP回显分页是学生管理系统里老师最常追问的功能点它的本质是「不要把全表数据一次性查出来放内存」。常见做法是前端传page当前页和pageSize每页条数后端算出偏移量(page-1)*pageSize执行LIMIT查询。同时还需要一条SELECT COUNT(*)的SQL来算总页数。Servlet端只需要把两个参数接住int page 1; int pageSize 10; try { page Integer.parseInt(req.getParameter(page)); pageSize Integer.parseInt(req.getParameter(pageSize)); } catch (NumberFormatException e) { // 参数缺失或非法时用默认值 }这里必须用try-catch包住parseInt。用户手输一个pageabc或者直接访问不带参数的URLNumberFormatException会把整个请求打挂页面直接500。用默认值兜底是课设项目里最容易加分的小细节。取到参数后调用DAO层查列表和总条数int total studentDao.count(); int totalPages (int) Math.ceil(total * 1.0 / pageSize); ListStudent list studentDao.findAll(page, pageSize); req.setAttribute(list, list); req.setAttribute(currentPage, page); req.setAttribute(totalPages, totalPages); req.getRequestDispatcher(/student_list.jsp).forward(req, resp);totalPages计算用的Math.ceil含义是向上取整总数23条每页10条总页数是3而不是2。最后一页不足10条是正常的不能因为查不到10条就报错。JSP页面里循环输出列表并生成分页导航c:forEach items${list} vars tr td${s.studentNo}/td td${s.name}/td td${s.deptName}/td /tr /c:forEach nav a href${pageContext.request.contextPath}/student/list?page${currentPage-1}上一页/a span${currentPage} / ${totalPages}/span a href${pageContext.request.contextPath}/student/list?page${currentPage1}下一页/a /nav如果使用JSTL要在JSP页面顶部加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %并在pom.xml里引入jstl依赖。上一页链接当currentPage-1小于1时要禁用下一页链接在currentPage1大于totalPages时也要禁用否则点到最后会查出一堆空数据。这里的pageContext.request.contextPath是JSP内置对象等价于Servlet里的getContextPath()用来拼项目根路径。5. 避坑清单JSP项目从404到中文乱码的5个典型翻车现场5.1 中文全是问号乱码问题不是一处配置能解决的现象页面能打开但数据库里的中文显示成???或者表单提交的中文存进库后变成乱码。原因乱码的根源是「请求编码、响应编码、页面编码、数据库编码」四个环节不一致。最常见的是JSP文件本身是UTF-8但浏览器提交请求时Tomcat默认用ISO-8859-1解码数据库表用了latin1编码或者是response.setContentType没设置浏览器用默认编码解析页面。解决四个环节一次性理顺。数据库层建库时指定utf8mb4第3章的建表语句里已经做了数据源连接串加characterEncodingutf8JSP页面顶部设置统一的编码指令Servlet接收参数前调用req.setCharacterEncoding(UTF-8)。关键是Servlet里这句必须在读取任何参数之前调用req.setCharacterEncoding(UTF-8); resp.setContentType(text/html;charsetUTF-8);另外一个隐藏点是Tomcat 9对GET请求参数的默认编码也是ISO-8859-1。如果表单用GET提交中文光设setCharacterEncoding没用需要改Tomcat的server.xml在Connector上加URIEncodingUTF-8。或者更简单所有表单强制用POST提交。5.2 Tomcat启动报端口占用分不清是8080被占还是项目重复部署现象启动Tomcat时控制台报Port 8080 required by Tomcat v9.0 Server at localhost is already in use启动失败。原因本机已有进程占用8080端口通常是上次Tomcat没正常关闭或者IDE里的Tomcat实例没有停止就又启动了一次。解决先确认是谁占用了端口。Windows下执行netstat -ano | findstr 8080 taskkill /PID 进程号 /FLinux或macOS用lsof -i:8080查看PID再kill -9。如果端口杀了还是报占用检查IDEA里是不是有多个Tomcat实例在跑全部停掉再重启。还有一种情况是war exploded和war两种deployment方式重复部署到同一个Context报错信息类似deploy is in progress这时在Deployment配置里只保留一种方式。5.3 Communications link failure驱动版本和连接参数一起背锅现象首次访问数据库时报Communications link failure后面跟着一串The last packet successfully received from the server was...有时还会提示Public Key Retrieval is not allowed。原因三种情况最常见。一是MySQL服务没启动连接被拒绝二是驱动版本和MySQL版本不匹配比如用5.x的驱动连MySQL 8.0三是连接串缺少8.0必需的参数导致SSL握手失败或公钥获取被拒。解决先确认MySQL确实在运行命令行执行mysql -uroot -p能进去再说。然后看驱动版本Maven依赖里确认是mysql-connector-java8.0.x。最后把连接串补全7个参数缺一不可jdbc:mysql://localhost:3306/student_db ?useSSLfalse allowPublicKeyRetrievaltrue serverTimezoneAsia/Shanghai characterEncodingutf8这个错误排查顺序不要反过来。很多人一看到Communications link failure就去改连接串折腾半天发现是MySQL服务根本没启动白花时间。5.4 登录成功后跳回登录页Session没存住还是Filter放行逻辑错了现象输入正确的用户名密码后页面闪了一下又回到登录页也没有报错提示。原因Filter的拦截逻辑把所有请求都拦了登录成功后sendRedirect跳转到列表页这个列表请求再次经过Filter时发现Session里没有loginUser又给踢回登录页。更隐蔽的一种可能是登录成功页跳转用了相对路径比如sendRedirect(student/list)浏览器解析地址错误落到一个不存在的URL上被Filter当未登录处理。解决在LoginServlet里确认设置Session的属性名和Filter里读取的名字完全一致。跳转一律使用req.getContextPath() /student/list这种带上下文的绝对路径不要用相对路径。排查时可以在Filter的拦截分支里加一行输出到控制台打印被拦截的URI和Session内容能很快定位是哪种情况。还有一种不太常见的原因是浏览器禁用了CookieTomcat用JSESSIONID标识SessionCookie被禁则每次请求都是新Session手动放行Cookie或改用URL重写可以解决但课设场景一般不用考虑。5.5 部署后CSS和JS全部丢失JSP里的相对路径坑现象本地用IDEA内置Tomcat跑页面样式正常打成War包部署到独立Tomcat后样式和脚本全丢了页面变成纯HTML文本排列。原因JSP里CSS引用的路径写成了css/style.css这种相对路径。浏览器解析相对路径时以当前页面URL的目录为基准。登录页在/student-management/login.jsp时css/style.css解析为/student-management/css/style.css没问题但学生列表页在/student-management/student/list时相对路径会解析成/student-management/student/css/style.css这个路径不存在CSS自然加载失败。解决所有静态资源引用必须加上项目上下文路径。在JSP页面顶部先定义上下文变量c:set varctx value${pageContext.request.contextPath} / link relstylesheet href${ctx}/css/style.css script src${ctx}/js/common.js/script或者在Filter白名单里直接放行所有/css/和/js/前缀的请求。这个坑还有一个变体页面用forward转发到JSP时浏览器地址栏还停留在Servlet路径上相对路径同样解析错误统一用${ctx}前缀是治本的办法。6. 打包War部署把传统JSP项目交到别人机器上跑通课设答辩到最后一公里往往是要把项目部署到另一台电脑或服务器上演示。传统JSP项目的交付物就是War包。用Maven打包最省事在项目根目录执行mvn clean package然后到target目录下找到student-management.war把它复制到Tomcat的webapps目录启动Tomcat后它会自动解压部署。这里有一个细节如果部署环境的Tomcat本来就在运行复制War包后要等几秒让它自动解压访问路径就是War包的文件名即http://服务器IP:8080/student-management/。如果想部署成根路径把War包改名为ROOT.war再放到webapps下访问时直接http://服务器IP:8080/即可。但要注意原来的ROOT目录要提前删掉否则Tomcat可能解压失败。部署完成后按这个顺序验证系统是否真正可用先访问登录页确认CSS加载正常用admin账号登录确认跳转到列表页新增一条学生记录确认MySQL里能查到修改这条记录并删除确认操作生效翻到第二页再翻回来确认分页参数没有串页。最后说说我自己的习惯。每次做完这类JSP项目我都会故意把环境版本降一档测试一次比如把MySQL从8.0换成5.7跑一遍把Tomcat从9换成8.5跑一遍。这个习惯帮我提前暴露了很多「本地能跑、换机器就崩」的问题尤其是驱动版本和连接参数对环境的敏感度能避免答辩前一天的深夜翻车。另一个教训是数据库脚本一定要单独存一份.sql文件放在项目里不要只留在本地MySQL里否则换机器部署时连建表语句都找不到。这套JavaJSPMySQL的组合虽然不够新潮但把这条链路走通之后你再去学SpringBoot、MyBatis理解映射和封装背后的原理会轻松得多。希望这篇笔记帮到你少踩几个我当年踩过的坑。本文还有配套的精品资源点击获取
返回列表