Open HolyZheng opened 6 years ago
采用官方提供的登录能力来获取用户标识,通过JWT(json web token)来保持登录状态,流程图:
`https://api.weixin.qq.com/sns/jscode2session?appid=
${option.appId}&secret=${option.appSecret}
&js_code=${body.code}&grant_type=authorization_code`
wx.login({
success: res => {
res.code
},
fail: err => {
/ ...
}
})
// https模块
const https = require('https')
// 封装请求方法
const request = (url) => {
return new Promise((resolve, reject) => {
https.get(url, res => {
let data = ''
res.on('data', chunk => {
data += chunk
})
res.on('end', () => {
resolve(JSON.parse(data))
})
}).on('error', err => {
reject(err)
})
})
}
const url = https://api.weixin.qq.com/sns/jscode2session?appid= ${option.appId}&secret=${option.appSecret} &js_code=${body.code}&grant_type=authorization_code
const login = async ctx => { try { const res = await request(url) let user = await users.find({ where: { open_id: res.openid } }) // 如果不存在该用户,向users表添加该用户 if (!user) { await users.create({ name: body.userName, picture: body.picture, open_id: res.openid }) // 获取该user user = await users.find({ where: { open_id: res.openid } }) } } catch (err) { // err } } export login
4. 使用密钥生成token
```js
import jsonwebtoken from 'jsonwebtoken'
// 封装方法
const signToken = payload => {
const token = jsonwebtoken.sign(
payload,
config.auth.jwtSecret, // 存放着openid
{
expiresIn: '24h'
}
)
return token
}
// 请求头带上token
headers: {
'Authorization': 'Bearer ' + token
}
import jsonwebtoken from 'jsonwebtoken'
// 封装方法
const checkToken = ctx => {
const token = ctx.header.authorization.split(' ')[1]
try {
jsonwebtoken.verify(token, config.auth.jwtSecret, {
expiresIn: '24h'
})
} catch (err) {
throw new Error('token验证错误:' + err)
}
}
// 注册中间件 app.use(jwt({ secret: config.auth.jwtSecret }).unless({ // 除开登录请求 path: [/\/login/] }))
[两种常用的认证方式](https://github.com/HolyZheng/holyZheng-blog/issues/9#issuecomment-402698538)
### 2. 优化
#### 图片懒加载
小程序image组件,自带lazy-load属性,但是为什么我还是选择自己实现懒加载呢,因为自带的懒加载并不是当图片出现在可视区域的时候再去加载,经过实践发现,它会提前两个屏幕的高度进行加载,也就是说你的图片数量多到超过可视范围的两个屏幕高度才能得到懒加载的好处,这个提前量太多了,所以有必要自己实现一个“真正的”懒加载。
```js
const ctx = this
let className = '.brief-image-' + ctx.properties.index
wx.createIntersectionObserver().relativeToViewport().observe(className, res => {
if (res.intersectionRatio > 0) {
ctx.setData({
load: true
})
}
})
createIntersectionObserver
方法创建一个IntersectionObserver对象,小程序里面给它的定义是节点布局交叉状态API可用于监听组件节点在布局位置上的相交状态。relativeToViewport
方法,接收一个选择符作为参数来规定相对的节点,默认为页面可视区域observe
方法,接收一个选择符作为参数来指定监视的节点。res.intersectionRatio
是否大于 0 来判断始出出现在了可视区域。
limit
,offset
参数来分组(分页)请求数据。
// 后端
Experiments.findAll({
offset: parseInt(body.offset),
limit: parseInt(body.limit),
include: [{
model: Users
}]
})
Express 优点:线性逻辑,通过中间件形式把业务逻辑细分、简化,一个请求进来经过一系列中间件处理后再响应给用户,清晰明了。 缺点:基于 callback 组合业务逻辑,业务逻辑复杂时嵌套过多,异常捕获困难。
Koa 优点:首先,借助 co 和 generator,很好地解决了异步流程控制和异常捕获问题。其次,Koa 把 Express 中内置的 router、view 等功能都移除了,使得框架本身更轻量。 缺点:社区相对较小。
koa2 采用async/await
- 首先我为什么不选择express呢?express在我眼中最大的缺点便是它采用callback的方式来处理异步操作,这种方式当我们的业务比较复杂的时候呢,就容易出现类似回调金字塔的现象,就会有可读性差,可维护性差,一场捕获困难等问题。所以我没有选择express
- 我为什么选择koa2而不选择koa1呢,因为koa1采用generator来处理异步,而koa2采用更合适的async/await特新来处理异步。
- generator本身是一个特殊的函数,可以暂停和重暂停的地方重新开始执行,通过function*来声明一个generator,通过yield来作为暂停的标志并返回跟在后面的表达式的值。在异步场景中需要我们在异步操作有了结果之后去调用generator实例的next()方法,让函数继续往下执行。
- 而async/await它更适合处理异步操作,它具有更好的语义,内置了执行器,返回promise,await后面可以跟promise和其他原始类型(数值、字符串和布尔值),通过跟promise配合呢,我们可以完全像处理同步操作一样去处理异步操作,通过try...catch...去捕获错误,十分方便。所以我选择了koa2
关注点分离就是说我们的应用程序中的每个模块或逻辑层只应该对一件事情负责,而不应该包含处理其它事情的代码。分离关注点的做法就是把一个大型的应用程序划分为多个稍小一些的功能单元,从而减少代码的复杂度,跟耦合度。 这里呢将我们的代码分为了:controllers、models、routers、services层
routers层 分发来自前端的请求到对应的controller中
controllers层 处理分发进来的请求,调用对应的services接口处理数据
service层 提供对数据库的直接操作
models 通过ORM抽象成对象,对数据的实体定义
RESTful形式
REST全称是Representational State Transfer,一种软件架构风格、设计风格,而不是标准。 把请求都当作资源来看代,而不是动作。 看Url就知道要什么 看http method就知道干什么 看http status code就知道结果如何
所以我们应该用名称去命名我们的资源,而不是动词,而且一般使用复数。 需要合理的适合http方法,比如,get获取数据,post提交数据进行处理并获取相应的资源,put提交数据去替代指定资源,delete删除资源等。
中间件相当于插件,用于扩展我们的功能。
// #1
app.use(async (ctx, next)=>{
console.log(1)
await next();
console.log(1)
});
// #2
app.use(async (ctx, next) => {
console.log(2)
await next();
console.log(2)
})
app.use(async (ctx, next) => {
console.log(3)
})
1
2
3
2
1
Nginx是一个高效的web和反向代理服务器。 与Apahce对比:
异步非阻塞:在处理异步任务的时候,当前线程不会被挂起,而是继续执行。相对的阻塞就是,执行异步任务的时候当前任务会被挂起,知道任务得到了结果再继续执行该任务。
// 配置一个要代理的服务器
upstream rockjins {
server 127.0.0.1:8081;
# 这里的端口号写你node.js运行的端口号,也就是要代理的端口号,我的项目跑在8081端口上
}
// 配置一个服务器,将这里的请求分配到rockjins
server {
listen 80; #这里的端口号是你要监听的端口号
server_name 39.108.55.xxx www.rockjins.com rockjins.com; # 这里是你的服务器名称,也就是别人访问你服务的ip地址或域名,可以写多个,用空格隔开
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Nginx-Proxy true;
proxy_set_header Connection "";
proxy_pass http://rockjins; # 这里要和最上面upstream后的应用名一致,可以自定义
}
}
关键词:formData对象,content-type:multipart/form-data
同input标签设置input标签的type属性为file,去提取本地文件,然后我们拿到的文件是一个二进制对象,如果文件比较大就每次通过slice方法去提取部分来分块上传,通过formData对象,用键值对模拟表单数据上传文件,并把content-type设为multipart/form-data以二进制的方式来传输内容。
通过formData对象,用键值对模拟表单数据,让我们可以直接通过JavaScript去发送表单数据。
设置请求头的content-type为:multipart/form-data,该content-type是以二进制的形式来传输文件,那样我们就可以传输各种类型的文件了。 与application/x-www-form-urlencoded的区别,此类型会以键值对的方式来传输。会添加到url上,组成url的参数部分(?parameters)
POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"
title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png
PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
通过一个boundary 来划分请求头和请求体。
ps:file api还有 lastModified对象所引用文件最后修改时间, 自 1970年1月1日0:00 以来的毫秒数。lastModifiedDate对象所引用文件最后修改时间的 Date 对象。name文件名称。size文件大小。type文件的类型。
蹦床函数
来模拟尾递归的效果。
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
整个配置分为三个部分:基本配置,开发环境配置,生产环境配置。
基本配置,是一些开发环境和生产环境都会用到的配置,比如配置babel,图片处理转base64等。
const path = require('path')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
// vue-loader 需要 配置此插件才能生效(有点懵)
const CleanWebpackPlugin = require('clean-webpack-plugin')
function resolve (dir) {
return path.join(__dirname, '..', dir)
}
module.exports = {
entry: {
app: path.resolve(__dirname, '../src/main.js')
},
resolve: {
extensions: ['.js', '.vue'],
alias: {
"@": resolve('src')
}
},
module: {
rules: [
{
test: /\.js$/,
use: 'babel-loader',
include: [path.resolve(__dirname, '../src')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'images/[name].[hash:7].[ext]'
}
}
]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10000,
name: 'fonts/[name].[hash:7].[ext]'
}
}
]
}
]
},
plugins: [
new VueLoaderPlugin(),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '..'),
exclude: [],
verbose: true,
dry: false
})
]
}
开发环境,配置一个本地服务,指定mode:development,用vue-loader处理.vue文件,用vue-style-loader,css-loader处理.vue文件中的css代码等。
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 每次都创建新的html确保引用的文件名正确
var baseWebpackConfig = require('./webpack.base.config')
module.exports = merge(baseWebpackConfig, {
devServer: {
port: 8000,
// 0.0.0.0 可使得我们用内网ip访问,这样可以用其他电脑或手机连接自己电脑进行访问
host: '0.0.0.0',
overlay: {
// 将错误显示到浏览器页面上
errors: true
}
},
output: {
publicPath: '/',
// 在dev阶段不需要输出,所有不需要设置路劲
// 通过htmlWebpackPlugin使得index.html中script src的值与生成的js文件路径保持一致
filename: 'js/[name].[hash:7].js'
},
mode: 'development',
module: {
// .vue文件中使用css需要css-loader
// vue-style-loader是vue-loader的dependencies
// css-loader 是 vue-loader的peerDependencies
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
},
{
test: /\.s[ac]ss$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
// 路径相对于 Webpack 配置项 output.path
template: 'index.html'
// 模板,在此模板上做修改
})
]
})
生产环境,指定mode:production,他的作用是自动帮我们定义环境变量为production,自动启用代码压缩,我们不再需要通过webpack.DefinePlugin去配置环境变量的值,也不需要在使用uglifyjs-webpack-plugin去进行代码压缩,还要通过vue-loader处理.vue文件,还要配置css代码提取,和自动添加前缀之类的,最后还需要配置一个公共代码的提取。
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
// 提取.vue文件中的css
const baseWebpackPlugin = require('./webpack.base.config')
// 通过postcss和cssnano对提取出来的css进行处理
// 这里好像还有点问题
var postOptions = {
plugins: [
require('cssnano')({
sourceMap: true,
autoprefixer: {
add: true,
browsers: ['> 5%']
}
})
]
}
module.exports = merge(baseWebpackPlugin, {
output: {
path: path.resolve(__dirname, '../dist'),
filename: 'js/[name].[chunkhash:7].js'
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.vue$/,
use: {
loader: 'vue-loader',
options: {
loaders: {
// 对.vue中的css做处理,并提取出来, posstcss-loader
// 需要放在css-loader后面预处理器loader前面
css: ExtractTextPlugin.extract({
use: [
'css-loader',
{
loader: 'postcss-loader',
options: postOptions
}
]
}),
scss: ExtractTextPlugin.extract({
use: [
'css-loader', {
loader: 'postcss-loader',
options: postOptions
},
'sass-loader'
]
}),
sass: ExtractTextPlugin.extract({
use: [
'css-loader', {
loader: 'postcss-loader',
options: postOptions
},
'sass-loader'
]
})
}
}
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', {
loader: 'postcss-loader',
options: postOptions
}]
})
},
{
test: /\.s[ac]ss/,
use: ExtractTextPlugin.extract({
use: ['css-loader', {
loader: 'postcss-loader',
options: postOptions
}, 'sass-loader']
})
}
]
},
mode: 'production',
plugins: [
// 作用域提升插件(还不太清楚在这里的作用)
// new webpack.optimize.ModuleConcatenationPlugin(),
// 定义环境变量,以便Uglify自动删除代码内的警告语句
// new webpack.DefinePlugin({
// 'process.env.NODE_ENV': JSON.stringify('production')
// }),
// 创建新的html
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
title: '智传'
}),
// webpack4 的代码压缩可通过指定mode为production来实现
// 故不需要再引入插件
new ExtractTextPlugin({
filename: 'css/style.[hash:7].css'
}),
// new UglifyjsWebpackPlugin()
],
// 优化 将公共代码提取到commons文件中,减少带宽的浪费。
//将node_module中(几乎不会变动的)的代码提取,到vendor中,以便浏览器缓存。
optimization: {
splitChunks: {
cacheGroups: {
commons: {
chunks: 'initial',
name: 'commons',
minChunks: 2,
maxInitialRequests: 5,
minSize: 0
},
vendor: {
chunks: 'initial',
test: /vue/,
name: 'vendor',
priority: 10,
enforce: true
}
}
},
runtimeChunk: true
}
})
总结: 使用了webpack4进行配置,整个配置过程分为三个部分:基本配置,开发环境配置,生产环境配置。
这是一个自己写得一个mvvm demo,主要是对一些原理的实践,比如对vue响应式数据绑定原理,flux架构,virtual dom机制的一个实践。
vue2.0响应式数据绑定原理 在这个库中呢,更数据绑定有关的代码集中在src/core/compile.js文件以及src/storeux/createStore.js中,通过数据劫持和观察者模式来实现。
observer
方法遍历我们的每一个数据,为每一个数据执行defineReactive
方法,在defineReactive
方法中通过Object.defineProperty方法去修改每一个数据的getter和setter。compile
方法对生成的virtual dom进行一次遍历,为virtual dom中每一处使用到了数据的地方创建一个watcher对象,这个watch对象就是我们观察者模式中的观察者,在创建的同时会把当前的watcher添加到一个全局变量Dep.target中。flux是一种架构模式,他的核心理念在于以一种单向数据流的方式来管理数据。它有几个关键的概念:
所有的应用状态在store中同一管理,视图层不允许直接修改应用状态,只能触发action,在dispatcher中监听action并执行相应的操作修改store中的数据。
随着应用的日益复杂,数据量的增大,如果不对数据进行相应的管理,管理不断变化的状态,是非常困难的。状态在什么时候,由于什么原因,发生了怎样的变化都难以观察。 通过我们的flux架构:
这部分代码主要集中在src/sroreux/createStore.js文件下,这里定义了一个createStore接受我们的应用状态和一个changeState方法为参数。
这个项目,nativescript本身也是一个跨平台开发框架,支持使用angular或js或typescript来跨平台开发原生应用,但是热度并不高。我也没有特别的关注它。然后有一天,突然发现它新增了一个选项,就是nativescript-vue,可以使用vue来进行开发了。于是就凑了一下热闹。搞了个demo。
nativescript的原理和react-native有点相似:提供一个运行环境,提供一个JavaScript引擎,android端是V8引擎,ios端是JavaScriptCore引擎,一个虚拟机上运行JavaScript代码,都可以调用平台api和ui组件。而nativescript更专注于创建一个与平台无关的单一的开发体验,react-native则是抽象业务逻辑的同时,支持每个平台UI呈现固有的差异,并把重心放在高性能的渲染和执行上面。
寻寻被试