<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node.js 開發(fā)實(shí)踐,前端工程師的MVP利器

          共 14284字,需瀏覽 29分鐘

           ·

          2020-12-31 07:12

          作者:愚坤,掘金優(yōu)秀作者,一名沒上高中的前端工程師,目前就職水滴籌。

          https://juejin.cn/post/6898612811891474440

          什么是 MVP,來自偉大的百科:

          Minimum Viable Product –最簡化可實(shí)行產(chǎn)品。

          是指以最低成本盡可能展現(xiàn)核心概念的產(chǎn)品策略,即是指用最快、最簡明的方式建立一個(gè)可用的產(chǎn)品原型,這個(gè)原型要表達(dá)出你產(chǎn)品最終想要的效果,然后通過迭代來完善細(xì)節(jié)。該術(shù)語由弗蘭克·羅賓遜和埃里克·里斯推廣于Web應(yīng)用程序 ,它也可能涉及到進(jìn)行市場前手的分析。

          前言:

          Node是前端工程師的貴人,拓寬了前端工程師的能力邊界,對比前幾年用Dreamweaver寫table頁面的我來說,感受到的變化是日新月異;前端搞搞工程化和框架什么的也就算了,竟然連編輯器都自己搞?,js你說你是不是有點(diǎn)過分了?

          當(dāng)然,這個(gè)過分的js幫助了我很多,從被后端大佬揪著耳朵按到工位上溫聲細(xì)語的說:“我套完頁面樣式亂了,幫我調(diào)下樣式”,演變成大佬氣沖沖的跑到我工位慈眉善目的拍著桌子說:“TM接口參數(shù)傳錯(cuò)了”。

          感謝Node吧,至少我可以在自己的工位上改自己寫的Bug了?。

          言歸正傳,再這么貧真就寫不下去代碼了,隨著Node能力的發(fā)展,我自己感覺出來自己有點(diǎn)飄了,因?yàn)橛杏肒indle看書的陋習(xí),一直覺的市面上所有的kindle筆記軟件都是垃圾?,于是自己寫了一個(gè)滿意的垃圾;這都不算啥,我居然因?yàn)橐獪p肥,就寫了個(gè)體重記錄小程序,上線以后我沖著鏡子里渾身贅肉的自己喊:“以為自己就是Node嗎?過分”?。

          體重記錄小程序的故事并沒有突兀的結(jié)束,有些用戶反饋有bug,我借口taro更新太快項(xiàng)目跑不起來了,而且騰訊云函數(shù)我用的很不方便,于是很不負(fù)責(zé)的停更了;在年后疫情期間,因?yàn)閷?shí)在太閑就打開了后臺(tái)留言,看到有一個(gè)莫名其妙的留言說尋求合作??♂?。

          我忐忑的撥通了電話,在說明了我是小程序的開發(fā)者以后,這個(gè)人上來就開始說瞎話:“你這個(gè)小程序太好了”??♂?,他闡述了一下自己的經(jīng)歷,是一位開了8年健身房的教練,后來混不下去把健身房關(guān)了,做在線減脂指導(dǎo),竟然收入還不錯(cuò),真是造化弄人?,他咨詢我可以一起做一個(gè)減脂管理系統(tǒng)嗎?不要錢那種,我恬不知恥的說:“好呀”。

          不久我們見面了,約在北京東五環(huán)外的常營龍湖·長楹天街,他問我可以吃川菜嗎?我說可以,于是我們找了一家老屋川菜館,坐在我面前的這個(gè)人因?yàn)槌粤艘豢谧约狐c(diǎn)的辣子雞,然后拿著紙巾一把鼻涕一把淚的懺悔自己是個(gè)假重慶人??♂?,我羞澀的吐掉了口中的辣椒,一起構(gòu)想了我們小程序的未來,現(xiàn)在回想起自己的高談闊論,都有些不好意思?。

          正文

          上邊的段子根據(jù)個(gè)人情況改編,純屬娛樂,如果沒有Node的開發(fā)能力,別人可能會(huì)安慰我:“你還是去寫個(gè)頁面把”,? 苦澀的淚水從眼眶涌出。

          簡單介紹了下最近折騰的3個(gè)項(xiàng)目的由來,從第一個(gè)體重記錄小程序,到Kindle筆記工具,再到現(xiàn)在的一套小程序 + 后臺(tái),作為一個(gè)前端程序員獨(dú)立作出一套可以跑起來的小系統(tǒng)還是比較有成就感的,雖然可能會(huì)被吐槽:不就是增刪改查嗎??但是不用擔(dān)心被吐槽:又沒寫過增刪改查懂個(gè)屁??

          下邊內(nèi)容介紹了3個(gè)項(xiàng)目的積累,重點(diǎn)貼一下第三個(gè)項(xiàng)目Node用到的代碼。共同交流,懇請斧正。

          21天體重記錄小程序

          累計(jì)7千用戶和每天不超過20個(gè)活躍用戶的數(shù)據(jù),還有3篇實(shí)踐筆記。小程序提供的Node云函數(shù) + 數(shù)據(jù)庫,可以不花一毛錢就能跑起來自己的小程序,最早是原生寫法,后來切換到Taro React語法,效率提高很多,對小程序登錄流程、云開發(fā)有了一些經(jīng)驗(yàn)積累,也意識(shí)到自己對表結(jié)構(gòu)設(shè)計(jì)的欠缺。

          奇怪的是竟然累計(jì)了7千用戶,用戶從哪來的呢,難道是因?yàn)槊制鸬暮脝幔?后續(xù)準(zhǔn)備再更新探索下。

          • 【小程序 + 云開發(fā)】體重記錄小程序 上手筆記
          • 【小程序 + 云開發(fā) 】 隨機(jī)讀取數(shù)據(jù)并生成分享圖片 上手筆記
          • 【小程序 + 云開發(fā)】體重排行榜 上手筆記

          kindle 筆記整理工具

          最早是在本地開發(fā),開發(fā)了用戶注冊、密碼找回、書籍管理、筆記管理的功能,然后買服務(wù)器部署到線上。

          前端使用的?Ant Pro,因?yàn)橐肓?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);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é)員在報(bào)名減脂教練的課程后,教練需要先了解學(xué)員日常飲食、睡眠、運(yùn)動(dòng)等生活習(xí)慣,然后根據(jù)學(xué)員狀況定制運(yùn)動(dòng)計(jì)劃和飲食方案,以及日常的運(yùn)營如對學(xué)員的飲食和運(yùn)動(dòng)的打卡審核、積分減重排行榜、知識(shí)庫等。

          主要的6個(gè)功能:

          1. 教練賬號(hào)管理
          2. 問卷收集
          3. 方案下發(fā)
          4. 打卡審核
          5. 知識(shí)管理
          6. 積分、減重排行榜

          后臺(tái)預(yù)覽:

          小程序預(yù)覽:

          知識(shí)點(diǎn)

          服務(wù)器 域名 備案

          我是從滴滴云上賣的服務(wù)器,一年才幾百塊,域名是之前在騰訊買的nihaojob.com, 備案過程中滴滴說有政策調(diào)整,花了20多天的時(shí)間,備案建議提前做準(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; #按照這個(gè)協(xié)議配置
          ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;#按照這個(gè)套件配置
          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="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Origin是根據(jù)入?yún)淼?,很容易造?code style="margin-right: 2px;margin-left: 2px;padding: 2px 4px;font-size: 14px;border-radius: 4px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.047);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使用

          前端在登錄時(shí)根據(jù)用戶id生成一個(gè)Token發(fā)給前端,前端之后的所有請求都攜帶這個(gè)Token,后端根據(jù)Token解開后的用戶id來進(jìn)行數(shù)據(jù)操作。

          利用jsonwebtoken生成Token,express-jwt進(jìn)行校驗(yàn)和非必需登錄接口檢查。

          個(gè)人認(rèn)為開發(fā)同學(xué)都應(yīng)該深挖一下無狀態(tài)Token機(jī)制與有狀態(tài)session機(jī)制的知識(shí)點(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)證失敗時(shí)會(huì)拋出如下錯(cuò)誤
          ??if?(err.name?===?'UnauthorizedError')?{
          ????//這個(gè)需要根據(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:?'信息錯(cuò)誤'?});
          ??}?else?{
          ????const?{?_id,?mail,?mobile,?type?}?=?userInfo;
          ????//生成token
          ????const?token?=?jwt.sign({?_id,?mail,?mobile,?type,?},?secret,?{
          ??????expiresIn:?3600?*?2?//秒到期時(shí)間
          ????});

          ????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ā)時(shí)用nodemon yarn start,部署線上環(huán)境時(shí)使用pm2 start --name coEnd npm -- run startPro

          需要根據(jù)環(huán)境變量走不同的數(shù)據(jù)庫連接地址和圖片前綴地址,如果公眾號(hào)或者小程序有區(qū)分測試和正式環(huán)境,也可以在這里配置APPIDSECRET。

          "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,?//?自動(dòng)增加創(chuàng)建、更新時(shí)間
          });

          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 查詢

          查詢的功能比較多了,比如字符串模糊查詢,常見的分頁、排序,時(shí)間范圍搜索等。直接貼一個(gè)模板吧,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ī)號(hào)模糊查詢
          ????????],
          ????????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();
          ????//?儲(chǔ)存上傳記錄
          ????const?fileInfo?=?await?inFileModel.create({?fileName:fileUrl?+?filePath,?})
          ????res.send({?code:1,?msg:'上傳成功',?data:fileInfo?})
          });
          module.exports?=?router;

          定時(shí)獲取accesstoken

          有過微信開發(fā)經(jīng)驗(yàn)的同學(xué)都知道,調(diào)用微信服務(wù)端api需要accesstoken,時(shí)效2小時(shí),利用CronJob定時(shí)獲取accesstoken并保存成文件,獲取失敗時(shí)利用nodemailer發(fā)送報(bào)警郵件。

          在部署時(shí)單獨(dú)跑一個(gè)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ā)送報(bào)警內(nèi)容
          ??????sendMail('[email protected]',?'保存錯(cuò)誤:'?+?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獲取失敗報(bào)警',
          ????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ù)器獲取圖片,這塊有個(gè)點(diǎn)需要注意,請求會(huì)直接返回圖片,需設(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:'請求錯(cuò)誤'?})
          ??}
          });
          module.exports?=?router;

          for await使用

          我們有一個(gè)用戶列表,需要根據(jù)用戶列表里的用戶id查詢另外一張列表里的用戶詳情,將他們拼接成一個(gè)新的列表返回給前端,我不太會(huì)用用、關(guān)聯(lián)查詢,探索出一個(gè)比較笨的方法,用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)擊openTypegetUserInfo的按鈕發(fā)起授權(quán),授權(quán)成功后調(diào)用Taro.login獲取code,再把code發(fā)給后端,后端通過code、APPIDSECRET獲取openid,剩下的就是用openid來綁定用戶關(guān)系了。

          getUserInfo按鈕 => 授權(quán) => getCode => 獲取openid

          taro-request

          taro官網(wǎng)上有一個(gè)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;

          后臺(tái)部分

          后臺(tái)使用vue-element-admin模板,幾乎沒有復(fù)雜的內(nèi)容,接入了圖表、富文本、圖片上傳,就不展開了,后續(xù)會(huì)開發(fā)發(fā)菜單、權(quán)限管理,有可能使用node-casbinacl實(shí)現(xiàn)。

          部署

          前端靜態(tài)文件直接使用Nginx指定靜態(tài)目錄,后端接口通過PM2啟動(dòng)服務(wù),并用Nginxproxy_pass轉(zhuǎn)到后端服務(wù)端口上。HTTPS證書申請與Nginx配置小節(jié)中有貼出,列一下自己最常用的幾個(gè)PM2命令。

          //?npm?script?啟動(dòng)應(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è)指手畫腳。希望自己擁抱變化,保持敬畏。

          聽說每個(gè)程序員都有一個(gè)創(chuàng)業(yè)夢,前端工程師真的可以借助Node跑起來自己的第一個(gè)MVP。

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 63
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲日本黄色片 | 天天干天天做 | 中文字幕亚洲高清 | 青青草激情在线 | 亚洲av电影天堂 亚洲AV高清在线 亚洲AV综合伊人 亚洲sv剧情在线 |