ARTICLE DETAIL

资讯详情

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

新闻管理系统开发实战:Java Web+MySQL从数据库设计到部署全流程

新闻管理系统开发实战:Java Web+MySQL从数据库设计到部署全流程 简介这是一份用于软件综合实习的新闻管理系统设计文档面向计算机相关专业学生及初级Web开发者可作为课程设计、毕业设计或实训项目的完整参考资料。文档从系统概述与需求分析入手详细梳理了前台新闻浏览与后台新闻管理两大模块前台涵盖主页导航、新闻分类导航、站内模糊搜索、分类显示及友情链接后台涵盖管理员设置、新闻信息管理、友情链接管理并给出了数据库设计、系统实现与安全性方面的设计思路。资源为单个doc文档压缩包仅77KB虽小巧但内容完整适合作为项目文档撰写或功能模块设计的对照模板。目前已有534人学习下载对于需要快速理解新闻管理系统架构并动手实现的学习者这份文档能提供清晰的功能结构划分和基础开发逻辑参考。1. 新闻管理系统从需求文档到能跑起来的完整落地课设和毕设的季节总有人拿到的只有一份写满功能描述的 doc而新闻管理系统就是这类题目里最常见的一种。它前台要按类别展示新闻、按内容做模糊搜索后台要管登录、管新闻、管友情链接表面看功能不多但真正从需求文档落到能跑的 Web 应用中间隔着数据库设计、权限过滤、文件上传和一堆编码坑。这篇把整套系统从需求拆到部署用 Java Web MySQL 这条最稳妥的路线完整走一遍。新手可以直接照着复现做过类似系统的人可以跳过前几章直接翻到常见问题那章看坑点。先说结论这套系统真正的难点不在 CRUD而在三个容易被忽略的细节——搜索需求里明确要求按新闻内容而不是标题查询、后台所有页面必须被登录状态保护、友情链接的 LOGO 上传要处理好相对路径。这三个点任何一个没做到验收的时候都会被指出来返工。2. 数据库设计先行六张表的结构与字段取舍2.1 为什么先定表结构把功能结构图映射成表拿到需求文档的第一步绝对不是写页面而是把功能结构图翻译成表结构。新闻管理系统前台有分类导航、新闻分类显示、站内搜索、友情链接后台有管理员设置、新闻信息管理、友情链接管理这些功能点对应下来一共六张表admin、category、news、friend_link再加两张扩展表 comment 和 admin_log。comment 表是为了给新闻加评论功能预留的需求文档没提但很多课设题目会现场加这个要求先建上不亏。admin_log 是操作日志记录管理员在后台做了哪些操作这个表在答辩时很加分因为需求里写了「后台由管理员进行管理维护保证了网站的安全性」日志就是安全性的最直接体现。建表 SQL 如下CREATE DATABASE IF NOT EXISTS news_system DEFAULT CHARACTER SET utf8mb4; USE news_system; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE news ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT NOT NULL, title VARCHAR(200) NOT NULL, content TEXT, author VARCHAR(50), publish_time DATETIME DEFAULT CURRENT_TIMESTAMP, click_count INT DEFAULT 0, CONSTRAINT fk_news_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE friend_link ( id INT PRIMARY KEY AUTO_INCREMENT, site_name VARCHAR(100) NOT NULL, site_url VARCHAR(255) NOT NULL, logo_path VARCHAR(255) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE comment ( id INT PRIMARY KEY AUTO_INCREMENT, news_id INT NOT NULL, nickname VARCHAR(50), content VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE admin_log ( id INT PRIMARY KEY AUTO_INCREMENT, admin_id INT, action VARCHAR(200), log_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段 SQL 的逻辑分三层。news 表里的 category_id 是外键关联 category 表的 id保证每条新闻必然属于一个存在的类别。admin_log 里的 admin_id 并不建物理外键因为日志表只要记录是谁做的操作就够了强外键反而会让删除管理员变得麻烦。如果后台要求管理员能删除物理外键会造成删除失败或者级联删掉日志数据。使用 utf8mb4 而不是 utf8是因为 utf8 在 MySQL 里只能存 3 字节的字符一些生僻字和表情符号会直接报错或者变成问号。新闻内容里可能出现特殊符号这一步直接用 utf8mb4 省得后面返工。接下来插入需求里列出的八个新闻类别INSERT INTO category (category_name) VALUES (时政要闻),(财经观察),(世界军事),(科学教育), (法制道德),(社会百态),(体育世界),(时尚娱乐);类别数据是需求文档写死的必须原样录进去。注意不要在 category 表里加排序字段因为前台分类导航就是按插入顺序显示的加字段反而会让逻辑复杂化。2.2 表字段怎么定类型、长度与外键的取舍news 表的 title 用 VARCHAR(200)是考虑到中文标题一般不超过 60 个字200 的余量足够容纳特殊情况。content 用 TEXT 类型MySQL 的 TEXT 最大存储 64KB一条新闻的正文完全够用。如果题目要求支持长文就改成 MEDIUMTEXT但课设级别用 TEXT 就够了。publish_time 用 DATETIME 并设置 DEFAULT CURRENT_TIMESTAMP这样在插入新闻的时候不需要手动写时间。click_count 加 DEFAULT 0新闻详情页每次被访问就加一这个字段可以用来做热点新闻排序答辩时可以作为扩展功能的素材。category_id 和 news_id 这两个外键字段注意类型必须和主表完全一致都是 INT否则 JOIN 查询会走全表扫描。friend_link 表的 site_url 用 VARCHAR(255)因为有些网站地址带着一长串参数255 是 URL 字段的常见安全值。2.3 JDBC 连接配置三处必须改对的参数数据库建好后Java Web 项目要连上 MySQL核心就在 jdbc.properties 这个配置文件。用 properties 文件而不是硬编码在 Java 里是为了换环境时不用重新编译。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/news_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456这里三个参数最容易出问题。characterEncodingutf8 负责应用和数据库之间的中文编码不写它中文存进去是乱码读出来也是乱码。serverTimezoneAsia/Shanghai 是 MySQL 8.x 必须带的不写会报时区错误。useSSLfalse 是在本机开发时关闭 SSL 握手不然每次都报 warning虽然不影响运行但会干扰排查其他问题。对应的 JDBC 工具类import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static String url; private static String user; private static String password; static { try { InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(jdbc.properties); Properties props new Properties(); props.load(in); Class.forName(props.getProperty(jdbc.driver)); url props.getProperty(jdbc.url); user props.getProperty(jdbc.username); password props.getProperty(jdbc.password); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } }这段代码把配置文件读取和驱动加载放在静态代码块里类第一次被加载时就执行。如果配置文件缺失或驱动类名写错会直接抛 ExceptionInInitializerError项目一启动就能发现问题而不是跑到第一个查询才报 ClassNotFoundException。getConnection 每次返回新连接课设项目并发量不大这样够用了。3. 前台功能拆解分类导航、模糊搜索与新闻展示3.1 三层页面结构index.jsp、list.jsp、detail.jsp 各自负责什么前台的页面职责必须拆清楚否则所有逻辑都堆在一个 JSP 里会变成难以维护的巨型页面。我的做法是拆成三个文件index.jsp 显示主页导航、八个分类的最新新闻标题列表、搜索框和友情链接区list.jsp 接收类别 ID 参数显示某个分类下的全部新闻列表并支持分页detail.jsp 接收新闻 ID 参数显示新闻全文并累加点击量。三个页面共用同一个顶部导航区可以抽成 header.jsp 用 include 引入。横幅广告条、LOGO、当前日期、分类导航按钮都在 header.jsp 里这样改导航只用改一处。主页上的「MORE」按钮指向 list.jsp?categoryId某个值新闻标题指向 detail.jsp?newsId某个值参数传递用 GET 请求足够。3.2 分类导航与列表页分页 SQL 的正确写法分类列表页的核心是新闻数据访问对象也就是 DAO 层的查询方法。这个查询有两个要点一是用 PREPARED 语句防止 SQL 注入二是用 LIMIT 做分页防止一次查出太多数据。public ListNews findNewsByCategory(int categoryId, int offset, int limit) { String sql SELECT id, category_id, title, author, publish_time FROM news WHERE category_id ? ORDER BY publish_time DESC LIMIT ?, ?; ListNews list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, categoryId); ps.setInt(2, offset); ps.setInt(3, limit); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { News news new News(); news.setId(rs.getInt(id)); news.setCategoryId(rs.getInt(category_id)); news.setTitle(rs.getString(title)); news.setAuthor(rs.getString(author)); news.setPublishTime(rs.getTimestamp(publish_time)); list.add(news); } } } catch (SQLException e) { e.printStackTrace(); } return list; }LIMIT 后面的问号参数也是用 setInt 设置的MySQL 支持在 LIMIT 子句里使用占位符。offset 表示从第几条开始取limit 表示取几条。每页显示 5 条新闻的话第一页 offset0第二页 offset5第三页 offset10。还有一个关键点列表页的 SELECT 不要查 content 字段正文很长列表页只需要标题、作者、发布时间和所属分类查出来的数据越少页面渲染越快这个习惯到企业开发里也是通用的。3.3 站内搜索按内容而非标题模糊查询的正确实现需求文档里明确标注了一句话此查询中按新闻内容进行查询而不是新闻标题。这是很多人会踩的坑默认思维里搜索就是搜标题但这个题目偏偏要求搜正文。搜索逻辑写在 SearchServlet 里接收 keyword 和 categoryId 两个参数。public ListNews searchNews(String keyword, int categoryId) { StringBuilder sql new StringBuilder( SELECT id, category_id, title, content FROM news WHERE content LIKE ?); if (categoryId 0) { sql.append( AND category_id ?); } sql.append( ORDER BY publish_time DESC); ListNews list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql.toString())) { ps.setString(1, % keyword %); if (categoryId 0) { ps.setInt(2, categoryId); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { News news new News(); news.setId(rs.getInt(id)); news.setTitle(rs.getString(title)); news.setContent(rs.getString(content)); list.add(news); } } } catch (SQLException e) { e.printStackTrace(); } return list; }这段代码有两个必须理解的点。第一个是通配符 % 拼接在参数值里而不是 SQL 语句里PreparedStatement 会把整个值当作普通字符串处理关键字里的单引号、百分号都不会破坏 SQL 结构。第二个是 if (categoryId 0) 的写法这个条件区分了两种情况下拉列表选了「全部类别」时 categoryId 传 0SQL 只看内容选了具体类别时追加 AND category_id ?在内容匹配的基础上再按类别过滤。搜索结果里要把 content 中命中的那一段截取出来加高亮做法是把内容按 keyword 切割取前后各 30 个字符作为摘要。这个功能在答辩时很好讲评委问起来也容易说清楚。3.4 新闻详情页点击量自增的原子性detail.jsp 显示新闻全文前要做两件事根据 newsId 查出新闻记录同时把 click_count 加一。很多人把这两步做成先查再更新这在高并发下会丢计数。正确做法是一条 UPDATE 完成自增UPDATE news SET click_count click_count 1 WHERE id ?;这条 SQL 依赖数据库自身的原子操作不需要先查出来再算不会出现两个请求同时读到相同值的问题。查新闻详情的 SQL 用 JOIN 把类别名称带出来因为详情页要显示「所属分类」这个信息。SELECT n.id, n.category_id, c.category_name, n.title, n.content, n.author, n.publish_time, n.click_count FROM news n LEFT JOIN category c ON n.category_id c.id WHERE n.id ?;4. 后台管理实现登录验证、新闻 CRUD 与友情链接维护4.1 登录验证的完整链路LoginServlet 与 Session 的配合后台入口在前台页面最下方单击进入 login.jsp。登录表单提交到 LoginServlet验证逻辑是先对密码做 MD5 加密再与数据库比对。密码不能明文存储这是个基本的安全意识问题。WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password MD5Util.md5(request.getParameter(password)); Admin admin new AdminDao().findByUsernameAndPassword(username, password); if (admin ! null) { request.getSession().setAttribute(loginAdmin, admin); response.sendRedirect(request.getContextPath() /admin/index.jsp); } else { request.setAttribute(msg, 用户名或密码错误请重新输入); request.getRequestDispatcher(login.jsp).forward(request, response); } } }登录成功用 sendRedirect 重定向到后台主页失败用 forward 转发回登录页并携带错误提示。重定向和转发的差别在于重定向会让浏览器重新发起一次请求地址栏变成后台主页用户刷新页面不会重复提交表单转发是服务端内部跳转地址栏还是 /login刷新会再次触发表单提交。登录成功必须用重定向这是个细节但很重要。Session 在登录成功后写入 loginAdmin 属性之后的每个后台请求都靠这个属性判断是否已登录。MD5Util.md5 是工具方法把输入的字符串转成 32 位十六进制摘要。MD5 不算强加密但课设级别够用如果想让答辩更有亮点可以换成加盐 MD5也就是 MD5(密码 固定盐值)。后台的安全保护靠一个 Filter 拦截所有 /admin/ 路径。这个过滤器是整个后台管理系统的安全底线。WebFilter(/admin/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session ! null session.getAttribute(loginAdmin) ! null) { chain.doFilter(req, resp); } else { response.sendRedirect(request.getContextPath() /login.jsp); } } }request.getSession(false) 表示如果当前没有 Session 就返回 null而不是创建一个新的。这样设计有两个好处未登录用户访问后台页面时不会被强制创建 Session节省内存已经登录但 Session 超时的用户会被正确拦截到登录页。如果写成 getSession(true)未登录用户也会拿到一个空 Session过滤条件永远不成立照样被拦截只是白白创建了无用对象。4.2 新闻信息管理添加、修改、删除与下拉框联动后台新闻管理三个操作对应三个 Servlet 或者一个 Servlet 加 method 参数区分。添加新闻的表单要包含三个字段新闻类别下拉框、新闻标题输入框、新闻内容文本域。下拉框的数据要动态读取 category 表不能在 JSP 里写死因为类别可能被后台维护。WebServlet(/admin/news/add) public class AddNewsServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); int categoryId Integer.parseInt(request.getParameter(categoryId)); String title request.getParameter(title); String content request.getParameter(content); String author admin; String sql INSERT INTO news (category_id, title, content, author) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, categoryId); ps.setString(2, title); ps.setString(3, content); ps.setString(4, author); ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } response.sendRedirect(request.getContextPath() /admin/news/list.jsp); } }发布时间的填充依赖数据库的 DEFAULT CURRENT_TIMESTAMP所以 INSERT 语句里不用写 publish_time 字段。修改新闻是 UPDATE 语句加 WHERE id ?删除是 DELETE 语句。删除时要注意有一个常见需求删除新闻后评论区数据如何处理。如果没有外键约束评论会成为孤儿数据所以删新闻之前先执行 DELETE FROM comment WHERE news_id ?再删新闻本身。4.3 友情链接管理LOGO 上传与保存路径的处理友情链接管理是后台功能里最容易出问题的模块核心在于 LOGO 图片的上传。Servlet 3.0 提供的 Part API 可以处理文件上传不需要额外引入第三方库。WebServlet(/admin/link/add) MultipartConfig(maxFileSize 1024 * 1024 * 2) public class AddLinkServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String siteName request.getParameter(siteName); String siteUrl request.getParameter(siteUrl); Part part request.getPart(logo); String submittedFileName part.getSubmittedFileName(); String ext submittedFileName.substring(submittedFileName.lastIndexOf(.)); String fileName UUID.randomUUID().toString().replace(-, ) ext; String uploadPath getServletContext().getRealPath(/uploads/link); File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } part.write(uploadPath File.separator fileName); String sql INSERT INTO friend_link (site_name, site_url, logo_path) VALUES (?, ?, ?); // 执行参数siteName、siteUrl、uploads/link/文件名 } }MultipartConfig 注解的 maxFileSize 限制单张图片最大 2MB超出会抛异常。UUID.randomUUID() 生成随机文件名是为了防止不同网站上传相同文件名的 LOGO 互相覆盖比如两个站都传了 logo.png不用 UUID 的话后上传的会覆盖先前的。part.write 把文件写到服务器磁盘的 uploads 目录数据库里存的是相对路径 uploads/link/文件名而不是磁盘绝对路径。数据库存相对路径是关键部署到不同机器、不同操作系统绝对路径都不一样写死绝对路径等于写死环境。页面引用 LOGO 时用${pageContext.request.contextPath}/uploads/link/xxx.pngTomcat 会把 uploads 目录映射成 URL前端直接访问即可。5. 常见问题与避坑登录失效、中文乱码与图片上传的五个坑课设验收前我会强制自己和学生把下面这五类问题完整踩一遍都是实际项目中反复出现的真实翻车点。坑一搜索结果为空明明数据库里有关键字。现象在前台搜索框输入新闻里出现过的词结果一条都查不出来。原因需求要求按内容搜索但 DAO 的 SQL 写成了 WHERE title LIKE ?标题里恰好没这个词。还有另一个可能keyword 从前台传到 Servlet 时编码不对中文变成了乱码乱码当然匹配不到任何记录。解决先把 SQL 改成 content LIKE ?确认字段选对了再在 Servlet 里检查 request.getParameter 拿到的值是否正常不正常就检查请求编码过滤器是否配置。坑二页面全是问号中文乱码成「???」或「锟斤拷」。现象新闻标题在列表页显示为问号后台添加的中文新闻内容存进数据库后变成乱码。原因MySQL 连接 URL 少了 characterEncodingutf8或者 jsp 页面本身没声明 UTF-8。解决jdbc.url 里加上 useUnicodetruecharacterEncodingutf8JSP 顶部写% page contentTypetext/html; charsetUTF-8 %再补一个全局编码过滤器把所有请求统一设置为 UTF-8。WebFilter(/*) public class EncodingFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { req.setCharacterEncoding(UTF-8); resp.setCharacterEncoding(UTF-8); chain.doFilter(req, resp); } }坑三友情链接的 LOGO 图上传播放后就 404。现象后台提示上传成功友链记录也在但前台图片区域是破图。原因数据库存的是磁盘绝对路径比如 D:/apache-tomcat/webapps/news/uploads/...部署机器的目录结构一换路径就失效。解决数据库只存相对路径 uploads/link/文件名JSP 页面用${pageContext.request.contextPath}拼完整地址。坑四后台页面不登录也能直接访问。现象地址栏输入 http://localhost:8080/news/admin/news/list.jsp 直接打开后台列表页没有任何拦截。原因WebFilter(/admin/) 没有生效或者根本没写 LoginFilter。解决检查 Filter 类上有没有 WebFilter 注解注意注解路径必须以 / 开头确认项目是 Servlet 3.0 及以上版本Tomcat 7 支持注解扫描。如果用的是 web.xml 配置过滤器还要检查url-pattern是否写成 /admin/。坑五登录时密码输入正确还是提示错误。现象第一次用管理员账号登录前台底部入口总是提示用户名或密码错误。原因直接在数据库里插入了一条明文密码比如 INSERT INTO admin VALUES (1, admin, 123456)但登录校验时把用户输入转成 MD5 再比对两者当然不一致。解决用工具类把密码转成 MD5 再写入数据库public static String md5(String source) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(source.getBytes(UTF-8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (Exception e) { throw new RuntimeException(e); } }然后执行 UPDATE admin SET password 转换后的密文 WHERE username admin。从那以后我每次初始化项目都会先准备好一条 MD5 后的管理员数据不会再在这个问题上浪费时间。6. 部署上线前的验证清单用一条路径测透前后台系统写完不等于验收通过建议按下面这条路径完整走一遍每一环节出错都能马上定位。先把 six_tables.sql 导入 MySQL修改 jdbc.properties 里的用户名密码然后把项目打包成 WAR 部署到 Tomcat 的 webapps 目录启动。步骤操作预期结果1访问 http://localhost:8080/news/主页显示八大分类、当前日期、搜索框、友情链接区2点击「财经观察」分类跳转 list.jsp只显示该分类的新闻每页最多 5 条3点击「MORE」按钮显示该分类全部新闻列表翻页正常4搜索框输入「GDP」类别选「全部」列出所有内容包含「GDP」的新闻5点击任意新闻标题详情页显示完整内容刷新后点击量 16页面底部点击「后台入口」跳转 login.jsp7不登录直接访问 admin/news/list.jsp被过滤器拦截重定向回登录页8输入管理员账号密码登录跳转后台主页Session 写入成功9后台添加一条新闻选择类别、填标题和内容前台对应分类下出现新新闻10后台修改刚才的新闻标题前台列表页和详情页同步更新11后台添加一个友链并上传 LOGO前台底部友情链接区显示新链接和图片12点击「安全退出」Session 清除返回登录页再访问后台被拦截这套流程覆盖了前台展示、搜索、详情、后台登录、过滤器拦截、新闻 CRUD、友链上传和退出登录全部关键路径。第 7 步是验证过滤器是否真的生效很多人部署完才发现后台裸奔这一步一定要测。第 8 步验证的是密码加密是否正确进不去就回头看 MD5 那一节。如果是做毕业设计还可以在这个基础上加两个低成本的功能点新闻评论模块把 comment 表用起来前台详情页展示评论列表、后台管理员可以删除违规评论操作日志把 admin_log 表用起来后台每次增删改都写入一条记录在后台管理页面加一个日志查询入口。这两个都是现有表结构直接支持的不需要改数据库只加界面和 Servlet 逻辑。从那以后我每次拿到任何课设题目都会先画一遍这张验证路径表再动手写代码而不是写完后随手点几个页面就当做完。希望帮到你。本文还有配套的精品资源点击获取
返回列表