SSM+JSP招投标系统实战:从架构到部署与安全加固

发布时间:2026/11/9 0:36:45

SSM+JSP招投标系统实战:从架构到部署与安全加固 简介这是一套面向计算机专业本科生的Java毕业设计实战项目基于SSMSpringSpringMVCMyBatis框架与JSP/HTML前端技术开发的网上招投标系统专为课程设计、期末大作业及毕业答辩场景打造代码注释详尽部署门槛低新手可快速上手并理解全栈开发流程。压缩包共812个文件涵盖104个核心Java业务逻辑与控制器类、38个HTML页面、43个Vue组件用于管理后台局部增强、55个CSS与167个JS脚本实现交互效果另含2个SQL数据库脚本、3个批处理部署脚本install/run/build及完整IDEA工程配置文件整体大小17.32MB结构规范、模块清晰。目前已有57人学习下载资源提供开箱即用的可运行环境适配MySQL 5.7 Tomcat 7.x/8.x包含前后端源码、建库建表语句、软件工具清单及基础部署说明助力学生高效完成毕设开发、答辩演示与代码讲解。1. 这不是又一个“Hello World”式毕设——SSMJSP招投标系统真能跑通、真有业务闭环、真经得起调试你下载的这个.zip文件表面看是“Java毕业设计”但拆开后你会发现它不是拼凑的 CRUD 演示而是一个具备真实招投标业务逻辑的轻量级 Web 系统。从招标公告发布、投标文件上传、评标打分到中标通知生成全流程覆盖前端用 JSP 渲染动态页面后端用 Spring SpringMVC MyBatisSSM分层协作数据库脚本直接建表并预置测试数据。它不依赖 Spring Boot 自动配置而是显式声明事务管理、拦截器、视图解析器——这对理解传统 Java Web 开发栈的耦合点与解耦逻辑至关重要。适合两类人一是大三刚学完 JDBC 和 Servlet、正卡在“怎么把三层架构串起来”的同学二是需要快速交付课程设计、但又不愿用网上泛滥的“学生管理系统”模板的开发者。它不炫技但每行 JSP 都带c:if判断权限每个 Controller 方法都标注TransactionalMapper XML 里resultMap明确映射多表关联字段——这种“教科书级可读性”正是新手读懂、老手复用、答辩老师一眼认可的关键。2. SSM 架构落地为什么选 MyBatis 而非 HibernateSpringMVC 如何接管 JSP 请求流2.1 技术选型背后的工程权衡轻量、可控、易调试SSM 组合在 2020 年前仍是企业级 Java Web 主流方案其核心价值不在“新”而在“稳”与“透”。本项目选用 MyBatis而非 Hibernate的根本原因在于对 SQL 的完全掌控——招投标系统中评标结果统计需多表 JOIN GROUP BY CASE WHEN 计算加权得分这类语句若交由 Hibernate HQL 生成极易因懒加载或 N1 查询拖垮响应。而本项目BidMapper.xml中第 87 行的getEvaluationSummary查询明确写出select idgetEvaluationSummary resultTypemap SELECT b.id AS bid_id, b.title AS bid_title, u.username AS bidder_name, SUM(CASE WHEN s.criteria 技术分 THEN s.score ELSE 0 END) * 0.6 SUM(CASE WHEN s.criteria 商务分 THEN s.score ELSE 0 END) * 0.3 SUM(CASE WHEN s.criteria 价格分 THEN s.score ELSE 0 END) * 0.1 AS total_score FROM bid_info b JOIN user_info u ON b.bidder_id u.id JOIN score_detail s ON b.id s.bid_id WHERE b.status evaluated GROUP BY b.id, b.title, u.username ORDER BY total_score DESC /select提示此 SQL 直接暴露了业务规则权重技术60%、商务30%、价格10%修改权重只需改 XML 中数字无需编译 Java 类——这是 MyBatis 在业务频繁调整场景下的硬优势。SpringMVC 的角色则聚焦于请求路由与数据绑定。web.xml中配置的DispatcherServlet显式指定contextConfigLocation指向spring-mvc.xml而非依赖 classpath 扫描。该文件第 12 行定义了InternalResourceViewResolverbean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean这意味着所有return admin/bid_list的 Controller 返回值都会被自动拼接为/WEB-INF/jsp/admin/bid_list.jsp。这种显式路径控制避免了 Spring Boot 中Controller与RestController混用导致的视图跳转失败问题。2.2 三层结构如何协同从 JSP 表单提交到数据库写入的完整链路以“发布招标公告”为例链路如下2.2.1 JSP 层表单校验与数据组装/WEB-INF/jsp/admin/bid_add.jsp中关键字段使用 JSTL 标签做服务端校验c:if test${empty param.title} div classerror标题不能为空/div /c:if c:if test${param.budget lt 10000} div classerror预算不得低于1万元/div /c:if form action${pageContext.request.contextPath}/bid/save methodpost input typetext nametitle value${param.title} / input typenumber namebudget value${param.budget} / textarea namedescription/textarea input typesubmit value发布 / /form注意param.budget lt 10000使用 EL 表达式直接比较数值而非 JavaScript 前端校验——这确保即使禁用 JS服务端仍能拦截非法输入。2.2.2 Controller 层参数接收与业务门面BidController.java中对应方法RequestMapping(value /save, method RequestMethod.POST) ResponseBody public MapString, Object saveBid(RequestParam String title, RequestParam BigDecimal budget, RequestParam String description, HttpServletRequest request) { MapString, Object result new HashMap(); try { // 从 session 获取当前登录用户 ID User user (User) request.getSession().getAttribute(user); if (user null) { throw new RuntimeException(未登录); } Bid bid new Bid(); bid.setTitle(title); bid.setBudget(budget); bid.setDescription(description); bid.setPublisherId(user.getId()); bid.setStatus(draft); // 初始状态为草稿 bidService.save(bid); // 调用 service 层 result.put(success, true); result.put(msg, 发布成功); } catch (Exception e) { result.put(success, false); result.put(msg, 发布失败 e.getMessage()); } return result; }关键点RequestParam强制要求参数名与表单 name 严格一致ResponseBody返回 JSON但 JSP 页面通过 AJAX 提交见bid_add.jsp底部script因此无需视图解析器介入。2.2.3 Service 层事务边界与业务规则封装BidServiceImpl.java中Transactional(rollbackFor Exception.class) public void save(Bid bid) { // 规则1同一用户24小时内最多发布3个招标 int count bidMapper.countByPublisherAndTime( bid.getPublisherId(), DateUtils.addHours(new Date(), -24) ); if (count 3) { throw new RuntimeException(今日发布招标已达上限); } // 规则2预算必须为正数且小数位不超过2位 if (bid.getBudget().compareTo(BigDecimal.ZERO) 0 || bid.getBudget().scale() 2) { throw new RuntimeException(预算格式错误); } bid.setCreateTime(new Date()); bidMapper.insert(bid); // 最终调用 MyBatis Mapper }Transactional注解确保上述两条规则要么全部成功要么全部回滚。若countByPublisherAndTime查询返回 3后续insert将不会执行——这是 SSM 中事务传播机制的典型应用。2.3 数据库设计为什么用status字段替代状态机表bid_info表结构来自db.sql中status字段定义为VARCHAR(20)取值包括draft,published,closed,evaluated,awarded。这种设计放弃单独的状态机表原因有三状态流转简单招投标生命周期线性明确无分支或并发状态查询高效WHERE status IN (published,evaluated)比JOIN status_table少一次关联维护成本低新增状态如revised只需改代码和 SQL无需 DDL。但代价是状态合法性依赖代码校验。BidService.updateStatus()方法中public void updateStatus(Long bidId, String newStatus) { // 显式枚举允许的状态转移 MapString, SetString validTransitions new HashMap(); validTransitions.put(draft, new HashSet(Arrays.asList(published))); validTransitions.put(published, new HashSet(Arrays.asList(closed, evaluated))); validTransitions.put(evaluated, new HashSet(Arrays.asList(awarded))); Bid bid bidMapper.selectById(bidId); if (!validTransitions.getOrDefault(bid.getStatus(), Collections.emptySet()) .contains(newStatus)) { throw new IllegalArgumentException(非法状态转移 bid.getStatus() → newStatus); } bid.setStatus(newStatus); bidMapper.updateById(bid); }这种“白名单式”状态校验比数据库 CHECK CONSTRAINT 更灵活也更易在日志中追踪非法操作来源。3. 部署实操Tomcat 7.x 下 JSP 编译失败MySQL 8.0 连接拒绝三步定位根因3.1 启动脚本解析1-install.bat做了什么为什么不能跳过项目根目录的1-install.bat并非简单解压而是执行以下关键动作echo off echo 正在初始化数据库... mysql -u root -p123456 db.sql echo 数据库初始化完成 echo 正在编译项目... mvn clean compile -Dmaven.test.skiptrue echo 编译完成 echo 正在打包为 WAR... mvn package -Dmaven.test.skiptrue echo WAR 包已生成target/bidding-system.war pause注意-Dmaven.test.skiptrue跳过单元测试但db.sql中包含INSERT INTO user_info预置管理员账号用户名admin密码123456这是登录系统的唯一入口。若跳过1-install.bat直接部署 WAR数据库为空登录必然失败。3.2 Tomcat 兼容性陷阱JSP 编译报错The method getJspApplicationContext(ServletContext) is undefined怎么解此错误高发于 Tomcat 9 环境根源是 Servlet API 版本冲突。本项目pom.xml中声明properties servlet-api.version3.1.0/servlet-api.version /properties dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version${servlet-api.version}/version scopeprovided/scope /dependency而 Tomcat 9 默认使用 Servlet 4.0getJspApplicationContext方法已被移除。解决方案只有两个降级 Tomcat使用apache-tomcat-7.0.109.zip项目推荐的 7.x或tomcat-8.5.94.zip升级项目依赖不推荐将servlet-api.version改为4.0.1并同步升级jsp-api和jstl但会导致 JSTL 标签如c:forEach语法兼容性问题。验证是否解决启动 Tomcat 后访问http://localhost:8080/bidding-system/若看到index.html.bak注意.bak后缀说明静态页未被正确路由——此时检查web.xml中welcome-file-list是否指向index.jsp而非index.html。3.3 MySQL 5.7 与 8.0 的连接差异Public Key Retrieval is not allowed错误的底层原因当使用 MySQL 8.0 时jdbc:mysql://localhost:3306/bidding?useSSLfalseserverTimezoneUTC连接字符串会触发异常。根本原因是 MySQL 8.0 默认启用caching_sha2_password认证插件而本项目pom.xml中的mysql-connector-java版本为5.1.47不支持该插件。三步修复法修改src/main/resources/jdbc.propertiesjdbc.urljdbc:mysql://localhost:3306/bidding?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueallowPublicKeyRetrievaltrue参数强制客户端请求公钥绕过认证插件限制登录 MySQL 8.0 执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;将认证方式降级为兼容的mysql_native_password若仍失败替换lib/mysql-connector-java-5.1.47.jar为mysql-connector-java-8.0.28.jar并修改pom.xml对应版本号。提示serverTimezoneAsia/Shanghai不可省略。MySQL 8.0 严格校验时区若缺失new Date()插入数据库时会变成 UTC 时间导致招标截止时间偏差 8 小时。3.4 IDEA 配置要点为什么index.jsp右键 Run As Server 会 404IDEA 默认将webapp目录设为 Web Resource Root但本项目结构中index.jsp位于根目录与WEB-INF同级。正确配置路径File → Project Structure → Modules → Web点击添加 Web FacetWeb Resource Directory选择项目根目录含index.jsp、WEB-INFDeployment Descriptors中指定web.xml路径为src/main/webapp/WEB-INF/web.xmlOutput directory设为target/bidding-system/WEB-INF/classes。配置后右键index.jsp → Run Tomcat Server即可直接启动无需导出 WAR。4. 权限与安全加固JSP 中的c:if test${user.role admin}真的安全吗4.1 会话劫持风险request.getSession().getAttribute(user)的脆弱性当前登录逻辑LoginController.java仅校验用户名密码后将User对象存入 SessionHttpSession session request.getSession(); session.setAttribute(user, user); // 危险未设置超时与 HttpOnly这带来两大风险Session 固定攻击攻击者可预测或窃取 Session ID冒充合法用户XSS 泄露 Cookie若页面存在scriptalert(document.cookie)/script注入JSESSIONID可被窃取。加固方案在web.xml中添加session-config session-timeout30/session-timeout http-onlytrue/http-only securefalse/secure !-- 若非 HTTPS 环境设为 false -- /session-confighttp-onlytrue阻止 JavaScript 访问 Cookie切断 XSS 窃取 Session 通道登录成功后重置 Session IDHttpSession oldSession request.getSession(false); if (oldSession ! null) { oldSession.invalidate(); // 使旧 ID 失效 } HttpSession newSession request.getSession(true); newSession.setAttribute(user, user);4.2 JSP 权限标签的局限性前端隐藏 ≠ 后端校验admin/bid_list.jsp中大量使用c:if test${user.role admin} a hrefbid/edit?id${bid.id}编辑/a /c:if这仅控制按钮显示绝不等于后端接口做了权限拦截。BidController.edit()方法目前无任何角色校验攻击者可直接访问/bid/edit?id123修改任意招标。必须补全的后端校验在BidController.java中RequestMapping(/edit) public String editBid(RequestParam Long id, Model model, HttpServletRequest request) { User user (User) request.getSession().getAttribute(user); if (user null || !admin.equals(user.getRole())) { return redirect:/login.jsp; // 强制跳转登录页 } Bid bid bidService.getById(id); model.addAttribute(bid, bid); return admin/bid_edit; }注意model.addAttribute(bid, bid)传递对象到 JSP 时c:out value${bid.title}/会自动 HTML 转义防止 XSS但若用% bid.getTitle() %则无此保护——项目中所有输出均采用 JSTLc:out这是安全编码的体现。4.3 SQL 注入防御实测bidMapper.xml中#{}与${}的生死线BidMapper.xml第 42 行存在一个危险写法!-- 危险勿模仿 -- select idsearchByKeyword resultTypeBid SELECT * FROM bid_info WHERE title LIKE %${keyword}% /select${keyword}是字符串拼接若传入keywordabc OR 11SQL 变为SELECT * FROM bid_info WHERE title LIKE %abc OR 11%导致全表泄露。而项目中实际使用的是安全写法第 35 行select idsearchByKeyword resultTypeBid SELECT * FROM bid_info WHERE title LIKE CONCAT(%, #{keyword}, %) /select#{keyword}由 MyBatis 转为 PreparedStatement 参数CONCAT函数确保%符号被安全包裹。验证方法在BidService.searchByKeyword(abc OR 11)观察日志中打印的 SQL 是否为SELECT * FROM bid_info WHERE title LIKE CONCAT(%, ?, %) // ? 参数化无注入5. 毕设答辩高频问题预判从“为什么用 JSP”到“SSM 各层职责边界”5.1 “现在都用 Vue/React为什么还选 JSP”——答出技术演进中的不可替代性这个问题本质是考察你对技术栈历史脉络的理解。标准回答应包含三层教学目的JSP 是 Servlet 的语法糖% %内嵌 Java 代码能让学生直观看到“请求→处理→响应”全流程而 Vue 的虚拟 DOM 抽象层掩盖了 HTTP 本质运维友好JSP 修改后 Tomcat 自动热编译web.xml中init-paramparam-namedevelopment/param-nameparam-valuetrue/param-value/init-param无需重启服务适合答辩现场演示遗留系统对接银行、政务类老系统仍大量使用 JSP掌握其调试技巧如catalina.out日志定位 JSP 编译错误是工程师基本功。实战技巧答辩时打开tomcat/logs/catalina.out搜索JasperException可快速定位某行 JSP 语法错误如c:if test${user.name}缺少! null判断。5.2 “SSM 三层中Service 层和 Controller 层职责怎么划分”——用代码行注释证明理解深度核心原则Controller 只做协议转换Service 只做业务逻辑。以BidController.submitBid()为例Controller 职责接收HttpServletRequest提取param.bidId、param.file调用bidService.submit()返回ModelAndViewService 职责校验投标文件格式PDF/JPG、检查投标人资质调用userMapper.selectById()、生成唯一投标编号UUID.randomUUID().toString().replace(-, )、插入bid_submit表。若在 Controller 中写if (file.getSize() 10*1024*1024) { ... }即违反单一职责——文件大小校验属于业务规则应放入 Service。5.3 数据库脚本执行后user_info表密码是明文如何改成 BCrypt 加密当前db.sql中INSERT INTO user_info (username, password, role) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, admin);e10adc3949ba59abbe56e057f20f883e是123456的 MD5已不安全。改造步骤在pom.xml添加 BCrypt 依赖dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId version5.7.8/version /dependency修改LoginController.java登录逻辑BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); boolean matches encoder.matches(paramPassword, user.getPassword()); // 验证重置数据库密码使用 BCrypt 加密123456System.out.println(new BCryptPasswordEncoder().encode(123456)); // 输出$2a$10$KZQzZ...长度60字符将输出结果更新到db.sql的password字段。关键细节BCrypt 生成的密文长度固定为 60 字符因此user_info.password字段长度需从VARCHAR(32)改为VARCHAR(60)否则插入失败。5.4 项目扩展建议如何接入 Redis 缓存招标列表降低数据库压力若答辩被问及“如何优化高并发场景”可给出具体方案在pom.xml添加spring-boot-starter-data-redis注意本项目非 Spring Boot需手动配置RedisTemplate创建RedisConfig.javaConfiguration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }在BidService.listPublished()中添加缓存逻辑public ListBid listPublished() { String cacheKey bid:published:list; ListBid bids (ListBid) redisTemplate.opsForValue().get(cacheKey); if (bids null) { bids bidMapper.selectPublished(); // 查库 redisTemplate.opsForValue().set(cacheKey, bids, Duration.ofHours(1)); } return bids; }此方案使招标列表查询从 DB 降为内存QPS 提升 5 倍以上且Duration.ofHours(1)确保数据最终一致性。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/11/8 23:26:34

C#设备信息化管理系统源码实战:从架构设计到数据采集

简介:这是一套面向C#开发者和设备管理从业者的企业级设备信息化管理系统源码。系统覆盖资产管理、设备维修保养、备件管理、文件管理与可视化仪表盘等核心模块,展示了C#与.NET框架在实际业务中的完整应用,尤其适合希望掌握企业级分层架构、数…

2026/11/8 21:34:08

EDEM离散元凝聚力接触模型API编程模板详解

简介:面向EDEM离散元软件二次开发的编程模板,适合需要自定义颗粒接触模型的仿真工程师与研究人员。模板聚焦凝聚力接触模型的API实现,通过C源文件与头文件展示如何将距离依赖的势能函数(如Lennard-Jones势)集成至EDEM&…

2026/11/7 21:58:46

2026年GEO优化服务商技术演进与选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/9 0:33:18

申请特种工的条件官方回应来了 外省证怎么转过来全说透

申请特种工的条件官方回应来了 外省证怎么转过来全说透 之前考的证在外省能不能转过来?这几乎是每个准备或已经持有特种作业操作证的学员最关心的问题。官方回应明确指出,根据《特种作业人员安全技术培训考核管理规定》,只要证件是通过 应急管理部…

2026/11/9 0:33:18

之前考的证在外省能不能转过来?电工证登记证怎么报名千万别踩坑

之前考的证在外省能不能转过来?电工证登记证怎么报名千万别踩坑 之前考的证在外省能不能转过来?这几乎是每个准备报名电工证的小伙伴最关心的问题,特别是像你这样在开封读工程类专业的应届生,可能已经拿到了其他地方的电工证,但不确定能不能直接用,或者…

2026/11/9 0:33:18

考电工证教程视频大全怎么报名别踩坑

考电工证教程视频大全怎么报名别踩坑 怕考不过白交培训费,电工证考试真的有那么难吗?其实,很多人考不过,不是因为题目太难,而是报名流程没搞清楚,备考资料没选对。尤其是现在网上各种【考电工证教程视频大全】让人眼花缭乱,稍不注意就可能花冤枉钱。…

2026/11/9 0:33:18

杭州电工证到哪考取啊 2026河南最新政策怎么影响你

杭州电工证到哪考取啊 2026河南最新政策怎么影响你 实操考试心里没底怕挂科,这种感觉我懂。尤其是像周口这种中小型施工企业负责人,每次安排员工报考电工证,心里都七上八下。2026年河南最新政策对电工证的报考流程、考试内容、复审周期等都有调整…

2026/11/9 0:28:18

茌平电工证在哪考取的,学历不高也能拿电子证书

茌平电工证在哪考取的,学历不高也能拿电子证书 学历不高怕报不上名?别急,茌平电工证在哪考取的其实并不难,关键是你得知道怎么操作。现在越来越多的单位要求持证上岗,而电工证作为特种作业操作证的一种,已经成了很多行业进入门槛。好消息是,现在大多数…

2026/11/8 0:00:27

电工证丢失还能补吗?千万别踩坑,丢了证等于失业

电工证丢失还能补吗?千万别踩坑,丢了证等于失业 你正在找工作,但手里的电工证突然丢了,这下怎么办?别急,先别慌, 电工证丢失还能补吗 ?答案是肯定的,但 千万别踩坑 ,补办流程如果不了解,可能会耽误你上岗、甚至影响到你的收入。…

2026/11/9 0:03:09

建设厅的特种工有哪些含金量高?别交完钱考不过

建设厅的特种工有哪些含金量高?别交完钱考不过 怕考不过白交培训费?别急,先搞清楚建设厅的特种工有哪些含金量高的证,再决定是不是值得投入时间和金钱。 建设厅的特种工证是建筑行业里最基础、也最实用的证书之一,涉及多个作业类别,每种证的 含金量…

2026/11/9 0:03:09

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱

荆门市电工证报名怎么考才靠谱 全国通用证别交冤枉钱 怕考不过白交培训费?荆门市电工证报名前必须知道这些避坑指南。别看网上一堆培训机构广告,真要报个电工证,很多人被坑得一塌糊涂。特别是那些打着“包过”旗号的,最后不是考试不过,就是证不是全国通…

2026/11/9 0:03:09

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑

高空电工作业过程通过率揭秘:怎么选靠谱机构不被坑 不知道去哪报名怕被中介坑?你不是一个人。现在特种作业证报名渠道五花八门,从线下培训机构到线上代理,真假难辨。尤其是像高空电工作业这类高风险、高门槛的作业类型,证书含金量和考试通过率直接影响你…

2026/11/7 14:54:47

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/7 14:50:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑