和朋友合夥寫了一個小程序,寫了一個以共享乒乓信息和交流的平臺———乒乓圈。咱們使用了微信的雲開發來完成數據和後臺的做用。免去了租賃服務器。css
我主要負責的是數據庫的設計和雲函數實現數據獲取和觸發器的功能和簡單的兩個頁面。html
當用戶未受權則會彈出,點擊下方指紋圖片,則會彈出受權框,受權後,若是未註冊則會註冊完畢後進入首頁前端
頁面流程大體分爲mysql
- 引導頁 - 首頁 - 同城圈 - 打卡 - 榜單 - 圈友頁 - 同城圈友 - 留言列表 - 我的頁 - 我的資料
從以上的功能出發個人數據庫設計思路如此
對象有如下幾個:git
對象就只有大體三個,可是爲了數據操做的簡便性我將我的的信息分紅兩個對象表,將留言中的對話又單獨放出一張表,因此最後的表有爲一下幾個:github
對象屬性的類型選擇sql
首先,小程序提供的數據庫是基於mangoDB的面向對象數據庫,區別於通常的關係數據庫如:mysql等。兩者之間的區別和個人理解會寫在總結中。數據庫
信息是反映對象狀態的一種編程
我認爲數據庫存儲的屬性大體可分爲三種小程序
可是不少信息都兼顧以上的幾種,例如:學號(便是標記型,又是基礎信息)
確認完對象基礎屬性後就要考慮對象之間的關係,例如人和對話,留言和對話信息。
關係種類有 一對一(1-1),一對多(1-n),多對多(m-n)。
在 關係數據庫 中,一對一的關係只要在一條記錄中添加一個屬性便可,例如:我的信息和我的詳情,在我的詳情中添加我的的惟一表示符字段;
一對多的關係中須要在多數的記錄中添加一個屬性,或者單獨創建一張表來存儲關係,
例如:我的和物品,第一種在物品對象中添加一個全部者對象,或者創建一個所屬關係表;
多對多的關係則只能經過單獨一張關係表來完成,例如:學生和課程,須要單獨一張選課表來表示關係。
在 面向對象數據庫中一對多和多對多的關係能夠經過對象中的一個數組字段來完成,例如:學生和課程,在學生對象中添加一個所選課程字段存儲課程 ID ,在課程中添加選課學生字段存儲學號,就完成了多對多的關係連接。
openId:{type:String}//openId主鍵 name:{type:String}//名字,默認爲微信名 avatarUrl:{type:String}//頭像,默認微信頭像 context:{type:String,default:"這我的很懶什麼都沒留下"}//我的簡介 //如下幾項應爲流水數據應但對放一張表,在此爲圖簡便放入基礎信息表 intergal:{type:Number,default:0}//積分 用來排名和升級 level:{type:String,default:"新人"}//等級 sign:{type:Array,default:[]}//記錄打卡簽到的日期 month:{type:Number}//記錄上次打卡簽到的月份,用於每個月清空簽到表
openId:{type:String}//openId主鍵 years:{type:String}//球齡 phone:{type:Array}//電話 bat:{type:String}//球拍 board:{type:String}//底板 context:{type:String}//正面膠皮 intergal:{type:Number,default:0}//反面膠皮
id:{type:String}//主鍵,由數據庫自動生成 address:{type:String}//地址 arena:{type:String}//所在區域,例如球館名 persons:{type:Array}//球館的活動者,需求更改數據庫中的字段爲circle city:{type:String}//球館所在的城市 img:{type:String}//球館圖片地址 latitude:{type:Number}//經度 longitude{type:Number}//緯度 table:{type:Number}//球桌數 time:{type:String}//開放時間
message:{type:Array,default:[]}//留言內容數組,存儲留言的id my_id:{type:String}//建立者的openId other_id:{type:String}//接收者的openId
id:{type:String}//主鍵,由數據庫自動生成 msg:{type:String}//留言內容 my_id:{type:String}//建立者的openId other_id:{type:String}//接收者的openId time:{type:Date}//時間戳
當第一次登錄進區就是如上所示,登錄進去後經過 openId 進行雲函數獲取數據庫中我的信息,若是沒有則默認進行註冊流程。
默認暱稱爲微信暱稱(可在我的頁更改),頭像爲微信頭像(暫不提供更改),餘下都爲默認值。
引導頁 js
const QQMapWX = require('../../libs/qqmap-wx-jssdk.js');// 鏈接騰訊地圖 const qqmapsdk = new QQMapWX({ key: 'HMGBZ-U5XCX-TUX4Y-ZPUH3-7RRX5-BZBCW' }); const app = getApp() Page({ data: { login: false }, getUserInfo(e) { if (e.detail.userInfo && !this.data.login) { console.log('登陸中') let the_first = false; // 掉用獲取用戶信息函數,用openId做爲惟一標識符 wx.cloud.callFunction({ name: "getPersonInfo", }) .then(res => { // 判斷是否爲空,空則表明第一次進入 if (res.result.data.length == 0) { the_first = true } else { // 已經註冊過,獲取到信息放入 app.globalData 全局數據中。 app.globalData.personInfo = res.result.data[0]; console.log(app.globalData.personInfo); wx.cloud.callFunction({ name: "getpingpang_info", success: res => { console.log('登陸成功') // console.log(res.result.data) app.globalData.ping_personInfo = res.result.data[0] wx.setStorage({ key: 'login', data: true }) wx.switchTab({ url: '../home/home', }) } }) } }).then(() => { // 進入註冊流程, return new Promise((resolve, reject) => { if (the_first) { // 獲取用戶的信息 wx.getUserInfo({ lang: "zh_CN", success: res => { app.globalData.userInfo = res.userInfo; resolve(); }, }) } }) }) .then(() => { if (the_first) { // 用戶註冊所需暱稱和頭像 const data = { name: app.globalData.userInfo.nickName, avatarUrl: app.globalData.userInfo.avatarUrl, }; // 顯示加載 wx.showLoading({ title: '受權登陸中', }) // 用戶註冊函數,除了暱稱和頭像,全置爲最低或空 wx.cloud.callFunction({ name: "pingpang_init", data: data }).then(res => { // 數據庫已經註冊完成 console.log("註冊完成") }) .then(() => { // 註冊完成後獲取一遍用戶信息 wx.cloud.callFunction({ name: "getPersonInfo" }).then(res => { app.globalData.personInfo = res.result.data[0]; console.log(res.result.data[0]) // 隱藏加載 app.globalData.ping_personInfo = { openId: app.globalData.personInfo.openId, phone: '***********', years: '0年', bat: '右手橫拍', board: '新手用具', infront_rubber: '新手用具', behind_rubber: '新手用具' } wx.hideLoading(); // 提示註冊完成 // wx.showModal({ // title: '註冊', // content: '註冊完成', // }) wx.setStorage({ key: 'login', data: true }) wx.switchTab({ url: '../home/home', }) }) }) } }) } }, onLoad() { wx.getStorage({ key: 'login', success: (res) => { if (res.data) { this.setData({ login: true }) app.timeout = setTimeout(() => { wx.showLoading({ title: 'lodaing', }) }, 3000) app.neterror = setTimeout(() => { wx.hideLoading() wx.showModal({ title: '傷心提示', content: '網絡走丟了...', showCancel: false }) }, 20000) } } }) } })
以上流程可分爲如下幾步。
首先調用 wx.getStorage
查詢收緩存的登錄信息,若是獲取成功,跳過引導頁,將當前登錄狀態的標識符(默認false)改成 true 。
經過 setTimeout
來控制提示信息和超時檢測。
若是獲取緩存中登錄狀態。先獲取受權信息 getUserInfo
判斷獲取到的用戶信息存在且爲登陸。
the_first
判斷是不是第一次進入要進行註冊流程(保險做用).
調用雲函數 getPersonhInfo
——獲取用戶信息,若是獲取成功,結果集不爲空,就將信息存儲到全局狀態 app.globalData
中.接着調用雲函數 getpingpang_info
獲取我的詳細信息同樣放入全局狀態中,而後寫入緩存信息
wx.setStorage({key: 'login',data: true})
以便下次不用檢驗,最後經過 wx.switchTab({url: '../home/home',})
來跳轉到首頁。
若是上一步未獲取成功,判斷爲第一次登入,進入註冊流程 先獲取用戶的暱稱後頭像,調用雲函數 pingpang_init
後臺進行註冊,並將初始值放入全局狀態中,跳轉到首頁。
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database(); const personinfo = db.collection("pingpang_personinfo") const _ = db.command; // 雲函數入口函數 exports.main = async(event, context) => { let { openIdarr, openId, all, city } = event; if (all) {//獲取全部人信息 return await personinfo.get() } else if (city) {//獲取所給城市的全部用戶信息 return await personinfo.where({ city }).get() } else if (openIdarr) {//獲取openId在所給數組中的全部用戶信息 console.log(openIdarr) return await personinfo.where({ openId: _.in(openIdarr) }).get() } else {//獲取所給openId或自身的用戶信息 return await personinfo.where({ openId: openId || event.userInfo.openId }).get() } }
這個雲函數是獲取用戶信息,
首先解構用戶傳來的參數來判斷須要的數據,openIdarr--經過openId數組獲取,openId--經過openId獲取,city--經過用戶所在城市獲取,all--獲取全部用戶,以上四種都沒有則獲取當前用戶的信息。
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database(); // 雲函數入口函數 exports.main = async(event, context) => { return await db.collection("pingpang_info").where({ openId: event.openId || event.userInfo.openId }).get() }
這個雲函數只是簡單的經過兩種方式(給與openId或默認自身)來獲取獲取用戶詳細信息
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() // 雲函數入口函數 exports.main = async (event, context) => { const fun1 = await cloud.callFunction({ name: "addPersonInfo", data: { openId: event.userInfo.openId, name: event.name || "未獲取到名字", avatarUrl: event.avatarUrl, city: event.city,//所在城市 level: "新人", intergal: 0, context: "", activitiew: [], circle: [] } }) const fun2 = await cloud.callFunction({ name: "addpingpang_info", data: { openId: event.userInfo.openId, phone: '***********', years: '0年', bat: '右手橫拍', board: '新手用具', infront_rubber: '新手用具', behind_rubber: '新手用具' } }) return { fun1, fun2 } }
這個函數是初始化函數,功能是向數據庫添加新用戶的初始數據。
簽到功能的頁面並不是我寫的,因此我只能提供思路和雲函數。
簽到的存儲是在我的信息的一個字段sign中,以數組的形式存儲,當點擊簽到時,先判斷這次簽到的月份與上次簽到的月份(person的month字段)是否相同,不一樣則將sign數據置爲空而且將month字段更新爲當前月份,接着存儲簽到的日期的的day,
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database(); const personInfo = db.collection("pingpang_personinfo") // 雲函數入口函數 exports.main = async(event, context) => { let data = personInfo.where({ openId: event.openId || event.userInfo.openId }) let info = await data.get()//先獲取 let sign = info.data[0].sign || [] //放入新數組 //判斷是否到了新的月份 if (info.data[0].month != new Date().getMonth()) { sign = []; var month = new Date().getMonth() } //替換數組 return await cloud.callFunction({ name: "setPersonInfo", data: { personInfo: { //event.date是爲了方便寫管理調試用的一次性放日期數組 sign: sign.concat(event.date || [new Date().getDate()]), month:month } } }) }
榜單十分簡單,有多種作法:
因此這種方法只適用於用戶量較少的狀況下。
這種方式使用用戶量較大可是分散的狀況,能夠廣泛使用。
city:{ type:String }, //存儲排行榜,存儲必定數量的用戶openId,或者是用戶對象 list:{ type:Array, default:[] }, minIntergal:{ type:Number, default:0 }
這種作法適用於用戶數量極大的時候。
留言功能,是這個小程序的主要功能之一,目的是爲了向興趣相同的乒乓愛好者有一個初始的交流平臺。
建立留言須要在圈友(同城的)中找到相應的用戶,而後點擊頭像,彈出詳情,接着點擊留言按鈕,會跳轉到留言對話頁。
留言有兩種狀況,一種是以前有過留言,存在留言對象,另外一種則是第一次對話,以前不存在留言對象。
第一種,只須要查詢到存在就可向裏面添加留言信息。第二種則須要先建立在進行添加。
第一種沒有任何問題,直接對對話對象的留言數組中進行添加,第二種則須要建立一個對話對象。
具體流程:首先在留言頁查詢到全部對話對象,這是走第一種狀況,能夠跳轉到直接添加留言,第二種則是在圈友頁中對象的詳情頁點擊留言按鈕,這會先查詢對話兌現,不存在則會跳轉到空白對話頁,不然跳轉到以前的留言對象。
這種方法不是很好
缺點以下:
推薦方案 :在點擊留言時查詢以前是否存在對話兌現,存在即讀取,不存在就跳轉到空白頁,若是發送了留言,則建立對象。這樣可已解決以上的缺點。可是仍是存在一個問題就是未使用 socket 沒法達成實時通訊。
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init() const db = cloud.database(); const dialoguedb = db.collection("pingpang_dialogue") const _ = db.command; // 雲函數入口函數 exports.main = async (event, context) => { let { my_id, other_id } = event; if (!my_id) my_id = event.userInfo.openId // let data1 = await dialoguedb.where({ // my_id, // other_id // }).get() if (!other_id) return await dialoguedb.where({ my_id }).get() return await dialoguedb.where({ my_id, other_id }).get() } // console.log(data1) // console.log('\n',data2) // return data2;
雲函數的大體功能爲:
首先,結構傳遞的參數my_id(當前用戶的id) 和 other_id(留言對象的id)。接着判斷 my_id 是否存在,不存在就給當前用戶的 openId ,最後判斷 ohter_id 若是不存在,則查詢前用戶全部的對話。
// 雲函數入口文件 const cloud = require('wx-server-sdk') cloud.init(); const db = cloud.database(); const messagedb = db.collection("pingpang_message"); const dialoguedb = db.collection("pingpang_dialogue"); const _ = db.command; // 雲函數入口函數 exports.main = async(event, context) => { let { message, my_id, other_id, msg } = event; console.log(other_id) if (!my_id) my_id = event.userInfo.openId; // console.log(other_id) let message_id = await messagedb.add({ data: message || { my_id, other_id, msg, time: new Date() } }) // console.log(message_id) const res = await cloud.callFunction({ name:"getpingpang_dialogue", data:{ my_id:my_id, other_id:other_id } }) let myTo = res.result.data[0]; await dialoguedb.doc(myTo._id).update({ data: { message: myTo.message.concat([message_id._id]) } }) const res1 = await cloud.callFunction({ name: "getpingpang_dialogue", data: { my_id: other_id, other_id: my_id } }) let otherTo = res1.result.data[0]; await dialoguedb.doc(otherTo._id).update({ data: { message: otherTo.message.concat([message_id._id]) } }) }
上述的雲函數功能大體爲:
首先,結構參數,message(留言對象,包含以後的幾個參數),my_id,other_id和msg(留言內容)。接着判斷 my_id是否存在,不存在就用當前用戶的 openId 。而後是向留言表添加一條新數據 messagedb.add
最後獲取對話對象並向對話中的留言數組中添加留言內容。
我的模塊沒有什麼複雜的邏輯,就是數據渲染頁面,不過頁面結構是我寫的,能夠聊一聊頁面了。
我的頁面中沒有什麼比較花裏胡哨的樣式操做,只有簡單基礎的 css 和 html ,因此就在此簡單結構一下。
大概要講的就是點擊切換成輸入框的所須要講的,還有下面的選擇欄變變成組件
頁面(部分)
//我的簡介 <view class="infocard" bindtap='typeInfo'> <input type="text" wx:if="{{changecontext}}" placeholder='' bindblur='setcontext' focus='true' value="{{personInfo.context}}" maxlength='18'></input> <view class="context" wx:else bindtap='changecontext'>個性簽名:{{personInfo.context}}</view> </view> //我的資料框 <view class="project collections" bindtap="ToPage" data-name="pingpang_info"> <image class="image" src="https://636f-coldday-67x7r-1259123272.tcb.qcloud.la/person.svg?sign=73135fcd2247e0a00ca78c131fa0d7d6&t=1559030458" /> <view class="title">我的資料</view> <text class='cuIcon-right righticon text-grey'></text> </view>
js(部分)
data:{ changecontext: false } changecontext() { this.setData({ changecontext: true }) }, setcontext(event) { this.setData({ changecontext: false }) if (event.detail.value != "") { this.setData({ "personInfo.context": event.detail.value, context: event.detail.value }) } else { this.setData({ "personInfo.context": "這傢伙打完球后不留任何足跡", context: "這傢伙打完球后不留任何足跡" }) } }, ToPage(event) { wx.navigateTo({ url: `../${event.currentTarget.dataset.name}/${event.currentTarget.dataset.name}`, fail: () => { wx.showModal({ title: '(ಥ_ಥ)', content: '敬請期待!', showCancel: false }) } }) },
文本和輸入框的切換,是經過 wx:if
來控制顯示,讓兩個大小近似的塊佔用相同的地方,當點擊文本時,數據源(data)中的 changecontext 變量變成 ture 頁面從新渲染,將輸入框顯示 value 爲數據源中的我的簡介,文本則隱藏;當輸入框失去焦點時,將輸入框中的value值寫入數據源中,而後changecontext變爲false,頁面從新渲染,就改完了我的簡介。
修改後提交數據的方案有三種
第一種和第三種均可以廣泛使用。推薦第一種方式,由於大多數用戶不會過於頻繁的去修改這些東西,可是頁面基本都是每次登錄都會訪問屢次的。頻率和併發都是第一種好。
我的詳情就是普通的頁面,沒有複雜的雲函數,只有一個獲取,一個提交修改,兩個函數都不復雜。
詳情頁中球拍和球齡是使用了小程序自帶組件 picker 其他則是使用了自定義組件 info-section
頁面
<view class="container"> <view class="cu-form-group"> <view class="title">球齡</view> <picker bindchange="PickerAgeChange" value="{{indexAge}}" range="{{pickerAge}}"> <view id='picker' class='picker'> {{indexAge?pickerAge[indexAge]:personInfo.years}}<text class='cuIcon-title' style='opacity:0'></text> </view> </picker> </view> <section title="電話" info="{{personInfo.phone}}" infoname="phone" bind:changend="getinfo" type='number'/> <!-- <section title="球齡" info="{{personInfo.years}}" infoname="years" bind:changend="getinfo" type='number'/> --> <!-- <section title="持拍" info="{{personInfo.bat}}" infoname="bat" bind:changend="getinfo" /> --> <section title="使用底板" info="{{personInfo.board}}" infoname="board" bind:changend="getinfo" /> <section title="正手膠皮" info="{{personInfo.infront_rubber}}" infoname="infront_rubber" bind:changend="getinfo" /> <section title="反手膠皮" info="{{personInfo.behind_rubber}}" infoname="behind_rubber" bind:changend="getinfo" isbottom="true" /> <view class="cu-form-group"> <view class="title">持拍</view> <picker bindchange="PickerChange" value="{{index}}" range="{{picker}}"> <view id='picker' class='picker'> {{index?picker[index]:personInfo.bat || '點擊選擇'}} </view> </picker> </view> <button class='button' bindtap="submit">點擊提交</button> </view>
js
const app = getApp(); Page({ /** * 頁面的初始數據 */ data: { personInfo: {}, picker: ['右手橫拍', '右手直拍', '左手橫拍', '左手直拍'] }, PickerChange(e) { let personInfo = this.data.personInfo; this.setData({ index: e.detail.value }) personInfo.bat = this.data.picker[this.data.index]; this.setData({personInfo}) }, PickerAgeChange(e) { let personInfo = this.data.personInfo; this.setData({ indexAge: e.detail.value }) personInfo.years = this.data.pickerAge[this.data.indexAge]; this.setData({personInfo}) }, getinfo() { this.setData({ personInfo: app.globalData.ping_personInfo, }) }, submit() { let personInfo = this.data.personInfo; if(personInfo.phone.length != 11){ wx.showModal({ title: '提示', content: '無效電話號碼', showCancel:false }) personInfo.phone = ''; this.setData({ personInfo }) return } const that = this; console.log("開始提交") wx.showLoading({ title: '提交中', }) let info = this.data.personInfo wx.cloud.callFunction({ name: "setpingpang_info", data: info }).then(res => { wx.hideLoading(); wx.showToast({ title: "提交成功", duration: 1000, }) console.log(res, "修改爲功") wx.navigateBack({ }) }) }, /** * 生命週期函數--監聽頁面加載 */ onLoad: function (options) { this.setData({ personInfo: app.globalData.ping_personInfo }) let pickerAge = [] for (let i = 0; i < 51; i++) { pickerAge.push(i + '年') } this.setData({ pickerAge }) } })
頁面
<view class="cu-form-group"> <view class="title">{{title}}</view> <input type='{{type}}' wx:if="{{changeinfo}}" bindblur='changend' value='{{info}}' placeholder="請輸入信息" focus='true'></input> <view class='info' wx:else> <input value='{{info?info:"新手用具"}}' disabled='true'></input> </view> <view class='icon-con' bindtap='changeinfo'> <image src="https://636f-coldday-67x7r-1259123272.tcb.qcloud.la/change-1.png?sign=c8936111328dcb2ee416201369716380&t=1559030699" class='icon'></image> </view> </view>
js
// components/info-section/section.js Component({ /** * 組件的屬性列表 */ properties: { title: { type: String, value: "屬性名" }, info: { type: String, value: "屬性值" }, infoname: { type: String, value: "" }, isbottom: { type: Boolean, value: false }, type:{ type:String, value:'text' } }, /** * 組件的初始數據 */ data: { changeinfo: false, }, /** * 組件的方法列表 */ methods: { changeinfo() { this.setData({ changeinfo: true }) this.triggerEvent("changeinfo"); }, changend(event) { this.setData({ changeinfo: false }) getApp().globalData.ping_personInfo[this.properties.infoname] = event.detail.value //拋出事件以便於父組件響應 this.triggerEvent("changend") } } })
父子組件的通信必定要注意在子組件中拋出事件,觸發父組件的事件來達成。
良好溝通的重要性
在和朋友一塊兒開發小程序的過程當中注意到瞭如下的問題, 溝通 是最重要的,在咱們開發的過程當中,由於沒有良好的溝通,致使,先後端的功能開發對接不完美。部分功能分配很差,有些功能能夠同過前端或後端單獨解決,缺由於沒有溝通完善,致使雙方都作了或者雙方都沒作的狀況發生,雖然有每一個人都有本身的事,大多數時間都是單獨開發的緣由在。可是這些問題應當在代碼開發流程就應當作的,這是我瞭解的一個問題。
程序的結構大體分爲前端頁面、後端服務器和數據庫三個組成部分。在小程序這種 MVVM 結構中前端佔有了很重要的一部分。
先後端和數據庫的比例大體爲 n:1:1 的關係,因此當用戶量大的程序,多數操做應當放在前端中處理,這是如今 mvvm 稱爲主流的緣由,後臺主要統籌管理整體數據或者對重要的流水數據處理,而且須要提供大量的 api 供前端獲取數據,
這樣能大量緩解數據庫的壓力。
關係型數據庫是傳統的數據庫,如今使用的主要是mysql 和 microsoft sql server。面向對象數據庫是新興數據庫,如今使用的是 mangoDB等。
關係型數據庫中,最獨特的也是最重要的是 規劃範式 在關係型數據庫中範式等級越高,數據的總體性越低,那麼冗餘度會逐漸降低。
一個學生用戶可能會被分紅多張表來存儲相關信息。而關係型數據庫中主要的也是兩張表之間的關係(聯繫),這個關係一般也必須使用一張表來存儲。
在面向對象數據庫中,與傳統關係型數據庫最大的區別數,它是以一個對象來存儲的,對象的屬性則是本身定義的,它的屬性能夠存儲一個對象(函數,數組)。這就極大的增長了可操做性,咱們能夠把關係做爲對象的一個屬性來存儲,例如:學生和課程的關係,兩者之間是多對多的關係,原本在關係型數據中須要創建一張選課表來存儲,如今只須要在課程對象中添加一個選課字段存儲選課學生的 id 數組,而在學生對象中添加一個所選課程字段,兩者之間的關係就連接起來了。面向對象數據庫中,對象的屬性一般能夠彙集在一塊兒,一個對象類就是一張表,這樣會形成每張表中擁有大量的數據每次操做會形成的併發問題,因此每一個對象類最好將屬性分割,讓數據訪問更加平均,減小每一個對象表的同時訪問次數。
在和他人一塊兒,寫小程序的時候出現種種問題,甚至有時候效率尚未一我的單獨寫的高,可是我發現和他人一塊兒寫會更有動力,每一個人的想法在碰撞,能快速的提升本身的編程水平和與他人的溝通能力。
https://github.com/TencentCloudBase/Good-practice-tutorial-recommended
更多雲開發使用技巧及 Serverless 行業動態,掃碼關注咱們~