Skip to content

yzh990918/Node_review

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

19 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Welcome to Best Node Practice

Version License: MIT

Koa framework foundation for node.js, a tutorial for building the restfuAPI

API Docs Path: DOCS

test this api: test

Getting started

# clone the Project
git clone https://github.com/251205668/Node_review.git

# enter the project directory
cd Node_review

# install dependency
npm install

# develop
npm run dev

# test
Listening the 3000 port

Features

  • middlewares
    • Global exception handling
    • JWT token validation
    • Token does not sense refresh
  • classic journals
    • Get the latest journals
    • Get the next issue of the journal
    • Get the last issue of the journal
    • For details of a particular issue
    • Get the thumb up information
    • Get the journals I liked
  • Book
    • Get a list of popular books
    • Get book reviews
    • Get the book thumb up information
    • A new comment
    • Get hot search keywords
    • Books retrieval
    • Get book details
  • favor
    • give a like
    • Cancel the thumb up

How It work

Basic

Write the most basic code for an API

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' }
})
// router.routes() 注册中间件
app.use(router.routes())
app.listen(3000, () => {
  console.log('http监听端口3000')
})

middleware

The middleware sends functions called by HTTP, one instance can define multiple middleware, and the middleware call always returns promise

app.useregister the middleware。ctxcontents,nextnext middleware

// 中间件--就是函数
app.use((ctx, next) => {
  // ctx 上下文
  console.log('七月')
  next()
})
app.use((ctx, next) => {
  console.log('八月')
})

Passing parameters By mounting to CTX, the onion model is first guaranteed

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()
})

// 打印出dom结构,实现了传参

The onion model

Execute on fun1 and then execute the middleware function under fun1.You can determine whether the function is fully executed, with the middleware function as the dividing line

Simple example:

app.use(async (ctx, next) => {
  fun1()
  await next()
  funt1下()
})

app.use(async (ctx, next) => {
  funt2()
})

use async,await to let promise sequentially,Call to order: fun1()TOP ====> fun2() ====> fun1 Bottom

async await

await Features:

  • evaluation 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)
})

// 1 3 2
  • Blocking threads A common asynchronous call: 'to the resource read file operation database send http`
//默认异步调用

app.use((ctx,next)=>{
    console.log(1)
    axios.get('www.baidu.com').then((res)=>{
          const a = res
    })
    console.log(a)
    console.log(2)
})

// 由于是异步请求 打印结果 1 2 res

使用await阻塞线程之后,异步同步化
app.use(async(ctx,next)=>{
    console.log(1)
    const a = await axios.get('www.baidu.com')
    console.log(a)
    console.log(2)
})
// 打印 1 res 2

Limited energy, not translated for the time being

路由

初级路由判断 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')

// 导入全部模块前判断 是否是Router的对象
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) => {
  // 获取url参数
  const params = ctx.params
  // 获取query
  const query = ctx.request.query
  // 获取token
  const query = ctx.request.header
  // 获取访问时内容
  const body = ctx.request.body
  ctx.body = { key: 'classic' }
})
异常处理

设置全局返回的状态码 异常分为:已知异常 未知异常

msg
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 = 500, errorCode = 9999) {
        super()
        this.msg = msg
        this.code = code
        this.errorCode = 9999
      }
    }
    
    class Paramexecption extends Httpexecption {
      constructor(msg, code, errCode) {
        super()
        this.msg = msg || '参数错误'
        this.code = 400
        this.errorCode = 10000
      }
    }
    module.exports = {
      Httpexecption,
      Paramexecption,
    }
    • 创建全局异常处理中间件(整合token鉴权)

前提是在app.js中引入koa-jwt鉴权路由,鉴权token,异常判断通常是第一个中间件

统一异常返回格式

{
  "msg":"错误信息",
  "code":999,
  "request":"HttpRequest.method/request.path"
}
const {HttpException} =require('Http-exception')
const exception = async(ctx,next){
  try{
    await next()
  }catch(error){
    // 处理token异常 
    //处理Httpexception,处理位置异常
    if(error.status === 401){
      ctx.status = 401
      ctx.body={
        msg:"token令牌不合法",
        code:401,
        request:`${ctx.method}${ctx.path}`
      }
    }else{
      // 已知异常
      const isHttpException = error instanceof HttpException
      if(isHttpException){
        ctx.status = error.code
        ctx.body = {
          msg:error.msg,
          code:error.errorCode,
          request:`${ctx.method}${ctx.path}`
        }
      }else{
        //未知异常
        ctx.status = 500
        ctx.body = {
          msg:"未知异常",
          code:999,
          request:`${ctx.method}${ctx.path}`
        }
      }
    }

  }
}
参数校验

使用Lin-validator进行参数校验 使用前必须定义好全局抛出参数异常的 Paramexecption,然后引入util.js

第一步 创建校验器类

const {Linvalidator,Rule} from 'Lin-validator.js'

// 校验正整数
class PositiveIntegerValidator extends Linvalidator{
    constructor(){
       super()
    //  使用lin-validator校验规则 三个参数 规则,返回提示信息,附加参数
    //  要与路由的参数信息一一对应 数组形式
    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') // 自动将id 转换为整形
// 如果不想转换
const id = v.get('param.id', (parsed: false))

配置开发环境的异常抛出

由于我们捕获到的异常都去做了全局异常处理,导致某些异常无法判断, 所以定义config来配置开发环境
module.exports = {
  enviorment: 'dev',
}

在全局异常中间件,判断是否是开发环境,然后抛出异常

if (global.config.enviorment === 'dev') {
  throw error
}

sql 复习

创建数据库
CREATE DATABASE 数据库名
删除数据库
DROP DATABASE 数据库名
创建表

约束

1. 非空约束  NOT NULL
2. 默认值约束  DEFAULT ''
3. 唯一性约束 UNIQUE
3. 主键约束 PRIMARY KEY
create table 表名(
字段名 类型(长度) [约束],
    ...
)

常见类型

删除表
DROP TABLE 表名;
查看表结构
DESC 表名
修改表结构
修改列名
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 字段 from2;         //查询结果插入
insert into 表名 select 字段 from2;               //查询结果,全表插入
delete fromwhere 条件
updateset 字段=where 条件
例: update user set username=7yue where id =1;

查询表中全部内容

select * from 表名

查询指定列信息

select1 from 表名

条件查询

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的数据

模糊查询

wherelike '%0';   //表示以0结尾
wherelike  '0%';   //表示以0开头
wherelike  '%0%';   //表示数据中包含0

排序

where 条件 order by 列 [asc/desc]

多表查询

select * from1,表2  where1.字段=2.字段;  //隐式内连接,使用where条件消除笛卡尔积

select * from1 [inner] join2 on1.字段=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,
    // 是否在命令行打印出sql
      logging:true,
    // 显示北京时间
  	  timezone:'+08:00',
      define:{
    // 加入创建时间 更新 updatedAt, createdAt
      timestamps:true,
    // 必须和timestamps同时使用 增加一个 deletedAt 标识当前时间
      paranoid:true,
    // 重命名 时间戳字段
       createdAt:'created_at',
       updatedAt:'updated_at',
       deletedAt:'deleted_at',
    // 不使用驼峰式命令规则,这样会在使用下划线分隔
    // 这样 updatedAt 的字段名会是 updated_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,
      // 自动增长 id编号
      autoIncrement: true,
    },
    username: { type: Sequelize.STRING, unique: true },
    password: Sequelize.STRING,
    email: { type: Sequelize.STRING, unique: true },
    openid: {
      // 最大字符长度 64
      type: Sequelize.STRING(64),
      unique: true,
    },
  },
  {
    sequelize,
    // 自定义表名  默认会以模型名为表名
    tableName: 'user',
  }
)

启动项目, sequelize 会创建一张user

sequelize 相关 API

sequelize 大多数查询的 API 都是返回的 pormise 对象,所以定义模型方法时加上 asyncawait

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
    }
  }
})
// WHERE attr1 > 50 AND attr2 <= 45 AND attr3 IN (1,2,3) AND attr4 != 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','邮箱不符合规范')
        ]
    }
    //自定义方法 校验用户和邮箱的唯一性 必须以validate开头
    validateUserName(params){
        // 通过params.body 拿到body参数
        const username = params.body.username
        // sequelize条件查询
        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'),
  }
  // 插入数据库  模型.create(参数列表)
  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,
}

正式编写路由 (三个步骤)

  • 编写校验器
  • 校验传入的参数的账号密码是否存在数据库
  • 返回响应

accountsecret进行校验,然后校验登录方式合法

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;
    }
    // 暂时返回token
    ctx.body={
        token
    }
})
微信登录接口

首先定义好需要的三个参数

URL
appId
appSecret
class wxManager {
  static async openidTotoken(code) {
    // 拼接url
    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)
    }
    // 成功后 查询数据库 然后插入数据库 再获取token
    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() // 生成令牌  需要传入参数:1.传入自定义信息(后面可封装在auth里) 2.secretKey秘钥(用户自定义) 3.配置(失效时间)

例:jwt.sign(
   {uid,scope},secretKey,{expiresIn}
  )


jwt.verify() //校验令牌 如果token无效会抛出异常
// 需要传入 token 秘钥 两个个参数
最好是放在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) {
      // user 登录时获
    
      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
    }

路由携带令牌校验

想清楚三件事:

  1. token 约定放在header还是body
  2. 用什么方式来检验 token 是否合法
  3. 校验令牌中间件放在什么位置

具体思路:首先一般情况下 token 在 HTTPBasicAuth 规则中是放在 header 部分的,然后我们通过这种方式测试

校验合法性,书写中间价时调用 jwt.verify()来检验合法 token

校验令牌必须放在路由中间件前面,因为是最高权重 只有放了权限才能进行后面

const baseAuth = require('base-auth')
class Auth {
    constructor(){}
    get m(){
        return async (ctx,next)=>{
            // 获取basicAuth的token值  而且这里必须用原生API req
            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) //必须 是base64加密后的token信息
}

// base64基本用法
import {Base64} from 'base64-js'
const base64 = Base64.encode(token+':')
return 'Basic'+base64

此时携带的令牌数据就可以传递

header: {
  Authorization: 封装的函数
}

使用 API key 方式就不需要 base64 加密处理

// 拿到约定好放在header或者query的token
class Auth {
  get m() {
    return async (ctx, next) => {
      const UserToken = ctx.request.header.token
      if (!USerToken) {
        throw new token不合法异常()
      }
      // 校验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)
  },
})

具体业务

首先先把数据表的概念分清

  • 业务表 : 解决业务问题 抽象出来的,记录业务 比如说:一期又一期的期刊,存放他们不同的 index 来区分期刊

  • 实体表 :具体到某个模型的数据,各种字段

    sequelize 操控数据库具体参考地址

    模型层:分出 classic art flow user favor

    定义模型层

    引入 sequelize 实例

    定义字段

    导出模型

    classic: 分为 movie sentence music 共同字段定义

    art :所有的实体,所有期刊 由几个模型拼凑

    flow: 业务模型 应有的字段:art_id,index,type 对取出实体模型记录非常重要

    user: 用户模型

    favor

获取最新一期期刊

获取最新一期,就是拿出期刊号最大的那一个实体。

fow 表降序取出第一条记录

router.get('/latest', new Auth().m,async(ctx)=>{
   let latest = async folw.findOne({
       // sequlize排序 写法 升序 ASC
       order:[
           ['index','DESC']
       ]
   })
   let art = async Art.getOne(latest.index)
   art.setDataValue('index',latest.index)
    ctx.body = art

})

前端请求数据之前需要携带令牌,

Art 模型中定义查找记录方法

class Art {
  // 传入art_id 和 type
  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)=>{
    ...
})
// 首先获取Favor中是否该用户点过赞 然后如果没有点赞向数据库添加一条记录 并且增加art实体的fav_nums

const favor =await Favor.finOne({
    where:{
        uid,art_id,type
    }
})
if(favor){
    throw new LikeException('已经点过赞')
}
// 事务一定要用return的方式执行
return sequelize.trancation(async t =>{
    await Favor.create({
        art_id,
        type,
        uid
    },{transcation : t})
    const art =await Art.getOne(art_id,type)
    // 自增涨一个字段 by 增长值
    await art.increment('fav_nums',{by:1,transaction:t})
})

// dislike 同理
软删除 增加一条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) => {
  // 校验 获取art_id type 获取实例 然后增加属性
  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) {
    // 返回用户是否喜欢这个art
    const favor = await Favor.findOne({
      where: {
        uid,
        art_id,
        type:type
      }
    })
    // 如果存在点赞 就返回true
    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) {
    // 最终返回结果 [[100:],[200:],[300:]]
    let FavorListObj = {
      100: [],
      200: [],
      300: []
    }
    // 分别向不同的type中添加 ids  现在的obj {100:[1,2,3],200:[1,2,3]..}
    list.forEach(item => {
      FavorListObj[item.type].push(item.art_id)
    })
    let ret = []
    for (let item in FavorListObj) {
      // 拿到每个key :item-type  拿到每个key下的ids FavorListobj[item]
      //进行in查询
      let itemX = parseInt(item)
      if (FavorListObj[item].length === 0) {
        continue
      }
      ret.push(await Favor._getlistByType(itemX, FavorListObj[item],uid))
    }
    // 需要借助loadsh 打散二维数组为一维数组
    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 返回实体数组 [[],[],[]]
    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){
    // 获取所有的art_id
    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'],
    // 分组结果 [{art_id:1,count:},{}....]
    attributes:['art_id',[Sequelize.fn('COUNT','*'),'count']]
    })
    // 循环遍历所有的图书
    list.forEach((book)=>{
      Hotbook._setCount(book,favors)
    })
    return list
  }
  // 如果存在favor表中就把 count赋给book
  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')))
// 这样localhost路径 就可以访问到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);
    });
  }

Author

👤 努力中的杨先生

🤝 Contributing

Contributions, issues and feature requests are welcome!
Feel free to check issues page. You can also take a look at the contributing guide.

Show your support

Give a ⭐️ if this project helped you!

Thanks

Thanks for the explanation of node. js course given by the teacher 七月, whose rigor of the course and exception handling of the project are all worthy of learning, which enabled me to acquire a lot of knowledge of Node and make my understanding of javascript to a higher level.Thanks!!

Here are the coruse of the project :course

📝 License

Copyright © 2020 努力中的杨先生.
This project is MIT licensed.

About

The best Node.js -Koa restfulAPI practice 💦💦💦

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published