# create-nuxt-app

  • 创建一个nuxt
npx create-nuxt-app front
  • 创建一个egg后台
npm init egg --type=simple

在package.json中ci命令测试代码覆盖率

"ci": "npm run lint && npm run cov",

# nuxt配置代理 nuxt.config.js

  • @nuxtjs/proxy
  • proxy对象

module.exports = {

 head: {
    title: '小开社区',//配置title
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
    ]
  },
  modules: [
    // Doc: https://axios.nuxtjs.org/usage
    '@nuxtjs/axios',
    '@nuxtjs/proxy'
  ],
  proxy:{
    "/api/":{
      target:"http://localhost:7001",
      secure:false,
      pathRewrite:{
        '^/api':""
      }
    }
  }
}

# nuxt plugins axios配置

import Vue from 'vue'
import axios from 'axios'
import {MessageBox} from 'element-ui'
let service = axios.create({
  timeout:5000,
  // 前缀
  baseURL:'/api'
})
const TOKEN_KEY = 'KKB_USER_TOKEN'

// @ todo 拦截器 管理token
// redirect:跳转
export default({store, redirect})=>{
  // 请求拦截
  service.interceptors.request.use(
    config=>{
      // 请求加token
      const token = window.localStorage.getItem(TOKEN_KEY)
      // 设置url白名单
      if(token){
        config.headers.common['Authorization'] = 'Bearer '+token
      }
      return config
    },
    err=>{
      return Promise.reject(err)
    }
  )
// 响应拦截
service.interceptors.response.use(
  async response=>{
    let {data, config} = response
    // console.log('响应拦截',response)
    // 写token
    // 也可以卸载login的逻辑李
    if(data.code===0){
      if(config.url ==='/api/user/login'){
        localStorage.setItem(TOKEN_KEY, data.data.token)
      }
    }else if(data.code===-666){
      // code是-666的 意味着token过期
      // @todo
       MessageBox.confirm('登录过期了','过期',{
        confirmButtonText:'登录',
        showCancelButton:false,
        type:'warning'
      }).then(()=>{
        localStorage.removeItem(TOKEN_KEY)
        redirect({ path:'/login'})
      })

    }
    return data
  },
  err=>{
    return Promise.reject(err)
  }
)
}


// 这里要设置token管理和路由跳转



Vue.prototype.$http =service

export const http = service

地址 (opens new window)

# 后台配置的egg相关内容

# egg部分使用插件安装

  • egg-router-group : 后端路由接口分组

  • egg-mongoose : 连接mongdb使用

  • egg-validate : 校验接口数据

  • md5 : 加密使用

  • jsonwebtoken : jwt

  1. app/router.js:定制api名称和请求类型,引入controller具体模块里写的方法
'use strict'

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app
  const jwt = app.middleware.jwt({ app })
  router.get('/', controller.home.index)

  // 验证码
  router.get('/captcha', controller.util.captcha)
  router.group({ name: 'article', prefix: '/article' }, router => {
    const { create, detail, index } = controller.article
    router.post('/create', jwt, create)
    router.get('/:id', detail)
    router.get('/', index)
  })
}
  1. app/controller/base.js:定制规范,创建一个基于egg Controller的基类,便于返回值展示给前端
// 定制规范
const { Controller } = require('egg')
class BaseController extends Controller {
  success(data) {
    this.ctx.body = {
      code: 0,
      data,
    }
  }
  message(message) {
    this.ctx.body = {
      code: 0,
      message,
    }
  }
  error(message, code = -1, errors = {}) {
    this.ctx.body = {
      code,
      message,
      errors,
    }
  }
}

module.exports = BaseController
  1. 其中之一模块:导出具体的方法,在这里实现请求数据,然后给出返回信息。
const BaseController = require('./base')
const marked = require('marked')
class ArticleController extends BaseController {
  async index() {
    const { ctx } = this
    const articles = await ctx.model.Article.find().populate('author').sort({ createdAt:-1})
    this.success(articles)
  }
  async detail() {
    // 访问量统计
    const { ctx } = this
    const { id } = ctx.params
    const article = await ctx.model.Article.findOneAndUpdate({ _id: id }, { $inc: { views: 1 } }).populate('author')
    this.success(article)
  }
  async create() {
    const { ctx } = this
    const { userid } = ctx.state
    const { content } = ctx.request.body

    const title = content.split('\n').find(v => {
      return v.indexOf('# ') === 0
    })

    const obj = {
      title: title.replace('# ', ''),
      article: content, // 内部编辑的时候看的
      article_html: marked(content), // 给外部显示看的
      author: userid,

    }
    const ret = await ctx.model.Article.create(obj)
    if (ret._id) {
      this.success({
        id: ret.id,
        title: ret.title,
      })
    } else {
      this.error('创建失败')
    }
  }

}

module.exports = ArticleController

# egg配置 csrf jwt

config.default.js,默认security中的csrf是开启的,所以没配token会自动返回403,在项目刚搭建时可以关闭,手动改为false即可。

/* eslint valid-jsdoc: "off" */

'use strict'
const path = require('path')
/**
 * @param {Egg.EggAppInfo} appInfo app info
 */
module.exports = appInfo => {
  /**
   * built-in config
   * @type {Egg.EggAppConfig}
   **/
  const config = exports = {}

  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1584231361040_3227'

  config.multipart = {
    mode: 'file',
    whitelist: () => true,
  }
  config.UPLOAD_DIR = path.resolve(__dirname, '..', 'app/public')
  // add your middleware config here
  config.middleware = []

  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
  }

  return {
    ...config,
    ...userConfig,
    security: {
      csrf: {
        enable: false,
      },
    },
    mongoose: {
      client: {
        url: 'mongodb://127.0.0.1:27017/kkbhub',
        options: {},
      },
    },
    jwt: {
      secret: '@Kaikeba!123Abc!:',
    },
  }
}

# egg查询数据库和校验以及组装信息

  • egg-validate完成校验
  • ctx.validate(createRule)结合校验规则
  • ctx.request.body:前端传递的参数在这里可以获取到
  • ctx.params.id:params中也可能传递了某些参数
  • ctx.model.User => 在Schema中定义好的模型使用
  • this可以解构出ctx和app
  • 在注册和登录时对密码加盐,提高安全性。
  • jwt.sign设token
  • app.config.jwt.secret => 在config.default.js中return中进行配置
const md5 = require('md5')
const jwt = require('jsonwebtoken')
const BaseController = require('./base')

const HashSalt = ':Kaikeba@good!@123'
const createRule = {
  email: { type: 'email' },
  nickname: { type: 'string' },
  passwd: { type: 'string' },
  captcha: { type: 'string' },
}

class UserController extends BaseController {

  async login() {
    // this.success('token')
    const { ctx, app } = this
    const { email, captcha, passwd, emailcode } = ctx.request.body
    if (captcha.toUpperCase() !== ctx.session.captcha.toUpperCase()) {
      return this.error('验证码错误')
    }

    if (emailcode !== ctx.session.emailcode) {
      return this.error('邮箱验证码错误')
    }

    const user = await ctx.model.User.findOne({
      email,
      passwd: md5(passwd + HashSalt),
    })
    if (!user) {
      return this.error('用户名密码错误')
    }
    // 用户的信息加密成token 返回
    const token = jwt.sign({
      _id: user._id,
      email,
    }, app.config.jwt.secret, {
      expiresIn: '100h',
    })
    this.success({ token, email, nickname: user.nickname })
  }
  async register() {
    const { ctx } = this
    try {
      // 校验传递的参数
      ctx.validate(createRule)
    } catch (e) {
      // console.log(e)
      return this.error('参数校验失败', -1, e.errors)
    }

    const { email, passwd, captcha, nickname } = ctx.request.body

    if (captcha.toUpperCase() !== ctx.session.captcha.toUpperCase()) {
      return this.error('验证码错误')
    }
    if (await this.checkEmail(email)) {
      this.error('邮箱重复啦')
    } else {
      const ret = await ctx.model.User.create({
        email,
        nickname,
        passwd: md5(passwd + HashSalt),
      })
      if (ret._id) {
        this.message('注册成功')
      }
    }


    // this.success({name:'kkb'})
  }
  async checkEmail(email) {
    const user = await this.ctx.model.User.findOne({ email })
    return user
  }
  async verify() {
    // 校验用户是否存在
  }
  async detail() {
    // 只有token怎么获取详情
    const { ctx } = this
    const user = await this.checkEmail(ctx.state.email)
    this.success(user)
  }
  async info() {
    const { ctx } = this
    const { email } = ctx.state
    const user = await this.checkEmail(email)
    this.success(user)
  }
  async updateInfo() {
    const { ctx } = this
    const url = ctx.request.body.url

    await ctx.model.User.updateOne(
      { _id: ctx.state.userid },
      { avatar: url }
    )
    this.success()
  }
  async isfollow() {
    const { ctx } = this
    const me = await ctx.model.User.findById(ctx.state.userid)
    // 我的follow字段李,有没有传来的这个用户id
    const isFollow = !!me.following.find(id => id.toString() === ctx.params.id)
    this.success({ isFollow })
  }
  async follow() {
    const { ctx } = this

    const me = await ctx.model.User.findById(ctx.state.userid)
    const isFollow = !!me.following.find(id => id.toString() === ctx.params.id)
    if (!isFollow) {
      me.following.push(ctx.params.id)
      me.save()
      this.message('关注成功')
    }
  }
  async cancelFollow() {
    const { ctx } = this
    const me = await ctx.model.User.findById(ctx.state.userid)
    // 把用户从我的following数组中删掉
    const index = me.following.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.following.splice(index, 1)
      me.save()
      this.message('取消成功')
    }
  }
  async following() {
    const { ctx } = this
    const users = await ctx.model.User.findById(ctx.params.id).populate('following')
    this.success(users.following)
  }
  async followers() {
    const { ctx } = this
    const users = await ctx.model.User.find({ following: ctx.params.id })
    this.success(users)
  }
  async likeArticle() {
    const { ctx } = this
    const me = await ctx.model.User.findById(ctx.state.userid)
    if (!me.likeArticle.find(id => id.toString() === ctx.params.id)) {
      me.likeArticle.push(ctx.params.id)
      me.save()
      await ctx.model.Article.findByIdAndUpdate(ctx.params.id, { $inc: { like: 1 } })
      return this.message('点赞成功')
    }
  }
  async cancelLikeArticle() {
    const { ctx } = this
    const me = await ctx.model.User.findById(ctx.state.userid)
    const index = me.likeArticle.map(id => id.toString()).indexOf(ctx.params.id)
    if (index > -1) {
      me.likeArticle.splice(index, 1)
      me.save()
      await ctx.model.Article.findByIdAndUpdate(ctx.params.id, { $inc: { like: -1 } })
      return this.message('取消点赞成功')
    }
  }
  async articleStatus() {
    const { ctx } = this
    const me = await ctx.model.User.findById(ctx.state.userid)
    console.log(me)
    const like = !!me.likeArticle.find(id => id.toString() === ctx.params.id)
    const dislike = !!me.disLikeArticle.find(id => id.toString() === ctx.params.id)
    this.success({
      like, dislike,
    })
  }
}

module.exports = UserController

# model层

  • 在app文件夹下创建model文件夹,创建对应的数据库模型(egg-mongoose已经安装)
  • timestamps:用来生成新建事件和更新时间
  • 返回mongoose.model
  • app.mongoose
  • const Schema = mongoose.Schema=> new Schema()
  • 某些数据不想被直接查出来,在Schema中的字段上添加select:false

module.exports = app => {
  const mongoose = app.mongoose
  const Schema = mongoose.Schema


  const UserSchema = new Schema({
    __v: { type: Number, select: false },
    email: { type: String, required: true },
    passwd: { type: String, required: true, select: false },
    nickname: { type: String, required: true },
    avatar: { type: String, required: false, default: '/user.png' },
    following: {
      type: [{ type: Schema.Types.ObjectId, ref: 'User' }],
      default: [],
    },
    likeArticle: {
      type: [{ type: Schema.Types.ObjectId, ref: 'Article' }],
      default: [],

    },
    disLikeArticle: {
      type: [{ type: Schema.Types.ObjectId, ref: 'Article' }],
      default: [],

    },
  }, { timestamps: true })
  return mongoose.model('User', UserSchema)
}

# 发送邮件

cnpm i  nodemailer --save
  • const transporter = nodemailer.createTransport
  • transporter.sendMail(mailOptions)
const { Service } = require('egg')
const path = require('path')
const nodemailer = require('nodemailer')

// 发出邮件方
const userEmail = 'shengxinjing@126.com'
// 创建通道
const transporter = nodemailer.createTransport({
  service: '126',
  //安全链接
  secureConnection: true,
  auth: {
    user: userEmail,
    pass: 'a316783812',
  },
})

class ToolService extends Service {
  async sendMail(email, subject, text, html) {
    console.log(email, subject, html)
    const mailOptions = {
      from: userEmail,
      //抄送,规避掉一些邮箱垃圾邮件的规则
      cc: userEmail,
      to: email,
      subject,
      text,
      html,
    }
    try {
      await transporter.sendMail(mailOptions)
      return true
    } catch (err) {
      console.log('email error', err)
      return false
    }
  }
}

module.exports = ToolService
  • 通过邮箱发送验证码
  async sendcode() {
    const { ctx } = this
    const email = ctx.query.email
    const code = Math.random().toString().slice(2, 6)
    console.log('邮箱' + email + '验证码:' + code)
    ctx.session.emailcode = code

    const subject = '开课吧验证码'
    const text = ''
    const html = `<h2>小开社区</h2><a href="https://kaikeba.com"><span>${code}</span></a>`
    const hasSend = await this.service.tools.sendMail(email, subject, text, html)
    if (hasSend) {
      this.message('发送成功')
    } else {
      this.error('发送失败')
    }
  }

# 解析token的中间件

cnpm i egg-jwt --save

也可以自己手动去实现,在egg的app文件夹下,创建一个middleware文件夹,创建jwt.js

  • jwt.verify => 解析jwt所加密的信息
  • 执行中间件返回处理结果
// 解析token的中间件,也可以用egg-jwt,自己封装更适合了解原理
const jwt = require('jsonwebtoken')

module.exports = ({ app }) => {
  return async function verify(ctx, next) {
    if (!ctx.request.header.authorization) {
      ctx.body = {
        code: -666,
        message: '用户没有登录',
      }
      return
    }
    const token = ctx.request.header.authorization.replace('Bearer ', '')
    try {
      const ret = await jwt.verify(token, app.config.jwt.secret)
      ctx.state.email = ret.email
      ctx.state.userid = ret._id
      await next()
    } catch (err) {
      console.log(err)
      if (err.name === 'TokenExpiredError') {
        ctx.body = {
          code: -666,
          message: '登录过期了',
        }
      } else {
        ctx.body = {
          code: -1,
          message: '用户信息出错',
        }
      }

    }
  }
}

  • 在需要加jwt校验的路由中使用
'use strict'
/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app
  const jwt = app.middleware.jwt({ app })
  router.group({ name: 'user', prefix: '/user' }, router => {
    const {
      info, register
    } = controller.user
    router.post('/register', register)
    //中间件使用
    router.get('/info', jwt, info)  
  })
}
最后更新: 2/7/2022, 11:27:05 AM