-中半部分)
一、金丹5ClientDB 前端直连数据库1、本重境界目标掌握前端直接操作云数据库的能力无需编写云函数即可完成增删改查大幅提升简单业务的开发效率。对应宗门职能凡间殿宇直连万象录藏简单数据操作无需经过天旨司中转凡间可直接按权限读写天界数据研发效率成倍提升2、技能点clientDB 概念、前端直接操作数据库、减少云函数编写1、ClientDB 本质前端直接调用数据库 API请求由 uniCloud 官方网关转发自动校验 DB Schema 权限 语法和云函数内数据库操作几乎完全一致学习成本极低 必须配合 DB Schema 权限规则使用无 Schema 默认禁止前端操作 2、适用场景简单增删改查、后台管理系统、快速原型开发3、学习方法用 clientDB 重写修士列表对比云函数方式4、完整实战前端直查修士列表无需创建云函数直接在页面代码里操作数据库新建monks 数据库文件路径pages-monk/monk-list/monk-list.vue 对应能力前端直连数据库查询、条件筛选、模糊搜索、下拉刷新、上拉加载 前置依赖monks 表已配置 Schema 并开启读权限 template view classpage !-- 搜索筛选区 -- view classsearch-bar input classsearch-input v-modelkeyword placeholder搜索弟子姓名 confirm-typesearch confirmrefreshList / picker modeselector :rangelevelOptions range-keylabel changeonLevelChange view classlevel-picker {{ selectedLevelLabel }} text classarrow▾/text /view /picker /view !-- 列表区域 -- scroll-view scroll-y classlist-scroll refresher-enabled :refresher-triggeredrefreshing refresherrefreshrefreshList scrolltolowerloadMore view classlist-box view classmonk-item v-foritem in list :keyitem._id clickgoDetail(item._id) view classavatar{{ item.name ? item.name.charAt(0) : ? }}/view view classinfo view classname-row text classname{{ item.name }}/text text classlevel-tag{{ item.level }}/text /view view classskill主修{{ item.mainSkill || 暂无 }}/view /view view classcultivation{{ item.cultivation }} 修为/view /view /view !-- 状态提示 -- view classstatus-tip text v-ifloading加载中.../text text v-else-if!hasMore list.length 0没有更多了/text text v-else-iflist.length 0 !loading暂无弟子数据/text /view /scroll-view !-- 新增按钮 -- view classadd-btn clickgoAdd/view /view /template script setup import { ref, computed, onMounted } from vue import { onPageShow } from dcloudio/uni-app // 基础状态 const list ref([]) const loading ref(false) const refreshing ref(false) const hasMore ref(true) const page ref(1) const pageSize 10 // 搜索筛选 const keyword ref() const selectedLevel ref() const levelOptions [ { label: 全部境界, value: }, { label: 炼气初期, value: 炼气初期 }, { label: 炼气中期, value: 炼气中期 }, { label: 炼气后期, value: 炼气后期 }, { label: 筑基初期, value: 筑基初期 } ] const selectedLevelLabel computed(() { const target levelOptions.find(item item.value selectedLevel.value) return target ? target.label : 全部境界 }) // 获取数据库实例ClientDB 直连 const db uniCloud.database() const dbCmd db.command const collection db.collection(monks) // 核心方法 /** * 加载列表数据 * param {Boolean} isRefresh 是否刷新 */ async function loadList(isRefresh false) { if (loading.value) return if (!hasMore.value !isRefresh) return loading.value true if (isRefresh) refreshing.value true try { // 1. 构建查询条件 let where {} // 境界筛选 if (selectedLevel.value) { where.level dbCmd.eq(selectedLevel.value) } // 姓名模糊搜索 if (keyword.value.trim()) { where.name new RegExp(keyword.value.trim(), i) } // 2. 计算分页偏移量 const skipNum isRefresh ? 0 : (page.value - 1) * pageSize // 3. 执行查询ClientDB 直连 const res await collection .where(where) .orderBy(cultivation, desc) // 按修为降序 .skip(skipNum) .limit(pageSize) .get() const dataList res.result.data if (isRefresh) { // 刷新替换全部数据 list.value dataList page.value 1 } else { // 加载更多追加数据 list.value [...list.value, ...dataList] } // 判断是否还有更多 hasMore.value dataList.length pageSize if (!isRefresh) page.value console.log(查询结果, res.result) } catch (err) { console.error(查询失败, err) uni.showToast({ title: err.errMsg || 查询失败, icon: none }) } finally { loading.value false refreshing.value false } } // 刷新列表 function refreshList() { hasMore.value true page.value 1 loadList(true) } // 加载更多 function loadMore() { loadList(false) } // 选择境界 function onLevelChange(e) { selectedLevel.value levelOptions[e.detail.value].value refreshList() } // 跳转详情 function goDetail(id) { uni.navigateTo({ url: /pages-monk/monk-detail/monk-detail?id${id} }) } // 跳转新增 function goAdd() { uni.navigateTo({ url: /pages-monk/monk-add/monk-add }) } // 生命周期 onMounted(() { loadList(true) }) onPageShow(() { // 页面返回时刷新列表 refreshList() }) /script style scoped langscss .page { height: 100vh; display: flex; flex-direction: column; background: #f5f7fa; } /* 搜索栏 */ .search-bar { display: flex; gap: 20rpx; padding: 20rpx 30rpx; background: #fff; flex-shrink: 0; } .search-input { flex: 1; height: 70rpx; background: #f5f7fa; border-radius: 35rpx; padding: 0 30rpx; font-size: 28rpx; } .level-picker { height: 70rpx; line-height: 70rpx; padding: 0 20rpx; background: #f5f7fa; border-radius: 35rpx; font-size: 26rpx; color: #333; display: flex; align-items: center; gap: 8rpx; .arrow { font-size: 20rpx; color: #999; } } /* 列表滚动区 */ .list-scroll { flex: 1; padding: 0 20rpx; } .list-box { padding: 20rpx 0; } .monk-item { display: flex; align-items: center; padding: 30rpx; background: #fff; border-radius: 16rpx; margin-bottom: 20rpx; gap: 24rpx; :active { opacity: 0.9; } } .avatar { width: 90rpx; height: 90rpx; border-radius: 45rpx; background: linear-gradient(135deg, #1976d2, #42a5f5); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: bold; flex-shrink: 0; } .info { flex: 1; min-width: 0; .name-row { display: flex; align-items: center; gap: 16rpx; margin-bottom: 8rpx; .name { font-size: 32rpx; font-weight: bold; color: #333; } .level-tag { font-size: 22rpx; color: #f57c00; background: #fff3e0; padding: 4rpx 12rpx; border-radius: 20rpx; flex-shrink: 0; } } .skill { font-size: 24rpx; color: #999; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } } .cultivation { font-size: 28rpx; color: #388e3c; font-weight: bold; flex-shrink: 0; } .status-tip { text-align: center; padding: 40rpx; font-size: 24rpx; color: #999; } /* 悬浮新增按钮 */ .add-btn { position: fixed; right: 40rpx; bottom: 120rpx; width: 100rpx; height: 100rpx; border-radius: 50%; background: #1976d2; color: #fff; font-size: 48rpx; line-height: 96rpx; text-align: center; box-shadow: 0 8rpx 20rpx rgba(25, 118, 210, 0.3); z-index: 99; } /style二、金丹6DB Schema 数据结构与权限1、本重境界目标掌握数据表结构定义、字段校验、读写权限配置从根源上保障数据安全是 ClientDB 正常运行的前提。对应宗门职能制定天条玉律定义每类数据的字段规范、校验规则、访问权限是云端数据安全的第一道防线所有凡间直连操作都必须遵守此律。2、技能点Schema 定义、字段校验、权限控制、触发器1、Schema 作用定义表结构、字段类型、必填校验、默认值、读写权限 2、权限粒度支持表级权限、字段级权限、行级权限 3、权限取值true 所有人可读写、false 禁止、auth 仅登录用户、自定义表达式 4、校验规则字段类型、长度、范围、正则等自动校验写入数据不符合规则直接拒绝3、学习方法为修士集合编写完整 Schema定义字段类型与读写权限4、完整实战monks 表 Schema 定义1. 创建 Schema 文件文件路径uniCloud/database/schema/monks.schema.json{ bsonType: object, // 表的中文描述便于管理 description: 修士信息表, // 字段定义所有允许的字段都要在这里声明 properties: { _id: { bsonType: string, description: 文档唯一ID系统自动生成 }, name: { bsonType: string, description: 修士道号, title: 道号, // 必填字段写入时不能为空 required: true, // 最小最大长度 minLength: 2, maxLength: 20 }, level: { bsonType: string, description: 境界, title: 境界, required: true, // 枚举值只能是指定范围内的值 enum: [ 炼气初期, 炼气中期, 炼气后期, 筑基初期, 筑基中期, 筑基后期, 金丹期 ] }, cultivation: { bsonType: int, description: 修为值, title: 修为, required: true, // 数值范围 minimum: 0, maximum: 100000 }, mainSkill: { bsonType: string, description: 主修功法, title: 主修功法, default: 基础吐纳术 // 默认值不传时自动填充 }, intro: { bsonType: string, description: 个人简介, title: 简介, maxLength: 200 }, avatar: { bsonType: string, description: 头像地址, title: 头像 }, createTime: { bsonType: timestamp, description: 创建时间, // 强制默认值新增时自动写入当前时间 forceDefaultValue: { $env: now } } }, // 权限配置核心安全部分 permission: { // 读权限true 所有人可读auth 仅登录用户可读false 禁止前端读 read: true, // 写权限包含新增、更新、删除 create: auth, // 新增仅登录用户可新增 update: auth, // 更新仅登录用户可修改 delete: auth // 删除仅登录用户可删除 }, // 附加设置字段在管理端的显示顺序 required: [name, level, cultivation] }2. 上传 Schema 生效右键 monks.schema.json 文件 → 选择「上传 Schema」等待控制台提示成功规则立即在云端生效3、核心心法总结1、Schema 是安全底线只要配置正确即使前端被篡改也无法写入非法数据、越权操作数据。 权限进阶 2、行级权限doc.auth auth.uid实现「用户只能修改自己的数据」 3、字段级权限敏感字段如密码、余额可单独设置前端不可读 4、自动校验字段类型、长度、枚举值等不符合规则时云端会直接拒绝写入并返回错误无需自己写校验代码。 5、ClientDB 必备没有配置 Schema 的表前端 ClientDB 默认没有任何操作权限会直接报错。三、金丹7云存储 - 文件上传管理1、本重境界目标掌握文件云端上传、访问、删除全流程将本地临时文件替换为云端永久存储实现图片、附件等资源的云端托管。对应宗门职能灵材云库执掌使负责将凡间灵材图片、文件收纳至天界云库生成永久可访问的灵材编号全宗共享调用。2、技能点文件上传、文件管理、图片处理、CDN 加速1、前端直传uniCloud.uploadFile 前端直接上传文件到云存储无需经过云函数中转 2、fileID云存储文件唯一标识格式为 cloud://服务空间ID/文件路径 3、云函数操作云端删除文件、批量管理文件 4、权限控制默认所有文件可读上传 / 删除需登录权限 5、地址转换getTempFileURL 将 fileID 转为临时访问链接用于 H5 / 外链访问3、学习方法实现头像上传到云存储返回可访问 URL4、完整实战头像上传云存储改造将之前的本地上传逻辑替换为上传到 uniCloud 云存储返回永久 fileID。1. 前端上传工具封装文件路径utils/upload.js/** * 云存储上传工具金丹七重·云端版 * 替代本地临时路径文件永久存储在云端 */ /** * 选择并上传单张图片到云存储 * param {Object} options 配置项 * param {String} options.folder 存储文件夹默认 avatar * returns {Promisestring} 返回云存储 fileID */ export function uploadImageToCloud(options {}) { const { folder avatar } options return new Promise((resolve, reject) { // 第一步选择本地图片 uni.chooseImage({ count: 1, sizeType: [compressed], sourceType: [album, camera], success: async (chooseRes) { const tempFilePath chooseRes.tempFilePaths[0] try { uni.showLoading({ title: 上传中... }) // 第二步上传到云存储 // 生成唯一文件名时间戳 随机数 后缀避免重名覆盖 const ext tempFilePath.split(.).pop() const fileName ${folder}/${Date.now()}_${Math.random().toString(36).slice(2, 8)}.${ext} // uniCloud.uploadFile 前端直传云存储 const uploadRes await uniCloud.uploadFile({ filePath: tempFilePath, // 本地文件临时路径 cloudPath: fileName, // 云端存储路径 cloudPathAsRealPath: true // 云端路径作为真实路径使用 }) // uploadRes.fileID 就是云端文件唯一标识 // 小程序端可直接用 fileID 作为 image 组件的 src resolve(uploadRes.fileID) } catch (err) { console.error(上传失败, err) reject(err) } finally { uni.hideLoading() } }, fail: (err) { // 用户主动取消 if (err.errMsg?.includes(cancel)) { reject(new Error(cancel)) } else { reject(err) } } }) }) } /** * 将 fileID 转为临时访问链接H5端、外链分享时使用 * param {String} fileID 云存储文件ID * returns {Promisestring} 临时访问地址 */ export function getFileUrl(fileID) { return new Promise(async (resolve, reject) { try { const res await uniCloud.getTempFileURL({ fileList: [fileID] }) resolve(res.fileList[0].tempFileURL) } catch (err) { reject(err) } }) }2. 新增弟子页调用改造页面云存储上传测试页金丹七重 文件路径pages-test/cloud-storage-demo.vue 对应能力图片选择、云端上传、预览、删除、地址转换 template view classpage view classtitle金丹七重 · 云存储上传测试/view view classcard view classcard-title上传操作区/view button typeprimary clickchooseAndUpload选择图片并上传/button view classupload-area v-iffileID view classlabel上传成功fileID/view view classfile-id{{ fileID }}/view /view /view view classcard view classcard-title图片预览/view view classpreview-box v-iffileID image :srcfileID modeaspectFit classpreview-img clickpreviewImage / view classtip点击图片预览大图/view /view view v-else classempty暂无上传图片/view /view view classcard view classcard-title地址转换H5外链用/view button clickgetTempUrl获取临时访问链接/button view classurl-box v-iftempUrl text classurl-text{{ tempUrl }}/text /view /view view classcard view classcard-title删除文件/view button typewarn :disabled!fileID clickdeleteFile删除云端文件/button /view !-- 日志 -- view classcard view classcard-title操作日志/view view classlog-item v-for(log, index) in logList :keyindex {{ log }} /view /view /view /template script setup import { ref, reactive } from vue const fileID ref() const tempUrl ref() const logList reactive([]) // 添加日志 function addLog(text) { const time new Date().toLocaleTimeString() logList.unshift([${time}] ${text}) if (logList.length 20) logList.pop() } /** * 选择并上传图片 */ async function chooseAndUpload() { try { // 1. 选择图片 const chooseRes await uni.chooseImage({ count: 1, sizeType: [compressed], sourceType: [album, camera] }) const tempPath chooseRes.tempFilePaths[0] addLog(已选择本地图片 tempPath) // 2. 上传到云存储 uni.showLoading({ title: 上传中... }) const ext tempPath.split(.).pop() const cloudPath test/${Date.now()}.${ext} const uploadRes await uniCloud.uploadFile({ filePath: tempPath, cloudPath: cloudPath, cloudPathAsRealPath: true }) uni.hideLoading() fileID.value uploadRes.fileID addLog(上传成功fileID uploadRes.fileID) uni.showToast({ title: 上传成功, icon: success }) } catch (err) { uni.hideLoading() if (err.errMsg?.includes(cancel)) { addLog(用户取消选择) return } console.error(上传失败, err) addLog(上传失败 (err.errMsg || err.message)) uni.showToast({ title: 上传失败, icon: none }) } } /** * 获取临时访问链接 */ async function getTempUrl() { if (!fileID.value) { uni.showToast({ title: 请先上传图片, icon: none }) return } try { addLog(正在获取临时链接...) const res await uniCloud.getTempFileURL({ fileList: [fileID.value] }) tempUrl.value res.fileList[0].tempFileURL addLog(获取成功 tempUrl.value) } catch (err) { console.error(获取链接失败, err) addLog(获取失败 (err.errMsg || err.message)) } } /** * 预览大图 */ function previewImage() { if (!fileID.value) return uni.previewImage({ urls: [fileID.value], current: fileID.value }) } /** * 删除云端文件 */ async function deleteFile() { if (!fileID.value) return const confirmRes await uni.showModal({ title: 确认删除, content: 确定要删除云端的这张图片吗 }) if (!confirmRes.confirm) return try { addLog(正在删除云端文件...) // 注意前端直删需要配置权限建议通过云函数删除 // 这里调用云函数删除 const res await uniCloud.callFunction({ name: delete-file, data: { fileList: [fileID.value] } }) if (res.result.code 0) { fileID.value tempUrl.value addLog(删除成功) uni.showToast({ title: 删除成功, icon: success }) } else { addLog(删除失败 res.result.msg) } } catch (err) { console.error(删除失败, err) addLog(删除失败 (err.errMsg || err.message)) } } /script style scoped langscss .page { padding: 30rpx; min-height: 100vh; background: #f5f7fa; box-sizing: border-box; } .title { font-size: 36rpx; font-weight: bold; text-align: center; margin: 20rpx 0 40rpx; } .card { background: #fff; border-radius: 16rpx; padding: 30rpx; margin-bottom: 30rpx; } .card-title { font-size: 30rpx; font-weight: bold; margin-bottom: 20rpx; color: #333; } .upload-area { margin-top: 20rpx; padding: 20rpx; background: #f5f7fa; border-radius: 8rpx; .label { font-size: 26rpx; color: #666; margin-bottom: 10rpx; } .file-id { font-size: 24rpx; color: #1976d2; word-break: break-all; font-family: monospace; } } .preview-box { text-align: center; .preview-img { width: 100%; height: 300rpx; background: #f5f7fa; border-radius: 8rpx; margin-bottom: 10rpx; } .tip { font-size: 24rpx; color: #999; } } .empty { text-align: center; color: #999; padding: 40rpx 0; font-size: 26rpx; } .url-box { margin-top: 20rpx; padding: 20rpx; background: #f5f7fa; border-radius: 8rpx; .url-text { font-size: 24rpx; color: #388e3c; word-break: break-all; font-family: monospace; } } .log-item { font-size: 24rpx; color: #666; line-height: 1.8; font-family: monospace; border-bottom: 1rpx solid #f5f5f5; padding: 8rpx 0; } /style3. 云函数删除云端文件新建云函数 delete-file用于删除云存储中的文件use strict // 获取云存储操作实例 const storage uniCloud.storage() exports.main async (event, context) { // 接收前端传递的 fileID 数组支持批量删除 const { fileList [] } event if (!Array.isArray(fileList) || fileList.length 0) { return { code: 400, msg: 文件列表不能为空 } } try { // 调用云存储删除接口 const result await storage.deleteFile(fileList) return { code: 0, msg: 删除成功, data: result } } catch (err) { console.error(删除文件失败, err) return { code: 500, msg: 删除失败 } } }4.核心心法总结1、fileID 用法小程序 / App 端 image 组件可直接使用 fileID 作为 srcH5 端需要用 getTempFileURL 转成临时链接。 2、命名规范建议按业务分文件夹存放如 avatar/、goods/文件名加时间戳避免重名覆盖。 3、性能优化小文件前端直传大文件建议分片上传上传前先压缩图片减少传输时间和存储成本。 4、安全注意默认所有文件公开可读敏感文件不要直接放云存储根目录可通过云函数鉴权后生成临时链接访问。四、金丹8云函数定时触发器1、本重境界目标掌握云端定时任务配置实现无需前端触发、按时间自动执行的云端逻辑完成数据统计、清理、同步等自动化场景。对应宗门职能天条巡守使按天时节律自动执行宗门例行事务无需人工值守保证宗门法度准时运转。2、技能点定时任务配置、自动执行、场景应用1、触发器本质按指定时间规则自动触发对应云函数执行 2、Cron 表达式定义执行时间的标准语法精确到秒级 3、配置文件云函数目录下的 index.config.json 定义触发器 4、典型场景每日数据统计、定时清理过期数据、定时推送消息、定时同步第三方数据3、学习方法配置每日凌晨自动发放修炼资源4、完整实战每日宗门数据统计每天凌晨 0 点自动统计宗门弟子总数、总修为、境界分布存入统计表。1. 创建云函数 daily-stat① 云函数主逻辑文件路径uniCloud/cloudfunctions/daily-stat/index.jsuse strict const db uniCloud.database() const monksCollection db.collection(monks) const statCollection db.collection(daily-stat) // 每日统计表 exports.main async (event, context) { console.log(开始执行每日统计任务时间, new Date().toLocaleString()) try { // 第一步统计各项数据 // 1. 弟子总数 const totalRes await monksCollection.count() const totalMonks totalRes.total // 2. 各境界人数 const levels [炼气初期, 炼气中期, 炼气后期, 筑基初期, 筑基中期, 筑基后期] const levelStat {} for (const level of levels) { const res await monksCollection.where({ level: db.command.eq(level) }).count() levelStat[level] res.total } // 3. 总修为值聚合求和数据量大建议用聚合能力 const allMonks await monksCollection.field({ cultivation: true }).get() const totalCultivation allMonks.data.reduce((sum, item) sum (item.cultivation || 0), 0) // 第二步写入统计表 const today new Date().toLocaleDateString() const statData { date: today, totalMonks, totalCultivation, levelStat, createTime: Date.now() } await statCollection.add(statData) console.log(每日统计完成, statData) return { code: 0, msg: 统计完成, data: statData } } catch (err) { console.error(每日统计失败, err) return { code: 500, msg: 统计失败, error: err.message } } }② 触发器配置文件在 daily-stat 文件夹下新建 index.config.json定义定时规则{ triggers: [ { name: dailyStatTrigger, type: timer, config: 0 0 0 * * * } ] } Cron 表达式说明6 位秒 分 时 日 月 星期 0 0 0 * * *每天 00:00:00 执行 0 30 8 * * *每天 08:30:00 执行 0 0 12 * * 1-5周一到周五中午 12 点执行 0 */30 * * * *每 30 分钟执行一次2. 上传部署并生效右键 daily-stat 云函数文件夹 → 「上传部署」触发器会自动同步到云端到达指定时间自动执行。3. 手动测试开发阶段可以手动触发测试不用等到凌晨 右键云函数 → 「本地运行云函数」直接执行看结果 或在 uniCloud 控制台的云函数详情页点击「测试」手动触发4、核心心法总结1、Cron 避坑注意是 6 位秒 分 时 日 月 周不是 Linux 标准的 5 位星期和日尽量不要同时指定容易逻辑冲突。 2、幂等性定时任务要保证重复执行不会出问题比如每日统计先判断当天是否已统计避免重复写入。 3、异常告警重要定时任务建议加失败告警如邮件、短信通知避免任务挂了无人知晓。 4、适用场景数据统计、过期清理、定时同步、定时推送、订单超时自动取消等所有需要自动化的场景。