
最近一直在折腾酒店管理系统的小程序项目技术栈选了PHP加uni-app这套组合。后台用PHP写接口前端统一用uni-app一套代码同时发布了微信小程序和H5管理端。整套东西从数据库设计到接口联调再到真机预览和审核上线踩了不少坑也积累了一些比较实在的经验。这篇就围绕这个项目把从零到一的完整思路捋一遍为什么选PHPuni-app而不是其他方案、系统结构怎么设计、数据库表怎么建不踩坑、前后端关键代码怎么写、上线阶段会遇到哪些高频问题。项目本身的定位是中小型酒店/民宿的预订管理系统核心场景就是客人在小程序上看房型、选日期、下单酒店管理员在后台接单、分配房间、办理入住退房。适合正在准备毕业设计的同学、接到酒店类接单项目的开发者还有想从零接触小程序全栈开发的读者参考。整套系统不追求高并发大流量重点在于业务闭环完整、代码结构清晰、能快速上线可用。1. 项目整体设计与技术选型1.1 为什么选 PHP uni-app 这套组合先说结论这个搭配在小程序管理系统这类项目里是投入产出比非常高的方案。项目本身业务不复杂核心是下单、订单流转、房态管理这一类表单加列表的典型场景PHP处理这种业务很顺手数组操作灵活转JSON返回也就一行json_encode($data)的事。部署成本也低国内云服务器、虚拟主机到处都是PHP环境一个小内存的轻量服务器完全跑得动。uni-app这边最核心的价值是跨端。一次开发可以编译到微信小程序、H5、安卓App、iOS App。酒店这类业务客户的需求往往不只是一个小程序很多老板还想要一个手机浏览器能直接打开的管理后台或者以后要加个安卓端的店员端App。如果用原生微信小程序写后面这些需求全部都要重写成本直接翻倍。uni-app基于标准的Vue语法写页面开发体验和Vue单页应用几乎一样前端开发者基本零门槛上手。放一个对比表格直观看看常用技术栈在这个场景里的取舍技术栈组合开发效率部署成本跨端能力维护难度适用场景PHP uni-app高低强低中小型管理系统、预订类应用Java Spring Boot Vue中高中中大型系统、复杂权限、高并发Node.js uni-app中高中强中前后端语言统一的团队Python Flask/Django uni-app中低强中快速原型、算法类后台结合这次我采用的是原生PHP PDO做接口层而不是直接套一个ThinkPHP或者Laravel这类重型框架。原因很简单项目的主体逻辑在数据库表和接口交互上框架能帮的忙主要是ORM和脚手架但引入框架的同时也引入了学习成本和运行开销。用原生PDO写SQL可控性强、执行逻辑透明排查问题的时候一眼就能看到底对个人开发者维护非常友好。当然如果你准备把这个项目当成长期产品做同事也要参与开发那换成ThinkPHP 6这类轻量框架会更合适。1.2 技术栈与运行环境说明后端这块我用的是PHP 7.4配合PDO扩展连接MySQL 5.7。这两个版本都是非常成熟的稳定版本网上资料多遇到问题很容易搜到解决方案。运行环境就是Nginx PHP-FPMMySQL单独跑一个实例服务器上装个宝塔面板这类管理工具就能快速完成环境搭建不需要自己手动编译安装一堆东西。前端用HBuilderX作为uni-app的开发和打包工具用VSCode或PHPStorm写业务代码。项目管理后台我做的也是一个uni-app的H5端和用户小程序端放在同一个工程里通过不同的tabBar和登录逻辑区分角色。页面样式用了uView组件库表格、表单、弹窗这些组件都是现成的能省去大量写UI的时间。数据库操作方面我没有用ORM框架直接用PDO预处理来封装一个DB工具类。PDO的预处理机制天然能防SQL注入参数绑定的写法也干净。封装好之后业务代码里调用就比较简洁?php class DB { private static $pdo null; public static function conn() { if (self::$pdo null) { $dsn mysql:host127.0.0.1;dbnamehotel;charsetutf8mb4; self::$pdo new PDO($dsn, root, 123456, [ PDO::ATTR_DEFAULT_FETCH_MODE PDO::FETCH_ASSOC, PDO::ATTR_ERRMODE PDO::ERRMODE_EXCEPTION ]); } return self::$pdo; } }注意PDO的两个属性设置一定要加上FETCH_ASSOC保证查出来是关联数组前端拿到JSON后操作字段自然ERRMODE_EXCEPTION让数据库报错直接抛异常方便接口层统一捕获并返回错误信息而不是让SQL错误赤裸裸地打到响应里。1.3 系统整体架构与目录规划整个系统采用前后端分离的思路这是做小程序项目很自然的演进结果。服务端只提供JSON接口不关心页面内容前端负责渲染和交互。这样做的好处不只是职责清晰更重要的是后续无论加安卓端、iOS端还是桌面端后端接口都可以原样复用顶多加几个新接口补业务缺口不用动原有的接口逻辑。接口统一使用/api前缀按业务模块划分目录。后端目录结构大概这样hotel-server ├── api │ ├── room.php # 房型相关接口 │ ├── order.php # 订单相关接口 │ ├── user.php # 用户登录、个人信息 │ └── admin │ ├── login.php # 管理员登录 │ ├── rooms.php # 后台房型管理 │ └── orders.php # 后台订单处理 ├── lib │ ├── db.php # PDO封装 │ ├── auth.php # token鉴权 │ └── response.php # 统一JSON返回 └── upload └── images # 上传的图片目录前端uni-app的页面目录hotel-uniapp ├── pages │ ├── index/index.vue # 首页轮播图精选房型 │ ├── room/list.vue # 房型列表 │ ├── room/detail.vue # 房型详情 │ ├── order/create.vue # 提交订单 │ ├── order/list.vue # 我的订单 │ ├── order/detail.vue # 订单详情 │ └── user/index.vue # 个人中心 ├── admin │ ├── dashboard.vue # 后台仪表盘 │ ├── room-type.vue # 后台房型管理 │ ├── order-list.vue # 后台订单管理 │ └── login.vue # 后台登录 ├── utils │ └── request.js # 请求封装 └── static └── logo.png接口层的设计所有接口约定统一的返回格式{ code: 0, msg: success, data: {} }约定好code为0表示成功非0表示失败失败信息放在msg里。这个约定要在开发一开始就定下来前后端都按照这个规范来联调的时候能省掉一大半扯皮的时间。2. 核心功能模块拆解与数据库设计2.1 小程序端功能规划小程序端面向C端用户功能围绕找房-看房-下单-管订单这条主线来设计全部功能可以拆成五个核心模块首页顶部轮播图展示酒店环境和促销活动下面放几个快捷入口再推荐几条精选房型。设计时注意别堆太多信息小程序首页的第一目标是让用户快速看到这家酒店有什么房型、什么价位。房型列表与详情支持按价格、人数筛选列表卡片展示封面图、房型名称、价格和可住人数。详情页重点展示房间图片、床型、面积、设施清单以及入住/离店日期选择器。日期选择这里有个细节用户选完日期后如果该房型在对应日期已经满房要直接给出已满房提示不要等提交订单时才报错。订单提交与支付用户确认日期、填写联系人姓名和手机号后提交订单。支付环节考虑到个人开发者和中小企业接入微信支付的审核门槛我在第一版做的是在线提交订单、线下前台付款后台可以改成到店付这样能避开支付资质问题先把业务跑通。要接在线支付的话后面单独接入微信支付JSAPI就好了订单号、金额这些字段在设计上已经预留好。订单列表与详情按状态区分待确认、已确认、已入住、已退房、已取消用户能查看订单状态变化也能在入住前申请取消。个人中心微信登录后展示头像昵称、手机号绑定、我的订单入口。第一版不搞复杂的会员体系够用就好。2.2 管理后台功能规划管理后台我直接复用了前端工程编译成H5端发布到服务器上管理员用手机浏览器或电脑浏览器访问域名就能登录操作等于送了一个跨端后台。后台的功能拆成四块仪表盘展示今日订单数、今日入住间夜数、本月营业额这几个核心指标下面再放最近7天的订单趋势和待办事项比如待确认的订单数量。这个页面是老板最常看的数据统计SQL要提前写好、做好日期范围索引。房型管理房型的增删改查、上下架操作上传房型封面图和多张房间实拍图。房型下架的时候要注意有未完成订单的房型不能直接物理删除否则订单表外键会出问题正确做法是逻辑下架。房间管理管理物理房间比如3楼301是标准大床房房号301。每个房型下挂多个房间房间状态有空闲、占用、清洁中、维修中四种。每天凌晨可以跑一个定时任务把已退房的房间状态重置为空闲。订单管理这是后台最核心的模块。操作链路是确认订单→分配房间→办理入住→办理退房→完成。分配房间时系统自动从该房型下挑一个状态为空闲的房间管理员也能手动指定。每步操作都改变订单状态同时更新房间状态这个状态联动是后台最容易出bug的地方。2.3 数据库表设计要点与SQL示例数据库是酒店管理系统的核心表结构直接决定业务复杂度。我设计时遵循一条原则能用数字状态表示的绝不用字符串能用整型时间戳的绝不用datetime。这样查询效率高前端做状态展示也方便。核心表有6张用户表、管理员表、房型表、房间表、订单表、轮播图表。看两个比较关键的建表语句CREATE TABLE room_type ( id int(11) unsigned NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 房型名称, cover varchar(255) NOT NULL COMMENT 封面图URL, price decimal(10,2) NOT NULL COMMENT 每晚价格, area varchar(20) DEFAULT COMMENT 房间面积, bed_type varchar(20) DEFAULT COMMENT 床型如大床1.8m, max_people tinyint(4) NOT NULL DEFAULT 2 COMMENT 可住人数, description text COMMENT 图文详情, status tinyint(4) NOT NULL DEFAULT 1 COMMENT 1上架 0下架, sort int(11) NOT NULL DEFAULT 0 COMMENT 排序权重, create_time int(11) NOT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房型表;CREATE TABLE order ( id int(11) unsigned NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id int(11) NOT NULL COMMENT 用户ID, room_type_id int(11) NOT NULL COMMENT 房型ID, room_id int(11) NOT NULL COMMENT 分配的房间ID, check_in date NOT NULL COMMENT 入住日期, check_out date NOT NULL COMMENT 离店日期, nights tinyint(4) NOT NULL COMMENT 入住晚数, amount decimal(10,2) NOT NULL COMMENT 订单总金额, contact varchar(20) NOT NULL COMMENT 联系人, phone varchar(20) NOT NULL COMMENT 联系电话, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 订单状态, create_time int(11) NOT NULL, PRIMARY KEY (id), KEY idx_user (user_id), KEY idx_status (status), KEY idx_date (check_in, check_out) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;订单金额一定不能用前端传过来的值。后端要根据check_in和check_out算出晚数再乘以房型单价得到金额防止用户改请求参数。订单号生成规则我用的是date(YmdHis)加4位随机数简单可读也不会和并发下的订单撞号。订单状态是整个系统的灵魂我用一个数字字段表示状态值含义后台可操作0待确认确认订单、取消订单1已确认/待入住分配房间、办理入住2已入住办理退房3已退房查看/归档4已取消查看状态流转只能按顺序走不能跳状态。比如待确认的订单不能直接变成已入住这在后台操作时要加校验防止并发操作把状态搞乱。3. 实操过程与关键代码实现3.1 后端接口开发从公共封装到下单事务后端开发的第一步是把公共返回函数写好。所有接口都用这个函数输出JSON保证前端解析格式一致?php function jsonResponse($code, $msg, $data null) { header(Content-Type: application/json); exit(json_encode([ code $code, msg $msg, data $data ])); } // 成功 function success($data null) { jsonResponse(0, success, $data); } // 失败 function fail($msg) { jsonResponse(1, $msg); }房型列表接口前端需要分页加载、按条件筛选后端处理分页参数时要做防御防止恶意传负数或者超大页码把数据库拖垮public function roomList() { $page isset($_GET[page]) ? max(1, intval($_GET[page])) : 1; $limit 10; $offset ($page - 1) * $limit; $where status 1; if (!empty($_GET[max_people])) { $where . AND max_people . intval($_GET[max_people]); } $total DB::conn()-query(SELECT COUNT(*) FROM room_type WHERE $where)-fetchColumn(); $sql SELECT id, name, cover, price, max_people, bed_type FROM room_type WHERE $where ORDER BY sort DESC LIMIT $offset, $limit; $list DB::conn()-query($sql)-fetchAll(); success([ list $list ?: [], page $page, total intval($total) ]); }下单接口是整个后端最复杂的环节它有几步操作必须保证原子性检查房型是否存在、检查日期有效性、检查空闲房间、插入订单、锁定房间。这几步要么全部完成要么全部回滚必须开启数据库事务public function createOrder() { // 假设user_id已经通过token鉴权获取 $userId getCurrentUserId(); $roomTypeId intval($_POST[room_type_id]); $checkIn $_POST[check_in]; $checkOut $_POST[check_out]; $contact trim($_POST[contact]); $phone trim($_POST[phone]); if (!$roomTypeId || !$checkIn || !$checkOut || !$contact || !$phone) { fail(参数不完整); } $nights (strtotime($checkOut) - strtotime($checkIn)) / 86400; if ($nights 0) { fail(离店日期必须晚于入住日期); } $type DB::conn()-query( SELECT * FROM room_type WHERE id $roomTypeId AND status 1 )-fetch(); if (!$type) { fail(房型不存在或已下架); } $amount $nights * $type[price]; $conn DB::conn(); $conn-beginTransaction(); try { // 查询空闲房间并加锁防止并发重复分配 $stmt $conn-query( SELECT id FROM room WHERE type_id $roomTypeId AND status 0 LIMIT 1 FOR UPDATE ); $room $stmt-fetch(); if (!$room) { $conn-rollBack(); fail(该时段暂无空闲房间); } $orderNo date(YmdHis) . mt_rand(1000, 9999); $insertStmt $conn-prepare( INSERT INTO order (order_no, user_id, room_type_id, room_id, check_in, check_out, nights, amount, contact, phone, status, create_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?) ); $createTime time(); $insertStmt-execute([ $orderNo, $userId, $roomTypeId, $room[id], $checkIn, $checkOut, $nights, $amount, $contact, $phone, $createTime ]); // 锁定房间状态改为占用 $conn-exec(UPDATE room SET status 1 WHERE id . $room[id]); $conn-commit(); success([order_no $orderNo]); } catch (Exception $e) { $conn-rollBack(); fail(下单失败请稍后重试); } }订单状态从待确认到确认再到分配房间、办理入住每一步的后台操作接口原则都是一样的校验当前状态是否符合流转条件然后更新订单状态和房间状态两步操作放进一个事务里不能只改订单不改房间否则就会出现满房但订单已确认的脏数据。3.2 uniapp 前端页面实现请求封装与列表渲染前端第一步也是最关键的一步是封装请求工具。uni-app有自带的uni.request但直接用的话每个页面都要写一遍成功失败回调非常痛苦。我封装成一个Promise风格的request方法自动带上token、统一处理错误弹窗// utils/request.js const BASE_URL https://api.yourdomain.com; export function request(path, method GET, data {}) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json, token: uni.getStorageSync(token) || }, success: res { if (res.data.code 0) { resolve(res.data.data); } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res.data); } }, fail: err { uni.showToast({ title: 网络异常请检查网络连接, icon: none }); reject(err); } }); }); }封装好之后页面里调用接口就很清爽了。以房型列表页为例template view classroom-list view classroom-card v-foritem in roomList :keyitem.id clickgoDetail(item.id) image classcover :srcitem.cover modeaspectFill/image view classinfo text classname{{ item.name }}/text text classprice¥{{ item.price }}/晚/text text classpeople可住{{ item.max_people }}人 · {{ item.bed_type }}/text /view /view view classloading-tip v-ifloading加载中.../view view classno-more v-else-if!hasMore已经到底了/view /view /template script import { request } from /utils/request.js; export default { data() { return { roomList: [], page: 1, loading: false, hasMore: true }; }, onLoad() { this.loadList(); }, onReachBottom() { if (this.hasMore !this.loading) { this.page; this.loadList(); } }, methods: { async loadList() { this.loading true; try { const data await request(/api/room/list?page${this.page}); this.roomList this.page 1 ? data.list : this.roomList.concat(data.list); this.hasMore this.roomList.length data.total; } finally { this.loading false; } }, goDetail(id) { uni.navigateTo({ url: /pages/room/detail?id${id} }); } } }; /script这里有个容易踩的坑下拉加载更多时一定要判断hasMore和loading状态避免用户快速上滑触发多次请求导致列表数据重复。另外concat拼接之前先判断data.list是否存在因为PHP端空数据默认返回空数组但某些接口失误时会变成其他结构前端统一加一层保护更稳妥。首页轮播图用uni-app自带的swiper组件没什么技术难度。真正值得注意的是图片地址的处理后端返回的图片路径必须是完整的https://开头URL不能是相对路径否则小程序端会直接白屏不显示。3.3 管理后台的搭建思路管理后台既然是一个uni-app的H5端代码上和小程序端共用一套组件、一套请求封装只是页面路由和角色权限不同。我在manifest.json里配置了H5端的运行域名和路由模式首页在管理员登录之后跳转到后台仪表盘页面。后台页面里大量使用了uView的表格和表单组件比如订单列表页就是一个u-table组件加操作按钮。订单操作接口通过角色权限控制普通用户token不能调用后台接口后台接口在鉴权函数里额外校验管理员身份function checkAdmin() { $token $_SERVER[HTTP_TOKEN] ?? ; $admin DB::conn()-query( SELECT id FROM admin WHERE token $token AND status 1 )-fetch(); if (!$admin) { fail(请先登录); } return $admin[id]; }后台发布上线时uni-app编译成H5之后是一堆静态文件放到Nginx的web目录下就行。注意如果用了history路由模式Nginx要配置重新rewrite到index.html不然刷新页面会404。如果使用默认的hash模式就不会有这个问题小项目直接用hash模式更省事。3.4 小程序发布与版本迭代完整流程小程序的发布流程第一次弄的时候每一步都可能卡住。简要梳理一遍跑通的路径先在HBuilderX里配置manifest.json把微信小程序端的appid填进去。没有的话到微信公众平台注册一个个人或企业小程序账号拿到AppID。然后HBuilderX菜单栏点击发行-小程序-微信会自动编译生成一个dist/build/mp-weixin目录。接下来打开微信开发者工具导入这个目录就能在模拟器里看到完整项目了。真机预览前需要把开发工具详情里的不校验合法域名、TLS版本以及HTTPS证书勾上否则请求会被拦截。上线必须要做的事情有两件一是在微信公众平台配置服务器域名request合法域名必须是HTTPS且已备案的域名二是把编译好的小程序代码通过开发者工具上传提交审核。审核这块有几个注意事项如果小程序里涉及用户提交内容比如评价、留言要有内容安全机制最简单的做法是收集到关键词过滤服务或人工审核后展示涉及酒店预订类目个人主体小程序可能没法直接通过类目审核这种情况一般建议注册企业主体。审核周期一般1到3个工作日驳回的原因大多是类目不符或功能不完整按平台提示修改就好。4. 常见问题与上线排查实录4.1 接口返回空数据处理成大坑PHP的数组转JSON有个经典问题空数组合成[]而前端模板里经常期望一个对象或直接遍历。比如房型列表为空时后端返回了list:[]前端v-for遍历没问题但如果你返回data:[]而前端代码写的是data.list直接报错Cannot read property list of undefined。解决办法是后端统一做一层包装我习惯把接口返回的字段定义死在文档里空值按照字段的语义处理。列表字段空就给[]对象字段空就显式转成new stdClass()。前端也要习惯用可选链或者判断一层双保险比自己猜接口结构可靠得多。4.2 跨域、域名配置与请求失败排查开发模式下小程序请求本地或IP地址接口没问题但真机和发布之后微信要求所有请求都走HTTPS的合法域名。我在第一次真机调试时就因为没有在公众平台配置request合法域名一直报url not in domain list。这个配置路径是微信公众平台-开发管理-开发设置-服务器域名把https://api.yourdomain.com加进request合法域名列表配置完等几分钟生效。H5端同样会遇到跨域问题。如果是Nginx部署接口跨域配置长这样location /api/ { add_header Access-Control-Allow-Origin *; add_header Access-Control-Allow-Methods GET, POST, PUT, DELETE, OPTIONS; add_header Access-Control-Allow-Headers Content-Type, token, Authorization; if ($request_method OPTIONS) { return 204; } }排查请求失败的顺序我总结出一个固定套路先看后端日志有没有收到请求再看PHP错误日志有没有SQL报错最后看响应内容格式是否符合封装。按照这个顺序90%的问题都能定位。千万别在小程序端盲目改代码很多时候问题在后端环境配置上。4.3 图片上传遇到的几个典型坑小程序端上传图片用的uni.uploadFile后端接收的方式和三年前的上传逻辑没什么大变化public function uploadImage() { if (empty($_FILES[file])) { fail(没有接收到上传文件); } $file $_FILES[file]; $ext pathinfo($file[name], PATHINFO_EXTENSION); $allowed [jpg, jpeg, png, gif, webp]; if (!in_array(strtolower($ext), $allowed)) { fail(不支持的图片格式); } $filename date(Ymd) . / . uniqid() . . . $ext; $savePath UPLOAD_PATH . / . $filename; if (!is_dir(dirname($savePath))) { mkdir(dirname($savePath), 0755, true); } if (move_uploaded_file($file[tmp_name], $savePath)) { success([url https://api.yourdomain.com/upload/ . $filename]); } else { fail(图片保存失败); } }这个环节至少有三个坑在前面等着PHP上传大小限制。默认upload_max_filesize只有2Mpost_max_size也才8M。酒店房间实拍图随便一张就3到5M不调大必炸。建议upload_max_filesize改成20Mpost_max_size改成30M。图片类型校验不要只靠后缀。有些图片后缀是jpg内容其实是脚本后端最好用getimagesize函数校验真实图片类型或者用第三方图片处理库做压缩和格式转换安全性和性能都能兼顾。返回给前端的URL必须是完整的https://路径。很多人会在这一步拼接相对地址导致小程序端图片无法加载。房型管理后台在编辑房型时就能发现这个问题因为后台H5端预览的图片如果用的是相对路径在子路由下也会失效。4.4 订单并发与状态流转变动问题最开始我做的是简单版下单逻辑先查一下有没有空闲房间有就插入订单。但后来做了个并发测试两个用户同时抢最后一间房结果都查询到了空闲房间两条订单都插入成功房间却被分配给了两个人。这就是经典的先查后写并发问题。解决思路是把查询和更新放进同一个事务并且对查询行加锁。MySQL的SELECT ... FOR UPDATE就是干这个事的在事务内锁定房间记录另一个事务必须等锁释放才能继续。这个锁的粒度很小对并发量不大的酒店业务完全够用不会带来明显性能瓶颈。我前面的下单接口代码里已经用了这个写法。订单状态这块还有一个常见问题就是后台管理员重复点击确认订单按钮导致订单状态被覆盖。处理办法很简单更新SQL里加一个状态条件比如UPDATE order SET status 1 WHERE id 10 AND status 0受影响行数为0就说明状态已经被改过了直接提示该订单已被处理。这种乐观锁的思路在小项目里比引入Redis分布式锁简单得多推荐直接用。整套系统做下来我自己最大的体会是技术选型真的不用追求新潮PHP加uni-app这套组合在酒店管理这种业务场景里就是好用、够用、省时间。真正花时间的不是写代码而是想清楚订单状态怎么流转、房间和订单怎么联动、并发情况下怎么保证数据不错乱。数据库设计这一关值得多花一倍时间去打磨因为后期改表结构的成本远远高于前期多设计几个字段的成本。还有一个建议给第一次做类似项目的朋友不要想着一次性把所有功能做完。先跑通一个最小闭环比如小程序下单到后台接单再到确认入住然后在这个基础上加图片上传、加数据统计、加上架下架。闭环跑通之后你会对整个系统的数据结构有更清晰的认识后面加功能会顺利很多。至于54ybz这个源码包编号如果你拿到的项目是类似版本建议导入之后先把示例数据清干净、重新初始化一遍管理员账号再开始改业务避免旧数据干扰新逻辑。