微信小程序脚手架
下载地址
https://www.zhengdianxue.com/wp-content/uploads/skills/org-amsek62q--miniprogram-scaffold.zip将提示词发送给你的 AI 安装该 skills
请根据 https://skillhub.cn/install/skillhub.md,安装 @org-amsek62q/miniprogram-scaffold。微信小程序云开发脚手架:一句话触发,自动生成含全服务端 CRUD、集合自举、云存储上传的标准工程骨架。 miniprogram-scaffold 是一个面向微信小程序的「云开发标准起手模板」生成器。它把经过真机验证的最佳实践沉淀为可一键落地的项目骨架,让开发者跳过繁琐的初始化配置,专注于业务本身。 核心亮点 1. 真机验证:所有模板均来自实际可运行项目,非理论示例。
2. 权限收口彻底:数据库访问完全后端隔离,安全性优于客户端直连方案。 3. 零手动建表:集合自举机制免去控制台操作步骤。 4. 自包含:模板内嵌于技能,运行时不依赖外部读取。 适用场景 – 需要快速搭建微信小程序 + 云开发(CloudBase)新项目的开发者。 – 希望统一团队小程序工程结构与云函数规范的团队。 – 想学习「数据库读写走云函数、文件上传走客户端直传」标准分层的前端工程师。
阅读全文 (展开查看完整说明)
概述
微信小程序项目脚手架(miniprogram-scaffold)
Overview
把"从零搭建一个标准微信小程序项目"的规范动作封装为可复用 SOP。当用户要求新建小程序、初始化项目、生成骨架时,按本技能落地:标准目录结构、全局配置(app.json / app.js / app.wxss / project.config.json / sitemap.json)、首页模板、网络请求封装,并按需接入云开发(wx.cloud)。
v2.0.0 默认形态为「云开发完整版」:在 v1 的基础上,额外沉淀一套已在真机验证可用的云开发标准实践——
- 全服务端 CRUD:增(
addTodo)、改(updateTodo)、删(removeTodo)、查(getTodos)四类操作全部走云函数,客户端不再持有任何集合直连代码,权限彻底收口到服务端。 - 集合自举:每个写入/查询云函数开头自动创建集合(
createCollectionIfNotExists优先,回退createCollection吞掉"已存在"错误),首次调用即自动建表,无需手动在控制台建集合。 - 云存储上传:首页演示
wx.chooseMedia+wx.cloud.uploadFile前端直传云存储(标准文件上传做法,避免云函数处理大文件),fileID随待办一并写入数据库并在列表展示。 - 统一分层:数据库读写一律经云函数(服务端身份,不受集合客户端权限约束);文件上传走客户端直传(性能与超时考量)。
目标:一次生成即可在微信开发者工具中直接打开运行,并可作为小程序云开发的标准起手模板。
触发场景
- 用户说"新建/初始化/搭一个小程序""生成项目骨架""帮我建项目结构"。
- 用户要求"配置 app.json / project.config.json""接入云开发""标准目录怎么放"。
- 用户要在现有空目录中补齐小程序基础文件。
- 用户要求"生成带云开发的完整小程序""来个云函数 CRUD 模板""小程序云存储示例"。
不触发:页面级/组件级的具体业务逻辑开发(那是后续编码,不是脚手架);微信小店分销、登录鉴权等特化领域任务(走对应技能)。
决策树(落地前先确认)
在生成文件前,按以下顺序向用户确认(可合并提问,已明确则跳过):
- 是否使用云开发(CloudBase)?
- 是(默认推荐)→ 启用
cloudfunctionRoot,app.js中wx.cloud.init,生成cloudfunctions/五个示例云函数(quickstart / getTodos / addTodo / updateTodo / removeTodo),首页用「全服务端读写 + 云存储上传」完整版。 - 否 → 纯前端骨架,首页用客户端直连简化版(见"纯前端降级首页"),不生成云函数目录。
- 是(默认推荐)→ 启用
- 目标目录是否为空?
- 空目录 → 直接生成全套。
- 已有文件 → 仅补齐缺失项,不覆盖用户已有文件(除非用户明确要求覆盖)。
- 是否使用 TypeScript?
- 默认用原生 JS(最通用)。若用户要求 TS,改用
.ts并加tsconfig.json+project.config.json的compileType设置(本技能默认 JS 模板,TS 为可选扩展)。
- 默认用原生 JS(最通用)。若用户要求 TS,改用
- 是否需要 tabBar?
- 需要 → 在
app.json加入tabBar字段并生成对应页面与图标占位(图标由用户提供,仅留说明)。 - 默认不生成 tabBar,保持最小骨架。
- 需要 → 在
标准目录结构(云开发完整版)
miniprogram/
├── app.js # 应用入口(云开发版:wx.cloud.init)
├── app.json # 全局配置(含 cloudfunctionRoot)
├── app.wxss # 全局样式
├── project.config.json # 项目配置(开发者工具)
├── sitemap.json # 索引配置
├── pages/
│ └── index/
│ ├── index.wxml # 待办列表 + 云存储上传 UI
│ ├── index.wxss
│ ├── index.js # 全服务端读写 + 上传
│ └── index.json
├── components/ # 自定义组件目录(先建空目录占位)
├── utils/
│ ├── request.js # wx.request 封装(纯前端接口场景用)
│ └── util.js # 通用工具
├── styles/
│ └── variables.wxss # 全局样式变量(通过 @import 引入)
├── images/ # 静态资源(占位说明,图标由用户提供)
└── cloudfunctions/ # 云函数目录
├── quickstart/ # 示例云函数(回显 event)
├── getTodos/ # 服务端查询 todos
├── addTodo/ # 服务端新增 todos(含可选 image 字段)
├── updateTodo/ # 服务端更新 todos
└── removeTodo/ # 服务端删除 todos若用户指定项目根即为小程序根目录,则省去外层
miniprogram/包裹,直接落在目标目录。
文件模板
生成时,将以下模板写入对应路径。{{APPID}} 占位默认填 touristappid(游客模式可直接预览),并提示用户替换为自己的小程序 AppID;{{ENV}} 占位为云开发环境 ID,提示用户在微信开发者工具中开通云开发后填入。
app.json(标准版)
{
"pages": [
"pages/index/index"
],
"window": {
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "小程序",
"backgroundColor": "#f5f5f5",
"backgroundTextStyle": "dark",
"enablePullDownRefresh": false
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}app.json(云开发版,追加字段)
在标准版基础上,于顶层加入:
"cloudfunctionRoot": "cloudfunctions/",app.js(标准版)
App({
globalData: {
// 非云开发场景可放全局配置,如 API 基址
baseURL: ''
},
onLaunch() {
// 应用启动逻辑
}
});app.js(云开发版)
App({
globalData: {
env: '{{ENV}}'
},
onLaunch() {
if (!wx.cloud) {
console.error('当前基础库版本过低,请使用 2.2.3 及以上的基础库以使用云能力');
} else {
wx.cloud.init({
env: this.globalData.env,
traceUser: true
});
}
}
});app.wxss(全局样式)
/* 全局重置与安全区适配 */
page {
--primary-color: #07c160;
--text-color: #1a1a1a;
--bg-color: #f5f5f5;
--border-color: #e5e5e5;
box-sizing: border-box;
font-size: 28rpx;
color: var(--text-color);
background-color: var(--bg-color);
}
view, text, image, button, input, scroll-view {
box-sizing: border-box;
}
/* 通用工具类 */
.flex-row { display: flex; flex-direction: row; align-items: center; }
.flex-col { display: flex; flex-direction: column; }
.flex-center { display: flex; align-items: center; justify-content: center; }
.text-ellipsis {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
/* 底部安全区适配 */
.safe-bottom { padding-bottom: env(safe-area-inset-bottom); }styles/variables.wxss
/* 在页面 wxss 中通过 @import "../../styles/variables.wxss"; 引入 */
page {
--primary: #07c160;
--warning: #ff976a;
--danger: #ee0a24;
--text-main: #1a1a1a;
--text-sub: #888888;
--page-bg: #f5f5f5;
--border-color: #e5e5e5;
--radius: 16rpx;
}project.config.json
{
"description": "项目配置文件",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"urlCheck": true,
"es6": true,
"enhance": true,
"postcss": true,
"minified": true,
"newFeature": true,
"bigPackageSizeSupport": true
},
"compileType": "miniprogram",
"libVersion": "3.0.0",
"appid": "{{APPID}}",
"projectname": "miniprogram-scaffold",
"condition": {}
}sitemap.json
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [
{
"action": "allow",
"page": "*"
}
]
}pages/index/index.wxml(云开发完整版)
<view class="container">
<view class="header">云数据库待办示例</view>
<view class="add-bar">
<input class="add-input" placeholder="输入待办内容" value="{{inputValue}}" bindinput="onInput" />
<button class="add-btn" type="primary" bindtap="addTodo">添加</button>
</view>
<view class="upload-bar">
<button class="up-btn" size="mini" bindtap="chooseAndUpload">上传图片</button>
<view class="preview" wx:if="{{pendingImagePath}}">
<image class="preview-img" src="{{pendingImagePath}}" mode="aspectFill" bindtap="clearPendingImage" />
<text class="preview-tip">点击取消</text>
</view>
</view>
<view class="list">
<view class="item" wx:for="{{list}}" wx:key="_id">
<image wx:if="{{item.image}}" class="item-img" src="{{item.image}}" mode="aspectFill" />
<view class="item-main">
<text class="item-text {{item.done ? 'done' : ''}}" bindtap="toggleTodo" data-id="{{item._id}}" data-done="{{item.done}}">{{item.content}}</text>
</view>
<text class="del" bindtap="removeTodo" data-id="{{item._id}}">删除</text>
</view>
<view class="empty" wx:if="{{list.length === 0}}">暂无数据,添加一条试试</view>
</view>
</view>pages/index/index.wxss(云开发完整版)
@import "../../styles/variables.wxss";
.container {
padding: 32rpx;
min-height: 100vh;
box-sizing: border-box;
}
.header {
font-size: 40rpx;
font-weight: 600;
color: var(--text-main);
margin-bottom: 24rpx;
}
.add-bar {
display: flex;
gap: 16rpx;
margin-bottom: 16rpx;
}
.add-input {
flex: 1;
height: 72rpx;
padding: 0 20rpx;
border: 1rpx solid var(--border-color);
border-radius: 12rpx;
background: #fff;
font-size: 28rpx;
}
.upload-bar {
display: flex;
align-items: center;
gap: 20rpx;
margin-bottom: 24rpx;
}
.up-btn {
font-size: 26rpx;
}
.preview {
display: flex;
align-items: center;
gap: 12rpx;
}
.preview-img {
width: 96rpx;
height: 96rpx;
border-radius: 12rpx;
border: 1rpx solid var(--border-color);
}
.preview-tip {
font-size: 22rpx;
color: var(--text-sub);
}
.list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx 24rpx;
background: #fff;
border-radius: 12rpx;
}
.item-img {
width: 80rpx;
height: 80rpx;
border-radius: 10rpx;
margin-right: 16rpx;
flex-shrink: 0;
}
.item-main {
flex: 1;
min-width: 0;
}
.item-text {
font-size: 28rpx;
color: var(--text-main);
word-break: break-all;
}
.item-text.done {
text-decoration: line-through;
color: var(--text-sub);
}
.del {
color: #ee0a24;
font-size: 26rpx;
margin-left: 24rpx;
}
.empty {
text-align: center;
color: var(--text-sub);
padding: 40rpx 0;
font-size: 26rpx;
}pages/index/index.js(云开发完整版 · 全服务端读写 + 云存储上传)
Page({
data: {
list: [],
inputValue: '',
pendingImage: '', // 已上传到云存储的 fileID,待提交
pendingImagePath: '' // 本地预览路径
},
onLoad() {
this.loadTodos();
},
// 查询(read,服务端:云函数 getTodos)—— 读操作也收口到后端
async loadTodos() {
wx.showLoading({ title: '加载中', mask: true });
try {
const res = await wx.cloud.callFunction({ name: 'getTodos' });
this.setData({ list: (res.result && res.result.data) || [] });
} catch (err) {
console.error('查询失败', err);
wx.showToast({ title: '查询失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
onInput(e) {
this.setData({ inputValue: e.detail.value });
},
// 选择图片并直传云存储(标准做法:文件上传由客户端直传,避免云函数处理大文件)
async chooseAndUpload() {
try {
const media = await wx.chooseMedia({
count: 1,
mediaType: ['image'],
sourceType: ['album', 'camera'],
sizeType: ['compressed']
});
const tempPath = media.tempFiles[0].tempFilePath;
wx.showLoading({ title: '上传中', mask: true });
const ext = (tempPath.match(/.(w+)$/) || [, 'png'])[1];
const cloudPath = `images/${Date.now()}-${Math.floor(Math.random() * 1e6)}.${ext}`;
const upRes = await wx.cloud.uploadFile({ cloudPath, filePath: tempPath });
this.setData({ pendingImage: upRes.fileID, pendingImagePath: tempPath });
wx.showToast({ title: '已上传', icon: 'success' });
} catch (err) {
if (err && err.errMsg && err.errMsg.indexOf('cancel') > -1) return;
console.error('上传失败', err);
wx.showToast({ title: '上传失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
// 取消已选图片
clearPendingImage() {
this.setData({ pendingImage: '', pendingImagePath: '' });
},
// 新增(服务端:云函数 addTodo,可携带 image)
async addTodo() {
const content = this.data.inputValue.trim();
if (!content) return;
wx.showLoading({ title: '提交中', mask: true });
try {
await wx.cloud.callFunction({
name: 'addTodo',
data: {
content,
image: this.data.pendingImage || ''
}
});
this.setData({ inputValue: '', pendingImage: '', pendingImagePath: '' });
this.loadTodos();
} catch (err) {
console.error('新增失败', err);
wx.showToast({ title: '新增失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
// 更新(服务端:云函数 updateTodo)
async toggleTodo(e) {
const { id, done } = e.currentTarget.dataset;
wx.showLoading({ title: '更新中', mask: true });
try {
await wx.cloud.callFunction({
name: 'updateTodo',
data: { id, done: !done }
});
this.loadTodos();
} catch (err) {
console.error('更新失败', err);
wx.showToast({ title: '更新失败', icon: 'none' });
} finally {
wx.hideLoading();
}
},
// 删除(服务端:云函数 removeTodo)
async removeTodo(e) {
const { id } = e.currentTarget.dataset;
wx.showLoading({ title: '删除中', mask: true });
try {
await wx.cloud.callFunction({
name: 'removeTodo',
data: { id }
});
this.loadTodos();
} catch (err) {
console.error('删除失败', err);
wx.showToast({ title: '删除失败', icon: 'none' });
} finally {
wx.hideLoading();
}
}
});pages/index/index.json
{
"usingComponents": {}
}utils/request.js(网络请求封装 · 纯前端接口场景用)
// 统一请求封装:loading 态 + 统一错误处理
const BASE_URL = ''; // 在 app.js 的 globalData 或此处配置后端基址
function request(options) {
const { url, method = 'GET', data = {}, showLoading = true, loadingText = '加载中' } = options;
if (showLoading) {
wx.showLoading({ title: loadingText, mask: true });
}
return new Promise((resolve, reject) => {
wx.request({
url: BASE_URL + url,
method,
data,
header: { 'content-type': 'application/json' },
success: (res) => {
if (res.statusCode >= 200 && res.statusCode < 300) {
resolve(res.data);
} else {
wx.showToast({ title: `请求失败 ${res.statusCode}`, icon: 'none' });
reject(res);
}
},
fail: (err) => {
wx.showToast({ title: '网络异常', icon: 'none' });
reject(err);
},
complete: () => {
if (showLoading) wx.hideLoading();
}
});
});
}
module.exports = {
get: (url, data, opt = {}) => request({ url, method: 'GET', data, ...opt }),
post: (url, data, opt = {}) => request({ url, method: 'POST', data, ...opt })
};utils/util.js(通用工具)
// 通用工具函数
function formatTime(date) {
const d = new Date(date);
const pad = (n) => (n < 10 ? '0' + n : '' + n);
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
}
module.exports = { formatTime };云函数通用 package.json
每个云函数目录放置一份,区别仅在 name:
{
"name": "<函数名>",
"version": "1.0.0",
"main": "index.js",
"dependencies": {
"wx-server-sdk": "~2.6.3"
}
}cloudfunctions/quickstart/index.js(示例 · 回显 event)
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
return {
code: 0,
message: 'hello from cloud function',
data: { event }
};
};cloudfunctions/getTodos/index.js(服务端查询 · 含集合自举)
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
// 服务端查询 todos 集合(读操作也收口到云函数,实现完全后端隔离)
exports.main = async (event, context) => {
const db = cloud.database();
// 自动创建集合(不存在则创建,已存在则忽略),保证首次查询前集合存在
if (typeof db.createCollectionIfNotExists === 'function') {
await db.createCollectionIfNotExists('todos');
} else {
try { await db.createCollection('todos'); } catch (e) { /* 已存在 */ }
}
// 服务端查询默认上限 100 条,按创建时间倒序
const res = await db.collection('todos').orderBy('createTime', 'desc').limit(100).get();
return { code: 0, message: 'ok', data: res.data };
};cloudfunctions/addTodo/index.js(服务端新增 · 含可选 image 与集合自举)
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
const { content, image } = event;
if (!content || !content.trim()) {
return { code: 1, message: 'content 不能为空' };
}
const db = cloud.database();
// 自动创建集合(不存在则创建,已存在则忽略)
if (typeof db.createCollectionIfNotExists === 'function') {
await db.createCollectionIfNotExists('todos');
} else {
try { await db.createCollection('todos'); } catch (e) { /* 已存在 */ }
}
const res = await db.collection('todos').add({
data: {
content: content.trim(),
image: typeof image === 'string' ? image : '',
done: false,
createTime: db.serverDate()
}
});
return { code: 0, message: 'ok', data: res };
};cloudfunctions/updateTodo/index.js(服务端更新 · 含集合自举)
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
const { id, done } = event;
if (!id) {
return { code: 1, message: 'id 不能为空' };
}
const db = cloud.database();
if (typeof db.createCollectionIfNotExists === 'function') {
await db.createCollectionIfNotExists('todos');
} else {
try { await db.createCollection('todos'); } catch (e) { /* 已存在 */ }
}
await db.collection('todos').doc(id).update({
data: { done: !!done }
});
return { code: 0, message: 'ok' };
};cloudfunctions/removeTodo/index.js(服务端删除 · 含集合自举)
const cloud = require('wx-server-sdk');
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV });
exports.main = async (event, context) => {
const { id } = event;
if (!id) {
return { code: 1, message: 'id 不能为空' };
}
const db = cloud.database();
if (typeof db.createCollectionIfNotExists === 'function') {
await db.createCollectionIfNotExists('todos');
} else {
try { await db.createCollection('todos'); } catch (e) { /* 已存在 */ }
}
await db.collection('todos').doc(id).remove();
return { code: 0, message: 'ok' };
};纯前端降级首页(仅在决策树第 1 项选"否"时使用)
当不使用云开发时,首页用客户端直连 wx.cloud.database()(需删去 app.js 云初始化与 cloudfunctionRoot,且不生成 cloudfunctions/):
Page({
data: { list: [], inputValue: '' },
onLoad() { this.loadTodos(); },
async loadTodos() {
try {
const db = wx.cloud.database();
const res = await db.collection('todos').orderBy('createTime', 'desc').get();
this.setData({ list: res.data });
} catch (err) {
console.error('查询失败', err);
wx.showToast({ title: '查询失败', icon: 'none' });
}
},
onInput(e) { this.setData({ inputValue: e.detail.value }); },
async addTodo() {
const content = this.data.inputValue.trim();
if (!content) return;
try {
await wx.cloud.database().collection('todos').add({
data: { content, done: false, createTime: new Date() }
});
this.setData({ inputValue: '' });
this.loadTodos();
} catch (err) {
wx.showToast({ title: '新增失败', icon: 'none' });
}
},
async toggleTodo(e) {
const { id, done } = e.currentTarget.dataset;
await wx.cloud.database().collection('todos').doc(id).update({ data: { done: !done } });
this.loadTodos();
},
async removeTodo(e) {
const { id } = e.currentTarget.dataset;
await wx.cloud.database().collection('todos').doc(id).remove();
this.loadTodos();
}
});纯前端降级版使用客户端直连,受集合客户端权限约束,且仅在已开通云开发但走简化路径时有意义;若完全无云能力,请将
wx.cloud.database()替换为自有后端接口(参考utils/request.js)。
执行步骤(AI 落地顺序)
- 确认目标目录与决策项:按"决策树"与用户对齐(云开发 / TS / tabBar / 是否覆盖)。
- 创建目录结构:
pages/index/、components/、utils/、styles/、images/,云开发版追加cloudfunctions/{quickstart,getTodos,addTodo,updateTodo,removeTodo}/。 - 写入配置文件:
app.json(按版本)、app.js(按版本)、app.wxss、project.config.json、sitemap.json。 - 写入首页与工具:
pages/index/四件套(按版本)、utils/request.js、utils/util.js、styles/variables.wxss。 - 云开发补充:若启用,写入五个云函数(每个含
index.js+package.json)。 - 校验与提示:
- 提示用户将
project.config.json的appid由touristappid替换为真实 AppID。 - 云开发场景提示:在开发者工具中"开通云开发"并填入
env环境 ID;必须右键cloudfunctions/下各目录依次"上传并部署(云端安装依赖)",云函数才会在云端生效。 - 提示
images/图标需用户自备(tabBar 场景)。
- 提示用户将
- 不主动运行:脚手架仅生成文件,不自动调用微信开发者工具 CLI 或上传,除非用户要求。
注意事项与常见坑
- AppID:
touristappid仅支持游客预览,真机调试与发布必须替换为真实 AppID。 - 基础库版本:云能力要求基础库 ≥ 2.2.3;
app.js已做版本判断。 - 云函数必须部署:仅写入本地文件不够,需在开发者工具中右键各云函数目录「上传并部署」。未部署时
wx.cloud.callFunction会报"找不到云函数"。 - 集合自举:各云函数已内置
createCollectionIfNotExists,首次调用自动建集合,无需在控制台手动建表;控制台建表反而可选。 - 文件上传分工:图片等文件必须前端
wx.cloud.uploadFile直传,不要塞进云函数(云函数有超时与内存限制,且难以接收大二进制)。数据库读写才走云函数。 - 服务端查询上限:云函数端
.get()默认上限 100 条,已显式limit(100);超量需分页(skip或游标)。 cloud.DYNAMIC_CURRENT_ENV:云函数内用此常量自动指向当前环境,无需硬编码 env ID。lazyCodeLoading:requiredComponents可缩小启动包,但要求所有页面显式声明usingComponents,默认保留。urlCheck:project.config.json默认开启域名校验,开发期自签名/本地接口可在开发者工具中临时关闭,但上线前需配置合法域名。- rpx 单位:小程序以
rpx为响应式像素(750rpx = 屏宽),模板已统一使用。 - 样式变量:
app.wxss/styles/variables.wxss中定义的 CSS 变量作用于全局page,页面通过@import引入即可使用。
边界
- 本技能只负责"骨架与配置",不替代具体业务逻辑开发,也不替代云函数业务实现(后者可在此基础上扩展)。
- 若用户后续要做登录鉴权、复杂数据库聚合、消息推送等,引导至对应的 cloudbase 子技能或官方文档。