目录 | 课程名 | 备注 |
---|---|---|
入门必学 | nodejs入门到企业web开发中的应用 | |
框架与工具 | node.js+koa2+mysql打造前后端分离精品项目《旧岛》 | |
项目实战 |
20190317-20200720:《imooc-nodejs入门到企业web开发中的应用》 |
一、课程安排
1、nodejs核心api
2、静态资源服务器 && http协议
3、项目代码构建、打包
4、api 单元测试&ui测试
5、headless爬虫
6、回顾总结
二、nodejs创建项目
1、cd Desktop
2、express mockData
3、cd mockData
4、npm i
5、npm start
(aSuncat) 三、node热更新
1、npm install --sava-dev nodemon
2、项目根目录新增文件nodemon.json
{
"restartable": "rs",
"ignore": [
".git",
".svn",
"node_modules/**/node_modules"
],
"verbose": true,
"execMap": {
"js": "node --harmony"
},
"watch": [
"routes/"
],
"env": {
"NODE_ENV": "development"
},
"ext": "js json"
}
watch: routes/指的是监控routes文件夹下的所有文件
ext:js json指的是监控后缀为js json的文件
3、package.json
scripts添加
"hotStart": "nodemon"
4、浏览器输入localhost:3000就能访问到网页了。
四、如果是创建最简单的服务
1、创建一个js,server.js
var http = require('http');
http.createServer((req, res) => {
console.log(`request come:${req.url}`)
res.end('123');
}).listen('8080');
console.log('请求成功了');
2、node server.js
3、浏览器输入localhost:8080,就能看到结果了。
一、nodejs
1、非阻塞I/O
input output,计算机输入输出,键盘,显示机,打印机,都是I/O设备,读写磁盘,网络操作
2、阻塞:I/O时进程休眠等待I/O完成后进行下一步
3、非阻塞:I/O时函数立即返回,进程不等待I/O完成
4、cpu:计算机1秒钟执行30亿条指令
二、事件驱动
1、I/O等异步操作结束后的通知
2、观察者模式
一、nodejs好处/nodejs适用场景
1、处理高并发、I/O密集的web场景性能优势明显
二、cpu密集 vs I/O密集
cpu密集:压缩、解压、加密、解密(计算、逻辑判断)
I/O密集:文件操作、网络操作、数据库
三、web常见场景
1、静态资源获取
2、数据库操作
3、渲染页面:读取模板文件,生成html
四、高并发应对之道(高并发:单位时间内访问量特别大)
1、增加机器数
2、增加每台机器的cpu数-多核
五、进程
1、进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位
2、多进程:启动多个进程,多个进程可以一块执行多个任务。
3、单进程,一个cpu只开一个进程,一个进程只开一个线程。
4、单线程只是针对主线程,I/O操作系统底层多线程调度。
5、单线程并不是单进程
六、主进程 event loop
七、单线程
1、单线程的好处:
(1)多线程占用内存高
(2)多线程间切换使得CPU开销大
(3)多线程由内存同步开销
(4)编写单线程程序简单
(5)线程安全
2、单线程的劣势:
(1)CPU密集型任务占用CPU时间长
(2)无法利用CPU的多核
(3)单线程抛出异常使得程序停止
八、常用场景
1、web server
2、本地代码构建
3、使用工具开发
一、环境
1、安装nodejs
nodejs.org进入官网
二、commonjs
1、commonjs是nodejs使用的模块规范
2、没有window这个全局对象,有window
3、process
4、每个文件是一个模块,有自己的作用域
5、在模块内部module变量代表模块本身
6、module.exports属性代表模块对外接口
三、node
1、 node test.js // 运行test.js
2、node --inspect-brk test.js 调试test.js,在chrome浏览器的调试工具f12的sources的test.js中就能看到调试信息
(function(exports, require, module, _filename, __dirname) { // module代表模块本身
console.log('this is a test');
}
);
3、chrome打开inspects
一、require规则
1、/表示绝对路径,./表示相对于当前文件的路径。
2、支持js、json、node扩展名,不写依次尝试
3、不写路径则认为是build-in模块或者各级node_modules内的第三方模块
二、require特性
1、module被加载的时候执行,加载后缓存(当我们加载一个模块时,这个模块里的内容都会被执行)
2、一旦出现某个模块被循环加载(A require B, B require A),就只输出已经执行的部分,还有未执行的部分不会输出。
一、异步I/O,执行之后立即返回,不在乎结果是什么
二、buffer:缓冲,二进制数据处理
三、fs是用来操作二进制流的
四、npm install… 的时候,会创建node_modules,把相关依赖下载到node_mobules;
五、npm root -g 得到全局的node_modules依赖
一、exports是module.exports的快捷方式
exports.test = 1; // 等同于module.exports.test = 1;
二、不能改变exports的指向
exports = { // exports修改了指向,不再生效了
a: 1,
b: 2,
test: 3
}
module.exports = { // 应该写成module.exports,这是exports的指向仍是module,所以会生效
a: 1,
b: 2,
test: 3
}
一、global自带属性方法
1、CommonJS
2、Buffer、 process、 console
3、timer:setTimeout()、setInterval()、clearTimeout()、clearInterval()、setImmediate
二、global特性
1、直接在模块中写的是局部变量。
2、模块中写了global会变成全局变量
testStr = '1'; // 局部变量
global.testStr = '2'; // 全局变量
一、process 进程
uncaughtException
:异常的捕获
1、process属性
const {argv, argv0, execArgv, execPath} = process; // argv:启动process时所有的参数; argv0:保存了argv第一个值的引用(不常用);execArgv:调用node传入的特殊参数(写在文件名之前的参数是不会进入argv的统计的); execPath:调用它的脚本的路径
argv.forEach(item => { // node安装的路径,当前文件路径
console.log(item);
});
2、process方法
process.cwd():当前process执行的路径
// 1
setImmediate(() => {console.log('setImmediate')}) // 等下一个事件队列, 与时间无关。放在下一队列的队首。比setTimeout()慢
// 2
process.nextTick(() => { // 当前事件队列中的东西执行完了,再执行它。比setImmediate执行得早。放在当前队列的队尾。比setTimeout() 快
console.log('nextTick');
})
一般情况下用setImmediate()
如果nextTick()循环调用,后面正常的异步就没办法执行了。
一、调试
1、inspector
进入官网,node-docs-inspector,可以看到api
控制台执行node --inspect-brk test.js
2、vscode:编辑器
一、path:处理和路径相关的一切
path.join()也会调用nomalize
path.resolve():根据相对路径,得到绝对路径
const {basename, dirname, extname} = require('path'); // basename:文件名;dirname:所在路径;extname:扩展名
process.env.PATH可以看到path
二、nodejs.cn:nodejs中文网站
一、path
1、__dirname、__filename总是返回文件的绝对路径
2、process.cwd()总是返回执行node命令所在文件夹
二、./
1、在require方法中总是相对当前文件所在文件夹
2、在其他地方和process.cwd()一样,相对Node启动文件夹
一、常用的:file和网络
二、buffer三个要点
1、buffer用于处理二进制数据流
2、实例类似整数数组,大小固定
3、c++代码在v8堆外分配物理内存
三、nodejs跑的代码并不纯粹是用javascript跑的,还有一部分是c++
四、buffer是一个全局对象
一、buffer静态属性方法(类的方法)
Buffer.byteLength();
Buffer.isBuffer();
Buffer.concat();
二、实例常用的属性和方法
buf.legnth
buf.toString() // 默认是utf-8,buf.toString('base64')
buf.fill()
buf.equals()
buf.indexOf()
buf.copy()
一、中文乱码问题
const StringDecoder = require('string_decode');
const decoder = new StringDecoder('utf8');
const buf = Buffer.from('中文字符串'); // 3个字符表达一个汉字
for (let i = 0; i < buf.legnth; i += 5) {
const b = Buffer.allocUnsafe(5);
buf.copy(b, 0, i);
// console.log(b.toString()); // 乱码
console.log(decoder.write(b)); // 正常
}
一、events
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('error', (err, time) => {
console.log('触发事件', err);
console.log('时间戳:', time);
});
myEmitter.emit('error', new Error('test error!'), Date.now());
一、removeListener('error', fn1);
removeAllListeners('error');
一、
const fs = require('fs');
const content = Buffer.from('this is a test.');
fs.writeFile('./test', content, err => {
if (err) throw err;
console.log('done');
});
二、可以用stat判断文件是否存在
const fs = require('fs');
fs.stat(filePath, (err, stats) => {
if (err) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end(`${filePath} is not a directory or file`);
return;
}
if (stats.isFile()) {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
fs.createReadStream(filePath).pipe(res);
} else if (stats.isDirectory()) {
fs.readdir(filePath, (err,files) => {
res.statusCode = 200;
res.setHeader(res.join(','));
})
}
})
三、建议大家不要用同步的方式去判断,如果在高并发情况下,会阻塞其他用户。
四、
const fs = require('fs');
fs.rename('./text', 'test.txt', err => {
if(err) throw err;
console.log('done!');
});
五、watch可以watch任何内容,watchFile只能watch一个文件
六、recursive美 /rɪˈkɜːrsɪv/ adj, 递归的,循环的
一、node热更新,supervisor,只能监听js文件的变化,不能监听tpl文件的变化。
npm i -g supervisor
一、模板引擎 handlebars
1、handlebars官网文档:https://www.handlebarsjs.cn/
2、安装
npm i handlebars
3、使用
const Handlebars = require('handlebars');
二、不同目录下启动,得到的相对路径是不同的。
eg:有个文件,路径为~/Users/imooc/test/routes/node01.js,这个文件里写了这段代码
const source = fs.readFileSync('../template/dir.tpl');
其他它想调用的文件路径是~/Users/imooc/test/template/dir.tpl
以下列举不同的两种情况
1、终端: cd ~/imooc/test
node node01.js
此时路径为 ~/Users/imooc/template/dir.tpl
2、终端:cd ~/imooc/test/routes
node node01.js
此时路径为~/Users/imooc/test/template/dir.tpl
三、解决方案:path,不同目录下启动,用相对路径获取文件
const path = require('path');
const tplpath = path.join(__dirname, '../template/dir.tpl');
const source = fs.readFileSync(tplPath);
一、compile接收的字符串,readFileSync读出来的是buffer,以下两种处理方式,推荐使用2,因为2更快。
1、utf-8
const source = fs.readFileSync(tplPath, 'utf-8'); // 读出来的是buffer,加上'utf-8',把buffer转换成字符串
const template = Handlebars.compile(source);
2、toString()
const source = fs.readFileSync(tplPath);
const template = Handlebars.compile(source.toString())
一、config/defaultConfig.js
module.exports = {
root: process.cwd(); // 项目根目录
hostname: '127.0.0.1',
port: 9527
}
二、如果使用的是require,可以放心地使用相对路径。
三、Content-Type
一、
compress: /\.(html|js|css|md)/
二、压缩的文件,浏览器支持的压缩方式request的accept-encoding
,告诉浏览器我们使用的压缩方式
Accept-Encoding
Content-Encoding
module.exports = (rs, req, res) => {
const acceptEncoding = req.headers['accept-encoding'];
if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
return rs;
} else if () {
res.setHeader('Content-Encoding', 'gzip');
}
}
一、range: bytes=[start]-[end]
Accept-Ranges: bytes
Content-Range: bytes start-ent/total
二、
module.export = (totalSize, req, res) => {
const sizes = range.match(/bytes=(\d*)-(\d*)/);
}
一、缓存
二、缓存header
1、Exprires, Cache-Control
2、If-Modified-Since / Last-modified
3、If-None-Match / Etag
三、
if (expires) {
res.setHeader('Expires', (new Date(Date.now() + maxAge * 1000).toUTCString()))
}
if (cacheControl) {
res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
}
if (lastModified) {
res.setHeader('Last-Modified', stats.mtime.toUTCString());
}
if (etag) {
res.setHeader('Etag', `${stats.size}-${stats.mtime}`);
}
一、处理process.args,如-p --port是同种东西,就直接处理成相同的
1、commander
2、yargs
npm i yargs
二、使用
const yargs = require('yargs');
const argv = yargs
.usage('anywhere [options]')
.option('p', {
alias: 'port',
describe: '端口号',
default: 9527
})
chmod +x bin/anydoor 修改执行权限
ll bin/anydoor
三、
1、代码提交代码到gitlab
2、版本号x.y.z
1.2.*和~1.2.0是一样的
一、自动打开浏览器
openUrl.js
const { exec } = require('child_process');
module.exports = url => {
switch (process.platform) {
case 'darwin': // mac
exec(`open ${url}`);
break;
case 'win32': // windows
exec(`start ${url}`);
break;
}
}
app.js
const openUrl = require('./helper/openUrl');
server.iisten(port, hostname, () => {
const addr = `http://${hostname}:${port}`;
console.info(addr);
openUrl(addr);
});
二、包
1、上传包
(1)登录npm官方站点,注册账号
(2)终端执行
npm login
npm publish
3、使用
npm i -g anydoor
anydoor -p 9906 -d /usr
一、nodejs在前端的用法
1、web server
2、前端开发周边工具
二、npm install gulp -g -D
-D 代表–save-dev
三、如果别人的gulp装在全局,自己把别人的项目download下来后,
在gulpfile.js文件中引入gulpimport gulp from 'gulp'
terminal运行程序时报错:command not found: gulp
1、原因:自己本地没有全局的gulp,只是在本项目中安装了gulp
2、解决方案:
(1)全局安装
(2)方法2:利用npm 的scripts解决
定义一个任务gulp
pacakge.json中添加
"scripts": {
"gulp": "gulp",
}
node_modules中的.bin目录暴露了自己安装的包的”可执行的引用“,类似于帮我们手工写了快捷方式。
四、*匹配任意个字符
?匹配一个字符
[…]匹配范围内字符
![pattern1|pattern2] 匹配取反
?(pattern1|pattern2) 匹配0或1个
+(pattern1|pattern2) 匹配1或多个
(a|b|c) 匹配任意个
@(pattern|pat|pat?erN) 匹配特定的一个
** 任意层级匹配
一、rm -rf buld && gulp
二、npm包:del可以删除一个或多个文件
const del = require('del');
gulp.task('clean', () => {
del.sync('build');
})
三、css自动添加前缀
gulp-autoprefixer
四、css压缩
gulp-clean-css
五、watch就会占用终端,不会退出
1、
gulpfile.js
gulp.task('watch', () => {
const watcher = gulp.watch('src/**/*', ['default'])
watch.on('change', event => {})
})
2、启动
(1)方法1:npm run gulp watch
(2)方法2:
package.json
"gulp": "gulp",
"gulp-watch": "gulp watch",
一、babel
babel is a javaScript compiler
二、
npm install --save-dev babel-presets-env
三、"presets": ["env"]
指的是可以把es6/ es7的语法转换成ecmascript2015
四、babel的plugins可以找到除了"presets": "env"外的其他presets
五、babel在webpack中的使用
1、
loader: "babel-loader",
options: {
}
可以在webpack.config.js中使用options,也可以用.bablerc文件
一、css单独拎出来,extract-text-webpack-plugin
引用import、实例化new、应用plugins
二、externals,
防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)。
externals: {
jquery: 'jQuery'
}
这样就剥离了那些不需要改动的依赖模块.
三、如果不是单页面应用,多个文件都需要用到react, react-dom,则使用vendor,能复用
entry: {
index: '',
vendor: ['react', 'react-dom']
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor', 'runtime']
})
]
四、运行时,webpack的模块系统,能单独拎出来。
五、tree-shaking,没有用到的方法不被打包进去。比如,lodash中只用到了部分方法,那么没有被使用的方法function就不要打包进去。
plugins = [
new UglifyJSPlugin()
]
.babelrc需要配置"modules": false
{
"presets": [["env", {"modules": false}], "react"],
}
播放不出来,下次再试试
一、react组件ui测试,jest
1、和mocha语法非常像
2、.babelrc需要配置
二、dom测试
Enzyme或react的testutils。
三、sinon,测试方法被调用几次
四、selenium-webdriver
1、自动测试
2、可以通过api来模拟用户行为
一、爬虫
1、定义:按照一定规则自动抓取网络信息的程序
二、反爬虫技巧
1、User-Agent:爬虫不是正常的User-Agent, 不过可以伪造
Referer,
验证码:12306
2、单位时间访问次数、访问量
3、关键信息图片混淆
4、异步加载
三、superAgent, 帮我们发送请求,得到html
cheerio,把html的内容转换成像jquery一样的对象
四、cheerio缺点
1、无法绕过反爬虫
五、GoogleChrome/ puppeteer
文档: https://github.com/puppeteer/puppeteer
1、 npm i puppeteer
六、gitignore
1、.gitignore目录一没了,就会报错
所以不能写screenshot/
,得写成screenshot/*.png
如果目录是空的,git就不会上传。
2、为了让screenshot文件夹不是空的,在文件夹中放一个空的文件,文件名叫.gitkeeper
一、puppeteer的evaluate很像javascript的el
二、
const browser = await puppeteer.lauch({headless: false}); // default is true
默认打开浏览器
一、获取页面的元素
page.$(selector)
page.$$(selector)
二、获取页面元素属性
page $eval(selector, pageFunction[, ...args])
const { promisify } = require('util');
const writeFile = promisify(fs.writeFile);
const urlToImg = promisify((url, dir, callback) => {
mod.get(url, res => {
res.pipe(fs.createWriteStream(file))
.on('finish', () => {
callback()
console.log(file)
})
})
})
const base64ToImg = async function (base64Str, dir) {
// data:image/jpeg;base64,/asdasda
const matches = base64Str.match(/^data:(.+?);base64,(.+$/))
const ext = matches[1].split('/')[1]
const file = path.join(dir, `${Date.now()}.${ext}`)
await writeFile(file, matches[2], 'base64')
console.log(file)
}
二、sleep
page.waitFor()
一、nodejs关键技术
1、Stream
2、动态Web framework(express/ koa)
3、child_process & cluster (单线程)
二、深入学习
1、through2
2、express、koa、egg
3、ssr & 同构
4、nodejs源码
20200722-2020:《node.js+koa2+mysql打造前后端分离精品项目《旧岛》》 |
课程地址:https://coding.imooc.com/class/342.html
一、知识点
egg.js think.js
校验器LinValidator
全局异常处理
自动路由注册
koa核心机制
为什么要有洋葱模型
中间件到底怎么用
查找类(class)的属性和方法
异步变成模型
async await
sequelize与MySQL
二、nodejs能力
脱离浏览器运行js
nodejs stream(前端工程化基础)
服务端api
作为中间层
三、CTO
需要设计整个公司架构
需要从全局考虑问题
需要掌握公司最重要的资产:数据(谁掌握数据,谁才有话语权)
四、
双层结构:前端+服务器
三层结构:前端+后端+服务端(返回的数据很纯粹,不关乎业务)
一、服务端语言
1、Python Flask
2、Java SprintBoot
3、NodeJS KOA
二、javascript是基于原型链设计的,越来越向工程化(OO)对齐,用面向对象的方式编程、构建代码。
三、flask django:同步
nodejs是强制用异步的。
四、koa: async await
一、框架、库
node.js
npm
koa
二、软件、工具
MySQL(xampp)
微信开发者工具
visual studio code
postman
navicat:数据可视化工具
nodemon(自动重启工具,自动重启服务器node server)、pm2(部署程序的工具)
三、长期支持版本 LTS
LTS= Long Term Support 长期支持
四、nvm可以管理不同的node版本
一、vs code 修改语言
1、command + shift + p,打开vs code的命令面板
configue display language,安装自己想要的语言
一、数据库
1、悲观锁 乐观锁 事务 脏读 幻读
二、https://github.com/TaleLin,cms框架,vue+koa
一、导入包/模块
1、commonJS
2、ES6
import…from
3、AMD
二、javascript新特性制定:https://github.com/tc39
1、es10, babel
2、TS Typescript
(1)比较好的javascript新特性
(2)有特性约束,大型项目中比较好维护
三、koa的内核也是用typescript编写的
一、koa 应用程序对象,特点是在对象上有很多中间件
const Koa = require('koa')
const app = new Koa()
app.listen(3001)
二、只有应用程序被阻塞了,才能监听到从前端发过来的http请求
三、将函数注册到对象上,函数就成了中间件
四、发送请求
浏览器
小程序
五、调用服务,可以浏览器地址栏输入以下的任何一种
(1)localhost:3000
(2)127.0.0.1:3000
六、ctx, next是koa内部机制自动传入的参数,不需要开发者管理
app.use(async (ctx, next) => {
console.log(1)
await next()
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
await next()
console.log(4)
})
打印 1 3 4 2
一、next()的调用结果一定是个promise
const a = next()
const a = await next()
app.use(async (ctx, next) => {
const a = await next() // 和async配合使用,a是一个promise,即Promise('hello world')
const b = next() // b得到的是next()直接返回的值,即’hello world‘
})
app.use((ctx, next) => {
return 'hello world'
})
二、await会阻塞线程,会求值(会计算表达式,包括promise,返回最终值)
三、异步
1、对资源的操作
2、读文件
3、发送http
(1)需安装包:库(axios)
4、操作数据库
(1)需安装包:sequelize
四、总结:需安装的包
axios
sequelize
koa-router
五
app.use((ctx, next) => {
const axios = require('axios')
const res = axios.get('http://7yue.pro')
})
六、如果有async,函数的任何返回值都会被包装成promise
七、面试题:为什么很多中间件函数前面要加async
1、函数用了await,如果不加async,await会报错
八、async, await最早出现在c#语言,异步终极解决方案
一、怎样保证中间件按洋葱模型执行
使用async, await
二、每个中间件执行的时候,都会被注入ctx
用ctx传值的时候,必须要保证中间件按洋葱模型执行
app.use(async (ctx, next) => {
await next()
const r1 = ctx.r3
})
app.use(async (ctx, next) => {
const axios = require('axios')
const res = await axios.get('http://7yue.pro')
ctx.r3 = res
await next()
})
三、nodejs写的项目,最大并发量比其他框架好
一、koa自己会将数据返回成application/json
二、koa返回数据,ctx.body
三、koa-router使用的3步
1、Router对象实例化
2、router实例对象编写一系列路由函数
3、app.use()注册
四、get post put delete
一、版本号携带策略
1、路径 /v1/classic/latest
2、查询参数 /classic/latest?version=v1
3、header
二、修改代码是存在风险的。
开闭原则:编程的时候,对代码的修改是关闭的,对代码的扩展是开放的。
三、对每个版本的api,都写一个路由
四、一旦产生项目循环引用,nodejs是不会报错的。
一、npm i -g nodemon
二、使用
terminal输入nodemon app.js,实现自动重启
三、全局安装的包是不会出现在项目的package.json里的
四、如果不是全局安装的nodemon,是不能直接使用nodemon app.js的,需要npx nodemon app.js,也可以在packag.json的scripts里面使用
scripts: {
'start:dev': 'nodemon app.js'
}
一、vscode, 点爬虫,点lauch,点击添加配置,得到launch.json
launch.json里面自定义启动方式
{
"configurations": [
{
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"name": "nodemon",
"program": "${workspaceFolder}/app.js",
"request": "launch",
"restart": true,
"runtimeExecutable": "nodemon",
"skipFiles": [
"/**"
],
"type": "pwa-node"
},
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"/**"
],
"program": "${workspaceFolder}/index.js"
},
{
"type": "node",
"request": "launch",
"name": "当前文件",
"program": "${file}"
}
]
}
一、路由自动加载的方式/原理
1、app文件夹中寻找所有的路由模块,自动require进来
2、通过循环的方式,注册在app上
二、require-directory
1、安装
npm i require-directory
2、使用
const requireDirectory = require('require-directory')
const modules = requireDirectory(module, './api/v1')
for (let i in modules) {
}
3、Lin CMS可以让module.exports = router和module.exports = { router }都能被处理
四、core/init.js中的requireDirectory(module, '../api/v1', { visit: whenLoadModule })
,’…/api/v1’,随着core文件夹放的位置不一样,里面的路径需要随着改变。解决方法
1、path config, 让开发者可以自己配置
2、用绝对路径
const apiDirectory = `${process.cwd()}/app/api`
requireDirectory(module, apiDirectory, {
visit: whenLoadModule
})
五、如果想得到process.cwd()
的值,
调试,当前行process.cwd()
打上断点,选中process.cwd()
,右键,选择调试求值(evaluate in debug console),就会在控制台打印出结果
一、常见传参方式,参数获取方式
1、url路径
/api/book/detail/:id
(1)获取
ctx.params
2、search
/api/book/detail?id=1
(1)获取
ctx.request.query
3、header
(1)获取
ctx.request.header
4、body
只有post才能在body中传参数
(1)获取
用到npm库koa-bodyparser
,中间件
二、浏览器url只能发送get请求,不能发送post请求
三、python 接口参数校验:WTSForms
一、函数设计
1、判断出异常
return false
return null
2、throw new Error
二、提高写代码的质量:《代码大全2》
三、1/0 ,Infinity
php, python, java, c#都会报错, 0不能为分母
四、ReferenceError是node内置错误
五、如果return false,或者return null会丢失error信息
六、中间件的本质还是一个函数
七、机制:可以监听到任何异常
八、前后端联调的效率不高的很大一个原因是:后端返回的错误信息不够具体
一、方法内部try{}catch{}是对同步方法有效果,如果方法内有异步方法,有时候try…catch是执行不到的
一、promise不是通过throw new error返回错误,而是通过reject返回错误信息
二、unhandled promise: promise有未处理的异常,可以用await,因为await能监听到这个异常。
三、全局异常处理,2步
1、全局监听到错误
app.js
const catchError = require('./middlewares/exception')
app.use(catchError)
2、监听到错误之后,输出一段有意义的提示信息
middlewares/exception.js
const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
ctx.body = '服务器有点问题,你等一下'
}
}
module.exports = catchError
四、AOP 面向切面编程
一、error信息给前端
1、http status code
2、message
3、error_code:详细,开发者自己定义
4、request_url:当前请求的url
二、错误类型
1、已知型错误
(1)用户传的参数,不符合规则。需要传整型,但是传了字符串。
(2)try… catch
2、未知型错误
(1)程序潜在错误,对开发者来说,是无意识的,或者说开发者根本不知道他出错了
(2)连接数据库,账号、密码输错了
一、错误返回
app/api/classic.js
if (!query) {
const error = new Error('为什么错误')
error.error_code = 10001
error.status = 400
error.request_url = `${ctx.method} ${ctx.path}`
throw error
}
二、python在两个单词之间用_, error_code
一、动态,面向对象方式 一个类
二、继承Nodejs内置的Error对象,因为是Error类型的,所以才能throw error
三、
1、core/http-exception.js
class HttpException extends Error {
constructor (msg = '服务器错误', errorCode = 1000, code = 400) {
super()
this.errorCode = errorCode
this.code = code
this.msg = msg
}
}
module.exports = {
HttpException
}
2、middlewares/exceptions.js
const { HttpException } = require('../core/http-exception')
const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
if (error instanceof HttpException) { // 已知错误
ctx.body = {
msg: error.msg,
errorCode: error.errorCode,
request: `${ctx.method} ${ctx.path}`,
}
ctx.status = error.code
}
}
}
module.exports = catchError
3、app/api/classic.js
const Router = require('koa-router')
const router = new Router()
const { HttpException } = require('../../../core/http-exception')
router.post('/v1/:id/classic/latest', (ctx, next) => {
if (true) {
// 动态 面向对象方式 一个类
const error = new HttpException('为什么错误ne', 10001, 400)
throw error
}
})
module.exports = router
一、nodejs有个全局变量, global
一、api中如果抛出了一个异常,后续的代码是不会执行的
二、LinValidator的npm包名:lin-mizar
git地址:https://github.com/TaleLin/lin-cms-koa-core/blob/master/lib/validator/lin-validator.ts
三、将校验器都放在同一处,app/validators/validator.js
四、validator.js的使用率很高,校验。
文档:https://github.com/validatorjs/validator.js
一、如果在class中,要在constructor中使用this,得要super()
1、没有extends的话,constructor中不用super
二、开发环境需要看到终端异常信息,生产环境不需要看到
config/config.js
module.exports = {
enviroment: 'env'
}
core/ int.js
const requireDirectory = require('require-directory')
const Router = require('koa-router')
class InitManager {
static initCore(app) {
InitManager.app = app
InitManager.loadConfig()
}
static loadConfig(path = '') {
const configPath = path || process.cwd() + 'config/config.js'
const config = require('configPath')
global.config = config
}
}
module.exports = InitManager
三、原型链作业
1、编写一个函数findMember,是最终输出结果为[‘nameA’, ‘nameB’, ‘nameC’, ‘validateC’, ‘ValidateB’, ‘validateA’]
class A {
constructor () {
this.nameA = 'a'
}
validateA () {
console.log('A')
}
}
class B extends A {
constructor () {
super()
this.nameB = 'b'
}
validateB () {
console.log('B')
}
}
class C extends B {
constructor () {
super()
this.nameC = 'c'
}
validateC () {
console.log('C')
}
}
// 编写一个函数findMember
const c = new C()
const members = findMembers(c, 'name', 'validate')
console.log(members) // ['nameA', 'nameB', 'nameC', 'validateC', 'ValidateB', 'validateA']
一、用户系统
1、通用型
2、针对小程序
二、
注册、登录
三、业务的处理通常是放到Model中处理的
1、基本所有的流程走的都是这个流程
四、常用数据库分为两大类
1、关系型数据库 SQL
(1)MySQL
(2)SQLServer
(3)Oracle
(4)PostgresSQL
2、非关系型数据库
(1)Redis
主要是用来做缓存的
(2)MongoDB
存储的是一个一个的类似javascript对象数据,也成为文档型数据库
五、持久存储数据,持久化
六、CRUD, 增删改查
一、xampp找到自己对应的版本,就可以不用去官网下载mysql
二、MariaDB是MySQL的分支
三、Navicat for MySQL,数据库可视化管理工具
四、navicat修改密码
1、修改密码
选择任意一个数据库,点击用户
双击后修改
2、是否设置成功
mysql user表,password应该是有值的,如果没有值,则没有设置成功
3、
root@localhost
root@`127.0.0.1`
五、koa中需要设置的用户名/密码,数据库名
数据库名:(自由填写)
默认字符集:utf8mb4
默认排序规则:utf8mb4_general_ci
六、sequelize会将模型自动转换成表
一、mysql, sqlserver, postgresdb,oracle这些类型数据库都能使用sequlize
const sequelize = new Sequelize(dbName, user, password, {
dialect: 'mysql', // 数据库类型
host,
port,
logging: true,
timezone: '+08:00',
}) // 控制或设置数据库的参数,dbName, user, password, js对象()
二、如果数据库类型是mysql,必须安装mysql的驱动
npm i mysql2
三、如果不设置timezone,会跟北京时间相差8小时。
一、导入的模块重命名
1、es6
import { sequelize as db } from './core/db'
2、commonjs
const { sequelize : db } = require('./core/db')
二、一个用户对一个小程序来说,是不变,且唯一的, openid
一个用户,对小程序,公众号,都唯一的是unionID
三、主键,不能重复,不能为空
四、不要用字符串做主键,用数字类型,查询效率高很多
五、如果是自己用60001, 600002,自己设计的编号作为主键,不能处理并发情况,很有可能计算重复。
六、接口保护,权限,访问接口, Token令牌
app/models/user.js
const { sequelize : db } = require('../../core/db')
const { Sequelize, Model } = require('sequelize')
class User extends Model {
}
User.init({ // mysql一种类型
// 主键 关系型数据库
// 注册 user id 设计 id编号系统 600001 60002
// 自动增长id编号
id: {
type: Sequelize.INTERGER,
primaryKey: true,
autoIncrement: true,
},
nickname: Sequelize.STRING,
email: Sequelize.STRING,
password: Sequelize.STRING,
openid: {
type: Sequelize.STRING(64),
unique: true,
},
}, {
sequelize: db,
tableName: 'user'
})
module.exports = {
User
}
core/db.js
const Sequelize = require('sequelize')
const { dbName, host, port, user, password } = require('../config/config').database
const sequelize = new Sequelize(dbName, user, password, {
dialect: 'mysql', // 数据库类型
host,
port,
logging: true,
timezone: '+08:00',
define: {
timestamps: true,
paranoid: true,
createdAt: 'created_at',
updatedAt: 'updated_at',
deletedAt: 'deleted_at',
underscored: true,
},
}) // 控制或设置数据库的参数,dbName, user, password, js对象()
sequelize.sync({
force: true,
})
module.exports = {
sequelize
}
一、抛出异常
开发环境,不是httpException
const isHttpException = error instanceof HttpException
const isDev = global.config.enviroment === 'dev'
if (isDev && !isHttpException) {
throw error
}
一、如果是自定义的校验中带异步验证,则Lin-Validator需要返回promise,这样可以使用await来先获取校验只,校验过了才能进行下一步。
一、中间件是一种静态的方式。
二、类,每次都是new一个类,将类实例化,每个api请求,都会实例化一个Lin-Validator
三、不要轻易在中间件以类的形式来组织中间件。函数一般来说,不会实例化。函数通常不会用来保存类的状态,校验器这种复杂的功能比较适合用类的方式来组织,不太适合用函数的方式来组织。
一、密码加密处理:bcryptjs
导入包的时候,npm包通常是放在第一位
npm i bcryptjs
二、
const salt = bcrypt.genSaltSync(10) // 10:计算机在生成盐的时候所花的成本
三、不能明文存储,即使密码相同,加密后也不能相同
四、
app/api/v1/user.js
const bcrypt = require('bcryptjs')
const salt = bcrypt.genSaltSync(10)
const psw = bcrypt.hashSync(password, salt)
一、app/models
password: {
type: Sequelize.STRING,
set(val) { // set:观察者模式,如果是值改变,则会自动调用这个方法。es6 reflect方法很容易实现观察者模式
const salt = bcrypt.genSaltSync(10)
const psw = bcrypt.hashSync(val, salt)
this.setDataValue('password', psw) // 赋值的时候会自动调用set方法, setDataValue是Model方法,所以能用this
}
},
一、全局异常处理throw new global.errs.Success()
core/http-exception.js
class Success extends HttpException {
constructor(msg, errorCode) {
super()
this.code = 201
this.msg = msg || 'ok'
this.errorCode = errorCode || 0
}
}
lib/helper.js
function success(msg, errorCode) {
throw new global.errs.Success(msg, errorCode)
}
module.exports = {
success
}
app/api/v1/user.js
router.post('/register', async (ctx) => {
const { body } = ctx.request
const { email, password, nickname } = body
const user = {
email,
password,
nickname,
}
User.create(user)
success()
})
二、ctx.body
router.post('/register', async (ctx) => {
const { body } = ctx.request
const { email, password, nickname } = body
const user = {
email,
password,
nickname,
}
User.create(user)
ctx.body = {
code: 201,
msg: ’这是success的另一种实现方法‘,
errorCode: 0,
}
})
一、身份校验
1、session,考虑状态
2、令牌,无状态
(1)token令牌:一串无意义的随机字符串
(2)jwt令牌:携带数据
二、无状态,有状态
rest:一次请求就能拿到数据(无状态)
websrevice :请求:open, 取数据,close (有状态)
三、asp, jsp:动态网页技术
一、javascript对象模拟枚举
lib/enum.js
function isThisType (val) {
for (let key in this) {
if (this[key] === val) {
return true
}
}
return false
}
const LoginType = {
USER_MINI_PROGRAM: 100,
USER_EMAIL: 101,
USER_MOBILE: 102,
ADMIN_EMAIL: 200,
isThisType,
}
module.exports = {
LoginType
}
二、java可以在编译阶段找到很多问题,javascript, python很多时候只能在执行阶段找问题。
一、async在函数名的前面
一、jwt令牌,npm包:jsonwebtoken
npm i jsonwebtoken
二、core /kɔːr/:核心
core/util.js
const jwt = require('jsonwebtoken')
// scope,用来做权限
const generaterToken = function (uid, scope) {
const secretKey = global.config.security.secretKey
const expiresIn = global.config.security.expiresIn
const token = jwt.sign({
uid,
scope,
}, secretKey, {
expiresIn,
})
return token
}
module.exports = {
generaterToken,
}
app/api/v1/token.js
router.post('/', async (ctx) => {
const { body } = ctx.request
const { type } = body
let token
switch (type) {
case LoginType.USER_EMAIL:
const { account, secret } = body
token = await emailLogin(account, secret)
ctx.body = {
token: token,
}
}
})
async function emailLogin(account, secret) {
const user = await User.verifyEmailPassword(account, secret)
return generaterToken(user.id, 2)
}
module.exports = router
一、token 过期,不合法
二、中间件自带参数ctx, next
三、token放在 body,还是 header ,取悦于前后端约定
四、HTTP 规定 身份验证机制 HttpBasicAuth
basic auth是最基本的
basic64加密
五、HttpBasicAuth需要npm包:basic-auth
npm i basic-auth
六、ctx.req: node.js原生的request对象
ctx.request: koa基于原生noejs的request封装后的request
七、
const basicAuth = require('basic-auth')
class Auth {
constructor () {
}
get m() {
return async (ctx, next) => {
const token = basicAuth(ctx.req)
ctx.body = token
}
}
}
module.exports = {
Auth
}
八、koa router上可以使用多个中间件。先执行的中间件写在前面,前面的中间件能阻止进入后一个中间件。上一个中间件末尾写await next(),才会去执行第二个中间件
router.get('/latest', new Auth().m, (ctx, next) => {} // m后面不需要加括号,m是一个属性,不是一个方法
一、权限 token角色,普通用户、管理员,可以用分级scope来区分
一、业务逻辑
1、在api接口编写
2、model,分层
二、业务分层,
1、nodejs: Model, Service
2、Thinkphp: Model Service Logic
3、java: Model DTO
二、MVC,业务逻辑应该写在M中,model
三、code appid appsecret
一、util是Node.js提供的帮助工具,不需要npm i
const util = require('util')
二、调用微信服务,可以用axios库
const result = await axios.get('https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code')
一、uid,用系统的,不用openid,因为openid是比较机密的数据,不能一直在前后端传输
二、
一、微信开发者工具
1、项目设置,选择使用npm模块,调试组件库不要调得太低,尽量调的新的一点
2、开发者工具里的工程根目录,选择“在终端中打开”
(1)要保证是在项目根目录中,现在是在pages 目录下,所以cd …
(2) npm init,这样就创建了package.json文件
(3) npm i Lin-UI,微信开发者工具中不会显示node_modules文件
(4)工具,构建npm。miniprogram_npm,下面会有lin–ui的所有组件
二、小程序对cnpm的支持度比较低
一、如果调用的是本地的api
配置,不校验合法域名
一、model code first,
要先考虑的不是数据表,而是model模型
二、主题,由粗到细
1、user
2、期刊
movie
sentence
music
(1)实体,表/model ,记录本身相关信息,事务,表
(2)大千世界事务的映射
3、一期一期, model/表,第1期、第2期、第3期
Flow
(1)很难找到实体,是抽象的,记录业务,解决业务问题,
一、classic 共同字段/属性
image
title
pubdate
content
fav_nums
type 代号
(1)url, music特有的
二、共同字段/属性,定义成基类,nodejs中的squelize不能做到直接用类继承,python或java可以
三、
const musicFields = Object.assign({
url: Sequelize.STRING,
}, classicFields)
Music.init(musicFields, {
sequelize,
tableName: 'music'
} )
一、where是特定的查询条件
二、order,排序
一、base64.js,是一个npm包,可用于base64加码
npm i js-base64
二、
header: {
Authorization: this._encode()
}
_encode() {
// account: password
// token:
const token = wx.getStorageSync('token')
const base64 = Base64.encode(token + ':')
return base64
}
一、序列化,对象转换成json
二、art(类)下的dataValues才会被序列化成json,才会被返回
三、对模型进行属性修改
1、
const flow = await Flow.findOne({
order: [
['index', 'DESC']
]
})
const art = await Art.getData(flow.art_id, flow.type)
art.dataValues.index = flow.index // 直接修改了一个类,没有私有成员的概念
ctx.body = art
2、以上方法不推荐,推荐使用以下方法
用内置方法,更为合理,更为安全
art.setDataValue('index', flow.index)
四、sequelize告诉koa框架要通过dataValues进行序列化。
五、json是由js对象抽象出来的
一、token令牌可能会过期
app.js
import { Token } from 'models/token.js'
App({
onLaunch: function () {
const token = new Token()
token.verify()
}
})
二、自动无感知帮助用户重新刷新令牌
退出后,短时间内再进入,不一定会触发onLaunch方法
二次重发机制
一、所有的自定义的models中,都不要写constructor
二、参数通过方法参数的方法传过来,而不是通过constrctor传过来
一、实时加载,从onload, 改成onshow,用户切换一下就改变数据
一、静态资源
1、api,读流,返回。
2、不需要api,需要插件(npm包:koa-static)
app.js
const path = require('path')
const static = require('koa-static')
app.use(static(path.join(__dirname, './static')))
二、models处理图片
models/art.js
if (art && art.image) {
let imgUrl = art.dataValues.image
art.dataValues.image = global.config.host + imgUrl
}
一、图片拼接,在最源头的地方处理,是最好的
二、以下方法不可行,因为get没办法修改dataValues
models/classic.js
const classicFields = {
image: {
type: Sequelize.STRING,
get() {
return global.config.host + this.getDataValue('image')
}
}
}
三、
const id = art.get('image') // 获取完整路径
const t = art.image // 获取完整路径
const s = art.getDataValue('image') // 获取到原始值
四、一个模型的dataValues是不受get影响的,存储的都是原始字符串。
五、方案
Model.prototype.toJSON 修改
六、hook钩子,最大的好处的是能解耦代码
九、判断是否以http开头
data[key].startsWith('http')
一、静态资源的加载,图片,最消耗流量
二、静态资源存储
1、网站目录中,app/static/images
2、静态资源服务器,微服务,带宽足够
3、云服务,阿里云OSS(ECS:,RDS:关系型数据库,OSS:云服务)
OSS可以进行CDN缓存
4、免费的静态资源服务器,github, gitpage。
个人服务器可以用。
三、html, css, js可以打包的,都是静态资源
四、vue/react打包出来的资源也是静态资源
nuxt的ssr不属于静态资源,服务端模板渲染
五、适用场景
1、react, vue
(1) CMS,内部管理系统,不需要SEO,可以用react, vue
(2) webApp, h5
2、next的ssr
(1) SEO,B2C技术
一、无感知登录
import { Token } from '../models/token.js'
const tips = {
1: '抱歉,出现了一个错误',
1005: 'appKey无效,请前往www.7yue.pro申请'
}
class HTTP {
request({
url,
data = {},
method = 'GET'
}) {
return new Promise((resolve, reject) => {
this._request(url, resolve, reject, data, method)
})
}
_request(url, resolve, reject, data = {}, method = 'GET', noRefetch = false) {
wx.request({
url: config.api_base_url + url,
method,
data,
header: {
'content-type': 'application/json',
Authorization: this._encode()
},
success: (res) => {
const code = res.statusCode.toString()
if (code.startsWith('2')) {
resolve(res.data)
} else {
if (code == '403') {
if (!noRefetch) {
this._refetch(
url,
resolve,
reject,
data,
method
)
}
} else {
reject()
const error_code = res.data.error_code
this._show_error(error_code)
}
}
},
fail: (err) => {
reject()
this._show_error(1)
}
})
}
_show_error(error_code) {
if (!error_code) {
error_code = 1
}
const tip = tips[error_code]
wx.showToast({
title: tip ? tip : tips[1],
icon: 'none',
duration: 2000
})
}
_refetch(...param) {
var token = new Token()
token.getTokenFromServer((token) => {
this._request(...param, true)
})
}
_encode() {
const token = wx.getStorageSync('token')
const base64 = new Base64()
const result = base64.encode(token + ':')
return 'Basic' + result
}
}
二、
三、app
1、缓存中存储app账号,密码
2、双令牌,access_token, refresh_token
(1)access_token,类似于jwt令牌
(2)access_token过期了,通过refresh_token重新获取access_token, 每次发access_token的时候,都重新发送一个refresh_token令牌
四、oAuth2.0,第3方登录的时候用到
一、 部署不属于技巧性的东西
二、本地电脑没有外网ip,可以本地访问,可以局域网访问,但是不能外部访问。
三、云服务,可以理解成linux电脑
四、购买域名之后一定要备案
五、部署
1、服务器、域名
(1)服务器
(2)域名
域名备案
域名解析:域名和ip绑定起来
2、环境安装
mysql:xampp
node
3、nginx 转发
六、通常来说,一个服务,或者一个项目,都有一个端口
七、80端口不需要加在域名后面
八、小程序:https
1、阿里云,有个https证书可以用一年
(1)免费:lets encrypt,每3个月需要续期一次
九、CMS,通常都是web端
十、结合,云开发,api开发
serverless
一、启动的不能是常规进程,得是守护进程,是后台进程。
1、node app.js启动的是常规进程,会阻塞,关闭terminal终端,进程就会停止。
二、守护进程pm2, 日志监控,重启,安装的时候需要-g安装
启动:pm2 start app.js
终止:pm2 stop app
三、pm2可代替nodemon