当前位置:   article > 正文

#端云一体化#SHOW出您的元服务#端云一体化开发诗词卡片_deveco next 端云一体化项目 云服务怎么配置

deveco next 端云一体化项目 云服务怎么配置

效果展示

创意来源

古诗词是中华传统文化的瑰宝,古人借诗词来言情、咏志、抒怀,古诗词记录了古人对于自然、社会、人文的观察与思考,也充满了真挚而多样的情感,有“每逢佳节倍思亲”的感慨孤独,有“但愿人长久,千里共婵娟”的真挚祝福,也有“爆竹声中一岁除,春风送暖入屠苏”的对于辞旧迎新的喜悦。每到季节更替、时节轮转、节日来临,总能唤起诗人对于人生的思考,造化的感悟,于是,诗意充盈在每一个平凡的日子之中,并融于我们的生活。而云服务和服务卡片又特别适合直观的展示诗词,于是有了根据节日或是节气,推荐展示符合主题的诗词的想法。

关于云服务和端云一体化的个人理解

云服务,顾名思义,“元”代表着轻量级和原子化,其具有免安装的特性,服务卡片是其重要的表现形式,既具有信息展示的功能,有可以作为应用的入口,一触即达,简洁直观。

端云一体化,传统的开发模式基本是前后端分离的,前端负责UI的开发,后端负责数据业务和接口的编写,这种模式虽然有很多好处,但也存在着沟通成本高,前后端技术栈差异大,学习成本高等缺点,且后端一般需要部署到服务器上,还需要根据需求对服务器进行运维或者扩容,成本较高,较为繁琐,对于一般小型应用显然不太友好。而端云一体化较好的解决了这些痛点,将华为的serverless和元服务结合起来,使用同一IDE同时开发前后端,使用同一种语言typescript开发前后端,既节约了开发者的学习成本,也降低了开发难度,可以让开发者更专注于应用本身,而不是部署运维等事务。

开发步骤

参考官方给出的文档,和个人的摸索。

1. 在Deveco Studio中新建项目,选择元服务端云一体化模版,点击next

2. 配置项目,填写项目名称,包名,点击next

3. 关联云端项目,如果没有创建,则创建云项目

 

4. 查看项目结构

  

5. 开发第一个云函数,参考官网文档

根据业务需求,我将需求拆分成了两个云函数,一个云函数的功能是根据今天的日期,查询今天是否是传统节日,或是节气,确定今天的诗词主题。另一个云函数的公司根据传入的诗词主题,查询云数据库中的诗词数据并返回。

首先是获取今日的诗词主题,为了获取今天的节日和节气数据,我引入了第三方库lunar-javascript,文档链接https://6tail.cn/calendar/api.html#lunar.jieqi.html,添加方式是在云函数get-theme的目录下的package.json里添加依赖  云函数get-theme代码如下:

  1. let isFestival: boolean = false;
  2. let isJieQi: boolean = false;
  3. let festivals=[]
  4. let jieQi=""
  5. // do something here
  6. const d = Lunar.fromDate(new Date())
  7. //获取物候名和候次
  8. const wuhou = d.getWuHou()
  9. const hou = d.getHou()
  10. let tag = ""
  11. let lunar=d.toString()
  12. let prev = d.getPrevJieQi();
  13. let next = d.getNextJieQi();
  14. //获取今天是否是节日
  15. let l = d.getFestivals();
  16. if (l.length > 0) {
  17. tag = l[0]
  18. festivals=l
  19. isFestival = true
  20. } else {
  21. jieQi = d.getJieQi();
  22. if (jieQi) {
  23. tag = jieQi
  24. isJieQi = true
  25. } else {
  26. //如果不是节气,获取上一个节气
  27. const prev_name = d.getPrevJieQi().getName();
  28. tag = prev_name
  29. }
  30. }
  31. logger.info("tag:" + tag)
  32. logger.info(event, context)
  33. callback({
  34. isFestival,
  35. festivals,
  36. isJieQi,
  37. jieQi,
  38. prev:{name:prev.getName(),time:prev.getSolar().toYmdHms()},
  39. next:{name:next.getName(),time:next.getSolar().toYmdHms()},
  40. hou,
  41. wuhou,
  42. tag,
  43. lunar,
  44. });
  45. };
  46. export { myHandler };
复制

部署云函数

可以通过IDE或是浏览器控制台调试云函数

6. 开发第二个云函数,创建数据对象并导入数据

云函数get-potery的功能是根据传入的主题在云数据库中查询符合条件的数据,那么我们需要先创建数据对象,导入数据,这一部分工作可以在IDE里通过编写配置文件完成,也可以通过浏览器控制台完成,推荐后者,因为操作起来比较直观。

感觉数据对象比较类似于关系型数据库定义表结构   

创建数据区,注意不同数据区是相互隔离的,因此一定要注意数据查询和导入时,一定要区分数据区 

导入数据 

导入的Json文件格式大致如下  导入成功后可以看到  数据对象和数据导入完成后,可以开始编写第二个函数,既根据传入的主题查询云数据库。云函数调用云数据库参考官方文档https://developer.huawei.com/consumer/cn/doc/development/AppGallery-connect-Guides/query-clouddb-overview-0000001549142354,文档写的比较详细,因此不再赘述。 云函数结构如下 CloudDBZoneWrapper.js中代码如下

  1. const clouddb = require('@hw-agconnect/database-server/dist/index.js');
  2. const agconnect = require('@agconnect/common-server');
  3. const path = require('path');
  4. const shiju=require('./model/shiju')
  5. /*
  6. 配置区域
  7. */
  8. //TODO 将AGC官网下载的配置文件放入resources文件夹下并将文件名替换为真实文件名
  9. const credentialPath = "/resources/agc-apiclient-1255177787686308800-7284180544987715810.json";
  10. // 修改为在管理台创建的存储区名称
  11. let zoneName = "MyData"
  12. // 修改为需要操作的对象
  13. let objectName =shiju.shiju;
  14. let logger
  15. let mCloudDBZone
  16. class CloudDBZoneWrapper {
  17. // AGC & 数据库初始化
  18. constructor(log) {
  19. logger = log;
  20. let agcClient;
  21. try {
  22. agcClient = agconnect.AGCClient.getInstance();
  23. } catch (error) {
  24. agconnect.AGCClient.initialize(agconnect.CredentialParser.toCredential(path.join(__dirname, credentialPath)));
  25. agcClient = agconnect.AGCClient.getInstance();
  26. }
  27. clouddb.AGConnectCloudDB.initialize(agcClient);
  28. const cloudDBZoneConfig = new clouddb.CloudDBZoneConfig(zoneName);
  29. const agconnectCloudDB = clouddb.AGConnectCloudDB.getInstance(agcClient);
  30. mCloudDBZone = agconnectCloudDB.openCloudDBZone(cloudDBZoneConfig);
  31. }
  32. async queryAll() {
  33. if (!mCloudDBZone) {
  34. logger.info("CloudDBClient is null, try re-initialize it");
  35. console.log("CloudDBClient is null, try re-initialize it")
  36. return;
  37. }
  38. try {
  39. const resp = await mCloudDBZone.executeQuery(clouddb.CloudDBZoneQuery.where(objectName));
  40. return resp
  41. } catch (error) {
  42. logger.info('queryAll=>', error);
  43. console.log(error)
  44. }
  45. }
  46. async queryCompound(data) {
  47. if (!mCloudDBZone) {
  48. logger.info("CloudDBClient is null, try re-initialize it");
  49. console.log("CloudDBClient is null, try re-initialize it")
  50. return;
  51. }
  52. try {
  53. // 根据业务需要修改查询条件
  54. const cloudDBZoneQuery = this.setQuery(data);
  55. const resp = await mCloudDBZone.executeQuery(cloudDBZoneQuery);
  56. return resp.getSnapshotObjects()
  57. } catch (error) {
  58. logger.info('queryCompound=>', error);
  59. console.log(error)
  60. }
  61. }
  62. setQuery(data) {
  63. const cloudDBZoneQuery = clouddb.CloudDBZoneQuery.where(objectName);
  64. if("contains" in data) {
  65. let contains = data.contains;
  66. for (var key in contains) {
  67. cloudDBZoneQuery.contains(key, contains[key]);
  68. }
  69. }
  70. return cloudDBZoneQuery.limit(50);
  71. }
  72. }
  73. module.exports = CloudDBZoneWrapper;
复制

get-potert.ts中代码如下

  1. const CloudDBZoneWrapper = require("./CloudDBZoneWrapper.js");
  2. module.exports.myHandler = async function(event, context, callback, logger) {
  3. logger.info(event);
  4. const cloudDBZoneWrapper = new CloudDBZoneWrapper(logger);
  5. let tag="冬至"
  6. if(event.body){
  7. //接收传入的参数
  8. tag=JSON.parse(event.body)['tag']
  9. }
  10. logger.info("tag="+tag)
  11. let queryResult;
  12. queryResult = await cloudDBZoneWrapper.queryCompound({
  13. "contains": {
  14. "theme": tag
  15. }
  16. });
  17. //返回查询的结果
  18. callback({ theme:tag,poem:queryResult});
  19. };
复制

返回的结果如下

  1. {
  2. "poem":[
  3. {
  4. "author":"白居易",
  5. "theme":"夏至",
  6. "id":834,
  7. "title":"和梦得夏至忆苏州呈卢宾客",
  8. "content":"粽香筒竹嫩,炙脆子鹅鲜。"
  9. },
  10. {
  11. "author":"白玉蟾",
  12. "theme":"夏至",
  13. "id":835,
  14. "title":"赠潘高士二首·冬至炼朱砂",
  15. "content":"冬至炼朱砂,夏至炼水银。"
  16. }
  17. ],
  18. "theme":"夏至"
  19. }
复制

云端的开发大致完毕,将云函数部署到云端并调试完成后,进入端侧的开发

7. 服务卡片的开发

在“src/main/ets/widget/pages/WidgetCard.ets”文件中编写界面UI代码 代码如下:

  1. @Entry
  2. @Component
  3. struct WidgetCard {
  4. @LocalStorageProp('title') title:string="水调歌头";
  5. @LocalStorageProp('author') author:string="苏轼";
  6. @LocalStorageProp('content') content:string="但愿人长久,千里共婵娟";
  7. /*
  8. * The max lines.
  9. */
  10. readonly MAX_LINES: number = 2;
  11. /*
  12. * The action type.
  13. */
  14. readonly ACTION_TYPE: string = 'router';
  15. /*
  16. * The message.
  17. */
  18. readonly MESSAGE: string = 'add detail';
  19. /*
  20. * The ability name.
  21. */
  22. readonly ABILITY_NAME: string = 'EntryAbility';
  23. /*
  24. * The with percentage setting.
  25. */
  26. readonly FULL_WIDTH_PERCENT: string = '100%';
  27. /*
  28. * The height percentage setting.
  29. */
  30. readonly FULL_HEIGHT_PERCENT: string = '100%';
  31. build() {
  32. Stack() {
  33. Image($r("app.media.card_bg"))
  34. .width(this.FULL_WIDTH_PERCENT)
  35. .height(this.FULL_HEIGHT_PERCENT)
  36. .objectFit(ImageFit.Cover)
  37. Column() {
  38. Text(this.content)
  39. .fontSize($r('app.float.title_immersive_font_size'))
  40. .textOverflow({ overflow: TextOverflow.Ellipsis })
  41. .fontColor($r('app.color.text_font_color'))
  42. .maxLines(this.MAX_LINES)
  43. Text(`——${this.author} 《${this.title}》`)
  44. .fontSize($r('app.float.detail_immersive_font_size'))
  45. .opacity($r('app.float.detail_immersive_opacity'))
  46. .margin({ top: $r('app.float.detail_immersive_margin_top') })
  47. .textOverflow({ overflow: TextOverflow.Ellipsis })
  48. .fontColor($r('app.color.text_font_color'))
  49. .maxLines(this.MAX_LINES)
  50. Button("换一个").onClick(()=>{
  51. //点击按钮,刷新卡片内容
  52. postCardAction(this,{
  53. "action":"message",
  54. 'params': {
  55. 'msgTest': 'messageEvent'
  56. }
  57. })
  58. })
  59. }
  60. .width(this.FULL_WIDTH_PERCENT)
  61. .height(this.FULL_HEIGHT_PERCENT)
  62. .alignItems(HorizontalAlign.Center)
  63. .justifyContent(FlexAlign.Center)
  64. .padding($r('app.float.column_padding'))
  65. }
  66. .width(this.FULL_WIDTH_PERCENT)
  67. .height(this.FULL_HEIGHT_PERCENT)
  68. .onClick(() => {
  69. //点击卡片进入元服务页面
  70. postCardAction(this, {
  71. "action": this.ACTION_TYPE,
  72. "abilityName": this.ABILITY_NAME,
  73. "params": {
  74. "message": this.MESSAGE
  75. }
  76. });
  77. })
  78. }
  79. }
复制

卡片预览效果 在“src/main/ets/entryformability/EntryFormAbility.ts”中编写卡片生命周期 代码如下:

  1. import formInfo from '@ohos.app.form.formInfo';
  2. import formBindingData from '@ohos.app.form.formBindingData';
  3. import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
  4. import update from "../services/util"
  5. export default class EntryFormAbility extends FormExtensionAbility {
  6. onAddForm(want) {
  7. // Called to return a FormBindingData object.
  8. let formData = {};
  9. return formBindingData.createFormBindingData(formData);
  10. }
  11. onCastToNormalForm(formId) {
  12. // Called when the form provider is notified that a temporary form is successfully
  13. // converted to a normal form.
  14. }
  15. onUpdateForm(formId) {
  16. update(this.context,formId)
  17. // Called to notify the form provider to update a specified form.
  18. }
  19. onChangeFormVisibility(newStatus) {
  20. // Called when the form provider receives form events from the system.
  21. }
  22. onFormEvent(formId, message) {
  23. console.info(`FormAbility onEvent, formId = ${formId}, message: ${JSON.stringify(message)}`);
  24. update(this.context,formId)
  25. }
  26. onRemoveForm(formId) {
  27. // Called to notify the form provider that a specified form has been destroyed.
  28. }
  29. onAcquireFormState(want) {
  30. // Called to return a {@link FormState} object.
  31. return formInfo.FormState.READY;
  32. }
  33. };
复制

其中,update是我编写的工具函数,util.ts的代码如下:

  1. import data_preferences from '@ohos.data.preferences';
  2. import { getPoem, getTheme } from "../services/Function"
  3. import formProvider from '@ohos.app.form.formProvider';
  4. import formBindingData from '@ohos.app.form.formBindingData';
  5. export default function update(context, formId) {
  6. data_preferences.getPreferences(context, 'mystore').then((preferences) => {
  7. //请求诗句主题
  8. getTheme(context).then((theme) => {
  9. let tag = theme['tag']
  10. console.log(tag)
  11. preferences.has(tag).then((has) => {
  12. if (has) {
  13. //如果有缓存,直接取出
  14. preferences.get(tag, "冬至").then((res) => {
  15. updateCard(formId, JSON.parse(res as string))
  16. })
  17. } else {
  18. //如果没有缓存,请求数据并缓存
  19. getPoem(context, tag).then((result) => {
  20. console.log(JSON.stringify(result))
  21. preferences.put(tag, JSON.stringify(result))
  22. updateCard(formId, result)
  23. })
  24. }
  25. })
  26. })
  27. })
  28. }
  29. function updateCard(formId, data) {
  30. console.log(JSON.stringify(data))
  31. let poem = data['poem']
  32. console.log("poem=" + poem)
  33. let count = poem.length
  34. let index = Math.floor(Math.random() * (count + 1));
  35. poem = poem[index]
  36. formProvider.updateForm(formId, formBindingData.createFormBindingData({
  37. title: poem['title'],
  38. author: poem['author'],
  39. content: poem['content']
  40. }))
  41. }
复制

其中,getPoem和getTheme的代码如下,代码参考模板给的调用云函数的代码

  1. import agconnect from '@hw-agconnect/api-ohos';
  2. import "@hw-agconnect/function-ohos";
  3. import { Log } from '../common/Log';
  4. import { getAGConnect } from './AgcConfig';
  5. const TAG = "[AGCFunction]";
  6. export function getPoem(context,tag): Promise<string> {
  7. return new Promise((resolve, reject) => {
  8. getAGConnect(context);
  9. let functionResult;
  10. //这里的名字在AGC控制台中的函数触发器里可以找到
  11. let functionCallable = agconnect.function().wrap("get-potery-$latest");
  12. functionCallable.call({tag}).then((ret: any) => {
  13. functionResult = ret.getValue();
  14. console.log(JSON.stringify(functionResult)+"_____________________")
  15. Log.info(TAG, "Cloud Function Called, Returned Value: " + JSON.stringify(ret.getValue()));
  16. resolve(functionResult);
  17. }).catch((error: any) => {
  18. Log.error(TAG, "Error - could not obtain cloud function result. Error Detail: " + JSON.stringify(error));
  19. reject(error);
  20. });
  21. });
  22. }
  23. export function getTheme(context): Promise<string> {
  24. return new Promise((resolve, reject) => {
  25. getAGConnect(context);
  26. let functionResult;
  27. let functionCallable = agconnect.function().wrap("get-theme-$latest");
  28. functionCallable.call().then((ret: any) => {
  29. functionResult = ret.getValue();
  30. Log.info(TAG, "Cloud Function Called, Returned Value: " + JSON.stringify(ret.getValue()));
  31. resolve(functionResult);
  32. }).catch((error: any) => {
  33. Log.error(TAG, "Error - could not obtain cloud function result. Error Detail: " + JSON.stringify(error));
  34. reject(error);
  35. });
  36. });
  37. }
复制

8. 元服务页面开发

在ets/pages下新建页面Main,并在entryability里修改入口页面为新建的页面。 Main.ets代码如下:

  1. import { getTheme, getPoem } from "../services/Function"
  2. import data_preferences from '@ohos.data.preferences';
  3. @Entry
  4. @Component
  5. struct Main {
  6. @State lunar: string = "二〇二三年八月二十"
  7. @State isFestival: boolean = false
  8. @State isJieQi: boolean = false
  9. @State festivals: string = ""
  10. @State jieQi: string = ""
  11. @State wuHou: string = "水始涸"
  12. @State hou: string = "秋分 三候"
  13. @State prev: string = ""
  14. @State next: string = ""
  15. @State tag: string = "秋分"
  16. @State poem: Array<Object> = new Array()
  17. aboutToAppear() {
  18. getTheme(getContext(this)).then((res) => {
  19. console.log(res['hou'])
  20. this.lunar = res['lunar']
  21. this.isFestival = res['isFestival']
  22. this.isJieQi = res['isJieQi']
  23. this.festivals = res['festivals'].join(',')
  24. this.jieQi = res['jieQi']
  25. this.wuHou = res['wuhou']
  26. this.hou = res['hou']
  27. this.prev = res['prev']['name'] + " " + res['prev']['time']
  28. this.next = res['next']['name'] + " " + res['next']['time']
  29. this.tag = res['tag']
  30. data_preferences.getPreferences(getContext(this), 'mystore').then((preferences) => {
  31. preferences.has(this.tag).then((has) => {
  32. if (has) {
  33. //如果有缓存,直接取出
  34. preferences.get(this.tag, "冬至").then((res) => {
  35. res = JSON.parse(res as string)
  36. let poem = res['poem']
  37. this.poem.push(...poem)
  38. })
  39. } else {
  40. getPoem(getContext(this), this.tag).then((result) => {
  41. console.log(JSON.stringify(result))
  42. preferences.put(this.tag, JSON.stringify(result))
  43. let poem = result['poem']
  44. this.poem.push(...poem)
  45. })
  46. }
  47. })
  48. })
  49. })
  50. }
  51. build() {
  52. Column() {
  53. Text(this.lunar).fontSize(32).fontWeight(FontWeight.Bold).fontColor($r("app.color.text_blue")).margin(12)
  54. if (this.isFestival) {
  55. Text(this.festivals).fontSize(24)
  56. }
  57. if (this.isJieQi) {
  58. Text(this.jieQi).fontSize(24)
  59. }
  60. Text(this.hou + " " + this.wuHou).fontSize(24).margin(12).fontColor($r("app.color.text_blue"))
  61. Text("上一节气:").fontSize(16).margin({ top: 12 })
  62. Text(this.prev).fontSize(18).margin(12).fontColor($r("app.color.text_blue"))
  63. Text("下一节气:").fontSize(16)
  64. Text(this.next).fontSize(18).margin(12).fontColor($r("app.color.text_blue"))
  65. Text("今日主题是:" + this.tag).fontSize(16).margin(12)
  66. Swiper(){
  67. ForEach(this.poem,(item,index)=>{
  68. Stack() {
  69. Image(index%2==0?$r("app.media.card_bg"):$r("app.media.card_bg2")).width("100%").height(200)
  70. Column() {
  71. Text(item.content).fontSize(18).margin(5).fontColor($r("app.color.white"))
  72. Text(`——${item.author}《${item.title}》`).fontSize(16).fontColor($r("app.color.white")).textOverflow({ overflow: TextOverflow.Ellipsis })
  73. }
  74. }.borderRadius(24).padding(12)
  75. },item=>item.title)
  76. }
  77. }.width("100%").height("100%")
  78. }
  79. }
复制

预览效果如下:

9. 图标及云服务名称配置

在APPSCOPE里的app.json5配置云服务的名称和图标

到此,诗词卡片开发完成

一些建议或意见

  1. 目前通过SDK的方式调用云函数和云数据库的方式,操作起来还是略显繁琐,如果可以直接集成到系统的API里,可能会更方便一些。
  2. 目前有关端云一体化的文档比较分散,向云函数和云数据库的文档在AGC里,服务卡片和元服务的文档在鸿蒙这边,如果有一个专区将这些文档放在一起,将学习路径优化一下可能会更好。
  3. 目前好像云函数的调用没有日志功能,这一点对于开发者来说挺重要的。

进入华为专区,解锁更多精彩内容

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/720146
推荐阅读
相关标签
  

闽ICP备14008679号