上文定义好了所有的表结构,本篇开始写接口逻辑啦~
用户模块
用户模块只设置了一个登录接口,未注册的直接注册然后登录,已注册的直接登录。
登录接口需要校验用户填写的手机号和密码,本文采用第三方库 joi 进行接口校验,先进行安装:
npm install joi
新建文件 /app/validators/validator.js
:
css
const Joi = require('joi');
const registerSchema = Joi.object({
telephone: Joi.string()
.required()
.pattern(new RegExp("^1[3456789][0-9]{9}$"))
.messages({
"string.base": "手机号码必须是字符串",
"string.empty": "手机号码不能为空",
"string.pattern.base": "手机号码格式不正确",
}),
password: Joi.string()
.required()
.pattern(new RegExp("^[a-zA-Z0-9]{3,30}$"))
.messages({
"string.base": "密码必须是字符串",
"string.empty": "密码不能为空",
"string.pattern.base": "密码格式不正确",
}),
})
module.exports = {
registerSchema
}
校验通过之后需要判断用户是否已经注册过,如果注册过的话直接生成 token
返回即可,没有注册过的需要先进行注册,然后再生成 token
返回。
在 user
模型中新增方法,判断用户是否已经注册,已经注册过的需要判断密码是否输入正确,最终返回用户相关信息。
修改文件 /app/models/user.js
:
scala
class User extends Model {
static async verifyTelephone(params){
// 手机号是唯一的,所以根据手机号来查询用户信息是否存在
const user = await User.findOne({
where: {
telephone: params.telephone
}
})
// 用户信息存在,则说明该用户已经注册
if(user){
const correct = bcryct.compareSync(params.password, user.password)
if(!correct){
throw new AuthFailed("密码不正确")
}
}
return user
}
}
修改文件 /app/api/v1/user.js
:
csharp
router.post("/login", async (ctx, next) => {
const params = ctx.request.body;
// 校验接口参数
await registerSchema.validateAsync(params);
let user;
// 验证用户是否已经注册
user = await User.verifyTelephone(params)
// 没有注册过的话先注册创建用户
if(!user){
user = await User.create({
telephone: params.telephone,
password: params.password,
});
}
// 根据用户id生成token
const token = generateToken(user.id)
// 返回token
ctx.body = {
token
}
});
生成 token
采用的是第三方库 jsonwebtoken。先来配置下生成 token
的密钥和过期时间:
修改配置文件 /config/config.js
,增加如下配置:
perl
security: {
secretKey: "aeq12@#$89.&", // 这个key要比较复杂,丢失的话jwt令牌就可能会被破解
expiresIn: 60 * 60 * 24 * 30, // 令牌过期时间
},
然后新建文件 /core/util.js
:
ini
const jwt = require("jsonwebtoken");
const { security } = require("../config/config");
// 使用用户id生成token
const generateToken = function (uid) {
const secretKey = security.secretKey;
const expiresIn = security.expiresIn;
const token = jwt.sign({ uid }, secretKey, { expiresIn });
return token;
};
module.exports = {
generateToken
}
用户登录接口已完成,可以用 Postman
来测试我们的接口:
然后打开 phpMyAdmin
,可以看到 user
表中新增了一条用户数据:
书籍模块
书籍模块设置了获取书籍列表的接口。
修改文件 /app/api/v1/book.js
:
ini
const Router = require("koa-router");
const { Book } = require("../../models/book");
const router = new Router({
prefix: "/v1/book",
});
router.get("/list", async (ctx, next) => {
const books = await Book.getAll();
ctx.body = {
books,
};
});
module.exports = router;
书籍列表接口已完成,可以使用 phpMyAdmin
在 book
表中导入一些数据或者手动创建几条,然后使用 Postman
测试接口 http://localhost:3000/v1/book/list
。
评论模块
部分接口需要登录才能访问,所以接口需要对 token
进行校验,校验它是否合法,是否有效。新增一个中间件用来校验 token
,校验其是否合法采用的是第三方库 basic-auth。
先安装依赖:
npm install basic-auth
新建文件 /middlewares/auth.js
:
ini
const basicAuth = require("basic-auth");
const jwt = require("jsonwebtoken");
const { Forbbiden } = require("../core/http-exception");
const { security } = require("../config/config");
class Auth {
get m() {
return async (ctx, next) => {
const userToken = basicAuth(ctx.req);
let errMsg = "token不合法";
if (!userToken || !userToken.name) {
throw new Forbaiden(errMsg);
}
try {
// 如果token是有效的jwt.verify函数会返回JWT中的数据,如果无效或者过期,该函数会抛出一个错误。
var decode = jwt.verify(userToken.name, security.secretKey);
} catch (error) {
if (error.name === "TokenExpiredError") {
errMsg = "token已过期";
}
throw Forbbiden(errMsg);
}
ctx.auth = {
uid: decode.uid,
};
await next();
};
}
}
修改文件 /core/http-exception.js
,新增一个 Forbbiden
类并导出:
scala
class Forbbiden extends HttpException {
constructor(msg, errorCode) {
super();
this.code = 403;
this.msg = msg || "禁止访问";
this.errorCode = errorCode || 10006;
}
}
module.exports = {
// ... 其它的
Forbbiden
}
评论模块设置了获取评论列表,新增评论的接口。
获取评论列表接口,修改文件 /app/api/v1/comment.js
,新增路由如下:
csharp
router.get("/:id/list", new Auth().m, async (ctx, next) => {
const { id } = ctx.params; // 获取路由里的id参数
// 根据书籍id获取评论
const commentList = await Comment.getBookCommentList(id);
ctx.body = {
commentList,
};
});
获取评论列表需要先校验权限,所以加了 auth
中间件,用户信息验证通过之后,才会执行下一个路由处理中间件函数。
修改文件 /app/models/comment.js
,新增 getBookCommentList
方法:
csharp
static async getBookCommentList(id) {
const commentList = await Comment.findAll({
order: [["created_at", "DESC"]], // 按照创建时间降序返回
where: {
bookId: id, // 查找该id的所有评论
},
});
return commentList;
}
评论列表接口已完成,可以使用 phpMyAdmin
在 comment
表中导入一些数据或者手动创建几条,注意 userId
和 bookId
需要从 user
表和 book
表中拿取, 然后使用 Postman
测试接口 http://localhost:3000/v1/comment/2/list
,注意需要配置好 Authorization
。
接下来我们来实现新增评论接口,该接口需要先校验用户输入信息,校验通过之后才能新增,先来实现校验函数,修改文件 /app/validators/validator.js
,增加如下:
css
const addCommentSchema = Joi.object({
bookId: Joi.number().required().messages({
"number.base": "书籍id必须是数字",
"string.empty": "书籍id不能为空",
}),
content: Joi.string().required().min(3).max(30).messages({
"string.base": "评论内容必须是字符串",
"string.empty": "评论内容不能为空",
"string.min": "最少3个字符",
"string.max": "最多30个字符",
}),
});
修改文件 /app/api/v1/comment.js
,新增路由如下:
csharp
router.post("/add", new Auth().m, async (ctx, next) => {
// 获取body中传递的参数
const params = ctx.request.body;
// 先校验参数
await addCommentSchema.validateAsync(params);
await Comment.addComment({
...params,
userId: ctx.auth.uid, // 校验token时我们有将用户id存在ctx.auth.uid中
});
success();
});
先来新增公共成功返回方法 success
,新增文件 /app/lib/helper.js
:
javascript
const { Success } = require("../../core/http-exception");
function success(msg, errorCode) {
throw new Success(msg, errorCode);
}
module.exports = {
success,
};
修改文件/app/models/comment.js
,新增方法 addComment
:
csharp
static async addComment(params) {
// 先校验传入的书籍id是否存在
const book = await Book.findOne({
where: {
id: params.bookId,
},
});
if (book) {
// 存在则新增评论
return await Comment.create({
...params,
});
} else {
// 不存在抛出错误提示
throw new NotFound();
}
}
修改文件 /core/http-exception.js
,新增 Success
和 NotFound
类:
scala
class Success extends HttpException {
constructor(msg, errorCode) {
super();
this.code = 201;
this.msg = msg || "ok";
this.errorCode = errorCode || 0;
}
}
class NotFound extends HttpException {
constructor(msg, errorCode) {
super();
this.code = 404;
this.msg = msg || "资源未找到";
this.errorCode = errorCode || 10000;
}
}
module.exports = {
// ....
Success,
NotFound
};
新增评论已完成,可自行进行测试~
收藏模块
收藏模块设置了获取用户收藏列表,新增收藏、取消收藏的接口。
收藏列表接口,修改文件 /app/api/v1/favor.js
:
csharp
static async getMyFavorList(id) {
const FavorList = await Favor.findAll({
order: [["created_at", "DESC"]],
where: {
userId: id,
},
});
return FavorList;
}
收藏接口,需要先进行校验传参是否正确,所以我们先来新增校验函数,修改文件 /app/validators/validator.js
,增加如下:
css
const favorSchema = Joi.object({
bookId: Joi.number().required().messages({
"number.base": "书籍id必须是数字",
"number.empty": "书籍id不能为空",
}),
likeFlag: Joi.string().required().valid("0", "1").messages({
"string.valid": "likeFlag传参错误",
}),
});
然后新增路由,修改文件 /app/api/v1/favor.js
:
csharp
router.post("/like", new Auth().m, async (ctx, next) => {
const params = ctx.request.body;
await favorSchema.validateAsync(params);
await Favor.handleLikeOrDisLike({
...params,
userId: ctx.auth.uid,
});
success();
});
修改文件 /app/models/favor.js
,新增方法 handleLikeOrDisLike
,我们需要先判断是否能找到该用户对这本书籍的收藏信息,如果有收藏信息的话,并且数据库存储的 like_flag
字段和我们传递的 likeFlag
字段都为"0"的话,需要提示用户还没有点赞,都为"1"的话需要提示用户已经点赞过。如果没有收藏信息,但是用户传递的 likeFlag
字段为"0"的话需要提示用户还没有点赞过所以不需要取消点赞。新增和修改 favor
表信息的同时还需要通过修改对应书籍的收藏数量。
csharp
static async handleLikeOrDisLike(params) {
const { bookId, userId, likeFlag } = params;
// 查找favor中是否有相应信息
const favor = await Favor.findOne({
where: {
bookId: bookId,
userId: userId,
},
});
// 有查询到信息并且传递的likeFlag字段和数据库中的相等,则需要提示报错
if (
favor &&
favor.getDataValue("likeFlag") === likeFlag
) {
if (likeFlag === "0") {
throw new DislikeError();
} else {
throw new LikeError();
}
}
// 没有查询到信息,则不能进行取消收藏操作
if(!favor && likeFlag === "0"){
throw new DislikeError();
}
// 使用事务功能,若其中的所有promise链都执行成功的话,则自动提交,若有一个执行失败,则自动回滚
return sequelize.transaction(async (t) => {
if (!favor) {
// 没有该用户该书籍的收藏信息,则新增
await Favor.create({
bookId,
userId,
likeFlag,
});
}else{
// 有该用户该书籍的收藏信息,则更新likeFlag字段
favor.update({
likeFlag
})
}
// 获取相应书籍的信息
const book = await Book.getBookDetail(bookId);
if (likeFlag) {
// 如果是收藏,则相应书籍的收藏数量加一
await book.increment("favorNums", { by: 1, transaction: t });
} else {
// 如果是取消收藏,则相应书籍的收藏数量减一
await book.decrement("favorNums", { by: 1, transaction: t });
}
});
}
收藏接口已完成,可自行测试~
到此所有的接口都已经实现啦,还木有在实际项目中实战过,有错误的话欢迎大家指出,一起学习交流,天天向上~~