koa+mongo+mongoose實戰仿知乎

一、個人信息增改查

1、編輯個人資料,隱藏一些信息,在獲取特定用戶資料時顯示信息

在models/user.js中寫userSchema:

const mongoose = require('mongoose');
const { Schema, model} = mongoose;

const userSchema = new Schema({
  __v:{type:Number, select:false},
  name: { type: String, required: true},
  password: {type: String, required: true,select: false},
  avatar_url: {type: String},
  gender: {type: String, enum: ['male','female'], default: 'male', required: true}, //enum是可枚舉
  headline: {type: String},
  locations:{type: [{type:String}],select: false},
  business: {type: String,select: false},
  employments:{type: [{ //工作
    company: {type: String},
    job:{type: String}
  }],select: false},
  educations:{
    type: [{
      school:{type: String},
      major: {type: String}, //專業
      diploma: {type: Number,enum:[1,2,3,4,5]}, //學歷
      entrance_year: {type: Number}, //入學年份
      graduation_year: {type: Number}
    }],select: false
  }
});

module.exports = model('ZhihuUser', userSchema);

在routes/user.js中:

const Router = require('koa-router');
const userRouter = new Router({prefix: '/user'});
const {find, findById, create, update} = require('../controllers/user');

userRouter.get('/',find);
userRouter.get('/:id',findById);
userRouter.post('/', create);
userRouter.post('/update/:id',update); //編輯個人資料

module.exports = userRouter;

在controllers/user.js中:

const User = require('../model/user');

class UserCtl {
  async find(ctx) {
    ctx.body = await User.find();
  }
  // 獲取特定用戶資料時顯示信息
  async findById(ctx) {
    // 獲取請求參數
    const {fields} = ctx.query; //返回字符串:locations;educations
    const selectFields = fields.split(';').filter(f=>f).map(f=> ' +' + f).join(''); //轉化爲: +location+educations
    const user = await User.findById(ctx.params.id).select(selectFields);
    if(!user) {
      ctx.throw(404, '用戶不存在');
    }
    ctx.body = user;
  }
  async create(ctx) {
    ctx.verifyParams({
      name: {type: 'string',require: true},
      password: {type: 'string',require: true}
    })
    const {name} = ctx.request.body;
    const userName = await User.findOne({name});
    if(userName) {
      ctx.throw(409, '用戶名已存在');
    }
    const user = await new User(ctx.request.body).save();
    ctx.body = user;
  }

  // 編輯個人資料
  async update(ctx) {
    console.log(ctx);
    ctx.verifyParams({
      name: {type: 'string',require: true},
      password: {type: 'string',require: false},
      avatar_url: {type: 'string',require: false},
      gender: {type: 'string',require: false},
      headline: {type: 'string',require: false},
      locations: {type:'array',itemType:'string',require:false},
      business: {type: 'string',require:false},
      employments: {type: 'array',itemType:'object', require:false},
      eduactions: {type: 'array',itemType:'object', require: false}
    })
    const user = await User.findByIdAndUpdate(ctx.params.id, ctx.request.body);
    console.log(user);
    if(!user) {
      ctx.throw(404,'用戶不存在');
    }
    ctx.body = user;
  }
}

module.exports = new UserCtl();

二、用戶關注用戶

1.model/user.js:

const mongoose = require('mongoose');
const { Schema, model} = mongoose;

const userSchema = new Schema({
  __v:{type:Number, select:false},
  name: { type: String, required: true},
  password: {type: String, required: true }, //,select: false
  avatar_url: {type: String},
  gender: {type: String, enum: ['male','female'], default: 'male', required: true}, //enum是可枚舉
  headline: {type: String},
  locations:{type: [{type:String}],select: false},
  business: {type: String,select: false},
  employments:{type: [{ //工作
    company: {type: String},
    job:{type: String}
  }],select: false},
  educations:{
    type: [{
      school:{type: String},
      major: {type: String}, //專業
      diploma: {type: Number,enum:[1,2,3,4,5]}, //學歷
      entrance_year: {type: Number}, //入學年份
      graduation_year: {type: Number}
    }],select: false
  },
  // 獲取用戶關注人列表
  following: {
    type: [{type: Schema.Types.ObjectId,ref: 'ZhihuUser'}], // 存儲用戶id,與ZhihuUser集合關聯
    select: false
  }
});

module.exports = model('ZhihuUser', userSchema);

 2.router/user.js

const Router = require('koa-router');
const userRouter = new Router({prefix: '/user'});

const jwt = require('koa-jwt');
const {secret} = require('../config.js');
const auth = jwt({secret: secret});

const {login, find, findById, create, update, listFollowing, checkOwner, following} = require('../controllers/user');

userRouter.post('/login',login);
userRouter.get('/',find);
userRouter.get('/:id',findById);
userRouter.post('/', create); //創建用戶
userRouter.post('/update/:id',auth, checkOwner, update); //更新用戶信息
userRouter.get('/:id/following',auth, checkOwner, listFollowing); //獲取用戶關注人列表
userRouter.put('/following/:id',auth,following); //關注操作
userRouter.delete('/unfollowing/:id',auth,unfollowing); //取消關注操作

module.exports = userRouter;

3.controllers/user.js:

const User = require('../model/user');
const jsonwebtoken = require('jsonwebtoken');
const {secret} = require('../config');

class UserCtl {
  // 登陸返回token
  async login(ctx) {
    ctx.verifyParams({
      name: {type: 'string', required: true},
      password: {type: 'string', required: true}
    })
    const user = await User.findOne(ctx.request.body);
    if(!user) {
      ctx.throw(401, '用戶名或密碼不正確');
    }
    const {_id, name} = user;
    ctx.body = _id
    const token = jsonwebtoken.sign({_id,name}, secret, {expiresIn: '10d'});
    // const token = jsonwebtoken.sign({_id,name}, secret, {expiresIn: '1d'})
    ctx.body = {token};
  }
  async checkOwner(ctx, next) {
    ctx.body = ctx.state.user;
    if(ctx.params.id != ctx.state.user._id) {
      ctx.throw(403, '沒有權限')
    }
    await next();
  }
  // 獲取用戶列表 (有分頁功能)
  async find(ctx) {
    const {per_page = 10} = ctx.query;
    const page = Math.max(ctx.query.page*1,1)-1;
    const perPage = Math.max(per_page*1,1);
    ctx.body = await User.find().limit(perPage).skip(perPage*page);
  }
  async findById(ctx) {
    // 獲取請求參數
    const {fields} = ctx.query; //返回字符串:locations;educations
    const selectFields = fields.split(';').filter(f=>f).map(f=> ' +' + f).join('');//返回字符串:+locations+educations
    const user = await User.findById(ctx.params.id).select(selectFields);
    if(!user) {
      ctx.throw(404, '用戶不存在');
    }
    ctx.body = user;
  }
  async create(ctx) {
    ctx.verifyParams({
      name: {type: 'string',required: true},
      password: {type: 'string',required: true}
    })
    const {name} = ctx.request.body;
    const userName = await User.findOne({name});
    if(userName) {
      ctx.throw(409, '用戶名已存在');
    }
    const user = await new User(ctx.request.body).save();
    ctx.body = user;
  }
  async update(ctx) {
    console.log(ctx);
    ctx.verifyParams({
      name: {type: 'string',required: true},
      password: {type: 'string',required: false},
      avatar_url: {type: 'string',required: false},
      gender: {type: 'string',required: false},
      headline: {type: 'string',required: false},
      locations: {type:'array',itemType:'string',required:false},
      business: {type: 'string',required:false},
      employments: {type: 'array',itemType:'object', required:false},
      eduactions: {type: 'array',itemType:'object', required: false}
    })
    const user = await User.findByIdAndUpdate(ctx.params.id, ctx.request.body);
    console.log(user);
    if(!user) {
      ctx.throw(404,'用戶不存在');
    }
    ctx.body = user;
  }

  // 獲取用戶關注人列表
  async listFollowing(ctx) {
    const user = await User.findById(ctx.params.id).select('+following').populate('following'); // select()默認只顯示id,加populate()顯示其他信息
    if(!user) {ctx.throw(404, '用戶不存在')};
    ctx.body = user.following;
  }

  // 關注操作
  async following(ctx) {
    const me = await User.findById(ctx.state.user._id).select('+following');
    if(!me.following.map(id=>id.toString()).includes(ctx.params.id)) {
      me.following.push(ctx.params.id);
      me.save();
    }
    ctx.status = 204;
  }

  // 取消關注
  async unfollowing(ctx) {
    const me= await User.findById(ctx.state.user._id).select('+following');
    const index = me.following.map(id=>id.toString()).indexof(ctx.params.id);
    if(index > -1) {
      me.following.split(index,1);
      me.save();
    }
  }
}

module.exports = new UserCtl();

4.登錄測試:

 5.在tests中設置全局變量token:

6.在獲取用戶關注人列表時設置請求頭的Authorization爲全局變量token

三、話題增改查

 1.創建/model/topic.js:

const mongoose = require('mongoose');
const { Schema, model } = require('mongoose');

const TopicSchema = new Schema({
  __v: {type: Number, select: false},
  name: {type: String,required: true},
  avatar_url: {type: String},
  introduction: {type: String, select: false}
})

module.exports = model('zhihuTopic',TopicSchema);

2.創建/router/topic.js:

const koa= require('koa');
const Router = require('koa-router');

const topicRouter = new Router({prefix: '/topic'});
const {find, create, update} = require('../controllers/topic.js');

topicRouter.get('/',find); //獲取話題
topicRouter.post('/create', create);
topicRouter.post('/update/:id',update);

module.exports = topicRouter;

3.創建/controller/topic.js

const Topic = require('../model/topic');
class TopicCtl {
  // 查詢話題(有分頁功能,模糊搜索)
  // /topic?page=1&per_page=10&q=模糊搜索字符
  async find(ctx) {
    const {per_page = 10} = ctx.query;
    const page = Math.max(ctx.query.page*1,1)-1;
    console.log(Math.max(ctx.query.page*1,1));
    const perPage = Math.max(per_page*1,1);
    const topic = await Topic.find({name:new RegExp(ctx.query.q)}).limit(perPage).skip(page*perPage);
    ctx.body = topic;
  }
  
  // 創建話題
  async create(ctx) {
    ctx.verifyParams({
      name: {type: 'string',required: true},
      avatar_url: {type: 'string',required:false},
      introduction: {type: 'string',required:false}
    })
    const topic = await new Topic(ctx.request.body).save();
    ctx.body = topic;
  }

  // 更新話題
  async update(ctx) {
    ctx.verifyParams({
      name:{type: 'string',required:false},
      avatar_url: {type: 'string', required: false},
      introduction:{type:'string', required: false}
    })
    const topic = await Topic.findByIdAndUpdate(ctx.params.id, ctx.request.body);
    ctx.body = topic;
  }
}

module.exports = new TopicCtl();

四、將用戶的信息連接話題(用戶的城市、工作、學校、專業等都可以是話題)

做法:用戶的schema參數連接話題的集合id

將model/user.js修改(locations,business,educations,employments):

const mongoose = require('mongoose');
const { Schema, model} = mongoose;

const userSchema = new Schema({
  __v:{type:Number, select:false},
  name: { type: String, required: true},
  password: {type: String, required: true }, //,select: false
  avatar_url: {type: String},
  gender: {type: String, enum: ['male','female'], default: 'male', required: true}, //enum是可枚舉
  headline: {type: String},
  locations:{type: [{type:Schema.Types.ObjectId, ref:'zhihuTopic'}],select: false},
  business: {type: Schema.Types.ObjectId, ref:'zhihuTopic',select: false},
  employments:{type: [{ //工作
    company: {type: Schema.Types.ObjectId, ref:'zhihuTopic'},
    job:{type: Schema.Types.ObjectId, ref:'zhihuTopic'}
  }],select: false},
  educations:{
    type: [{
      school:{type: Schema.Types.ObjectId, ref:'zhihuTopic'},
      major: {type: Schema.Types.ObjectId, ref:'zhihuTopic'}, //專業
      diploma: {type: Number,enum:[1,2,3,4,5]}, //學歷
      entrance_year: {type: Number}, //入學年份
      graduation_year: {type: Number}
    }],select: false
  },
  // 獲取用戶關注人列表
  following: {
    type: [{type: Schema.Types.ObjectId,ref: 'ZhihuUser'}], // 存儲用戶id,與ZhihuUser集合關聯
    select: false
  }
});

module.exports = model('ZhihuUser', userSchema);

修改controllers/user.js(findById):

async findById(ctx) {
    // 獲取請求參數
    const {fields} = ctx.query; //返回字符串:locations;educations
    const selectFields = fields.split(';').filter(f=>f).map(f=> ' +' + f).join('');//返回字符串:+locations+educations
    const populateStr = fields.split(';').filter(f=>f).map(f=> {
      console.log(f)
      if(f === 'employments') {
        return 'employments.company employments.job'
      }
      if(f === 'educations') {
        return 'educations.school educations.major'
      }
      return f;
    }).join(' ');
    const user = await User.findById(ctx.params.id).select(selectFields).populate(populateStr); //select()是顯示隱藏的參數,populate()是顯示連接其他集合的數據
    if(!user) {
      ctx.throw(404, '用戶不存在');
    }
    ctx.body = user;
  }

五、用戶關注取關話題

1.在model/user.js中增加followingTopic字段:

const mongoose = require('mongoose');
const { Schema, model} = mongoose;

const Topic = require('./topic');
const userSchema = new Schema({
  __v:{type:Number, select:false},
  name: { type: String, required: true},
  password: {type: String, required: true }, //,select: false
  avatar_url: {type: String},
  gender: {type: String, enum: ['male','female'], default: 'male', required: true}, //enum是可枚舉
  headline: {type: String},
  locations:{type: [{type:Schema.Types.ObjectId, ref:Topic}],select: false},
  business: {type: Schema.Types.ObjectId, ref:Topic,select: false},
  employments:{type: [{ //工作
    company: {type: Schema.Types.ObjectId, ref:Topic},
    job:{type: Schema.Types.ObjectId, ref:Topic}
  }],select: false},
  educations:{
    type: [{
      school:{type: Schema.Types.ObjectId, ref:Topic},
      major: {type: Schema.Types.ObjectId, ref:'Topic'}, //專業
      diploma: {type: Number,enum:[1,2,3,4,5]}, //學歷
      entrance_year: {type: Number}, //入學年份
      graduation_year: {type: Number}
    }],select: false
  },
  // 獲取用戶關注人列表
  following: {
    type: [{type: Schema.Types.ObjectId,ref: 'ZhihuUser'}], // 存儲用戶id,與ZhihuUser集合關聯
    select: false
  },
  followingTopic:{
    type: [{type:Schema.Types.ObjectId, ref: Topic}], // 存儲話題id,與話題model關聯
    select: false
  }
});

module.exports = model('ZhihuUser', userSchema);

 2.router/user.js中增加  獲取用戶關注的話題、關注話題、取消關注話題:

const Router = require('koa-router');
const userRouter = new Router({prefix: '/user'});

const jwt = require('koa-jwt');
const {secret} = require('../config.js');
const auth = jwt({secret: secret});

const {login, find, findById, create, update, listFollowing, checkOwner, following, unfollowing, listFollowingTopic, followingTopic, unfollowingTopic, listQuestions} = require('../controllers/user');
const { checkTopicExist } = require('../controllers/topic');

userRouter.post('/login',login);
userRouter.get('/',find);
userRouter.get('/:id',findById);
userRouter.post('/', create); //創建用戶
userRouter.post('/update/:id',auth, checkOwner, update); //更新用戶信息
userRouter.get('/:id/following',auth, checkOwner, listFollowing); //獲取用戶關注人列表
userRouter.put('/following/:id',auth,following); //關注操作
userRouter.delete('/unfollowing/:id',auth,unfollowing); //取消關注操作
userRouter.get('/:id/listFollowingTopic',auth,listFollowingTopic); //獲取用戶關注的話題
userRouter.put('/followingTopic/:id',auth, checkTopicExist, followingTopic); //關注話題
userRouter.delete('/unfollowingTopic/:id',auth, checkTopicExist, unfollowingTopic); //取消關注話題

module.exports = userRouter;

3.在controllers/user.js中:

const User = require('../model/user');
const Questions = require('../model/questions');

const jsonwebtoken = require('jsonwebtoken');
const {secret} = require('../config');

class UserCtl {

  。。。。。。

  // 獲取用戶關注的話題
  async listFollowingTopic(ctx) {
    const user = await User.findById(ctx.params.id).select('+followingTopic').populate('followingTopic');
    console.log(user);
    if(!user) {
      ctx.throw(404, '用戶不存在');
    }
    ctx.body = user.followingTopic;
  }

  // 關注話題
  async followingTopic(ctx) {
    const user = await User.findById(ctx.state.user._id).select('+followingTopic');
    console.log(user);
    if(!user.followingTopic.includes(ctx.params.id)) {
      user.followingTopic.push(ctx.params.id);
      user.save();
    }
    // ctx.status = 204;
    ctx.body = user;
  }

  // 取消關注話題
  async unfollowingTopic(ctx) {
    const user = await User.findById(ctx.state.user._id).select('+followingTopic');
    console.log(user.followingTopic);
    // const index = me.following.map(id=>id.toString()).indexof(ctx.params.id);

    const index =  user.followingTopic.map(id=>id.toString()).indexOf(ctx.params.id);
    console.log(index);
    if(index > -1) {
      user.followingTopic.splice(index,1);
      user.save();
    }
  }
}

module.exports = new UserCtl();

 

六、問題的增刪改查

1.創建model/questions.js:

const mongoose = require('mongoose');
const {Schema, model} = mongoose;

const User = require('./user.js');

const questionSchema = new Schema({
  title: {type: String,required: true},
  description:{type:String,required: false},
  questioner: {type: Schema.Types.ObjectId, ref:User, required:true}
});

module.exports = model('ZhihuQuestions', questionSchema);

 2.創建routers/questions.js:

const Router = require('koa-router');
const questionsRouter = new Router({prefix: '/questions'});
const {find,listQuestions, create, update, delete:del, checkQuestionExist, checkQuestioner }= require('../controllers/questions');

const jwt = require('koa-jwt');
const {secret} = require('../config.js');
const auth = jwt({secret: secret});

questionsRouter.get('/', find);
questionsRouter.get('/:id/listQuestions');
questionsRouter.post('/create',auth, create);
questionsRouter.put('/update/:id',auth,checkQuestionExist, checkQuestioner, update);
questionsRouter.delete('/delete/:id',auth,checkQuestionExist, checkQuestioner, del);

module.exports = questionsRouter;

3.創建controllers/questions.js:

const Questions = require('../model/questions');

class QuestionCtl {
  // 檢測創建問題的用戶是否是當前用戶,判定是否有修改刪除問題的權限
  async checkQuestioner(ctx, next) {
    const {question} = ctx.state;
    if(question.questioner!=ctx.state.user._id) {
      ctx.throw(403,'沒有權限');
    }
    await next();
  }
  // 獲取問題列表(模糊搜索)
  async find(ctx) {
    // const questions = await Questions.find();
    const page = Math.max(ctx.query.page, 1) - 1;
    const {per_page = 10} = ctx.query;
    const perPage = Math.max(per_page, 1);
    const q = new RegExp(ctx.query.q)
    const questions = await Questions.find({$or:[{title:q},{description:q}]}).limit(perPage).skip(page*perPage);
    ctx.body = questions;
  }
  // 查詢問題是否存在
  async checkQuestionExist(ctx, next) {
    const question = await Questions.findById(ctx.params.id).select('questioner');
    ctx.state.question = question;
    if(!question) {
      ctx.throw(404,'該問題不存在');
    }
    await next();
  }

  // 創建問題
  async create(ctx) {
    ctx.verifyParams({
      title: {type: 'string', required:true},
      description:{type: 'string', required:false}
    })
    const question = await new Questions({...ctx.request.body,questioner: ctx.state.user._id}).save();
    ctx.body = question;
  }

  async update(ctx) {
    ctx.verifyParams({
      title: {type: 'string', required:true},
      description:{type: 'string', required:false}
    })
    await ctx.state.question.update(ctx.request.body);
    ctx.body = ctx.state.question;
  }

  async delete(ctx) {
    const question = await Questions.findByIdAndRemove(ctx.params.id);
    ctx.status = 204;
  }
}

module.exports = new QuestionCtl();

 

發佈了66 篇原創文章 · 獲贊 8 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章