资讯动态

原生Servlet+JDBC实现设备维修管理系统:源码解析与二开实践指南

发布时间:2026/10/8 20:44:46 来源:尧图企业网站定制
简介设备维修管理系统是一套基于原生Servlet与JDBC的JavaWeb项目源码面向计算机、电子信息等专业学生可直接用于课程设计、期末大作业或毕业设计参考也可作为学习原生Web开发的练手项目。压缩包仅2.84MB包含248个文件主要有54个Java源码文件及对应class文件、50个JavaScript脚本、17个CSS样式、13个PNG与24张JPG图片另有4个db和3个SQL数据库脚本以及XML、properties等部署配置结构完整便于直接导入运行还附有项目说明文档便于梳理整体设计思路。从核心类名可见系统实现了设备报修、维修记录、零件管理、客户管理、检修单生成和已完成任务查询等典型模块能够帮助学习者理解Servlet请求处理、JDBC数据访问及项目分层组织方式。目前已有346人学习下载适合有一定Java基础、希望掌握原生Web开发的读者借鉴并二次扩展功能也可在调整数据表与页面后用于设备报修流程演示。1. 设备维修管理系统原生 Servlet JDBC 的老派 JavaWeb 项目还值不值得看设备维修管理系统一套没上 Spring、没上 MyBatis、连 Maven 都没有的 JavaWeb 完整源码包控制层是原生 Servlet数据层是手写 JDBC拿到手就是一个可以直接导入 IDEA 的课程设计级项目。系统把设备报修、维修派工、配件明细、客户档案、完工查询这几条主线做成了闭环DAO 层全部手写 SQL对想弄清 HTTP 请求从 Tomcat 一路穿透到 MySQL 的完整链路的读者来说它比 Spring Boot 全家桶更有参考价值。适合三类人正在做 JavaWeb 课程设计或期末大作业的学生想补 Servlet 和 JDBC 底层原理的在职开发以及需要一套能快速跑通的后台管理模板来改二开的从业者。2. 从包结构和类名反推系统设计维修工单的完整闭环是怎么组织的拿到压缩包先别急着导 IDEA先把源码里的类名过一遍基本就能反推出这套系统的表结构和业务流程。源码包里直接暴露的类有八个 DAO/Servlet 加两个实体类按命名规范拆开看系统的骨架其实非常清晰。2.1 十个核心类的职责划分谁在负责报修、派工、配件和完工把这十个类按三层架构归类对应关系如下类名分层推测职责ReportEquipment实体类设备报修单实体字段大概率含报修编号、设备名称、故障描述、报修人、报修时间、状态RepairRecord实体类维修记录实体字段大概率含维修单号、报修单外键、维修人、维修内容、工时、费用、完工时间ReportEquipmentDaoDAO 层报修单的增删改查核心方法是新增报修和按状态分页查询RepairRecordDaoDAO 层维修记录的写入与更新核心方法是完工回填PartDaoDAO 层配件主表操作对应配件库存表PartDetailDaoDAO 层配件明细操作对应维修单与配件的关联表CustomerDaoDAO 层客户档案的增删改查报修时关联客户信息ReportRecordDaoDAO 层报修记录查询注意这个和 ReportEquipmentDao 职责有重叠一般用于跨表联查GenerateCheckServletServlet 层接收前端请求生成派工单QueryCompletedTasksServletServlet 层接收前端请求查询已完成任务从命名能看出一个关键设计决策作者把「报修登记」和「报修记录查询」拆成了两个 DAO而不是挤在一个类里。ReportEquipmentDao 偏向写操作负责报修单落库ReportRecordDao 偏向读操作负责多表联查。这种拆分在课程设计里很常见优点是每个类的代码量小、答辩时好讲缺点是同一个实体被两个 DAO 操作时事务边界容易模糊。后面写二开时我会演示怎么用 Service 层统一收口。2.2 核心表结构和关联关系维修业务主流程的数据落点从类名和业务闭环推测这套系统至少需要五张表支撑我按常见课程设计写法给你把表结构列出来字段名按 Java 驼峰转数据库下划线的规范来-- 客户档案表 CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(50) NOT NULL, phone VARCHAR(20), address VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 设备报修单表 CREATE TABLE report_equipment ( id INT PRIMARY KEY AUTO_INCREMENT, report_no VARCHAR(32) UNIQUE NOT NULL, customer_id INT NOT NULL, device_name VARCHAR(100) NOT NULL, fault_desc VARCHAR(500), report_person VARCHAR(30), report_time DATETIME, status TINYINT DEFAULT 0 COMMENT 0待派工 1维修中 2已完成, FOREIGN KEY (customer_id) REFERENCES customer(id) ); -- 维修记录表 CREATE TABLE repair_record ( id INT PRIMARY KEY AUTO_INCREMENT, report_id INT NOT NULL, repair_person VARCHAR(30), repair_content VARCHAR(500), labor_hours DECIMAL(5,2), total_cost DECIMAL(10,2), start_time DATETIME, finish_time DATETIME, status TINYINT DEFAULT 0, FOREIGN KEY (report_id) REFERENCES report_equipment(id) ); -- 配件表 CREATE TABLE part ( id INT PRIMARY KEY AUTO_INCREMENT, part_name VARCHAR(100) NOT NULL, spec VARCHAR(100), stock INT DEFAULT 0, price DECIMAL(10,2) ); -- 维修配件明细表 CREATE TABLE part_detail ( id INT PRIMARY KEY AUTO_INCREMENT, repair_id INT NOT NULL, part_id INT NOT NULL, quantity INT DEFAULT 1, FOREIGN KEY (repair_id) REFERENCES repair_record(id), FOREIGN KEY (part_id) REFERENCES part(id) );这里有一个容易被忽略的关键点维修记录和报修单是一对一关系但维修记录和配件是多对多关系中间用 part_detail 关联。这也是为什么会有 PartDetailDao 和 PartDao 两个类的根本原因。主表存配件库存和单价明细表存每次维修实际用了多少件这样改配件价格不会影响历史维修单的成本计算。设计时把「当前库存」和「历史消耗」分离是这套系统在数据层面最值得借鉴的点。从 GenerateCheckServlet 的命名还能确认一个流程报修登记之后有一个显式的「生成派工单」动作也就是说状态流转不是报修后自动进入维修中而是需要操作人员手动触发派工。这个设计在真实场景里是有意义的——维修工的资源是有限的派工需要人工协调。理解了这个闭环第 4 章的代码拆解你才能看懂为什么 Servlet 里要同时操作三张表。3. 本地把项目跑起来JDK 8 Tomcat 9 MySQL 5.7 的完整配置这个项目拿到手能不能「下载直接用」关键不在代码而在环境匹配。原生 Servlet 项目对 JDK 版本和 Tomcat 版本很敏感我按最常见的课程设计环境给你捋一遍配置流程。3.1 环境选型为什么是 Tomcat 9 和 MySQL 5.7先定版本组合再谈理由。我建议的组合是 JDK 8 Tomcat 9 MySQL 5.7 IDEA 2020 以上版本。Tomcat 9 对应 Servlet 4.0 规范兼容 javax.servlet 命名空间而项目里用的是原生 servlet不是 jakarta.servlet这点决定了你不能用 Tomcat 10 以上版本——Tomcat 10 把包名改成了 jakarta.servlet直接跑会报 ClassNotFoundException。MySQL 用 5.7 是因为课程设计项目的 JDBC 驱动一般配置的是com.mysql.jdbc.Driver这个驱动类在 MySQL 8.x 的驱动包里虽然还保留但需要额外配时区参数5.7 起步最稳。如果你本机只有 MySQL 8也没关系后面第 5 章会写解决办法。提示JDK 版本不建议上 17原生 Servlet 项目用 JDK 8 编译兼容性最好避免一些 IDE 层面的编译选项问题。3.2 部署步骤导入源码、建库、改 JDBC 配置、启动第一步在 IDEA 里直接 Open 解压后的源码目录选择信任项目。IDEA 会识别为普通 Java 项目此时需要手动添加 Web 支持右键项目 → Add Framework Support → 勾选 Web Application。注意这一步要确认 WEB-INF 下的 web.xml 被正确识别如果识别失败后面启动 Tomcat 时会直接 404。第二步建库。打开 MySQL 客户端执行CREATE DATABASE IF NOT EXISTS device_repair DEFAULT CHARACTER SET utf8mb4;建议在 Navicat 或命令行里先执行这一步不要在 Java 代码里用CREATE DATABASE IF NOT EXISTS自动建库——课程设计代码里常见这种写法但生产环境里给 JDBC 用户授权时通常没有建库权限养成手动建库的习惯后面少踩坑。第三步改 JDBC 配置。原生 Servlet 项目的数据库连接信息一般写在两种地方一个 DBUtil 工具类的静态代码块里或者 src 下的 db.properties / jdbc.properties 文件。如果是 DBUtil 类找到类似这样的代码段// DBUtil.java 中的常见写法 static { try { Class.forName(com.mysql.jdbc.Driver); // 注意这三个参数url、username、password 都需要改成你本机的值 String url jdbc:mysql://localhost:3306/device_repair?useSSLfalsecharacterEncodingutf8; String username root; String password 123456; conn DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } }参数说明useSSLfalse是因为本地开发没有配置 SSL 证书不加这个参数 MySQL 5.7 会警告但不报错MySQL 8 直接报连接失败characterEncodingutf8保证写入数据库的中文不乱码。password 改成你本机 MySQL 的 root 密码。如果项目用的是 db.properties 文件那就直接编辑 properties 文件里对应的键值效果一样。第四步部署到 Tomcat。在 IDEA 的 Run/Debug Configurations 里新增 Tomcat Server → Local点击 Deployment 页签添加 Artifact。如果你的项目结构正确Artifact 选项目名:war exploded即可。Application context 建议改成/repair这样访问路径是http://localhost:8080/repair/。改 context 的目的是避免和其他项目冲突也方便记忆。3.3 运行验证从登录到完工查询走一遍启动 Tomcat 后浏览器访问http://localhost:8080/repair/预期能看到登录页或系统首页。完整走一遍流程验证核心功能是否通新增一个客户档案验证 CustomerDao 的 insert 方法新增一条设备报修验证 ReportEquipmentDao 和报修单号生成逻辑到待派工列表找到这条记录点击生成派工单验证 GenerateCheckServlet填写维修内容和配件消耗完成维修验证 RepairRecordDao 和 PartDao 的库存扣减打开已完成任务列表验证 QueryCompletedTasksServlet 多表联查提示如果第 2 步报错大概率是报修单号生成逻辑里用了日期格式化检查系统时间格式和数据库表字段长度是否匹配。这条在避坑章会展开。4. 核心代码拆解报修登记、派工单生成与状态流转的关键实现很多读者下载这种课程设计源码最想看的就是核心业务怎么写。这一章我从 DAO 和 Servlet 两个层面拆关键代码代码是这套资源的常见写法核心逻辑与类名职责一致。4.1 ReportEquipmentDao报修单落库与主键回填报修登记是整个系统的入口它要做的动作是接收表单参数、生成报修单号、写入 report_equipment 表、返回自增主键供后续流程使用。常见写法如下// ReportEquipmentDao.java - 新增报修单 public int insertReport(ReportEquipment report) { String sql INSERT INTO report_equipment(report_no, customer_id, device_name, fault_desc, report_person, report_time, status) VALUES(?, ?, ?, ?, ?, NOW(), 0); int generatedId 0; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, report.getReportNo()); ps.setInt(2, report.getCustomerId()); ps.setString(3, report.getDeviceName()); ps.setString(4, report.getFaultDesc()); ps.setString(5, report.getReportPerson()); ps.executeUpdate(); // 拿到自增主键后续生成派工单要用 try (ResultSet rs ps.getGeneratedKeys()) { if (rs.next()) { generatedId rs.getInt(1); } } } catch (SQLException e) { e.printStackTrace(); } return generatedId; }逻辑说明prepareStatement的第二个参数传Statement.RETURN_GENERATED_KEYS这样执行完 insert 之后可以通过getGeneratedKeys()拿到数据库自增的主键。很多课程设计代码省略这一步插入后直接按SELECT MAX(id)去查多表插入时会出并发问题。生成的报修单号report_no一般由 Servlet 层拼接常见格式是REP System.currentTimeMillis()或日期加序号。这部分逻辑放在 Servlet 里而不是 DAO 里因为 DAO 只负责持久化业务编号规则属于控制层职责。参数说明DBUtil.getConnection()每次调用都新建连接项目里没有连接池。这种方式在课程设计里没问题但如果演示时连续快速点击提交会出现连接耗尽。第 6 章我会给改造方案。report_time直接由数据库NOW()生成而不是 Java 端传new Date()好处是数据库服务器时间为准避免应用服务器和数据库服务器时钟不一致。4.2 GenerateCheckServlet派工单生成的业务逻辑派工是整个系统里业务逻辑最集中的一个点因为它同时要更新报修单状态、创建维修记录、可能还要预占配件。看 Servlet 的命名就知道它的职责是「生成」而非「查询」代码核心逻辑是这样的// GenerateCheckServlet.java - doPost 方法核心逻辑 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); int reportId Integer.parseInt(request.getParameter(reportId)); String repairPerson request.getParameter(repairPerson); // 第一步更新报修单状态为维修中 boolean updateReport reportEquipmentDao.updateStatus(reportId, 1); // 第二步创建维修记录并拿到维修单ID RepairRecord record new RepairRecord(); record.setReportId(reportId); record.setRepairPerson(repairPerson); record.setStatus(1); // 维修中 int repairId repairRecordDao.insertRecord(record); // 第三步关联配件明细 String[] partIds request.getParameterValues(partId); String[] quantities request.getParameterValues(quantity); if (partIds ! null) { for (int i 0; i partIds.length; i) { PartDetail detail new PartDetail(); detail.setRepairId(repairId); detail.setPartId(Integer.parseInt(partIds[i])); detail.setQuantity(Integer.parseInt(quantities[i])); partDetailDao.insertDetail(detail); // 同步扣减配件库存 partDao.reduceStock(Integer.parseInt(partIds[i]), Integer.parseInt(quantities[i])); } } // 跳转到待派工列表刷新 response.sendRedirect(request.getContextPath() /queryPendingTasks); }逻辑说明这个 Servlet 做了三件事对应三张表的写操作。第一件事更新报修单状态从 0 到 1表示这条报修已经被处理第二件事往维修记录表插一条记录拿到自增 repairId第三件事是循环处理前端提交的配件列表每个配件插入一条 part_detail 明细同时扣减 part 表的库存。这里暴露了一个事务隐患三步操作如果中间任何一步失败前面成功的步骤不会回滚。比如报修单状态已改成 1但维修记录插入失败这条报修就卡在「维修中」没人管了。课程设计里这是常态但答辩时如果被问到「你怎么保证数据一致性」你直接回答这个环节确实没加事务然后给出第 6 章的 Connection 共享方案反而比强行解释更有说服力。4.3 QueryCompletedTasksServlet多表联查与状态过滤已完成任务查询页是这个系统最典型的联表查询场景涉及 report_equipment、repair_record、customer 三张表。常见写法如下// QueryCompletedTasksServlet.java - 查询已完成任务 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String sql SELECT r.id AS report_id, r.report_no, c.customer_name, r.device_name, r.fault_desc, rr.repair_person, rr.repair_content, rr.total_cost, rr.finish_time FROM report_equipment r JOIN customer c ON r.customer_id c.id JOIN repair_record rr ON rr.report_id r.id WHERE rr.status 2 ORDER BY rr.finish_time DESC; ListMapString, Object list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 遍历结果集封装到 ListMap while (rs.next()) { MapString, Object row new HashMap(); row.put(reportId, rs.getInt(report_id)); row.put(reportNo, rs.getString(report_no)); row.put(customerName, rs.getString(customer_name)); row.put(deviceName, rs.getString(device_name)); row.put(repairPerson, rs.getString(repair_person)); row.put(totalCost, rs.getBigDecimal(total_cost)); row.put(finishTime, rs.getTimestamp(finish_time)); list.add(row); } } catch (SQLException e) { e.printStackTrace(); } request.setAttribute(taskList, list); request.getRequestDispatcher(/completedTasks.jsp).forward(request, response); }逻辑说明这里用INNER JOIN而不是LEFT JOIN是因为「已完成任务」必然同时存在报修单和维修记录内连接正好过滤掉那些报了修但没完工的数据。WHERE rr.status 2是状态过滤配合ORDER BY finish_time DESC让最新完工的排最前。返回的类型是ListMap而不是实体对象列表因为多表联查结果没法直接映射到单个实体Map 是最省事的封装方式。这种做法答辩时不用回避——它就是课程设计里最常见的快速实现方式性能上没毛病就是类型安全差一些。参数说明这个查询没有参数所以直接用executeQuery()不需要 setXXX。如果后续要按客户名搜索把WHERE改成WHERE rr.status 2 AND c.customer_name LIKE ?然后ps.setString(1, % name %)即可。LIKE 查询在数据量小的时候没问题这是课程设计不涉及索引优化。5. 避坑记录我把这个项目从跑不通调到能用的四条排查经验课程设计源码的坑不在逻辑深而在环境匹配和基础配置。这一章写我实际调试这套资源时遇到过的四类问题按「现象 → 原因 → 解决」的顺序记录每一条都是可以直接照做的排查路径。5.1 现象一启动 Tomcat 报 ClassNotFoundException指向 com.mysql.jdbc.Driver原因MySQL 驱动 jar 包没有复制到 WEB-INF/lib 目录。很多人在 IDEA 里通过 Project Structure → Libraries 添加了 jarIDEA 编译时不报错但 Tomcat 运行时只认 WEB-INF/lib 下的包Project Structure 里配的 Library 不会被 Tomcat 加载。解决把 mysql-connector-java 的 jar 包直接复制到项目名/web/WEB-INF/lib/目录下重启 Tomcat。如果项目没有 lib 目录手动创建。这里要确认驱动 jar 版本MySQL 5.7 对应 5.x 的驱动包MySQL 8 用 8.x 驱动包两个版本的核心区别是驱动类名——5.x 是com.mysql.jdbc.Driver8.x 是com.mysql.cj.jdbc.Driver。如果项目代码里写死的是老驱动类名而你又只能用 MySQL 8两个选择要么把驱动 jar 换成 5.1.49要么改代码里Class.forName的字符串。5.2 现象二数据库连接失败报 Communications link failure但 JDBC URL 明明看着没问题原因八成是时区参数问题。MySQL 8.x 默认时区比驱动本地时区不一致连接时需要显式声明serverTimezone。另外 URL 里的characterEncodingutf8和数据库实际字符集不一致也会导致连接被拒。解决JDBC URL 改成完整格式jdbc:mysql://localhost:3306/device_repair?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8serverTimezoneAsia/Shanghai是指定服务器时区也可以用serverTimezoneGMT%2B8表示东八区。如果连这个都报错检查 MySQL 服务是否真的在监听 3306 端口命令行执行netstat -ano | findstr 3306确认。5.3 现象三页面报 500 错误IDEA 控制台里却看不到 Java 堆栈原因控制台输出被 System.out 刷屏异常信息走了printStackTrace()输出到了 Tomcat 的本地日志而不是 IDEA 控制台。课程设计代码里到处是e.printStackTrace()这个方法默认输出到 stderr而 Tomcat 的 stderr 可能被重定向了。解决最省事的办法是打开 Tomcat 的日志目录IDEA 配置 Tomcat 时可以看到日志路径查看catalina.out或localhost.2025-xx-xx.log。更快的办法是查 MySQL 的通用查询日志看 SQL 到底有没有发到数据库端。如果 SQL 发了数据库也执行了但还报 500那就是结果集封装阶段的问题——建议在 catch 块里临时加一行System.err.println(SQL: sql)把 SQL 打出来直接在 IDEA 控制台定位是哪一行出的错。5.4 现象四中文乱码而且是 GET 和 POST 请求两种乱码原因POST 乱码是 Servlet 没有设置request.setCharacterEncoding(UTF-8)GET 乱码是 Tomcat 8 以下版本对 URL 参数的默认编码是 ISO-8859-1中文参数在到达 Servlet 前就已经解码失败。这个项目如果用了原生 Servlet两种情况都可能遇到。解决POST 请求在 doPost 方法第一行加request.setCharacterEncoding(UTF-8)。GET 请求乱码没有代码层面的通用解法最直接的是检查是否用到 GET 传中文参数如果有把表单 method 改成 post。如果还有就在 Tomcat 的 server.xml 里给 Connector 加URIEncodingUTF-8属性重启生效。5.5 现象五部署后访问 404Tomcat 显示 HTTP Status 404原因web.xml 没有正确注册 Servlet或者注册了但 URL 映射路径不对。原生 Servlet 项目的 web.xml 必须有servlet和servlet-mapping两段配置漏了 mapping 就会 404。这个项目里的 GenerateCheckServlet 和 QueryCompletedTasksServlet 如果都在 web.xml 里漏配列表页的链接点了就是 404。解决打开 web.xml检查每个 Servlet 是否成对出现servlet servlet-nameGenerateCheckServlet/servlet-name servlet-classcom.example.servlet.GenerateCheckServlet/servlet-class /servlet servlet-mapping servlet-nameGenerateCheckServlet/servlet-name url-pattern/generateCheck/url-pattern /servlet-mappingurl-pattern的写法是前导斜杠 路径不能写类名全路径。如果你看到项目里同时存在WebServlet注解和 web.xml 配置以注解为准——Servlet 3.0 起注解优先。排查思路是先去浏览器看 404 页面的 URL再回 web.xml 里找对应的 url-pattern确认大小写完全一致Linux 服务器上大小写敏感Windows 上不敏感这个差异很可能在本地跑通后部署到云服务器时突然出现。6. 二开方向把原生 Servlet 项目改造成毕业设计加分项的三个技巧如果这套资源就是你的课程设计或毕设底子直接交原生版本也能过但如果想拿高分下面三个改造方向性价比最高因为每个都能在答辩时讲出「我解决了什么问题」。第一个改造是给系统加 Filter 做登录鉴权。原项目如果没有登录拦截任何一个 Servlet 都可以不登录直接访问答辩考官大概率会问「系统安全性怎么体现」。常见做法是写一个LoginFilter实现javax.servlet.Filter在doFilter里判断 session 中是否有用户信息没有就重定向到登录页// LoginFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } chain.doFilter(req, resp); }在 web.xml 里配置filter-mapping把需要保护的 URL 全部拦截。这个改动只需新增一个类加一段 XML但能明显提升项目的完整性观感。第二个改造是用连接池替代每次DriverManager.getConnection()。Druid 或 C3P0 都行以 Druid 为例引入 jar 包后在项目里配一个druid.properties把 DBUtil 的 getConnection 改成从DruidDataSourceFactory取连接。这个改造能解决第 4 章提到的连接耗尽问题答辩时可以直接展示压测数据改造前连续请求 50 次报连接超时改造后 500 次无压力。第三个改造是加一个统计报表页比如按月份统计维修数量和费用用第 4 章的联查思路加一个GROUP BY DATE_FORMAT(finish_time,%Y-%m)就能出数据前端用 ECharts 画柱状图。这个功能虽然简单但演示效果最直观而且能讲出「我从业务数据里提炼了管理决策信息」这个层次。从那以后我拿到任何课程设计源码都不急着跑先花十分钟看类名、理清 DAO 和 Servlet 的对应关系再动手改配置启动。这套流程帮我省了大量翻车时间。这套设备维修管理系统源码的完整度和注释水平足够当课程设计底子如果你的核心目标是搞懂 Servlet 和 JDBC 的真实协作方式它也是比框架项目更好入手的解剖样本。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑