毕业设计——基于node + koa + mongodb 的博客系统

项目说明:

该项目采用前后端分离结构,权限认证采用 token 来实现

目录结构:

| public
| src
| | config ------------------配置文件
| | controller --------------控制层
| | database ----------------数据库相关
| | middleware --------------中间件
| | serviece ----------------服务层
| | utils -------------------工具类
| | app.js ------------------入口文件
| .babelrc
| .editorconfig
| .eslintrc
| .gitignore
| index.js ------------------启动文件
| package.json

技术栈:

  • Node.js
  • Koa
  • MongoDB

项目启动:

# 安装依赖
yarn install 或者 npm install

# 启动项目
yarn start 或者 npm run start

代码说明:

  • 项目的日志管理使用 log4js 模块,具体配置如下:
// 配置文件
const log4jsConf = {
  appenders: {
    console: {
      type: 'console'
    },
    http: {
      type: 'dateFile',
      filename: path.resolve(__dirname, '../../log/http'),
      pattern: '-yyyy-MM-dd.log',
      alwaysIncludePattern: true
    },
    error: {
      type: 'file',
      filename: path.resolve(__dirname, '../../log/error.log'),
      maxLogSize: 1048576,
      backups: 10
    }
  },
  categories: {
    default: {
      appenders: ['console'],
      level: 'info'
    },
    http: {
      appenders: ['http'],
      level: 'info'
    },
    error: {
      appenders: ['error'],
      level: 'error'
    }
  }
}

// 日志管理
export default app => {
  app.use(async (ctx, next) => {
    const { method, host, url, headers } = ctx
    const httpInfo = {
      method,
      host,
      url,
      referer: headers['referer'],
      userAgent: headers['user-agent']
    }
    httpLogger.info(JSON.stringify(httpInfo))
    await next()
  })
  • 统一错误处理则使用了 Decorator 装饰器来实现,从而减少一定的代码量:
//装饰器配置
export function handleError(target, name, descriptor) {
  const oldValue = descriptor.value

  descriptor.value = async function() {
    try {
      return await oldValue.apply(this, arguments)
    } catch (error) {
      return Result.error(
        ERROR_INFO.DatabaseError.code,
        ERROR_INFO.DatabaseError.msg
      )
    }
  }
}

//使用装饰器
...
@handleError
async publish(headers) {
  const userid = await getUserId(headers)
  const category = await Category.findOne({
    _id: this.categoryid,
    author: userid
  })
  const article = new Article({
    author: userid,
    title: xss(this.title),
    content: xss(this.content),
    category: category._id,
    privated: this.privated
  })

  await article.save()
  return Result.success(null)
}
...

API 接口功能:

  • 用户登录
  • 用户注册
  • 修改资料
  • 上传头像
  • 收藏文章
  • 发布文章
  • 删除文章
  • 更新文章
  • 模糊查询
  • 分页查询
  • 阅读统计
  • 用户评论
  • 文章分类
import fs from 'fs'
import path from 'path'
import jwt from 'jsonwebtoken'
import xss from 'xss'
import Crypto from '../utils/crypto'
import { User, Article } from '../database/model'
import { TOKEN_SECRET, TOKEN_EXPIRESIN, PASSWORD_SALT, DEFAULT_AVATAR } from '../config/application'
import Result, { ERROR_INFO } from '../utils/result'
import { getToken, getUserId } from '../utils/roles'
import { handleError } from '../utils/decorator'

export default class UserService {
  constructor({ username, password, avatar, introduction, collect }) {
    this.username = username
    this.password = password
    this.avatar = avatar
    this.introduction = introduction
    this.collect = collect
  }

  @handleError
  static async getUserInfo(headers) {
    const token = getToken(headers)
    const userData = await User.findOne({ token })

    try {
      jwt.verify(userData.token, TOKEN_SECRET)
      const _userData = await User.findOne({ token }, { password: 0 }).populate({
        path: 'collect',
        populate: { path: 'author', select: 'username avatar' }
      })
      return Result.success(_userData)
    } catch (error) {
      return Result.error(ERROR_INFO.InvalidToken.code, ERROR_INFO.InvalidToken.msg)
    }
  }

  @handleError
  async register() {
    const userData = await User.findOne({ username: this.username })

    if (userData) {
      return Result.error(ERROR_INFO.ExistAccount.code, ERROR_INFO.ExistAccount.msg)
    } else if (this.username.length > 10) {
      return Result.error(ERROR_INFO.BeyondLength.code, ERROR_INFO.BeyondLength.msg)
    } else {
      const token = jwt.sign({ username: this.username }, TOKEN_SECRET, {
        expiresIn: TOKEN_EXPIRESIN
      })
      const crypto = new Crypto(PASSWORD_SALT, this.password)
      const user = new User({
        username: this.username,
        password: crypto.encrypt(),
        token
      })
      await user.save()
      return Result.success(
        await User.findOne({ username: this.username }, { password: 0 }).populate('collect')
      )
    }
  }

  @handleError
  async login() {
    const crypto = new Crypto(PASSWORD_SALT, this.password)
    const userData = await User.findOne(
      { username: this.username, password: crypto.encrypt() },
      { password: 0 }
    )

    if (userData) {
      const token = jwt.sign({ username: this.username }, TOKEN_SECRET, {
        expiresIn: TOKEN_EXPIRESIN
      })
      await User.updateOne({ username: this.username }, { token })
      return Result.success(
        await User.findOne({ username: this.username }, { password: 0 }).populate('collect')
      )
    } else {
      return Result.error(ERROR_INFO.BadAccount.code, ERROR_INFO.BadAccount.msg)
    }
  }

  @handleError
  async updateIntroduction(headers) {
    const token = getToken(headers)
    await User.updateOne({ token }, { introduction: xss(this.introduction) })
    const userData = await User.findOne({ token }, { introduction: 1 })
    return Result.success(userData)
  }

  @handleError
  async uploadAvatar(headers) {
    const avatarType = ['image/jpeg', 'image/png']
    if (!avatarType.includes(this.avatar.type)) {
      return Result.error(ERROR_INFO.InvalidFile.code, ERROR_INFO.InvalidFile.msg)
    }

    // 删除之前的头像
    const token = await getToken(headers)
    const userData = await User.findOne({ token })
    const username = userData.username
    const oldUploadPath = path.resolve(__dirname, `../../public${userData.avatar}`)
    userData.avatar === `/upload/${DEFAULT_AVATAR}` ? null : fs.unlinkSync(oldUploadPath)

    // 添加时间戳,以禁止缓存
    const avatarName = username + Date.now().toString() + path.extname(this.avatar.name)
    const uploadPath = path.resolve(__dirname, `../../public/upload/${avatarName}`)
    const reader = fs.createReadStream(this.avatar.path)
    const writer = fs.createWriteStream(uploadPath)
    const avatar = `/upload/${avatarName}`

    reader.pipe(writer)
    await User.updateOne({ username }, { avatar })
    const _userData = await User.findOne({ username }, { avatar: 1 })
    return Result.success(_userData)
  }

  @handleError
  static async handleCollect(articleid, cancelCollect, headers) {
    const userId = await getUserId(headers)
    const _article = await Article.findOne({ _id: articleid, privated: false })
    const article = _article ? _article : await Article.findOne({ author: userId, _id: articleid })

    if (cancelCollect) {
      await User.update({ _id: userId }, { $pull: { collect: article._id } })
    } else {
      await User.update({ _id: userId }, { $addToSet: { collect: article._id } })
    }

    return Result.success(null)
  }
}

  • 8
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

毕业小助手

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值