2.首页、分类、课程接口
章节接口
js
复制代码
// 新建router/chapters.js
// 完成后app中引入
const express = require("express")
const router = express.Router()
const { Course, Category, Chapter, User } = require("../models")
const { success, failure } = require("../utils/responses")
// 章节的内容
// 章节详情接口
// 注意:章节属于一个课程,课程属于一个用户,章节和用户没有直接关系,∴在include中嵌套include,先通过章节查课程,再通过课程查用户
router.get("/:id", async (req, res) => {
try {
const { id } = req.params
const condition = {
attributes: {
exclude: ["CourseId"],
},
include: [
{
model: Course,
as: "course",
attributes: {
exclude: ["id", "name"],
},
include: [
{
model: User,
as: "user",
attributes: {
exclude: ["id", "username", "nickname", "avatar", "company"],
},
},
],
},
],
}
const chapter = await Chapter.findByPk(id, condition)
if (!chapter) {
throw new Error("chapter not found,章节未找到")
}
// 同属一个课程的所有章节
const chapters = await Chapter.findAll({
attributes: {
exclude: ["CourseId", "content"],
},
where: {
courseId: chapter.courseId,
},
order: [
["rank", "DESC"],
["id", "DESC"],
],
})
success(
res,
{
chapter,
chapters,
},
"查询章节成功"
)
} catch (error) {
failure(res, error)
}
})
module.exports = router
文章列表接口
js
复制代码
// 新建router/articles.js
// 完成后app中引入
const express = require("express")
const router = express.Router()
const { Article } = require("../models")
const { success, failure } = require("../utils/responses")
// #region 查询文章列表的接口
router.get("/", async (req, res) => {
try {
// 分页
const query = req.query
const currentPage = Math.abs(query.currentPage) || 1
const pageSize = Math.abs(query.pageSize) || 10
const offset = (currentPage - 1) * pageSize
const condition = {
attributes: {
exclude: ["content"],
},
order: [["id", "DESC"]],
limit: pageSize,
offset,
}
const { count, rows } = await Article.findAndCountAll(condition)
success(
res,
{
articles: rows,
pagination: {
total: count,
currentPage,
pageSize,
},
},
"查询文章列表成功"
)
} catch (error) {
failure(res, error)
}
})
// #region 文章详情接口
router.get("/:id", async (req, res) => {
try {
const id = req.params.id
const article = await Article.findByPk(id)
if (!article) {
return failure(res, "文章不存在")
}
success(res, { article }, "查询文章详情成功")
} catch (error) {
failure(res, error)
}
})
module.exports = router
系统设置接口
js
复制代码
// 新建router/settings.js
// 完成后app中引入
const express = require("express")
const router = express.Router()
const { Setting } = require("../models")
const { success, failure } = require("../utils/responses")
// #region 设置接口
router.get("/", async (req, res) => {
try {
const setting = await Setting.findOne()
if (!setting) {
return failure(res, "设置不存在")
}
success(res, { setting }, "查询设置成功")
} catch (error) {
failure(res, error)
}
})
module.exports = router
搜索接口
js
复制代码
// 新建router/serach.js
// 完成后app中引入
const express = require("express")
const router = express.Router()
const { Course } = require("../models")
const { success, failure } = require("../utils/responses")
const { Op } = require("sequelize")
// #region 搜索接口
router.get("/", async (req, res) => {
try {
const query = req.query
const currentPage = Math.abs(query.page) || 1
const pageSize = Math.abs(query.pageSize) || 10
const offset = (currentPage - 1) * pageSize
const keyword = query.keyword || ""
const condition = {
attributes: {
exclude: ["categoryId", "UserId", "content"],
},
order: [["id", "DESC"]],
limit: pageSize,
offset,
}
if (query.name) {
condition.where = {
name: {
[Op.like]: `%${query.name}%`,
// 模糊查询
},
}
}
const { count, rows } = await Course.findAndCountAll(condition)
success(
res,
{
courses: rows,
pagination: {
total: count,
currentPage,
pageSize,
},
},
"搜索课程成功。"
)
} catch (error) {
failure(res, error)
}
})
module.exports = router
app.js中引入
js
复制代码
const chaptersRouter = require("./routes/chapters") // 章节路由
const articlesRouter = require("./routes/articles") // 文章路由
const settingsRouter = require("./routes/settings") // 系统设置路由
const searchRouter = require("./routes/search") // 搜索路由
app.use("/chapters", chaptersRouter) // 章节路由
app.use("/articles", articlesRouter) // 文章路由
app.use("/settings", settingsRouter) // 系统设置路由
app.use("/search", searchRouter) // 搜索路由
3.用户注册、登录、认证接口
用户注册,隐藏密码
js
复制代码
// 新建一个routes/auth.js文件
const express = require("express")
const router = express.Router()
const { User } = require("../models")
const { success, failure } = require("../utils/responses")
const { BadRequestError, UnauthorizedError, NotFoundError } = require("../utils/error")
const bcrypt = require("bcryptjs")
const jwt = require("jsonwebtoken")
const { Op } = require("sequelize")
/**
* 用户注册
*/
// 注1:
// sex:2为未选择
// role:0为普通用户
// 注2:
// 注册成功后,密码也返回了,要把密码字段删除掉
router.post("/sign_up", async (req, res) => {
try {
const { username, password, nickname, email } = req.body
const body = { username, password, nickname, email, sex: 2, role: 0 }
const user = await User.create(body)
// 删除密码
delete user.dataValues.password
success(res, { user }, "注册成功")
} catch (error) {
failure(res, error)
}
})
module.exports = router
用户登录
js
复制代码
// route/auth.js中
/**
* 用户登录
*/
// 注1:bcrypt验证密码
// 注2:生成token
// 注3:apifox-后置操作添加userToken
// 注4:认证中间件
router.post("/sign_in", async (req, res) => {
try {
const { login, password } = req.body
if (!login) {
throw new BadRequestError("邮箱或用户名不能为空")
}
if (!password) {
throw new BadRequestError("密码不能为空")
}
const condition = {
where: {
[Op.or]: [{ username: login }, { email: login }],
},
}
const user = await User.findOne(condition)
if (!user) {
throw new NotFoundError("用户不存在")
}
// 验证密码
const isPasswordValid = await bcrypt.compare(password, user.password)
if (!isPasswordValid) {
throw new UnauthorizedError("密码错误")
}
// 生成token
const token = jwt.sign({ userId: user.id }, process.env.SECRET, {
expiresIn: "30d",
})
success(res, { token }, "登录成功")
} catch (error) {
failure(res, error)
}
})
用户认证中间件
js
复制代码
// 复制粘贴middlewares/admin-auth.js,改为user-auth.js
const jwt = require("jsonwebtoken")
const { User } = require("../models")
const { UnauthorizedError } = require("../utils/error")
const { success, failure } = require("../utils/responses")
module.exports = async (req, res, next) => {
try {
// 判断 Token 是否存在
const { token } = req.headers
if (!token) {
throw new UnauthorizedError("当前接口需要认证才能访问。")
}
// 验证 token 是否正确
const decoded = jwt.verify(token, process.env.SECRET)
const { userId } = decoded
// 如果通过验证,将 user 对象挂载到 req 上,方便后续中间件或路由使用
req.userId = userId
// 一定要加 next(),才能继续进入到后续中间件或路由
next()
} catch (error) {
failure(res, error)
}
}
4.用户相关的接口
用户首页,使用中间件
js
复制代码
// 修改自带的route/users.js:
const express = require("express")
const router = express.Router()
const { User } = require("../models")
const { success, failure } = require("../utils/responses")
const { NotFoundError, BadRequestError } = require("../utils/error")
const bcrypt = require("bcryptjs")
// #region 用户首页,当前用户的信息
router.get("/me", async (req, res) => {
try {
const user = await getUser(req)
success(res, { user }, "用户信息查询成功!")
} catch (error) {
failure(res, error)
}
})
/**
* 公共方法:查询当前用户
*/
// 注:返回值中要不显示密码
async function getUser(req, showPassword = false) {
const id = req.userId
let condition = {}
if (!showPassword) {
condition = {
attributes: {
exclude: ["password"], // 不显示密码
},
}
}
const user = await User.findByPk(id, condition)
if (!user) {
throw new NotFoundError("用户不存在")
}
return user
}
module.exports = router
js
复制代码
// app.js中引入路由和中间件
// 前台-中间件
const userAuth = require("./middlewares/user-auth")
const usersRouter = require("./routes/users") // 用户路由
app.use("/users", userAuth, usersRouter) // 用户路由
更新用户信息:性别\公司\昵称等
js
复制代码
// users.js
/**
* 用户的信息修改
*/
router.put("/info", async (req, res) => {
try {
const { nickname, sex, company, introduce, avatar } = req.body
const body = {
nickname,
sex,
company,
introduce,
avatar,
}
const user = await getUser(req)
await user.update(body)
success(res, { user }, "更新用户成功!")
} catch (error) {
failure(res, error)
}
})
更新账户信息:邮箱\用户名\邮箱
js
复制代码
// route/users.js
/**
* 更新账户信息
* 修改密码等
*/
router.put("/account", async (req, res) => {
try {
const { email, username, password, currentPassword, passwordConfirmation } = req.body
const body = {
email,
username,
password,
currentPassword,
passwordConfirmation,
}
if (!currentPassword) {
throw new BadRequestError("请输入当前密码")
}
if (password !== passwordConfirmation) {
throw new BadRequestError("两次输入密码不一致")
}
const user = await getUser(req, true) // 这里需要拿到密码作比对
const isPasswordValid = bcrypt.compareSync(currentPassword, user.password)
if (!isPasswordValid) {
throw new BadRequestError("当前密码输入错误")
}
await user.update(body)
delete user.dataValues.password
success(res, { user }, "更新账户成功!")
} catch (error) {
failure(res, error)
}
})
5.点赞
点赞/取消点赞
js
复制代码
// 新建route/likes.js
const express = require("express")
const router = express.Router()
const { User, Course, Like } = require("../models")
const { success, failure } = require("../utils/responses")
const { NotFoundError, BadRequestError } = require("../utils/error")
const bcrypt = require("bcryptjs")
const { or } = require("sequelize")
/**
* 点赞
*/
// 注1:
// increment/decrement:自增/自减操作
router.post("/", async (req, res) => {
try {
// req.userId是当前对应的点赞的用户
const userId = req.userId
const { courseId } = req.body
const course = await Course.findByPk(courseId)
if (!course) {
throw new NotFoundError("课程不存在")
}
const like = await Like.findOne({
where: { userId, courseId },
})
// 没有点赞就新增,并且课程的likesCount + 1
if (!like) {
await Like.create({
userId,
courseId,
})
await course.increment("likesCount")
success(res, {}, "点赞成功")
} else {
// 有点赞就删除,并且课程的likesCount - 1
await like.destroy()
await course.decrement("likesCount")
success(res, {}, "取消点赞成功")
}
} catch (error) {
failure(res, error)
}
})
module.exports = router
查询当前用户点赞的课程
js
复制代码
// course模型中:
class Course extends Model {
static associate(models) {
// ...
models.Course.belongsToMany(models.User, { through: models.Like, foreignKey: "courseId", as: "likeUsers" }) // 多对多关联,through指通过Likes中间表来实现关联
}
}
// user模型中:
class User extends Model {
static associate(models) {
// ...
models.User.belongsToMany(models.Course, { through: models.Like, foreignKey: "userId", as: "likeCourses" })
}
}
// route/likes.js
/**
* 查询当前用户点赞的课程
*
* 多对多关系
* 每个课程有多个点赞
* 每个用户可以点赞多个课程
*
* 新东西:getLikeCourses获取likeCourses数据
*
* 新东西:countLikeCourses获取likeCourses的总和
*
* 查询到的结果分页处理
*
* 新东西:去除掉中间表Like数据joinTableAttributes: [],
*/
router.get("/", async (req, res) => {
try {
const query = req.query
const currentPage = query.currentPage || 1
const pageSize = query.pageSize || 10
const offset = (currentPage - 1) * pageSize
const user = await User.findByPk(req.userId)
// 新东西:getLikeCourses获取likeCourses数据
const courses = await user.getLikeCourses({
joinTableAttributes: [], // 去除掉中间表数据
attributes: { exclude: ["CategoryId", "userId", "content"] },
offset,
limit: pageSize,
order: [["id", "DESC"]],
})
// 新东西:countLikeCourses获取likeCourses的总和
const count = await user.countLikeCourses()
success(res, { courses, pagination: { currentPage, pageSize, total: count } }, "查询成功")
} catch (error) {
failure(res, error)
}
})
get的妙用
js
复制代码
// 回到章节详情接口chapters.js
const express = require("express")
const router = express.Router()
const { Course, Category, Chapter, User } = require("../models")
const { success, failure } = require("../utils/responses")
// 章节的内容
// 章节详情接口
// 注意:章节属于一个课程,课程属于一个用户,章节和用户没有直接关系,∴在include中嵌套include,先通过章节查课程,再通过课程查用户
router.get("/:id", async (req, res) => {
try {
const { id } = req.params
// const condition = {
// attributes: { exclude: ["CourseId"] },
// include: [
// {
// model: Course,
// as: "course",
// attributes: {
// exclude: ["id", "name"],
// },
// include: [
// {
// model: User,
// as: "user",
// attributes: {
// exclude: ["id", "username", "nickname", "avatar", "company"],
// },
// },
// ],
// },
// ],
// }
// const chapter = await Chapter.findByPk(id, condition)
/**
* 写法二:get的妙用
* 1. 可以通过getCource获取课程
* 2. 可以通过getUser获取用户
* 这样写也避免了condition写法的嵌套
*/
const chapter = await Chapter.findByPk(id, {
attributes: { exclude: ["CourseId"] },
})
if (!chapter) {
throw new Error("chapter not found,章节未找到")
}
// 查询章节关联的课程
const course = await Chapter.getCource({
attributes: {
exclude: ["id", "name", "userId"],
},
})
// 查询章节相关的用户
const user = await Course.getUser({
attributes: {
exclude: ["id", "username", "nickname", "avatar", "company"],
},
})
// 同属一个课程的所有章节
const chapters = await Chapter.findAll({
attributes: {
exclude: ["CourseId", "content"],
},
where: {
courseId: chapter.courseId,
},
order: [
["rank", "DESC"],
["id", "DESC"],
],
})
success(
res,
{
chapter,
chapters,
course,
user,
},
"查询章节成功"
)
} catch (error) {
failure(res, error)
}
})
module.exports = router