简介:博客社区项目完整前后端源码,基于uniapp开发,可直接打包生成H5、Android App及微信小程序,适合具备Vue基础的移动端开发者、全栈学习者及需要快速搭建社区类应用的团队参考。压缩包共1588个文件,约31.34MB,以vue页面组件、js逻辑脚本、json配置、wxml/wxss等小程序文件及png/svg图标资源为主,还附带可直接安装的apk安装包和jql云数据库查询示例,便于快速查阅与复用。目前已吸引190人学习下载。项目实现了用户注册登录、文章发布与增删改查、评论点赞等核心功能,并接入uni-id用户体系、unicloud云存储云数据库,同时引入uni-ui与uView优化界面,另通过uni-admin提供后台管理参考,是一套从端到云、从前台到后台的完整实践案例,便于排查打包配置、学习JQL语法和多端适配,亦可作为毕业设计或快速搭建同类社区应用的起始模板。
1. 一套代码跑三端:这个uniapp博客社区项目到底值不值得拆
博客社区这种项目,说简单也简单,说麻烦也麻烦。简单在于核心功能无外乎发文章、评论、点赞、用户体系,任何有两年经验的开发者都能在三天内用传统后端撸一个出来;麻烦在于如果你想同时覆盖微信小程序、App 和 H5,传统开发模式意味着三套前端代码、三套发布流程、三个团队(或者一个人干三份活)。所以当我看到这套基于 uniapp 的博客社区源码时,第一反应不是去看它功能有多全,而是看它在多端复用和云端一体化上到底做到了什么程度。
这套项目的技术选型很明确:uniapp 做前端框架,uni-ui 加 uView 做 UI 层,unicloud 做后端,uni-id 做用户体系,uni-admin 做后台管理。换句话说,整个项目从客户端到服务端到管理端都跑在 uni 生态里,前后端不用单独部署服务器,这在中小型项目和外包交付场景里是个非常实际的选择。本文会把这套源码的工程结构、JQL 操作云数据库的完整流程、uni-id 登录注册的落地方式、跨端打包遇到的典型问题串起来讲一遍,重点是可复现的操作细节和参数级排错思路。适合正在用 uniapp 做内容社区类项目、或者刚接触 unicloud 想找个完整参考实现的开发者。
2. 工程结构与 UI 层:uni-ui 和 uView 混用的组件组织方式
2.1 从 HBuilderX 导入到目录结构解读
拿到源码包后,解压直接用 HBuilderX 导入即可,需要强调一点:这个项目依赖的是 HBuilderX 内置的 uniapp 编译器,而不是 CLI 方式创建的项目。两者的区别在于,CLI 项目通过 package.json 管理依赖,而 HBuilderX 项目把依赖放在 uniapp 内置的编译环境中,因此你不需要执行 npm install 来安装 uni-ui 和 uView,只要在 manifest.json 里开启了相应模块,编译器会自动处理。
打开项目后先看根目录结构,我们需要关注几个关键目录:
pages:页面文件,存放所有业务页面uni_modules:uniapp 插件市场风格的模块目录,uView 和 uni-ui 都可能在这里uniCloud-aliyun:云函数和云数据库相关代码,这是后端逻辑的核心static:静态资源目录store:Vuex 状态管理目录(如果项目用了的话)
其中uniCloud-aliyun目录是这套源码区别于普通前端项目的地方。uniapp 的云开发空间绑定在这里,云函数、云对象、数据库 Schema 都放在这个目录下,上传部署后即可在云端运行。
2.2 uni-ui 与 uView 的分工边界
很多初学者会把 uni-ui 和 uView 混为一谈,其实在这个项目里它们的分工很清晰。uni-ui 是 DCloud 官方维护的组件库,组件风格偏原生,稳定性优先,适合基础表单、列表、弹窗这类通用场景。uView 则是第三方开源的高质量组件库,在 UI 美观度上更胜一筹,尤其在 tabBar 图标、按钮样式、标签、空状态这些视觉效果明显的组件上表现更好。
这个项目将两者混用,具体的原则是:全局页面骨架用 uni-ui,业务展示组件用 uView。比如发布文章页面的输入框和选择器用的是 uni-ui 的uni-forms和uni-data-picker,而文章卡片列表、点赞按钮、用户头像这些偏展示的组件用的是 uView 的u-card、u-icon、u-avatar。
在页面中引用 uView 组件的方式是:
<template> <view> <u-card :title="article.title" sub-title="发布于 2023-10-25"> <view class="content">{{ article.summary }}</view> <view class="actions"> <u-button size="small" type="primary" @click="likeArticle(article._id)"> 点赞 {{ article.like_count }} </u-button> </view> </u-card> </view> </template> <script> export default { data() { return { article: {} } }, methods: { likeArticle(id) { // 点赞逻辑 } } } </script>这里有两个细节需要注意。第一,uView 的组件在使用前需要在main.js中通过uni.$u挂载配置,否则部分组件无法正常显示。第二,uView 的样式是基于 SCSS 的,如果项目里没有安装sass依赖,在编译 H5 端时会报错,HBuilderX 创建的项目需要在插件市场安装scss编译插件。
2.3 pages.json 的多端差异化配置
pages.json 是 uniapp 项目的路由和页面配置文件,在这套博客社区项目里它承担了多端适配的重要职责。一个常见的坑是:微信小程序端要求导航栏标题不能使用自定义导航组件,否则会导致胶囊按钮重叠;而 App 端则推荐使用自定义导航来统一风格。
项目中的 pages.json 里对每个页面配置了style字段下的navigationBarTitleText和app-plus:
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "博客社区", "enablePullDownRefresh": true, "app-plus": { "titleNView": { "backgroundColor": "#FFFFFF", "titleText": "首页" } } } } ], "tabBar": { "color": "#909399", "selectedColor": "#2979ff", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/publish/publish", "text": "发布" }, { "pagePath": "pages/mine/mine", "text": "我的" } ] } }这里app-plus是 App 端特有配置,titleNView用于控制原生导航栏。如果发布到微信小程序时发现导航栏样式异常,优先检查这里是否配置了只在 App 端生效的自定义导航参数。反向的坑也有:小程序端的navigationStyle设置为custom后,需要自己在页面顶部预留状态栏高度,否则内容会被刘海屏遮挡。
3. unicloud 实战:JQL 操作云数据库与 uni-id 用户体系落地
3.1 云数据库的 Schema 设计与权限规则
unicloud 的数据库是 MongoDB 的兼容实现,但与直接操作 MongoDB 不同,unicloud 推荐使用 JQL(JSON Query Language)语法来操作数据库。这套博客社区项目的核心数据表包括:用户表(uni-id-users)、文章表(articles)、评论表(comments)、点赞记录表(likes)。
以文章表为例,它的 Schema 定义了这个集合的字段结构和权限规则。在uniCloud-aliyun/database/articles.schema.json中你会看到类似这样的配置:
{ "bsonType": "object", "required": ["title", "content", "author_id"], "permission": { "read": true, "create": "auth.openid != null", "update": "doc.author_id == auth.uid", "delete": "doc.author_id == auth.uid" }, "properties": { "_id": { "description": "文章ID,系统自动生成" }, "title": { "bsonType": "string", "title": "标题", "trim": "both" }, "content": { "bsonType": "string", "title": "内容", "maxLength": 50000 }, "author_id": { "bsonType": "string", "title": "作者ID", "foreignKey": "uni-id-users._id" }, "like_count": { "bsonType": "int", "title": "点赞数", "default": 0 }, "create_time": { "bsonType": "timestamp", "title": "创建时间", "forceDefaultValue": { "$env": "now" } } } }这段 Schema 定义的权限规则是整个后端安全的关键。read: true表示所有人可读文章列表;create: "auth.openid != null"表示只有登录用户才能发布文章;update和delete都要求doc.author_id == auth.uid,也就是说只能操作自己发布的文章。这个权限模型直接对应了需求中「用户可以修改、删除自己发布的文章」的功能描述。
3.2 前端 JQL 查询文章列表与联表操作
在传统开发模式中,查询文章列表需要后端写接口,前端调接口,还要处理分页、排序、关联用户信息等逻辑。用 JQL 直接在客户端查询,可以省去写云函数的步骤。项目首页的文章列表是通过 JQL 的db.collection方法完成的:
// pages/index/index.vue 中拉取文章列表 const db = uniCloud.database() const articleCollection = db.collection('articles') async function loadArticles(page = 1) { const res = await articleCollection .where({ status: 'published' }) .orderBy('create_time desc') .skip((page - 1) * 10) .limit(10) .field('_id,title,summary,like_count,comment_count,author_id') .get() return res.result.data }这段代码的要点在于field方法指定了返回字段,避免了查询大字段 content 造成流量浪费。如果需要展示文章作者的头像和昵称,JQL 支持getTemp联表查询,这是 JQL 相对传统 RESTful API 的一个明显优势:
const res = await articleCollection .where({ status: 'published' }) .orderBy('create_time desc') .skip((page - 1) * 10) .limit(10) .field('_id,title,summary,like_count,comment_count,author_id') .getTemp() const authorCollection = db.collection('uni-id-users') const authorRes = await authorCollection .where({ _id: db.command.in(res.result.data.map(item => item.author_id)) }) .field('_id,nickname,avatar') .getTemp() const result = await db.collection('articles,uni-id-users') .get()这里使用了 JQL 的联表查询特性:先用getTemp()分别查询文章表和用户表的临时结果,再用db.collection('articles,uni-id-users').get()合并结果。合并后的数据会自动将author_id关联到对应用户表的_id,前端可以直接通过row.author_id[0].nickname获取作者昵称。需要注意,联表查询的性能开销比单表查询大,首页这种高频场景建议还是把作者昵称冗余在文章表里,或者使用缓存。
3.3 uni-id 登录注册完整流程
uni-id 是这套项目中用户体系的基石,它已经内置了密码注册登录、手机号一键登录、微信登录等多种方式。项目里采用的是用户名密码加 token 的方式,核心登录逻辑封装在/common/login.js中:
// 登录方法示例 async function login(username, password) { const uniIdCo = uniCloud.importObject('uni-id-co') const res = await uniIdCo.login({ username: username, password: password, captcha: '' // 如果开启了图形验证码,需要传入 }) if (res.code === 0) { // 登录成功,token 已自动存入 storage uni.setStorageSync('uni_id_token', res.token) uni.setStorageSync('uni_id_token_expired', res.tokenExpired) return res } else { // 处理错误码,常见的有 uni-id-account-not-exists、uni-id-password-error uni.showToast({ title: res.msg, icon: 'none' }) return null } } async function register(username, password) { const uniIdCo = uniCloud.importObject('uni-id-co') const res = await uniIdCo.registerUser({ username: username, password: password }) if (res.code === 0) { // 注册成功后自动登录 return login(username, password) } }使用uni-id-co云对象的方式是关键,它不同于旧的uni-id云函数,新的实现将业务逻辑封装为云对象,简单来说就是一个类,方法对应不同的操作,比如login、registerUser、logout、updateUser等。你不需要为每个接口单独部署云函数,一个云对象就能搞定全部用户操作。
登录成功后的 token 管理也需要注意细节。uni-id 默认 token 有效期为 7 天,过期后访问受保护的数据会自动失败。项目中的做法是在main.js里封装了一个全局请求拦截器,每次请求前检查 token 是否过期,过期则跳转登录页:
// main.js 中 token 过期检查 uni.addInterceptor({ invoke(args) { const token = uni.getStorageSync('uni_id_token') const expired = uni.getStorageSync('uni_id_token_expired') if (token && expired && Date.now() < expired) { return args } else { uni.navigateTo({ url: '/pages/login/login' }) return false } } })这个方法可以帮助理解 uni-id 的 token 机制,但也要注意,在客户端做过期判断只能拦截跨页面跳转,真正的数据安全仍然依赖云端 Schema 的权限规则。更稳妥的做法是直接在云函数或云对象中通过uniID.checkToken校验 token,本项目在云对象中已经做了这一步。
4. 发布文章与评论点赞:从前端交互到云数据库写入
4.1 发布页面实现与图片上传云存储
发布文章功能涉及前端表单、图片上传、云数据库写入三个环节。在 uView 的u-form组件中,发布页面包含标题、摘要、正文、封面图四个字段。图片上传用的是 unicloud 云存储的uniCloud.uploadFileAPI,请求路径为/pages/publish/publish.vue:
<template> <view class="publish-page"> <u-form :model="form" ref="formRef"> <u-form-item label="标题" prop="title"> <u-input v-model="form.title" placeholder="请输入文章标题" /> </u-form-item> <u-form-item label="摘要" prop="summary"> <u-input v-model="form.summary" type="textarea" placeholder="一句话介绍文章内容" /> </u-form-item> <u-form-item label="封面" prop="cover"> <u-upload :fileList="fileList" name="cover" @afterRead="afterRead" :maxCount="1" /> </u-form-item> <u-form-item label="正文" prop="content"> <u-input v-model="form.content" type="textarea" :autoHeight="true" /> </u-form-item> </u-form> <u-button type="primary" :loading="submitting" @click="submitArticle"> 发布文章 </u-button> </view> </template> <script> export default { data() { return { form: { title: '', summary: '', content: '', cover: '' }, fileList: [], submitting: false } }, methods: { async afterRead(event) { const file = event.file const res = await uniCloud.uploadFile({ filePath: file.url || file.path, cloudPath: `article_cover/${Date.now()}_${file.name}` }) this.form.cover = res.fileID this.fileList = [{ url: res.fileID }] }, async submitArticle() { this.submitting = true const db = uniCloud.database() const res = await db.collection('articles').add({ title: this.form.title, summary: this.form.summary, content: this.form.content, cover: this.form.cover, author_id: uni.getStorageSync('uni_id_token') ? uniCloud.getCurrentUserInfo().uid : '', status: 'published', like_count: 0, comment_count: 0 }) this.submitting = false if (res.result._id) { uni.navigateBack() } } } } </script>这段代码里容易踩坑的点在于uniCloud.getCurrentUserInfo()的调用时机。这个 API 必须在登录成功且 token 未过期时才能正确返回uid,如果页面是公开可访问的(未登录状态),这里会返回 null,导致author_id为空字符串。后续在 Schema 上配置的create: "auth.openid != null"就会拦住这条数据,表现为前端发布成功但实际上数据没有写入。
因此在实际开发中,发布页面通常要在onLoad里先检查登录态,未登录则不允许进入发布页。项目中的处理方式是在发布页面添加了onShow生命周期检测:
onShow() { const token = uni.getStorageSync('uni_id_token') if (!token) { uni.navigateTo({ url: '/pages/login/login?redirect=/pages/publish/publish' }) } }4.2 点赞功能的防重复设计与计数器更新
点赞是社区项目里一个典型的并发场景:用户点击点赞按钮时,前端直接给文章的like_count字段加 1,但如果没有防重复限制,用户可以通过频繁点击来刷赞。项目中的做法是维护一个独立的点赞记录集合,同时利用数据库的doc更新命令保证计数器原子性:
async function toggleLike(articleId) { const db = uniCloud.database() const likesCollection = db.collection('likes') // 当前用户是否已点赞 const currentUid = uniCloud.getCurrentUserInfo().uid const existing = await likesCollection .where({ article_id: articleId, user_id: currentUid }) .get() if (existing.result.data.length === 0) { // 未点赞,写入记录并增加文章点赞数 await likesCollection.add({ article_id: articleId, user_id: currentUid, create_time: Date.now() }) await db.collection('articles') .doc(articleId) .update({ like_count: db.command.inc(1) }) } else { // 已点赞,删除记录并减少点赞数 await likesCollection .doc(existing.result.data[0]._id) .remove() await db.collection('articles') .doc(articleId) .update({ like_count: db.command.inc(-1) }) } }db.command.inc(1)是 MongoDB 风格的原子自增操作,它保证了即使多个用户同时点赞,计数结果也不会出现丢失更新。相比先读取后写入的方式,这样更可靠。不过,likesCollection集合的 Schema 权限也需要配置好,确保只有登录用户才能写入点赞记录,且同一用户对同一文章的点赞记录是唯一的。可以在 likes 表的 Schema 里设置一个联合唯一索引,但目前 unicloud 的可视化界面不支持联合索引配置,所以更稳妥的方案是在前端判断后,再在云对象中做一次二次校验。
评论功能的实现和点赞类似,只是数据结构上需要维护article_id和parent_id(如果是楼中楼),因为本文重点是整体流程,这里就不展开嵌套评论的细节了。
4.3 编辑与删除文章:Schema 权限在客户端的限制
当用户编辑或删除文章时,直接使用客户端 SDK 调用数据库是最快的方式,但必须理解 Schema 权限在其中扮演的角色。项目的删除文章实现如下:
async function deleteArticle(articleId) { const db = uniCloud.database() const res = await db.collection('articles') .doc(articleId) .remove() if (res.result.deleted === 1) { uni.showToast({ title: '删除成功' }) // 同时删除该文章下的评论 await db.collection('comments') .where({ article_id: articleId }) .remove() } }这段代码能正确工作的前提是:articles表 Schema 中配置了"delete": "doc.author_id == auth.uid",这样当请求者不是文章作者时,云数据库会直接拒绝删除操作。但是注意,这里的doc.author_id存储的必须是 uni-id 的 uid 字符串,并且字符完全匹配,任何一端的数据类型不一致(比如存成了 ObjectId)都会导致权限判断失败。
同样的逻辑适用于更新操作。如果用户试图把author_id改成别人的 id 来通过权限校验,由于 Schema 权限是在写操作时校验doc.author_id(即数据库中的当前值)而不是请求中的值,因此这种篡改是无效的。但是为了避免不必要的信任边界,发布文章时author_id应该由云函数或云对象自动注入,而不是信任前端的传参。这是很多初学 uniapp 云开发的人容易忽略的问题。
5. 多端打包发布:从 manifest 配置到 H5、App、小程序的差异化处理
5.1 manifest.json 基础配置与 App 图标启动图
打包发布是这套博客社区项目的最后一公里,也是踩坑最多的环节。在 HBuilderX 中,manifest.json是控制多端发布的核心配置文件,它包含了应用名称、AppID、图标、启动图、各端 SDK 配置等关键信息。
拿到源码包后,首先要修改manifest.json中的 AppID。如果你直接使用示例项目的 AppID,HBuilderX 会报错或无法真机运行,需要替换成你自己的 DCloud 开发者账号对应 AppID。修改路径为:manifest.json -> 基础配置 -> AppID。
图标和启动图是发布 App 时的必填项。HBuilderX 提供了图标生成工具,可以一键生成所有尺寸的图标,但需要注意图片底色会影响 iOS 的应用圆角效果,建议使用带安全边距的设计。启动图则需要在manifest.json -> App 图标配置 -> 启动图配置中分别上传 iOS 和 Android 的启动图,如果漏掉某个尺寸,部分机型会以黑屏代替启动图。
微信小程序端的配置则需要额外关注mp-weixin节点:
{ "mp-weixin": { "appid": "你的小程序AppID", "setting": { "urlCheck": false, "es6": true, "minified": true }, "usingComponents": true, "lazyCodeLoading": "requiredComponents" } }urlCheck: false在开发阶段可以关闭合法域名校验,但发布体验版时必须配置 uniCloud 的域名白名单。关于这部分,需要在微信公众平台后台把https://api.next.bspapp.com添加到 request 合法域名中,否则小程序端请求云数据库会失败。
5.2 H5 端发布与跨域问题
H5 端是发布最简单的端,HBuilderX 点击「运行到浏览器」即可本地调试,正式发布用「发行 -> 网站-H5手机版」。但发布到独立域名后,会遇到一个典型问题:如果将 H5 部署在与 unicloud 云服务不同的域名下,浏览器跨域请求会被拦截。
解决方案是在 manifest.json 中配置h5节点的devServer代理:
{ "h5": { "title": "博客社区", "router": { "mode": "hash", "base": "/" }, "devServer": { "proxy": { "/api": { "target": "https://你的云函数URL", "changeOrigin": true, "pathRewrite": { "^/api": "" } } } } } }生产环境更简单的方案是使用 uniCloud 自带的前端网页托管,将 H5 打包后的静态文件直接托管到 uniCloud 的服务空间,这样前后端同域,从根上规避跨域问题。项目源码中如果已有 H5 打包产物,可以直接在 web 控制台上传托管。
5.3 App 端打包与真机调试的常见坑
App 端是三种发布方式里最容易出问题的。当运行到真机时,如果控制台报出未配置AppID或云函数请求失败,大致从下面几个方向排查:
manifest.json的 AppID 是否已被替换为你自己的- 是否已在 uniCloud 控制台创建了服务空间并将项目关联到该空间
- 云函数是否已上传部署:右键
uniCloud-aliyun/cloudfunctions目录,选择「上传所有云函数、公共模块及actions」 - 真机与电脑是否在同一局域网内(HBuilderX 真机运行时的传统要求)
关于打包为 App 后无法连接 uniCloud 的问题,常见原因是服务空间是阿里云版但项目里用的是腾讯云版,或者反过来。检查uniCloud-aliyun/config.json中的spaceId与云服务空间类型是否一致。如果更换过服务空间,需要删除项目下的uniCloud-aliyun/.temp目录后重新关联。
App 端还有一个独有的uses权限配置。如果你要实现类似定位、推送、NFC 功能,必须在 manifest.json 的App 模块配置中勾选对应模块并申请权限。这个项目中虽然没有这些高级功能,但如果你基于此源码二次开发要接入地图或扫码,需要准备相应的 SDK 配置,否则相关 API 在 App 端不会有任何响应。
5.4 小程序端的 subpackages 分包加载优化
当博客社区的文章数量增多,小程序主包体积很容易超过 2MB 的发布时间限制。常见做法是按照路由维度拆分:把pages/index/index、pages/article-detail/article-detail、pages/login/login这几个核心页面放在主包,将pages/publish/publish、pages/mine/mine、pages/user-center/user-center放入 subpackages:
{ "subPackages": [ { "root": "pages/publish", "pages": [ { "path": "publish", "style": { "navigationBarTitleText": "发布文章" } } ] }, { "root": "pages/mine", "pages": [ { "path": "mine", "style": { "navigationBarTitleText": "个人中心" } } ] } ], "preloadRule": { "pages/index/index": { "network": "all", "packages": ["pages/publish"] } } }分包配置中要注意的是,所有 tabBar 页面必须放在主包,否则会报编译错误。preloadRule是预下载规则,表示在进入首页后空闲时预下载 publish 分包,提升用户点击发布按钮时的加载速度。如果你的项目后续增加了富文本编辑、图片裁剪等重型组件,建议把非首屏功能都挪到分包里。
6. 基于 uni-admin 搭建后台管理系统与二次开发技巧
6.1 uni-admin 的部署与角色权限体系
不要被「后台管理系统」这几个字吓到,uni-admin 不是你想象中的需要从零开发的 Vue 后台,而是一套基于 uni-app 的完整后台管理框架。将它引入这个博客社区项目的目的是让运营人员能直接管理文章、用户和评论,而不用去数据库后台手动改记录。
uni-admin 部署流程是将仓库中的 admin 目录单独导入 HBuilderX,并在 manifest.json 中配置自己的服务空间 ID,然后在uniCloud-aliyun/cloudfunctions下上传uni-admin相关云对象。首次运行需要创建管理员账号,在登录页面点击「注册管理员」,系统会要求提供一个超级管理员码(在云对象的配置项中设置)。
uni-admin 的权限模型基于 uni-id 的角色体系,默认包含admin和editor两个角色。如果你想给运营配置一个只读权限,需要在 uniCloud 控制台的用户管理中找到该用户,在扩展字段中修改role数组:
{ "role": ["editor"] }然后在 uni-admin 的页面路由中通过配置permission字段控制菜单可见性:
// uni-admin 的 pages.json 中配置管理员专属页面 { "path": "pages/article-manage/article-manage", "style": { "navigationBarTitleText": "文章管理" }, "permission": { "role": ["admin", "editor"] } }通过这种方式,非技术背景的运营人员可以在 uni-admin 中直接编辑文章标题、下架违规内容,而这些操作最终都会写入同一套 unicloud 数据库,与客户端的数据实时同步。
6.2 云对象的二次封装:统一返回格式与错误处理
实战中,如果直接在客户端调用db.collection().add(),错误处理会散落在每个页面,可维护性较差。我一般建议在云函数层封装一层 CRUD 云对象,把所有写操作收敛到一个入口,这样也便于后续加上自定义的权限判断和字段过滤,而不是完全信任 Schema 权限。
在这个项目里,我为文章模块封装了article-admin云对象:
// uniCloud-aliyun/cloudfunctions/article-admin/index.obj.js const db = uniCloud.database() module.exports = { _before() { // 云对象内置的钩子,每次请求前执行 this.user = this.getUniIdToken() if (!this.user || this.user.role !== 'admin') { throw new Error('无管理员权限') } }, async updateArticle(params) { const { id, title, content, status } = params // 增加日志 console.log(`admin update article: ${id}`) const res = await db.collection('articles') .doc(id) .update({ title: title, content: content, status: status }) return { code: 0, data: { updated: res.updated } } }, async deleteArticle(params) { const { id } = params // 删除文章前先清理关联评论 await db.collection('comments') .where({ article_id: id }) .remove() const res = await db.collection('articles') .doc(id) .remove() return { code: 0, data: { deleted: res.deleted } } } }_before()钩子是 uniapp 云对象 2.0 版本引入的请求前置过滤器,它能在方法调用前统一执行鉴权、日志、参数校验。上述代码中,管理员删除文章时会级联删除评论,避免数据库中残留僵尸数据。这里要再次强调,客户端直接调用db.collection与云对象调用的区别:前者受限于表级 Schema 权限,适合简单场景;后者可以在方法内部做更细粒度的逻辑,适合敏感操作。
6.3 你可能会踩的性能坑与优化方向
基于这套源码二次开发时,有几个已经浮现的潜在性能隐患值得提前处理。第一是云数据库的 limit 默认值,JQL 查询如果不写limit,默认返回 20 条,但这个项目中首页还叠加了orderBy create_time desc,如果文章量大,MySQL 式索引策略就不适用了,需要在articles表上对create_time字段建立索引。在 unicloud 控制台的数据库管理中,需要手动确认索引是否存在。
第二个坑是客户端 JQL 联表查询的性能衰减。前面展示的db.collection('articles,uni-id-users').get()写法在数据量小的时候没问题,但文章超过千条后,联表查询的耗时会有明显上升。建议将文章列表页改为「文章数据 + 作者昵称冗余」模式,发布文章时把作者昵称和头像直接写入文章记录,评论列表同理。
第三个坑在图片云存储的防盗链上。默认情况下,.jpg、.png等的云存储链接是可以公开访问的,如果博客被爬虫大量抓取图片,流量费可能会超支。可以在 unicloud 控制台对云存储设置 URL 鉴权,但注意这会影响微信小程序端图片显示,需要在小程序开发后台配置 downloadFile 合法域名。
以上这些点不会立刻让项目崩溃,但随着数据和用户的增长会成为瓶颈。理解这套源码的结构和价值,比直接改功能更重要。
本文还有配套的精品资源,点击获取