• home > webfront > server > koa >

    一文解决koa后端开发

    Author:zhoulujun Date:

    首先在mac上搭建环境:mac搭建php(mysql)运行环境—搭建xmapp之前一直都是使用php,现在mysql 用nodejs 玩耍,更加舒服搭建koa2安装koa2

    首先在mac上搭建环境:mac搭建php(mysql)运行环境—搭建xmapp

    之前一直都是使用php,现在mysql 用nodejs 玩耍,更加舒服

    搭建koa2

    安装koa2 koa-bodyparser koa-router koa2-cors koa-views jsonwebtoken

    npm i koa-bodyparser koa-body koa-router koa-views koa-static koa2-cors jsonwebtoken  --save
    • koa-bodyparser:用于接收并解析前台发送过来的post数据

    • koa-body 允许上传文件

    • koa-router:路由

    • koa2-cors:用来解决前端的跨域

    • koa-views 解析前端hmtl 模板文件

    • koa-static 静态资源服务

    • jsonwebtoken  token登录验证

    • koa-jwt 中间件 对 jsonwebtoken 进一步的封装,主要用来校验 token

    下面的代码是验证koa是否搭建成功

    const Koa = require('koa');
    const app = new Koa();
        
    app.use(async (ctx) => {
      ctx.body = 'hello';
    })   
    app.listen(3000);

    这是为了启动一个后台,可以用:https://www.npmjs.com/package/koa-generator


    koa静态资源服务器

    const Koa = require('koa')
    const views = require('koa-views');
    const static = require('koa-static');
    let app = new Koa();
    app.use(views(__dirname + '/views'))
    // 1.静态资源服务,指定对外提供访问的根目录,不包括 public
    app.use(static(__dirname + '/public'));
    app.listen(3000);

    比如图片文件在pulic/upload/c65247fc3be64e100de570900.jpg

    实际访问路径是:

    koa路由配置

    /router/index

    let api = require("./api");
    module.exports = (app)=>{
      app.use(api.routes())
    }

    路由详细配置

    const Router = require('koa-router')
    const newsRouter = new Router({
      prefix: '/api' //前缀
    })
    const Controller = require('../controller/api')
    newsRouter.post('/login', Controller.login)
    newsRouter.get('/product', Controller.getProduct)
    newsRouter.post('/product', Controller.addProduct)
    module.exports  = newsRouter

    koa 控制器配置

    const mysql = require('../mysql');
    const addToken = require('../token/index');
    module.exports = {
      async getProduct(ctx) {
        let data = { a: 5 }
        ctx.body = {
          'result': true,
          'data': data,
          'message': 'ok'
        }
      },
      async login(ctx) {
        let { user, password } = ctx.request.body
        if(!user||!password){
          return  ctx.body = {
            'result': false,
            'data': data,
            'message': '参数不合法'
          }
        }
        console.log(user, password)
        let sql = `select id, user
                   from user
                   where \`user\` = "${user}"
                     AND \`password\` = "${password}";`
        let data = await mysql.query(sql)
        if (data?.length===1) {
          const user = data[0]
          let token = addtoken(user)  //token中要携带的信息,自己定义
          ctx.body = {
            'result': true,
            'data': {user,token},
            'message': 'ok'
          }
        }else {
          ctx.body = {
            'result': false,
            'data': data,
            'message': '用户活密码不正确,无法登录'
          }
        }
    
    
      },
      async addProduct(ctx) {
        const file = ctx.request.files.file;
        const basename = path.basename(file.path); //这时拿到的 localhost:端口号/uploads/ 文件名
        //但是不可能把这串地址写死进去, 因为部署到服务器要添加域名
        //用灵活变量代表
        ctx.body = { url: `${ctx.origin}/uploads/${basename}` } //ctx.origin 就是 localhost:端口号
      }
    
    }

    koa mysql连接配置:

    mysql配置文件:

    // config/defualt.js
    // 设置配置文件
    const config = {
        // 启动端口
        port: 3000,
        // 数据库配置
        database: {
            DATABASE: 'taihe',
            USERNAME: 'root',
            PASSWORD: '123456',
            PORT: '3306',
            HOST: 'localhost'
        }
    }
    
    module.exports = config

    koa mysql查询器

    const mysql = require('mysql');
    const config = require('../config/defualt')
    const pool  = mysql.createPool({
        host     : config.database.HOST,
        user     : config.database.USERNAME,
        password : config.database.PASSWORD,
        database : config.database.DATABASE
    });
    class Mysql {
        constructor () {
    
        }
        query (sql='SELECT * from user') {
            return new Promise((resolve, reject) => {
                pool.query(sql, function (error, results, fields) {
                    if (error) {
                        throw error
                    };
                    resolve(results)
                    // console.log('The solution is: ', results[0].solution);
                });
            })
    
        }
    }
    
    module.exports = new Mysql()


    下面是正式代码

    // index.js
    const Koa = require('koa')
    const views = require('koa-views');
    const static = require('koa-static');
    const koaBody = require('koa-body');
    const cors = require('koa2-cors');
    
    const config = require('./config/defualt')
    const mysql = require('./mysql/index')
    const path = require('path')
    
    
    const Router = require('@koa/router');
    let router = require('./router/router');
    
    const PORT = 3005
    
    let app = new Koa();
    //这是处理前端跨域的配置
    app.use(cors(
      {
       /* origin: function (ctx) {
          // if (ctx.url === '/login') {
          //   return "*"; // 允许来自所有域名请求
          // }
          return '*';
        },*/
        exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
        maxAge: 5,
        credentials: true,
        allowMethods: ['GET', 'POST', 'DELETE'],
        allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
      }
    ));
    
    
    app.use(koaBody({
      multipart: true,  //允许上传文件
      formidable: {
        //这是个 node 包, 设置一下选项
        uploadDir: path.join(__dirname, '/static/uploads'), //设置上传目录
        keepExtensions: true,  //设置文件后缀名保留
      }
    }))
    app.use(views(__dirname + '../dist'))
    app.use(static(__dirname + '../static/'));
    router(app);
    
    app.listen(PORT)
    
    
    app.use(async (ctx) => {
      let data = await mysql.query()
      ctx.body = {
        'result': true,
        'data': data,
        'message': 'ok'
      }
    
    })
    
    app.listen(config.port)
    
    console.log(`listening on port ${config.port}`)


    koa登录 token鉴权

    具体参看:【Node】使用 koa2 实现一个简单JWT鉴权 https://juejin.cn/post/6921493257578872845

    生成token

    const jwt = require('jsonwebtoken');
    const serect = 'token';  //密钥,不能丢
    module.exports = (userinfo) => { //创建token并导出
      return jwt.sign({
        user: userinfo.user,
        id: userinfo.id
      }, serect, {expiresIn: '1h'});
    };

    然后再login api 里面返回token,在前端 拿到login数据


    前端缓存token

    login() {
      this.$axios
        .post("/api/login", {
          ...this.ruleForm,
        })
        .then(res => {
          if (res.code === "0") {
            this.$message.success('登录成功');
            localStorage.setItem("token", res.data.token);
            this.$router.push("/");
          } else {
            this.$message(res.message);
          }
        });
    }

    ajax请求统一鉴权

    封装 axios 的拦截器,每次请求的时候把 token 带在请求头发送给服务器进行验证。这里如果之前放在 Cookie 中,可以让它自动发送,但是这样不能跨域。所以推荐做法是放在 HTTP 请求头 Authorization 中,注意这里的 Authorization 的设置,前面要加上 Bearer 。详情可以见 Bearer Authentication

    Authorization为啥必须要以Bearer开头

    Bearer代表Authorization头定义的schema ,除了Bearer,还有其它的一些 schemas , 标准规范请查看文档地址:

    https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication#authentication_schemes


    // axios 请求拦截器处理请求数据
    axios.interceptors.request.use(config => {
      const token = localStorage.getItem('token');
      config.headers.common['Authorization'] = 'Bearer ' + token; // 留意这里的 Authorization
      return config;
    })


    校验 token



    koa解决跨域的方法

    由于浏览器同源策略,凡是发送请求url的协议、域名、端口三者之间任意一个与当前页面地址不同即为跨域。存在跨域的情况:

    • 网络协议不同,如http协议访问https协议。

    • 端口不同,如80端口访问8080端口。

    • 域名不同,如zhoulujun.cn访问zhoulujun.cn。

    • 子域名不同,如www.zhoulujun.cn访问cdn.zhoulujun.cn。

    • 域名和域名对应ip,如www.zhoulujun.cn访问127.0.0.1

    跨域请求资源的方法

    • JSONP跨域

    • nginx反向代理

    • 服务器端修改heade

    • document.domain

    • window.name

    • postMessage

    koa中如何设置跨域


    自行添加一个中间件

    app.use(async (ctx, next) => {
        // 允许来自所有域名请求
        ctx.set("Access-Control-Allow-Origin", "*");
        // 这样就能只允许 http://localhost:8080 这个域名的请求了
        // ctx.set("Access-Control-Allow-Origin", "http://localhost:8080"); 
    
        // 设置所允许的HTTP请求方法
        ctx.set("Access-Control-Allow-Methods", "OPTIONS, GET, PUT, POST, DELETE");
    
        // 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段.
        ctx.set("Access-Control-Allow-Headers", "x-requested-with, accept, origin, content-type");
    
        // 服务器收到请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
    
        // Content-Type表示具体请求中的媒体类型信息
        ctx.set("Content-Type", "application/json;charset=utf-8");
    
        // 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。
        // 当设置成允许请求携带cookie时,需要保证"Access-Control-Allow-Origin"是服务器有的域名,而不能是"*";
        ctx.set("Access-Control-Allow-Credentials", true);
    
        // 该字段可选,用来指定本次预检请求的有效期,单位为秒。
        // 当请求方法是PUT或DELETE等特殊方法或者Content-Type字段的类型是application/json时,服务器会提前发送一次请求进行验证
        // 下面的的设置只本次验证的有效时间,即在该时间段内服务端可以不用进行验证
        ctx.set("Access-Control-Max-Age", 300);
    
        /*
        CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:
            Cache-Control、
            Content-Language、
            Content-Type、
            Expires、
            Last-Modified、
            Pragma。
        */
        // 需要获取其他字段时,使用Access-Control-Expose-Headers,
        // getResponseHeader('myData')可以返回我们所需的值
        //https://www.rails365.net/articles/cors-jin-jie-expose-headers-wu
        ctx.set("Access-Control-Expose-Headers", "myData");
        
        await next();
    })


    使用koa2-cors

    const Koa = require('koa');
    const cors = require('koa2-cors');
    const app = new Koa();
    
    app.use(cors());
    //或者
    app.use(
        cors({
            origin: function(ctx) { //设置允许来自指定域名请求
                if (ctx.url === '/test') {
                    return '*'; // 允许来自所有域名请求
                }
                return 'http://localhost:8080'; //只允许http://localhost:8080这个域名的请求
            },
            maxAge: 5, //指定本次预检请求的有效期,单位为秒。
            credentials: true, //是否允许发送Cookie
            allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], //设置所允许的HTTP请求方法
            allowHeaders: ['Content-Type', 'Authorization', 'Accept'], //设置服务器支持的所有头信息字段
            exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'] //设置获取其他自定义字段
        })
    );

    参看文章:koa解决跨域的方法 https://juejin.cn/post/6844904042196533255




    koa 图片上传详解

    参考文章:

    const logger = require("koa-logger")
    const serve = require("koa-static")
    const koaBody = require("koa-body")
    const Koa = require('koa')
    const fs = require("fs")
    const app = new Koa()
    const os = require("os")
    const path = require("path")
    
    app.use(logger())
    // 使用文件上传中间件
    app.use(koaBody({ multipart: true }))
    
    // 1.静态资源服务,指定对外提供访问的根目录
    app.use(serve(path.join(__dirname, '/public')));
    
    app.use(async function (ctx, next) {
        await next()
        if (ctx.body || !ctx.idempotent) return
        ctx.response.body = '<h1>Hello, koa2!</h1><p>not upload photo</p>'
    })
    
    // 2. 文件上传
    app.use(async function (ctx, next) {
        if ('POST' != ctx.method) return await next()
        // 获取图片源
        //  <input type="file" name="file" multiple>
        const file = ctx.request.files.file
        // 接收读出流
        const reader = fs.createReadStream(file.path)
        // 创建写入流 
        // 3. 指定图片路径文件名(即上传图片存储目录)
        const stream = fs.createWriteStream(path.join('public/images', file.name))
        // 用管道将读出流 "倒给" 输入流
        reader.pipe(stream)
        // 4.打印上传文件在服器上存储的相对路径 
        console.log('uploading %s -> %s', file.name, stream.path)
        // 5.重定向到基于根目录下的静态资源web访问路径,展示图片
        ctx.redirect(stream.path.substr(6).replace(/\\/g,'/'))
    })
    
    // 监听
    app.listen(3000, () => {
        console.log("listening port 3000");
    })


    其实,被这些文章坑了。用了koa-body后,直接这返回路径就可以了

    module.exports = {
      async add(ctx) {
        const file = ctx.request.files.file;
        const fileName = file.newFilename
        console.log(fileName)
        ctx.body = {
          data:{
            src:fileName,
            path:uploadUrl},
          code: 0,
          message: '上传成功'
        };
      }
    }




    koa-bodyparser

    如果要处理post请求,可以用 router.post('/path', async fn)。

    用post请求处理URL时,我们会遇到一个问题:post请求通常会发送一个表单,或者JSON,它作为request的body发送,但无论是Node.js提供的原始request对象,还是koa提供的request对象,都不提供解析request的body的功能!

    所以,我们需要引入另一个middleware来解析原始request请求,然后,把解析后的参数,绑定到 ctx.request.body 中。

    koa-bodyparser就是用来干这个活的。

    注意,由于middleware的顺序很重要,这个 koa-bodyparser 必须在router之前被注册到app对象上。如果 ctx.request.body 为 undefined,说明缺少middleware,或者middleware没有正确配置。

    koa2 使用 koa-body 代替 koa-bodyparser 和 koa-multer

    具体查看:http://www.ptbird.cn/koa-body.html

    koa-body 的基本参数

    参数名描述类型默认值
    patchNode将请求体打到原生 node.js 的ctx.reqBooleanfalse
    patchKoa将请求体打到 koa 的 ctx.request 中Booleantrue
    jsonLimitJSON 数据体的大小限制String / Integer1mb
    formLimit限制表单请求体的大小String / Integer56kb
    textLimit限制 text body 的大小String / Integer56kb
    encoding表单的默认编码Stringutf-8
    multipart是否支持 multipart-formdate 的表单Booleanfalse
    urlencoded是否支持 urlencoded 的表单Booleantrue
    text是否解析 text/plain 的表单Booleantrue
    json是否解析 json 请求体Booleantrue
    jsonStrict是否使用 json 严格模式,true 会只处理数组和对象Booleantrue
    formidable配置更多的关于 multipart 的选项Object{}
    onError错误处理Functionfunction(){}
    stict严格模式,启用后不会解析 GET, HEAD, DELETE 请求Booleantrue

    formidable 的相关配置参数

    参数名描述类型默认值
    maxFields限制字段的数量Integer1000
    maxFieldsSize限制字段的最大大小Integer2 * 1024 * 1024
    uploadDir文件上传的文件夹Stringos.tmpDir()
    keepExtensions保留原来的文件后缀Booleanfalse
    hash如果要计算文件的 hash,则可以选择 md5/sha1Stringfalse
    multipart是否支持多文件上传Booleantrue
    onFileBegin文件上传前的一些设置操作Functionfunction(name,file){}

    关于 onFileBegin 的更多信息可以查看:https://github.com/felixge/node-formidable#filebegin


    Koa 本地搭建 HTTPS 环境

    首先依赖于 https koa-sslify

    // 引入https 以及 koa-ssl
    const https = require('https')
    const sslify = require('koa-sslify').default
    
    if (Const.dev === 'product') {
      const https = require('https')
      const sslify = require('koa-sslify').default
      app.use(sslify())
      const options = {
        key: fs.readFileSync(__dirname + '/certificate/www.zhoulujun.co.key'),
        cert: fs.readFileSync(__dirname + '/certificate/www.zhoulujun.co_bundle.pem'),
      }
      https.createServer(options, app.callback()).listen(config.port, (err) => {
        if (err) {
          console.log('服务启动出错', err);
        } else {
          // db.connect();  // 数据库连接
          console.log('guessWord-server运行在' + config.port + '端口');
        }
      });
    } else {
      app.listen(config.port)
    }

    具体参看:koa2如何使用https https://blog.csdn.net/qq_30604453/article/details/100092986




    kao学习文档:https://chenshenhai.github.io/koa2-note/note/project/session.html


    参考文章:

    【Node】使用 koa2 实现一个简单JWT鉴权 https://juejin.cn/post/6921493257578872845

    vue+koa2+token登陆验证 https://segmentfault.com/a/1190000017379244

    koa解决跨域的方法 https://juejin.cn/post/6844904042196533255

    浅谈koa跨域问题及koa2-cors中间件 https://blog.csdn.net/qq_30868289/article/details/83657535

    koa2基于stream(流)进行文件上传和下载 https://www.cnblogs.com/tugenhua0707/p/10828869.html

    koa2+mysql实现简单的登陆注册 https://blog.csdn.net/itKingOne/article/details/89703097

    koa2+mysql+vue实现用户注册、登录、token验证 https://www.programminghunter.com/article/6015355220/

    koa2+mysql+vue实现用户注册、登录、token验证 https://www.shuzhiduo.com/A/E35pRajyzv/

    vue+koa2+token登陆验证  https://segmentfault.com/a/1190000017379244

    KOA 与 REST API https://www.jianshu.com/p/1a98f2b2e7bf

    koa 图片上传详解 https://learnku.com/articles/38437

    koa 搭建一个后台,实现crud, swagger,loger记录等 https://juejin.cn/post/6974294533030805534




    转载本站文章《一文解决koa后端开发》,
    请注明出处:https://www.zhoulujun.cn/html/webfront/server/koa/8800.html