内容简介:最近使用koa搭建了个web服务器,包括Session的处理、路由的设计、项目结构的设计、错误的处理、数据库的读写等,用起来特别的爽,在这做个总结,供大家参考,有什么不对的,欢迎讨论。server/使用到的中间件版本
最近使用koa搭建了个web服务器,包括Session的处理、路由的设计、项目结构的设计、错误的处理、数据库的读写等,用起来特别的爽,在这做个总结,供大家参考,有什么不对的,欢迎讨论。
项目结构
-config.js -package.json -scripts/,用来存放初始脚本文件 -logs/,用来存放日志 -static/,用来存放前端文件 -server/,用来存放后端的代码复制代码
server/
├── app.js//主文件,创建server,并使用对应的中间件
├── controller//响应路由,调用services中对应的模块,返回结果
│ ├── home.js
│ ├── strategy.js
│ └── user.js
├── models//操作数据库
│ ├── strategy.js
│ └── user.js
├── routers//路由的定义目录
│ ├── home.js
│ ├── routers.js
│ ├── strategy.js
│ └── user.js
├── services//调用models,返回正确的处理;之所以要增加这么一个在controller和models之间的模块,主要还是考虑到services的抽象
│ ├── strategy.js
│ └── user.js
└── utils//公共的模块,比如日志,数据库的读写
├── datetime.js
├── db.js
├── log.js
└── redis.js复制代码
使用到的中间件版本
"bcrypt": "^3.0.6",
"ioredis": "^4.9.5",
"koa": "^2.7.0",
"koa-bodyparser": "^4.2.1",
"koa-redis": "^4.0.0",
"koa-router": "^7.4.0",
"koa-session-minimal": "^3.0.4",
"koa-mysql-session": "^0.0.2",
"koa-static": "^5.0.0",
"log4js": "^4.3.1",
"mysql": "^2.17.1",
"superagent": "^5.0.6"
复制代码
创建web server
app.js
const Koa = require('koa');
const app = new Koa();
const static = require('koa-static');
const bodyParser = require('koa-bodyparser');
const routers = require('./routers/routers');
const config = require('../config');
const session = require('koa-session-minimal');
const redisStore = require('koa-redis')
const redis = require('./utils/redis')
const log = require('./utils/log')
const logger = async (ctx, next)=>{
log.info(`[uid: ${ctx.session.uid}] ${ctx.request.method}, ${ctx.request.url}`);
await next();
}
const handler = async (ctx, next)=>{
try {
await next();
} catch (error) {
ctx.response.status = error.statusCode || error.status || 500;
ctx.response.body = {
message: error.message
};
}
}
// 配置存储session信息的redis
const store = new redisStore({
client: redis
});
app.use(bodyParser());
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: {// 存放sessionId的cookie配置
maxAge: 24*3600*1000, // cookie有效时长
expires: '', // cookie失效时间
path: '/', // 写cookie所在的路径
domain: config.domain, // 写cookie所在的域名
httpOnly: '', // 是否只用于http请求中获取
overwrite: '', // 是否允许重写
secure: '',
sameSite: '',
signed: '',
}
}));
app
.use(logger)//处理log
.use(handler)//处理出错信息
.use(static(config.staticPath));//配置静态资源路径
app
.use(routers.routes())
.use(routers.allowedMethods());//注册路由
app.listen(config.port, ()=>{
log.info('server is running on port:'+config.port);
});
复制代码
处理Session
由于http协议不能记住是哪个用户在使用该协议,所以需要另外的机制来辅助;Session指的是服务器端用来记住哪个user在使用的机制,当然也需要前端来配合的(一般都是将Session的信息,比如session_id写入cookie中),http协议会带着cookie信息,然后服务器端通过该session_id从 mysql 或 redis 中找到对应的uid,这样就知道是谁在使用了。
上面的处理机制说着复杂,我们用着其实很简单,koa-session-minimal中间件已经帮我们处理了,当然我们需要告诉它应该把session信息存在哪里,可以是mysql或者redis;
将session信息存入redis
const session = require('koa-session-minimal');
const redisStore = require('koa-redis')
const redis = require('./utils/redis')
// 配置存储session信息的redis
const store = new redisStore({
client: redis
});
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: {// 存放sessionId的cookie配置
maxAge: 24*3600*1000, // cookie有效时长
expires: '', // cookie失效时间
path: '/', // 写cookie所在的路径
domain: config.domain, // 写cookie所在的域名
httpOnly: '', // 是否只用于http请求中获取
overwrite: '', // 是否允许重写
secure: '',
sameSite: '',
signed: '',
}
}));
复制代码
./utils/redis.js
const config = require('../../config')
const redisConfig = config.redis
const Redis = require('ioredis')
let redis = new Redis({
host: redisConfig.HOST,
port: redisConfig.PORT,
password: redisConfig.PASSWORD,
family: redisConfig.FAMILY,
db: redisConfig.DB,
ttl: redisConfig.TTL//设置过期时间,单位是秒
})
module.exports = redis
复制代码
将session信息存入mysql
这种方法有个问题,当很多用户不断登录的时候,会在mysql上遗留很多的过期没用的session信息,这个就需要清理,比较麻烦。但redis中就可以直接设置个过期时间,很方便,所以我用的是redis来存储
const session = require('koa-session-minimal');
const MysqlSession = require('koa-mysql-session');
// 配置存储session信息的mysql
const store = new MysqlSession({
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE,
host: config.database.HOST,
});
app.use(session({
key: 'SESSION_ID',
store: store,
cookie: {// 存放sessionId的cookie配置
maxAge: 24*3600*1000, // cookie有效时长
expires: '', // cookie失效时间
path: '/', // 写cookie所在的路径
domain: 'localhost', // 写cookie所在的域名
httpOnly: '', // 是否只用于http请求中获取
overwrite: '', // 是否允许重写
secure: '',
sameSite: '',
signed: '',
}
}));
复制代码
路由的设计
koa支持多路由的注册与响应,很方便根据模块来设计api与处理
app.js
const routers = require('./routers/routers');
app
.use(routers.routes()).use(routers.allowedMethods());复制代码
routers/routers.js,将多个模块中的路由整合在这里
const Router = require('koa-router');
const home = require('./home');
const user = require('./user');
const strategy = require('./strategy');
const router = new Router();
router.use(home.routes(), home.allowedMethods());
router.use(user.routes(), user.allowedMethods());
router.use(strategy.routes(), strategy.allowedMethods());
module.exports = router;
复制代码
routers/user.js,用户相关的路由
const Router = require('koa-router');
const router = new Router();
const userCtl = require('../controller/user');
router.get('/user/list', userCtl.getUserList);
router.get('/user/info', userCtl.getUserInfo);
router.post('/user/signin', userCtl.userSignin);
router.post('/user/signup', userCtl.userSignup);
module.exports = router;
复制代码
项目结构的设计
路由注册好后,就应该响应路由,返回结果,对应到项目中,
routers/user.js --> controller/user.js --> services/user.js --> models/user.js --> utils/db.js复制代码
async/await的使用
async、await的使用,使得写异步代码就像是写同步代码一样,逻辑上非常清晰,可以很好的解决回调地狱的情况。
错误处理
项目中的出错的处理,都是在controller中,通过try/catch来捕获;逻辑上感觉很干净,不知道实际工程中会不会有什么问题,有大量实战经验的童鞋可以说说哦。
controller/user.js
controller中处理api相关的逻辑;
项目中使用bcrypt进行密码的加密与对比;
const userService = require('../services/user')
const bcrypt = require('bcrypt')
const log = require('../utils/log')
const userSignin = async (ctx)=>{
let result = {
success: false,
message: '',
data: null,
}, message = ''
if (ctx.session.uid) {
message = 'aleady login.'
result.data = {
uid: ctx.session.uid
}
} else {
try {
let formData = ctx.request.body
let res = await userService.signin(formData)
if (res) {
if (bcrypt.compareSync(formData.password, res.password)) {
ctx.session = {uid: res.id}
result.success = true
result.data = {uid: res.id, name: res.name}
} else {
message = 'phone or password error.'
}
} else {
message = 'no such user.'
}
} catch (error) {
message = 'login failed'
log.error(message+', '+error)
}
}
result.message = message
ctx.body = result
}
module.exports = {
getUserList,
getUserInfo,
userSignin,
userSignup
}复制代码
services/user.js
因为service可以是多种多样的,所以需要用services模块来封装一层;
services中,应该考虑到从数据库中获取到的各种情况,比如通过用户的手机获取用户信息,可能获取失败、可能获取不到、可能获取成功,比如下面的处理。
const userModel = require('../models/user')
const signin = async (formData)=>{
return new Promise((resolve, reject)=>{
userModel.getUserByPhone(formData.phone)
.then(res=>{
if (Array.isArray(res) && res.length> 0) {
resolve(res[0])
} else {
resolve(null)
}
}, err=>{
reject(err)
})
})
}
module.exports = {
getUserList,
getUserInfoById,
signin,
checkIsUserAdmin,
modifyUser
}
复制代码
models/user.js
models也是类似,也是为了封装对应模块的数据库操作
const db = require('../utils/db')
const getUserList = async ()=> {
let keys = ['id', 'phone', 'level', 'avatar', 'login_count', 'create_time', 'last_login_time']
return await db.selectKeys('user_info', keys)
}
const getUserInfoById = async (uid)=> {
let keys = ['id', 'phone', 'level', 'avatar', 'login_count', 'create_time', 'last_login_time']
return await db.selectKeysById('user_info', keys, uid)
}
const getUserByPhone = async (phone)=> {
let _sql = "select * from user_info where phone="+phone+" limit 1"
return await db.query(_sql)
}
const modifyUser = async (user)=> {
return await db.updateData('user_info', user, user.id)
}
module.exports = {
getUserList,
getUserInfoById,
getUserByPhone,
modifyUser
}
复制代码
utils/db.js
mysql的操作模块,对外提供一些接口给其他模块使用
const config = require('./../../config')
const dbConfig = config.database
const mysql = require('mysql')
const pool = mysql.createPool({
host: dbConfig.HOST,
user: dbConfig.USERNAME,
password: dbConfig.PASSWORD,
database: dbConfig.DATABASE
})
let query = (sql, values)=>{
return new Promise((resolve, reject)=>{
pool.getConnection((err, connection)=>{
if (err) {
resolve(err)
} else {
connection.query(sql, values, (err, rows)=>{
if (err) {
reject(err)
} else {
resolve(rows)
}
connection.release()
})
}
})
})
}
let createTable = sql => {
return query(sql, [])
}
let selectAll = (table)=>{
let _sql = "select * from ??"
return query(_sql, [table])
}
let selectAllById = (table, id)=>{
let _sql = "select * from ?? where id = ?"
return query(_sql, [table, id])
}
let selectKeys = (table, keys)=>{
let _sql = "select ?? from ??"
return query(_sql, [keys, table])
}
module.exports = {
query,
createTable,
selectAll,
selectAllById,
selectKeys,
selectKeysById,
selectKeysByKey,
insertData,
insertBatchData,
updateData,
deleteDataById
}复制代码
utils/log.js
使用log4js来对日志进行归档分类
const config = require('../../config')
const debug = config.debug
const logPath = config.logPath
const log4js = require('log4js')
const getCfg = ()=>{
var cfg = {}
if (debug) {
cfg.type = 'console'
} else {
cfg.type = 'file'
cfg.filename = logPath+'/server-'+new Date().toLocaleDateString()+'.log'
}
return cfg
}
log4js.configure({
appenders: {
access: getCfg()
},
categories: {
default: {appenders: ['access'], level: 'info'}
}
})
module.exports = log4js.getLogger('access')
复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Bulletproof Web Design
Dan Cederholm / New Riders Press / 28 July, 2005 / $39.99
No matter how visually appealing or packed with content a Web site is, it isn't succeeding if it's not reaching the widest possible audience. Designers who get this guide can be assured their Web site......一起来看看 《Bulletproof Web Design》 这本书的介绍吧!
随机密码生成器
多种字符组合密码
RGB HSV 转换
RGB HSV 互转工具