# NodeJS 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"}
})
// router.routes() 注册中间件
app.use(router.routes())
app.listen(3000,()=>{
console.log('http监听端口3000')
})
# 中间件
中间件-发送http调用的函数,一个实例可以定义多个中间件,中间件调用总是返回promise
app.use
注册中间件。ctx
上下文,next
下一个中间件
// 中间件--就是函数
app.use((ctx,next)=>{
// ctx 上下文
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()
})
// 打印出dom结构,实现了传参
# 洋葱模型
先执行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)
})
// 1 3 2
- 阻塞线程
常见的异步调用:
对资源 读文件 操作数据库 发送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
# 路由
初级路由判断 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"}
})
# 异常处理
设置全局返回的状态码 异常分为:已知异常
未知异常
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 // app.js注册该中间件
在定义路由时抛出异常
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()
// 使用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 字段 from 表2; //查询结果插入
insert into 表名 select 字段 from 表2; //查询结果,全表插入
# 删
delete from 表 where 条件
# 改
update 表 set 字段=值 where 条件
例: update user set username=7yue where id =1;
# 查
查询表中全部内容
select * from 表名
查询指定列信息
select 列1 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的数据
模糊查询
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文档 (opens new window) 或者中文文档 (opens new window)
第一步,定义数据库配置
config.js
module.exports = {
database:{
// 数据库名 主机号 端口 用户名 密码
dbName:"koa",
host:"localhost",
port:3306,
user:"root",
password:"wohenpi0918"
}
}
第二步,配置sequelize
更多配置参考
API文档 (opens new window)
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 更多定义方法 参考数据类型 (opens new window)
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对象,所以定义模型方法时加上
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
}
}
})
// 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
}
正式编写路由 (三个步骤)
- 编写校验器
- 校验传入的参数的账号密码是否存在数据库
- 返回响应
对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;
}
// 暂时返回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 }
# 路由携带令牌校验
想清楚三件事:
- token约定放在
header
还是body
中 - 用什么方式来检验token是否合法
- 校验令牌中间件放在什么位置
具体思路:首先一般情况下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操控数据库具体参考地址 (opens new window)
模型层:分出 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);
});
}
# 配置webpack 支持ES6
安装
webpack
npm i -D webpack webpack-cli
安装 相关依赖
"devDependencies": { "@babel/core": "^7.6.2", "@babel/node": "^7.6.2", "@babel/preset-env": "^7.6.2", "babel-loader": "^8.0.6", "clean-webpack-plugin": "^3.0.0", "cross-env": "^6.0.3", "nodemon": "^1.19.3", "npm-run-all": "^4.1.5", "rimraf": "^3.0.0", "terser-webpack-plugin": "^2.1.2", "webpack": "^4.41.0", "webpack-cli": "^3.3.9", "webpack-merge": "^4.2.2", "webpack-node-externals": "^1.7.2" }
根路径下创建
webpack.config.js
← 中后台学习 Springboot 笔记 →