FEATURED · 精选文章

PHP+uniapp+微信小程序:戏曲推广小程序开发实战全记录

发布时间 / 2026/9/7 17:38:45
来源 / 创域科博编辑部
栏目 / 资讯中心
PHP+uniapp+微信小程序:戏曲推广小程序开发实战全记录 做戏曲推广类的小程序很多人第一反应是找现成的CMS套模板或者直接买一套Java后端配原生小程序代码。但真落到“phpuniapp微信小程序”这个组合上的时候我反而觉得这才是最贴合实际需求的选择。这个项目我从后端接口设计到前端页面渲染全流程都走了一遍中间踩了不少坑也沉淀出一些非常有价值的实践方法。如果你正准备做传统文化相关的微信小程序或者正纠结于PHP后端怎么和uniapp优雅配合这篇文章应该能帮你少走很多弯路。先交代一下这个项目到底在做什么一个传统戏曲文化推广微信小程序目标用户是戏迷和泛文化爱好者核心功能包括戏曲剧目展示、经典唱段点播、演出资讯发布、剧种分类检索以及用户收藏、分享、留言互动等。技术栈是PHPThinkPHP 3.2.3提供后端APIuniapp构建微信小程序前端。整个过程中我重点解决了三件事一是接口数据结构的统一设计二是uniapp在微信小程序端的兼容处理三是围绕戏曲内容本身的功能闭环。下面就把这套方案的细节和实操过程完整拆给你看。1. 项目整体设计与技术选型思路1.1 为什么选了「PHP uniapp 微信小程序」这套组合先聊选型。当时摆在我面前的有几个方向Java Spring Boot 原生小程序、Node.js Taro、PHP uniapp。最后选了PHP uniapp不是因为PHP比Java更先进而是从项目实际需求倒推出来的结论。这个项目的核心痛点有三个开发周期紧、业务逻辑不复杂但展示形式多样、后期需要同时覆盖微信小程序和可能的App端。PHP在这类场景下优势非常明显ThinkPHP 3.2.3虽然老但胜在稳定、资料多、部署简单虚拟主机都能跑对中小型文化类项目来说维护成本极低。而uniapp的价值在于“一套代码多端复用”我写好的戏曲列表页、播放页后续如果要出App版除了登录和支付要调原生SDK其余UI和交互逻辑基本可以直接搬。还有一个现实因素这类推广型小程序运营方往往是文化单位或剧团他们后台上传戏曲视频、图文资讯用的是网页管理端PHP做后台管理系统的生态相当成熟。直接用PHP把“管理后台 API接口”统一起来比引入两套技术栈更省事。1.2 面向戏曲内容场景的功能模块拆解传统戏曲文化推广不是简单的“视频列表播放器”它天然带有强烈的分类属性和文化语境。我拿到需求的第一个动作是从业务角度把功能拆成四个维度第一是内容资源维度包括剧种管理京剧、昆曲、越剧、豫剧等、剧目管理经典剧目、新编剧目、唱段管理音频、视频、唱词文本。第二是资讯维度包括演出公告、剧团动态、戏曲知识科普。第三是互动维度包括用户收藏、点赞、评论留言、微信分享。第四是用户维度包括微信授权登录、浏览记录、个人偏好设置。这个拆解决定了数据库表结构的设计方向。戏曲内容不同于普通短视频它牵扯到“剧种—剧目—唱段”三层隶属关系一张简单的分类表根本撑不起来。所以在设计数据库时我特意把剧种、剧目、唱段拆成了三张独立表用外键关联这样前端做分类钻取的时候接口写起来最顺手。1.3 数据库设计与API规划数据库我选MySQL一共规划了九张核心表。这里重点说几张容易踩坑的表剧种表opera_type字段包括type_id、type_name、description、sort_order、create_time。剧目表drama字段包括drama_id、type_id、title、intro、cover_image、director、performer、publish_time。唱段表segment字段包括segment_id、drama_id、title、audio_url、video_url、lyrics_text、duration、play_count。这里最关键的细节是cover_image和audio_url这两个字段。小程序端展示需要的是能直接访问的完整URL但后台管理员上传时往往只给了相对路径。我的做法是在PHP接口层统一做一次拼接判断URL是否以http开头不是就自动补上CDN域名前缀。这样前端拿到的一定是可直接加载的完整地址避免了在小程序端反复拼接的麻烦。API设计上我遵循RESTful风格但不刻意套用实际就两类接口内容查询接口GET和用户操作接口POST。返回格式统一为JSON{ code: 200, msg: success, data: {} }其中data字段不管有没有数据都必须是一个对象或数组不能为null。这一点是前端统一处理异常的前提后面对接uniapp时省了很多if判断。2. 后端核心接口实现PHP ThinkPHP 3.2.3 实战详解2.1 ThinkPHP 3.2.3 的项目结构搭建ThinkPHP 3.2.3虽然老但目录结构清晰上手极快。我按官方规范建了Application目录里面分了Admin模块和Api模块。Admin模块跑后台管理页面Api模块专门输出JSON接口。这里有个比较重要的经验Api模块下的Controller我建议继承同一个BaseController在这个基类里统一做跨域处理、请求参数过滤、登录态校验和返回格式封装。跨域问题在小程序里通常不突出因为微信小程序请求不强制CORS但如果你用同样的API调试H5端跨域就会暴露出来。我在BaseController的_initialize方法里直接加了响应头header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Headers: token,Content-Type); header(Access-Control-Allow-Methods: GET,POST,OPTIONS);2.2 获取剧种列表与剧目详情的接口实现先说剧种列表接口。这个接口本身不复杂就是查表、排序、返回。关键在于我做了缓存处理。戏曲小程序首页会一次性展示所有剧种及其代表剧目如果每次请求都查数据库高峰期压力很大。我的方案是使用ThinkPHP自带的S缓存缓存键设置为opera_type_list缓存时间600秒public function getTypeList() { $list S(opera_type_list); if (empty($list)) { $model M(opera_type); $list $model-order(sort_order asc)-select(); S(opera_type_list, $list, 600); } $this-ajaxReturn([code 200, data $list, msg success]); }再说剧目详情接口。前端需要传drama_id后端拿到后先查剧目基本信息再关联查询该剧目下所有唱段列表。这里如果分别查两次数据库会出现N1查询问题所以我直接用了关联查询public function dramaDetail() { $dramaId I(get.drama_id, 0, intval); if ($dramaId 0) { $this-ajaxReturn([code 400, data [], msg 参数错误]); } $drama M(drama)-where([drama_id $dramaId])-find(); $segments M(segment)-where([drama_id $dramaId])-order(segment_id asc)-select(); $drama[segment_list] $segments; $this-ajaxReturn([code 200, data $drama, msg success]); }2.3 搜索接口与模糊匹配的坑戏曲搜索是个隐藏难点。用户输入的可能是“贵妃醉酒”、“霸王别姬”也可能是“梅派”、“程派”甚至可能是剧种名“昆曲”。所以搜索接口不能只匹配剧目名称还得同时匹配剧种名、唱段名、表演者。实现方式是在MySQL里用LIKE多字段匹配public function search() { $keyword trim(I(get.keyword, , htmlspecialchars)); if (empty($keyword)) { $this-ajaxReturn([code 400, data [], msg 请输入关键词]); } $map[title] [like, % . $keyword . %]; $map[_logic] or; $dramaList M(drama)-where($map)-select(); $this-ajaxReturn([code 200, data $dramaList, msg success]); }这里要说明LIKE模糊查询在数据量小的时候完全够用不必一上来就上Elasticsearch或者MySQL全文索引。但有一个坑必须提ThinkPHP的where方法如果多个字段要OR匹配必须用_map或直接构造数组否则查询条件会互相覆盖。我在调试时遇到过一次搜索“贵妃”只能搜到剧目但搜不到唱段的情况就是因为忽略了唱段表的LIKE匹配。实际上后来我把搜索接口做成了联合查询同时查剧目表、唱段表、剧种表再把结果按照类型字段合并返回。前端根据type字段区分展示样式比如剧目显示封面卡片唱段显示播放列表剧种显示横向标签。这样搜索体验会友好很多。2.4 用户收藏与点赞的后端逻辑用户收藏接口前端传user_id和drama_id或segment_id后端先判断是否已收藏已收藏就执行取消收藏未收藏就插入记录。这个“切换式”接口设计比分开写两个接口更高效public function toggleFavorite() { $userId I(post.user_id, 0, intval); $dramaId I(post.drama_id, 0, intval); if ($userId 0 || $dramaId 0) { $this-ajaxReturn([code 400, data [], msg 参数错误]); } $where [user_id $userId, drama_id $dramaId]; $exist M(favorite)-where($where)-find(); if ($exist) { M(favorite)-where($where)-delete(); M(drama)-where([drama_id $dramaId])-setDec(favorite_count); $this-ajaxReturn([code 200, data [status 0], msg 已取消收藏]); } else { M(favorite)-add($where [create_time time()]); M(drama)-where([drama_id $dramaId])-setInc(favorite_count); $this-ajaxReturn([code 200, data [status 1], msg 收藏成功]); } }点赞逻辑同理唯一要注意的是重复点赞的幂等控制。数据库里要给user_id和drama_id加联合唯一索引否则并发请求时会出现重复记录。2.5 PHP接口安全防护与性能优化接口安全这块我做了三层防护。第一层是参数过滤所有传入参数都经过I()方法过滤数值类型强制intval字符串类型做htmlspecialchars处理。第二层是简单的请求签名验证小程序端每次请求在header里带上timestamp和signsign md5(参数拼接 密钥)后端验证timestamp在5分钟内有效sign一致才算合法请求。这个方案能拦截掉大部分恶意刷接口的行为。第三层是慢查询日志开启MySQL的slow_query_log把超过1秒的查询记录下来定期优化索引。性能方面除了前面说的缓存还有两个细节值得注意。一是图片资源统一走阿里云OSS或腾讯云COSPHP服务器只存路径不存二进制文件上传接口直接生成OSS签名的直传URL小程序端调用uni.uploadFile上传到OSS后再把路径回传给后端保存。这样PHP服务器的带宽和存储压力会小很多。二是列表接口默认只返回第一页20条数据前端通过下拉加载翻页避免一次查全表。3. uniapp前端架构页面结构、组件生命周期与数据交互3.1 项目初始化和目录结构规划用HBuilderX创建uniapp项目时选择“默认模板”即可但建议在创建后第一时间调整目录结构。我的习惯是pages目录下按业务模块分子目录components目录放自定义组件common目录放公共JS工具static目录放静态图片utils目录放request封装和工具函数。这个项目我划分了六个页面模块首页推荐剧目轮播和分类入口、剧目列表页、剧目详情页含唱段列表、搜索页、个人中心页收藏和浏览记录、资讯页演出公告和戏曲知识。一个实践教训页面文件命名千万别用中文也别用数字开头。微信小程序对页面路径有规范要求uniapp虽然能编译但有些Android低版本机型的缓存机制会对特殊路径报错。统一用小写字母加下划线最稳妥。3.2 小程序端请求封装与拦截器设计uniapp环境下的网络请求不能直接拿原生小程序里的wx.request就会到处复制粘贴。封装一个统一的request工具类非常必要。我在utils/request.js里做了如下封装const BASE_URL https://api.example.com/index.php; const TOKEN_KEY user_token; function request(url, method GET, data {}) { return new Promise((resolve, reject) { const token uni.getStorageSync(TOKEN_KEY); uni.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, token: token || }, success: (res) { if (res.statusCode 200) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { uni.navigateTo({ url: /pages/login/login }); reject(res.data); } else { uni.showToast({ title: res.data.msg, icon: none }); reject(res.data); } } else { uni.showToast({ title: 网络异常, icon: none }); reject(res); } }, fail: (err) { uni.showToast({ title: 请求失败, icon: none }); reject(err); } }); }); } export default { get: (url, data) request(url, GET, data), post: (url, data) request(url, POST, data) };这个封装的妙处在于统一处理了业务状态码和HTTP状态码页面里只需要这样调用getDramaList() { api.get(/Api/Drama/getList, { type_id: this.typeId, page: this.page }) .then(res { this.dramaList res; }); }页面代码干净很多不需要在每个页面重复写错误提示和401跳转逻辑。3.3 首页轮播图与分类导航的computed缓存首页是最容易出问题的地方因为包含轮播图、分类导航、推荐列表多个数据源。轮播图用uniapp的swiper组件分类导航用scroll-view横向滚动推荐列表用纵向列表。这三个模块的数据是独立接口如果全部在onLoad里同时发起请求会出现短暂的白屏抖动。我采用的方案是把推荐剧目数据缓存到本地每次进入首页先读取本地缓存展示再从接口拉取新数据覆盖。这样用户每次打开小程序都能秒开新数据到了以后自动更新。onLoad() { this.loadBanner(); this.loadTypeList(); this.loadRecommend(); }, onShow() { const cached uni.getStorageSync(home_recommend); if (cached) { this.recommendList cached; } this.loadRecommend(); }, methods: { loadRecommend() { api.get(/Api/Drama/recommendList, {}).then(res { this.recommendList res; uni.setStorageSync(home_recommend, res); }); } }这里有个细节onLoad只在页面首次加载时触发一次onShow每次页面显示都会触发。所以“先读缓存再拉新数据”的逻辑放在onShow里体验最平滑。3.4 组件生命周期与onLoad、onShow的执行时机uniapp的页面生命周期和组件生命周期是两个容易混淆的概念。很多新手在自定义组件里写onLoad发现根本不执行就是因为把页面生命周期和组件生命周期搞混了。我在这里说一下实际经验页面生命周期onLoad、onShow、onReady只写在pages目录下的页面文件里。自定义组件里用的是Vue的标准生命周期created、mounted、updated、destroyed。两者互相独立。我在项目里做了一个“戏曲卡片”自定义组件props传入剧目名称、封面、表演者template里渲染卡片样式。这个组件的created里会根据传入的数据做默认图处理 mounted里监听图片加载完成事件。页面里写onLoad拉数据数据到位后通过:listlist传给组件组件用watch监听list变化重新渲染。这里有一个容易踩的坑页面onLoad里请求接口拿数据然后传给组件组件的created已经执行完了但如果组件用props直接渲染数据是没问题的因为模板会自动响应数据变化。真正的问题出在如果组件在created或mounted里对props做了初始化赋值赋值时机与数据传入时机不一致就会出现数据覆盖。我的做法是组件内部不要对props做深拷贝直接使用props渲染需要二次处理的数据用computed计算。3.5 播放器页面实现与音频播放细节戏曲推广小程序的播放器页面既要支持音频唱段也要支持视频演出录像。音频我用的HTML5 audio标签在H5端兼容小程序端则用uni.createInnerAudioContext这是uniapp推荐的音频播放方案。playAudio(url) { if (this.audioCtx) { this.audioCtx.destroy(); } this.audioCtx uni.createInnerAudioContext(); this.audioCtx.src url; this.audioCtx.autoplay true; this.audioCtx.onPlay(() { this.isPlaying true; }); this.audioCtx.onEnded(() { this.isPlaying false; this.audioCtx.destroy(); }); }注意销毁播放实例的时机。小程序内的音频实例数量是有限制的如果不主动调用destroy()页面跳转后旧的实例还会驻留在内存里多次切换唱段会出现音频重叠播放的问题。所以在每次播放新音频前先销毁旧实例是必须的操作。视频播放使用video组件这里需要设置enable-progress-gesture属性为true让用户可以通过手势控制进度同时设置show-center-play-btn和show-fullscreen-btn提升播放体验。视频封面的加载我习惯用v-if配合poster属性控制避免封面图闪烁。3.6 自定义分享好友与onShareAppMessage戏曲内容天然适合分享给微信群和好友比如“这出《牡丹亭》唱段不错”这种场景。小程序有两种分享入口右上角菜单的转发按钮和页面内的自定义分享按钮。两种都依赖onShareAppMessage生命周期函数。onShareAppMessage() { return { title: this.drama.title - 经典戏曲名段, path: /pages/drama/detail?id this.drama.id, imageUrl: this.drama.coverImage }; }自定义按钮的话在页面里加一个 button 标签设置 open-typeshare 属性点击就会自动触发onShareAppMessage无需手动绑定点击事件。这里有一个细节imageUrl必须是小程序白名单内的图片域名或者用云存储的临时链接否则分享卡片会不显示图片。另外如果希望分享路径带参数path里可以携带drama_id好友点进分享卡片后目标页面的onLoad(options)里能拿到id参数直接跳转到对应的剧目详情页。4. 项目联调、发布流程与常见问题排查4.1 本地联调环境搭建与代理配置开发阶段最烦的是“真机调试时接口不通”。因为微信开发者工具里默认开启了“不校验合法域名”所以http://localhost或局域网IP接口可以直接访问。但真机预览时如果后端跑在本机手机访问到的localhost其实是手机自己必须改成电脑的局域网IP。更稳妥的方案是在HBuilderX里运行到微信开发者工具时单独配置一个开发环境域名。我在utils/config.js里区分了三种环境const ENV development; const config { development: { BASE_URL: http://192.168.1.100/index.php }, production: { BASE_URL: https://api.example.com/index.php } }; export default config[ENV];提交代码前手动把ENV切到production这样就不会出现上线后还请求开发环境接口的问题。4.2 微信小程序登录与用户信息获取的坑热词里有“小程序获取登录后的微信用户失败”这个坑我确实踩过。微信官方调整了用户信息获取策略现在不能直接通过uni.getUserInfo拿到用户的昵称和头像了必须通过button的open-typegetUserInfo让用户主动点击授权或者在业务场景中引导用户使用头像昵称填写能力。我的做法是在个人中心页放一个“微信一键登录”按钮点击后先调用uni.login获取code传给后端换取openid再引导用户完善头像昵称。核心代码login() { uni.login({ provider: weixin, success: (loginRes) { api.post(/Api/User/login, { code: loginRes.code }).then(res { uni.setStorageSync(user_token, res.token); uni.setStorageSync(user_info, res.user_info); this.userInfo res.user_info; }); } }); }后端拿到code后通过微信的code2Session接口换取openid然后查询或创建用户记录。这里注意不要在前端自行调用code2Session因为需要appid和secret暴露在前端是极不安全的。必须由后端PHP去请求微信接口。4.3 HBuilderX运行到微信开发者工具报错not found page报错信息“not found page”是热词里提到的这个问题的原因通常有两个第一pages.json里配置了页面路径但pages目录下没有对应的.vue文件第二pages.json里指定的页面是首页但首页路径不存在。排查方法点击HBuilderX的“运行”菜单选择“运行到小程序模拟器”先清理一下编译缓存再重新编译。如果还报错打开pages.json检查第一个page节点的path是否准确注意不能以/开头比如应该写pages/index/index而不是/pages/index/index。还有一个小技巧在微信开发者工具里点击“编译模式”的“添加编译模式”把启动页面改成某个已存在的页面路径能快速定位是不是首页配置问题。4.4 微信小程序顶部导航栏高度适配顶部导航栏高度在不同机型上差异很大iPhone X以上有刘海屏Android机型和普通iPhone的胶囊位置也不一样。如果页面使用了自定义导航栏就需要动态获取胶囊按钮位置来计算导航栏高度。getNavBarHeight() { const systemInfo uni.getSystemInfoSync(); const menuButtonInfo uni.getMenuButtonBoundingClientRect(); const navBarHeight (menuButtonInfo.top - systemInfo.statusBarHeight) * 2 menuButtonInfo.height; this.statusBarHeight systemInfo.statusBarHeight; this.navBarHeight navBarHeight; }这段代码的核心思路是胶囊按钮的底部到状态栏底部的距离乘以2加胶囊高度就是导航栏总高度。不同机型算出来的值基本一致误差极小。实测下来iPhone 14 Pro的导航栏高度是44pxAndroid主流机型是48px左右用这套计算方法都能准确覆盖。4.5 uniapp打包上架安卓应用市场与iOS测试包流程uniapp打包上架是很多开发者头疼的地方。安卓端推荐使用云打包方式在HBuilderX里点击“发行”菜单选择“原生App-云打包”填入Android包名和证书信息。第一次打包前需要生成签名证书用keytool生成keytool -genkey -alias youralias -keyalg RSA -validity 20000 -keystore yourname.keystoreiOS端相对麻烦云打包也需要Apple开发者账号的证书和描述文件。测试阶段可以用HBuilderX云打包的“自定义调试基座”先把App装到iPhone上测试确认没问题后再申请正式证书打正式包。这里有一个重要提示小程序和App打包是两套东西。小程序是在微信开发者工具里预览App是通过HBuilderX云打包。如果你用了uni-app特有的API比如uni.getLocation务必在App端配置对应的权限描述否则上架审核时会被拒。4.6 小程序推送消息方案选择戏曲推广类小程序用户可能希望收到“新剧目上线”的通知。微信小程序的消息推送目前主要有三种方式订阅消息一次性订阅、模板消息已废弃、小程序内部消息中心。订阅消息是当前唯一推荐的方式但有一个限制用户必须主动点击“允许”按钮订阅一次才能收到一次推送。要实现持续推送运营上需要引导用户每次进入小程序时再次订阅。在代码里订阅消息通过uni.requestSubscribeMessage调用uni.requestSubscribeMessage({ tmplIds: [模板ID], success: (res) { // 用户同意订阅后后端才能推送 } });后端的推送还是要PHP调微信的subscribeMessage.send接口把用户的openid、模板ID和要传递的数据组装好POST到微信服务器。我实际测试后建议不要把推送当成核心功能用户转化率比预期低不少而且模板需要提前在小程序后台申请审核周期较长要在项目排期里预留出来。4.7 常见问题速查表把整个开发过程中最常遇到的坑整理成一张表方便后面你直接对照排查问题原因解决方案小程序请求接口报403小程序后台未配置合法域名微信公众平台添加request合法域名必须是HTTPS请求返回400参数错误uni.request的data格式不对检查Content-Type是否为application/json封面图不显示图片域名不在downloadFile合法域名内小程序后台配置downloadFile合法域名分享卡片无图片imageUrl不在白名单或拼接错误检查图片完整URL或使用云存储临时链接播放音频没声音静音开关或autoplay被浏览器拦截设置uni.createInnerAudioContext的obeyMuteSwitch为false页面跳转后数据丢失未使用页面参数或全局状态管理详情页通过onLoad的options参数接受id或存storage安卓真机样式错乱涉及百分比高度或flex布局兼容改用rpx单位避免百分比高度依赖父容器编译报错找不到组件路径大小写不一致检查import路径和文件名的相对路径数据库连接失败配置了localhost但服务器需要内网IP数据库主机改用服务器实际内网IP地址接口返回慢未做缓存或索引缺失对常用查询条件加索引列表接口加缓存4.8 真机调试时的小技巧真机调试遇到问题不要直接找代码bug先把手机和电脑连接到同一个WiFi关闭防火墙检查后端服务器的80或443端口是否对局域网开放。我在Windows开发机上遇到过几次后端服务跑在Nginx里Nginx只监听了127.0.0.1导致手机访问不到。解决办法是修改Nginx配置的listen为0.0.0.0:80重启Nginx。另外微信开发者工具里有一个“真机调试2.0”模式比旧版稳定很多但它要求小程序后台开启调试域名白名单。开发阶段可以临时关闭域名校验但上线前一定要把真实HTTPS域名配好否则正式版会全部请求失败。调试接口时我习惯在微信开发者工具的Network面板里看请求的入参和出参同时结合PHP后端的日志函数trace()把SQL语句打出来。ThinkPHP 3.2.3默认开启了调试模式的话接口返回的JSON里会多出SQL日志字段上线前要记得关闭debug。5. 内容运营与后台管理的最佳实践5.1 后台管理系统的设计思路推广型小程序的生命力在于内容更新。如果每次更新戏曲视频、演出资讯都要找开发人员改代码这个项目基本就废了。所以后台管理系统是整套方案的隐形核心。我用PHP只做了一个极简后台管理员登录、剧目管理增删改查、唱段管理、资讯发布、评论审核、数据统计。界面不追求花哨用最基础的HTML表格和表单加上一点Bootstrap样式甚至不需要前端框架。管理员的权限控制用ThinkPHP自带的Auth类实现区分超级管理员和普通编辑角色普通编辑只能维护内容不能修改用户数据和系统配置。5.2 戏曲内容录入的批量操作技巧后台录入戏曲视频时一个剧目可能包含几十个唱段如果每次只能点击“新增”再逐个填写效率极低。我在后台实现了“批量导入唱段”功能在剧目编辑页支持一次性粘贴多行唱段信息格式为“唱段名|音频地址|唱词文本”后端用PHP函数explode按换行和竖线拆分成数组逐条插入。$lines explode(\n, I(post.segment_batch)); foreach ($lines as $line) { $parts explode(|, trim($line)); if (count($parts) 2) { $data [ drama_id $dramaId, title trim($parts[0]), audio_url trim($parts[1]), lyrics_text trim($parts[2] ?? ) ]; M(segment)-add($data); } }这个功能上线后运营录入效率至少提升三倍。5.3 评论审核与敏感词过滤戏曲评论区会有人发广告、刷屏甚至夹带不文明言论。我在后端评论接口里加了一道敏感词过滤通过一个简单的Trie树实现把需要拦截的词汇放进数组遍历评论内容时把命中词替换成星号。同时评论发布前需要经过管理员审核。前端提交评论后评论状态默认是0待审核只有管理员在后台点击通过后状态变成1前端才会展示出来。这样虽然会损失一点互动即时性但对文化类项目的舆论安全是必要的。6. 项目部署上线与运行维护6.1 Linux服务器环境配置后端部署我用的是腾讯云轻量服务器2核4G够用。环境是LNMPLinux Nginx MySQL PHP。PHP版本这里有个值得说的点ThinkPHP 3.2.3官方支持到PHP 5.6但我实测在PHP 7.2下也跑得很稳只有个别废弃函数需要改一下。如果服务器预装了PHP 7.4以上建议用docker跑一个PHP 7.2的容器省去兼容性折腾。Nginx配置有一个关键点为了支持PATH_INFO模式需要在server块里配置location / { if (!-e $request_filename) { rewrite ^/(.*)$ /index.php/$1 last; } }这个配置如果不加ThinkPHP的路由会失效接口全部404。6.2 HTTPS证书配置与小程序的域名要求微信小程序强制要求接口域名必须是HTTPS。我把域名解析到服务器后用certbot一键申请了Lets Encrypt免费证书有效期90天配置了自动续期任务。这里提醒一下小程序后台配置request合法域名时只填协议头加域名比如https://api.example.com不要带路径。证书部署完成后记得在Nginx配置里把HTTP请求301重定向到HTTPS否则用户通过http访问接口时小程序会报“不是合法域名”或“SSL证书错误”。6.3 备份策略与日常运维数据是这个项目最核心的资产尤其是用户收藏、评论这些互动数据。我在服务器crontab里加了每天凌晨自动备份MySQL数据库的任务0 2 * * * mysqldump -u root -p你的密码 opera_db /backup/opera_db_$(date %Y%m%d).sql保留最近30天的备份避免磁盘占满。同时把备份文件同步到对象存储防止服务器故障导致备份丢失。小程序侧的版本更新流程也不复杂在HBuilderX里改完代码点击“发行”菜单选择“小程序-微信”编译后会生成一个dist目录里面是完整的微信小程序代码。直接在微信开发者工具中导入这个dist目录点击“上传”按钮然后在微信公众平台提交审核审核通过后发布。一般审核时间在几小时到一天不等建议避开节假日提交。6.4 上线后的数据埋点与运营分析上线后不能只看小程序后台自带的访问量我在关键页面埋了点首页banner点击、剧目详情浏览时长、播放器播放次数、搜索关键词、收藏转化率。实现方式很简单在每个操作后调用一个统一的track接口api.post(/Api/Stat/track, { event: drama_detail_view, drama_id: this.dramaId });后端把这些事件写入日志表每周生成一份汇总报表。通过数据能直观看到用户对哪类剧种更感兴趣、哪些剧目播放完成率高。我就发现“越剧”分类的收藏转化率明显高于其他剧种于是推荐算法里把越剧内容的权重调高了首页的点击率涨了不少。7. 项目迭代方向与我的踩坑总结这个项目上线后的第一个月访问量稳步上升用户平均停留时长接近三分钟说明传统戏曲内容在小程序端是有人愿意消费的。但用户留存率偏低很多人看完一个唱段就离开了。复盘下来主要有两个原因一是缺少连续播放功能用户听完全部唱段后不知道该干嘛二是个性化推荐不足首页展示的内容对所有用户是一样的。后续迭代我计划加两个核心功能连续播放列表和基于收藏偏好的推荐流。连续播放就是在剧目详情页把唱段按顺序排列播放完一个自动播下一个并且在小程序后台播放时全局悬浮一个迷你播放条用户切到其他页面也不会中断。推荐流则利用已有的收藏和浏览记录按剧种聚类做简单的协同过滤数据量大了以后可以考虑接一个轻量级的推荐算法。我把这个项目完整的开发流程和踩过的坑都整理出来了最后说几个我自己的体会。第一PHP uniapp 微信小程序这个组合绝非过时反而是快速上线运营推广类小程序的性价比之选。第二后端的接口规范和小程序端的请求封装一定要尽早统一越往后改成本越高。第三做内容推广类项目前期一定要把后台管理的体验打磨好因为最终真正天天用系统的不是你自己而是运营人员。第四真机调试和上线前测试非常关键尤其是Android机型的兼容性和不同微信版本的API差异宁可多花时间测也不要上线后被用户骂。希望对正在做类似项目的你有所帮助。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻