后端服务
选择后端框架( 我选择的是koa2)
创建文件夹
backend/
├── src/
│ ├── app/ # web app本身
│ │ ├── index.js
│ ├── controllers/ # 控制器,处理路由请求
│ │ ├── test.js
│ ├── middleware/ # 中间件
│ │ ├── test.js
│ ├── models/ # 数据模型(Mongoose)
│ │ ├── test.js
│ ├── routes/ # 路由定义
│ │ ├── index.js # 路由
│ │ ├── test.js
│ ├── services/ # 业务逻辑
│ │ ├── test.js
│ ├── utils/ # 工具函数
│ │ ├── db.js # 数据库连接
│ │ ├── globalVariables.js # 导入.env 全局变量
│ └── main.js # 启动服务入口文件
└─——.env # 环境变量
├── .gitignore # git上传配置
└── package.json # 项目依赖
安装
npm install koa //安装koa
npm i nodemon -D //自动重启服务
npm install dotenv //全局变量配置
npm install koa-logger // 监控中间件
npm install koa-route // 路由中间件
npm install koa-parameter // 用来校验请求传送过来的参数是否是自己所需要的
npm install koa-cors //解决跨越
npm install mongoose //连接数据库
配置
在app/index.js
const Koa = require('koa');
const Cors = require("koa-cors");
const parameter = require('koa-parameter')
const router = require('../routes')
const app = new Koa(); // 创建koa实例
app.use(Cors()); // 使用cors中间件 处理跨域
app.use(parameter(app)) // 使用parameter中间件 处理请求参数
app.use(router.routes()).use(router.allowedMethods());// 定义路由中间件
module.exports = app; // 导出app实例
在routes/index
const fs = require('fs')
const Router = require('koa-router')
const router = new Router()
fs.readdirSync(__dirname).forEach(file => {
// console.log(file)
if (file !== 'index.js') {
let r = require('./' + file)
router.use(r.routes())
}
})
module.exports = router //导出
在routes/test.js
const Router = require('koa-router')
const router = new Router({ prefix: "/MyTest" }); // 实例化路由
router.get('/', async (ctx) => { // 定义路由
ctx.body = 'Hello World!';
})
module.exports = router // 导出路由
在.env文件
APP_PORT = 8000 #端口号
#数据库配置
MONGO_URI = mongodb://127.0.0.1:27017/ #数据库地址
MONGO_DB_NAME = xx #数据库名称
MONGO_USERNAME = xx #数据库用户名
MONGO_PASSWORD = xx #数据库密码
# 密码加密配置
JWT_SECRET = xx # 密钥
REFRESH_TOKEN_SECRET = xx # 刷新密钥
JWT_EXPIRES_IN = 7d # token过期时间
REFRESH_TOKEN_EXPIRES_IN = 30d # 刷新token过期时间
在utils/globalVariables
const dotenv = require('dotenv')
dotenv.config()
// console.log(process.env.APP_PORT)
module.exports = process.env
在main.js中导入
const { APP_PORT } = require('./utils/globalVariables') //导入端口号
const app = require('./app') //导入app实例
app.listen(APP_PORT, () => { //监听端口
console.log(`server is running on http://localhost:${APP_PORT}`)
})
简单的服务已经完成,浏览器访问localhost:8000/MyTest/会看到'Hello World!'
业务
- 连接数据库
- 设计数据表模型(models)
- 写路由 (router)
- 写中间件(middleware)和控制器(controller)
- 处理业务逻辑(service)
- 利用模型实现业务
连接数据库
我这里使用的是 mongoose 连接 MongoDB,也可以使用其他的例如 sequelize连接 MySQL,要记得把数据库建好
在utils/db.js文件夹下
const mongoose = require('mongoose');
const url = process.env.MONGO_URI || 'mongodb://localhost:27017/';
const optins = {
dbName: process.env.MONGO_DB_NAME,// 数据库名称
}
// 连接数据库
const connectDB = async () => {
try {
await mongoose.connect(url, optins);
console.log('数据库连接成功');
} catch (err) {
console.error(err.message);
// Exit process with failure
process.exit(1);
}
};
module.exports = connectDB;
main.js
const { APP_PORT } = require('./utils/globalVariables') //导入端口号
const connectDB = require('./utils/db')
const app = require('./app') //导入app实例
//连接数据库
connectDB().then(()=>{
app.listen(APP_PORT, () => { //监听端口
console.log(`server is running on http://localhost:${APP_PORT}`)
})
})
创建数据模型
在 models 文件夹下创建user.js
// models/user.js
const moogoose = require('mongoose');
const UserSchema = new moogoose.Schema({
name: {
type: String,
required: true
},
password: {
type: String,
required: true
},
role: {
type: String,
required: true,
enum: ['admin', 'user', 'guest',],//限定role的值只能是admin、user、guest
default: 'user',
comment: 'admin(管理员), user(用户), guest (访客)'
}
})
编写路由
在routes文件夹下创建user.js
// routes/user.js
const Router = require("koa-router"); //
const router = new Router({ prefix: "/user" });
const {checkEmpty,checkUser,crpytPassword,checkLogin,auth} = require("../middleware/User");
const {register,login,updatePassword,updateToken} = require("../controllers/User");
// 注册
router.post("/register",checkEmpty,checkUser,crpytPassword,register)
// 登录
router.post("/login",checkEmpty,checkLogin,login);
// 修改用户信息
router.put("/updatePassword",auth,checkLogin, updatePassword);
// 刷新token
router.post("/refreshToken",updateToken);
module.exports = router;
编写中间件(middleware)与控制器(controllers)
在middleware和controllers文件夹下创建User.js
// middleware/User.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const { hashPassword, comparePassword } = require('../utils/bcrpyt')
const { userFormateError, userAlreadyExited, userDoesNotExist, invalidPassword,tokenExpiredError } = require('../constant/errorType')
// 验证用户名或密码是否空
const checkEmpty = async (ctx, next) => {
try {
const { username, password } = ctx.request.body
if (!username || !password) {
console.error('用户名或密码为空', ctx.request.body)
ctx.app.emit('error', userFormateError, ctx)
return
}
await next()
} catch (error) {
console.error("验证用户名或密码是否空", error);
}
}
// 验证用户名是否已经存在
const checkUser = async (ctx, next) => {
try {
const { username } = ctx.request.body
const user = await User.findOne({ username })
if (user) {
console.error('用户名已存在', ctx.request.body)
ctx.app.emit('error', userAlreadyExited, ctx)
return
}
await next()
} catch (error) {
console.error("验证用户名是否存在", error);
}
}
// 密码加加密
const crpytPassword = async (ctx, next) => {
const { password } = ctx.request.body
const hash = await hashPassword(password)//加密
ctx.request.body.password = hash
await next()
}
//验证用户是否注册,密码是否正确
const checkLogin = async (ctx, next) => {
try {
const { username, password } = ctx.request.body;
const user = await User.findOne({ username })
if (!user || !(await comparePassword(password, user.password))) {
console.error('验证用户是否注册,密码是否正确', ctx.request.body)
ctx.app.emit('error', !user ? userDoesNotExist : invalidPassword, ctx)
return
}
await next()
} catch (error) {
console.log("验证用户是否注册", error);
}
}
// 验证用户登录是否过期
const auth = async (ctx, next) => {
try {
const token = ctx.headers.authorization?.split(' ')[1];
const user = jwt.verify(token, process.env.JWT_SECRET);
ctx.state.user = user;
await next();
} catch (error) {
console.log("验证用户登录是否过期", error);
ctx.app.emit('error',tokenExpiredError,ctx )
}
}
module.exports = {
checkEmpty,
checkUser,
crpytPassword,
checkLogin,
auth
}
// controllers/User.js
const { userRegisterError, userLoginError, invalidPassword,invalidToken } = require('../constant/errorType')
const { createtUser, getUser,changPassword,changToken } = require('../services/User')
// 注册
const register = async (ctx, next) => {
try {
const { username, password } = ctx.request.body;
const user = await createtUser(username, password);
ctx.body = {
code: 200,
message: '注册成功',
result: user
};
} catch (error) {
console.log("注册", error)
ctx.app.emit('error', userRegisterError, ctx)
}
}
// 登录
const login = async (ctx, next) => {
try {
const { username, password } = ctx.request.body;
const userinfo = await getUser(username, password)
ctx.body = {
code: 200,
message: '登录成功',
result: userinfo
}
} catch (error) {
console.log("登录", error);
ctx.app.emit('error', userLoginError, ctx)
}
}
// 修改密码
const updatePassword = async (ctx, next) => {
try {
const {id, username, newPassword } = ctx.request.body;
await changPassword(id,username,newPassword)
ctx.body = {
code: 200,
message: '修改密码成功',
}
}catch (error) {
console.log("修改密码", error);
ctx.app.emit('error', invalidPassword, ctx)
}
}
//刷新token
const updateToken= async (ctx, next) => {
try {
const { refreshToken } = ctx.request.body;
const token= await changToken(refreshToken)
ctx.body = {
code: 200,
message: '刷新token成功',
token
}
} catch (error) {
console.log("刷新token", error);
ctx.app.emit('error', invalidToken, ctx)
}
}
module.exports = {
register,
login,
updatePassword,
updateToken
}
处理业务逻辑
在services文件夹下创建User.js
//services/User.js
const User = require('../models/user')
const { generateToken, generateRefreshToken,verifyToken } = require('../utils/jwt')
const { hashPassword } = require('../utils/bcrpyt')
// 创建用户
const createtUser = async (username, password) => {
const user = new User({ username, password });
await user.save(); // 保存用户
return user;
}
// 用户登录
const getUser = async (username) => {
const user = await User.findOne({ username }); // 查询用户
const token = generateToken(user) // 生成token
const refreshToken = generateRefreshToken(user); // 生成刷新token
// 将refreshToken存入数据库
user.refreshToken = refreshToken;
await user.save();
const userinfo = {
token,
refreshToken,
user
}
return userinfo
}
//更新密码
const changPassword = async (id, username, newPassword) => {
const password = await hashPassword(newPassword)//加密
await User.findByIdAndUpdate({ _id: id }, { password });
}
// 刷新token
const changToken = async (refreshToken) => {
if (!refreshToken) {
ctx.status = 400;
ctx.body = { error: 'refreshToken不能为空' };
return;
}
const decoded = verifyToken(refreshToken, process.env.REFRESH_TOKEN_SECRET);//验证token
const user = await User.findById(decoded.id);//查询用户
if (!user || user.refreshToken !== refreshToken) {
ctx.status = 403;
ctx.body = { error: '无效或过期的refreshToken' };
return;
}
const newToken = generateToken(user); // 生成新的token
const newRefreshToken = generateRefreshToken(user); // 生成刷新token
user.refreshToken = newRefreshToken;
await user.save(); // 保存用户刷新token
let token = {
token: newToken,
refreshToken: newRefreshToken,
}
return token
}
module.exports = {
createtUser,
getUser,
changPassword,
changToken
}