内容简介:最近使用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') 复制代码
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Raspberry Pi设置自动拨号搭建无线路由环境
- hapi框架搭建记录(二):路由改造和生成接口文档
- 简单搭建OSPF,RIP,NSSA,外部路由汇总网络拓扑
- 仿有赞后台+vue+ts+vuecli3.0+elementUi+二期项目结构文件搭建+以及路由的使用
- vue路由篇(动态路由、路由嵌套)
- 小程序封装路由文件和路由方法,5种路由方法全解析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
产品经理面试宝典
[美] Gayle Laakmann McDowell、[美]Jackie Bavaro / 吴海星、陈少芸 / 人民邮电出版社 / 2015-3 / 59.00元
本书针对IT 行业产品经理,以面试为主线,首先介绍产品经理职责以及谷歌、微软等知名企业中产品经理的作用和要求;然后采访了几位知名企业的产品经理,介绍成为产品经理的基本素质;之后从简历准备、各公司面试要点到具体面试问题进行详细分析,这部分是本书的重点内容。读者对象包括IT 行业产品经理以及对如何做好产品有兴趣的人士。一起来看看 《产品经理面试宝典》 这本书的介绍吧!