npm init -y
生成package.json文件:
- 用于记录项目的依赖
git init
生成.git隐藏文件夹,git的本地仓库
npm install koa
koa版本号: v2.13.4
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000,()=>{
console.log('服务器已经启动在: http://localhost:3000')
});
npm install nodemon -D
编写package.json
"scripts": {
"dev":"nodemon ./src/main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
启动服务:
npm run dev
npm install dotenv
新建文件config.default.js
const dotenv = require('dotenv')
dotenv.config()
console.log(process.env.APP_PORT)
module.exports = process.env
.env文件
APP_PORT=8000
通过node运行命令
node src/config/config.default.js
控制台输出: 8000
const Koa = require('koa');
const app = new Koa();
const {APP_PORT} = require('./config/config.default')
app.use(async ctx => {
ctx.body = 'Hello World2';
});
app.listen(APP_PORT,()=>{
console.log(`服务器已经启动在: http://localhost:${APP_PORT}`)
});
路由:根据不同的url,调用对应的处理函数
npm install koa-router
步骤:
-
导入包
-
实例化对象
-
编写路由
-
注册中间件
创建src/router目录,新建user.route.js
const Router = require('koa-router')
const router = new Router({prefix:'/users'})
router.get("/",(ctx,next) =>{
ctx.body = "hello users"
})
module.exports = router
改写main.js
const Koa = require('koa');
const app = new Koa();
const userRouter = require('./router/user.route')
const {APP_PORT} = require('./config/config.default')
app.use(userRouter.routes());
app.listen(APP_PORT,()=>{
console.log(`服务器已经启动在: http://localhost:${APP_PORT}`)
});
创建目录src/app/index.js
const Koa = require('koa');
const app = new Koa();
const userRouter = require('../router/user.route')
app.use(userRouter.routes());
module.exports = app
改写main.js
const {APP_PORT} = require('./config/config.default')
const app = require('../src/app/index')
app.listen(APP_PORT,()=>{
console.log(`服务器已经启动在: http://localhost:${APP_PORT}`)
});
class userController {
async register(ctx, next){
ctx.body = '用户注册成功'
}
async login(ctx, next){
ctx.body = '用户登录成功'
}
}
module.exports = new userController
const Router = require('koa-router')
const router = new Router({prefix:'/users'})
const {register,login} = require('../controller/user.controller')
// 注册接口
router.post("/register",register)
// 登录接口
router.post("/login",login)
module.exports = router
npm i koa-body
// 官方用法截取:
const Koa = require('koa');
const koaBody = require('koa-body');
const app = new Koa();
app.use(koaBody());
app.use(ctx => {
ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`;
});
app.listen(3000);
class userController {
async register(ctx, next){
// 获取请求参数
console.log(ctx.request.body)
// 操作数据库----抽离service层
// 返回对应的结果
ctx.body = ctx.request.body
}
async login(ctx, next){
ctx.body = '用户登录成功'
}
}
module.exports = new userController
新建user.service.js
class userService {
async createUser(userName,password){
// to do:写入数据库
return '写入数据库成功'
}
}
module.exports = new userService()
const {createUser} = require("../service/user.service")
class userController {
async register(ctx, next){
// 获取请求参数
console.log(ctx.request.body)
const {userName,password} = ctx.request.body
// 操作数据库
const res = await createUser(userName,password)
// 返回对应的结果
ctx.body = res
}
async login(ctx, next){
ctx.body = '用户登录成功'
}
}
module.exports = new userController
Sequelize 是一个基于 promise 的 Node.js ORM, 目前支持 Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能。
Sequelize 简介 | Sequelize 中文文档 | Sequelize 中文网
对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。
-
数据表映射(对应)一个类
-
数据表中的数据行(记录)对应一个对象
-
数据表字段对应对象的属性
-
数据表的操作对应对象的方法
# 使用 npm
npm i sequelize # 这将安装最新版本的 Sequelize
# 使用 yarn
yarn add sequelize
你还必须手动为所选数据库安装驱动程序:
# 使用 npm
npm i pg pg-hstore # PostgreSQL
npm i mysql2 # MySQL
npm i mariadb # MariaDB
npm i sqlite3 # SQLite
npm i tedious # Microsoft SQL Server
npm i ibm_db # DB2
# 使用 yarn
yarn add pg pg-hstore # PostgreSQL
yarn add mysql2 # MySQL
yarn add mariadb # MariaDB
yarn add sqlite3 # SQLite
yarn add tedious # Microsoft SQL Server
yarn add ibm_db # DB2
const { Sequelize } = require('sequelize');
// 方法 1: 传递一个连接 URI
const sequelize = new Sequelize('sqlite::memory:') // Sqlite 示例
const sequelize = new Sequelize('postgres://user:pass@example.com:5432/dbname') // Postgres 示例
// 方法 2: 分别传递参数 (sqlite)
const sequelize = new Sequelize({
dialect: 'sqlite',
storage: 'path/to/database.sqlite'
});
// 方法 3: 分别传递参数 (其它数据库)
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
});
const { Sequelize } = require('sequelize');
const {MYSQL_DB,MYSQL_USER,MYSQL_PASSWORD,MYSQL_HOST,MYSQL_PORT} = require('../config/config.default')
// 方法 3: 分别传递参数 (其它数据库)
const sequelize = new Sequelize(MYSQL_DB, MYSQL_USER, MYSQL_PASSWORD, {
host: MYSQL_HOST,
port:MYSQL_PORT,
dialect: 'mysql',/* 选择 'mysql' | 'mariadb' | 'postgres' | 'mssql' 其一 */
timeZone:'+8:00'
});
sequelize.authenticate()
.then(()=>{
console.log("连接数据库成功")
})
.catch(err =>{
console.log("连接数据库失败",err)
})
module.exports = sequelize
APP_PORT=8000
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=admin123
MYSQL_DB=husi
const { DataTypes } = require('sequelize');
const sequelize = require("../db/seq");
const User = sequelize.define('husi_user', {
// 在这里定义模型属性
// id会被sequelize自动创建、管理
userName: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
comment:'用户名 唯一'
},
password: {
type: DataTypes.CHAR(64),
allowNull: false,
comment:'密码'
},
isAdmin: {
type: DataTypes.BOOLEAN,
allowNull: false,
comment:'是否是管理员 0 不是管理员(默认) 1 是管理员',
defaultValue: 0
},
}, {
// 这是其他模型参数
});
// `sequelize.define` 会返回模型
console.log(User === sequelize.models.User); // true
// User.sync({ force: true });
// console.log("用户模型表刚刚(重新)创建!");
module.exports = User
const {createUser} = require("../service/user.service")
class userController {
async register(ctx, next){
// 获取请求参数
console.log(ctx.request.body)
const {userName,password} = ctx.request.body
// 操作数据库
const res = await createUser(userName,password)
// 返回对应的结果
ctx.body = {
code: 0,
message: "用户注册成功",
result:{
id: res.id,
userName: res.userName
}
}
}
async login(ctx, next){
ctx.body = '用户登录成功'
}
}
module.exports = new userController
const User = require('../model/user.model');
class userService {
async createUser(userName, password) {
// to do:写入数据库
// 创建一个新用户
const res = await User.create({
userName: userName,
password: password,
});
console.log(res);
return res.dataValues;
}
}
module.exports = new userService();
app下新建errHandler.js
module.exports = (err, ctx) => {
let status = 500;
switch (err.code) {
case '10001':
status = 400;
case '10002':
status = 409;
case '10003':
status = 500;
break
default:
status = 500
}
ctx.status = status
ctx.body = err;
};
src下新建constant文件夹、用户存储错误类型信息
module.exports = {
userFormateError:{
code:'10001',
message:'用户名或者密码为空',
result:''
},
hasUserNameExistError:{
code:'10002',
message:'用户名已经存在',
result:''
},
userRegisterError:{
code:'10003',
message:'用户注册错误',
result:''
}
}
app/index.js统一处理错误
const Koa = require('koa');
const app = new Koa();
const koaBody = require('koa-body');
app.use(koaBody());// 需要在最上层
const userRouter = require('../router/user.route')
app.use(userRouter.routes());
// 处理错误的函数
const errHandler = require('./errHandler')
// 统一处理错误
app.on('error',errHandler)
module.exports = app
在将密码保存到数据库之前、要对密码进行加密处理,
加盐的加密方式:bcrypt.js 零依赖
npm i bcryptjs
const verifyLogin = async (ctx, next) => {
const { userName, password } = ctx.request.body;
try {
// 1.判断用户是否存在,不存在就报错
const res = await getUserInfo({ userName });
console.log(res, '***************');
if (!res) {
console.log('用户名不存在', { userName });
ctx.app.emit('error', userDoesNotExistError, ctx);
return;
}
// 2.用户存在,比对密码是否匹配,不匹配报错
// 明文密码解密
let isMatch = bcrypt.compareSync(password, res.password)
if (!isMatch) {
console.error('密码不匹配');
ctx.app.emit('error', invalidPasswordError, ctx);
return
}
} catch (error) {
console.log(error)
console.error('用户登录错误');
ctx.app.emit('error', userLoginError, ctx);
return;
}
await next();
};
// 登录接口
router.post("/login",userValidator,verifyLogin,login)
登录成功后、给用户颁发token令牌,用户在以后的每一次请求中、携带该令牌、在服务端对令牌进行有效性校验。
第一部分我们称它为头部(header)
,第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),
第三部分是签证(signature).
npm i jsonwebtoken
并在登录成功后作为结果返回。
// 操作数据库
try {
// 从返回结果中剔除password字段,剩下的属性放到新的对象resUser中
const { password, ...res } = await getUserInfo({ userName });
console.log(res)//{ id: 27, userName: 'sss5', isAdmin: false }
// 返回对应的结果
ctx.body = {
code: 0,
message: '用户登录成功',
result: {
token: jwt.sign(res, JWT_SECRET, { expiresIn: '1d' }),
},
};
} catch (error) {
console.log('用户登录失败', error);
ctx.app.emit('error', userLoginError, ctx);
}
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjcsInVzZXJOYW1lIjoic3NzNSIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE2NjY3MTA2OTIsImV4cCI6MTY2Njc5NzA5Mn0.pfebYFF4T-JRjxB4M3L8CZ5AAnhZK5tnvVDgkkzUzYk
{
method: 'PATCH',
url: '/users/',
header: {
authorization: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjcsInVzZXJOYW1lIjoic3NzNSIsImlzQWRtaW4iOmZhbHNlLCJpYXQiOjE2NjY3MTA2OTIsImV4cCI6MTY2Njc5NzA5Mn0.pfebYFF4T-JRjxB4M3L8CZ5AAnhZK5tnvVDgkkzUzYk',
'content-type': 'application/json',
'user-agent': 'PostmanRuntime/7.29.2',
accept: '*/*',
'cache-control': 'no-cache',
'postman-token': 'ffdf5be9-4e00-48f8-80c4-b7f95cba5fb1',
host: 'localhost:8000',
'accept-encoding': 'gzip, deflate, br',
connection: 'keep-alive',
'content-length': '26'
}
}
const jwt = require('jsonwebtoken');
const {JWT_SECRET} = require('../config/config.default')
const auth = async (ctx, next) => {
const {authorization} = ctx.request.header;
const token = authorization.replace('Bearer ','')
console.log(token)
// 验证token
try {
const user = jwt.verify(token,JWT_SECRET)
console.log(user)
ctx.state.user = user
} catch (error) {
switch (error.name) {
case 'TokenExpiredError':
console.error('token已经过期', error);
ctx.app.emit('error', tokenExpiredError, ctx);
return;
case 'JsonWebTokenError':
console.error('无效的token', error);
ctx.app.emit('error', invalidTokenError, ctx);
return;
}
}
await next();
};
module.exports = {
auth,
};
{
id: 27,
userName: 'sss5',
isAdmin: false,
iat: 1666710692,
exp: 1666797092
}
async updatePassword (ctx,next) {
// 获取请求参数
// 1.获取请求中的新密码 携带token
const id = ctx.state.user.id
const {password} = ctx.request.body
// 操作数据库
try {
const res = await updateById({id,password})
if(res){
// 返回对应的结果
ctx.body = {
code: 0,
message: '修改密码成功',
result: ''
};
}
} catch (error) {
console.log('修改密码失败', error);
ctx.app.emit('error', updatePasswordError, ctx);
}
}
}
async updateById({ id, userName, password, isAdmin }) {
const wehreOpt = { id };
const newUser = {};
userName && Object.assign(newUser, { userName });
password && Object.assign(newUser, { password });
isAdmin && Object.assign(newUser, { isAdmin });
const res = await User.update(newUser, {
where: wehreOpt,
});
console.log(res);
return res[0]>0 ? true : false;
}
基本逻辑:先验证是否登录、登录后验证是否是管理员、是管理员可以上传、不是的话提示无管理员权限。
验证是否登录复用之前的auth方法
const hadAdminPermission = async (ctx, next) => {
try {
const {isAdmin} = ctx.state.user
console.log(isAdmin);
if(!isAdmin){
console.error('该用户没有管理员权限',ctx.state.user)
ctx.app.emit('error', noAdminPermissionError, ctx);
}
} catch (error) {
console.error("上传图片失败",error)
ctx.app.emit('error', uploadPictureError, ctx);
}
await next();
};
koa-body文件上传ctx.request.files为undefined
解决:应该用post请求
async upload(ctx, next) {
try {
// 获取请求参数
const file = ctx.request.files.file;
console.log(file,"***")
// 操作数据库
// 返回对应的结果
if(file){
ctx.body = {
code: 0,
message: '图片上传成功',
result: ''
};
}
else{
console.error("上传图片失败");
ctx.app.emit('error', uploadPictureError, ctx);
return
}
} catch (error) {
console.log(error,"上传图片失败");
ctx.app.emit('error', uploadPictureError, ctx);
return
}
}
PersistentFile {
_events: [Object: null prototype] { error: [Function (anonymous)] },
_eventsCount: 1,
_maxListeners: undefined,
lastModifiedDate: 2022-10-26T16:41:03.473Z,
filepath: 'D:\\api\\src\\upload\\98ef725fe046f9fe45fbb0100.jpg',
newFilename: '98ef725fe046f9fe45fbb0100.jpg',
originalFilename: '微信图片_20220710140048.jpg',
mimetype: 'image/jpeg',
hashAlgorithm: false,
size: 7890553,
_writeStream: WriteStream {
_writableState: WritableState {
objectMode: false,
highWaterMark: 16384,
finalCalled: true,
needDrain: true,
ending: true,
ended: true,
finished: true,
destroyed: true,
decodeStrings: true,
defaultEncoding: 'utf8',
length: 0,
writing: false,
corked: 0,
sync: false,
bufferProcessing: false,
onwrite: [Function: bound onwrite],
writecb: null,
writelen: 0,
afterWriteTickInfo: null,
buffered: [],
bufferedIndex: 0,
allBuffers: true,
allNoop: true,
pendingcb: 0,
prefinished: true,
errorEmitted: false,
emitClose: true,
autoDestroy: true,
errored: null,
closed: false
},
_events: [Object: null prototype] { error: [Function (anonymous)] },
_eventsCount: 1,
_maxListeners: undefined,
path: 'D:\\api\\src\\upload\\98ef725fe046f9fe45fbb0100.jpg',
fd: null,
flags: 'w',
mode: 438,
start: undefined,
autoClose: true,
pos: undefined,
bytesWritten: 7890553,
closed: false,
[Symbol(kFs)]: {
appendFile: [Function: appendFile],
appendFileSync: [Function: appendFileSync],
access: [Function: access],
accessSync: [Function: accessSync],
chown: [Function: chown],
chownSync: [Function: chownSync],
chmod: [Function: chmod],
chmodSync: [Function: chmodSync],
close: [Function: close],
closeSync: [Function: closeSync],
copyFile: [Function: copyFile],
copyFileSync: [Function: copyFileSync],
createReadStream: [Function: createReadStream],
createWriteStream: [Function: createWriteStream],
exists: [Function: exists],
existsSync: [Function: existsSync],
fchown: [Function: fchown],
fchownSync: [Function: fchownSync],
fchmod: [Function: fchmod],
fchmodSync: [Function: fchmodSync],
fdatasync: [Function: fdatasync],
fdatasyncSync: [Function: fdatasyncSync],
fstat: [Function: fstat],
fstatSync: [Function: fstatSync],
fsync: [Function: fsync],
fsyncSync: [Function: fsyncSync],
ftruncate: [Function: ftruncate],
ftruncateSync: [Function: ftruncateSync],
futimes: [Function: futimes],
futimesSync: [Function: futimesSync],
lchown: [Function: lchown],
lchownSync: [Function: lchownSync],
lchmod: undefined,
lchmodSync: undefined,
link: [Function: link],
linkSync: [Function: linkSync],
lstat: [Function: lstat],
lstatSync: [Function: lstatSync],
lutimes: [Function: lutimes],
lutimesSync: [Function: lutimesSync],
mkdir: [Function: mkdir],
mkdirSync: [Function: mkdirSync],
mkdtemp: [Function: mkdtemp],
mkdtempSync: [Function: mkdtempSync],
open: [Function: open],
openSync: [Function: openSync],
opendir: [Function: opendir],
opendirSync: [Function: opendirSync],
readdir: [Function: readdir],
readdirSync: [Function: readdirSync],
read: [Function: read],
readSync: [Function: readSync],
readv: [Function: readv],
readvSync: [Function: readvSync],
readFile: [Function: readFile],
readFileSync: [Function: readFileSync],
readlink: [Function: readlink],
readlinkSync: [Function: readlinkSync],
realpath: [Function],
realpathSync: [Function],
rename: [Function: rename],
renameSync: [Function: renameSync],
rm: [Function: rm],
rmSync: [Function: rmSync],
R_OK: 4,
W_OK: 2,
X_OK: 1,
constants: [Object: null prototype],
promises: [Getter]
},
[Symbol(kCapture)]: false,
[Symbol(kIsPerformingIO)]: false
},
hash: null,
[Symbol(kCapture)]: false
} ***
const path = require('path')
const Koa = require('koa');
const app = new Koa();
const koaBody = require('koa-body');
const koaStatic = require('koa-static');
app.use(koaBody(
{
multipart:true,
// 配置选项不推荐使用相对路径
// 在option里的相对路径,不是相对的当前文件,相对process.cwd()
formidable:{
uploadDir:path.join(__dirname,'../upload'),
keepExtensions:true,
}
}
));// 需要在最上层
app.use(koaStatic(path.join(__dirname,'../upload')))
// 处理错误的函数
const errHandler = require('./errHandler')
const router = require('../router/index')
app.use(router.routes())
app.use(router.allowedMethods())
// const userRouter = require('../router/user.route')
// app.use(userRouter.routes());
// const goodRouter = require('../router/good.route')
// app.use(goodRouter.routes());
// 统一处理错误
app.on('error',errHandler)
module.exports = app
访问:
{
"code": 0,
"message": "图片上传成功",
"result": {
"good_img": "f497c30c6639edbeb1fd2ef03.jpg"
}
}
http://localhost:8000/指定资源文件夹下的文件名:
(http://localhost:8000/f497c30c6639edbeb1fd2ef00.jpg)`
koa-parameter
- 初步:koa-parameter
const {goodFormateError} = require('../constant/err.type')
const paramValidator = async (ctx, next) => {
try {
ctx.verifyParams({
goodName: {
type:'string',
required: true
},
goodPrice: {
type:'number',
required: true
},
goodNum: {
type:'number',
required: true
},
goodImg: {
type:'string',
required: true
},
});
} catch (error) {
console.error(error)
goodFormateError.result = error
ctx.app.emit('error', goodFormateError, ctx);
return
}
await next();
};
module.exports = {
paramValidator,
};
Nginx是俄罗斯人Igor Sysoev编写的轻量级Web服务器,它的发音为 [ˈendʒɪnks] ,它不仅是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。
截至2019年12月,差不多世界上每3个网站中就有1个使用Nginx。
Nginx以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡服务器。在性能上,Nginx占用很少的系统资源,能支持更多的并发连接,达到更高的访问效率;在功能上,Nginx是优秀的代理服务器和负载均衡服务器;在安装配置上,Nginx安装简单、配置灵活。
Nginx支持热部署,启动速度特别快,还可以在不间断服务的情况下对软件版本或配置进行升级,即使运行数月也无需重新启动。
在微服务的体系之下,Nginx正在被越来越多的项目采用作为网关来使用,配合Lua做限流、熔断等控制。
项目的源码地址为:https://github.com/Mhist/api
通过npm i安装依赖
并且通过pm2 启动项目
pm2 start src/main.js
server
{
listen 80;
server_name localhost;
index index.html index.htm index.php;
root /www/server/phpmyadmin;
#error_page 404 /404.html;
include enable-php.conf;
location / {
proxy_pass http://localhost:8666;
}
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
{
expires 30d;
}
location ~ .*\.(js|css)?$
{
expires 12h;
}
location ~ /\.
{
deny all;
}
access_log /www/wwwlogs/access.log;
}
listen 80; server_name localhost;
location / { proxy_pass http://localhost:8666; }
修改env中的端口号:本地8000 线上是8666
修改数据库用户名 本地是root 线上是admin
保持mysql版本一致,本地是8.0;线上也需要是8.0,model文件中的强制建表语句取消注释运行一遍建表语句、然后重新注释掉。
koa2_husi项目 https://www.apifox.cn/apidoc/shared-5bd98502-7911-48ee-9428-1fecdf8dd35d/api-47064905