ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

微信小程序天气预报双框架兼容实战:ThinkPHP与Laravel一套代码两处跑

微信小程序天气预报双框架兼容实战:ThinkPHP与Laravel一套代码两处跑 微信小程序天气项目我前后接过三四个后端从原生PHP写到ThinkPHP最近一次又用Laravel完整重写了一遍。这个Thinkphp和Laravel框架都支持 微信小程序天气预报系统的项目核心点不在天气接口怎么调——那太简单了真正麻烦的是怎么让一套前端代码同时适配两个主流PHP框架的后端接口以及处理小程序登录、定位授权、缓存刷新这些琐碎但又绕不开的细节。这篇就把整个项目的设计思路、双框架适配技巧、踩过的坑都摊开讲清楚给正在做类似项目的同学一个可以直接抄作业的参考。1. 项目从零梳理为什么一套系统要同时兼容两个框架1.1 需求源头与技术选型先交代一下这个项目的背景。客户手里已经有一套运营中的微信小程序功能就是基础的天气查询、城市切换、未来几天预报。上一轮交付的是纯前端源码工程后端接口散落在几个老项目里代码风格混乱文档基本等于没有。这轮的需求很明确重做一套标准化的后端接口但有个硬性约束——服务端环境不确定部署方可能用ThinkPHP也可能用Laravel所以代码要同时兼容这两个框架。单看天气预报四个字很多人觉得拿个免费天气API一接就完事了。但实际做下来你会发现接口对接本身只占20%的工作量剩下80%都在处理生态问题小程序端的登录态、用户授权、定位反查城市、缓存策略、接口安全校验、多环境部署适配。这套系统真正的价值在于把这些问题标准化让前端不用关心后端到底是哪个框架后端也不用关心前端业务怎么展示。选ThinkPHP和Laravel做双兼容还有一个现实原因这两个框架在国内PHP项目里的占有率加起来超过一半很多外包团队和中小公司都在用。做一套代码两处跑省的是以后重复开发的成本。ThinkPHP的快速上手和Laravel的优雅语法各有拥趸把业务逻辑抽离成纯PHP类库再用各自的框架壳去调用这是最稳妥的做法。1.2 前后端分离的整体架构架构上我坚持前后端完全分离。小程序端只管渲染和交互所有数据请求都走wx.request到后端接口后端统一返回JSON。这样做的好处是前端调试时可以先用mock数据把页面写完后端接口就绪后再联调后端框架切换时前端代码一行都不用动。接口层面我设计了这几类接口分类路由路径职责说明登录接口/api/auth/login接收wx.login的code换取openid返回登录态token当前天气/api/weather/now返回指定城市的实况天气未来预报/api/weather/forecast返回未来3-7天天气预报城市搜索/api/city/search根据关键字搜索城市城市定位/api/city/locate根据经纬度反查城市信息这套结构下来小程序端的页面逻辑就简单了进入首页先调定位接口拿城市再调天气接口拿数据数据渲染完存一份到本地缓存下次冷启动先吐缓存再刷新网络。后端两个框架的差异只发生在路由定义和控制器写法上业务逻辑类完全复用。2. 小程序端核心模块实现2.1 天气首页与城市选择器的设计小程序端我先说首页。天气页面的核心诉求是打开就看见结果所以首屏设计上温度、天气现象、风力和空气质量这四项必须放在第一屏可视区域。城市名称放在左上角点击后进入城市管理页。背景色根据天气状况动态变化——晴天用蓝、阴天用灰、雨天用深色这个小细节很提升体验但很多人会忽略。城市选择器是这个项目里比较费心思的模块。微信小程序没有现成的城市选择组件我用picker的moderegion来选省市区同时做了一个搜索入口可以搜索全国城市搜索结果调用后端接口去匹配。这里要特别注意region组件只能选到市级层面像朝阳区这类区县级天气后端接口未必有精确数据所以我在前端做了城市名归一化处理把用户选到的区县映射到所属地级市再查天气。代码层面城市选择页我用了scroll-view做列表滚动城市列表数据从后端一次性拉取后存入storage后续打开直接读取本地数据速度快而且省流量。列表按拼音首字母分组右侧做一个可拖动的字母索引条这在小程序里要用movable-area实现比H5要繁琐一些但效果很接近原生体验。2.2 wx.login登录态与用户信息微信小程序登录流程是绕不开的一环。核心原理就是wx.login获取临时code传给后端后端拿着code调微信的code2Session接口换来openid和session_key。openid是用户在这个小程序里的唯一标识session_key用来解密用户手机号等敏感信息。整个流程必须在后端完成code不能暴露给第三方。登录态的维护我用的是自定义token机制。后端拿到openid后先去数据库查用户是否存在不存在就自动注册一条记录然后把用户ID、openid、过期时间这三个字段做签名生成token返回给前端。前端把token存到storage里后续所有请求在header里带上。token有效期我设的是7天过期后接口返回401前端察觉后静默重新走一次wx.login流程用户完全无感知。这里有个非常容易踩的坑不要直接在storage里存openid。小程序端一旦被逆向openid泄露虽然不至于直接被盗号但配合其他接口漏洞可能被刷数据。用token做一层隔离即使token被截获也有过期机制兜底。另外session_key不要在后端持久化存储只在需要解密手机号时临时使用用完即弃。最后说一句获取手机号。小程序获取手机号必须用button组件的open-typegetPhoneNumber前端拿到code后再传给后端后端用session_key调接口解密。这个流程和wx.login是两步独立的操作很多新手混在一起导致解密失败。我项目里是登录成功后用户主动点击一键登录按钮再走手机号流程。2.3 缓存策略让小程序秒开小程序首屏加载速度直接决定用户留存率。天气数据的特点是小时级变化所以我设计了多级缓存城市列表一周一更新当前天气缓存30分钟天气预报缓存2小时。冷启动时先把本地缓存渲染出来同时后台拉取新数据回来后用setData替换这样用户感知就是秒开实际数据最多滞后半小时完全在可接受范围内。缓存的存储形式我直接用的wx.setStorageSync没有用复杂的数据库方案。小程序的Storage有10MB限制天气项目的数据量很小几十个城市的JSON也就几十KB完全够用。每次后端返回数据时带一个timestamp字段前端判断是否过期再决定是否写入缓存。这里补充一个细节wx.setStorageSync的key要注意命名规范我习惯用cache_weather_{cityId}这种格式方便排查和清理。另外缓存更新时不要整块替换先和旧数据做合并防止因为网络异常导致缓存变成空数据页面出现大块空白。我遇到过几次这种情况后来加了一层兜底如果新数据为空直接保留旧缓存不更新。3. 后端接口设计与双框架适配3.1 天气数据源接入与容错天气数据源我选的是和风天气的免费开发者版原因很简单注册就能用QPS限制对个人项目足够数据接口返回JSON结构稳定。另一个备选是心知天气也支持免费额度但接口字段命名我个人觉得不如和风直观。接入的时候要注意几点。第一所有请求必须带key参数这是你的身份标识不要写死在前端代码里必须放在后端通过代理请求防止key被刷。第二和风的接口是按城市ID查询的不是城市名所以系统里必须有城市编码表前端传城市ID后端透传给天气服务商。第三接口返回的数据结构要二次封装把需要的字段比如温度、天气现象、风力等级提取出来按照我们自己的JSON结构返回给前端不要让前端直接面对服务商的数据格式。容错处理是这部分的重头戏。天气服务商的接口偶尔会超时或者返回异常数据我在后端加了三层保护超时控制curl超时设为5秒、解析失败降级返回空数组并记录日志、数据合理性校验温度范围在-50到60之间超过则丢弃该数据。这样即使天气源出了问题小程序端最多显示数据更新失败不会因为拿到脏数据而崩溃。另外我在后端加了一个简单的Redis缓存层同一城市的天气数据5分钟内不重复请求天气服务商。一方面节省API调用额度另一方面加快响应速度。没有Redis环境的时候我退回到文件缓存把JSON数据写到一个临时文件里配合失效时间判断效果也还行。3.2 ThinkPHP与Laravel路由和控制器对照实现这个项目最有意思的部分来了。要让同一套业务代码同时跑在ThinkPHP和Laravel上关键在于把业务逻辑和框架逻辑彻底分离。我的做法是先写一个纯PHP的业务类库不继承任何框架的基类只依赖Composer的自动加载和自己的命名空间。然后分别在两个框架里写一层薄薄的控制器来做适配。路由定义上两个框架的风格完全不同。ThinkPHP用路由文件里的Route::ruleLaravel用routes/api.php里的Route::post。我建了一个统一的路由映射表记录每个接口对应的控制器和方法部署时根据框架类型选择加载哪份路由文件。控制器的内部实现尽量精简只做三件事接收请求参数、实例化业务类并调用方法、返回JSON响应。看一段Laravel的控制器代码?php namespace App\Http\Controllers\Api; use App\Services\WeatherService; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; class WeatherController extends Controller { protected $weatherService; public function __construct(WeatherService $weatherService) { $this-weatherService $weatherService; } public function now(Request $request) { $cityId (int) $request-input(city_id); if ($cityId 0) { return response()-json([code 400, msg 参数错误]); } // 缓存逻辑 $cacheKey weather_now_ . $cityId; $data Cache::remember($cacheKey, 1800, function () use ($cityId) { return $this-weatherService-getNowWeather($cityId); }); return response()-json([code 0, msg ok, data $data]); } }再看ThinkPHP版本?php namespace app\api\controller; use think\facade\Cache; use app\common\service\WeatherService; class Weather { protected $weatherService; public function __construct() { $this-weatherService new WeatherService(); } public function now() { $cityId (int) input(param.city_id, 0); if ($cityId 0) { return json([code 400, msg 参数错误]); } // 缓存逻辑 $cacheKey weather_now_ . $cityId; $data Cache::remember($cacheKey, 1800, function () use ($cityId) { return $this-weatherService-getNowWeather($cityId); }); return json([code 0, msg ok, data $data]); } }你会发现业务代码几乎一模一样差异只在响应方法response()-json对比json、输入获取方式$request-input对比input方法、缓存门面Cache门面用法几乎一致。这个设计是完全可行的核心业务类写一遍两个框架各自套壳就行。3.3 接口返回格式约定与错误码前后端联调最怕的事情就是接口返回格式不统一。前端判断业务逻辑有时候看code有时候直接看HTTP状态码出现混乱基本就是接口格式设计不合格。我这次把格式标准定死了所有的接口统一返回JSON包含code、msg、data三个字段。code为0代表成功非0代表业务错误HTTP状态码统一用200业务错误通过code字段区分。错误码的规划如下表错误码含义前端处理策略0请求成功正常渲染数据400参数缺失或格式错误提示用户重新操作401登录凭证失效静默重新登录403无权限访问提示无权限404资源不存在提示找不到页面500服务器内部错误提示稍后重试1001天气API超时使用缓存数据兜底前端封装了一个统一的request方法全局处理这些code。401做静默重登500做Toast提示其他错误码打印日志方便排查。这个约定一旦落地前后端就不会因为这次返回格式不一样的问题反复扯皮。还有一个容易忽略的点跨域。虽然小程序端不存在浏览器跨域问题但如果你用H5调试工具打开接口就会遇到CORS。所以后端我从一开始就在中间件层加了跨域头ThinkPHP和Laravel各有各的写法但效果一样。加这个纯粹是为了开发调试省心线上小程序其实用不到。4. 联调、部署与常见问题排查实录4.1 本地联调关闭域名校验与内网穿透本地开发的第一个障碍就是小程序的域名白名单限制。开发者在工具里可以勾选不校验合法域名但这只管开发工具真机预览时如果后端跑在本地请求会直接失败。我的解决办法是内网穿透——把本地服务映射到公网域名这样小程序真机也能访问到。我用的是ngrok之类的隧道工具一条命令就能把本地80端口映射到公网临时域名。不过要注意这种临时域名一次会话就会变每次重新启动都要改小程序的请求基地址。后来我直接把API基地址做进了小程序的配置文件区分dev/prod环境切换一行配置就行。联调过程中最耗时的是定位功能。小程序端的wx.getLocation拿到的经纬度是火星坐标系而天气服务商的城市反查接口用的是百度或高德坐标系两边直接对不上逆地理编码出来的城市会偏个十几公里。解决办法是统一走一个坐标系转换函数我在前端写了一个wgs84转gcj02的工具函数转完再传给后端反查城市基本就准了。4.2 线上部署HTTPS与合法域名配置小程序正式上线必须有HTTPS的合法域名这是硬性门槛。域名要提前准备好ICP备案、SSL证书都不可少。证书我用的是Lets Encrypt的免费证书三个月自动续期配置好cron脚本后基本不用去管。运营商的云服务器自带免费证书额度也够用。后端部署这块ThinkPHP和Laravel的入口文件位置不同但Nginx配置的核心规则一致把请求都转发到public目录的index.php。有一个细节我要特别提一下Laravel默认的Nginx配置需要处理PUT、DELETE等请求方法只写try_files是不够的还要把PUT、DELETE也透传给PHP-FPM。我第一次上线时就因为没注意这个前端调PUT接口全是404。部署目录的结构我维持在比较清晰的状态project/ ├── laravel/ # 后端Laravel代码版本单独管理 │ ├── app/ │ ├── public/ │ └── artisan ├── thinkphp/ # 后端ThinkPHP代码 │ ├── app/ │ └── public/ ├── miniprogram/ # 小程序前端源码 │ ├── pages/ │ ├── utils/ │ └── app.js └── docs/ # 接口文档、部署文档4.3 现场实录5个高频坑与解决方式第一个坑是小程序端的wx.request并发限制。小程序对同域名的并发请求数有限制超过限制的请求会被挂起排队。天气页面恰好有多个接口同时调用出现部分数据始终加载不出来的诡异现象。解决办法是把多个接口合并成一个聚合接口一次请求返回全部首页数据既减少并发压力又加快了首屏渲染。第二个坑是后端框架版本差异导致的语法兼容问题。ThinkPHP的input函数从5.0到8.0参数有变化Laravel的辅助函数在不同版本也有调整。项目里遇到过一次在ThinkPHP 8里使用input(param.city_id, 0)没问题但老项目ThinkPHP 5里这个写法返回的是null而不是默认值。后来我统一改成手动从$_GET和$_POST里取参数再做类型转换彻底绕开框架差异。第三个坑是天气接口的限流。和风天气免费版有每日调用上限上线当天用户量一大直接把额度刷爆了。痛定思痛除了后端加缓存我还给前端加了一层限流逻辑同一个用户5分钟内只能手动刷新一次天气通过后端Redis计数实现超限就返回刷新过于频繁请稍后再试。另外缓存时间从30分钟延长到45分钟流量高峰也能扛得住。第四个坑是小程序安卓端和iOS端的定位权限不一致。iOS需要用户明确授权才返回经纬度安卓部分机型默认不弹授权框直接返回失败。我在前端加了授权状态的检查和引导弹窗没有授权时提示去设置页开启同时给了一个手动选择城市的兜底入口。第五个坑也是大家容易忽视的服务器时间与缓存过期时间的差异。小程序端做缓存判断用的是本地时间如果用户手机时间不准缓存策略就会错乱。后来我所有缓存判断都改用后端返回的server_time字段做参考前端只记录时间戳不用本地时间做判断。5. 这个项目的扩展方向与我的个人体会做完这套系统我最大的感受是双框架兼容这件事说白了就是业务代码的抽象能力。你不把业务层和框架层切干净换个框架就得重写一遍那才是真的灾难。这段时间的实操验证下来我的体会是——把业务模型抽成纯PHP类让控制器只做参数接收和响应输出不管TP还是Laravel迁移成本都能压到极低。后续如果再往深了做我建议从三个方向扩展。一是接入更多数据源比如空气质量指数、紫外线强度、穿衣指数这些生活天气指标做聚合展示提升用户粘性。二是增加消息订阅推送到点提醒用户出门带伞或者增减衣物这个在订阅消息的合规类目里是被允许的值得做。三是做一个简单的用户偏好设置比如用户关注了哪些城市、常用城市排序配合后端数据库做个性化推荐把单薄的工具类小程序做成有留存的价值产品。最后再分享一个小技巧项目里所有的接口返回都打了统一的时间戳字段排查问题的时候特别方便。前后端都记录这个时间点一旦出bug对着时间轴一眼就能看出是哪个环节慢。这种小习惯真的是踩过几次坑之后才慢慢养成的。
返回列表