nodejs-koa2(mvc模式)前后端分离 前端设计

前言

前后端分离,前端nodejs运行环境,使用koa2集成负责资源分配与用户交互,实现token验证用户身份,路由控制。等!

一、开发准备

  • Nodejs介绍;
  • koa2介绍;
  • Nodejs环境安装;
  • 开发工具Visual Studio Code(下文简称vsc),vsc怎么新建项目;

自行百度解决;

二、开始干活

1、在硬盘上新增一个文件夹,打开VSC,点击‘添加工作区文件夹’,如果没有欢迎‘使用页面’,点击--文件--新建窗口,效果如下图

2、添加vsc调试。Shift+ctrl+p,输入框内输入:launch.json

选择刚刚新建的文件夹


launch.json文件是vsc帮忙生成的,里面有一些版本信息启动设置等

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "node",
            "request": "launch",
            "name": "启动程序",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\app.js"
        }
    ]
}

"program": "${workspaceFolder}\app.js"

此处就是是将app.js作为启动文件。${workspaceFolder}代表根目录,vsc启动时会在根目录下找到并加载app.js文件。

3、项目结构

3-1、package.json

参数介绍:name项目名称、version版本号、description项目描述、main项目启动文件、scripts启动快捷设置,author作者,dependencies第3方中间件名称及版本。

{
    "name": "koa2mcv",
    "version": "1.0.0",
    "description": "Hello Koa 2 example with MVC",
    "main": "app.js",
    "scripts": {
        "start": "node app.js"
    },
    "author": "baba",
    "dependencies": {
        "koa": "2.11.0",
        "koa-router": "8.0.8",
        "koa-bodyparser": "4.3.0",
        "koa-static-plus": "0.1.1",
        "koa-view": "2.1.3",
        "koa-jwt": "4.0.0",
        "koa-log4": "2.3.2",
        "jsonwebtoken": "8.5.1",
        "nunjucks": "3.2.1",
        "mime": "2.4.5",
        "mz": "2.7.0"
    }
}

最重要的
dependencies”这里添加一些要用到的包,以上是这次要用到的所有的包,版本自己更改。
scripts”这里是一些nodejs的便捷命令,上线的时候会用到,直接在终端中,package.json同级目录 ,执行‘npm start’ 即 可启动app.js。
别的没啥太大作用瞎写即可。

3-2、app.js

启动相关配置,封装到config/init.js中,启动文件直接引用即可

//启动服务
require('./config/init').startServer();

3-3、views存放html页面。

3-4、static存放静态文件,css,js,font等。

3-5、src存放业务控制,类似于springMVC中的controller、service。

3-6、config存放核心配置文件。

3-6-1、init.js项目核心。

异常友好处理方法封装

function handler(){
    return async (ctx, next) => {
        const start = new Date().getTime(); 
        var urlReq=ctx.request.url;
        if(urlReq !== '/favicon.ico'){
            console.log(`请求地址:${ctx.request.method} ${urlReq}`); 
            try {
                let params =Object.assign({}, ctx.request.query, ctx.request.body);
                if(config["token"].excludeUrl.indexOf(urlReq) == -1 && !tokenFunction.varifyToken(params.token)){
                    ctx.status =401;
                }else{
                    await next();
                }
            } catch (error) {
                ctx.status=401;
                console.log(`错误!无法获取token参数`);
            }finally{
                    let err={};
                    if(!ctx.status){
                        err.status = 500;
                    }else if(ctx.status==200){
                        return;
                    }else{
                        err.status = ctx.status;
                    }
                    switch(err.status){
                        case 404: 
                            err.url = config["server-name"]+'/static/public/404.html';
                            err.message="资源不存在!";
                            break;
                        case 401: 
                            err.url = config["server-name"]+'/static/public/401.html';
                            err.message="登陆失效!请重新登陆!";
                            break;
                        case 500: 
                            err.url = config["server-name"]+'/static/public/505.html';
                            err.message="系统内部错误!";
                            break;
                    }
                    switch(ctx.request.type){
                        case 'application/json': 
                            ctx.type = 'application/json';
                            ctx.body = {errorCode:err.errorCode,message: err.message}
                            break;
                        default: 
                            ctx.type = 'text/html';
                            ctx.redirect(err.url);
                            break;
                    }
            }
        }
        const ms = new Date().getTime() - start;
        console.log(`请求消耗时间: ${ms}ms`);
    }
}

路由配置

function controller(){
    const router = new koaRouter({
        prefix: config["server-name"]
    });
    function findJsonFile(rootpathStr){
        fs.readdirSync(rootpathStr).forEach(function (item, index) {
            let fPath = path.join(rootpathStr,item);
            let stat = fs.statSync(fPath);
            if(stat.isDirectory() === true) {
                findJsonFile(fPath);
            }
            if (stat.isFile() === true&&fPath.endsWith('.js')) { 
                var mapping =  require(fPath);
                for (var url in mapping) {
                    if (url.startsWith('GET ')) {
                        router.get(url.substring(4), mapping[url]);
                    } else if (url.startsWith('POST ')) {
                        router.post(url.substring(5), mapping[url]); 
                    } else if (url.startsWith('PUT ')) {
                        router.put(url.substring(4), mapping[url]); 
                    } else if (url.startsWith('DELETE ')) {
                        router.del(url.substring(7), mapping[url]);
                    }
                    console.log(`注册 URL: ${url}`);
                }
            }
        });
    }
    findJsonFile(rootpath + 'src');
    return router.routes(); 
}

视图渲染

function templating() {
    var
        autoescape = config['templating-autoescape'] === null ? true : config['templating-autoescape'],
        noCache = config['templating-noCache'] === null ? false : config['templating-noCache'],
        watch = config['templating-watch'] === null ? false : config['templating-watch'],
        throwOnUndefined = config['templating-throwOnUndefined'] === null ? false :config['templating-throwOnUndefined'],
        env = new nunjucks.Environment(
            new nunjucks.FileSystemLoader(rootpath+'views', {
                noCache: noCache,
                watch: watch,
            }), {
                autoescape: autoescape,
                throwOnUndefined: throwOnUndefined
            });
    if (config['templating-filters'] != null) {
        for (var f in config['templating-filters']) {
            env.addFilter(f, config['templating-filters'][f]);
        }
    }
    return async (ctx, next) => {
        ctx.render = function (view, model) {
            ctx.response.body = env.render(view, Object.assign({}, ctx.state || {}, model || {}));
            ctx.response.type = 'text/html';
        };
        await next();
    };
}

核心集成

function startServer(){
    const app = new koa();
    app.use(koaStaticPlus(rootpath+'static', {
        pathPrefix: config["server-name"]+'/static'
    })
    );
    app.use(koaBodyParser());
    app.use(handler());
    app.use(templating());
    app.use(controller());
    app.listen(config["server-port"]);
}

3-6-2、config.js项目参数配置。为什么不用json文件 因为json不能加注释

module.exports ={
    'server-name':'/koa',
    'server-port':3000,
    "templating-noCache":true,
    "templating-watch":true,
    "templating-autoescape":null,
    "templating-throwOnUndefined":null,
    "templating-filters":null,
    "token":{
      "excludeUrl":[
        "/koa/login",
        "/koa/dologin"
      ],
      "timeout":1000 * 60 * 60 * 24 * 7,
      "secret":"jiaobaba"
    }

}

3-6-3、token.js项目token相关方法封装。

const jwt = require("jsonwebtoken");
const config = require('./config');
/**
 * 创建token的方法
 */
let createToken = (data)=>{
    let obj = {};
    //存入token的数据
    obj.data = data || {};
    //token的创建时间
    obj.ctime = (new Date()).getTime();
    return jwt.sign(obj,config["token"].secret);
}
/**
 * 验证token是否合法的方法
 * @param {*} token 
 */
let varifyToken = (token)=>{
    let result = null;
    try{
        let {data,ctime,expiresIn} = jwt.verify(token,config["token"].secret);
        let nowTime = (new Date()).getTime();
        if(nowTime-ctime<config["token"].timeout){
            result = data;        
        }
    }catch(error){

    }
    return result;
}

module.exports = {
    createToken,
    varifyToken
};

4、项目结构构建结束,接着引入所有依赖包,在终端中执行‘npm install’ ,会下载package.json中dependencies所有包,以及这些包所依赖的包。

执行后项目结构会增加两个文件

5、编写测试用例

新增

src/hello.js。


//token
const token = require('../config/token');

var fn_hello = async (ctx, next) => {
    var name = ctx.params.name;
    ctx.response.body = `<h1>Hello, ${name}!</h1>`;
};

var fn_index = async (ctx, next) => {
    ctx.response.body = `<h1>Index</h1>
        <form action="/koa/dologin" method="post">
            <p>Name: <input name="name" value=""></p>
            <p>Password: <input name="password" type="password"></p>
            <p><input type="submit" value="Submit"></p>
        </form>`;
};

var fn_signin = async (ctx, next) => {
    var name = ctx.request.body.name || '',
        password = ctx.request.body.password || '';
    console.log(`登陆名: ${name}, 密码: ${password}`);
    ctx.response.body = `<h1>Welcome, ${name}!<br/>you token:<br/>${token.createToken({user: name,password: password})}</h1>`;

};

var fn_view = async (ctx,next) =>{
    ctx.render('index.html', {
        name: 'Welcome'
    });
}

module.exports = {
    'GET /hello/:name': fn_hello,
    'GET /login': fn_index,
    'POST /dologin': fn_signin,
    'GET /views': fn_view
};

views/index.html

<!DOCTYPE html>
<html>
<body>
   <strong>Well done!</strong> You successfully signed in as<h1> {{ name }}!</h1>
</body>
</html>

6、启动项目

6-1、启动成功

6-2、测试

浏览器访问:http://127.0.0.1:3000/koa/login

输入值获取token

获取的token如图:

先不用带token进行访问:http://127.0.0.1:3000/koa/hello/jiaobaba,被token拦截,返回401

带上token访问:http://127.0.0.1:3000/koa/hello/jiaobaba

测试页面渲染,及跳转html页面,直接访问http://127.0.0.1:3000/koa/views

结束!!!!!!

需要源码联系我

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。