koa学习和项目开发学习记录
编写一个接口的最基本代码
const koa = require('koa') const Router =require('koa-router')
const router = new Router()
const app = new koa()
router.get('/classic/latest',(ctx,next)=>{ ctx.body ={key:"classic"} })
app.use(router.routes()) app.listen(3000,()=>{ console.log('http监听端口3000') })
|
中间件
中间件-发送http调用的函数,一个实例可以定义多个中间件,中间件调用总是返回promise
app.use
注册中间件。ctx
上下文,next
下一个中间件
app.use((ctx,next)=>{ console.log('七月') next() }) app.use((ctx,next)=>{ console.log('八月') })
|
传递参数
通过挂载到ctx传参,首先要保证洋葱模型
app.use(async(ctx,next)=>{ await next() console.log(ctx,r) })
app.use(async(ctx,next)=>{ ctx.r = await axios.get('www.baidu.com').data await next() })
|
洋葱模型
先执行fun1上 再执行中间件函数 在执行fun1下。可以判断函数是否完全执行,以中间件函数为分界线

简单的例子:
app.use(async(ctx,next)=>{ fun1() await next() funt1下() })
app.use(async(ctx,next)=>{ funt2() })
|
使用async,await强制promise同步调用化,调用顺序
fun1() ====> fun2() ====> fun1下
async await
await特性:
- 求值
await 理解为计算promise
的值,使用await使用一定要在function前加上async
,也可以对表达式求值。使用async,await一定可以使中间件保持洋葱模型。
app.use(async(ctx,next)=>{ console.log(1) const a = await next() console.log(2) })
app.use(async(ctx,next)=>{ console.log(3) })
|
- 阻塞线程
常见的异步调用:对资源 读文件 操作数据库 发送http
app.use((ctx,next)=>{ console.log(1) axios.get('www.baidu.com').then((res)=>{ const a = res }) console.log(a) console.log(2) })
使用await阻塞线程之后,异步同步化 app.use(async(ctx,next)=>{ console.log(1) const a = await axios.get('www.baidu.com') console.log(a) console.log(2) })
|
路由
初级路由判断 ctx.path
返回路由,ctx.method
返回调用方法,ctx.body
定义返回内容
app.use(async(ctx,next)=>{ if(ctx.path ==='/clasic/latest' && ctx.method ==='GET'){ ctx.body = {key:"clasic"} } })
|
使用koa-router
方法
const router = new route()
router.get('/',(ctx,next)=>{ ... })
app.use(router.routes())
|
nodemon 自动重启node服务
全局启动nodemon app.js
一键导入module require-directory
轮子
const requireDirectory = reuire('require-directory')
requreDirectory(module,'./api/v1',visit:function)
function whenExportModule(obj){ if(obj instanceof Router){ app.use(obj.routes()) } }
|
改造 路由
创建 core.js
const Router = require('koa-router') const requireDirectory = require('require-dicectory')
class InitManger{ static initcore(app){ InitManager.app = app InitManager.InitLoadRouters() } static InitLoadRouters(){ const path = `${process.cwd()}/api/v1` requireDirectory(module,path,{ visit:whenLoadrouters }) function whenLoadrouters(obj){ if(obj instanceof Router){ InitManger.app.use(obj.routes()) } } } }
|
app.js引入
const IniManager = require('core.js')
InitManager.initcore(app) app.listen(3000)
|
校验处理
获取参数
假设访问的路由地址为localhost:3000/v1/3/classic/latest?password=123
header:token:1111
,body:{"key":"localhost"}
router.get('/v1/:id/classic/latest',(ctx,next)=>{ const params = ctx.params const query = ctx.request.query const query = ctx.request.header const body = ctx.request.body ctx.body={key:"classic"} })
|
异常处理
设置全局返回的状态码 异常分为:已知异常
未知异常
message error_code request_url HTTP status code 2xx 4xx 5xx 常见Http状态码
200 'ok' 400 'params error' 404 'Not found' 403 'forbidden' 502 'bad gateway' 路径错误 500 '服务器异常' 504 '服务器超时'
|
全局异常处理
定义一个execption
类继承Error
,然后抛出异常时实例化,传递参数,打印全局异常返回json
创建Http-execption.js
class Httpexecption extends Errror{ constructor(msg="服务器异常",code=400,errorCode=10001){ super() this.msg = msg this.code = code this.errorCode = 10001 } }
class Paramexecption extends Httpexecption{ constructor(msg,code,errCode){ super() this.msg = msg || '参数错误' this.code = 400 this.errorCode = 10000 } } module.exports = { Httpexecption, Paramexecption }
|
创建全局异常处理中间件
const {Httpexecption} = require('Http-execption')
const execption = async(ctx,next){ try{ await next() }catch(error){ if(error instanceof Httpexecption){ ctx.body = { msg: error.msg, error_code:error.errorCode, request:`${ctx.method} ${ctx.path}` } } } }
module.exports=execption
|
在定义路由时抛出异常
const {Paramexecption} = require('Http-execption') router.get('/latest',(ctx,next)=>{ const query = ctx.request.query if(!query){ const error = new Paramexecption() thorw error } })
|
未知异常
else{ ctx.body = { msg:"未知异常发生", erro_code:999, request:`${ctx.method} ${ctx.path}` } ctx.status = 500 } }
|
参数校验
使用Lin-validator
进行参数校验 使用前必须定义好全局抛出参数异常的 Paramexecption
,然后引入util.js
第一步 创建校验器类
const {Linvalidator,Rule} from 'Lin-validator.js'
class PositiveIntegerValidator extends Linvalidator{ constructor(){ super() this.id = [ new Rule('isInt','参数必须是正整数',{min:1}) ] } } } module.exports = {PositiveIntegerValidator}
|
第二步 引用校验器进行校验 (调用validate方法)
const {PositiveIntegerValidator} = require('validator.js')
router.get('/classic/:id/latest',(ctx,next)=>{ const v = new PositiveIntegerValidator().validate(ctx) })
|
**使用校验器获参数 ** get方法
const param = ctx.params const v =new PositiveIntegerValidator().validate(ctx) const id = v.get('param.id')
const id = v.get('param.id',parsed:false)
|
配置开发环境的异常抛出
由于我们捕获到的异常都去做了全局异常处理,导致某些异常无法判断, 所以定义config来配置开发环境
module.exports={ enviorment:"dev" }
|
在全局异常中间件,判断是否是开发环境,然后抛出异常
if(global.config.enviorment === 'dev'){ throw error }
|
sql复习
创建数据库
删除数据库
创建表
约束
1. 非空约束 NOT NULL 2. 默认值约束 DEFAULT '男' 3. 唯一性约束 UNIQUE 3. 主键约束 PRIMARY KEY
|
create table 表名( 字段名 类型(长度) [约束], ... )
|
常见类型

删除表
查看表结构
修改表结构
修改列名 Alter table 表名 change 列名 新列名 类型;
修改列类型 Alter table 表名 modify 列名 新类型;
|
增
insert into 表名(字段1,字段2...)values(值1,值2...)
|
其他方式
insert into 表名(字段1,字段2) values(值1,值2),(值1,值2); //插入多条数据【MYSQL】 insert into 表名 values(值1,值2); //针对全表所有字段进行插入操作 insert into 表名(字段) select 字段 from 表2; //查询结果插入 insert into 表名 select 字段 from 表2; //查询结果,全表插入
|
删
改
update 表 set 字段=值 where 条件 例: update user set username=7yue where id =1;
|
查
查询表中全部内容
查询指定列信息
条件查询
select 列.. from 表名 where 条件
|
条件运算符 逻辑运算符
= > >= < <= and && or not
|
范围查询
where 列 between 条件1 and 条件2; //列在这个区间的值
where 列 not between 条件1 and 条件2; //不在这个区间
where !( 列 between 条件1 and 条件2 ); //同样表示不在这个区间
|
空值查询
where 列 is null; //查询列中值为null的数据
|
模糊查询
where 列 like '%0'; //表示以0结尾 where 列 like '0%'; //表示以0开头 where 列 like '%0%'; //表示数据中包含0
|
排序
where 条件 order by 列 [asc/desc]
|
多表查询
select * from 表1,表2 where 表1.字段=表2.字段; //隐式内连接,使用where条件消除笛卡尔积
select * from 表1 [inner] join 表2 on 表1.字段=表2.字段; //显式内连接,如果是多张表,则一直在join..on后依次添加join..on即可,inner关键字可被省略
|
sequlize(模型导入数据库)
初始化配置
以上配置都可以参考sequelize文档 或者中文文档
第一步,定义数据库配置
config.js
module.exports = { database:{ dbName:"koa", host:"localhost", port:3306, user:"root", password:"wohenpi0918" } }
|
第二步,配置sequelize
更多配置参考
API文档
const Sequelize = require('sequelize') const {dbName,host,port,user,password} = require('config.js')
const sequlize = 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, } }) sequelize.sync({ force: false, }) module.exports = {sequelize}
|
第三步,定义模型层
model下创建user.js 更多定义方法 参考数据类型
const {sequelize} =require('db.js') const {Sequelize,Model} = require('sequelize')
class User extends Model{ }
User.init({ id:{ type:Sequelize.INTEGER, primaryKey:true, autoIncrement:true }, username:{type:Sequelize.STRING,unique:true}, password:Sequelize.STRING, email:{type:Sequelize.STRING,unique:true}, openid:{ type:Sequelize.STRING(64), unique:true } },{ sequelize, tableName:'user' })
|
启动项目, sequelize会创建一张user
表

sequelize相关API
sequelize大多数查询的API都是返回的pormise对象,所以定义模型方法时加上 async
和await
1. 定义模型 class A extends Model{} A.unit({},{sequelize,tableName:"name"}) 2. 访问字段和设置字段值 get(){ let title = this.getDataValue('title')..} set(val){this.setDataValue('index',value)}
3. 验证Validations username:{type:Sequelize.STRING,validate:{len:[2,10] ....}}
---------- Model类API
1. removeAttribute([attribute]) 删除一个字段属性(列)
2. sync() 将当前模型同步到数据库 可以配置{force:true} 强制覆盖,每次同步都会先删除之前的表
3. drop() 删除数据库的表
4. getTableName() 获取表名,可以指定schema
5. scope() 定义限制范围
6. findOne 查询单条数据 await ModelNmae.findOne({ where:{ 'index':value } })
7. findAll 查询多条数据 await ModelName.findAll({ where:{ attr1:value, attr2:value } }) 这里可以使用到 大于,小于等`$gt` `lte` `$or` Model.findAll({ where: { attr1: { $gt: 50 }, attr2: { $lte: 45 }, attr3: { $in: [1,2,3] }, attr4: { $ne: 5 } } })
8. findById() 通过主键id查询单个实例
9. count() 统计数量
10. findAndCount 分页查询
11. create() 创建实例
12. max() min() sum()
13. upsert 创建或更新
14. className.transition(async (t)=>{ ... }) 创建事务 Modle类.transcation(async t =>{ await Favor.create({ uid,art_id,type },{transcation:t}) ...更多操作 })
15. destory 删除记录
16. restore 恢复记录
17 自增自减 increment decrement user.increment(['age', 'number'], {by:2}).then(function(user){ console.log('success'); })
|
注册
首先定义好模型 然后编写校验器 密码用盐加密,处理好异常
编写校验器
用户名: 用户名长度规范 唯一性规范
密码: 正则表达式规范
邮箱: 邮箱规范 唯一性规范
const {User} = require('user.js')
class RegisterValidator extends Linvalidator{ constructor(){ this.username= [ new Rule('isLength','用户名不符合规范',{min:4,max:32}) ] this.password1 = [ new Rule('matches','密码必须包含特殊字符,字母,数字并且超过六位','/^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/') ] this.password1 = this.password2 this.email = [ new Rule('isEmail','邮箱不符合规范') ] } validateUserName(params){ const username = params.body.username const user = User.finOne({ where:{ username:username } }) } validateEmail(params){ const email = params.body.email const user = USer.findOne({ where:{ email:email } }) } }
|
编写注册API (标准流程)
const Router = require('koa-router') const router = new Router({ prefix:'/v1/user' })
router.post('/register',async(ctx)=>{ const v = await new RegisterValidator().validate(ctx) const params = { username:v.get('body.username'), password:v.get('body.password1'), email:v.get('body.email') } await User.create(user) })
|
明文加密
const bcryptjs = require('bcryptjs')
User.init({ password:{ type:Sequeize.STRING, set(val){ const sault = bcryptjs.getSaltSync(10) const pwd = bcryptjs.hashSync(val,sault) this.setDataValue('password',pwd) } } })
|
登录
定义好登陆的方式 然后编写相关的校验器,
准备工作
首先定义好登录的方式 ,模仿枚举方式
function isInType(val){ for(let key in this){ if(this[key] === val){ return true } } return false } const LoginType = { USER_EMAIL:100, USER_PHONE:101, USER_MINI:102, isInType }
module.exports = { LoginType }
|
正式编写路由 (三个步骤)
- 编写校验器
- 校验传入的参数的账号密码是否存在数据库
- 返回响应
对account
和secret
进行校验,然后校验登录方式合法
class Loginvalidator extends Linvalidator{ constructor(){ super() this.account = [ new Rule('isLength',"账号不符合规范",{min:4,max:128}) ] this.secret = [ new Rule =('isOptional'), new Rule('isLength','密码长度必须大于6',{min:6}) ] } validateLoginType(params){ const type = params.body.loginType if(!type){ throw new Error('请传入登录方式') } if(!global.config.LoginType.isInType(type)){ throw new Error('type参数不合法 ') } } }
module.exports = {Loginvalidator}
|
isOptional 参数可以传可以不传
当参数通过校验器时, 定义方法判断传入的参数是否在数据库中存在
async function emailLogin (account,secret){ const user = await User.vertifyEmail(account,secret) }
|
一般情况下 校验传入的参数是否在数据库中,在模型中完成
user.js
class User extends Model{ async function vertifyEmail(account,secret){ const user = await User.findOne({ where:{ email:account } }) if(!user){ throw 没有此账户的错误异常 } const corret = bcrypt.compareSync(secret,User.password) if(!corret){ throw 密码错误异常 } return user } }
|
模型定义完之后
书写路由
router.get('/token',async(ctx,next)=>{ const v = new Loginvalidator().validate(ctx) const type = v.get('body.loginType') const account = v.get('body.account') const secret = v.get('body.secret') switch(type){ case LoginType.USER_EMAIL: await emailLogin(account,secret) break; case : break; } ctx.body={ token } })
|
微信登录接口
首先定义好需要的三个参数
class wxManager{ static async openidTotoken(code){ const url = util.format(URL,appId,appSecret,code) const result = await axios.get(url) if(result.status!==200){ throw new getOpenIDException() } if(result.data.errcode!==0){ throw new getOpenIDException('获取openID失败'+result.data.errcode) } let user = User.getOpenIdUser(result.data.openid) if(!user){ user = User.registerOpenId(result.data.openid) } let token = generate(user.id,Auth.USER) } }
|
util.format Node.js提供的util的API,可以将第一个参数中的占位符换成后面的参数
getOpenIdUser Model层中定义的静态方法 查询user
registerOpenId 插入openid
Auth.USER 提前定义在Auth 获取令牌的class中的,表示权限的值 用户 只需要判断scope和传入的level值(当传入的level小于用户级别的scope时就可以获取token,反之就不可以)
class Auth { constructor(level){ this.level = level || 1 Auth.USER = 8 Auth.ADMIN = 16 Auth.SUPER_ADMIN = 32 } }
|
书写完微信登录获取openid之后,可以书写一个校验拿到的token的方法
static verifyToken(token){ try { jwt.verify(token, global.config.security.secretKey) return true } catch (error) { return false } }
|
当前端从Storage
里面拿出token,检验它的合法性,然后再继续走下去
颁布令牌,获取token
在获取token
之前先了解jwt
的主要API
jwt.sign()
例:jwt.sign( {uid,scope},secretKey,{expiresIn} )
jwt.verify()
最好是放在try catch中捕获异常
|
定义基本配置
security = { secretKey:'abcdefg', expiresIn:60*60 }
|
书写颁发令牌方法
const generateToken = function(uid,scope){ const token= jwt.sign({uid,scope},security.secretKey,{expiresIn}) return token }
|
登录时获取token
async function emailLogin(account,secret){
const token = generate(user.id,2) return token }
|
async function vertifyEmail(account, secret) { const user = await User.findOne({ where: { email: account } }) if (!user) { throw new LoginExecption('账号不存在') } if (!bcrypt.compareSync(secret, user.password)) { throw new LoginExecption('密码输入错误') } return user }
|
路由携带令牌校验
想清楚三件事:
- token约定放在
header
还是body
中
- 用什么方式来检验token是否合法
- 校验令牌中间件放在什么位置
具体思路:首先一般情况下token在HTTPBasicAuth规则中是放在header部分的,然后我们通过这种方式测试
校验合法性,书写中间价时调用 jwt.verify()
来检验合法token
校验令牌必须放在路由中间件前面,因为是最高权重 只有放了权限才能进行后面
const baseAuth = require('base-auth') class Auth { constructor(){} get m(){ return async (ctx,next)=>{ const UserToken = baseAuth(ctx.req) if(!USerToken || !UserToken.name){ throw new token异常 } try{ var decode = jwt.verify(UserToken,secretKey) }catch(error){ if (error.name == 'TokenExpiredError') { errMsg = 'token令牌已经过期' } throw new ForbidenException(errMsg) } } ctx.auth = { uid:decode.uid, scope:decode.scope } await next() } }
|
new Auth().m 这里 m并不是方法 是class里面属性 通过get获取 实则是个中间件函数
校验完令牌之后,在router.get(‘’) 中间注册中间件 new Auth().m
前端携带令牌(BasicAuth方式) (API key方式)
在发送HTTP请求时,加入这样的header
header:{ Authorization:Basic base64(account:password) }
import {Base64} from 'base64-js' const base64 = Base64.encode(token+':') return 'Basic'+base64
|
此时携带的令牌数据就可以传递
header:{ Authorization:封装的函数 }
|
使用API key方式就不需要base64加密处理
class Auth { get m(){ return async(ctx,next)=>{ const UserToken = ctx.request.header.token if(!USerToken){ throw new token不合法异常 } try{ var decode = jwt.verify(UserToken,global.config.secretKey) }catch(error){ if(error.name ='TokenExpiredError'){ throw new Error('令牌过期') } throw new token不合法 } } } }
|
前端调用:
wx.request({ url:'', method:'POST', header:{ token:wx.getStorageSync('token') }, success:(res)=>{ console.log(res.data) } })
|
具体业务
首先先把数据表的概念分清
获取最新一期期刊
获取最新一期,就是拿出期刊号最大的那一个实体。
fow表降序取出第一条记录
router.get('/latest', new Auth().m,async(ctx)=>{ let latest = async folw.findOne({ order:[ ['index','DESC'] ] }) let art = async Art.getOne(latest.index) art.setDataValue('index',latest.index) ctx.body = art })
|
前端请求数据之前需要携带令牌,
Art模型中定义查找记录方法
class Art { static async getOne(art_id, type) { const find = { where: { id: art_id } }
let result = null switch (type) { case 100: result = await movie.findOne(find) break case 200: result = await music.findOne(find) break case 300: result =await sentence.findOne(find) break case 400: break default: break } return result } }
|
点赞 取消点赞
首先确定业务,点赞和取消点赞需要操作两张表,一张表记录用户点赞的记录,另一张实体表里面的收藏数量就会增加,或者减少
保证两个操作都能同时进行,可以使用数据库的事物
sequelize操作数据库的事物
sequelize.transaction(async (t)=>{ ... })
|
const favor =await Favor.finOne({ where:{ uid,art_id,type } }) if(favor){ throw new LikeException('已经点过赞') }
return sequelize.trancation(async t =>{ await Favor.create({ art_id, type, uid },{transcation : t}) const art =await Art.getOne(art_id,type) await art.increment('fav_nums',{by:1,transaction:t}) })
软删除 增加一条deleted_at MOdel.class.destroy(force:false,transcation:t)
|
上一期 下一期
首先查找flow表中index的记录,然后index+1 ,增加异常判断,查询出art表的记录,并田间like_status和index
router.get('/:index/next',new Auth().m,async(ctx)=>{ const v = await new IndexValidator().validate(ctx) const index = v.get('path.index') const next = await flow.findOne({ where:{ index:index + 1 } }) if(!next){ throw new NotFoundException('没有下一期了') } let art = await Art.getOne(next.art_id,next.type) const like_status = await Favor.Userlike(ctx.auth.uid,next.art_id,next.type) art.setDataValue('index',next.index) art.setDataValue('like_status',like_status) ctx.body = art })
|
获取点赞信息
传入uid, art_id和type对favor表进行记录查询,返回布尔值
static async Userlike(uid, art_id, type) { const favor = await Favor.findOne({ where: { uid, art_id, type:type } }) return !!favor }
|
获取某一期的详情信息
传入type和art_id查找flow的记录 找到后查找like_status和index
router.get('/:type/:id/detail',new Auth().m,async(ctx)=>{ const v = await new ClassicValidator().validate(ctx) const art_id = v.get('path.id') const type = parseInt(v.get('path.type')) const classic = await flow.scope('bh').findOne({ where:{ art_id, type:{ [Op.not]:400 } } }) if(!classic){ throw new NotFoundException('找不到该资源') } let art = await Art.getOne(art_id,type) const like_status = await Favor.Userlike(ctx.auth.uid,art_id,type) art.setDataValue('index',classic.index) art.setDataValue('like_status',like_status) ctx.body = { art } })
|
获取用户喜欢期刊列表
传入uid查询favor表得到一个数组,然后Art模型编写获取列表方法
定义一个对象 注意json的key永远都是字符串
const artInfoObj ={ 100:[], 200:[], 300:[] }
|
获取列表元素 for循环 artInfoList
artInfoObj[artinfo.type].push(artinfo.art_id)
再循环对象 查找type下的数组列表
ids artInfoObj[key]
type key
如果是空数组 跳出循环
for循环不需要定义大量的复杂逻辑,封装一个函数
注意:obj的key是字符串 传参会报错
循环引用会报undefined 解决方法:局部导入
最终结果需要将所有的数组提取到大数组 [[],[],[]]
使用faltten
方法
static async getFavorList(list,uid) { let FavorListObj = { 100: [], 200: [], 300: [] } list.forEach(item => { FavorListObj[item.type].push(item.art_id) }) let ret = [] for (let item in FavorListObj) { let itemX = parseInt(item) if (FavorListObj[item].length === 0) { continue } ret.push(await Favor._getlistByType(itemX, FavorListObj[item],uid)) } return flatten(ret) } static async _getlistByType(type, ids,uid) { const find = { where: { id: { [Op.in]: ids } } } let result = [] switch (type) { case 100: result= await movie.scope('bh').findAll(find) break case 200: result = await music.scope('bh').findAll(find) break case 300: result = await sentence.scope('bh').findAll(find) break default: break } result.forEach(async(item)=>{ let like_status =await Favor.Userlike(uid,item.id,item.type) item.setDataValue('like_status',like_status) }) return result }
|
获取热门图书
static async getHotBooklist(list){ let ids = [] list.forEach((book)=>{ ids.push(book.id) }) const favors= await Favor.scope('bh').findAll({ art_id:{ [Op.in]:ids }, type:400, group:['art_id'], attributes:['art_id',[Sequelize.fn('COUNT','*'),'count']] }) list.forEach((book)=>{ Hotbook._setCount(book,favors) }) return list } static _setCount(book,favors){ let count = 0 favors.forEach((favor)=>{ if(book.id === favor.art_id){ count = favor.get('count') } }) book.setDataValue('count',count) return book }
|
评论
定义book模型 id fav_nums
从服务器请求数据 返回详情
图书搜索
定义校验器 三个参数 query :keyword,start count
start 可传可不传 传一个默认值 summary=1 鱼书搜索不返回概要信息
encodeURI(q) 将可能为中文的编码转换
json序列化
再返回的字段中定义toJSON
方法指定返回的字段,sequelize就定义在模型中 实例方法。
this.getDataValue()
toJSON(){ return { content:this.getDataValue('content') } }
|

原型链方法定义Model方法 删除dataValues的某些字段
KOA-STATIC
处理静态资源 __dirname 项目目录
访问/static文件夹 const static = require('koa-static') const path = require('path') app.use(static(path.join(__dirname,'/static')))
添加配置:host:'http://localhost:3000/'
|

art模型替换image路径
自动无感知刷新令牌
_request(url, resolve, reject, data = {}, method = 'GET', noRefetch = false) { wx.request({ url: api.url, method: method, data: data, header: { Authorization: wx.getStorageSync('token'); }, success: (res) => { const code = res.statusCode if (code === 403) { if (!noRefetch) { _refetch( url, resolve, reject, data, method ) } } } }) } _refetch(...param) { getTokenFromServer((token) => { this._request(...param, true); }); }
|
自己动手改造koa框架,方便开发(最新更新)
了解详情可以前往Koa-template