# 开发者论坛 **Repository Path**: liuzian310011/developer-forum ## Basic Information - **Project Name**: 开发者论坛 - **Description**: 基于HarmonyOS Api9开发的开发者论坛元服务,欢迎各位开发者伙伴的指点 - **Primary Language**: TypeScript - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 0 - **Created**: 2024-01-31 - **Last Updated**: 2024-11-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # HarmonyOS端云一体化开发——开发者论坛元服务 **作者:清明雨揽月 日期:2024年2月4日** ## 一、前言 端云一体化工程的创建大大减少了开发者前后端部署的时间,提高了开发者开发应用服务的效率。DevEco Studio中的端云一体化工程,方便开发者在一个工具中调试端侧和云测代码。云侧提供主流应用平台SDK以及跨平台SDK,方便开发者快速上手;无需搭建服务器,云侧依托云函数、云数据库、云存储等服务,开箱即用、一键部署、免运维;一个开发团队即可完成端云侧开发,且云侧服务按量计费、弹性伸缩,降低了人力/资源成本。 ## 二、前提条件 1、开发工具:DevEco Studio 4.0.0.600 2、API版本:9 3、注册华为开发者账号并通过开发者联盟审核 4、登录AppGallery Connect平台(https://developer.huawei.com/consumer/cn/service/josp/agc/index.html#/)并且创建项目应用(创建应用时的包名与后续在DevEco Studio创建端云一体化工程的包名一致,否则DevEco Studio将查询不到您所创建的应用) 5、打开DevEco Studio并登录您的华为开发者账号 6、选择新建项目(Create Project),进入二级引导页面后在选择您的模板中选择元服务(Automic Service),此时左侧会提供模板,选择云开发([CloudDev]Empty Ability)即可,点击下一步(Next),自行填写应用信息,包括项目名称、包名(与AppGallery Connect中您所创建的项目应用的包名一致)、保存路径、目标SDK(Compile SDK)等等,信息填写完成后点击下一步(Next)即创建好端云一体化项目了 ## 三、项目结构 ### 端侧 ![输入图片说明](ScreenShot/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202024-02-04%20153258.png) main:项目主目录,存放代码以及资源文件 ets:模块配置文件、开发者代码存放目录,建议使用.ets文件编写代码,目前兼容.ts文件 resource:资源文件存放目录 ### 云侧 ![输入图片说明](ScreenShot/%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE%202024-02-04%20164749.png) CloudProgram:云侧项目 clouddb:云侧数据库目录 cloudfunctions:云侧函数目录 ## 四、功能介绍 1、手机号码注册、验证码登录、登出 2、资讯推荐、文章搜索、文章分类、发布文章、删除文章、查看文章 3、点赞、积分、阅读量、评论、等级等个性功能 4、个人信息修改、个人信息查询、上传头像 ## 五、关键代码介绍 ### 端侧 #### 判断是否有用户登录 如果存在登录用户,配置全局变量为true;如果不存在登录用户,则配置全局变量为false ```ts let user: AuthUser = await cloud.auth().getCurrentUser() if (user) { AppStorage.setOrCreate('isLogin', true) } else { AppStorage.setOrCreate('isLogin', false) } ``` #### 申请登录/注册验证码 设置国家码为86,语言为zh_CN,验证码发送间隔时间为60秒,由于requestVerifyCode()函数是一个异步函数,所以您可以在then()和catch()中设置函数执行后的行为 ```ts readonly COUNTRY_CODE: string = '86' readonly LANG: string = 'zh_CN' readonly WITH_IN: number = 60 cloud.auth().requestVerifyCode({ action: VerifyCodeAction.REGISTER_LOGIN, lang: this.LANG, sendInterval: 60, verifyCodeType: { phoneNumber: this.phone, countryCode: this.COUNTRY_CODE, kind: 'phone' } }).then(() => { /* 函数执行成功,无异常 */ }).catch((error) => { /* 函数执行成功,存在并捕获异常,返回error */ }) ``` #### 请求登录 登录后,即当前账号用户认证成功,开发者可获取当前用户UID、手机号码等等,此时,在云数据库中依据从认证账户所获取的UID(唯一标识)查询数据库是否存在该用户信息。如果存在,路由返回;如果不存在,进入注册页面 ```ts cloud.auth().signIn({ credentialInfo: { kind: 'phone', phoneNumber: this.phone, countryCode: this.COUNTRY_CODE, verifyCode: this.verifyCode } }).then(async (user) => { let me = await queryUser(user.getUser().getUid()) if (me) { /* 数据库中存在用户,路由返回 */ router.back() } else { /* 数据库中不存在用户,路由跳转至注册页面 */ router.pushUrl({ url: PATH.PAGE_REGISTER, params: { phone: this.phone, uid: user.getUser().getUid() } }) } }) ``` #### 请求登出 ```ts cloud.auth().signOut().then(() => { router.back() }) ``` #### 添加用户 ```ts export async function insertUser(uid: string, phone: string, name: string, email: string, career: string) { return cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_INSERT_USER, uid: uid, phone: phone, name: name, email: email, career: career } }) } ``` #### 查询用户 由于返回值是一个数组,而我们需要的是具体的一名用户的数据信息,所以我们获取返回数组的第一个元素就行 ```ts export async function queryUser(uid: string) { return (await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, action: CLOUD_ACTION.ACTION_QUERY_USER } })).getValue().data[INDEX.INDEX_0] } ``` #### 修改用户信息 目前仅支持昵称、个人简介修改 ```ts export function updateName(uid: string, name: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, name: name, action: CLOUD_ACTION.ACTION_UPDATE_NAME } }) } export function updateIntroduce(uid: string, introduce: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, introduce: introduce, action: CLOUD_ACTION.ACTION_UPDATE_INTRODUCE } }) } ``` #### 更换用户头像 首先需要在module.json5文件中添加权限信息: name表示权限名称, reason表示申请该权限的原因, usedScence表示使用场景, ability表示使用该权限的ability, when表示何时使用该权限:前台?后台? ```json "requestPermissions": [ { "name": 'ohos.permission.READ_MEDIA', "reason": "$string:UseReadPermissionReason", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } }, { "name": 'ohos.permission.WRITE_MEDIA', "reason": "$string:UseReadPermissionReason", "usedScene": { "abilities": [ "EntryAbility" ], "when": "inuse" } } ] ``` 在module.json5文件中添加好所需权限后,由于我们申请的这两个权限为用户授权权限,所以我们需要在UIAbility中动态申请权限 RequestPermission.ets ```ts import common from '@ohos.app.ability.common'; import abilityAccessCtrl from '@ohos.abilityAccessCtrl'; export function requestPermission(context: common.UIAbilityContext) { let atManager = abilityAccessCtrl.createAtManager() try { atManager.requestPermissionsFromUser(context, ['ohos.permission.READ_MEDIA', 'ohos.permission.WRITE_MEDIA']) .then((data) => { let grantStatus: Array = data.authResults for (let index = 0; index < grantStatus.length; index++) { if (0 === grantStatus[index]) { console.info(`${data.permissions[index]} get permission from user`) } else { console.info(`${data.permissions[index]} have not got permission from user`) } } }) } catch (err) { console.log(`catch err->${JSON.stringify(err)}`); } } ``` EntryAbility.ets ```ts import { requestPermission } from '../utils/RequestPermission' export default class EntryAbility extends UIAbility { /* 其他生命周期函数 */ onWindowStageCreate(windowStage: Window.WindowStage) { requestPermission(this.context) /* 页面加载 */ } } ``` 此时,权限配置以及动态申请开发完毕,接下来需要我们选择本地图片作为头像以及将头像上传到云存储中 选择本地图片作为头像,返回url ```ts import picker from '@ohos.file.picker' export async function selectPortrait(): Promise { let url: string = '' try { let PhotoSelectOptions = new picker.PhotoSelectOptions(); PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE PhotoSelectOptions.maxSelectNumber = 1 let photoPicker = new picker.PhotoViewPicker() let res = await photoPicker.select(PhotoSelectOptions) url = res.photoUris[0] } catch (err) { console.error('PhotoViewPicker failed with err: ' + err) } return url } ``` 上传头像,获取头像在云存储中的地址,更新至用户信息中 ```ts async uploadPortrait(uid: string) { let uri: string = await selectPortrait() let storage = cloud.storage() let cloudPath: string = `${uid}/${String(new Date().valueOf())}.jpg` controller.open() let uploadResult = await storage.upload({ localPath: uri, cloudPath: cloudPath, onUploadProgress: (p: ProgressEvent) => { console.log(`onUploadProgress:bytes:${p.loaded} total:${p.total}`) } }) console.log(`bytesTransferred:${uploadResult.bytesTransferred} totalByteCount:${uploadResult.totalByteCount}`) let downUrl = await storage.getDownloadURL(cloudPath) updatePortrait(uid, downUrl) } ``` ```ts export function updatePortrait(uid: string, portrait: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, portrait: portrait, action: CLOUD_ACTION.ACTION_UPDATE_PORTRAIT } }) } ``` #### 查询文章 ##### 根据分类查询文章 ```ts export async function queryArticleList(type: string) { return (await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_QUERY_ARTICLE, type: type } })).getValue().data } ``` ##### 根据作者UID查询文章 ```ts export async function queryMyArticleList(uid: string) { return (await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_QUERY_ARTICLE_BY_UID, uid: uid } })).getValue().data } ``` ##### 根据搜索内容查询文章 ```ts export async function queryArticleListBySearch(searchContent: string) { return (await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_QUERY_ARTICLE_BY_SEARCH_CONTENT, search_content: searchContent } })).getValue().data } ``` #### 发布文章 ```ts export async function insertArticle(title: string, uid: string, date: string, type: string, main_body: string, ip_address: string) { await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_INSERT_ARTICLE, title: title, uid: uid, date: date, type: type, main_body: main_body, ip_address: ip_address } }) } ``` #### 删除文章 ```ts export async function deleteArticle(id: number) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_DELETE_ARTICLE, id: id } }) } ``` #### 文章评论 ```ts export async function insertDiscuss(uid: string, id: number, date: string, name: string, main_body: string) { await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_INSERT_DISCUSS, id: id, uid: uid, date: date, name: name, main_body: main_body } }) } ``` #### 文章点赞/取消点赞 ##### 点赞 ```ts export function updateUpvote(id: number, uid: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, id: id, action: CLOUD_ACTION.ACTION_UPDATE_UPVOTE } }) } ``` ##### 取消点赞 ```ts export function updateReUpvote(id: number, uid: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, id: id, action: CLOUD_ACTION.ACTION_UPDATE_RE_UPVOTE } }) } ``` #### 文章阅读量增加 ```ts export function updateReading(id: number, uid: string) { cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { uid: uid, id: id, action: CLOUD_ACTION.ACTION_UPDATE_READING } }) } ``` #### 资讯查询 ```ts export async function queryInformation() { return (await cloud.callFunction({ name: FUNCTION_NAME, version: FUNCTION_VERSION, params: { action: CLOUD_ACTION.ACTION_QUERY_INFORMATION } })).getValue().data ``` ### 云侧 #### 云数据库 ##### 云数据库配置介绍 ###### 字段 在云侧,我设置了5个数据对象:article(文章)、discuss(评论)、information(资讯)、news(新闻)、user(用户) 云数据库字段填写介绍,以user(用户)为例 ```json { "belongPrimaryKey": false, "fieldName": "reading", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 } ``` belongPrimaryKey:是否为主键, fieldName:字段名称, fieldType:字段类型, isNeedEncrypt:是否需要加密, notNull:是否为空, defaultValue:默认值,如果不为空,该属性必填 ###### 索引 云数据库对象索引填写介绍,以article(文章)为例 ```json { "indexName": "Uid", "indexList": [ { "fieldName": "uid", "sortType": "DESC" } ] } ``` indexName:索引名称, indexList:索引列表, fieldName:字段名称, sortType:索引顺序:升序?降序? ###### 权限 云数据库对象权限填写介绍 ```json "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] ``` right:权限列表,分为可修改(Upsert)、可读取(Read)、可删除(Delete) role:用户,分为数据对象创建者(Creator)、认证用户(Authenticated)、管理员(Administrator)、所有用户(world) ##### article(文章)数据库 ```json { "fields": [ { "belongPrimaryKey": true, "fieldName": "id", "fieldType": "IntAutoIncrement", "isNeedEncrypt": false, "notNull": true }, { "belongPrimaryKey": false, "fieldName": "uid", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "title", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "date", "fieldType": "Date", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "ip_address", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "main_body", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "type", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "reading", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 }, { "belongPrimaryKey": false, "fieldName": "upvote", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 } ], "indexes": [ { "indexName": "Uid", "indexList": [ { "fieldName": "uid", "sortType": "DESC" } ] }, { "indexName": "Title", "indexList": [ { "fieldName": "title", "sortType": "DESC" } ] }, { "indexName": "Type", "indexList": [ { "fieldName": "type", "sortType": "DESC" } ] } ], "objectTypeName": "article", "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] } ``` ##### discuss(评论)数据库 ```json { "fields": [ { "belongPrimaryKey": true, "fieldName": "id", "fieldType": "IntAutoIncrement", "isNeedEncrypt": false, "notNull": true }, { "belongPrimaryKey": false, "fieldName": "uid", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "article_id", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "portrait", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "date", "fieldType": "Date", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "name", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "main_body", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false } ], "indexes": [ { "indexName": "Uid", "indexList": [ { "fieldName": "uid", "sortType": "DESC" } ] }, { "indexName": "Article_Id", "indexList": [ { "fieldName": "article_id", "sortType": "DESC" } ] } ], "objectTypeName": "discuss", "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] } ``` ##### user(用户)数据库 ```json { "fields": [ { "belongPrimaryKey": true, "fieldName": "uid", "fieldType": "String", "isNeedEncrypt": false, "notNull": true }, { "belongPrimaryKey": false, "fieldName": "name", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "phone", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "email", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "career", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "introduce", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "grade", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 }, { "belongPrimaryKey": false, "fieldName": "portrait", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "integral", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 }, { "belongPrimaryKey": false, "fieldName": "total_upvote", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 }, { "belongPrimaryKey": false, "fieldName": "total_reading", "fieldType": "Integer", "isNeedEncrypt": false, "notNull": true, "defaultValue": 0 } ], "indexes": [ { "indexName": "Uid", "indexList": [ { "fieldName": "uid", "sortType": "DESC" } ] }, { "indexName": "Name", "indexList": [ { "fieldName": "name", "sortType": "DESC" } ] }, { "indexName": "Phone", "indexList": [ { "fieldName": "phone", "sortType": "DESC" } ] } ], "objectTypeName": "user", "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] } ``` ##### information(资讯)数据库 ```json { "fields": [ { "belongPrimaryKey": true, "fieldName": "id", "fieldType": "IntAutoIncrement", "isNeedEncrypt": false, "notNull": true }, { "belongPrimaryKey": false, "fieldName": "title", "fieldType": "String", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "author", "fieldType": "String", "isNeedEncrypt": false, "notNull": true, "defaultValue": "开发者论坛" }, { "belongPrimaryKey": false, "fieldName": "main_body", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "date", "fieldType": "Date", "isNeedEncrypt": false, "notNull": false }, { "belongPrimaryKey": false, "fieldName": "picture", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false } ], "indexes": [ { "indexName": "Id", "indexList": [ { "fieldName": "id", "sortType": "DESC" } ] } ], "objectTypeName": "information", "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] } ``` ##### news(新闻)数据库 ```json { "fields": [ { "belongPrimaryKey": true, "fieldName": "id", "fieldType": "IntAutoIncrement", "isNeedEncrypt": false, "notNull": true }, { "belongPrimaryKey": false, "fieldName": "image", "fieldType": "Text", "isNeedEncrypt": false, "notNull": false } ], "indexes": [ { "indexName": "Id", "indexList": [ { "fieldName": "id", "sortType": "DESC" } ] } ], "objectTypeName": "news", "permissions": [ { "rights": [ "Read" ], "role": "World" }, { "rights": [ "Read", "Upsert" ], "role": "Authenticated" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Creator" }, { "rights": [ "Read", "Upsert", "Delete" ], "role": "Administrator" } ] } ``` #### 云函数 ##### package.json 在包配置文件package.json中添加以下依赖 ```json "dependencies": { "@hw-agconnect/database-server": "^1.0.21" } ``` ##### resource 资源文件,包括控制台导出的云数据库对象文件(.js)和agc-apiclient.json文件 ##### clouDb.ts 由于接收到的数据是string类型,所以我们需要将其转化为对象类型,使用JSON.parse,我们的数据在event.body中,在该文件,使用_body接收端侧调用函数时传递的对象,如果event.body为空,表示用户在DevEco Studio或者控制台中测试云函数,可直接赋值 ```ts const CloudDBFunction = require('./cloudDbUtils') let myHandler = async function (event, context, callback, logger) { const utils = new CloudDBFunction(logger) var _body, action, phone, uid, title, main_body, date, type, ip_address, email, name, introduce, career, id, portrait, article_id, search_content if (event.body) { _body = JSON.parse(event.body) action = _body.action phone = _body.phone uid = _body.uid title = _body.title main_body = _body.main_body date = _body.date type = _body.type ip_address = _body.ip_address email = _body.email name = _body.name introduce = _body.introduce career = _body.career id = _body.id article_id = _body.article_id portrait = _body.portrait search_content = _body.search_content } else { action = event.action phone = event.phone uid = event.uid title = event.title main_body = event.main_body date = event.date type = event.type ip_address = event.ip_address email = event.email name = event.name introduce = event.introduce career = event.career id = event.id article_id = event.article_id portrait = event.portrait search_content = event.search_content } switch (action) { case 'QUERY_USER': utils.queryUser(uid).then((user) => { callback({ code: 200, data: user }) }) break case 'INSERT_USER': utils.insertUser(uid, name, phone, email, career).then(() => { callback({ code: 201 }) }) break case 'QUERY_INFORMATION': utils.queryInformation().then((information) => { callback({ code: 202, data: information }) }) break case 'INSERT_ARTICLE': utils.insertArticle(uid, title, ip_address, date, main_body, type).then(() => { callback({ code: 203 }) }) break case 'QUERY_ARTICLE': utils.queryArticles(type).then((articles) => { callback({ code: 204, data: articles }) }) break case 'UPDATE_UPVOTE': utils.updateUpvote(id, uid).then(() => { callback({ code: 205 }) }) break case 'UPDATE_RE_UPVOTE': utils.unUpdateUpvote(id, uid).then(() => { callback({ code: 206 }) }) break case 'UPDATE_READING': utils.updateReading(id, uid).then(() => { callback({ code: 207 }) }) break case 'INSERT_DISCUSS': utils.insertDiscuss(uid, id, date, name, main_body).then(() => { callback({ code: 208 }) }) break case 'QUERY_DISCUSS': utils.queryDiscuss(article_id).then((discuss) => { callback({ code: 209, data: discuss }) }) break case 'UPDATE_PORTRAIT': utils.updatePortrait(uid, portrait).then((discuss) => { callback({ code: 210, data: discuss }) }) break case 'QUERY_ARTICLE_BY_SEARCH_CONTENT': utils.queryArticlesBySearchContent(search_content).then((articles) => { callback({ code: 211, data: articles }) }) break case 'QUERY_ARTICLE_BY_UID': utils.queryArticlesByUid(uid).then((articles) => { callback({ code: 212, data: articles }) }) break case 'DELETE_ARTICLE': utils.deleteArticle(id).then(() => { callback({ code: 213 }) }) break case 'QUERY_NEWS': utils.queryNews().then((news) => { callback({ code: 214, data: news }) }) break case 'UPDATE_NAME': utils.updateName(uid, name).then((news) => { callback({ code: 214, data: news }) }) break case 'UPDATE_INTRODUCE': utils.updateIntroduce(uid, introduce).then((news) => { callback({ code: 214, data: news }) }) break default: callback({ code: 400, data: _body, desc: "choose error" }) break } } export { myHandler } ``` ##### clouDbUtils.ts ###### 获取必要资源 ```ts const clouddb = require('@hw-agconnect/database-server/dist/index.js') const agconnect = require('@hw-agconnect/common-server') const path = require('path'); const credentialPath = "/resource/agc-apiclient-1341328693062035136-7328025712296950955.json" ``` ###### 云数据库初始化 ```ts constructor(log) { let agcClient try { agcClient = agconnect.AGCClient.getInstance() } catch (error) { agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(path.join(__dirname, credentialPath))); agcClient = agconnect.AGCClient.getInstance() } clouddb.AGConnectCloudDB.initialize(agcClient) const cloudDBZoneConfig = new clouddb.CloudDBZoneConfig(zoneName) const agconnectCloudDB = clouddb.AGConnectCloudDB.getInstance(agcClient) mCloudDBZone = agconnectCloudDB.openCloudDBZone(cloudDBZoneConfig) log.info('Cloud DB init successfully') } ``` ###### 判断存储区是否为空 ```ts if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } ``` ###### 查询用户 ```ts async queryUser(uid: string) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(user).equalTo('uid', uid)) return resp.getSnapshotObjects() } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 查询新闻 ```ts async queryNews() { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(news).orderByDesc("id")) console.info(`info :${JSON.stringify(resp)}`) return resp.getSnapshotObjects() } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 查询资讯 ```ts async queryInformation() { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let info = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(information).orderByDesc("id")) return info.getSnapshotObjects() } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 根据用户UID查询文章 ```ts async queryArticlesByUid(uid: string) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(article).equalTo('uid', uid).orderByDesc("id")) return resp.getSnapshotObjects() } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 根据搜索内容查询文章 ```ts async queryArticlesBySearchContent(searchContent: string) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(article).orderByDesc("id")) const rExp: RegExp = new RegExp(searchContent) let articleList: Array
= resp.getSnapshotObjects() let articleArray: Array
= [] for (let article of articleList) { if (rExp.test(article.getMain_body())) { articleArray.push(article) } } return articleArray } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 根据分类查询文章 ```ts async queryArticles(type: string) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let articleList if ('全部' === type) { articleList = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(article).orderByDesc("id")) } else { articleList = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(article).equalTo('type', type).orderByDesc("id")) } return articleList.getSnapshotObjects() } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 发布文章 ```ts async insertArticle(uid: string, title: string, ip_address: string, date: Date, main_body: string, type: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } try { const article = clouddb.CloudDBZoneGenericObject.build('article'); const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); article.addFieldValue('uid', uid); article.addFieldValue('title', title); article.addFieldValue('date', date); article.addFieldValue('main_body', main_body); article.addFieldValue('ip_address', ip_address); article.addFieldValue('type', type); await mCloudDBZone.executeUpsert(article); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('integral', 40)); } catch (error) { console.warn('upsertArticleInfo =>', error) } } ``` ###### 删除文章 ```ts async deleteArticle(id: number) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { const constraint = clouddb.CloudDBZoneObjectOperatorConstraint.where(article).equalTo('id', id) await mCloudDBZone.executeBatchDeleteByCondition(constraint) } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 用户注册 ```ts async insertUser(uid: string, name: string, phone: string, email: string, career: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } try { const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); user.addFieldValue('name', name); user.addFieldValue('phone', phone); user.addFieldValue('email', email); user.addFieldValue('career', career); await mCloudDBZone.executeUpsert(user); } catch (error) { console.warn('upsertUserInfo =>', error) } } ``` ###### 更新用户信息 ```ts async updateName(uid: string, name: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).update('name', name)); } catch (error) { console.warn('update portrait error=>', error) } } async updateIntroduce(uid: string, introduce: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).update('introduce', introduce)); } catch (error) { console.warn('update portrait error=>', error) } } ``` ###### 更新头像信息 ```ts async updatePortrait(uid: string, portrait: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).update('portrait', portrait)); } catch (error) { console.warn('update portrait error=>', error) } } ``` ###### 发布评论 ```ts async insertDiscuss(uid: string, article_id: number, date: Date, name: string, main_body: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } try { const discuss = clouddb.CloudDBZoneGenericObject.build('discuss'); discuss.addFieldValue('uid', uid); discuss.addFieldValue('name', name); discuss.addFieldValue('article_id', article_id); discuss.addFieldValue('date', date); discuss.addFieldValue('main_body', main_body); await mCloudDBZone.executeUpsert(discuss); } catch (error) { console.warn('upsertUserInfo =>', error) } } ``` ###### 查询评论 ```ts async queryDiscuss(article_id: number) { if (!mCloudDBZone) { console.log("CloudDB Client is null, try re-initialize it"); return; } try { let resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(discuss).equalTo('article_id', article_id)) let discussList: Array = resp.getSnapshotObjects() for (let index = 0; index < discussList.length; index++) { let user: user = (await this.queryUser(discussList[index].uid))[0] discussList[index].setPortrait(user.getPortrait()) } console.info(`${JSON.stringify(discussList)}`) return discussList } catch (error) { console.error(JSON.stringify(error)) } } ``` ###### 点赞 ```ts async updateUpvote(id: number, uid: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const article = clouddb.CloudDBZoneGenericObject.build('article'); const user = clouddb.CloudDBZoneGenericObject.build('user'); article.addFieldValue('id', id, true); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(article).increment('upvote', 1)); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('total_upvote', 1)); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('integral', 20)); } catch (error) { console.warn('upvote article error=>', error) } } ``` ###### 取消点赞 ```ts async unUpdateUpvote(id: number, uid: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const article = clouddb.CloudDBZoneGenericObject.build('article'); const user = clouddb.CloudDBZoneGenericObject.build('user'); article.addFieldValue('id', id, true); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(article).increment('upvote', -1)); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('total_upvote', -1)); } catch (error) { console.warn('upvote article error=>', error) } } ``` ###### 积分 ```ts async updateIntroduce(uid: string, introduce: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const user = clouddb.CloudDBZoneGenericObject.build('user'); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).update('introduce', introduce)); } catch (error) { console.warn('update portrait error=>', error) } } ``` ###### 阅读量 ```ts async updateReading(id: number, uid: string) { if (!mCloudDBZone) { console.log("CloudDBClient is null, try re-initialize it"); return; } const article = clouddb.CloudDBZoneGenericObject.build('article'); const user = clouddb.CloudDBZoneGenericObject.build('user'); article.addFieldValue('id', id, true); user.addFieldValue('uid', uid, true); try { await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(article).increment('reading', 1)); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('total_reading', 1)); await mCloudDBZone.executeUpdate(clouddb.CloudDBZoneObjectOperator.build(user).increment('integral', 10)); } catch (error) { console.warn('upvote article error=>', error) } } ``` ## 六、成果展示 浏览器复制以下链接跳转bilibili观看 https://www.bilibili.com/video/BV1NZ421S7qo/?share_source=copy_web&vd_source=aae8d3254cc2d4b13e1d1c31ba94b4b7 ## 七、仓库地址 https://gitee.com/liuzian310011/developer-forum