news 2026/10/14 4:08:51

SpringBoot+Vue家电销售平台:从电商系统设计到实战部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue家电销售平台:从电商系统设计到实战部署

在2025年这个时间点,电商类的管理系统项目已经多到让人眼花缭乱,但大多数都停留在“能做出来”的阶段——界面能用、增删改查齐全,可真要部署上线或者拿来作为学习样板,漏洞和设计缺陷就全暴露出来了。这个基于SpringBoot+Vue的家电销售展示平台管理系统,难得地把展示端和管理端揉在了一个项目里,既有面向访客的商品陈列、购物流程,又有后台的订单、库存、用户运维,再加上MyBatis和MySQL这套经典组合,基本覆盖了国内中小型Web系统的绝大多数需求。

整个项目要解决的核心问题很直接:家电这类客单价高、决策链路长的商品,需要一个既能充分展示产品细节、又能支撑销售转化和后台管理的统一平台。前台是“门面”,得撑得起品牌调性;后台是“引擎”,得扛得住日常运营。抱着这套系统啃一遍,你其实就把“一个真实的电商系统是怎么从零搭起来”的完整链路捋清楚了。无论你是刚起步的Java学习者,还是要做毕业设计的学生,或者是想找个成熟模板快速二次开发的小团队,这套项目都值得花点时间沉下去看。

1. 项目整体解析:这套系统到底做了什么

1.1 需求定位与功能边界

先明确一点,这套系统名字里的“销售展示平台”很精准,它不是一个重量级的电商中台,没有复杂的营销引擎、没有分布式事务、没有秒杀系统,它的定位是**“面向中小规模家用电器销售的展示与交易管理系统”**。这样的定位意味着两条主线非常清晰:

  • 展示线:品牌形象、商品橱窗、分类导航、商品详情、图片轮播,目标是让用户“看得舒服、产生购买欲”。
  • 管理线:后台的商品维护、订单处理、库存调整、会员管理、数据看板,目标是让运营者“管得顺手、心里有数”。

这个定位的聪明之处在于,它没有为了炫技而塞入大量超出需求的功能,而是把每个模块都做到了“够用且完整”。我做过的几个实际电商项目里,很多团队反而在一开始就栽在需求膨胀上——做着做着加入优惠券、秒杀、直播带货,结果核心的商品和订单反而做得稀烂。这套系统的功能边界控制,恰恰是它作为学习样板最有价值的点。

1.2 系统角色与核心业务流程

系统的使用角色分成三类,对应的权限和视图完全不同:

角色主要能力典型操作场景
游客/未登录用户浏览商品、查看详情、搜索逛逛首页、看看冰箱的规格参数
注册会员下单购买、查看订单、管理收货信息选好一台洗衣机,加入购物车并结算
管理员商品上下架、库存管理、订单发货、用户禁用后台录入新款空调、修改库存、处理退款

核心业务路径是一条典型的B2C交易闭环:用户浏览商品 → 加入购物车 → 生成订单 → 支付(或模拟支付) → 管理员处理发货 → 用户确认收货。这套流程虽然看起来简单,但里面牵扯的库存扣减时机、订单状态流转、购物车与订单的数据一致性,都是面试和实操中必考的重点。

1.3 技术栈画像与生态定位

从技术选型上看,这就是一套标准的“Java主流技术栈全家桶”组合:

  • 后端:SpringBoot 2.x / 3.x、MyBatis、MySQL、Maven
  • 前端:Vue 2.x / 3.x、Element UI / Element Plus、Axios、Vue Router、Pinia / Vuex
  • 工具链:IDEA、VSCode、Navicat、Postman、Git

SpringBoot负责把复杂的Spring配置“约定优于配置”地简化掉,MyBatis则挑起了SQL与Java对象映射的重任。很多初学者对MyBatis的理解停留在“写XML里的SQL”,但实际上项目中往往能用上它的动态SQL、结果映射、二级缓存等特性——这套系统里对MyBatis的用法,值得你逐行去推敲。MySQL这边,表结构设计、索引使用、事务隔离级别的取舍,才是真正见功力的地方。

2. 架构设计拆解:前后端分离下的工程组织策略

2.1 为什么是前后端分离

这套系统选择前后端分离架构,最直接的动机是让展示端和管理端能够独立迭代、独立部署。前端通过RESTful API与后端交互,这也意味着你本地跑两个端口就够了:前端一个Vite/Webpack的开发服务器,后端一个SpringBoot内嵌Tomcat,非常清爽。

前后端分离的核心难点不在“分开”,而在于如何约定接口。这套系统里会看到统一响应体(通常是{code: 200, data: ..., msg: "success"}),这就是前后端协作的契约。每次接口联调时,前端只需要关心返回的数据结构,后端只需要保证接口路径和响应格式稳定,开发效率确实要比传统的JSP模板嵌套高出好几个档次。

2.2 后端分层设计与分包策略

SpringBoot的项目结构通常遵循固定的分包模式,这套系统也不例外。我见过很多人包结构乱成一锅粥,而这里的分层是教科书级别的参考样例:

com.example.appliance ├── controller // 接收前端请求,参数校验,返回响应 ├── service // 业务逻辑层,事务边界都在这层 │ └── impl // Service接口实现 ├── mapper // MyBatis的Mapper接口(DAO层) ├── entity // 数据库实体类 ├── dto // 数据传输对象,避免entity直接暴露 ├── vo // 视图对象,给前端用的组合数据 ├── config // 全局配置类(拦截器、跨域、异常处理) ├── common // 通用工具类、常量、统一返回体 └── exception // 自定义异常与全局异常处理器

注意区分interface和impl的分离、entity与DTO的隔离,这是很多粗糙项目不具备的。实际开发中,直接拿数据库实体类返回给前端,会导致接口里暴露多余字段(比如密码字段的哈希值),这不仅是规范问题,更是安全隐患。

2.3 前端工程结构与核心目录

Vue这一侧,项目的组织方式是典型的“单页应用 + 组件化开发”模式:

src ├── api // 封装所有axios请求 ├── assets // 静态资源(图片、公共样式) ├── components // 可复用组件(轮播图、商品卡片等) ├── router // 路由配置,区分前台与后台 ├── store // 全局状态管理(用户信息、购物车) ├── views // 页面级组件 │ ├── home // 首页展示 │ ├── product // 商品列表与详情 │ ├── cart // 购物车 │ ├── order // 订单相关 │ └── admin // 后台管理页面 └── utils // 工具函数

重点是路由层面的前后台分离,一般会使用嵌套路由:/admin下挂载所有管理页面,配合导航守卫做登录认证。这样设计的好处是,前台的页面和后台的管理页面互不干扰,权限逻辑只需要在守卫中间件里统一处理。

2.4 为什么选用MyBatis而不是JPA

聊到数据库访问层,很多人会纠结MyBatis和JPA到底选哪个。这套系统选用MyBatis,我更倾向于认为它是站在“可控SQL”的角度做的决策。家电销售平台的查询场景特别多:多条件筛选、价格区间、分类联动、模糊搜索,这些场景用MyBatis动态SQL来写,逻辑会非常直观:

<select id="selectProductList" resultType="com.example.appliance.entity.Product"> SELECT * FROM product <where> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND (name LIKE CONCAT('%', #{keyword}, '%') OR description LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price &lt;= #{maxPrice} </if> </where> ORDER BY create_time DESC </select>

这种动态拼接的能力,是JPA注解式查询很难取代的。在数据量达到几十万条商品记录时,DBA和研发协同调优的,就应该是这种可见、可控、可explain的SQL,而不是由ORM自动生成的“黑盒SQL”。

3. 核心模块实战拆解:商品、购物车、订单与权限

3.1 商品展示模块:参数规格与品牌分类的三层联动

家电商品和普通快消品最大的不同在于参数维度又深又多。一台冰箱涉及容量、能效等级、面板材质、冷冻能力、噪音值等十几个参数;一台空调涉及匹数、变频/定频、适用面积、噪音、制冷剂类型。设计商品模型时,必须把这些参数从商品主体中抽离出来,否则一张product表会被撑得无比臃肿。

这套系统里的商品管理方案是典型的“品牌-分类-商品”三层结构。分类表管理层级关系(大分类如“大家电”、“厨卫电器”,子分类如“冰箱”、“空调”),品牌表与商品表通过品牌ID关联。商品表本身只存通用字段(名称、价格、库存、主图、简介),而详细的规格参数通过JSON格式或参数扩展表存储。

实际操作建议是:规格参数的存储优先考虑JSON字段。比如MySQL的JSON类型,配合MyBatis的TypeHandler可以将JSON字符串自动映射成Java对象,既减少关联表数量、又保留查询能力。如果想在参数上做筛选,再考虑拆出单独的规格表。这套系统如果扩展,这个方向是性价比最高的。

3.2 购物车与订单状态机的设计精髓

购物车模块看起来简单,但包含了两个常见的业务陷阱。

第一个陷阱是未登录用户的购物车。常见的方案有两种:存localStorage(本地存储)或者后端Redis。如果你学习这套系统,会发现它的做法通常是将购物车数据绑定到用户上,只有登录后才能使用。但真实电商场景里,强制用户先登录再加入购物车,是非常影响转化率的。如果你要做二次开发,建议至少加一层localStorage兜底,用户登录后把本地数据同步到服务端。

第二个陷阱是库存的扣减时机。家电这类商品没有超卖问题到话可能不算致命,但正确做法仍然是:提交订单时锁定库存,支付成功后才真正扣减可用库存,超时未支付则释放锁定。这套系统里大概率采用的是相对简化的“下单即减库存”,这在小流量场景下没问题。但你心里要清楚这个简化,以及如果要接真实支付场景时需要做哪些改造。

订单模块是整套系统里状态流转最复杂的部分。一个合理的订单状态机应该是:

待支付 → 已支付/待发货 → 已发货 → 已收货 → 已完成 ↓ ↓ 已取消 已退款

每次状态变更都要记录操作时间和操作人,这就引出了“订单状态日志表”。很多项目图省事不做日志表,只靠傻傻地更新订单表状态字段,最后出纠纷时根本无从追溯。这个系统的订单表旁边如果配了订单操作日志表,那就是很专业的做法;如果没有,我建议你二次开发时一定补上。

3.3 用户权限与登录认证:拦截器和JWT交互方案

权限认证这块,市面上主流的两种风格——Session方案和JWT方案——这套系统更可能采用JWT + SpringBoot拦截器的方式。流程是这样的:

  1. 用户发送用户名密码,后端校验通过后生成Token返回
  2. 前端把Token存到localStorage或Pinia里,每次请求在请求头里带上Authorization: Bearer <token>
  3. 后端通过拦截器统一解析Token,并把用户信息放入ThreadLocal供后续业务使用

关键实现要点是拦截器里对白名单的处理。比如/api/login、/api/register、/api/products这些接口必须放行,而/api/admin/**需要校验管理员角色。一个统一的AuthInterceptor配合HandlerMethod上的自定义注解(比如@RequireRole("admin"))就能优雅地解决这个问题。

安全这个坑我要多说一句:密码存储必须使用带盐的哈希算法,比如BCrypt。千万不要用MD5直接加密,更不能明文存数据库。Spring Security自带的BCryptPasswordEncoder就可以直接集成,这也是这套系统如果正规一些应该有的配置。

3.4 后台管理系统:基于权限的组件化页面

后台管理页面通常是前台页面工作量的两倍。你在这套系统的/admin路由下面会找到商品管理、分类管理、品牌管理、订单管理、用户管理、数据统计等板块。这里想提醒你注意的是,后台管理页面的数据绑定思路和前台是完全不同的:

  • 前台是“展示驱动”:接口以最方便页面渲染的方式返回数据,比如商品列表直接返回前端需要的VO。
  • 后台是“CRUD驱动”:围绕业务表格的增删改查,配合查询表单做分页筛选。

因此后台的每个管理页,标准组合就是“搜索表单 + 数据表格 + 分页器 + 弹窗表单”。Element UI的el-table、el-form、el-dialog组合,包括常见的新增/编辑弹窗、删除确认框、状态标签着色,这套系统都会覆盖得很全面。我建议你重点看看它是怎么封装通用的表格请求逻辑的,很多项目会封装一个useTable组合函数或mixin来减少重复代码,这个设计意识很值得借鉴。

4. 数据库设计与核心表结构优化

4.1 核心表关系梳理

这套系统背后的数据库设计,从ER图看应该是有几条清晰的业务线。我根据自己的经验,推测核心表结构大概如下:

表名核心字段作用
userid, username, password, phone, avatar, role, status用户与管理员统一存储,role区分角色
categoryid, name, parent_id, level, sort商品分类,支持无限层级
brandid, name, logo, description品牌信息
productid, name, category_id, brand_id, price, stock, main_image, sales_count商品主表
product_paramid, product_id, param_key, param_value商品扩展参数
product_imageid, product_id, image_url, sort商品相册图集
cart_itemid, user_id, product_id, quantity, checked购物车条目
ordersid, order_no, user_id, total_amount, status, receiver_info订单主表
order_itemid, order_id, product_id, product_name, price, quantity订单明细快照
addressid, user_id, receiver_name, phone, province, city, detail收货地址

4.2 三个容易被忽略的关键设计

第一个设计是订单明细的快照机制。订单明细表里面的商品名称、价格,不能通过JOIN商品表实时获取,而应该在订单生成的那一刻把商品信息“复制”一份存进order_item表。为什么?因为商品后来可能改名、下架,甚至删除,但已生成的订单必须保持当时购买的信息原样。很多初学者不理解这点,常在订单页直接关联商品表,遇到商品被删后订单页直接报错。

第二个设计是逻辑删除而非物理删除。商品和分类表都应设置deleted字段,默认0。删除商品只是置为1,这样可以避免破坏历史订单的外键关系。物理删除是开发中最容易埋雷的操作。

第三个设计是唯一索引与订单号生成策略。订单号必须是唯一的,用数据库自增ID不够安全(会暴露销量)、用UUID又太长没规律。常见做法是使用时间戳+随机数+用户ID的拼接,并给订单号字段加唯一索引。高并发场景下,如果碰上订单号重复导致唯一索引冲突,代码里要做重试机制。

4.3 慢查询优化:从索引到Explain分析

数据库这块,这套系统面对的是一个典型的中等数据量场景。用户量和订单量增长后,最先扛不住的就是商品列表的模糊搜索和订单列表的按条件查询。“索引失效”是我见得最多的性能问题。

以订单查询为例,如果经常按user_id和status过滤,那么(user_id, status)的联合索引就很有必要。MySQL索引优化有一条基本准则:区分度高的列放在联合索引前面。但像status这种只有几个枚举值的列,单独加索引效果非常差,放在联合索引靠后更合适。

我强烈建议你把项目跑起来后,在Navicat或命令行里执行几次下面的SQL,好好观察执行计划:

EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 0;

重点看key字段是否命中了你建的索引、type是ref还是ALL、rows扫了多少行。这套系统的数据量虽然不够大,但通过Explain训练自己的优化直觉,这对你以后处理真实生产问题非常有价值。

5. 从源码到部署:本地运行与二次开发的完整流程

5.1 环境准备与快速启动

拿到这套源码后,第一步不是着急看代码,而是先把环境对齐。我按自己的经验给你整理一份快速启动清单:

  1. 安装JDK 1.8+(或17),配置JAVA_HOME环境变量
  2. 安装Maven 3.6+,配置阿里云镜像加速依赖下载
  3. 安装MySQL 5.7+或8.0,新建数据库并导入项目提供的init.sql脚本
  4. 安装Node.js 14+,前端用npm install或yarn安装依赖
  5. 后端改application.yml里的数据库用户名密码,直接mvn spring-boot:run启动
  6. 前端npm run dev启动开发服务器,按提示访问前端地址

这里最容易栽的坑有两个:一是MySQL 8.0和5.7的驱动配置不一样,8.0的驱动类名是com.mysql.cj.jdbc.Driver,连接串里还得加上serverTimezone=Asia/Shanghai;二是前端项目如果用的是Vite,node_modules安装速度可能很慢,建议直接用淘宝镜像源。

5.2 测试账号与演示数据梳理

这类系统源码里通常都会预留演示账号。管理员账号一般是admin/admin123,会员账号可能是user/123456。登录后台以后,建议按“商品管理 → 分类管理 → 品牌管理 → 新增商品”的顺序走一遍,把这个系统里最复杂的一组数据关联关系摸清楚。我自己的习惯是:拿到代码后先用测试账号把每条业务线从头到尾走一遍,然后打开数据库,把对应的表数据对着页面过一遍。这样你能很快建立起“页面 → 接口 → SQL → 表”的全链路认知。

5.3 二次开发的核心切入点

如果你不只是想跑通源码,还想基于它做扩展,我建议优先从这三处入手:

第一处:接入真实支付。当前系统多半是模拟支付(直接点支付就改状态)。你可以尝试接入支付宝沙箱或微信支付沙箱,需要改动的核心位置是订单服务的支付回调逻辑。这个改造会让你彻底理解“支付回调验签”和“订单状态更新幂等性”这两个生产中最重要的概念。

第二处:增加商品评价模块。电商系统里评价是闭环中很关键的一环。在现有表结构上增加review表,关联用户与订单明细;再在商品详情页加入评价列表。这个过程能锻炼你设计一对多关系的能力。

第三处:数据统计可视化。后台的首页如果只是简单的欢迎页,你可以尝试接入ECharts,增加“近30天销售额趋势”、“家电品类销售占比”等图表。这需要写聚合SQL(GROUP BY+DATE_FORMAT),算是我见过最有性价比的前后端联动练手题。

6. 常见问题与避坑血泪实录

6.1 前端请求跨域与404问题

前后端分离开发中,跨域是绕不开的第一道坎。表现是前端页面能打开,但所有请求都报CORS错误,或者请求能发出但响应被浏览器拦截。解决方案一般是在后端加一个全局的跨域配置类:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

另一个容易混淆的问题是:前端路由用了history模式,刷新页面时报404。这是因为开发服务器的路由回退没有配置;生产部署时Nginx需要加一段try_files $uri $uri/ /index.html。这个坑我当年踩了整整一下午,这里直接帮你排雷。

6.2 MyBatis常见的Mapper绑定异常

“Invalid bound statement (not found)”是MyBatis生涯里出现频率最高的报错,没有之一。引起这个错误的原因不外乎三种:

  • Mapper接口和XML文件中的namespace不一致
  • 接口方法和XML中的id对不上
  • 配置里没有扫描到XML目录(mapper-locations路径写错)

排查方法很机械:打开XML文件,确认namespace是否和接口全限定名一致;确认方法名、参数名、返回类型是否都匹配。这个系统里如果配置文件加了mybatis.mapper-locations=classpath:mapper/*.xml,那XML必须放在resources/mapper目录下,放错位置一样报错。

6.3 年薪五万和五十万的差距:事务与大字段

关于事务,我要说一个本项目里最容易出问题的点:下单操作必须加事务。一个下单动作牵扯到创建订单主记录、插入多条订单明细、更新商品库存、清空购物车,这几个步骤要么全部成功要么全部回滚。如果没加@Transactional,订单生成一半库存扣了但订单保存失败,你就会被无穷无尽的售后问题缠住。在Service层的方法上标注@Transactional(rollbackFor = Exception.class)是底线操作。

关于大字段,商品详情页通常包含很长的图文内容,可能是一段HTML富文本。这类内容如果直接加载进列表查询,会让列表响应变慢,还会造成不必要的网络传输。正确的分法是:商品主表只存短描述,长图文详情放到product_detail表,用product_id关联——列表接口只查主表,详情接口才加载富文本字段。

6.4 前端状态管理的取舍:什么时候用Pinia/Vuex

很多初学者有个坏习惯:什么都往Vuex/Pinia里塞。这套系统的购物车、用户信息放全局状态管理是合理的,但商品列表、订单列表这些页面级的数据,如果也塞进全局Store,刷新页面就要重新拉取、状态还会互相污染,完全是给自己找罪受。正确的做法是,只把需要跨页面共享、且需要响应式更新的数据放入全局状态。判断标准很简单:如果数据只有当前页面在用,那就放在组件内部的ref/reactive里,不要全局化。

7. 我的实测心得与给你的直接建议

把这套系统完整跑一遍、再动手改几个模块之后,我的总体评价是:它非常适合用“先跑通、再改造、后思考”三个步骤去消化。跑通让你建立自信,改造让你掌握技能,思考设计背后的取舍才是让你真正超过同龄开发者的关键环节。

我自己实际操作的感觉是,这套源码最大的学习价值反而不是那些“正确”的写法,而是那些“界限模糊”的地方——比如Token过期后前端如何处理、订单状态并发更新怎么防、商品图片上传如何限制类型和大小。这些真实项目一定会遇到的边界场景,恰恰是这段源码里最值得你动手去补完的部分。

最后给你一句掏心窝的建议:学这套源码,不要沉迷于“跑起来了”的成就感。你可以在现有基础上给自己加一个需求,比如“把商品列表改为按销量排序并且支持多条件组合筛选”,然后完整走一遍“设计表结构 → 写SQL → 写Mapper接口 → 写Service层 → 写Controller → 前端接数据”的流程。这样的一个闭环走下来,你收获的绝对不只是技术本身,还有对整套Web系统运作方式的直觉判断。把源码当脚手架,早日走出自己的路,这才是看这类项目真正的意义。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 4:08:16

《启示录》产品管理方法论:告别堆功能,用原型验证用户价值

1. 这本书到底在讲什么&#xff1a;把“做产品”从“做项目”里拉出来我入行做产品差不多十年&#xff0c;经手过好几个从零到一的项目&#xff0c;也见过大量“上线即死亡”的功能模块。有段时间我很困惑&#xff1a;明明团队很拼、排期很紧、代码质量也说得过去&#xff0c;为…

作者头像 李华
网站建设 2026/10/14 4:08:05

H5开红包特效实战:CSS翻盖动效、Canvas粒子系统与移动端优化全解析

简介&#xff1a;H5开红包特效是一套面向网页前端开发者的轻量级互动组件代码&#xff0c;服务于社交活动、电商营销等需要模拟开红包玩法的场景&#xff0c;解决从零搭建交互逻辑与视觉动效的重复劳动问题&#xff0c;覆盖从点击到展示结果的全流程。压缩包共9个文件&#xff…

作者头像 李华
网站建设 2026/10/14 4:08:00

基于PySpark在人力资源管理中的应用:基于机器学习算法的员工流失预测系统|毕设答辩|大数据专业项目|毕设项目|大数据专业

一、项目介绍 摘 要 本系统是一款基于机器学习算法的员工流失预测平台&#xff0c;集成了Django框架、机器学习技术和Spark大数据处理能力&#xff0c;旨在为企业提供精准的员工流失预测服务。系统前台功能丰富&#xff0c;包括训练模型数据、系统管理、数据探索、模型预测以及…

作者头像 李华
网站建设 2026/10/14 4:07:54

走进C语言-----基础篇

C语言是一门编译型计算机语言&#xff0c;代码需要经过编译和链接生成可执行程序才能运行的&#xff0c;C语言代码经过编译生成目标文件&#xff0c;目标文件和链接库通过链接生成可执行程序。 2.main函数&#xff1a;main函数是程序的入口&#xff0c;一个程序中有且只有一个m…

作者头像 李华
网站建设 2026/10/14 4:07:36

计算机毕业设计选题推荐:基于大数据的天气预报预警数据可视化与分析|毕业设计选题|计算机毕设|选题推荐|毕设指导|项目定制|源码|高质量项目

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、PHP、.NET、Node.js、GO、微信小程序、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇…

作者头像 李华