注册接口
创建数据库
-- user表
CREATE TABLE IF NOT EXISTS `user` (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(30) UNIQUE NOT NULL,
password VARCHAR(50) NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
);
router路由
const KoaRouter = require('@koa/router')
const userController = require('../controller/user.controller')
const { verifyUser, handlePassword } = require('../middleware/user.middleware')
// 1.定义路由对象
const userRouter = new KoaRouter({ prefix: "/users" })
// 2.具体路由规则
// 2.1用户注册
// verifyUser 验证注册合法性中间件
// handlePassword 密码加密中间件
// userController.create 插入数据库
userRouter.post('/register', verifyUser, handlePassword, userController.create)
module.exports = userRouter
middleware中间件
一般为公用的中间件函数
const userService = require("../service/user.service")
const { NAME_OR_PWD_IS_REQUIRED, NAME_IS_ALREADY_EXISTS } = require("../config/error")
const md5Encryption = require("../utils/md5-encryption")
// 验证用户注册中间件
const verifyUser = async (ctx, next) => {
const { name, password } = ctx.request.body
if (!name || !password) {
return ctx.app.emit('error', NAME_OR_PWD_IS_REQUIRED, ctx)
}
const users = await userService.findUserByName(name)
if (users.length > 0) {
return ctx.app.emit('error', NAME_IS_ALREADY_EXISTS, ctx)
}
await next()
}
// 加密存储中间件
const handlePassword = async (ctx, next) => {
const { password } = ctx.request.body
ctx.request.body.password = md5Encryption(password)
await next()
}
module.exports = {
verifyUser,
handlePassword
}
controller控制器
一般为具体路由对应的中间件函数
调用service定义的数据库方法
const userService = require("../service/user.service");
class UserController {
async create(ctx, next) {
const user = ctx.request.body
// 将请求参数传递给数据库操作中间件
let res = await userService.create(user)
ctx.body = {
msg: '创建用户成功',
data: res
}
}
}
module.exports = new UserController()
service数据库操作
数据库驱动mysql2操作数据库
const connection = require('../app/database')
class UserService {
// 插入数据到数据库
async create(user) {
// 1.获取请求参数
const { name, password } = user
// 2.定义预处理语句
const statement = 'INSERT INTO `user` ( `name`, `password`) VALUES (?, ?);'
// 3.执行sql语句
let [res, fields] = await connection.execute(statement, [name, password])
return res
}
// 查找数据
async findUserByName(name) {
const statement = 'SELECT* FROM `user` WHERE `name` = ?;'
let [res, fields] = await connection.execute(statement, [name])
return res
}
}
module.exports = new UserService()
错误统一处理
1.监听error事件
handle-error.js
const app = require("../app");
const { NAME_OR_PWD_IS_REQUIRED, NAME_IS_ALREADY_EXISTS } = require("../config/error");
app.on('error', (errType, ctx) => {
let code = 0
let msg = '未知错误'
switch (errType) {
case NAME_OR_PWD_IS_REQUIRED:
code = -1001
msg = '用户名或密码不能为空'
break;
case NAME_IS_ALREADY_EXISTS:
code = -1002
msg = '用户已经注册'
default:
break;
}
ctx.body = {
code,
msg
}
})
2.调用监听
main.js
// require导入时,能执行该文件
require('./utils/handle-error')
3.抛出错误事件
// 验证用户注册中间件
const verifyUser = async (ctx, next) => {
const { name, password } = ctx.request.body
if (!name || !password) {
return ctx.app.emit('error', NAME_OR_PWD_IS_REQUIRED, ctx)
}
const users = await userService.findUserByName(name)
if (users.length > 0) {
return ctx.app.emit('error', NAME_IS_ALREADY_EXISTS, ctx)
}
await next()
}
登录接口
登录接口的实现同注册接口
登录有使用到token令牌的颁发与验证
会话控制知识点
token令牌的颁发
const jwt = require('jsonwebtoken')
const { PRIVATE_KEY } = require('../config/serect')
class LoginController {
async sign(ctx, next) {
const { id, name } = ctx.user
// 使用私钥PRIVATE_KEY颁发token令牌
const payload = { id,name}
const token = jwt.sign(payload, PRIVATE_KEY, {
expiresIn: 60,
algorithm: 'RS256'
})
ctx.body = {
code:200,
data:{ id,name,token }
}
}
}
module.exports = new LoginController()
token令牌的验证
验证token令牌中间件
const verifyAuth = async (ctx, next) => {
// 1.获取token
const authorization = ctx.header.authorization
const token = authorization.replace('Bearer ', '')
// 2.使用公钥验证token
try {
const res = jwt.verify(token, PUBLIC_KEY, {
algorithms: ['RS256']
})
await next()
} catch (error) {
ctx.app.emit('error', UNAUTHORIZED, ctx)
}
}
自动注册所有路由
const fs = require('fs')
function registerAllRouters(app) {
// 1.读取路由文件所在文件夹
const files = fs.readdirSync(__dirname)
// 2.读取路由文件
for (const file of files) {
if(!file.endsWith('router.js')) continue
// 导入具体路由对象
const router = require(`./${file}`)
// 注册路由
app.use(router.routes())
app.use(router.allowedMethods())
}
}
module.exports = registerAllRouters
const Koa = require('koa')
const registerAllRouters = require('../router')
const app = new Koa()
// 注册所有路由中间件
// 传入app
registerAllRouters(app)
module.exports = app
用户动态接口
创建数据库
-- moment(动态)表
CREATE TABLE IF NOT EXISTS `moment` (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(1000) NOT NULL,
user_id INT NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES `user`(id)
);
user_id表示发表动态的用户,故user_id来自用户表,使用外键约束。
发表动态
获取请求参数
获取动态列表
1.定义接口
2.中间件函数
3.操作数据库
删除、修改
流程同上述接口的编写,删除需添加一个操作资源的权限验证。
1.添加验证权限的中间件
2.编写中间件
注:接口的params参数需严格同一命名规范 [tableName]Id
3.验证权限的实现
评论接口(一对多)
创建数据库
-- comment评论表
-- 一对多:一条动态有多个评论
CREATE TABLE IF NOT EXISTS `comment` (
-- 该条评论的id
id INT PRIMARY KEY AUTO_INCREMENT,
-- 该条评论的内容
content VARCHAR(1000) NOT NULL,
-- 被评论动态的id
moment_id INT NOT NULL,
-- 发表这条评论的用户id
user_id INT NOT NULL,
-- 回复某条评论的评论id
comment_id INT NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY(moment_id) REFERENCES moment(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(user_id) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(comment_id) REFERENCES `comment`(id) ON DELETE CASCADE ON UPDATE CASCADE
);
给动态列表添加评论数字段
SELECT
m.id id, m.content content,m.createTime createTime,m.updateTime updateTime,
JSON_OBJECT('id',u.id,'name',u.`name`) `user`,
( SELECT COUNT(*) FROM `comment` WHERE `comment`.moment_id = m.id ) commentCounts
FROM `moment` m
LEFT JOIN `user` u ON m.user_id = u.id
LIMIT 10 OFFSET 0
给动态详情添加评论列表字段
SELECT
m.id id, m.content content,m.createTime createTime,m.updateTime updateTime,
JSON_OBJECT('id',u.id,'name',u.name) user,
(
JSON_ARRAYAGG(JSON_OBJECT(
'id', c.id, 'content', c.content, 'commentId', c.comment_id,
'user', JSON_OBJECT('id', cu.id, 'name', cu.name)
))
) comments
FROM moment m
LEFT JOIN user u ON m.user_id = u.id
LEFT JOIN `comment` c ON c.moment_id = m.id
LEFT JOIN `user` cu ON cu.id = c.user_id
WHERE m.id = 1
GROUP BY m.id
标签接口(多对多)
创建数据库
一个标签对应多个动态,一个动态对应多个标签
CREATE TABLE IF NOT EXISTS `lable` (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10) NOT NULL UNIQUE,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP
);
-- moment(动态)表
CREATE TABLE IF NOT EXISTS `moment` (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(1000) NOT NULL,
user_id INT NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY(user_id) REFERENCES `user`(id)
);
-- moment和lable的关系表
CREATE TABLE IF NOT EXISTS `moment_lable` (
moment_id INT NOT NULL,
lable_id INT NOT NULL,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY(moment_id,lable_id),
FOREIGN KEY(moment_id) REFERENCES moment(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY(lable_id) REFERENCES lable(id) ON DELETE CASCADE ON UPDATE CASCADE
);
给动态添加标签
1.定义接口
verifyAuth 验证登录
verifyPermission 验证有操作资源的权限
verifyLableExists 将已存在和新创建的lable合并,添加到lable数据库中
addLables 将moment_id与lable_id添加到关系表中
2.中间件
3.控制器
4数据库操作
给查询动态添加标签数字段
SELECT
m.id id, m.content content,m.createTime createTime,m.updateTime updateTime,
JSON_OBJECT('id',u.id,'name',u.name) user,
( SELECT COUNT(*) FROM comment WHERE comment.moment_id = m.id ) commentCounts,
-- 添加的字段lableCounts
( SELECT COUNT(*) FROM moment_lable ml WHERE ml.moment_id = m.id ) lableCounts
FROM moment m
LEFT JOIN user u ON m.user_id = u.id
LIMIT ? OFFSET ?
给查询动态详情添加标签列表字段
错误做法:返回的lables数据个数是正确的2倍
正确做法:子查询
-- 原先的sql查询
SELECT
m.id id, m.content content,m.createTime createTime,m.updateTime updateTime,
JSON_OBJECT('id',u.id,'name',u.name) user,
(
JSON_ARRAYAGG(JSON_OBJECT(
'id', c.id, 'content', c.content, 'commentId', c.comment_id,
'user', JSON_OBJECT('id', cu.id, 'name', cu.name)
))
) comments
FROM moment m
LEFT JOIN user u ON m.user_id = u.id
LEFT JOIN `comment` c ON c.moment_id = m.id
LEFT JOIN `user` cu ON cu.id = c.user_id
WHERE m.id = 1
GROUP BY m.id
SELECT
m.id id, m.content content,m.createTime createTime,m.updateTime updateTime,
JSON_OBJECT('id',u.id,'name',u.name) user,
(
SELECT
JSON_ARRAYAGG(JSON_OBJECT(
'id', c.id, 'content', c.content, 'commentId', c.comment_id,
'user', JSON_OBJECT('id', cu.id, 'name', cu.name)
))
FROM `comment` c
LEFT JOIN `user` cu ON cu.id = c.user_id
WHERE c.moment_id = m.id
) comments,
(
JSON_ARRAYAGG(JSON_OBJECT(
'id', l.id, 'name', l.name
))
) lables
FROM moment m
LEFT JOIN user u ON m.user_id = u.id
LEFT JOIN moment_lable ml ON ml.moment_id = m.id
LEFT JOIN lable l ON l.id = ml.lable_id
WHERE m.id = 1
GROUP BY m.id
单文件上传接口(头像上传接口)
创建数据库
-- avatar 头像表
CREATE TABLE IF NOT EXISTS `avatar` (
id INT PRIMARY KEY AUTO_INCREMENT,
filename VARCHAR(255) NOT NULL UNIQUE,
mimetype VARCHAR(255),
size INT,
user_id INT,
createTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updateTime TIMESTAMP DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE
);
相对路径问题
获取头像并显示接口
1.通过该路径访问userId用户的头像
2.从数据库中读取文件信息,到存储磁盘中找到文件并显示
浏览器访问http://localhost:8000/users/avatar/9 可显示图片
存储路径AVATAR_UPLOAD_PATH保存为常量,便于修改
3.查询数据库具体操作
给user表添加头像字段
其他查询中有用户信息时,可以携带上用户头像信息