1.2 云对象开发
1. 云对象的基本概念和优势
云对象的概念
云对象(Cloud Object)是 uniCloud 提供的一种新的服务端开发模式,它允许开发者以面向对象的方式编写服务端代码,并在客户端直接调用这些对象的方法。与传统的云函数相比,云对象具有以下特点:
- 面向对象:以对象的形式组织代码,更加符合面向对象的编程思想。
- 方法直接调用:客户端可以直接调用云对象的方法,无需通过
callFunction传递方法名和参数。 - 代码更简洁:减少了传统云函数中的
switch-case或if-else结构,使代码更加简洁。 - 类型提示:支持 JSDoc 注释,提供更好的代码提示和类型检查。
- 自动交互界面:默认提供加载提示和错误处理,减少重复代码。
云对象的优势
- 开发效率更高:减少了样板代码,使开发更加高效。
- 代码更易维护:面向对象的方式使代码结构更清晰,更易于维护。
- 更好的类型支持:通过 JSDoc 注释提供更好的类型提示和检查。
- 更好的错误处理:统一的错误处理机制,减少重复代码。
- 更好的用户体验:默认提供加载提示和错误处理,提升用户体验。
2. 云对象的创建和结构
创建云对象
通过 HBuilderX 创建
- 在 HBuilderX 中,打开项目。
- 在项目的
uniCloud/cloudfunctions目录下,右键点击。 - 选择"新建云函数"。
- 在弹出的对话框中,选择"云对象"类型。
- 输入云对象名称,点击"创建"。
云对象的基本结构
一个典型的云对象包含以下文件:
- index.obj.js:云对象的主入口文件,包含云对象的实现代码。
- package.json:云对象的配置文件,包含依赖项、版本号等信息。
- node_modules:云对象的依赖包目录。
index.obj.js 的基本结构
// 云对象名:todo
module.exports = {
// 添加待办事项
async add(title, content) {
title = title.trim();
content = content.trim();
if (!title || !content) {
return {
errCode: 'INVALID_TODO',
errMsg: 'TODO标题或内容不可为空'
};
}
// 数据库操作
const db = uniCloud.database();
const collection = db.collection('todos');
const result = await collection.add({
title,
content,
createTime: Date.now()
});
return {
errCode: 0,
errMsg: '创建成功',
data: result
};
},
// 获取待办事项列表
async getList() {
const db = uniCloud.database();
const collection = db.collection('todos');
const result = await collection.orderBy('createTime', 'desc').get();
return {
errCode: 0,
errMsg: '获取成功',
data: result.data
};
},
// 更新待办事项
async update(id, title, content) {
const db = uniCloud.database();
const collection = db.collection('todos');
const result = await collection.doc(id).update({
title,
content,
updateTime: Date.now()
});
return {
errCode: 0,
errMsg: '更新成功',
data: result
};
},
// 删除待办事项
async remove(id) {
const db = uniCloud.database();
const collection = db.collection('todos');
const result = await collection.doc(id).remove();
return {
errCode: 0,
errMsg: '删除成功',
data: result
};
}
};
云对象的特殊方法
云对象支持一些特殊的方法,这些方法以 _ 开头,用于处理云对象的生命周期和通用逻辑:
- _before:在调用云对象方法前执行,用于预处理,如参数验证、权限检查等。
- _after:在调用云对象方法后执行,用于后处理,如日志记录、结果转换等。
- _timing:定时执行的方法,用于定时任务。
示例:使用 _before 和 _after 方法
// 云对象名:todo
module.exports = {
// 预处理方法
async _before() {
// 获取客户端信息
const clientInfo = this.getClientInfo();
console.log('客户端信息', clientInfo);
// 获取云端信息
const cloudInfo = this.getCloudInfo();
console.log('云端信息', cloudInfo);
// 获取客户端 token
const token = this.getClientToken();
console.log('客户端 token', token);
// 获取当前调用的方法名
const methodName = this.getMethodName();
console.log('当前调用的方法名', methodName);
// 获取当前参数列表
const params = this.getParams();
console.log('当前参数列表', params);
// 获取当前请求 id
const requestId = this.getRequestId();
console.log('当前请求 id', requestId);
},
// 后处理方法
async _after(result) {
// 记录日志
console.log('方法执行结果', result);
// 返回结果
return result;
},
// 添加待办事项
async add(title, content) {
// 方法实现
},
// 其他方法
};
3. 云对象的调用方式
客户端调用云对象
基本调用
// 导入云对象
const todo = uniCloud.importObject('todo');
// 调用云对象方法
async function addTodo() {
try {
const result = await todo.add('标题', '内容');
if (result.errCode === 0) {
uni.showToast({
title: '创建成功'
});
} else {
uni.showModal({
title: '创建失败',
content: result.errMsg,
showCancel: false
});
}
} catch (e) {
uni.showModal({
title: '创建失败',
content: e.errMsg,
showCancel: false
});
}
}
自定义交互界面
默认情况下,云对象调用会自动显示加载提示和错误处理。如果需要自定义交互界面,可以在导入云对象时传入配置:
// 导入云对象,自定义交互界面
const todo = uniCloud.importObject('todo', {
customUI: false, // 是否取消自动展示的交互提示界面,默认为 false
loadingOptions: { // loading 相关配置
title: '加载中...', // 显示的 loading 内的提示文字
mask: true // 是否使用透明遮罩,配置为 true 时不可点击页面其他内容
},
errorOptions: { // 错误界面相关配置
type: 'modal', // 错误信息展示方式,可取值:modal(弹框)、toast(toast 消息框)
retry: false // 是否展示重试按钮,仅在 type 为 modal 时生效
},
parseSystemError({ // 转化云对象内未捕获的错误,或客户端网络错误
objectName, // 云对象名
methodName, // 方法名
params, // 调用方法时传的参数
errCode, // 请求返回的错误码
errMsg // 请求返回的错误信息
} = {}) {
return {
errMsg: '系统错误,请稍后再试' // 用于展示的错误信息
};
}
});
完全关闭自动交互界面
如果不需要自动交互界面,可以在导入云对象时设置 customUI: true:
// 导入云对象,关闭自动交互界面
const todo = uniCloud.importObject('todo', {
customUI: true // 取消自动展示的交互提示界面
});
云函数或云对象内调用云对象
在云函数或云对象中,可以通过 uniCloud.importObject 方法调用其他云对象:
// 云对象名:user
module.exports = {
async login(username, password) {
// 调用 todo 云对象
const todo = uniCloud.importObject('todo');
const result = await todo.getList();
return {
errCode: 0,
errMsg: '登录成功',
data: {
username,
todos: result.data
}
};
}
};
定时触发云对象
云对象支持定时触发,可以通过配置 _timing 方法实现:
// 云对象名:todo
module.exports = {
// 定时执行的方法
async _timing() {
console.log('定时任务执行', new Date().toISOString());
// 执行定时任务
const db = uniCloud.database();
const collection = db.collection('todos');
const result = await collection.where({
status: 'pending'
}).update({
status: 'completed',
completeTime: Date.now()
});
return {
errCode: 0,
errMsg: '定时任务执行成功',
data: result
};
},
// 其他方法
};
URL 化调用云对象
云对象支持 URL 化,可以通过 HTTP 请求调用云对象的方法:
- 在云对象的
package.json文件中配置 URL 化:
{
"cloudfunction-config": {
"triggers": [
{
"name": "httpTrigger",
"type": "http",
"config": {
"path": "/todo",
"method": "POST",
"auth": "none"
}
}
]
}
}
- 通过 HTTP 请求调用云对象:
// 调用 URL 化的云对象
fetch('https://your-api-endpoint/todo', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
action: 'add',
params: ['标题', '内容']
})
})
.then(response => response.json())
.then(data => {
console.log(data);
})
.catch(error => {
console.error('Error:', error);
});
4. 云对象的高级特性
JSDoc 注释和类型提示
云对象支持 JSDoc 注释,可以提供更好的代码提示和类型检查:
// 云对象名:todo
module.exports = {
/**
* 添加待办事项
* @param {string} title 待办事项标题
* @param {string} content 待办事项内容
* @returns {object} 返回结果
*/
async add(title, content) {
// 方法实现
},
/**
* 获取待办事项列表
* @returns {object} 返回结果
*/
async getList() {
// 方法实现
}
};
云对象中使用 Cookie
在 URL 化场景下,云对象支持使用 Cookie:
// 云对象名:user
module.exports = {
async login(username, password) {
// 设置 Cookie
this.setCookie('username', username, {
maxAge: 7 * 24 * 60 * 60, // 7 天
path: '/'
});
return {
errCode: 0,
errMsg: '登录成功'
};
},
async logout() {
// 删除 Cookie
this.deleteCookie('username');
return {
errCode: 0,
errMsg: '退出成功'
};
}
};
云对象的参数体积限制
不同云服务商对云对象的参数体积有不同的限制:
- 支付宝云:接收参数大小不可超过 6MB
- 阿里云:接收参数大小不可超过 2MB
- 腾讯云:接收参数大小不可超过 5MB
由于传输层还有上下文环境信息,所以开发者给云对象发送参数时需注意控制参数体积。
5. 云对象的最佳实践
云对象与云函数的对比
uniCloud 提供了三种服务器和客户端交互的方式:
- 云函数:传统的函数调用方式,通过
callFunction方法调用。 - 云对象:面向对象的方式,直接调用对象的方法。
- clientDB:直接在前端操作数据库,适用于简单的数据库操作。
从云对象发布后,不再推荐使用传统云函数了。如果是以数据库操作为主,则推荐使用 clientDB,开发效率是最高的。如果服务器端不操作数据库外,或者还有复杂的、不宜公开在前端的逻辑,此时推荐使用云对象。
云对象的命名规范
- 使用小写字母:云对象名称应使用小写字母,如
todo、user、order等。 - 使用有意义的名称:云对象名称应具有描述性,能够表达其功能,如
userService、orderManager等。 - 避免使用特殊字符:云对象名称应避免使用特殊字符,如空格、下划线等。
云对象的方法命名规范
- 使用动词开头:方法名应使用动词开头,如
add、get、update、remove等。 - 使用驼峰命名法:方法名应使用驼峰命名法,如
addTodo、getUserInfo等。 - 避免使用特殊字符:方法名应避免使用特殊字符,如空格、下划线等。
云对象的错误处理
- 统一错误格式:所有方法应返回统一的错误格式,包含
errCode和errMsg。 - 使用有意义的错误码:错误码应具有描述性,能够表达错误的原因,如
INVALID_PARAMETER、NOT_FOUND等。 - 提供详细的错误信息:错误信息应提供详细的描述,帮助开发者理解错误的原因。
云对象的性能优化
- 减少数据库查询:尽量减少数据库查询次数,使用批量操作代替单个操作。
- 使用缓存:对于频繁访问的数据,可以使用缓存减少数据库查询。
- 控制参数体积:注意控制参数体积,避免超过云服务商的限制。
云对象的安全考虑
- 参数验证:对所有输入参数进行验证,避免恶意输入。
- 权限控制:根据用户角色和权限控制方法的访问。
- 敏感数据处理:对于敏感数据,应进行加密处理。
6. 云对象的复杂示例
以下是一个较为复杂的云对象示例,用于实现用户管理功能:
// 云对象名:user
const db = uniCloud.database();
const crypto = require('crypto');
// 工具函数
function md5(text) {
return crypto.createHash('md5').update(text).digest('hex');
}
module.exports = {
// 预处理方法
async _before() {
// 获取客户端信息
const clientInfo = this.getClientInfo();
console.log('客户端信息', clientInfo);
// 获取客户端 token
const token = this.getClientToken();
console.log('客户端 token', token);
},
// 后处理方法
async _after(result) {
// 记录日志
console.log('方法执行结果', result);
// 返回结果
return result;
},
/**
* 用户注册
* @param {string} username 用户名
* @param {string} password 密码
* @param {string} email 邮箱
* @returns {object} 返回结果
*/
async register(username, password, email) {
// 参数验证
if (!username || !password || !email) {
return {
errCode: 'INVALID_PARAMETER',
errMsg: '用户名、密码和邮箱不能为空'
};
}
// 检查用户名是否已存在
const userCollection = db.collection('users');
const existUser = await userCollection.where({
username: username
}).get();
if (existUser.data.length > 0) {
return {
errCode: 'USER_EXISTS',
errMsg: '用户名已存在'
};
}
// 创建用户
const result = await userCollection.add({
username,
password: md5(password),
email,
createTime: Date.now(),
updateTime: Date.now()
});
return {
errCode: 0,
errMsg: '注册成功',
data: {
userId: result.id
}
};
},
/**
* 用户登录
* @param {string} username 用户名
* @param {string} password 密码
* @returns {object} 返回结果
*/
async login(username, password) {
// 参数验证
if (!username || !password) {
return {
errCode: 'INVALID_PARAMETER',
errMsg: '用户名和密码不能为空'
};
}
// 查询用户
const userCollection = db.collection('users');
const user = await userCollection.where({
username: username,
password: md5(password)
}).get();
if (user.data.length === 0) {
return {
errCode: 'INVALID_CREDENTIALS',
errMsg: '用户名或密码错误'
};
}
// 生成 token
const token = md5(username + Date.now());
// 更新用户 token
await userCollection.doc(user.data[0]._id).update({
token,
updateTime: Date.now()
});
// 设置 Cookie
this.setCookie('token', token, {
maxAge: 7 * 24 * 60 * 60, // 7 天
path: '/'
});
return {
errCode: 0,
errMsg: '登录成功',
data: {
userId: user.data[0]._id,
username: user.data[0].username,
token
}
};
},
/**
* 用户退出
* @returns {object} 返回结果
*/
async logout() {
// 获取 token
const token = this.getClientToken();
if (token) {
// 清除用户 token
const userCollection = db.collection('users');
await userCollection.where({
token: token
}).update({
token: '',
updateTime: Date.now()
});
}
// 删除 Cookie
this.deleteCookie('token');
return {
errCode: 0,
errMsg: '退出成功'
};
},
/**
* 获取用户信息
* @returns {object} 返回结果
*/
async getUserInfo() {
// 获取 token
const token = this.getClientToken();
if (!token) {
return {
errCode: 'NOT_LOGIN',
errMsg: '用户未登录'
};
}
// 查询用户
const userCollection = db.collection('users');
const user = await userCollection.where({
token: token
}).get();
if (user.data.length === 0) {
return {
errCode: 'NOT_LOGIN',
errMsg: '用户未登录'
};
}
// 返回用户信息
return {
errCode: 0,
errMsg: '获取成功',
data: {
userId: user.data[0]._id,
username: user.data[0].username,
email: user.data[0].email,
createTime: user.data[0].createTime,
updateTime: user.data[0].updateTime
}
};
},
/**
* 更新用户信息
* @param {string} email 邮箱
* @param {string} oldPassword 旧密码
* @param {string} newPassword 新密码
* @returns {object} 返回结果
*/
async updateUserInfo(email, oldPassword, newPassword) {
// 获取 token
const token = this.getClientToken();
if (!token) {
return {
errCode: 'NOT_LOGIN',
errMsg: '用户未登录'
};
}
// 查询用户
const userCollection = db.collection('users');
const user = await userCollection.where({
token: token
}).get();
if (user.data.length === 0) {
return {
errCode: 'NOT_LOGIN',
errMsg: '用户未登录'
};
}
// 更新用户信息
const updateData = {
updateTime: Date.now()
};
if (email) {
updateData.email = email;
}
if (oldPassword && newPassword) {
// 验证旧密码
if (user.data[0].password !== md5(oldPassword)) {
return {
errCode: 'INVALID_PASSWORD',
errMsg: '旧密码错误'
};
}
// 更新密码
updateData.password = md5(newPassword);
}
// 更新用户
await userCollection.doc(user.data[0]._id).update(updateData);
return {
errCode: 0,
errMsg: '更新成功'
};
}
};
7. 总结
云对象是 uniCloud 提供的一种新的服务端开发模式,它以面向对象的方式组织代码,使开发更加高效、代码更加简洁。通过云对象,开发者可以更加专注于业务逻辑的实现,而不必关心底层的通信细节。
云对象的主要优势包括:
- 开发效率更高:减少了样板代码,使开发更加高效。
- 代码更易维护:面向对象的方式使代码结构更清晰,更易于维护。
- 更好的类型支持:通过 JSDoc 注释提供更好的类型提示和检查。
- 更好的错误处理:统一的错误处理机制,减少重复代码。
- 更好的用户体验:默认提供加载提示和错误处理,提升用户体验。
在实际开发中,应根据业务需求选择合适的交互方式:如果是以数据库操作为主,则推荐使用 clientDB;如果服务器端不操作数据库外,或者还有复杂的、不宜公开在前端的逻辑,此时推荐使用云对象。