news 2026/9/9 9:14:06

Java旅游系统源码实战:多端架构、订单库存与二次开发避坑指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java旅游系统源码实战:多端架构、订单库存与二次开发避坑指南

前阵子老同学找到我,说他们旅行社准备上一套线上预订系统,需求列得很干脆:“小程序能订票、公众号里能下单、微信群转发的H5活动页也能直接买,后台最好能改价格、排期、库存”。他最后补了一句:“网上不是有很多JAVA旅游系统源码嘛,你帮我看看靠不靠谱。”做了这么多年Java后端,类似问题我接过不止一次,所以这次我没急着下结论,而是把市面上这类源码的结构、业务模型和多端适配逻辑整体捋了一遍。这篇文章就从“畅享旅游系统”这类Java旅游系统源码出发,聊一聊一套支持小程序、公众号和H5的旅游系统,背后到底需要解决哪些核心问题,以及你在接手、二次开发、部署上线时会踩到哪些坑。无论你是打算选型的技术负责人,还是准备拿源码练手的Java开发,都应该能从里面读出一些比“能跑demo”更实在的东西。

1. 先搞清楚一件事:旅游系统不是套了电商模板就能交付的项目

很多第一次接触旅游系统的人会下意识觉得,这不就是“商品、购物车、订单、支付”四件套吗?换个皮就行了。真实情况远没有这么简单。旅游行业的交易对象不是标准化的实物商品,而是一系列“在特定时间、特定条件下才成立的出行服务”。这个本质差异,会直接改变整个后端的建模方式。

1.1 旅游业务的几个“非标”特征

先说产品维度。一个旅游产品由“线路、出发日期、套餐类型、出行人数”共同决定。同样是“云南大理丽江6日游”,7月1日出发和9月15日出发可能是两个完全不同的价格,余位也不一样;同一日期下,成人价、儿童价、单房差可能又是完全不同的SKU。普通电商的SKU库存是个静态数字,旅游系统的库存本质上是一张“日历表”。

再说订单链路。实物电商用户付款后,系统基本只需要推给仓库发货。旅游订单付款后还要经历“确认出行人信息、供应商二次确认、出团通知书发送、入园/上车核销、行程结束后的售后”等多个阶段。每个阶段的超时时间、取消规则、退款比例都不一样,如果底层的订单状态设计得不够灵活,后面每加一个业务需求都要动一次表结构,那就很痛苦了。

所以在看这套Java旅游系统源码时,我首先不看页面好看不好看,而是看两样东西:一是日期库存怎么建表,二是订单状态机怎么定义。这两块立住了,系统才真正像个旅游系统;立不住,界面再花哨也只能当展示Demo用。

1.2 为什么“小程序+公众号+H5”会成为标配需求

很多旅游公司并不是只有一个获客入口。公众号用来做内容种草和品牌沉淀,小程序承载日常预订和会员运营,H5页面则主要投放在朋友圈广告、分销员分享、短信营销链接等场景。这三个入口如果各做一套系统,光是账号打通和订单归集就能把运维逼疯。所以“一套服务端支撑三端”几乎是最合理的解法。

这里也解释了为什么这类系统偏爱Java技术栈。旅游业务的交易金额通常比较大、售后周期长,团队对稳定性要求高,Java生态里的Spring Boot、MyBatis-Plus、Redis、MySQL这些组合在中小型项目中足够成熟,无论是招聘人员还是找外包维护,都比冷门技术栈更稳妥。

2. 从源码工程结构看架构取舍:一个后端怎么撑起三个用户端

拿到源码第一件事不是急着启动,而是先看工程目录。虽然每家写的源码风格不同,但成熟的旅游系统通常都分成管理端和用户端两大块。

2.1 后端模块的前后分离设计

以我过手一遍的这套源码为例,后端基本结构长这样:

com.changxiang ├── framework // 通用配置、拦截器、异常处理 ├── admin // 后台管理端接口模块 │ ├── controller │ ├── service │ └── mapper ├── api // 用户端接口模块,供小程序/公众号/H5调用 │ ├── controller │ └── service ├── modules │ ├── product // 旅游产品、分类、线路、日历库存 │ ├── order // 订单主表、明细、出行人、状态流转 │ ├── pay // 微信支付、退款、回调 │ ├── marketing // 优惠券、积分、推广员 │ ├── member // 会员资料、收货地址、实名/出行人 │ └── notify // 短信、公众号模板消息、小程序订阅消息 └── common // 返回体、常量、工具类

关键点在于:用户端模块(api)是三个前端共用的,而不是给小程序单独写一套Controller、给H5再写一套。这样做的好处非常明显,业务逻辑只维护一份,小程序端、公众号H5端、外部H5端调用的是同样的接口,只是不同端在登录方式、支付调起参数上有所区别。

2.2 框架选型背后的理由

大多数这类Java系统会采用Spring Boot作为基础框架。版本通常是2.x或3.x,配套MyBatis-Plus做数据库操作,Redis存会话和缓存,MySQL存业务数据。有些更完整的源码会把RuoYi这类后台脚手架作为管理端基础,再在上层扩展业务模块。单纯从“快速交付”的角度,这个组合确实能省不少事。

我特别想提醒一点:看到“支持小程序+公众号+H5”的描述,不要幻想它是一套前后端彻底分离的微服务架构。绝大多数有实战价值的源码都是“单体内聚”的,也就是一个Spring Boot应用同时承载管理端接口和用户端接口。单体应用不等于落后,对于单景区、单旅行社每天几千到几万单的业务量,单体后端配合MySQL读写分离和Redis缓存完全扛得住,反而比盲目拆微服务节省大量部署和运维成本。

2.3 管理后台与用户端的数据边界

管理后台负责复杂的数据维护,例如产品上架、日期价格设置、订单审核、优惠券发放、数据报表;用户端只暴露可预订产品、下单、支付、查看订单等必要接口。

这里有个我比较认可的实践:即使是同一个业务表,管理端和用户端的查询逻辑也应该分开。比如后台查看订单可以列表里带出支付单号、供应商、操作日志等内部数据;用户端的订单详情则只应该包含用户下单时看到的信息、当前状态提示、退款进度。把“内部视角”和“用户视角”的数据结构混在一起,是旅游系统二次开发时最常见的安全隐患。

3. 账号打通才是这套多端系统的灵魂:微信生态登录态设计

很多开发者拿到类似源码,第一反应是跑起来看看首页长什么样。但我一般会先打开登录相关的代码,因为小程序、公众号、H5这三端的用户身份体系是截然不同的,让三种入口的用户归并到同一个“会员账号”下,是整个系统成立的前提。

3.1 openid、unionid、手机号,哪个才是真正的用户ID

微信生态里的用户标识有三个容易混淆的概念。小程序端调用wx.login()后,后端拿code去微信接口换回的openid,每个小程序都不同;公众号网页授权拿到的openid,每个公众号也都不一样。也就是说,同一个微信用户,在小程序里和公众号里的openid是两个值。如果系统只拿openid做主键,用户会分裂成两个账号。

解决这个问题的标准方案是unionid。前提是:小程序和公众号必须绑定在同一个微信开放平台账号下。用户只要在绑定过的任一应用里授权过,后端就能通过接口数据关联到同一个unionid。因此成熟的旅游系统源码一定会保留unionid字段,并且会把“首次登录自动注册,再次登录通过unionid找回老账号”作为核心逻辑。

很多人在测试环境只有小程序没有公众号,导致这套逻辑测不全。等上了生产,用户在公众号里下单后发现自己在小程序里买的优惠券、积分、订单记录全都不见了,才回头查unionid配置,浪费不少时间。

3.2 三种端口的登录流程差异

具体流程上,三端差别很明显:

小程序是典型的静默登录:前端wx.login()拿code,请求后端/api/auth/wx-mini-login,后端通过code2session接口拿openid和session_key,再返回自定义token。

公众号内嵌H5走的是OAuth2网页授权。如果只需要识别用户身份,用snsapi_base静默授权即可,用户几乎感觉不到跳转;如果要拿到头像昵称,才需要snsapi_userinfo弹出授权页。旅游业务中比较推荐先snsapi_base静默建档,等用户真正下单需要实名信息时再引导填写,因为现在微信对主动弹窗授权越来越敏感,一上来就弹授权框的转化率往往很差。

微信外的H5则根本没有微信code可用,系统只能走“手机号+短信验证码”注册登录。注意这并不意味着H5用户进不来,很多旅游产品的浏览和下单其实不需要强登录,等到支付阶段再让用户输手机号或微信扫码登录,体验会更顺。

3.3 后端如何统一管理会话状态

看这类源码时,重点关注它怎么管理登录态。现在很多项目喜欢直接把JWT塞给前端,然后每个接口解析令牌。对于旅游系统,我反而更建议用“Redis存储会话”的方式。原因很朴素:旅游订单生命周期长,又有大量运营人员需要后台代客下单、查看用户订单的场景,后台希望随时能把某个用户踢下线,或者强制延长VIP客户的会话时长。如果token越权逻辑在JWT里写死,这些运维操作实现起来会绕一大圈。

一个合格的登录拦截器应该长这样:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); LoginUser user = tokenService.getLoginUser(token); if (user == null) { throw new BusinessException(401, "登录已过期,请重新登录"); } UserContext.setCurrentUser(user); return true; } }

这里的tokenService内部实现是查Redis,而不是解签JWT。UserContext用线程变量保存当前登录人,后续所有业务方法都能拿到UserId。这样设计后,登录来源在小程序、公众号还是H5已经无关紧要,后面接口层面只认token,不需要频繁地判断来自哪个端。

3.4 用户信息同步的一个隐藏细节

很多人会忽略的是用户头像昵称的更新策略。小程序端可以拿着session_key解密出手机号或用户信息,但解密手机号需要小程序前端先触发getPhoneNumber;公众号授权则不一定能拿到最新头像。所以源码里用户表通常会拆成“基础账号表”和“第三方授权资料表”两张表:账号表主键是自增userId,unionid作为唯一标识;授权资料表存每个端的openid、头像、昵称。不同端登录后,各自更新各自的资料,再通过unionid归并到同一userId。这个设计简洁可靠,后续万一小程序或公众号要换绑,也不需要动主账号结构。

4. 日历库存与订单状态机:旅游交易链路的两块硬骨头

如果只说框架和登录,这篇文章和其他“源码介绍”就没区别了。真正能拉开差距的,是旅游业务核心数据模型的设计。我还是坚持那句话:看旅游系统源码,先看这两块,基本能判断写这套代码的人是不是真正做过旅游业务。

4.1 日历库存表该怎么设计

普通电商的商品表加库存字段就完事了。旅游系统不行。以“一日游”产品为例,同一个产品每天都可以有自己的库存和价格,因此存在一张与产品关联的“日期库存表”。常见的表结构类似:

CREATE TABLE `travel_date_stock` ( `id` bigint NOT NULL AUTO_INCREMENT, `product_id` bigint NOT NULL COMMENT '产品ID', `sku_id` bigint NOT NULL COMMENT '套餐/票型ID', `travel_date` date NOT NULL COMMENT '出游日期', `total_stock` int NOT NULL DEFAULT 0 COMMENT '总库存', `stock` int NOT NULL DEFAULT 0 COMMENT '剩余库存', `price` decimal(10,2) NOT NULL COMMENT '当天售价', `child_price` decimal(10,2) DEFAULT NULL COMMENT '儿童价', PRIMARY KEY (`id`), UNIQUE KEY `uk_product_sku_date` (`sku_id`, `travel_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意那个唯一索引,它保证了同一个套餐在同一天只有一条记录,这是防止并发重复插入的关键。

实际操作中,库存扣减不能用“先查再减”的套路,要使用条件更新原子扣减:

UPDATE travel_date_stock SET stock = stock - #{num} WHERE sku_id = #{skuId} AND travel_date = #{travelDate} AND stock >= #{num}

这个SQL执行后,如果影响行数为1,说明扣减成功;影响行数为0,说明余位不足或日期不存在。绕开事务里的分布式锁,也不会超卖。

不过这里有个值得细想的业务场景:一个旅游线路通常包含“成人票”、“儿童票”等不同票型,它们的库存可能是共享的,比如一辆大巴总共49座,成人票和儿童票共享这49个名额。如果系统为成人票和儿童票各建一条库存记录,各自有50个库存,就会出现超卖风险。成熟的源码会做“父SKU库存”或“产品级日期库存”的概念,把基础库存挂在产品上,票型只做价格差异和儿童占比限制。如果源码里只是简简单单给每个票型一个独立stock字段,遇到真正的旅游团队项目时一定会在旺季出事。

4.2 订单状态机的正确打开方式

旅游订单的状态比实物电商多出一截,至少要覆盖“待支付、已支付待确认、已确认待出行、出行中/已完成、退款中、已退款、已取消”这些核心节点。如果状态字段是简单的string,乱写一通,后面对账和售后会非常难做。

一个规范的订单状态流转如下:

状态含义可操作行为
CREATED已创建待支付支付、取消、超时关单
PAID已支付待确认供应商确认、申请退款
CONFIRMED已确认待出行出团通知、申请退款/改期
TRAVELLING行程中核销/服务中
COMPLETED已完成评价、申请售后
REFUNDING退款处理中财务审核、原路退回
REFUNDED已退款关闭订单
CANCELLED已取消

状态机不是一个简单的“当前状态”字段,还包括状态变更记录表(order_status_log)。每一笔订单从创建到完成,每一步谁在什么时间把状态改成了什么,都应该可追溯。这在处理客诉和财务对账时几乎是必需品。

退款流程也值得多看两眼。旅游的非标准化决定了全额退款和部分退款会频繁发生:出发前7天以上退款不扣费、3天前扣20%、当天不可退。这种阶梯退改规则在源码里通常是独立的一张refund_rule表,而不是在代码里写一堆if else。如果看到哪套源码把退改规则写死在业务代码里,趁早避开,后期维护成本太高。

4.3 超时订单处理与重复支付防护

旅游系统里“锁座”和“锁价”是很敏感的操作。用户选择了某个出游日期的库存并进入支付页,系统应当为这个订单短期占住余位,比如15分钟,超过未支付自动释放。这通常在订单表里用一个expire_time字段控制,再配合一个定时任务扫单:把状态为CREATED且超时的订单改成CANCELLED,并把步骤4.1里的库存加回去。

这里特别容易踩一个坑:用户支付成功了,但支付回调因为网络问题延迟到达,此时定时任务判断“未支付”把订单取消了,库存也释放了。然后回调来了,系统一看订单已经取消,是给用户退款还是把订单状态改回已支付?两种做法都有瑕疵。最稳妥的方案是:把“取消订单”动作判断为只有CREATED状态才能取消,已支付回调进来时如果发现订单状态是CANCELLED,必须触发自动退款并记录异常日志,而不是静默忽略。处理这个情况的代码往往只有十几行,但少了这十几行,旺季退款工单就能堆满客服的桌面。

5. 支付与消息通知:三端差别最大、踩坑最多的地方

登录解决了“用户是谁”的问题,支付和消息通知则解决了“钱怎么收、服务怎么触达”的问题。这三块在微信生态里各有一套规则,稍微混用就会线上翻车。

5.1 支付请求的渠道差异

旅游交易金额高,支付稳定是命根子。源码里通常会把支付模块单独抽出来,统一对外提供PayService.createOrder(orderId, channel)方法,内部再按渠道分流。

小程序支付场景必须在微信小程序环境内调起,后端调用统一下单接口时使用小程序的appid和当前用户的openid,拿到prepay_id后签名返回给前端,前端用wx.requestPayment拉起支付。

公众号H5的场景稍微复杂一点。如果H5页面运行在微信公众号内、使用了公众号OAuth登录,那么支付可以沿用JSAPI支付;但如果H5页面只是运行在普通浏览器里,用户既没有openid也没有公众号环境,就需要走微信支付的“H5支付”,这时后端下单价会多传一个scene_info,微信要求发起支付的页面域名必须在商户平台配置为H5支付域名。

很多刚跑通源码的人会把小程序下单、公众号下单、H5下单全用同一个支付方法,结果上线后出现“当前页面URL未注册”之类的报错。所以源码里查看订单表应有一个order_source字段来标明订单来源,下单、支付、后续退款对账都要以这个字段为依据。

支付回调处理也要仔细看。微信支付回调是异步的,而且网络不稳定时微信会重试多次,所以处理回调的接口必须具有天然幂等性:判断支付单号是否已处理过,已处理直接返回成功,不再重复修改订单。同时回调验签绝对不能省,一旦验签失败要记录下来并返回错误信息,防止有人伪造回调把订单改成已支付。

5.2 退款与对账的逻辑

退款在旅游系统里是个高频操作:用户申请退款、供应商确认、财务原路退回。微信支付的退款接口也有自己的异步结果通知,因此代码里需要一张refund_record表,记录退款单号、退款金额、原订单号、操作人、退款状态。退款不代表订单状态立刻变成REFUNDED,而是要等微信退款回调确认。很多源码把退款处理得太简单,直接在Controller里同步调用微信退款接口后立刻把订单改成退款成功,一旦网络超时,用户钱没收到、后台状态却是已退款,后面全乱套。

5.3 公众号模板消息与小程序的订阅消息

下单成功后的出团通知、出发前一天提醒,是旅游系统提升体验的重要环节。

公众号端通常使用模板消息或订阅消息。模板消息有行业类目限制,申请时产品类目要匹配;现在微信把模板消息逐步收紧,新注册的公众号更多使用“订阅消息”能力,用户需要主动授权一次才能收到一条消息。系统设计上要预先规划好用户会在哪个节点授权:用户完成支付后请求授权订阅“出团通知”,是转化率最高也最不打扰人的做法。

小程序端类似,调用wx.requestSubscribeMessage让用户勾选订阅,后端在出行前一天通过接口推送。需要特别注意的是,订阅消息的一次授权只对应一次推送,如果业务上可能推送多条(比如先发订单确认、再发出团提醒),用户需要授权该消息模板两次。源码里如果没有做“订阅授权次数管理”,很容易出现用户下完单却没收到后续关键通知的情况。

6. 从“能跑通的源码”到“稳定上线”:部署与二次开发要过的几道关

很多开发把源码在本地启动成功后,就觉得万事大吉。但生产环境和本地开发完全是两码事,尤其是牵涉微信生态时,一堆配置项要一一核对。

6.1 微信公众平台侧的配置清单

一个最容易让新手崩溃的报错是“redirect_uri参数错误”。这通常不是代码逻辑问题,而是公众号后台的“网页授权域名”没有配置,或者配置的域名与代码里OAuth2回调地址不完全一致。注意这里的域名不要带协议头和路径,必须是精确到域名的格式。

小程序端调试时经常遇到“request合法域名不合法”。小程序的网络请求只能在开发设置-服务器域名里配置的域名下进行。开发模式可以勾选“不校验合法域名”,但上线前一定要把https://api.xxx.com加入request合法域名;如果涉及图片上传或文件下载,还要分别配置uploadFile合法域名downloadFile合法域名。H5页面里通常会加载产品图片、富文本详情里的外链图片,这些域名也可能被小程序拦截,源码里要通过后端代理或者图片转存,让所有图片都走自己的CDN域名。

微信支付商户平台还需要配置API密钥、证书API,尤其是退款接口必须使用商户证书。很多源码只在配置项里写了API密钥,退款证书没配好,结果正常收款没问题,一退款系统就报错。我拿到任何一套旅游系统源码,都会第一时间检查配置中心里有没有apiclient_cert.p12apiclient_key.pem的加载路径,并在联调环境真实退款一笔。

6.2 服务器端部署拓扑参考

旅游系统前中期不需要上太复杂的云原生架构。一台2核4G的云服务器起步,装好JDK、MySQL、Redis、Nginx,跑一个Spring Boot应用,完全够一个小旅行社使用。如果用户量起来了,再按下面路径演进:

  • Nginx与后端分离,静态H5站点的资源直接让Nginx处理;
  • 图片、上行文件对象存储化;
  • MySQL开启慢查询日志,给日期库存和订单状态查询补索引;
  • Redis独立到单独实例,设置合理的过期策略;
  • 同一套后端多实例部署时,用Nginx做负载均衡。

源码里的application.yml通常暴露了配置文件修改入口。上线前至少要把数据库密码、Redis密码、微信AppSecret等信息移到环境变量或配置中心,不能直接明文放在配置文件里。这些私密信息一旦随着前端源码或Git仓库泄露,会给系统带来巨大风险。

6.3 二次开发前必做的事情

最后给拿源码做二次开发的同学几条建议。第一,先通读订单、库存、用户三张核心表和状态流转代码,不要急着加功能。任何新需求最后都会落到这三张表上,看懂了全局再做开发,才不会把原设计毁掉。

第二,把时间字段和金额字段的格式化统一掉。旅游系统里大量涉及“出游日期”和“下单时间”,如果不同接口返回的时间格式不统一,小程序端的解析代码会被逼着写大量补丁。

第三,在一个独立分支上做配置改造,同时保留一份“演示数据”的初始化脚本。很多人二次开发时,因为手欠把后台的测试产品、测试订单全部删光,后面测前端页面时一张图都没有,又得从零录数据,非常耽误时间。

第四,不要小看日志。支付回调、退款回调、定时任务、管理员的订单操作,这些都需要完整的操作日志。线上出了问题,没有日志基本上等于没有线索。很多旅游源码在这块是薄弱项,二次开发时值得优先补强。

我自己在跑这套系统时,习惯给关键的支付、关单、改价操作都加上结构化日志,里面记录userId、订单号、变更前后状态、请求参数和耗时。这么做可能一天会多几百MB日志,但换来的是出问题时十分钟内定位问题,比什么都值。如果你准备正式商用一套多端旅游系统,也建议从接手第一天就建立起同样的习惯,后面会省下无数互相扯皮的时间。

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

.NET源码生成器实战:基于Roslyn与partial范式打造AutoNotify生成器

.NET 源码生成器(Source Generator)这两年已经从“高级黑魔法”变成我日常工作中相当依赖的常规武器了。它能让你在编译期间用 Roslyn 解析代码结构,按规则自动生成新的 C# 源码,并且这些源码会以 partial 类型的形式和手写代码合…

作者头像 李华
网站建设 2026/9/9 9:13:29

HashMap核心机制全解:从哈希冲突到红黑树,进阶必读

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

作者头像 李华
网站建设 2026/9/9 9:12:56

汽车门户网站系统设计:数据建模、筛选链路与SEO优化实战

简介:面向汽车行业门户网站建设需求的ASP源码方案,适用于企业或个人快速搭建集新车、二手车、维修保养、用品商城、租赁培训于一体的垂直信息平台。系统预设新车报价、二手车、维修保养、汽车用品、汽车租赁、汽车培训、汽车资讯、商户名录等频道&#x…

作者头像 李华
网站建设 2026/9/9 9:12:42

光电隔离光耦与光纤耦合器的区别:原理、参数与应用选型对比

去年冬天帮朋友排查一个工业现场的通信故障,PLC柜里有个数字量输入模块一直偶发丢信号,折腾了两三天,最后发现有人把一只标注着"12 光纤耦合器"的盒子接到了24V输入回路上——板子烧了,问题其实不在程序里。这种乌龙在设…

作者头像 李华
网站建设 2026/9/9 9:12:16

论文降重和降AI的正确顺序:先重后AI,避免越改越废

每年这个时间点,后台就会堆满同一类私信:降重降到最后,AI率又红了;改完AI率,查重率又上去了;论文被我改得快不认识原稿了,到底还能不能救?说句不好听的,很多同学的论文不…

作者头像 李华
网站建设 2026/9/9 9:10:57

Zephyr国产化落地:从技术优势到生态适配

1. 这不是Zephyr不行,是它撞上了中国嵌入式开发的“真实墙面”Zephyr、RTOS、本土生态——这三个词凑在一起,不是技术讨论,是一面照见中国嵌入式产业现状的镜子。我从2014年在ST的CubeMX里第一次接触FreeRTOS开始,到2018年带团队用…

作者头像 李华