uniCloud 云对象开发

99 阅读12分钟

1.2 云对象开发

1. 云对象的基本概念和优势

云对象的概念

云对象(Cloud Object)是 uniCloud 提供的一种新的服务端开发模式,它允许开发者以面向对象的方式编写服务端代码,并在客户端直接调用这些对象的方法。与传统的云函数相比,云对象具有以下特点:

  1. 面向对象:以对象的形式组织代码,更加符合面向对象的编程思想。
  2. 方法直接调用:客户端可以直接调用云对象的方法,无需通过 callFunction 传递方法名和参数。
  3. 代码更简洁:减少了传统云函数中的 switch-caseif-else 结构,使代码更加简洁。
  4. 类型提示:支持 JSDoc 注释,提供更好的代码提示和类型检查。
  5. 自动交互界面:默认提供加载提示和错误处理,减少重复代码。

云对象的优势

  1. 开发效率更高:减少了样板代码,使开发更加高效。
  2. 代码更易维护:面向对象的方式使代码结构更清晰,更易于维护。
  3. 更好的类型支持:通过 JSDoc 注释提供更好的类型提示和检查。
  4. 更好的错误处理:统一的错误处理机制,减少重复代码。
  5. 更好的用户体验:默认提供加载提示和错误处理,提升用户体验。

2. 云对象的创建和结构

创建云对象

通过 HBuilderX 创建
  1. 在 HBuilderX 中,打开项目。
  2. 在项目的 uniCloud/cloudfunctions 目录下,右键点击。
  3. 选择"新建云函数"。
  4. 在弹出的对话框中,选择"云对象"类型。
  5. 输入云对象名称,点击"创建"。
云对象的基本结构

一个典型的云对象包含以下文件:

  1. index.obj.js:云对象的主入口文件,包含云对象的实现代码。
  2. package.json:云对象的配置文件,包含依赖项、版本号等信息。
  3. 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
    };
  }
};

云对象的特殊方法

云对象支持一些特殊的方法,这些方法以 _ 开头,用于处理云对象的生命周期和通用逻辑:

  1. _before:在调用云对象方法前执行,用于预处理,如参数验证、权限检查等。
  2. _after:在调用云对象方法后执行,用于后处理,如日志记录、结果转换等。
  3. _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 请求调用云对象的方法:

  1. 在云对象的 package.json 文件中配置 URL 化:
{
  "cloudfunction-config": {
    "triggers": [
      {
        "name": "httpTrigger",
        "type": "http",
        "config": {
          "path": "/todo",
          "method": "POST",
          "auth": "none"
        }
      }
    ]
  }
}
  1. 通过 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 提供了三种服务器和客户端交互的方式:

  1. 云函数:传统的函数调用方式,通过 callFunction 方法调用。
  2. 云对象:面向对象的方式,直接调用对象的方法。
  3. clientDB:直接在前端操作数据库,适用于简单的数据库操作。

从云对象发布后,不再推荐使用传统云函数了。如果是以数据库操作为主,则推荐使用 clientDB,开发效率是最高的。如果服务器端不操作数据库外,或者还有复杂的、不宜公开在前端的逻辑,此时推荐使用云对象。

云对象的命名规范

  1. 使用小写字母:云对象名称应使用小写字母,如 todouserorder 等。
  2. 使用有意义的名称:云对象名称应具有描述性,能够表达其功能,如 userServiceorderManager 等。
  3. 避免使用特殊字符:云对象名称应避免使用特殊字符,如空格、下划线等。

云对象的方法命名规范

  1. 使用动词开头:方法名应使用动词开头,如 addgetupdateremove 等。
  2. 使用驼峰命名法:方法名应使用驼峰命名法,如 addTodogetUserInfo 等。
  3. 避免使用特殊字符:方法名应避免使用特殊字符,如空格、下划线等。

云对象的错误处理

  1. 统一错误格式:所有方法应返回统一的错误格式,包含 errCodeerrMsg
  2. 使用有意义的错误码:错误码应具有描述性,能够表达错误的原因,如 INVALID_PARAMETERNOT_FOUND 等。
  3. 提供详细的错误信息:错误信息应提供详细的描述,帮助开发者理解错误的原因。

云对象的性能优化

  1. 减少数据库查询:尽量减少数据库查询次数,使用批量操作代替单个操作。
  2. 使用缓存:对于频繁访问的数据,可以使用缓存减少数据库查询。
  3. 控制参数体积:注意控制参数体积,避免超过云服务商的限制。

云对象的安全考虑

  1. 参数验证:对所有输入参数进行验证,避免恶意输入。
  2. 权限控制:根据用户角色和权限控制方法的访问。
  3. 敏感数据处理:对于敏感数据,应进行加密处理。

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 提供的一种新的服务端开发模式,它以面向对象的方式组织代码,使开发更加高效、代码更加简洁。通过云对象,开发者可以更加专注于业务逻辑的实现,而不必关心底层的通信细节。

云对象的主要优势包括:

  1. 开发效率更高:减少了样板代码,使开发更加高效。
  2. 代码更易维护:面向对象的方式使代码结构更清晰,更易于维护。
  3. 更好的类型支持:通过 JSDoc 注释提供更好的类型提示和检查。
  4. 更好的错误处理:统一的错误处理机制,减少重复代码。
  5. 更好的用户体验:默认提供加载提示和错误处理,提升用户体验。

在实际开发中,应根据业务需求选择合适的交互方式:如果是以数据库操作为主,则推荐使用 clientDB;如果服务器端不操作数据库外,或者还有复杂的、不宜公开在前端的逻辑,此时推荐使用云对象。