ARTICLE DETAIL

资讯详情

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

uni-app实战:微信小程序与Android校园新闻APP双端开发

uni-app实战:微信小程序与Android校园新闻APP双端开发 最近接了一个校园新闻发布APP的项目甲方提了一个挺现实的需求学生群体几乎人人都有微信日常获取信息的第一入口就是小程序但校方又希望有一个独立的Android客户端沉淀用户、支持后续的消息推送和教务功能扩展。所以这个“微信小程序基于Android的校园新闻发布APP”本质不是二选一而是要求一套业务逻辑同时覆盖两个终端。我最终选择了uni-app作为开发框架一套Vue语法的代码分别编译成微信小程序和Android安装包后端用Spring Boot提供RESTful接口数据库选MySQL。今天这篇文章就从需求拆解、功能设计、具体落地代码到双端打包的完整链路讲一遍包括我在这个项目里实际踩过的兼容性坑给正在做类似校园应用的同学一个比较完整的参考。1. 需求梳理与技术选型为什么是微信小程序加Android的双端组合1.1 校园新闻场景的真实需求到底是什么接触过学校类项目的人都懂校园新闻发布和商业新闻App有一个本质区别用户不是冲着刷资讯来的而是冲着查信息来的。课表变动、考试通知、社团活动、讲座安排这些内容有极强的时效性和校园属性学生关心的是今天学校发生了什么和我相关的事而不是泛资讯流。所以需求调研阶段我特别跟校方信息中心的老师确认了几个关键点新闻发布频率不高通常一天几条到十几条不需要复杂的推荐算法但分类一定要清晰比如通知公告、教学动态、校园活动、学术讲座。发布权限在老师或管理员手里学生只读为主但要有搜索和收藏方便期末考前翻找往期通知。小程序端承担大多数流量入口Android端则承载下载安装后的留存场景比如推送、WebView内嵌页、课表查询等后续功能。后台管理不用太花哨能发图文、能置顶、能管理分类就够了。这些需求直接影响后端的表结构设计和前端页面规划而不是一上来就堆功能。1.2 微信小程序与Android原生怎么取舍摆在面前的技术路线其实有三条我简单做了个对比再拍板方案优点缺点微信小程序原生 Android原生App各端体验最极致两套代码、两套团队/两份工时小项目扛不住纯微信小程序开发快、免安装、传播方便覆盖不了需要Android安装包的场景uni-app跨平台一套代码一套代码编译双端成本最低需要处理少量平台差异化逻辑我的判断是这个项目的核心是信息发布与展示逻辑不算复杂原生和跨平台的体验差异在校园场景里并不明显。相比之下开发效率和后续维护成本才是关键。uni-app不仅能同时输出微信小程序和Android的APK还保留了条件编译机制真需要端特定功能时也可以单独写原生插件进退都比较灵活。1.3 技术栈定型和项目结构规划最终定的技术栈是这样的前端uni-appVue 3语法编译目标为微信小程序和Android App后端Spring Boot 2.7 MyBatis-Plus MySQL 8.0鉴权微信小程序通过wx.login获取openidAndroid端通过账号密码登录统一发放JWT文件存储图片和附件走阿里云OSS小程序端直传减少后端带宽压力消息推送Android端集成个推小程序端通过订阅消息实现通知目录结构上我按uni-app社区的推荐做了分层project-root/ ├── pages/ # 页面文件 │ ├── index/ # 新闻列表首页 │ ├── detail/ # 新闻详情页 │ ├── category/ # 分类页 │ ├── search/ # 搜索页 │ └── mine/ # 个人中心 ├── components/ # 自定义组件 ├── utils/ # 封装工具类 │ ├── request.js # 请求封装 │ ├── auth.js # 登录鉴权 │ └── cache.js # 缓存管理 ├── api/ # 接口模块 ├── static/ # 静态资源 └── App.vue / main.js # 应用入口后面所有的开发都围绕这个骨架展开没有走弯路。2. 核心功能设计与数据流转从表结构到前端页面的完整链路2.1 新闻模块的表结构设计新闻发布系统的核心表其实不多我设计了三张主表加一张关联表news新闻主表字段类型说明idbigint主键titlevarchar(200)标题summaryvarchar(500)摘要列表页直接展示contentlongtext正文支持富文本cover_imgvarchar(255)封面图URLcategory_idbigint分类IDstatustinyint0草稿 1已发布 2下线is_toptinyint是否置顶publisher_idbigint发布人IDpublish_timedatetime发布时间viewsint浏览量news_category分类表字段类型说明idbigint主键namevarchar(50)分类名称sortint排序值news_attachment附件表存储新闻关联的图片、文件。user用户表区分管理员、教师、学生三种角色。这样设计足够应对校园新闻的体量不要一上来就搞微服务拆库没有任何必要。2.2 新闻发布的完整数据流转我把新闻从后台上线到前端展示的过程整理成一条链路管理员登录后台 → 创建新闻填写标题、分类、封面、正文 → 保存草稿或直接发布 → 后端写入MySQL → 小程序/App首页请求新闻列表接口 → 后端按置顶、时间倒序返回数据 → 前端渲染列表点击进入详情页 → 详情页根据news_id查询正文与附件 → 浏览结束前端上报浏览量这里有一个细节值得提新闻详情页不要用列表页传参拼数据。很多新手为了省事会在列表页把整个对象传给详情页跳转参数容易被截断而且数据不是实时的。我这边详情页只接收news_id再通过接口拉取详情虽然多一次请求但换来了可靠性和数据实时性。2.3 列表页的信息架构设计首页信息架构上我没有把分类做成tab页而是参考了主流新闻App的做法顶部是置顶公告轮播只展示is_top1的新闻最多5条。中间是分类横向滚动条默认“全部”点击后按分类过滤。下方是新闻信息流每条展示封面图、标题、摘要、发布时间、浏览量。右下角悬浮一个搜索按钮进入独立搜索页。列表接口我加了分页参数page和size后端用MyBatis-Plus的Page对象做物理分页前端上拉触底加载下一页。实测在5000条数据量下接口响应在100ms以内校园场景完全够用。3. 手把手实现uni-app项目初始化与核心页面代码3.1 创建uni-app项目和基础配置使用HBuilderX创建项目时选择uni-app的Vue3模板然后看package.json是否已经依赖vue版本确认无误后安装必要依赖。我这里用的命令行脚手架方式创建# 使用vue-cli方式创建uni-app项目 npx degit dcloudio/uni-preset-vue#vite my-news-app cd my-news-app npm install创建完成后需要做几项基础配置manifest.json里配置小程序的AppID以及Android打包用的包名、版本号、图标等。pages.json里注册页面路由、配置底部tabBar和顶部导航栏样式。我这里的四个主页面是首页、分类、搜索、我的。main.js引入全局样式和工具类。pages.json里顶部导航栏的配置值得单独说。微信小程序的导航栏可以直接用原生导航栏但Android端原生导航栏在沉浸式适配时异常麻烦。我最终选择了自定义导航栏组件统一控制两端样式这样既能保证标题居中、状态栏高度一致又方便在导航栏右侧塞进搜索图标。代价不大收益很直接。3.2 请求封装统一处理Token和错误码请求封装是前端项目的基础设施没做好后面寸步难行。我的utils/request.js里同时处理了微信小程序和Android端的环境差异核心代码如下const BASE_URL https://api.example.edu.cn/api export function request(options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { // 业务状态码与HTTP状态码分离code为0表示成功 if (res.data.code 0) { resolve(res.data.data) } else { // token过期统一处理 if (res.data.code 401) { uni.removeStorageSync(token) uni.navigateTo({ url: /pages/login/login }) } uni.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常请检查网络连接, icon: none }) reject(err) } }) }) }这里有两个细节我特别强调一下token存储不要用uni.setStorageSync之外的方式小程序端跟App端API基本一致避免额外判断。错误码要区分HTTP状态码和业务状态码。后端接口HTTP永远返回200业务错误在body里用code标识这样前端能统一用success里处理业务错误不会被微信小程序拦截非2xx状态码搞晕。3.3 新闻列表页的实现要点列表页我用了一个三列布局左图右文下面是时间。关键点在于触底加载和下拉刷新的实现。uni-app的onReachBottom和onPullDownRefresh生命周期在微信小程序和Android App端都是默认支持的前提是pages.json里开启enablePullDownRefresh。import { getNewsList } from /api/news export default { data() { return { list: [], page: 1, pageSize: 10, hasMore: true, categoryId: 0, loading: false } }, onLoad() { this.loadList() }, onReachBottom() { if (this.hasMore !this.loading) { this.page this.loadList() } }, onPullDownRefresh() { this.list [] this.page 1 this.hasMore true this.loadList().finally(() { uni.stopPullDownRefresh() }) }, methods: { loadList() { this.loading true return getNewsList({ page: this.page, pageSize: this.pageSize, categoryId: this.categoryId }).then(data { this.list this.list.concat(data.records) this.hasMore this.list.length data.total this.loading false }) } } }一个非常容易踩的坑是上拉触底时page自增的时机。千万别在点击回调里先加page再判断hasMore否则最后几帧会出现多请求一页的空数据。正确做法是进入onReachBottom时先判断hasMore和loading再决定page自增。3.4 详情页的富文本渲染与视频展示新闻正文是富文本小程序端我刚开始直接用rich-text组件渲染。但实际测试发现富文本里如果含有视频iframe或者表格rich-text的解析会漏掉不少样式。再三考虑后我换成了web-view渲染H5页面把新闻正文作为一个独立HTML页面传入两端都可以完整展示视频、表格和复杂排版。实现方式后端针对新闻详情额外生成一个HTML渲染接口返回完整HTML片段。前端详情页里嵌入web-view组件src指向该HTML的URL拼接news_id参数。原生App和微信小程序的web-view都能正常渲染。这样代价是加载速度略慢一点点但换取的是排版还原度大幅提升尤其学校经常发带有表格的教务处通知这条收益非常明显。4. 微信小程序端的兼容性适配与审核避坑4.1 导航栏高度、胶囊按钮与安全区域这是小程序开发和H5开发体验差异最大的一个点。微信小程序的胶囊按钮固定在右上角导航栏剩余可用宽度很有限自定义导航栏时必须动态获取胶囊按钮位置和状态栏高度。我封装了一个工具方法export function getNavBarInfo() { const systemInfo uni.getSystemInfoSync() const menuRect uni.getMenuButtonBoundingClientRect() const statusBarHeight systemInfo.statusBarHeight const navBarHeight (menuRect.top - statusBarHeight) * 2 menuRect.height return { statusBarHeight, navBarHeight, menuRect } }Android端运行时statusBarHeight普遍在24px到48px之间iPhone在44px到47pxnavBarHeight需要动态计算不能写死。我在自定义导航栏组件里拿到这些值后给内容区设置padding-top同时预留右侧胶囊按钮的宽度这样在iPhone和华为、小米上都能正常显示标题。4.2 请求域名白名单与HTTPS强制要求微信小程序上线后有个让许多人头大的限制所有请求域名必须在小程序后台配置为白名单并且强制HTTPS。开发时在开发者工具可以勾选“不校验合法域名”但真机预览和正式发布就必须走合法域名。我们的做法是后端API服务全部前置到HTTPS证书用Lets Encrypt的通配符证书一年免费。图片和附件走的对象存储域名也需要在小程序后台的downloadFile合法域名里加上否则图片直接裂掉。上线前统一替换BASE_URL确保没有残留http://的接口遗留。有个隐蔽坑是OSS直传的回调域名。前端直传OSS时上传请求走的是OSS域名如果不在uploadFile合法域名列表里真机上传图片会失败。我为了排查这个浪费了半天最后在微信公众平台后台把阿里云OSS域名加到uploadFile合法域名才正常。4.3 小程序审核被拒的常见原因校园新闻类小程序容易踩一些内容合规雷需要提前准备新闻内容必须可溯源发布人信息要留存在后台不能做成全匿名投稿。我们在关于页注明了信息来源和举报入口。非互联网信息服务资质问题校园新闻属于时政信息传播的边缘地带实际上很多学校小程序是通过校园内部应用场景过审的不要做成新闻聚合平台的样子名字和简介中尽量避免“新闻”二字用“信息发布”或“校园通知”这类中性表述。隐私政策必须明确尤其是获取用户头像昵称不能强迫授权才能浏览内容。我们在登录逻辑上做了游客模式不授权依然可以浏览新闻。需要基于实际场景合理说明不能泛泛而谈。让我收尾精细一些。4.3 小程序审核被拒的两个高频原因处理校园类小程序被拒最普遍的两个原因是类目不符和诱导分享。类目方面新闻资讯类目通常需要《互联网新闻信息服务许可证》学校单位不具备这个资质。我实际操作时的对策是申请类目单选“教育-教育信息服务”别碰“新闻资讯”类目。小程序名称避开“新闻”字样最终定的名称是“XX校园信息通”。在简介里明确写“面向本校师生的信息发布与查询服务”别写“实时新闻资讯”。诱导分享方面小程序里常见的弊病是“分享得积分”“转发解锁内容”校园场景其实不需要这套玩法。我做的是自然分享详情页右上角菜单自带转发但在分享文案里带上当前新闻标题这样既有利于传播又不会有违规风险。好的别纠结这个了继续写Android端章节。5. Android端打包与原生能力整合5.1 uni-app在Android端的打包方式对比uni-app发布Android有两条路径HBuilderX云打包和本地离线打包。云打包本质是把前端资源上传到DCloud服务器再配合原生基座打包成APK不涉及本地Android工程本地离线打包则需要你下载Android Studio、配置好原生工程把uniapp编译产物合并进去。我项目里用了本地离线打包原因是校方后续希望集成自己的一套推送SDK和硬件设备扫码能力离线打包方便在原生层集成插件。本地打包的主要步骤下载DCloud官方提供的Android离线SDK包解压后用Android Studio打开。将HBuilderX编译出的app资源目录拷贝到Android工程的assets/apps/下。修改AndroidManifest.xml中的包名、应用图标。配置dcloud_properties.xml声明需要使用的基础模块比如微信登录、个推、Bluetooth等。构建APK签名并安装。这个过程不算复杂但Android Studio版本、Gradle版本和SDK包版本三者必须匹配。我一开始用了Android Studio最新版配合一份较旧的SDK包结果编译时Gradle一直报依赖找不到。最后换成SDK包官方说明里指定的Gradle版本问题才解决。5.2 Android权限配置与存储适配Android端在配置权限时我按照功能最小化原则来申请uses-permission android:nameandroid.permission.INTERNET / uses-permission android:nameandroid.permission.ACCESS_NETWORK_STATE / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion28 / uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE android:maxSdkVersion32 /这里有个重要的适配细节Android 10及以上强制分区存储Android 13又推出了更细粒度的媒体权限。如果我们应用需要保存图片到相册用WRITE_EXTERNAL_STORAGE在新版已经不够了要在Android 13及以上机器上声明READ_MEDIA_IMAGES权限。我这边为了稳妥没有让App直接写外部存储而是用uni.saveFile保存到应用私有目录或缓存目录新闻类内容本来也不需要保存到公共相册。这样省去一堆权限适配的麻烦用户隐私上也说得清楚。5.3 Android与小程序的数据互通双端数据互通的核心是统一用户体系。我的设计是小程序端微信登录后端通过微信code2session接口拿到openid然后签发JWT。Android端账号密码登录后端校验通过后同样签发JWT。两端的JWT在同一个Redis会话体系里维护用户绑定了微信和手机号之后内容收藏和浏览记录就能跨端同步。实现时有一个坑Android端使用WebView时H5页面请求后端接口的cookie跟原生请求不是一套体系。我索性全部走Authorization头传递JWT规避了WebView的cookie丢失问题。这个小决策在后续接入推送H5页面时帮我少踩了一个大坑。6. 实测效果、性能数据与我回头看的一些建议6.1 双端真机实测数据项目上线前我分别在iPhone 12、小米10、华为nova 8上做了真机测试记录了几个关键指标指标微信小程序iPhone 12Android APK小米10冷启动到首页首屏1.8s2.1s新闻列表接口响应5000条数据90ms90ms首页图片懒加载滚动流畅度流畅流畅包体积1.1MB小程序代码包28.6MBAPK小程序首屏速度主要卡在首页富文本新闻列表渲染和图片懒加载上我在uni-app里用image组件的lazy-load属性后滚动流畅度提升明显。Android端包体积偏大是因为集成了web-view内核和部分推送SDK完全在可接受范围内。6.2 数据缓存策略与冷启动优化新闻类应用的一个特点是内容重读率较低但首页配置需要秒开。我给首页列表做了一层本地缓存首次加载成功后把列表页的响应数据存入Storage缓存有效期30分钟。冷启动时先读缓存渲染再静默请求最新数据比对更新时间有新数据时再刷新页面。详情页不做缓存始终走实时接口因为富文本内容体积大而且新闻修订频率低没必要缓存。小程序端的Storage单位有限制单个key最大1MB所以我缓存的是列表的news_id和title数组而不是整页JSON这样30分钟内的列表缓存占用空间不超过30KB非常安全。6.3 回头看如果再来一次我会怎么改这个项目交付后我复盘了几个可以做得更好的地方也分享给大家参考富文本编辑器选型要提前。当时后台富文本用的是一款开源编辑器但导出的HTML在小程序web-view解析时存在样式兼容问题后来费了不小力气清洗样式。如果再来一次我会锁定一款前后端都兼容的编辑器并在需求阶段直接确认排版规则。后端接口应该预留草稿与定时发布能力。学校老师经常晚上写好新闻第二天早上8点才希望展示这个需求完全可以在Table里加一个scheduled_publish_time字段解决我后来是临时加的改动成本不小。Android端推送一定要在项目早期规划。等上线后再推SDK会发现消息点击去重、别名绑定、前后端推送状态回执这些工作非常零散最好像我这次一样在离线打包时就把个推集成进去。还有一个小技巧双端调试时准备一个“切换环境”的隐藏入口通过连续点击版本号5次弹出开发/测试/生产环境切换菜单。虽然上线时一般会隐藏但在联调阶段能省大量反复打包的时间。这个项目的完整链路不算复杂但胜在需求切合实际、双端逻辑统一。如果你手头也在做校园信息发布相关的应用希望这篇能帮你少走几步弯路尤其是权限适配、富文本渲染、小程序审核类目这几个大坑都提前规避掉进度会顺畅很多。以后有机会我再单独讲讲这套系统的后端接口设计和消息推送的具体实现欢迎交流。
返回列表