Node.js 開發(fā)實(shí)踐,前端工程師的 MVP 利器
本文經(jīng)作者授權(quán)轉(zhuǎn)載至公眾號?“圖雀社區(qū)”,作者:愚坤,掘金優(yōu)秀作者,一名沒上高中的前端工程師,目前就職水滴籌。
什么是 MVP,來自偉大的百科:
Minimum Viable Product –最簡化可實(shí)行產(chǎn)品。
是指以最低成本盡可能展現(xiàn)核心概念的產(chǎn)品策略,即是指用最快、最簡明的方式建立一個可用的產(chǎn)品原型,這個原型要表達(dá)出你產(chǎn)品最終想要的效果,然后通過迭代來完善細(xì)節(jié)。該術(shù)語由弗蘭克·羅賓遜和埃里克·里斯推廣于Web應(yīng)用程序 ,它也可能涉及到進(jìn)行市場前手的分析。
前言:
Node是前端工程師的貴人,拓寬了前端工程師的能力邊界,對比前幾年用Dreamweaver寫table頁面的我來說,感受到的變化是日新月異;前端搞搞工程化和框架什么的也就算了,竟然連編輯器都自己搞?,js你說你是不是有點(diǎn)過分了?
當(dāng)然,這個過分的js幫助了我很多,從被后端大佬揪著耳朵按到工位上溫聲細(xì)語的說:“我套完頁面樣式亂了,幫我調(diào)下樣式”,演變成大佬氣沖沖的跑到我工位慈眉善目的拍著桌子說:“TM接口參數(shù)傳錯了”。
感謝Node吧,至少我可以在自己的工位上改自己寫的Bug了?。
言歸正傳,再這么貧真就寫不下去代碼了,隨著Node能力的發(fā)展,我自己感覺出來自己有點(diǎn)飄了,因?yàn)橛杏肒indle看書的陋習(xí),一直覺的市面上所有的kindle筆記軟件都是垃圾?,于是自己寫了一個滿意的垃圾;這都不算啥,我居然因?yàn)橐獪p肥,就寫了個體重記錄小程序,上線以后我沖著鏡子里渾身贅肉的自己喊:“以為自己就是Node嗎?過分”?。
體重記錄小程序的故事并沒有突兀的結(jié)束,有些用戶反饋有bug,我借口taro更新太快項目跑不起來了,而且騰訊云函數(shù)我用的很不方便,于是很不負(fù)責(zé)的停更了;在年后疫情期間,因?yàn)閷?shí)在太閑就打開了后臺留言,看到有一個莫名其妙的留言說尋求合作??♂?。
我忐忑的撥通了電話,在說明了我是小程序的開發(fā)者以后,這個人上來就開始說瞎話:“你這個小程序太好了”??♂?,他闡述了一下自己的經(jīng)歷,是一位開了8年健身房的教練,后來混不下去把健身房關(guān)了,做在線減脂指導(dǎo),竟然收入還不錯,真是造化弄人?,他咨詢我可以一起做一個減脂管理系統(tǒng)嗎?不要錢那種,我恬不知恥的說:“好呀”。
不久我們見面了,約在北京東五環(huán)外的常營龍湖·長楹天街,他問我可以吃川菜嗎?我說可以,于是我們找了一家老屋川菜館,坐在我面前的這個人因?yàn)槌粤艘豢谧约狐c(diǎn)的辣子雞,然后拿著紙巾一把鼻涕一把淚的懺悔自己是個假重慶人??♂?,我羞澀的吐掉了口中的辣椒,一起構(gòu)想了我們小程序的未來,現(xiàn)在回想起自己的高談闊論,都有些不好意思?。
正文
上邊的段子根據(jù)個人情況改編,純屬娛樂,如果沒有Node的開發(fā)能力,別人可能會安慰我:“你還是去寫個頁面把”,? 苦澀的淚水從眼眶涌出。
簡單介紹了下最近折騰的3個項目的由來,從第一個體重記錄小程序,到Kindle筆記工具,再到現(xiàn)在的一套小程序 + 后臺,作為一個前端程序員獨(dú)立作出一套可以跑起來的小系統(tǒng)還是比較有成就感的,雖然可能會被吐槽:不就是增刪改查嗎? 但是不用擔(dān)心被吐槽:又沒寫過增刪改查懂個屁??
下邊內(nèi)容介紹了3個項目的積累,重點(diǎn)貼一下第三個項目Node用到的代碼。共同交流,懇請斧正。
21天體重記錄小程序
累計7千用戶和每天不超過20個活躍用戶的數(shù)據(jù),還有3篇實(shí)踐筆記。小程序提供的Node云函數(shù) + 數(shù)據(jù)庫,可以不花一毛錢就能跑起來自己的小程序,最早是原生寫法,后來切換到Taro React語法,效率提高很多,對小程序登錄流程、云開發(fā)有了一些經(jīng)驗(yàn)積累,也意識到自己對表結(jié)構(gòu)設(shè)計的欠缺。
奇怪的是竟然累計了7千用戶,用戶從哪來的呢,難道是因?yàn)槊制鸬暮脝幔?后續(xù)準(zhǔn)備再更新探索下。


【小程序 + 云開發(fā)】體重記錄小程序 上手筆記 【小程序 + 云開發(fā) 】 隨機(jī)讀取數(shù)據(jù)并生成分享圖片 上手筆記 【小程序 + 云開發(fā)】體重排行榜 上手筆記
kindle 筆記整理工具
最早是在本地開發(fā),開發(fā)了用戶注冊、密碼找回、書籍管理、筆記管理的功能,然后買服務(wù)器部署到線上。
前端使用的 Ant Pro,因?yàn)橐肓?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">Echarts,沒有做按需引入,所以第一次進(jìn)入比較慢,目前只有自己用,就沒優(yōu)化。
整套流程跑起來以后,用著自己做的小工具覺得還挺香,也有信心做一些更大的挑戰(zhàn)了。
地址:http://nihaojob.com/



減脂管理系統(tǒng)開發(fā)
終于到今天的主題了,先說下應(yīng)用場景,學(xué)員在報名減脂教練的課程后,教練需要先了解學(xué)員日常飲食、睡眠、運(yùn)動等生活習(xí)慣,然后根據(jù)學(xué)員狀況定制運(yùn)動計劃和飲食方案,以及日常的運(yùn)營如對學(xué)員的飲食和運(yùn)動的打卡審核、積分減重排行榜、知識庫等。
主要的6個功能:
教練賬號管理 問卷收集 方案下發(fā) 打卡審核 知識管理 積分、減重排行榜
后臺預(yù)覽:



小程序預(yù)覽:

知識點(diǎn)
服務(wù)器 域名 備案
我是從滴滴云上賣的服務(wù)器,一年才幾百塊,域名是之前在騰訊買的nihaojob.com,
備案過程中滴滴說有政策調(diào)整,花了20多天的時間,備案建議提前做準(zhǔn)備,備案期間可以把Nginx + Node + Mongodb環(huán)境搭建起來。

HTTPS證書申請與Nginx配置
微信小程序的開發(fā)域名必須是HTTPS,滴滴云有免費(fèi)的證書。
證書申請后需要在域名解析匯總增加TXT記錄,不懂就問滴滴云客服,服務(wù)很nice。
證書申請成功后,把證書上傳到服務(wù)器,在Nginx的/etc/nginx/conf.d目錄下,https.conf文件中ssl_certificate、ssl_certificate_key配置證書路徑。
server {
listen 443 ssl;
server_name api.nihaojob.com;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照這個協(xié)議配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照這個套件配置
ssl_prefer_server_ciphers on;
ssl_certificate /root/sslcert/cert.crt;
ssl_certificate_key /root/sslcert/private.key;
location / {
alias /root/coach/coach-fe/dist/;
}
location /prod-api/ {
proxy_pass http://127.0.0.1:3000/;
}
}
跨域設(shè)置
這里設(shè)置了跨域請求頭,因?yàn)?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">Origin是根據(jù)入?yún)淼?,很容易造?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;">CROS攻擊,對安全系數(shù)有要求的系統(tǒng)還是用別的方案吧,也可以使用express推薦的cors中間件。
app.all('*',?function?(req,?res,?next)?{
??res.header("Access-Control-Allow-Origin",?req.headers.origin);
??res.header('Access-Control-Allow-Headers',?'Origin,?X-Requested-With,?Content-Type,?Accept,?token');
??res.header("Access-Control-Allow-Methods",?"PUT,POST,GET,DELETE,OPTIONS");
??res.header("Access-Control-Allow-Credentials",?"true");
??res.header("Content-Type",?"application/json;charset=utf-8");
??if?(req.method?==?'OPTIONS')?{
????res.send(200);?/*讓options請求快速返回*/
??}
??else?{
????next();
??}
});
express jwt使用
前端在登錄時根據(jù)用戶id生成一個Token發(fā)給前端,前端之后的所有請求都攜帶這個Token,后端根據(jù)Token解開后的用戶id來進(jìn)行數(shù)據(jù)操作。
利用jsonwebtoken生成Token,express-jwt進(jìn)行校驗(yàn)和非必需登錄接口檢查。
個人認(rèn)為開發(fā)同學(xué)都應(yīng)該深挖一下無狀態(tài)Token機(jī)制與有狀態(tài)session機(jī)制的知識點(diǎn)。
//引入
const?jwt?=?require('jsonwebtoken');
const?expressJwt?=?require('express-jwt');
//定義簽名字符串
const?secret?=?'anyThingString';
//使用中間件驗(yàn)證token合法性
app.use(expressJwt({
??secret:?secret,
??getToken:?function?fromHeaderOrQuerystring(req)?{
????if?(req.cookies?&&?req.cookies.token?||?req.headers.token)?{?//?使用query.token
??????return?req.headers.token;
????}
????return?null;
??}
}).unless({
??path:?['/login','/users/register',?'/students/login',?]??//除了這些地址,其他的URL都需要驗(yàn)證
}));
//攔截器
app.use(function?(err,?req,?res,?next)?{
??//當(dāng)token驗(yàn)證失敗時會拋出如下錯誤
??if?(err.name?===?'UnauthorizedError')?{
????//這個需要根據(jù)自己的業(yè)務(wù)邏輯來處理(?具體的err值?請看下面)
????res.status(401).send({?code:?-1,?msg:?'未登錄',?status:?41002?});
??}
});
//?解析token中間件?后續(xù)所有接口可直接使用req.tokenDecode獲取參數(shù)
app.use((req,?res,?next)?=>?{
??//?獲取token
??let?token?=?req.cookies.token?||?req.headers.token;
??if?(token)?{
????let?decoded?=?jwt.decode(token,?secret);
????req.tokenDecode?=?decoded
??}
??next()
});
//返回token給客戶端.
app.use('/login',?async?function?(req,?res)?{
??const?{?mail,?password?}=?req.body;
??//?格式校驗(yàn)
??if(?mail?===?''?||?password?===?''?){
????return?res.send({?code:?-1,?msg:?'非法參數(shù)'?});
??}
??const?userInfo??=?await?userModel.findOne({?mail:?mail,?password:?password??})
??if?(!userInfo)?{
????res.send({?code:?-1,?msg:?'信息錯誤'?});
??}?else?{
????const?{?_id,?mail,?mobile,?type?}?=?userInfo;
????//生成token
????const?token?=?jwt.sign({?_id,?mail,?mobile,?type,?},?secret,?{
??????expiresIn:?3600?*?2?//秒到期時間
????});
????res.cookie('token',?token,?{
??????maxAge:?1000?*?60?*?60?*?24?*?30,
??????httpOnly:?true
????});
????res.send({?code:?1,?msg:?'登錄成功',?status:?'ok',?data:{?userInfo,?token?}?});
??}
});
環(huán)境變量
環(huán)境變量在npm script中設(shè)置,本地開發(fā)時用nodemon yarn start,部署線上環(huán)境時使用pm2 start --name coEnd npm -- run startPro。
需要根據(jù)環(huán)境變量走不同的數(shù)據(jù)庫連接地址和圖片前綴地址,如果公眾號或者小程序有區(qū)分測試和正式環(huán)境,也可以在這里配置APPID和SECRET。
"scripts":?{
????"start":?"NODE_ENV=development?node?./bin/www",
????"startPro":?"NODE_ENV=production?node?./bin/www"
??},
//?全局配置文件?/utils/config.js
const?configBase?=?{
?mongoDb:?{?//?數(shù)據(jù)庫
????????development:'mongodb://127.0.0.1:27017/coach',
????????production:'mongodb://127.0.0.1:27017/coach',
????},
????fileUrl:{?//?圖片地址
????????development:'http://127.0.0.1:3000/',
????????production:'https://api.nihaojob.com/prod-api/',
????}
}
//?引用的配置對象?在各分模塊中調(diào)用
let?infoConfig?=?{}
let?envKey?=?process.env.NODE_ENV
//?預(yù)知環(huán)境變量
let?keys?=?['development','production']
//?拼接引用的配置對象
Object.keys(configBase).forEach(item?=>?{
?//?預(yù)知環(huán)境變量
?if(keys.includes(envKey)){
??infoConfig[item]?=?configBase[item][envKey]
?}else{//?本地
??infoConfig[item]?=?configBase[item].host
?}
})
module.exports?=?infoConfig;
//?使用
const?{?fileUrl?}?=?require("../utils/config");
Mongooes 連接
在app.js中執(zhí)行 require('./utils/dbs')(),并且把DB實(shí)例掛到global上。
//?配置文件
var?baseConfig?=?require('./config.js');
const?dbs?=?async?function?(env)?{
????const?mongoose?=?require('mongoose');
????mongoose.connect(baseConfig.mongoDb,?{?useNewUrlParser:?true,?auto_reconnect:?true,?poolSize:?10?});
????const?db?=?mongoose.connection;
????db.on('error',?console.error.bind(console,?'數(shù)據(jù)庫鏈接失敗'));
????db.once('open',?callback?=>?{
????????console.log(`數(shù)據(jù)庫鏈接成功,地址:${baseConfig.mongoDb}`)
????});
????global.db?=?db;
}
module.exports?=?dbs;
Mongooes 增刪改
這部分不多說了,利ORM框架Mongooes增刪改特別簡單,先創(chuàng)建模型再根據(jù)模型操作。
const?mongoose?=?require('mongoose');
const?{?db?}?=?global;
//?創(chuàng)建Model
const?model?=?new?mongoose.Schema({
????coachId:String,?//?教練ID
},?{
????timestamps:?true,?//?自動增加創(chuàng)建、更新時間
});
const?dbManage?=?db.model('tag',?model);
//?創(chuàng)建
dbManage.create({?name:''?})
//?更新?前邊為查詢條件?后邊為更新內(nèi)容
dbManage.updateOne({_id:'id'},{?name:''?})
//?刪除
dbManage.remove({_id?:?'id'})
//?查找
dbManage.find({_id?:?'id'?})
dbManage.findOne({_id?:?'id'?})
Mongooes 查詢
查詢的功能比較多了,比如字符串模糊查詢,常見的分頁、排序,時間范圍搜索等。直接貼一個模板吧,copy直接用版,哈哈。
router.get('/getList',?async?function(req,?res,?next)?{
????const?{?nickName?=?'',?name?=?'',?tel?=?'',?newStatus?=?'',?start?=?'1900-01-01',?end?=?'2222-01-01'??}?=?req.query
????const?{?current?=?1,?pageSize?=?20??}?=?req.query
????const?startTime?=?moment(start).startOf('day')
????const?endTime?=?moment(end).startOf('day')
????const?queryParams?=?{
????????$and:?[
????????????{?nickName:?{?$regex:?new?RegExp(nickName,?'i')?}?},?//?昵稱模糊查詢
????????????{?name:?{?$regex:?new?RegExp(name,?'i')?}?},?//?姓名模糊查詢
????????????{?tel:?{?$regex:?new?RegExp(tel,?'i')?}?},???//手機(jī)號模糊查詢
????????],
????????coachId:?req.tokenDecode._id,
????????createdAt:?{
????????????$gte:?startTime.toDate(),
????????????$lte:?moment(endTime).endOf('day').toDate()
????????????//?$lte:?moment(today).endOf('day').toDate()?//?查詢當(dāng)天
????????}
????}
????if?(newStatus?!==?''){
????????//?數(shù)字狀態(tài)模糊查詢
????????queryParams.$and.push({?newStatus:?newStatus?},)
????}
????//?分頁?skip跳過數(shù)?limit每頁數(shù)?sort排序方式
????const?resault?=?await?studentsModel.find(queryParams).skip(Number(pageSize)?*?Number(current-1)).limit(Number(pageSize)).sort({?createdAt:?'desc'?});
????//?總數(shù)
????const?total?=?await?studentsModel.find(queryParams).count()
????res.send({
????????code:1,
????????data:{
????????????list:?resault,
????????????page:?{
????????????????total,
????????????????current:?Number(current),?//?當(dāng)前頁
????????????????pageSize:?Number(pageSize)
????????????}
????????},
????});
});
圖片上傳
很多地方都要用到圖片上傳,使用formidable插件,設(shè)置上傳路徑為public,根據(jù)環(huán)境變量 + 文件名拼接圖片地址,單獨(dú)把圖片地址存到一張表中,方便其他地方復(fù)用。
var?express?=?require('express');
var?router?=?express.Router();
const?path?=?require('path');
const?formidable?=?require('formidable');
var?inFileModel?=?require("../model/inFile");
//?環(huán)境變量配置
const?{?fileUrl?}?=?require("../utils/config");
//?上傳文件
router.post('/',?async?function?(req,?res,?next)?{
????var?imgPath?=?path.dirname(__dirname)?+?'/public';
????var?form?=?new?formidable.IncomingForm();
????form.encoding?=?'utf-8';?//設(shè)置編輯
????form.uploadDir?=?imgPath;?//設(shè)置上傳目錄
????form.keepExtensions?=?true;?//保留后綴
????form.maxFieldsSize?=?2?*?1024?*?1024;?//文件大小
????form.type?=?true;
????const?awaitFn?=?()?=>?{
????????return?new?Promise((resolve,?reject)?=>?{
????????????form.parse(req,?(err,?fields,?files)?=>?{
????????????????let?src?=?files.file.path.split('/');
????????????????let?urlString?=?src[src.length?-?1]
????????????????if?(err)?{?reject(err);?}
????????????????resolve(urlString);
????????????});
????????});
????};
????//?文件名
????const?filePath?=?await?awaitFn();
????//?儲存上傳記錄
????const?fileInfo?=?await?inFileModel.create({?fileName:fileUrl?+?filePath,?})
????res.send({?code:1,?msg:'上傳成功',?data:fileInfo?})
});
module.exports?=?router;
定時獲取accesstoken
有過微信開發(fā)經(jīng)驗(yàn)的同學(xué)都知道,調(diào)用微信服務(wù)端api需要accesstoken,時效2小時,利用CronJob定時獲取accesstoken并保存成文件,獲取失敗時利用nodemailer發(fā)送報警郵件。
在部署時單獨(dú)跑一個PM2進(jìn)程,pm2 start cronTask.js。
//?cronTask.js
var?CronJob?=?require('cron').CronJob;
const?moment?=?require('moment');
const?{?writeFile?}?=?require('fs').promises
const?axios?=?require('axios')
var?weConfig?=?require('./utils/weConfig');
var?nodemailer?=?require('nodemailer');
//?獲取微信token
var?getWeToken?=?new?CronJob('0?0?0/1?*?*?*',?async?function?()?{
??console.log('獲取token任務(wù)執(zhí)行:',?moment().format('YYYY-MM-DD?HH:mm:ss'));
??const?{?APPID,?SECRET?}?=?weConfig
??const?url?=?`https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${SECRET}`
??axios.get(url).then(res?=>?{
????if?(res?&&?res.status?===?200?&&?res.data)?{
??????//?保存accesstoken文件
??????writeFile('./accesstoken.txt',?res.data.access_token,?'utf8')
????}?else?{
??????//?發(fā)送報警內(nèi)容
??????sendMail('[email protected]',?'保存錯誤:'?+?JSON.stringify(res))
????}
??})
},?null,?true,?'America/Los_Angeles');
function?sendMail(mail,?text)?{
??//?發(fā)送郵箱配置
??var?mailTransport?=?nodemailer.createTransport({
????host:?'smtp.163.com',
????port:?465,
????secureConnection:?true,?//?使用SSL方式(安全方式,防止被竊取信息)
????auth:?{
??????user:?'郵箱',
??????pass:?'密碼'
????},
??});
??const?options?=?Object.assign(mailTransport,?{?from:?'[email protected]',?to:?mail?},?{
????subject:?'token獲取失敗報警',
????text:?text,
??})
??mailTransport.sendMail(options,?(err,?msg)?=>?{
????if?(err)?{
??????res.send({?code:?-1,?msg:?err?});
??????return
????}
????mailTransport.close();
??});
}
getWeToken.start();
生成小程序參數(shù)二維碼
讀取accesstoken.txt獲取token,利用axios發(fā)送給微信服務(wù)器獲取圖片,這塊有個點(diǎn)需要注意,請求會直接返回圖片,需設(shè)置responseType: 'arraybuffer'直接把buffer數(shù)據(jù)保存為圖片。
var?express?=?require('express');
var?router?=?express.Router();
const?{?fileUrl?}?=?require("../utils/config");
const?{?writeFile,?readFile?}?=?require('fs').promises
const?axios?=?require('axios')
router.post('/getQrcode',?async?function?(req,?res,?next)?{
??const?{?path?=?'',?scene?=?''?}?=?req.body;
??//?格式校驗(yàn)
??if?(path?===?''){
????res.send({?code:?-1,?msg:?'非法參數(shù)'?});
????return
??}
??const?accessToken?=?await?readFile('./accesstoken.txt')
??const?url?=?`https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${accessToken.toString()}`
??const?data?=?await?axios.post(url,{
????scene:?scene,
????page:?path,
????width:430,
??},{
????headers:{
??????"Content-Type":?"application/json"
????},
????responseType:?'arraybuffer',
??})
??if?(data.data){
????const?fileName?=??'qrCode_'?+?new?Date().getTime()?+?'.jpg'
????await?writeFile('./public/'?+?fileName,?data.data)
????res.send({?code:?1,?data:?fileUrl?+?fileName?});
??}else{
????res.send({?code:-1,?msg:'請求錯誤'?})
??}
});
module.exports?=?router;
for await使用
我們有一個用戶列表,需要根據(jù)用戶列表里的用戶id查詢另外一張列表里的用戶詳情,將他們拼接成一個新的列表返回給前端,我不太會用用、關(guān)聯(lián)查詢,探索出一個比較笨的方法,用for await這種方法實(shí)現(xiàn)的。
router.get('/getTopList',?async?function?(req,?res,?next)?{
????const?{?coachId?}?=?req.tokenDecode;
????let?data?=?await?topModel.find({?coachId?}).sort({?integral:?'desc'?});
????if?(data?&&?data.length?!==?0)?{
????????data?=?JSON.parse(JSON.stringify(data))
????????for?(const?item?of?data)?{
????????????const?info?=?await?studentsModel.findOne({?_id:?item.studentsId})
????????????item.info?=?info
????????}
????}
????res.send({code:?1,?data?})
});
taro小程序
這篇筆記的重點(diǎn)在Node上,就不展開聊了,簡單寫下登錄、request封裝、環(huán)境變量。
登錄
登錄的流程是,用戶點(diǎn)擊openType為getUserInfo的按鈕發(fā)起授權(quán),授權(quán)成功后調(diào)用Taro.login獲取code,再把code發(fā)給后端,后端通過code、APPID、SECRET獲取openid,剩下的就是用openid來綁定用戶關(guān)系了。
getUserInfo按鈕 => 授權(quán) => getCode => 獲取openid
taro-request
taro官網(wǎng)上有一個taro-request的封裝,蠻好用的地址。
環(huán)境變量
Taro的環(huán)境變量從process.env.NODE_ENV中讀取,內(nèi)置環(huán)境變量為development、production,前端需要根據(jù)環(huán)境變量走不同的環(huán)境。
const?getBaseUrl?=?(url)?=>?{
??let?BASE_URL?=?'';
??if?(process.env.NODE_ENV?===?'development')?{
????//開發(fā)環(huán)境
????BASE_URL?=?'http://localhost:3000'
??}?else?{
????//?生產(chǎn)環(huán)境
????BASE_URL?=?'https://api.nihaojob.com/prod-api'
??}
??return?BASE_URL
}
export?default?getBaseUrl;
后臺部分
后臺使用vue-element-admin模板,幾乎沒有復(fù)雜的內(nèi)容,接入了圖表、富文本、圖片上傳,就不展開了,后續(xù)會開發(fā)發(fā)菜單、權(quán)限管理,有可能使用node-casbin或acl實(shí)現(xiàn)。
部署
前端靜態(tài)文件直接使用Nginx指定靜態(tài)目錄,后端接口通過PM2啟動服務(wù),并用Nginx的proxy_pass轉(zhuǎn)到后端服務(wù)端口上。HTTPS證書申請與Nginx配置小節(jié)中有貼出,列一下自己最常用的幾個PM2命令。
//?npm?script?啟動應(yīng)用
$?pm2?start?--name?應(yīng)用名稱?npm?--?run?'npm?sctipt名稱'
//?應(yīng)用列表
$?pm2?list
//?查看日志
$?pm2?log
//?重啟應(yīng)用
$?pm2?restart?'應(yīng)用id'

總結(jié)
開頭段子中有調(diào)侃后端大佬,純粹只是玩笑;互聯(lián)網(wǎng)技術(shù)日新月異,大家都在齊頭并進(jìn),前端的內(nèi)容都學(xué)不完,又怎敢對不懂的行業(yè)指手畫腳。希望自己擁抱變化,保持敬畏。
聽說每個程序員都有一個創(chuàng)業(yè)夢,前端工程師真的可以借助Node跑起來自己的第一個MVP。
關(guān)于本文
作者:愚坤 https://juejin.cn/post/6898612811891474440
·END·
匯聚精彩的免費(fèi)實(shí)戰(zhàn)教程
喜歡本文,點(diǎn)個“在看”告訴我


