☰
SSM超市进销存系统实战:环境搭建、事务处理与避坑指南
2026/10/4 16:51:19 网站建设 项目流程

简介:面向Java毕设与课设场景,这份基于SSM框架的美特超市进销存管理系统覆盖商品入库、库存管理、销售统计等核心业务,适合需要完整可运行项目参考的计算机专业学生;开发环境明确,采用SSM+Vue前后端分离结构,JDK1.8、Tomcat7、MySQL5.7即可部署,附带管理员账号便于快速登录测试。资源包共717个文件,约20.28MB,以184个Java后台源码、130个Vue前端组件、162个SVG图标及JS、XML配置文件为主,另有SQL数据库脚本、运行/构建批处理与项目配置文件,目录层次分明,可快速在eclipse/idea中导入运行。目前已有98人学习下载,对于正在做进销存类毕设的同学,可直接参考其模块划分、权限控制与前端页面实现,也可作为修改扩展的基础模板,省去从零搭建的时间。

1. 基于Java美特超市进销存管理系统ssm-91crh.zip:这个标题背后是什么,值不值得跑通

“基于Java美特超市进销存管理系统ssm-91crh.zip”这个标题,翻译成大白话就是:一个用Spring、SpringMVC、MyBatis(SSM)三件套写成的超市进货、售货、库存管理项目,打好包通过ZIP分发。做过相关项目的人看到这个命名就知道里面是什么结构——解压后是标准JavaWeb工程,JSP页面、Controller控制层、Service业务层、Mapper数据库访问层一层压一层。它解决的是传统超市把进销存从Excel表格挪进数据库,让店长有一个能查流水、盯库存、管供应商的网页后台。

这类项目最常见的来源是毕业设计和中小企业信息化改造,价值不在于代码多难,而在于业务闭环完整:有商品档案、供应商档案、进货单、销售单、库存流水。适合三类人——想拿一个完整SSM项目练手的学生,准备Java面试但项目经验偏理论、想补一个业务系统的从业者,以及要给小超市做信息化的外包开发。跑通它比读十篇框架教程都管用,因为你会真正看到一张进货单是怎么从页面流进MySQL的。

2. 环境准备与项目导入:JDK、Tomcat、MySQL版本选对,后面才能少踩坑

2.1 先看pom.xml再装环境:SSM项目对版本配合极度敏感

跑这种SSM项目,最忌讳的就是把JDK、Tomcat装成最新版,然后直接双击Open。我最早跑类似项目时用的是JDK 17加Tomcat 10,结果项目里Spring还停留在4.x,Tomcat一启动就抛ClassNotFoundException,整整折腾一个下午,最后发现就是版本代差问题。后来养成的习惯是:解压ZIP后第一件事不是运行,而是找pom.xml、web.xml,从依赖版本反推环境要求。

SSM技术栈的成熟期集中在2018到2022年,常见组合是JDK 1.8、Tomcat 8.5、MySQL 5.7、Maven 3.6.x。pom.xml里一般长这样:

<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <spring.version>4.3.18.RELEASE</spring.version> <mybatis.version>3.4.6</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> </dependencies>

上面的依赖只给前后端交互的骨架。逻辑说明:看到spring.version是4.x,就老老实实用JDK 7或8;看到mybatis是3.4.x,配套的mybatis-spring一般也要用1.3.x。这几个库的版本咬合得很紧,单独把其中一个升到新版本,很容易踩到“方法不存在”或者“类找不到”的隐性问题。

参数说明:maven.compiler.source和target决定编译字节码版本,source=1.8编译出的class文件才能稳定跑在Tomcat 8.5上。如果本机已经装了JDK 17,不需要卸载,在IDEA里给这个项目单独设置Project SDK为1.8,作用范围只在这个Module内,比全局改JAVA_HOME安全得多。

提示:打开项目后先看IDEA右下角用的JDK版本,如果显示17,去Project Structure里改成1.8。这一步没做对,后面所有启动报错都会被带偏方向。

2.2 导入IDE与Maven依赖:先认目录结构再配置镜像源

解压这种ZIP后,一个常见的Maven工程结构是这样:

ssm-91crh/ ├─ pom.xml ├─ src/main/java │ ├─ com/meite/controller │ ├─ com/meite/service │ ├─ com/meite/dao │ └─ com/meite/entity ├─ src/main/resources │ ├─ jdbc.properties │ ├─ log4j.properties │ └─ spring │ ├─ spring-mvc.xml │ └─ spring-mybatis.xml ├─ src/main/webapp │ ├─ WEB-INF/web.xml │ └─ jsp │ ├─ goods.jsp │ ├─ purchase.jsp │ ├─ sale.jsp │ └─ stock.jsp └─ README.md

解压后在IDEA里直接Open,定位到pom.xml,等待右下角进度条把依赖拉完。如果用的是Eclipse,则走Import、Existing Maven Projects,也是一样定位到pom.xml。目录分得这么清楚是有原因的:com.meite.controller只处理页面请求,service只写业务逻辑,dao只碰数据库,entity跟数据库表一一对应。后面改需求时,入口在controller,逻辑在service,SQL在mapper,不会跑错地方。

依赖下载慢或者直接报Could not resolve dependency,是国内网络环境下的常见情况。解决办法是给Maven配一个可用的镜像源,在settings.xml的mirrors节点里加:

<mirrors> <mirror> <id>aliyun</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/central</url> </mirror> </mirrors>

逻辑说明:mirrorOf写central表示所有对中央仓库的请求都走这个镜像地址。IDEA里配置Maven的User settings file指向这个settings.xml,改完点Reload All Maven Projects,依赖会重新解析。这一步做完,启动时的很多玄学报错会直接消失,因为依赖没下全的项目连编译都过不了。

2.3 数据库连接配置:连不上库时先按这三处排查

绝大多数SSM项目的数据库配置集中在src/main/resources下的jdbc.properties里。打开这个文件,通常只需要核对三行:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_meite?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

参数说明:MySQL 5.7直接用com.mysql.jdbc.Driver;如果本地装的是MySQL 8.x,这一行必须换成com.mysql.cj.jdbc.Driver,否则启动时抛No suitable driver。jdbc.url里的characterEncoding=utf8解决后续中文乱码问题,serverTimezone=Asia/Shanghai是MySQL 8必填的时区参数。username和password按本地数据库实际值改,这个估计没人会忘。

数据库本身也得先建好。ZIP包里多数会带一个meite.sql或db.sql,导入命令是这样:

mysql -uroot -p123456 < meite.sql

执行导入前建议先手动建库:CREATE DATABASE IF NOT EXISTS ssm_meite DEFAULT CHARSET utf8mb4;否则导入时会报No database selected。导入完成后在Navicat或命令行里执行SHOW TABLES;确认核心表都在,再回IDEA启动Tomcat。启动成功后在浏览器访问http://localhost:8080/ssm-91crh/,看到登录页说明三件套已经和环境咬合上了。

3. 数据库与核心表设计:进销存系统的关键不在库存字段,而在流水记录

3.1 六张核心表的结构:商品、供应商、进货、销售、库存各司其职

进销存系统最少要六张核心表:goods商品表、supplier供应商表、purchase进货单主表、purchase_item进货明细表、sale销售单主表、sale_item销售明细表。商品表存档案信息,供应商表存供货商资料,进货和销售两类单据各拆主表和明细表,是为了支持一张单子包含多个商品。给出最常见的建表语句:

CREATE DATABASE IF NOT EXISTS ssm_meite DEFAULT CHARSET utf8mb4; CREATE TABLE goods ( goods_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品ID', goods_name VARCHAR(100) NOT NULL COMMENT '商品名称', barcode VARCHAR(32) COMMENT '条码', purchase_price DECIMAL(10,2) COMMENT '进价', sale_price DECIMAL(10,2) COMMENT '售价', stock INT DEFAULT 0 COMMENT '当前库存', unit VARCHAR(10) COMMENT '单位:件/箱/斤', category VARCHAR(50) COMMENT '分类', is_deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE supplier ( supplier_id INT PRIMARY KEY AUTO_INCREMENT, supplier_name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20) ); CREATE TABLE purchase ( purchase_id INT PRIMARY KEY AUTO_INCREMENT, supplier_id INT NOT NULL, purchase_time DATETIME, total_amount DECIMAL(12,2), status TINYINT DEFAULT 0 COMMENT '0未入库 1已入库' ); CREATE TABLE purchase_item ( item_id INT PRIMARY KEY AUTO_INCREMENT, purchase_id INT NOT NULL, goods_id INT NOT NULL, quantity INT, price DECIMAL(10,2), subtotal DECIMAL(12,2) );

逻辑说明:goods表里的stock字段看起来是核心,但真正决定库存值的是purchase和purchase_item两张流水表。进货时往purchase主表插一张单,往明细表插对应商品数量;销售时同理。goods.stock只是冗余汇总,用空间换查询速度。

参数说明:DECIMAL(10,2)表示金额最大10位、保留2位小数,超市商品几块钱到几百块完全够用。TINYINT DEFAULT 0用来做状态标记和逻辑删除,比用1、2、3这种裸数字可读性好得多。外键在这个版本里先不建,后面小节单独说原因。

为什么说流水才是核心?超市对账时问的不是“现在库存多少”,而是“这个月一共进了多少、卖了多少、损耗在哪”。这些答案只有流水表能给。如果系统只维护goods.stock一个值,一旦有人手工改库存或者程序写错,连哪里错的都找不回来。流水账可以追溯,冗余字段只能看到结果。

3.2 外键和索引:推迟建外键是为了业务灵活,索引不能省

销售单结构和进货单对称,不再重复列SQL。但有一点值得单独说:purchase表和purchase_item表之间、supplier表和商品表之间,很多毕设源码会直接写外键约束。我一般会建议保留外键定义来保证参照完整性,但要做两个调整。

第一个调整是给所有关联字段建普通索引,比如:

CREATE INDEX idx_purchase_supplier ON purchase(supplier_id); CREATE INDEX idx_item_purchase ON purchase_item(purchase_id); CREATE INDEX idx_item_goods ON purchase_item(goods_id);

逻辑说明:进销存系统的查询特征是按供应商查进货单、按商品查销售明细。没有索引时,数据量到几万条就会明显卡顿,MySQL只能全表扫描。加上这几个索引后,关联查询走索引,响应时间能从秒级降到毫秒级。

第二个调整是商品删除用逻辑删除而不是物理删除,也就是UPDATE goods SET is_deleted = 1 WHERE goods_id = ?,而不是DELETE。原因很简单:商品一旦被历史进货单引用,物理删除会被外键约束拦下来,而后台页面又要处理“删不掉”的报错。is_deleted字段加进来,删除只是标记,历史流水不受影响,查询时默认过滤is_deleted = 0即可。

3.3 事务注解放对位置:库存扣减不是一个步骤,是一个原子操作

进销存系统最容易出问题的操作是进货入库和销售出库。销售出库在代码上至少两步:往sale表插记录,更新goods表的stock字段。如果往sale表插完、更新库存前程序抛了异常,数据库里就会出现“卖了货但库存没减”的脏数据。把这两步放进同一个事务才是正解。Service层代码常见做法是这样:

@Service public class SaleService { @Autowired private SaleMapper saleMapper; @Autowired private GoodsMapper goodsMapper; @Transactional(rollbackFor = Exception.class) public void saleOut(SaleDTO dto) { saleMapper.insertSale(dto); for (SaleItem item : dto.getItems()) { goodsMapper.decreaseStock(item.getGoodsId(), item.getQuantity()); } } }

逻辑说明:@Transactional把整个saleOut方法包成一个事务,任何一个SQL出错,前面所有已执行的SQL都回滚。rollbackFor = Exception.class是关键细节,因为Spring默认只在抛出RuntimeException时回滚,而SQLException这类受检异常不会触发回滚,显式声明后所有异常都能兜住。

参数说明:insertSale和decreaseStock是两次独立数据库操作,事务管理器负责把它们绑在一起。MySQL里InnoDB引擎支持事务,建表时ENGINE=InnoDB不能省。如果哪天发现数据只写了一半,优先检查Service方法有没有被Spring代理,同类里this.saleOut()这种内部调用不会走事务代理,事务会静默失效——这是Java业务系统里特别容易翻车的点。

4. SSM三层架构与核心业务链路:一张进货单从页面到数据库的完整路程

4.1 SpringMVC请求链路:JSP表单到Mapper接口中间经过七个环节

跑通项目后,要理解的不只是“能点”,而是数据到底怎么流动。一张进货单从页面提交到写入数据库,完整链路是:浏览器表单、Ajax提交、DispatcherServlet、Controller、Service、Mapper接口、Mapper XML、MySQL。前端的进货页面通常会收集供应商ID和商品明细数组,组装成JSON发给后端。

Controller代码风格如下:

@Controller @RequestMapping("/purchase") public class PurchaseController { @Autowired private PurchaseService purchaseService; @RequestMapping("/add") @ResponseBody public Result add(@RequestBody PurchaseDTO dto) { purchaseService.stockIn(dto); return Result.success(); } }

逻辑说明:@RequestBody把前端传来的JSON字符串反序列化成PurchaseDTO对象,比传统request.getParameter一个个取值干净得多。@ResponseBody把Result对象序列化成JSON返回给前端,前端再根据success字段决定提示“保存成功”还是弹出错误。Controller本身不做业务判断,只负责参数接收、调用Service、返回结果。

参数说明:@RequestMapping("/add")与类上的@RequestMapping("/purchase")拼出完整访问路径/purchase/add。PurchaseDTO里一般包含supplierId、List 、totalAmount三个字段,正好对应进货单主表和明细表两份数据。

前端Ajax这边,常见做法是用jQuery的$.ajax或$.post把对象转成JSON字符串,Content-Type设为application/json。好多跑不通这个环节的人,问题不在后端,而是前端默认提交了表单格式,后端@RequestBody接了个空对象。记住:用了@RequestBody,前端就要JSON.stringify,并且请求头带application/json,两边对齐才不翻车。

4.2 MyBatis动态SQL:多条件查询和库存台账的写法和边界

进销存页面总少不了条件筛选:按供应商、按时间段、按商品分类查进货单。这种场景正是MyBatis动态SQL的用武之地。Mapper XML里常见的写法:

<select id="selectPurchaseHistory" resultType="com.meite.entity.Purchase"> SELECT * FROM purchase <where> <if test="supplierId != null"> AND supplier_id = #{supplierId} </if> <if test="startTime != null"> AND purchase_time &gt;= #{startTime} </if> <if test="endTime != null"> AND purchase_time &lt;= #{endTime} </if> </where> ORDER BY purchase_time DESC </select>

逻辑说明: 会自动处理第一个AND,条件都不传时生成SELECT * FROM purchase,传了供应商ID则拼上AND supplier_id = ?。>和<是XML里的转义写法,对应SQL中的>=和<=,不转义XML解析直接报错。

库存台账的逻辑比单表查询复杂一步。需求通常是“每个商品累计进了多少、卖了多少、当前应该剩多少”。如果系统建了库存流水表stock_log,SQL直接按商品ID聚合;没有流水表时,常见做法是把进货明细和销售明细UNION起来分组算,MyBatis里可以写两个select分开查,再在Service层合并,或者写一条带UNION ALL的多表SQL。

一条典型的台账SQL是这样:

SELECT goods_id, SUM(CASE WHEN type = 'IN' THEN quantity ELSE 0 END) AS total_in, SUM(CASE WHEN type = 'OUT' THEN quantity ELSE 0 END) AS total_out FROM stock_log WHERE goods_id = #{goodsId} GROUP BY goods_id;

说明:这里的type字段用IN和OUT区分进货与销售,SUM配合CASE把两条方向的数量折算成两个累加列。运行时只要这个SQL查出来的库存和goods.stock对不上,就说明某个事务漏了日志或冗余字段没更新,该去查代码了。

4.3 SSM常用注解速查:一眼看出一个类归谁管、一个方法有什么行为

接手这种SSM项目,最快上手的办法是看注解。注解是Spring给类的“身份标签”,看到类上的注解就知道它待在哪一层。整理一张对照表:

注解标注位置作用常见误用
@Controller控制器类交给MVC层管理,处理请求映射返回JSON时漏写@ResponseBody,页面显示乱码
@Service业务类标记Service组件,纳入事务代理加在接口上,导致实现类不被扫描
@RepositoryMapper实现DAO层组件,同时转换数据库异常只写接口不加注解,包扫描扫不到
@Autowired字段/构造器依赖注入多个同类型Bean时报NoUniqueBeanDefinitionException
@Transactional方法或类声明事务边界同类内部调用、private方法上均失效
@RequestMapping类或方法绑定URL路径类路径和方法路径拼出斜杠问题

逻辑说明:Spring容器启动时会扫描指定包下的类,发现类上有这些注解就创建Bean放进容器。Controller依赖Service、Service依赖Mapper,全靠@Autowired注入。面试里被问到SSM常用注解时,把这张表的职责边界讲清楚就够了。

包扫描配置分两个文件:spring-mvc.xml里一般只扫描com.meite.controller包,spring-mybatis.xml里扫com.meite.service和com.meite.dao。两层扫描范围如果重叠,可能导致一个Bean被创建两次,启动时抛BeanDefinitionStoreException,或者事务代理失效。改配置时始终保持“MVC管控制层,Spring管业务和持久层”这个边界。

5. 避坑指南:SSM进销存从启动到能用的五个高频问题排查

5.1 Tomcat启动成功但打开页面404

现象:IDEA控制台Tomcat日志显示启动成功,浏览器访问http://localhost:8080/ssm-91crh/却一直404。

原因:最常见是Artifact没部署,或者IDEA里Application context配置的路径和实际访问路径不一致。第二个高发原因是web.xml里没配置欢迎页,访问根路径找不着默认页。还有一个隐藏点是war exploded和war两种部署方式,选了war包模式但没重新构建。

解决:打开Run Configuration,确认Deployment下挂了当前项目的war exploded,Application context填/ssm-91crh,和浏览器地址保持一致。然后检查web.xml里有没有 指向index.jsp,没有就补上。改完配置重启,看到“Artifact is deployed successfully”才算真正部署成功。

5.2 中文乱码:页面、数据库、连接串三个位置必须同步统一

现象:页面上输入“洗发水”,保存后数据库看到的是“????”,更夸张的是变成“å æ´æ°´”这种乱码。

原因:三个位置至少有一个不一致。jdbc.url里没有characterEncoding=utf8,MySQL驱动用默认编码传输;或者数据库表是latin1字符集;或者HTTP请求和响应编码没过滤。这三处只要有一处是latin1或ISO-8859-1,中文就会变形。

解决:先改jdbc.properties,URL追加useUnicode=true&characterEncoding=utf8;再确认建表语句带了DEFAULT CHARSET=utf8mb4,已经建好的表用ALTER TABLE goods CONVERT TO CHARACTER SET utf8mb4;修复;最后在web.xml里加Spring的CharacterEncodingFilter,强制请求和响应都走UTF-8,url-pattern配/*覆盖所有路径。

5.3 MySQL驱动版本不匹配:连不上库先看报错信息里的驱动类名

现象:启动时抛No suitable driver found,或者Communications link failure,有时还跟着Caused by的详细内容。

原因:本地安装的MySQL是8.x,项目里却引了5.1.x版本的mysql-connector-java,或反之。驱动与服务端版本差代太远,握手协议对不上,连接只能在建立前就被掐断。

解决:在pom.xml里检查mysql-connector-java的version,MySQL 5.7用5.1.4x,MySQL 8.0用8.0.x。如果拿不准,直接在Maven依赖树窗口看已解析版本,改完重新导入。

5.4 库存变负数:并发扣减必须用SQL层原子操作

现象:两个收银台同时卖最后一件商品,两边都显示成功,商品库存变成-1。

原因:Service代码里先SELECT stock查库存,判断大于0后再UPDATE减一。这两个动作之间有间隙,两个请求同时读到的库存都是1,各自走完判断,各自做减一操作,结果自然变成负的。

解决:把“校验库存和扣减库存”合并成一条UPDATE语句,利用MySQL行锁保证原子性:

UPDATE goods SET stock = stock - #{num} WHERE goods_id = #{goodsId} AND stock >= #{num};

逻辑说明:SQL的WHERE条件里带上stock >= #{num},是让数据库在更新瞬间判断库存是否足够,而不是靠Java代码先查再算。MyBatis执行这条SQL时返回受影响行数,受影响行数为1说明扣减成功,为0说明库存不足要回滚业务。

参数说明:这方案依赖InnoDB的行锁,stock >= #{num}迫使数据库对命中行加锁,两个并发请求到数据库层时会被串行执行,后到的一个因为条件不满足直接返回0行。这是进销存系统保证数据一致性的基础做法,面试问“Java怎么保证数据一致性”时也可以往这个方向答。

5.5 Mapper绑定错误:XML文件没编译进target是经典翻车点

现象:Tomcat启动成功,页面一点按钮就抛BindingException,提示Invalid bound statement (not found),后跟一堆Mapper接口名。

原因:MyBatis的Mapper接口和Mapper XML不在同一个目录,或者XML文件放在src/main/java下,但没有在pom.xml里声明resources包含这个目录,导致编译时XML没有复制进target/classes。

解决:最简单是把XML放到resources/mapper目录,并在spring-mybatis.xml里配置mapperLocations指向classpath:mapper/*.xml。如果XML必须和接口同包,则在pom.xml里补充资源声明:

<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build>

逻辑说明:Maven默认只把resources目录下的文件当作资源打包,src/main/java下的XML不会自动进target。加上这段配置后,Java目录里的XML才会在compile阶段被拷贝出去,接口和实现才能对上。

6. 跑通之后:用Spring Test给库存服务上道保险,再把慢SQL揪出来

项目能跑不代表能放心用。我接手这类进销存系统时,会先做两件事:给库存服务写回归测试,给MySQL开慢查询日志。库存这种数据偏一分,月底对账就头疼十分。

回归测试用Spring Test加载已有的spring-mybatis.xml,真实连接测试库执行一次入库逻辑:

@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-mybatis.xml") public class StockServiceTest { @Autowired private PurchaseService purchaseService; @Autowired private GoodsMapper goodsMapper; @Test public void testStockIn() { PurchaseDTO dto = new PurchaseDTO(); dto.setSupplierId(1); dto.addItem(new PurchaseItem(null, 1, 10, 2.5)); purchaseService.stockIn(dto); assertEquals(Integer.valueOf(10), goodsMapper.selectStock(1)); } }

逻辑说明:这个测试验证的不是某个页面,而是“进货入库后库存是否精确增加”。断言assertEquals(10, stock)直接卡住最核心的业务规则。改过库存SQL、动过事务注解之后,先跑一遍这个测试,再谈其他功能,能省很多手工点页面的时间。

MySQL慢查询日志用来找性能瓶颈,测试环境执行下面两行:

SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1;

long_query_time=1表示执行超过1秒的SQL都会被记录。跑一轮进销存的主流程,再查看慢查询日志文件,集中优化那些频繁出现的全表扫描。常见优化方向就是第3章里说的——给外键字段补索引,给商品表的关键字查询加联合索引。

以前我跑通一套SSM系统后总觉得完工了,后来被库存对账翻车教训过一次,才明白跑通只是开始。现在我每次改完这类系统的业务代码,都先把回归测试跑一遍,再把慢查询日志打开,心里才有底。这比“看起来能点”可靠得多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询