Servlet+JSP+MySQL房屋租赁系统实战:生产级Java Web架构落地

发布时间:2026/9/14 3:24:31
Servlet+JSP+MySQL房屋租赁系统实战:生产级Java Web架构落地 简介这是一套基于Java Web经典技术栈开发的房屋租赁管理信息系统面向Java初学者与Web开发入门者提供前后端分离式JSPServlet的完整项目实践案例。系统覆盖用户注册登录、信息检索、出租/求租/出售/求购信息发布与管理、合同与财务统计等核心业务支持管理员与普通用户双角色操作适合作为课程设计、毕业设计或Servlet技术进阶学习的参考范例。资源包共681个文件以91个JSP页面实现前端交互、29个Java类封装后端逻辑、181个JS脚本支撑动态效果、195个GIF用于界面动效辅以CSS样式、HTML模板及MySQL建表SQL脚本整体压缩后仅2.6MB结构清晰、模块完整。目前已有261人学习下载包含完整的目录组织、可直接部署运行的代码结构、后台管理功能全覆盖以及轮播图、Ajax异步交互、密码找回等典型Web功能实现细节便于快速理解MVC分层思想与传统Java Web开发全流程。1. 这不是又一个“学生管理系统”ServletJSPMySQL 实现的房屋租赁系统真正在生产环境跑得动的轻量级 Web 架构落地路径你可能已经看过几十个“基于 JSP 的图书管理系统”“基于 Servlet 的学生成绩管理”但那些往往止步于 Eclipse 里右键 Run on Server——页面能打开表单能提交数据库能插数据就宣告成功。而真正需要支撑真实业务的房屋租赁系统必须面对多角色权限隔离房东、租客、管理员、房源状态实时流转待租→已预约→已签约→已退租、合同附件上传与下载、租金按月自动计算与提醒、以及最关键的——在 Tomcat 8.5 环境下不因 JSP 编译失败、Servlet 映射冲突或 MySQL 连接池耗尽而整站崩溃。本项目标题里的“含前后台”不是指两个 HTML 页面而是指一套可独立部署、具备完整业务闭环、且所有技术选型都严格对齐 Java Web 传统企业级开发规范的最小可行架构。它适合刚脱离课堂项目、正准备投递中小型企业 Java 开发岗的工程师也适合需要快速搭建内部资产/场地管理系统的运维或行政人员——因为它的全部依赖都在 JDK 8 Tomcat 9 MySQL 5.7 范围内无需 Spring Boot 自动装配、不引入任何云服务 SDK所有配置和逻辑都显式可控。2. Servlet 作为控制器中枢为什么不用 Filter 或 Listener 替代如何设计 URL 映射与请求分发逻辑2.1 Servlet 在本系统中的不可替代性从 HTTP 请求生命周期看控制权归属在房屋租赁系统中“发布房源”“查看待审核订单”“生成电子合同”等操作本质都是对特定资源如House、Order、Contract执行 CRUD。若用 Filter 拦截所有请求再做路由判断会导致业务逻辑与拦截逻辑耦合若用 Listener 监听 session 创建来初始化用户权限则无法响应具体业务动作。Servlet 的核心价值在于每个业务动作对应一个明确的、可调试的 Java 类其doGet()/doPost()方法天然承载了“接收参数 → 校验 → 调用 Service → 转发/重定向”的完整链路。例如PublishHouseServlet只负责解析表单字段、校验图片格式、调用HouseService.save()不处理登录态校验由LoginCheckFilter统一拦截也不渲染页面交由 JSP 完成。这种职责分离让代码可测试、可复用、可审计。提示不要在 Servlet 中直接写 JDBC 连接代码。本系统采用DBUtil工具类封装DataSource基于 Apache Commons DBCP2所有 Servlet 通过HouseService等业务类间接访问数据库确保连接复用与事务边界清晰。2.2 基于 web.xml 的显式映射避免注解式配置导致的部署兼容性问题虽然WebServlet(/publish)看起来简洁但在实际企业环境中Tomcat 7/8/9 对注解扫描的支持存在差异且部分老旧中间件要求web.xml存在。本系统采用传统web.xml声明方式确保一次打包、全版本 Tomcat 兼容!-- web.xml 片段 -- servlet servlet-namePublishHouseServlet/servlet-name servlet-classcom.rental.servlet.PublishHouseServlet/servlet-class /servlet servlet-mapping servlet-namePublishHouseServlet/servlet-name url-pattern/publishHouse/url-pattern /servlet-mapping servlet servlet-nameOrderListServlet/servlet-name servlet-classcom.rental.servlet.OrderListServlet/servlet-class /servlet servlet-mapping servlet-nameOrderListServlet/servlet-name url-pattern/admin/order/list/url-pattern /servlet-mapping关键点说明url-pattern必须以/开头且不能包含*.jsp或*.html否则会与 JSP 容器默认映射冲突后台路径统一加/admin/前缀便于后续通过 Filter 实现角色拦截所有 Servlet 类必须继承HttpServlet且service()方法不重写——交由父类根据请求方法自动分发到doGet()/doPost()。2.3 请求参数解析与中文乱码统一处理方案房屋信息表单常含中文小区名、户型描述、备注说明。若未处理request.getParameter(address)将返回??。常见错误是仅在doPost()中调用request.setCharacterEncoding(UTF-8)但doGet()的参数编码由 Tomcat URI 解码决定。正确做法是双管齐下// 在每个 Servlet 的 doGet/doPost 开头统一添加 protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. POST 请求体编码 request.setCharacterEncoding(UTF-8); // 2. GET 请求 URI 编码需同步修改 Tomcat conf/server.xml String uri request.getRequestURI(); // 3. 响应编码 response.setContentType(text/html;charsetUTF-8); }同时在conf/server.xml的Connector标签中添加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /注意URIEncoding是 Tomcat 特有属性Jetty 或 WebLogic 需用其他方式。本系统锁定 Tomcat故此配置为必需项。2.4 Servlet 间跳转策略RequestDispatcher.forward() vs HttpServletResponse.sendRedirect()场景推荐方式原因示例表单提交后显示结果页如“发布成功”sendRedirect()避免用户刷新导致重复提交URL 变更为目标地址利于书签收藏response.sendRedirect(publish_success.jsp);查询列表后展示数据如“我的房源列表”RequestDispatcher.forward()保持原始请求对象可传递request.setAttribute()数据URL 不变符合 RESTful 语义req.getRequestDispatcher(house_list.jsp).forward(req, resp);验证技巧在house_list.jsp中打印request.getRequestURL()若为/admin/house/list则是 forward若为/house_list.jsp则是 redirect —— 后者说明你误用了 sendRedirect丢失了请求上下文。3. JSP 作为视图层如何规避 scriptlet 泛滥、EL 表达式失效、以及页面跳转时的 session 失效问题3.1 JSP 模板结构标准化header.jsp / footer.jsp / menu.jsp 的包含逻辑本系统将公共 UI 抽离为三个标准片段通过jsp:include动态组装而非% include %静态包含后者在编译期合并无法传递动态参数!-- house_list.jsp -- % page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % !DOCTYPE html html headtitle我的房源列表/title/head body jsp:include page/WEB-INF/jsp/common/header.jsp/ jsp:include page/WEB-INF/jsp/common/menu.jsp/ div classmain-content h2房源管理/h2 c:forEach items${houseList} varhouse div classhouse-item h3${house.title}/h3 p地址${house.address} | 月租¥${house.rent}/p /div /c:forEach /div jsp:include page/WEB-INF/jsp/common/footer.jsp/ /body /html关键约束所有jsp:include路径必须以/开头表示相对于 Web 应用根目录header.jsp中定义base href${pageContext.request.contextPath}/确保 CSS/JS 路径不因包含层级错乱menu.jsp通过session.getAttribute(userRole)动态渲染菜单项实现角色差异化导航。3.2 EL 表达式与 JSTL 的强制启用解决 “${xxx} 不解析” 的 5 种排查路径当 JSP 中${house.title}显示为原文而非值绝非单纯“没配 JSTL”。按优先级逐项检查确认 web.xml 的 schema 版本 ≥ 2.4Servlet 2.4 才支持 ELweb-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1检查 JSP 页面开头是否声明isELIgnoredfalse虽默认为 false但显式声明防误% page isELIgnoredfalse %验证 JSTL 标签库是否正确引入本系统使用 JSTL 1.2下载jstl-1.2.jar和standard-1.1.2.jar放入WEB-INF/lib/JSP 中声明% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %注意URI 必须完全匹配http://java.sun.com/jsp/jstl/core不能写成http://java.sun.com/jstl/core确认 Servlet 设置 request 属性时未用错作用域// 正确存入 request 作用域供当前请求的 JSP 读取 request.setAttribute(houseList, houseList); // 错误存入 session除非跨请求共享 session.setAttribute(houseList, houseList);检查 Tomcat 的conf/web.xml是否禁用了 EL极少见但需排除!-- 确保此节点不存在或值为 false -- context-param param-nameorg.apache.jasper.compiler.Parser.STRICT_QUOTE_ESCAPING/param-name param-valuefalse/param-value /context-param3.3 屏蔽离开页面提示的精准控制只对关键表单生效不影响全局体验用户填写长表单如发布房源时意外关闭浏览器会导致数据丢失。但全站启用onbeforeunload会干扰后台管理页的正常跳转。本系统采用细粒度控制!-- publish_house.jsp -- script // 仅当表单被修改时才触发提示 let isFormModified false; document.addEventListener(DOMContentLoaded, function() { const form document.getElementById(houseForm); if (form) { form.addEventListener(input, function() { isFormModified true; }); window.addEventListener(beforeunload, function(e) { if (isFormModified) { e.returnValue 您有未保存的房源信息确定要离开吗; return e.returnValue; } }); } }); /script提示该脚本必须放在body底部或DOMContentLoaded事件中确保 DOM 加载完成后再绑定事件。若放在head中document.getElementById(houseForm)将返回 null。3.4 JSP 编译后的 Java 类位置与调试技巧定位 class 文件、热替换失效原因Tomcat 将 JSP 编译为 Servlet 类存放路径为work/Catalina/localhost/[your_app_name]/org/apache/jsp/。例如house_list.jsp编译后生成house_list_jsp.java和house_list_jsp.class。调试关键点修改 JSP 后未生效检查work/目录下对应.java文件的最后修改时间是否更新报错行号与 JSP 行号不符查看.java文件第 10 行可能是 JSP 的第 3 行因 JSP 指令、注释被编译为 Java 注释想查看 EL 表达式如何被翻译打开.java文件搜索out.print(其参数即为 EL 解析后的 Java 表达式禁用 JSP 编译缓存仅开发用在conf/web.xml的 JSP Servlet 配置中添加init-param param-namedevelopment/param-name param-valuetrue/param-value /init-param4. MySQL 数据库设计从 E-R 图到建表语句重点解决房屋租赁特有的状态机与关联查询性能瓶颈4.1 核心实体关系与状态流转设计为什么house_status不能用布尔值房屋状态不是简单的“上架/下架”而是包含业务语义的有限状态机状态值含义可触发操作状态迁移条件ONLINE待租已审核通过预约、下架房东主动下架或超期未租出BOOKED已预约租客已付定金签约、取消预约72 小时内未签约则自动释放LEASED已签约合同生效续租、退租合同到期或双方协商终止ARCHIVED已退租历史记录查看合同租期结束且无纠纷对应建表语句关键字段加索引CREATE TABLE house ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, address VARCHAR(200) NOT NULL, rent DECIMAL(10,2) NOT NULL, status ENUM(ONLINE,BOOKED,LEASED,ARCHIVED) DEFAULT ONLINE, landlord_id BIGINT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_status (status), INDEX idx_landlord (landlord_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_info ( id BIGINT PRIMARY KEY AUTO_INCREMENT, house_id BIGINT NOT NULL, tenant_id BIGINT NOT NULL, status ENUM(PENDING,CONFIRMED,CANCELLED,EXPIRED) DEFAULT PENDING, book_time DATETIME DEFAULT CURRENT_TIMESTAMP, expire_time DATETIME, -- 预约过期时间用于自动释放 INDEX idx_house_status (house_id, status), FOREIGN KEY (house_id) REFERENCES house(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意ENUM类型在 MySQL 5.7 中支持比VARCHAR节省空间且保证取值范围ON DELETE CASCADE确保删除房源时自动清理关联订单避免外键约束异常。4.2 关联查询优化解决“我的房源及最新订单状态” N1 查询问题典型需求管理员查看house_list.jsp时需显示每套房源的最新一条订单状态如“已预约”“待审核”。若在 JSP 中循环查SELECT * FROM order_info WHERE house_id? ORDER BY book_time DESC LIMIT 1将产生 N 次查询。正确解法单次 JOIN 查询 窗口函数MySQL 8.0或子查询兼容 5.7-- MySQL 5.7 兼容写法推荐本系统默认适配 SELECT h.*, o.status AS latest_order_status FROM house h LEFT JOIN ( SELECT house_id, status, ROW_NUMBER() OVER (PARTITION BY house_id ORDER BY book_time DESC) as rn FROM order_info ) o ON h.id o.house_id AND o.rn 1;若使用 MySQL 5.7ROW_NUMBER()不可用则改用相关子查询SELECT h.*, (SELECT status FROM order_info oi WHERE oi.house_id h.id ORDER BY book_time DESC LIMIT 1) AS latest_order_status FROM house h;性能验证命令-- 查看执行计划确认是否使用索引 EXPLAIN SELECT h.*, ... FROM house h LEFT JOIN ...; -- 检查慢查询日志开启后 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;4.3 MySQL 连接池配置DBCP2 参数调优防止“Connection closed”异常DBUtil使用 Apache Commons DBCP2关键参数需根据并发量调整public class DBUtil { private static BasicDataSource dataSource; static { dataSource new BasicDataSource(); dataSource.setUrl(jdbc:mysql://localhost:3306/rental_db?useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); // 核心连接池参数 dataSource.setInitialSize(5); // 初始化连接数 dataSource.setMaxIdle(20); // 最大空闲连接数 dataSource.setMinIdle(5); // 最小空闲连接数 dataSource.setMaxOpenPreparedStatements(200); // 防止 PreparedStatement 泄漏 dataSource.setValidationQuery(SELECT 1); // 连接有效性检测 SQL dataSource.setTestOnBorrow(true); // 借用连接时检测 dataSource.setRemoveAbandonedOnBorrow(true); // 连接泄漏回收 dataSource.setRemoveAbandonedOnMaintenance(true); dataSource.setAbandonedUsageThreshold(5); // 5 次借用未归还即回收 } }故障排查表现象可能原因检查命令Cannot get a connection, pool error Timeout waiting for idle object连接池耗尽SELECT COUNT(*) FROM information_schema.PROCESSLIST WHERE COMMANDSleep;Connection closed连接被 MySQL 主动断开wait_timeout 默认 28800 秒SHOW VARIABLES LIKE wait_timeout;需设置dataSource.setValidationQuery(SELECT 1);查询缓慢未命中索引EXPLAIN FORMATTRADITIONAL SELECT ...5. 前后台权限隔离与部署验证用真实 Tomcat 环境跑通全流程的 7 个必检环节5.1 前台/后台物理路径隔离/WEB-INF/目录的防御性设计本系统将所有 JSP 页面置于WEB-INF/jsp/下通过 Servlet 转发访问禁止直接 URL 访问。例如✅ 正确http://localhost:8080/rental/admin/house/list→OrderListServlet处理 →request.getRequestDispatcher(/WEB-INF/jsp/admin/house_list.jsp).forward(...)❌ 错误http://localhost:8080/rental/WEB-INF/jsp/admin/house_list.jsp→ Tomcat 返回 404WEB-INF下资源受容器保护验证方法在浏览器直接输入http://localhost:8080/rental/WEB-INF/应返回 404若返回目录列表说明 Tomcat 配置了listingstrue需在conf/web.xml中设为false。5.2 角色权限 Filter 实现精确拦截/admin/**路径拒绝未登录或越权访问public class AdminAuthFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getServletPath(); // 仅拦截后台路径 if (path.startsWith(/admin/)) { HttpSession session request.getSession(false); User user session ! null ? (User) session.getAttribute(user) : null; if (user null || !ADMIN.equals(user.getRole())) { response.sendRedirect(request.getContextPath() /login.jsp?erroraccess_denied); return; } } chain.doFilter(req, resp); } }web.xml中注册filter filter-nameAdminAuthFilter/filter-name filter-classcom.rental.filter.AdminAuthFilter/filter-class /filter filter-mapping filter-nameAdminAuthFilter/filter-name url-pattern/admin/*/url-pattern /filter-mapping提示Filter 的url-pattern必须与 Servlet 的url-pattern匹配规则一致。/admin/*可匹配/admin/house/list但不匹配/admin/末尾无*。5.3 Tomcat 部署全流程验证清单7 步必做步骤操作预期结果故障线索1将项目打成 WAR 包mvn clean package或手动压缩rental.war文件大小 5MB含 lib/jsp/css若 1MB说明WEB-INF/lib/未打包2将 WAR 包放入webapps/目录启动 Tomcatwebapps/rental/目录自动生成work/下出现编译文件若rental/为空检查catalina.out是否有SEVERE: Error filterStart3访问http://localhost:8080/rental/login.jsp显示登录页面无 404 或 500若报ClassNotFoundException: com.mysql.cj.jdbc.Driver检查WEB-INF/lib/mysql-connector-java-8.0.33.jar是否存在4输入测试账号登录如 admin/123456跳转至/admin/dashboard.jsp页面显示欢迎信息若跳转失败检查LoginServlet中response.sendRedirect()的路径是否带上下文名5点击“发布房源”填写表单并提交显示“发布成功”数据库house表新增记录若报NullPointerException检查PublishHouseServlet中request.getParameter()是否为空6登录租客账号预约一套房源order_info表新增记录house.status变为BOOKED若状态未更新检查OrderService中事务是否提交connection.commit()7停止 Tomcat清空work/和temp/目录重启所有功能仍正常JSP 重新编译若页面空白检查web.xml中welcome-file-list是否指向index.jsp5.4 MySQL 字符集终极校验从服务器到连接的四层 UTF-8 一致性房屋地址含中文必须确保字符集全链路一致-- 1. 服务器级别 SHOW VARIABLES LIKE character_set_server; -- 2. 数据库级别 SHOW CREATE DATABASE rental_db; -- 3. 表级别 SHOW CREATE TABLE house; -- 4. 连接级别JDBC URL 中已指定 -- jdbc:mysql://localhost:3306/rental_db?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai -- 若任一层非 utf8mb4执行修正 ALTER DATABASE rental_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE house CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;验证 SQLINSERT INTO house (title, address) VALUES (阳光花园, 北京市朝阳区建国路88号); SELECT title, address FROM house WHERE id LAST_INSERT_ID(); -- 结果必须为原样中文而非乱码或问号6. 生产环境就绪检查3 个被忽略却致命的部署细节与本地调试技巧6.1context.xml中 JNDI 数据源配置替代硬编码连接字符串的安全实践开发阶段用DBUtil直连 MySQL但生产环境应使用容器管理的 JNDI 数据源避免密码泄露!-- Tomcat conf/context.xml -- Resource namejdbc/RentalDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://prod-db-host:3306/rental_db?useSSLfalseamp;serverTimezoneAsia/Shanghai usernamerental_app passwordstrong_password_here maxActive50 minIdle5 validationQuerySELECT 1 testOnBorrowtrue/Java 代码中获取Context initCtx new InitialContext(); Context envCtx (Context) initCtx.lookup(java:comp/env); DataSource ds (DataSource) envCtx.lookup(jdbc/RentalDB); Connection conn ds.getConnection(); // 密码由 Tomcat 管理代码中不出现提示context.xml中的password仅对 Tomcat 进程可见若需加密可使用 Tomcat 的JndiRealm或外部密钥管理服务本系统暂不展开。6.2 JSP 编译选项调优解决org.apache.jasper.JasperException: Unable to compile class的根源当 JSP 含复杂 EL 或大量嵌套标签时可能因内存不足编译失败。在conf/web.xml的 JSP Servlet 配置中增加servlet servlet-namejsp/servlet-name servlet-classorg.apache.jasper.servlet.JspServlet/servlet-class init-param param-namefork/param-name param-valuefalse/param-value /init-param init-param param-namexpoweredBy/param-name param-valuefalse/param-value /init-param init-param param-namecompilerTargetVM/param-name param-value1.8/param-value /init-param init-param param-namecompilerSourceVM/param-name param-value1.8/param-value /init-param !-- 关键增大编译内存 -- init-param param-namejavaEncoding/param-name param-valueUTF-8/param-value /init-param /servlet同时在bin/catalina.shLinux或bin/catalina.batWindows中设置 JVM 参数# catalina.sh 末尾添加 JAVA_OPTS$JAVA_OPTS -Xms512m -Xmx1024m -XX:MaxMetaspaceSize256m6.3 本地调试技巧用curl模拟表单提交绕过浏览器缓存与 JS 干扰当页面提交无响应时用curl直接测试 Servlet 接口排除前端干扰# 模拟房东发布房源POST 表单 curl -X POST http://localhost:8080/rental/publishHouse \ -H Content-Type: application/x-www-form-urlencoded \ -d title测试房源 \ -d address上海市浦东新区张江路123号 \ -d rent8500.00 \ -d bedroom2 \ -d livingRoom1 \ -b JSESSIONIDABC123... # 复制浏览器 Cookie 中的 JSESSIONID观察响应返回302 FoundLocation: publish_success.jsp→ Servlet 逻辑正常跳转配置正确返回500 Internal Server Error→ 查看logs/catalina.out中堆栈定位PublishHouseServlet第几行抛异常返回400 Bad Request→ 检查request.getParameter()是否为 null确认表单字段名与 Servlet 中读取名一致。注意-b参数必须携带有效JSESSIONID否则触发登录拦截。可在浏览器开发者工具 Network 标签页中复制当前会话的 Cookie。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询