舒爾特方格游戲-關(guān)系型數(shù)據(jù)庫版本,請移步到上一篇帖子查看,本篇帖子內(nèi)容還是舒爾特方格游戲,只是換了后宮_云數(shù)據(jù)庫。
此項目是用最新版 DevEco Studio 3.1 Release 并創(chuàng)建端云一體開發(fā),由于目前此版本不支持直接調(diào)用云數(shù)據(jù)庫,不過可以通過云函數(shù)調(diào)用云數(shù)據(jù)庫。
也就是在服務(wù)卡片業(yè)務(wù)邏輯里通過調(diào)用云函數(shù)來完成游戲數(shù)據(jù)保存到云數(shù)據(jù)庫,開發(fā)工具支持本地函數(shù)調(diào)用測試,大大方便了開發(fā)。
此貼重點講解云函數(shù)和云數(shù)據(jù)庫開發(fā),本地和遠端調(diào)用,從而進一步學習 Serverless 知識。
舒爾特方格游戲效果圖如下:
知識點
為豐富 HarmonyOS 對云端開發(fā)的支持、實現(xiàn) HarmonyOS 生態(tài)端云聯(lián)動,DevEco Studio 推出了云開發(fā)功能,開發(fā)者在創(chuàng)建工程時選擇云開發(fā)模板。
即可在 DevEco Studio 內(nèi)同時完成 HarmonyOS 應(yīng)用/服務(wù)的端側(cè)與云側(cè)開發(fā),體驗端云一體化協(xié)同開發(fā)。
相比于傳統(tǒng)開發(fā)模式,云開發(fā)模式具備成本低、效率高、門檻低等優(yōu)勢,具體區(qū)別見下表。
①開發(fā)流程
HarmonyOS 應(yīng)用端云一體化開發(fā)流程如下圖所示:
②創(chuàng)建端云一體化開發(fā)工程
新建原子化服務(wù)工程:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-createproject-0000001443369760-V3#section15198822192610
工程初始化配置:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-createproject-0000001443369760-V3#section1938317533494
端云一體化開發(fā)工程介紹:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-createproject-0000001443369760-V3#section20250910164411
③開發(fā)云工程
開發(fā)云函數(shù):
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-cloudfunctions-0000001493089797-V3
開發(fā)云數(shù)據(jù)庫:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-clouddb-0000001443049860-V3
④部署云工程
部署云工程:
https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/agc-harmonyos-clouddev-deploy-0000001493209765-V3
小結(jié):了解這些端云一體化開發(fā)知識點后,下面圍繞舒爾特方格游戲,在云數(shù)據(jù)庫里設(shè)計卡片表結(jié)構(gòu)和成績表結(jié)構(gòu),然后再編寫相關(guān)云函數(shù),并在本地測試通過后,再測試遠程,最后在元服務(wù)業(yè)務(wù)邏輯調(diào)用云函數(shù)。
云數(shù)據(jù)庫開發(fā)講解
①objecttype 創(chuàng)建
展開 CloudProgram→clouddb→objecttype 右擊 objecttype 目錄,創(chuàng)建→Cloud DB Object Type 輸入 Object Type Name 為 t_form,點擊確認。
修改內(nèi)容如下:
{
"fields":[
{
"isNeedEncrypt":false,
"fieldName":"formId",
"notNull":true,
"belongPrimaryKey":true,
"fieldType":"String"
},
{
"isNeedEncrypt":false,
"fieldName":"formName",
"notNull":true,
"defaultValue":"",
"belongPrimaryKey":false,
"fieldType":"String"
},
{
"isNeedEncrypt":false,
"fieldName":"dimension",
"notNull":true,
"defaultValue":"0",
"belongPrimaryKey":false,
"fieldType":"Integer"
}
],
"indexes":[
{
"indexName":"formId",
"indexList":[{"fieldName":"formId","sortType":"ASC"}]
}
],
"objectTypeName":"t_form",
"permissions":[...]
}
展開 CloudProgram→clouddb→objecttype 右擊 objecttype 目錄,創(chuàng)建→Cloud DB Object Type 輸入 Object Type Name 為 t_score,點擊確認。
修改內(nèi)容如下:
{
"fields":[
{
"isNeedEncrypt":false,
"fieldName":"formId",
"notNull":true,
"belongPrimaryKey":true,
"fieldType":"String"
},
{
"isNeedEncrypt":false,
"fieldName":"matrixNum",
"notNull":true,
"defaultValue":"",
"belongPrimaryKey":false,
"fieldType":"String"
},
{
"isNeedEncrypt":false,
"fieldName":"bestScore",
"notNull":true,
"defaultValue":"0",
"belongPrimaryKey":false,
"fieldType":"Double"
}
],
"indexes":[
{
"indexName":"formId",
"indexList":[{"fieldName":"formId","sortType":"ASC"}]
}
],
"objectTypeName":"t_score",
"permissions":[...]
}
②dataentry 創(chuàng)建
展開 CloudProgram→clouddb→dataentry 右擊 dataentry 目錄,創(chuàng)建→Cloud DB Data Entry 這里先選擇上面創(chuàng)建的 Object Type 為 t_form,再輸入 Data Entry Name 為 form_data,點擊確認。修改內(nèi)容如下:
{
"cloudDBZoneName":"widgetCard",
"objectTypeName":"t_form",
"objects":[
{
"formId":"x000001",
"formName":"卡片1",
"dimension":2
}
]
}
展開 CloudProgram→clouddb→dataentry 右擊 dataentry 目錄,創(chuàng)建→Cloud DB Data Entry 這里先選擇上面創(chuàng)建的 Object Type為t_score,再輸入 Data Entry Name 為 score_data,點擊確認。修改內(nèi)容如下:
{
"cloudDBZoneName":"widgetCard",
"objectTypeName":"t_score",
"objects":[
{
"formId":"x000001",
"matrixNum":"3x3",
"bestScore":2.234
}
]
}
小結(jié):其實 dataentry 文件可以不創(chuàng)建,這里對兩個表都初始化了一條數(shù)據(jù),是方便下面的調(diào)用使用,云數(shù)據(jù)庫就是定義好表結(jié)構(gòu)、權(quán)限配置就可以,數(shù)據(jù)的添加、修改、刪除、查詢都可以通過云函數(shù)來完成。
云函數(shù)開發(fā)講解
①卡片云函數(shù)創(chuàng)建
展開 CloudProgram→cloudfunctions 右擊 cloudfunctions 目錄,創(chuàng)建→Cloud Function 輸入 Cloud Function Name 為 form-func,點擊確認。卡片云函數(shù)里包含了增刪改查操作,所以在 form-func 下,創(chuàng)建不同的文件夾來區(qū)分,目錄結(jié)構(gòu)如下:

如卡片實例體類:
classt_form{
getFieldTypeMap(){
letfieldTypeMap=newMap();
fieldTypeMap.set('formId','String');
fieldTypeMap.set('formName','String');
fieldTypeMap.set('dimension','Integer');
returnfieldTypeMap;
}
getClassName(){
return't_form';
}
getPrimaryKeyList(){
letprimaryKeyList=[];
primaryKeyList.push('formId');
returnprimaryKeyList;
}
getIndexList(){
letindexList=[];
returnindexList;
}
getEncryptedFieldList(){
letencryptedFieldList=[];
returnencryptedFieldList;
}
//setandget
setFormId(formId){this.formId=formId;}
getFormId(){returnthis.formId;}
setFormName(formName){this.formName=formName;}
getFormName(){returnthis.formName;}
setDimension(dimension){this.dimension=dimension;}
getDimension(){returnthis.dimension;}
}
module.exports={t_form}
CloudDBZoneWrapper 操作云數(shù)據(jù)庫,這里主要列舉構(gòu)造函數(shù)和增加方法內(nèi)容:
import*asclouddbfrom'@agconnect/database-server';
import{t_formasFormBean}from'./models/t_form';
import*asagconnectfrom'@agconnect/common-server';
constZONE_NAME="widgetCard";
exportclassCloudDBZoneWrapper{
logger;
cloudDbZone;
constructor(credential,logger){
this.logger=logger;
try{
//初始化AGCClient
letagcClient;
try{
agcClient=agconnect.AGCClient.getInstance();
}catch{
agconnect.AGCClient.initialize(credential);
agcClient=agconnect.AGCClient.getInstance();
}
//初始化AGConnectCloudDB實例
letcloudDbInstance;
try{
cloudDbInstance=clouddb.AGConnectCloudDB.getInstance(agcClient);
}catch{
clouddb.AGConnectCloudDB.initialize(agcClient);
cloudDbInstance=clouddb.AGConnectCloudDB.getInstance(agcClient);
}
//創(chuàng)建CloudDBZoneConfig配置對象,并設(shè)置云側(cè)CloudDBzone名稱,打開CloudDBzone實例
constcloudDBZoneConfig=newclouddb.CloudDBZoneConfig(ZONE_NAME);
this.cloudDbZone=cloudDbInstance.openCloudDBZone(cloudDBZoneConfig);
}catch(err){
logger.error("xx[form-func]CloudDBZoneWrapperinitCloudDBZoneWrappererror:"+err);
}
}
asyncinsert(addForm){
if(!this.cloudDbZone){
this.logger.error("xx[form-func]CloudDBZoneWrapper->insertCloudDBClientisnull,tryre-initializeit");
}
try{
letres=awaitthis.cloudDbZone.executeUpsert(addForm);
this.logger.info("xx[form-func]CloudDBZoneWrapper->insertInsert"+res+"recordssuccess");
}catch(error){
this.logger.error("xx[form-func]CloudDBZoneWrapper->insertexecuteInsertaddressRecordsfailed"+error);
}
}
}
新增卡片函數(shù) form-insert,關(guān)鍵代碼如下:
import{CloudDBZoneWrapper}from'../clouddb/CloudDBZoneWrapper.js';
import*asUtilsfrom'../utils/Utils.js';
exportconstmyHandler=asyncfunction(event,context,callback,logger){
constcredential=Utils.getCredential(context,logger);
try{
constcloudDBZoneWrapper=newCloudDBZoneWrapper(credential,logger);
letformObj=cloudDBZoneWrapper.getForm(event);
awaitcloudDBZoneWrapper.insert(formObj);
callback({
ret:{code:0,desc:"SUCCESS"},
});
}catch(err){
logger.error("xx[form-func]insertfuncerror:"+err.message+"stack:"+err.stack);
callback({
ret:{code:-1,desc:"ERROR"},
});
}
};
卡片云函數(shù)主入口,關(guān)鍵代碼如下:
letmyHandler=asyncfunction(event,context,callback,logger){
letoperation;
letparams;
logger.info("xxenterformfuncwithoperation"+event.operation);
operation=event.body?JSON.parse(event.body).operation:event.operation;
params=event.body?JSON.parse(event.body).params:event.params;
switch(operation){
case"query":
query.myHandler(params,context,callback,logger);
break;
case"queryById":
queryById.myHandler(params,context,callback,logger);
break;
case"insert":
insert.myHandler(params,context,callback,logger);
break;
case"update":
update.myHandler(params,context,callback,logger);
break;
case"delete":
deleteByObj.myHandler(params,context,callback,logger);
break;
default:
callback({
ret:{code:-1,desc:"nosuchfunction"},
});
}
};
module.exports.myHandler=myHandler;
②成績云函數(shù)創(chuàng)建
展開 CloudProgram→cloudfunctions 右擊 cloudfunctions 目錄,創(chuàng)建→Cloud Function 輸入 Cloud Function Name 為 score-func,點擊確認,。成績云函數(shù)里包含了增刪改查操作,所以在 score-func 下,創(chuàng)建不同的文件夾來區(qū)分。目錄結(jié)構(gòu)如下:

云函數(shù)本地與遠程調(diào)試
①Run 模式啟動調(diào)試
右擊“cloudfunctions”目錄,選擇“Run Cloud Functions”。查看“Run”面板。如果出現(xiàn)“Cloud Functions loaded successfully”,表示所有函數(shù)已成功加載到本地運行的 HTTP Server 中,并生成對應(yīng)的 POST URL。在菜單欄選擇“Tools > CloudDev > Cloud Functions Requestor”,使用 Cloud Functions Requestor 觸發(fā)函數(shù)調(diào)用。在彈出的“Cloud Functions Requestor”面板,填寫觸發(fā)事件參數(shù)。點擊“Save”,可保存當前觸發(fā)事件。②Debug 模式啟動調(diào)試
右擊“cloudfunctions”目錄,選擇“Run Cloud Functions”。查看 Console 面板。如果出現(xiàn)“Cloud Functions loaded successfully”,表示函數(shù)成功加載到本地運行的 HTTP Server 中,并生成對應(yīng)的 POST URL。如需設(shè)置斷點調(diào)試,在函數(shù)代碼中選定要設(shè)置斷點的有效代碼行,在行號后單擊鼠標左鍵設(shè)置斷點,設(shè)置斷點后,調(diào)試能夠在斷點處中斷,并高亮顯示該行。在菜單欄選擇“Tools > CloudDev > Cloud Functions Requestor”,使用 Cloud Functions Requestor 觸發(fā)函數(shù)調(diào)用。在彈出的“Cloud Functions Requestor”面板,填寫觸發(fā)事件參數(shù)。點擊“Save”,可保存當前觸發(fā)事件。③自定義 Run/Debug 配置
在菜單欄選擇“Run > Edit Configurations”。在“Run/Debug Configurations”窗口,點擊+,選擇“Cloud Functions”,新增一個 Run/Debug 配置。自定義 Run/Debug 配置,完成后點擊“OK”。
· Name:Run/Debug配置的名稱,如“functions-custom1”。
· Server Port:HTTP服務(wù)端監(jiān)聽端口。默認為“18090”,自定義端口號建議大于1024。勾選“Autoincrement”表示如當前端口被占用則端口號自動加“1”。
· Environment variables:函數(shù)運行的環(huán)境變量,為key-value形式。
點擊“Edit environment variables”按鈕,在“Environment Variables”彈窗中點擊“+”添加一個環(huán)境變量,然后點擊“OK”。添加成功后,您便可以將變量配置信息傳入到函數(shù)執(zhí)行環(huán)境中,用于函數(shù)運行時讀取。
選擇剛剛自定義的 Run/Debug 配置,分別點擊 Run 或 Debug。后續(xù)調(diào)試步驟與默認配置下的調(diào)試步驟一致,請分別參見Run 模式啟動調(diào)試或Debug 模式啟動調(diào)試。④測試
實現(xiàn)云函數(shù)調(diào)用云數(shù)據(jù)庫,需要您先部署云工程,云端才會有相關(guān)數(shù)據(jù)及環(huán)境變量。同時,云函數(shù)為訪問云數(shù)據(jù)庫使用了“PROJECT_CREDENTIAL”環(huán)境變量,部署函數(shù)到 AGC 云端時,云端會自動配置好“PROJECT_CREDENTIAL”以運行環(huán)境變量。但在本地調(diào)試函數(shù)時,需要您手動將“PROJECT_CREDENTIAL”環(huán)境變量添加到 Run/Debug 配置中。否則,函數(shù)調(diào)試代碼執(zhí)行會因獲取不到“PROJECT_CREDENTIAL”環(huán)境變量而中斷。
從 AGC 獲取的“PROJECT_CREDENTIAL”環(huán)境變量添加到調(diào)試配置中。您也可以添加您需要的其他環(huán)境變量。

添加完環(huán)境變量后,啟動函數(shù),再點擊 Trigger,就可以看到成功返回數(shù)據(jù)了。
代碼講解
①云函數(shù)調(diào)用公共類
DatabaseUtils.ets 云函數(shù)操作類部分代碼如下:
exportclassDatabaseUtils{
asynccallWithParams(context,trigger,operation,params){
awaitgetAGConnect(context);
letbody={
"operation":operation,
"params":params
}
try{
letfunctionCallable=agconnect.function().wrap(trigger);
letfunctionResult=awaitfunctionCallable.call(body);
returnfunctionResult.getValue();
//returnfunctionResult.getValue().result;
}
catch(err){
return{
"ret":{"code":-1,"desc":"ERROR"}
}
}
}
asyncinvoke(context:any,trigger?:string,operation?:string,params?:object){
console.info(CommonConstants.DATABASE_TAG,'xxinvokeparams:'+JSON.stringify(params))
returnawaitthis.callWithParams(context,trigger,operation,params);
}
/**
*插入卡片數(shù)據(jù)。
*
*@param{Form}Form表單實體。
*@param{DataRdb.RdbStore}RDB存儲RDB數(shù)據(jù)庫。
*@return返回操作信息。
*/
asyncinsertForm(context:any,form:Form){
letres=awaitthis.invoke(context,Triggers.FormFunc,RequestType.Insert,form);
console.info(CommonConstants.DATABASE_TAG,'xxinsertFormresult:'+JSON.stringify(res));
}
......
}
②卡片 Ability 調(diào)用公共類
EntryFormAbility.ets 卡片生命周期代碼如下:
onAddForm(want){
//獲取卡片ID:ohos.extra.param.key.form_identity
letformId:string=want.parameters[CommonConstants.FORM_PARAM_IDENTITY_KEY]asstring;
//獲取卡片名稱:ohos.extra.param.key.form_name
letformName:string=want.parameters[CommonConstants.FORM_PARAM_NAME_KEY]asstring;
//獲取卡片規(guī)格:ohos.extra.param.key.form_dimension
letdimensionFlag:number=want.parameters[CommonConstants.FORM_PARAM_DIMENSION_KEY]asnumber;
//卡片信息
letform:Form=newForm();
form.formId=formId;
form.formName=formName;
form.dimension=dimensionFlag;
//保存卡片信息到數(shù)據(jù)庫
DatabaseUtils.insertForm(this.context,form);
//獲取最優(yōu)成績
getScoreById(this.context,dimensionFlag,formId);
//每五分鐘刷新一次
formProvider.setFormNextRefreshTime(formId,CommonConstants.FORM_NEXT_REFRESH_TIME,(error,data)=>{
if(error){
console.error(CommonConstants.ENTRY_FORM_ABILITY_TAG,'xx onAddForm 更新卡片失敗:'+JSON.stringify(error))
}else{
console.info(CommonConstants.ENTRY_FORM_ABILITY_TAG,'xxonAddForm更新卡片成功')
}
});
//返回初始化卡片數(shù)據(jù)
letformData:FormData=newFormData();
formData.formId=formId;
formData.bestScore=0;
formData.matrixNum='1x1';
formData.totalBestScore=0;
returnformBindingData.createFormBindingData(formData);
}
③主界面調(diào)用公共類
@Entry
@Component
structIndex{
@StatescoreDataList:Array=[]
aboutToAppear(){
//請求通知欄權(quán)限
this.requestNotification();
//更新卡片信息
DatabaseUtils.updateForms(getContext(this));
//獲取成績歷史記錄
this.getScoreListData()
}
onPageShow(){
//更新卡片信息
DatabaseUtils.updateForms(getContext(this));
//獲取成績歷史記錄
this.getScoreListData()
}
//獲取成績歷史數(shù)據(jù)
getScoreListData(){
DatabaseUtils.getScoreListData(getContext(this))
.then((res)=>{
this.scoreDataList=res;
//發(fā)送通知
NotificationUtils.sendNotifications(this.scoreDataList[0].totalBestScore);
}).catch((error)=>{
console.error(CommonConstants.MAIN_PAGE_TAG,'xxaboutToAppearoronPageShowgetScoreListDataerror'+JSON.stringify(error));
});
}
build(){...}
}
總結(jié)
通過把之前小游戲元服務(wù)-關(guān)系型數(shù)據(jù)庫修改為使用 Serverless 云函數(shù)、云數(shù)據(jù)庫,學習到不少知識,開始時不懂得怎么使用云函數(shù)調(diào)用云數(shù)據(jù)庫,一邊參考官方商城模板,一邊測試,到使用到這個小游戲上,。總結(jié)這個項目用到以下知識點:- 使用 notification 發(fā)布通知。
- 使用端云一體化開發(fā)、開發(fā)云函數(shù)、開發(fā)云數(shù)據(jù)庫。
- 使用 FormExtensionAbility 創(chuàng)建、更新、刪除元服務(wù)卡片。
備注:資源文件是我在學習云函數(shù)調(diào)用云數(shù)據(jù)庫寫的一個簡單實例,有云函數(shù)調(diào)用云數(shù)據(jù)庫需求的小伙伴可以下載下來參考一下。
-
數(shù)據(jù)庫
+關(guān)注
關(guān)注
7文章
3868瀏覽量
65004 -
模板
+關(guān)注
關(guān)注
0文章
108瀏覽量
20667 -
HarmonyOS
+關(guān)注
關(guān)注
79文章
1987瀏覽量
31070
原文標題:HarmonyOS云開發(fā):舒爾特方格游戲
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論