【Vue + Koa 前后端分离项目实战4】使用开源框架==>快速搭建后台管理系统 -- part4 后端实现【增删改查】功能

如果你和你想做的事情不在同一个频道,你就会浪费许多精力。等你有机会做你想做的事情时,你可能已经没有力气或时间了。

完整章节回顾

【Vue + Koa 前后端分离项目实战3】使用开源框架==>快速搭建后台管理系统 -- part3 权限控制+行为日志_小白Rachel的博客-CSDN博客

【Vue + Koa 前后端分离项目实战2】使用开源框架==>快速搭建后台管理系统 -- part2 后端新增期刊功能实现_小白Rachel的博客-CSDN博客_koa 开源项目

【Vue + Koa 前后端分离项目实战】使用开源框架==>快速搭建后台管理系统 -- part1 项目搭建_小白Rachel的博客-CSDN博客_vue+koa

后端分层架构的模式和框架中的文件说明 :

分层结构 说明 对应文件夹 对应文件
Model层

实体类层 主要用于定义与数据库对象应的属性

models movies.js  music.js  sentence.js
Dao层

持久层 访问数据库,向数据库发送sql语句,完成数据的增删改查任务

dao movies.js  music.js  sentence.js
Service层

业务逻辑层,完成功能的设计

service contents.js
Controller层

控制层,控制请求和响应,负责前后端交互 接口控制具体的业务流程

Validators

api

content.js

content.js

本章节主要完成后端的逻辑实现,并借助接口测试postman工具测试 ,暂无涉及到前端内容。


目录

完整章节回顾

一、基础工作

1.修改配置项文件

2.定义模型层数据

3.测试工具的使用

二、期刊列表查询

1.在dao层定义查询方法

2.在service层合并查询数据

3.在接口层调用接口

4.使用postman接口测试工具测试

三、新增期刊内容

1.添加自定义参数校验器

2.在dao层添加新增方法

3.service层实现新增逻辑

4.接口层调用实现

四、编辑期刊内容

1.添加自定义参数校验器

2.在dao层定义编辑方法

3.在service层中处理数据

4.在接口层编写接口

5.使用postman测试接口

 五、删除期刊内容

1.定义删除校验器

2.在dao层定义删除方法

3.在service层定义删除逻辑

4.在接口层调用删除数据

5.使用测试工具测试


一、基础工作

1.修改配置项文件

// config/secure.js
'use strict';

module.exports = {
  db: {
    database: 'lin-cms',
    host: 'localhost',
    dialect: 'mysql',
    port: 3306,
    username: 'root',
    password: '123456',
    logging: false,
    timezone: '+08:00',
    dialectOptions: {   // 添加配置 修改日期格式
      dateStrings: true,
      typeCast: true
    }
  },
  secret:
    '\x88W\xf09\x91\x07\x98\x89\x87\x96\xa0A\xc68\xf9\xecJJU\x17\xc5V\xbe\x8b\xef\xd7\xd8\xd3\xe6\x95*4'
};

2.定义模型层数据

// models/movie.js
import { Sequelize, Model } from 'sequelize';
import sequelize from '../libs/db';
import { config } from 'lin-mizar';

class Movie extends Model {

}

Movie.init (
  {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    image: {
      type: Sequelize.STRING(64)
    },
    content: {
      type: Sequelize.STRING(300),
      allowNull: true
    },
    pubdate: {
      type: Sequelize.INTEGER,
      allowNull: true
    },
    fav_nums: {
      type: Sequelize.INTEGER,
      defaultValue: 0
    },
    title: {
      type: Sequelize.STRING(50)
    },
    type: {
      type: Sequelize.INTEGER
    },
    status: {
      type: Sequelize.INTEGER
    }
  },
  {
    // 定义表名
    tableName: 'movie',
    // 定义模型名
    modelName: 'movie',
    // 删除
    paranoid: true,
    // 自动写入时间
    timestamps: true,
    // 重命名时间字段
    createdAt: 'created_at',
    updatedAt: 'updated_at',
    deletedAt: 'deleted_at',
    sequelize
  }
)

export { Movie as MovieModel }

 music音乐,包含url

// models/music.js
import { Sequelize, Model } from 'sequelize';
import sequelize from '../libs/db';
import { config } from 'lin-mizar';

class Music extends Model {

}

Music.init (
  {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    image: {
      type: Sequelize.STRING(64)
    },
    content: {
      type: Sequelize.STRING(300),
      allowNull: true
    },
    url: {
      type: Sequelize.STRING(100),
      allowNull: true
    },
    pubdate: {
      type: Sequelize.INTEGER,
      allowNull: true
    },
    fav_nums: {
      type: Sequelize.INTEGER,
      defaultValue: 0
    },
    title: {
      type: Sequelize.STRING(50)
    },
    type: {
      type: Sequelize.INTEGER
    },
    status: {
      type: Sequelize.INTEGER
    }
  },
  {
    // 定义表名
    tableName: 'music',
    // 定义模型名
    modelName: 'music',
    // 删除
    paranoid: true,
    underscored: true,
    // 自动写入时间
    timestamps: true,
    // 重命名时间字段
    createdAt: 'created_at',
    updatedAt: 'updated_at',
    deletedAt: 'deleted_at',
    sequelize
  }
)

export { Music as MusicModel }

 sentence句子

// models/sentence.js
import { Sequelize, Model } from 'sequelize';
import sequelize from '../libs/db';
import { config } from 'lin-mizar';

class Sentence extends Model {

}

Sentence.init (
  {
    id: {
      type: Sequelize.INTEGER,
      primaryKey: true,
      autoIncrement: true
    },
    image: {
      type: Sequelize.STRING(64)
    },
    content: {
      type: Sequelize.STRING(300),
      allowNull: true
    },
    pubdate: {
      type: Sequelize.INTEGER,
      allowNull: true
    },
    fav_nums: {
      type: Sequelize.INTEGER,
      defaultValue: 0
    },
    title: {
      type: Sequelize.STRING(50)
    },
    type: {
      type: Sequelize.INTEGER
    },
    status: {
      type: Sequelize.INTEGER
    }
  },
  {
    // 定义表名
    tableName: 'sentence',
    // 定义模型名
    modelName: 'sentence',
    // 删除
    paranoid: true,
    underscored: true,
    // 自动写入时间
    timestamps: true,
    // 重命名时间字段
    createdAt: 'created_at',
    updatedAt: 'updated_at',
    deletedAt: 'deleted_at',
    sequelize,
  }
)

export { Sentence as SentenceModel }

3.测试工具的使用

 项目添加权限之后,使用postman需要添加权限token

具体方法:运行(npm run serve)前端项目工程(lin-cms-vue),后打开http://localhost:8080/ 登录查看token信息,并复制到postman中

二、期刊列表查询

最终数据包含三张表的数据,即:从三张表中查询返回合并结果数据。

1.在dao层定义查询方法

使用框架已有的findAll()方法,完成dao文件夹下的movie.js  music.js sentence.js文件

// dao/movie.js文件
import { MovieModel } from '../models/movie';
import { NotFound } from 'lin-mizar';

// 在Dao层中调用模型层
class Movie {
    static async getMovieList () {
        return await MovieModel.findAll()
    }
}

export { Movie as MovieDao }
// dao/music.js文件
import { MusicModel } from '../models/music';
import { NotFound } from 'lin-mizar';

// 在Dao层中调用模型层
class Music {
  // 查询音乐列表
  static async getMusicList () {
    return await MusicModel.findAll()
  }
}

export { Music as MusicDao }
// dao/sentence.js文件
import { SentenceModel } from '../models/sentence';
import { NotFound } from 'lin-mizar';

class Sentence {
  static async getSentenceList () {
    return await SentenceModel.findAll()
  }
}

export { Sentence as SentenceDao }

2.在service层合并查询数据

新建getContentList 查询列表数据方法
// service/content.js文件
import { MovieDao } from '../dao/movie';
import { MusicDao } from '../dao/music';
import { SentenceDao } from '../dao/sentence';
import { NotFound } from 'lin-mizar';

class Content {
  static async getContentList (v) {
    const movieList = await MovieDao.getMovieList()  // 电影列表数据
    const musicList = await MusicDao.getMusicList()   // 音乐列表数据
    const sentenceList = await SentenceDao.getSentenceList()   // 句子列表数据
    let res = []  // 整合三个资源的数据
    res.push.apply(res, movieList)
    res.push.apply(res, musicList)
    res.push.apply(res, sentenceList)
    res.sort((a, b) => b.created_at.localeCompare(a.created_at))  // 按照创建时间排序
    return res
  }
}

export { Content as ContentService };

3.在接口层调用接口

// api/v1/content.js
contentApi.get("/", async (ctx) => {
  const contentList = await ContentService.getContentList();
  ctx.json(contentList);
});
module.exports = { contentApi };

4.使用postman接口测试工具测试

http://localhost:5000/v1/content

三、新增期刊内容

第二篇文章已经详细讲过新增期刊的逻辑了,这里简单梳理。

【Vue + Koa 前后端分离项目实战】使用开源框架==>快速搭建后台管理系统 -- part2 后端新增期刊功能实现_小白Rachel的博客-CSDN博客_koa 开源项目

1.添加自定义参数校验器

 需要检验的内容有:所有描述信息不为空、id为数字、url合法、日期格式正确等

class AddContentValidator extends LinValidator {
  constructor () {
    super();
    this.image = [
      new Rule('isNotEmpty', '内容封面不能为空')
    ]
    this.type = [
      new Rule('isNotEmpty', '内容类型不能为空'),
      new Rule('isInt', '内容类型id必须是数字')
    ]
    this.title = [
      new Rule('isNotEmpty', '内容标题不能为空')
    ]
    this.content = [
      new Rule('isNotEmpty', '内容介绍不能为空')
    ]
    this.url = [
      new Rule('isOptional'),
      new Rule('isURL', '内容外链必须是合法url地址')
    ]
    this.pubdate = [
      new Rule('isNotEmpty', '发布日期不能为空'),
      new Rule('isISO8601', '发布日期格式不正确')
    ]
    this.status = [
      new Rule('isNotEmpty', '内容有效状态未指定'),
      new Rule('isInt', '内容有效状态标识不正确')
    ]
  }
}

2.在dao层添加新增方法

// dao/movie.js
static async addMovice (v) {
    return await MovieModel.create(v)
  }
// dao/music.js
static async addMusic (v) {
    return await MusicModel.create(v)
  }
// dao/sentence.js
static async addSentence (v) {
    return await SentenceModel.create(v)
  }

3.service层实现新增逻辑

// service/content.js
static async addContent (v) {
    // 根据不同种类判断数据
    switch (v['type']) {
      case 100:
        // 电影
        delete v['url']
        await MovieDao.addMovice(v);
        break;
      case 200:
        // 音乐
        await MusicDao.addMusic(v);
        break;
      case 300:
        // 句子
        delete v['url']
        await SentenceDao.addSentence(v);
        break;
      default:
        throw new NotFound({ msg: '内容类型不存在' });
    }
  }

4.接口层调用实现

// api/v1/content.js
contentApi.linPost(
  "addContent", // 函数唯一标识
  "/",
  {
    permission: "添加期刊内容", // 权限名称
    module: "内容管理", // 权限所属模块
    mount: true,
  },
  groupRequired, // 权限级别
  logger("{user.username}新增了期刊内容"),
  async (ctx) => {
    // 1.参数校验
    const v = await new AddContentValidator().validate(ctx);
    // 2.执行业务逻辑
    // 3.插入数据库-封装在service层
    await ContentService.addContent(v.get("body"));
    // 4.返回成功
    ctx.success({
      msg: "期刊内容新增成功",
    });
  }
);

四、编辑期刊内容

1.添加自定义参数校验器

编辑时id不能为空,其余属性和【新增编辑器】相同,直接继承复用即可

// validators/content.js
class EditContentValidator extends AddContentValidator {
  // 对于id验证
  constructor () {
    super();
    this.id = [
      new Rule('isNotEmpty', '期刊id不能为空'),
      new Rule('isInt', '期刊id必须是数字且大于0', {min: 1 })
    ]
  }
}
export { AddContentValidator, EditContentValidator}

2.在dao层定义编辑方法

editMovie方法接收两个参数:id params

findBuPK(id)  根据id调用模型的查询方法。返回对象信息

update() 调用模型的更新方法用于更新数据

// dao/movie.js文件
static async editMovie (id, params) {
    const movie = await MovieModel.findByPk(id)  // 根据id查询数据对象
    if (!movie) {
      throw new NotFound()
    }
    return await movie.update({ ...params })  // 修改相应字段数据
}
// dao/music.js文件
static async editMusic (id, params) {
    const music = await MusicModel.findByPk(id)
    if (!music) {
      throw new NotFound()
    }
    return await music.update({ ...params })
}
// dao/sentence.js文件
static async editSentence (id, v) {
    const sentence = await SentenceModel.findByPk(id)
    if (!sentence) {
      throw new NotFound()
    }
    return await sentence.update({ ...v })
}

3.在service层中处理数据

根据数据类型type(100,200,300) 三种类型,使用switch语句,实现三种类型的区分。分别调用dao层的编辑方法。对于电影、句子没有url字段,需要删除。如果类型不对应时抛出异常。

// service/content.js
static async editContent (id, params) {
    switch (params['type']) {
      case 100:
        delete params['url']
        await MovieDao.editMovie(id, params)
        break;
      case 200:
        await MusicDao.editMusic(id, params)
        break;
      case 300:
        delete params['url']
        await SentenceDao.editSentence(id, params)
        break;
      default:
        throw new NotFound({ msg: '内容类型不存在' })
    }
  }

4.在接口层编写接口

使用put方法。上篇文章已讲过【权限问题】和【新增】的接口逻辑详解,不再赘述

// api/v1/content.js
contentApi.linPut(
  "editContent",
  "/:id",
  {
    permission: "编辑期刊内容", // 权限
    module: "内容管理",
    mount: true,
  },
  groupRequired, // 权限级别
  logger("{user.username}编辑了期刊内容"),
  async (ctx) => {
    // 1.参数校验
    const v = await new EditContentValidator().validate(ctx);
    // 2.取值
    const id = v.get("path.id");
    const params = v.get("body");
    // 3.编辑期刊逻辑
    await ContentService.editContent(id, params);
    // 4.返回成功提示
    ctx.success({
      msg: "期刊内容编辑成功",
    });
  }
);

5.使用postman测试接口

修改类型type=100 id=6的数据。

 

 五、删除期刊内容

1.定义删除校验器

需要校验id和type字段,保证能够唯一定义到数据。

class DeleteContentValidator extends LinValidator {
  constructor () {
    super();
    this.id = [
      new Rule('isNotEmpty', '期刊id不能为空'),
      new Rule('isInt', '期刊id必须是数字且大于0', { min: 1 })
    ]
    this.type = [
      new Rule('isNotEmpty', '期刊类型不能为空'),
      new Rule('isInt', '期刊类型必须是数字')
    ]
  }
}

export { AddContentValidator, EditContentValidator, DeleteContentValidator }

2.在dao层定义删除方法

调用模型中的查询方法,按照id查询,并删除对应数据。

// dao/movie.js
static async deleteMovieById (id) {
    return MovieModel.destroy({
      where: { id }
    })
}
// dao/music.js
static async deleteMusicById (id) {
    return MusicModel.destroy({
      where: { id }
    })
  }
// dao/sentence.js
static async deleteSentenceById (id) {
    return SentenceModel.destroy({
      where: { id }
    })
  }

3.在service层定义删除逻辑

使用switch语句,分类删除数据

// service/content.js
static async deleteContent (id, type) {
    switch (type) {
      case 100:
        await MovieDao.deleteMovieById(id)
        break;
      case 200:
        await MusicDao.deleteMusicById(id)
        break;
      case 300:
        await SentenceDao.deleteSentenceById(id)
        break;
      default:
        throw new NotFound({ msg: '内容类型不存在' })
    }
  }

4.在接口层调用删除数据

定义delete方法,传递参数为id

contentApi.linDelete(
  "deleteContent",
  "/:id",
  {
    permission: "删除期刊内容", // 权限
    module: "内容管理",
    mount: true,
  },
  groupRequired, // 权限级别
  logger("{user.username}删除了期刊内容"),
  async (ctx) => {
    const v = await new DeleteContentValidator().validate(ctx);
    const id = v.get("path.id");
    const type = v.get("query.type");
    await ContentService.deleteContent(id, type);
    ctx.success({
      msg: "期刊删除成功",
    });
  }
);

5.使用测试工具测试

http://localhost:5000/v1/content/1?type=100

 至此,期刊模块的增删查改功能全部完成。

猜你喜欢

转载自blog.csdn.net/Sabrina_cc/article/details/126876522