張鼎仁 任娟 韓英夫
摘 要 隨著“互聯(lián)網(wǎng)+”的快速發(fā)展,用戶(hù)的需求也日益增加,敏捷開(kāi)發(fā)、快速跟進(jìn)用戶(hù)需求變得更加重要。華為云appcube是華為自主研發(fā)的aPaaS服務(wù)平臺(tái),開(kāi)發(fā)者可以迅捷高效的整合自己的實(shí)際需求,集成云計(jì)算、大數(shù)據(jù)、視頻、人工智能、5G等多種新技術(shù)的平臺(tái),將這些新技術(shù)以組件化的方式接入和融合至開(kāi)發(fā)者數(shù)據(jù),本文以口罩預(yù)約與配送系統(tǒng)的開(kāi)發(fā)與設(shè)計(jì)為例,通過(guò)華為appcube平臺(tái)快速開(kāi)發(fā)構(gòu)建。最后總結(jié)分析本項(xiàng)目的成功經(jīng)驗(yàn),以及項(xiàng)目存在的不足和改進(jìn)措施。
關(guān)鍵詞 appcube aPaaS JavaScript 預(yù)約配送
中圖分類(lèi)號(hào):TN919 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1007-0745(2021)09-0005-05
1 緒論
在新冠疫情影響下,口罩成為人們?nèi)粘I钪斜夭豢扇钡娜沼闷?,如何分配和調(diào)度口罩成為人們面臨的一項(xiàng)難題,即使是同一地區(qū),不同社區(qū)和鄉(xiāng)鎮(zhèn)也存在需求不均衡的現(xiàn)象,手動(dòng)通過(guò)填報(bào)表格匯總統(tǒng)計(jì),這項(xiàng)工作本身耗時(shí)耗力,不同地區(qū)的表格格式還可能不盡相同,更會(huì)影響發(fā)放口罩的效率。因此,亟需開(kāi)發(fā)一個(gè)能快速適應(yīng)各種需求、能夠快速構(gòu)建的口罩預(yù)約與配送系統(tǒng),引入的華為appcube開(kāi)發(fā)平臺(tái)可安裝行業(yè)服務(wù),平臺(tái)自身?yè)碛胸S富的工具集,它是支持在云上開(kāi)發(fā)、測(cè)試、部署、運(yùn)維的aPaaS平臺(tái),針對(duì)不同水平的開(kāi)發(fā)者,可采用對(duì)應(yīng)程度的開(kāi)發(fā)模式,促進(jìn)了開(kāi)發(fā)的高效應(yīng)用和實(shí)現(xiàn),降低了開(kāi)發(fā)門(mén)檻。
2 口罩預(yù)約與配送系統(tǒng)總體設(shè)計(jì)方案
第一步,注冊(cè)登錄。開(kāi)發(fā)者通過(guò)注冊(cè)華為開(kāi)發(fā)者中心進(jìn)入應(yīng)用開(kāi)發(fā);第二步,創(chuàng)建應(yīng)用。首先定義命名空間,創(chuàng)建APP及目錄并定義業(yè)務(wù)對(duì)象,之后組裝前端頁(yè)面,定義市政報(bào)表管理頁(yè)面,配置物業(yè)人員無(wú)需登錄即可預(yù)約和市政人員使用的菜單最后編譯發(fā)布應(yīng)用,其中組裝前端頁(yè)面包括定義市政人員管理頁(yè)面、定義市政人員修改頁(yè)面、定義物業(yè)人員預(yù)約頁(yè)面、定義“根據(jù)ID查詢(xún)”邏輯和定義“新增與編輯”邏輯;第三步,應(yīng)用獨(dú)立部署并對(duì)外開(kāi)放。獨(dú)立部署后的應(yīng)用包括三個(gè)方面,分別是物業(yè)人員掃碼預(yù)約、市政人員管理信息和市政人員報(bào)表統(tǒng)計(jì)。
3 口罩預(yù)約與配送系統(tǒng)詳細(xì)設(shè)計(jì)方案
3.1 定義業(yè)務(wù)對(duì)象
通過(guò)appcube創(chuàng)建app以及相應(yīng)目錄,app名稱(chēng)為MaskMgtApp,開(kāi)發(fā)者支持多種數(shù)據(jù)格式的字段,這樣就能靈活應(yīng)對(duì)各種用戶(hù)需求,同時(shí)平臺(tái)可以通過(guò)增刪改自定義對(duì)象,同時(shí)每個(gè)字段需要定義是否需要索引、是否必需、創(chuàng)建人、最后修改人、最后修改時(shí)間。
對(duì)應(yīng)口罩預(yù)約的申請(qǐng)信息,我們同時(shí)需要自定義一個(gè)對(duì)象以表示預(yù)約信息,對(duì)象名稱(chēng)為MaskMgtInfo,華為appcube會(huì)自動(dòng)將這些信息寫(xiě)入數(shù)據(jù)庫(kù)表結(jié)構(gòu),開(kāi)發(fā)者無(wú)需再寫(xiě)相關(guān)的數(shù)據(jù)庫(kù)增刪查改的接口,省去了大量數(shù)據(jù)交互的工作量,這種可視化建立表結(jié)構(gòu)的方法也為用戶(hù)更快的了解項(xiàng)目提供了基礎(chǔ)(口罩預(yù)約信息屬性表如表1所示)。
3.2 組裝前端頁(yè)面
3.2.1 定義“新增與編輯”邏輯
//本腳本用于新增或者修改信息
import * as db from ‘db;//導(dǎo)入處理object相關(guān)的標(biāo)準(zhǔn)庫(kù)
import * as context from ‘context;//導(dǎo)入上下文相關(guān)的標(biāo)準(zhǔn)庫(kù)
import * as date from ‘date;
import * as buffer from ‘buffer;
//定義入?yún)⒔Y(jié)構(gòu),入?yún)?個(gè)參數(shù):業(yè)務(wù)對(duì)象,為必填字段
@action.object({ type: “param” })
export class ActionInput {
@action.param({ type: ‘Struct, required: true, label: ‘object })
maskMgtInfo: object;
}
//定義出參結(jié)構(gòu),出參包含1個(gè)參數(shù),記錄業(yè)務(wù)對(duì)象的id
@action.object({ type: “param” })
export class ActionOutput {
@action.param({ type: ‘String })
maskMgtInfoId: string;
}
//使用數(shù)據(jù)對(duì)象lgj__maskMgtInfo__CST
@useObject([‘lgj__MaskMgtInfo__CST])
@action.object({ type: “method” })
export class editMaskMgtInfo {? ? //定義接口類(lèi),接口的入?yún)锳ctionInput,出參為ActionOutput
@action.method({ input: ‘ActionInput, output: ‘ActionOutput })
public editMaskMgtInfo(input: ActionInput): ActionOutput {
let out = new ActionOutput();? ? //新建出參ActionO utput類(lèi)型的實(shí)例,作為返回值
let error = new Error();? ? //新建錯(cuò)誤類(lèi)型的實(shí)例,用于在發(fā)生錯(cuò)誤時(shí)保存錯(cuò)誤信息
try {
let maskMgtInfo = input.maskMgtInfo;? ? //將入?yún)①x值給maskMgtInfo變量,方便后面使用
let s = db.object(‘lgj__MaskMgtInfo__CST);? ? //獲取lgj__MaskMgtInfo__CST這個(gè)Object的操作實(shí)例
//新增口罩預(yù)約信息
if (!maskMgtInfo[‘id]) {
//合法性效驗(yàn)
this.doValidate(input);
this.checklegality(input);
//根據(jù)預(yù)約小區(qū)名稱(chēng)與聯(lián)系人手機(jī)號(hào)碼,查詢(xún)近8天內(nèi)是否有預(yù)約記錄,如果有預(yù)約記錄,則不允許再次預(yù)約
let maskMgtInfosByCondition = s.queryByCondition({
conjunction: db.Conjunction.AND,
conditions: [
{ field: “l(fā)gj__expectArriveDate__CST”, operator: db.Operator.gt, value: date.format(date.now(), ‘yyyy-MM-dd, context.getTimeZone()) },
{ field: “l(fā)gj__residence__CST”, operator: db.Operator.eq, value: maskMgtInfo[‘lgj__residence__CST] },
{ field: “l(fā)gj__phoneNumber__CST”, operator: db.Operator.eq, value: maskMgtInfo[‘lgj__phoneNumber__CST] }]
});
console.log(“maskMgtInfosByCondition is: “ + JSON.stringify(maskMgtInfosByCondition));
if ((maskMgtInfosByCondition || []).length) {
let expectArriveday = maskMgtInfosByCondition[0].lgj__expectArriveDate__CST;
//效驗(yàn)8天后的日期
let now = date.now();
now.setDate(now.getDate() + 8);
if (expectArriveday <= date.format(now, ‘yyyy-MM-dd, context.getTimeZone())) {
error.name = “EM”;
error.message = "您近8天內(nèi)有預(yù)約,不能再預(yù)約.";
throw error;
}
}
else {
this.removeSpace(maskMgtInfo);
let maskMgtInfoId = s.insert(maskMgtInfo);? ? //向lgj__MaskMgtInfo__CST插入一條數(shù)據(jù),返回?cái)?shù)據(jù)的唯一標(biāo)識(shí)即口罩預(yù)約信息ID
if (maskMgtInfoId && maskMgtInfoId != “”) {
out.maskMgtInfoId = maskMgtInfoId;
}
else {
error.name = “EM”;
error.message = “maskMgtInfo Cannot Be Added.”;
throw error;
}
}
}
//編輯修改口罩預(yù)約信息
else {
this.removeSpace(maskMgtInfo);
let id = maskMgtInfo[‘id];
delete maskMgtInfo[‘id];
let count = s.update(id, maskMgtInfo);? ? //根據(jù)口罩預(yù)約信息ID,編輯更新lgj__maskMgtInfo__CST的一條數(shù)據(jù)
if (count && count == 1) {
out.maskMgtInfoId = id;
}
else {
error.name = “EM”;
error.message = “maskMgtInfo Cannot Be Updated.”;
throw error;
}
}
} catch (error) {
console.error(error.name, error.message);
context.setError(error.name, error.message);
}
return out;
}
//去除空格
private removeSpace(maskMgtInfo) {
//防止前后空格入庫(kù)
maskMgtInfo[‘lgj__phoneNumber__CST] = maskMgtInfo[‘lgj__phoneNumber__CST].replace(/\s+/g, “”);
maskMgtInfo[‘lgj__residence__CST] = maskMgtInfo[‘lgj__residence__CST].replace(/\s+/g, “”);
maskMgtInfo[‘name] = maskMgtInfo[‘name].replace(/\s+/g, “”);
if (maskMgtInfo[‘lgj__address__CST]) {
maskMgtInfo[‘lgj__address__CST] = maskMgtInfo[‘lgj__address__CST].replace(/\s+/g, “”);
}
}
//檢查具體的合法性
private checklegality(input) {
console.log(“checklegality”);
let error = new Error();
if (JSON.stringify(input.maskMgtInfo) == “{}”) {
error.name = “EM”;
error.message = “”;
error.name = “EM”;
error.message = "信息有誤";
throw error;
}
console.log(input.maskMgtInfo[‘lgj__residence__CST]);
//residence不能為空
let residence = input.maskMgtInfo[‘lgj__residence__CST];
if (!residence || residence.replace(/\s+/g, “”) == “”) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)-預(yù)約小區(qū)名稱(chēng).";
throw error;
}
if (/[~!@#$%^&*()/\|,.<>?”();:_+\-=\[\]{}a-zA-Z]/.test(residence.replace(/\s+/g, “”))) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)-無(wú)特殊符號(hào)及數(shù)字與字母的小區(qū)名稱(chēng).";
throw error;
}
console.log(input.maskMgtInfo[‘lgj__orderCount__CST]);
//orderCount不能為空
let orderCount = input.maskMgtInfo[‘lgj__orderCount__CST];
if (!orderCount) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)口罩預(yù)約數(shù)量.";
throw error;
}
let numberReg = /^([1-9][0-9]{0,2})$/;
console.log(!numberReg.test(orderCount));
if (!numberReg.test(orderCount) || orderCount > 500) {
error.name = “EM”;
error.message = "請(qǐng)正確填寫(xiě)口罩預(yù)約數(shù)量,最多預(yù)約500個(gè).";
throw error;
}
//expectArriveDate不能為空
let expectArriveDate = input.maskMgtInfo[‘lgj__expectArriveDate__CST];
console.log(expectArriveDate);
if (!expectArriveDate || expectArriveDate == “”) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)期望到達(dá)日期.";
throw error;
}
//效驗(yàn)3天后的日期
let now = date.now();
now.setDate(now.getDate() + 2);
if (expectArriveDate <= date.format(now, ‘yyyy-MM-dd, context.getTimeZone())) {
error.name = “EM”;
error.message = "期望到貨日期,請(qǐng)至少填寫(xiě)3天后日期.";
throw error;
}
console.log(input.maskMgtInfo[‘lgj__phoneNumber__CST]);
//phoneNumber不能為空
let phoneNumber = input.maskMgtInfo[‘lgj__phoneNumber__CST];
if (!phoneNumber || phoneNumber.replace(/\s+/g, “”) == “”) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)聯(lián)系人手機(jī)號(hào)碼.";
throw error;
}
//手機(jī)號(hào)碼合法
let reg = /^1[3|4|5|7|8][0-9]{9}$/;
console.log(reg.test(phoneNumber.replace(/\s+/g, “”)));
if (!reg.test(phoneNumber.replace(/\s+/g, “”))) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)合法的聯(lián)系人手機(jī)號(hào)碼.";
throw error;
}
console.log(input.maskMgtInfo[‘name]);
//personName不能為空
let personName = input.maskMgtInfo[‘name];
if (!personName || personName.replace(/\s+/g, “”) == “”) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)-聯(lián)系人姓名.";
throw error;
}
if ((/[~!@#$%^&*()/\|,.<>?”();:_+-=\[\]{}0-9]/.test(personName.replace(/\s+/g, “”)))) {
error.name = “EM”;
error.message = "請(qǐng)?zhí)顚?xiě)-無(wú)特殊符號(hào)的聯(lián)系人姓名.";
throw error;
}
}
//長(zhǎng)度基礎(chǔ)效驗(yàn)
private doValidate(input) {
console.log(“doValidate”);
for (let property in input) {
console.log(property);
if (Array.isArray(input[property])) {
console.log(‘a(chǎn)aa + input[property]);
for (let tmp of input[property]) {
this.doValidate(tmp);
}
} else {
let buf = buffer.from(input[property] || ‘);
console.log(‘bbb + input[property]);
if (typeof (input[property]) == “string”) {
console.log(‘ccc + input[property]);
if (property != “url”) {
if (buf.size() > 255) {
context.throwError(‘請(qǐng)檢查--某個(gè)屬性是否填的長(zhǎng)度>255, property);
}
}
} else if (typeof (input[property]) == “object”) {
console.log(‘ddd + input[property]);
for (let i in input[property]) {
buf = buffer.from(input[property][i] || ‘);
if (i == “l(fā)gj__residence__CST”) {
if (buf.size() > 64) {
context.throwError(‘請(qǐng)檢查--預(yù)約小區(qū)名稱(chēng)是否填的長(zhǎng)度>64, i);
}
}
else (i != “url” && i != “content”) {
console.log(‘eee + buf);
if (buf.size() > 255) {
context.throwError(‘請(qǐng)檢查--某個(gè)屬性是否填的長(zhǎng)度>255, i);
}
}
}
}
}
}
}
}
Appcube平臺(tái)同時(shí)支持腳本驗(yàn)證功能,可以通過(guò)編輯器測(cè)試新增邏輯能否正常執(zhí)行,測(cè)試的結(jié)果以json字符串格式顯示。同理,可以采用同樣的方法定義“根據(jù)Id查詢(xún)”邏輯,腳本名稱(chēng)定義為queryMaskMgtDetail。
腳本代碼如下所示:
/*****************************
* 本腳本用于按記錄ID查詢(xún)信息記錄
* ***************************/
import * as db from ‘db;//導(dǎo)入處理object相關(guān)的標(biāo)準(zhǔn)庫(kù)
import * as context from ‘context;//導(dǎo)入上下文相關(guān)的標(biāo)準(zhǔn)庫(kù)
//定義入?yún)⒔Y(jié)構(gòu)
@action.object({ type: “param” })
export class ActionInput {
@action.param({ type: ‘String, required: true })
maskMgtInfoId: string;//口罩預(yù)約ID
}
//定義出參結(jié)構(gòu)
@action.object({ type: “param” })
export class ActionOutput {
@action.param({ type: ‘Struct, label: ‘object })
maskMgtInfo: object;//口罩預(yù)約對(duì)象
}
@useObject([‘lgj__MaskMgtInfo__CST])//使用數(shù)據(jù)庫(kù)對(duì)象lgj__MaskMgtInfo__CST
@action.object({ type: “method” })
export class QueryMaskMgtInfoDetail {
@action.method({ input: ‘ActionInput, output: ‘ActionOutput })
public queryMaskMgtInfoDetail(input: ActionInput): ActionOutput {
let out = new ActionOutput();? ? //新建出參ActionO utput類(lèi)型的實(shí)例,作為返回值
let error = new Error();? ? //新建錯(cuò)誤類(lèi)型的實(shí)例,用于在發(fā)生錯(cuò)誤時(shí)保存錯(cuò)誤信息
try {
//必填校驗(yàn)
if (!input.maskMgtInfoId || input.maskMgtInfoId == “”) {
error.name = “TM”;
error.message = "請(qǐng)傳入口罩預(yù)約記錄的Id.";
throw error;
}
//獲取lgj__MaskMgtInfo__CST這個(gè)Object的操作實(shí)例
let s = db.object(‘lgj__MaskMgtInfo__CST);
//查詢(xún)字段(全部)
let option = {};
//查詢(xún)條件
let condition = {
“conjunction”: “AND”,
“conditions”: [{
“field”: “id”,
“operator”: “eq”,
“value”: input.maskMgtInfoId
}]
};
//調(diào)用按條件查詢(xún)lgj__MaskMgtInfo__CST的接口
let record = s.queryByCondition(condition, option);
//如果查詢(xún)到數(shù)據(jù)
if (record && record[0]) {
//將結(jié)果掛入輸出對(duì)象中
out.maskMgtInfo = record[0]
}
} catch (error) {
console.error(error.name, error.message);
context.setError(error.name, error.message);
}
return out;
}
}
3.2.2 組裝頁(yè)面
3.3 定義市政管理報(bào)表
Appcube報(bào)表關(guān)聯(lián)的對(duì)象需要設(shè)置為“允許報(bào)表使用”,定義的報(bào)表分為按預(yù)約日統(tǒng)計(jì)各小區(qū)預(yù)約量報(bào)表和按期望到貨日統(tǒng)計(jì)各小區(qū)待配發(fā)量報(bào)表。同時(shí),為了方便物業(yè)人員的使用,能夠?qū)⑷藗兊目谡中枨髷?shù)量及時(shí)快速統(tǒng)計(jì),我們將申請(qǐng)?jiān)O(shè)置為通過(guò)二維碼掃碼預(yù)約的形式免登陸登記。
3.4 獨(dú)立部署并對(duì)外開(kāi)放
項(xiàng)目開(kāi)發(fā)成功后,我們還可以導(dǎo)入對(duì)應(yīng)的資產(chǎn)包給其他用戶(hù),以便其他用戶(hù)直接使用和測(cè)試該項(xiàng)目。這種軟件包式的管理方式大大提高了項(xiàng)目部署效率,免去了部署開(kāi)發(fā)環(huán)境的重復(fù)操作,同時(shí)云端部署還省去了運(yùn)維成本,提高了項(xiàng)目運(yùn)行的穩(wěn)定性。
4 結(jié)語(yǔ)
本文主要介紹了基于華為appcube平臺(tái)開(kāi)發(fā)的口罩預(yù)約與配送系統(tǒng),它可以滿(mǎn)足新冠疫情防控期間口罩需求的統(tǒng)計(jì)匯總的現(xiàn)實(shí)需求,解決了基層繁雜的表格填報(bào)難題,對(duì)其他行業(yè)的數(shù)據(jù)統(tǒng)計(jì)和匯總同樣具有借鑒意義,更加值得學(xué)習(xí)的地方是該aPaaS平臺(tái)云開(kāi)發(fā)、云測(cè)試、云部署、云運(yùn)維的特點(diǎn)。它不再依賴(lài)于復(fù)雜的現(xiàn)場(chǎng)網(wǎng)絡(luò)環(huán)境和硬件局限性,同時(shí)大大縮短了項(xiàng)目開(kāi)發(fā)周期,提高了工作效率。同時(shí)在appcube平臺(tái)開(kāi)發(fā)過(guò)程中也發(fā)現(xiàn)了一些問(wèn)題,比如開(kāi)發(fā)的舊模塊無(wú)法適配新版本的問(wèn)題。在后續(xù)的學(xué)習(xí)和工作中,我將不斷充電學(xué)習(xí),開(kāi)發(fā)具有高適配性、高安全性的口罩預(yù)約與配送系統(tǒng)。