一步一步教你完成Koa2接口開發

1、系統環境

操做系統:windows10 64bitvue

node:v10.15.3node

npm:6.4.1mysql

koa:2.7.0web

mariadb:10.2.14sql

2、項目初始化

  1. 進入要初始化的項目目錄,執行命令

    npm init複製代碼

  2. 安裝koa核心依賴庫

    npm install koa複製代碼

  3. 開啓服務,在項目根目錄下建立app.js文件,代碼以下

    /*項目依賴*/
    const Koa = require('koa');
    const http = require('http');
    /*應用實例*/
    const app = new Koa();
    /*web服務*/
    http.createServer(app.callback())
        .listen(3000)
        .on('listening', function () {
            console.log(`服務已開啓,端口:3000`)
        });複製代碼

  4. 進入項目目錄,執行命令,查看結果,如圖:


3、數據庫(mysql)準備

  1. 解壓數據庫文件到安裝目錄,此處目錄爲D:\db\mariadb\10.2.14(已安裝好mysql的能夠根據狀況忽略下面幾步)
  2. 添加環境變量,如圖(個人電腦->右鍵->屬性->高級系統設置->系統變量->選擇Path->新建)其餘環境請自行百度
  3. 建立install.bat文件(用來安裝Mariadb服務),文件內容以下:

    ::指定建立服務的程序
    
    @set mysql_service="D:\db\mariadb\10.2.14\bin\mysqld.exe"
    
    ::設置服務名
    
    @set service_name="MariaDB.10"
    
    
    ::開始安裝Mariadb服務
    
    %mysql_service% --install %service_name% --defaults-file="D:\db\mariadb\10.2.14\my-medium.ini"
    
    
    pause複製代碼

  4. 建立uninstall.bat文件(用來卸載Mariadb服務,若是服務處在開啓狀態,須要先中止服務),文件內容以下:

    @set mysql_service="D:\db\mariadb\10.2.14\bin\mysqld"
    @set service_name="MariaDB.10"
    
    :: 卸載服務
    %mysql_service% --remove %service_name%
    
    pause複製代碼

  5. 建立start.bat文件(用來開啓服務),服務名稱須要與安裝的名稱同樣

    net start MariaDB.10複製代碼

  6. 建立stop.bat文件(用來關閉服務),服務名稱須要與安裝的名稱同樣

    net stop MariaDB.10複製代碼

  7. 建立完成以後,先執行install.bat文件(只須要執行一次,以後無論重啓與否只要沒卸載改服務也不須要再次執行),再執行start.bat文件
  8. 打開mysql鏈接工具(此處使用SQLyog,其餘工具相似),輸入配置信息,輸入完成以後點擊測試鏈接(默認root密碼爲空,鏈接以後可經過管理工具添加帳戶或者修改root密碼):
  9. 建立數據庫及表(任意表都行),如圖:(account表示帳戶表,chia表示中國省市區的表)

4、接口開發

項目結構


  1. 添加dao文件夾 -- 數據訪問層,用來鏈接數據庫,經過sql語句返回數據供service層使用
  2. 添加service文件夾 -- 業務服務層,執行業務邏輯而且經過dao層獲取數據供controller層使用
  3. 添加controller文件夾 -- 控制器層,編寫接口,經過service層獲取數據供接口返回
  4. 添加public/upload文件夾 -- 用來保存上傳的文件內容
  5. 安裝依賴

    // koa-json -- get提交數據的中間件
    // koa-bodyparser -- post提交數據的中間件
    // koa-body -- 文件上傳的中間件
    // koa-router -- 路由中間件(接口地址)
    // mysql -- mysql數據庫鏈接中間件
    npm install koa-json koa-bodyparser koa-body koa-router mysql複製代碼

  6. 添加config.js文件,具體代碼以下:

    module.exports = {
        // 服務器配置
        SERVICE:{
            HOST:"",
            PORT:"3000"
        },
        // 數據庫鏈接配置
        DATABASE:{
            HOST: 'localhost',
            USER: 'root',
            PASSWORD: '123456',
            DATABASE: 'test',
            CONNECTION_LIMIT: 10
        },
        // 接口地址配置
        API:{
            // 項目接口前綴
            PROJECT_INTERFACE_PREFIX:'/testApi',
            // 後臺接口前綴
            ADMIN_INTERFACE_PREFIX: '/adminApi',
            // 移動端接口前綴
            MOBILE_INTERFACE_PREFIX:'/mobileApi'
        },
        // 路徑配置
        PATH:{
            UPLOAD_PATH:"public/upload"
        },
        // 限制條件配置
        LIMIT:{
            UPLOAD_IMG_SIZE:200*1024*1024
        }
    
    };複製代碼

  7. 添加通用方法文件utilitys.js,具體代碼以下

    const mysql = require('mysql');
    const fs = require("fs");
    const path = require("path");
    const config = require("./config");
    const db = config.DATABASE;
    
    
    const pool = mysql.createPool({
        host: db.HOST,
        user: db.USER,
        password: db.PASSWORD,
        database: db.DATABASE,
        connectionLimit: db.CONNECTION_LIMIT
    });
    const utils = {
        // 數據庫查詢方法
        query: (sql, values) => {
            return new Promise((resolve, reject) => {
                pool.getConnection((err, connection) => {
                    if (err) {
                        return reject(err);
                    } else {
                        connection.query(sql, values, (err, rows) => {
                            connection.release();
                            if (err) {
                                return reject(err)
                            } else {
                                return resolve(rows);
                            }
                        })
                    }
                })
            });
        },
        // 錯誤JSON
        resultErrorJson:(code=-1,message="失敗",data={})=>{
            return {
                code:code,
                data:data,
                message:message
            }
        },
        // 成功JSON
        resultSuccessJson:(code=0,message="成功",data={})=>{
            return {
                code:code,
                data:data,
                message:message
            }
        },
        // 切割文件後綴名
        splitFileName:(text) =>{
            let index = text.lastIndexOf(".");
            return {
                name:text.substring(0,index),
                suffix:text.substring(index+1)
            };
        },
        // 遞歸建立目錄
        mkdirsSync: (dirname)=>{
            if (fs.existsSync(dirname)) {
                return true;
            } else {
                if (utils.mkdirsSync(path.dirname(dirname))) {
                    fs.mkdirSync(dirname);
                    return true;
                }
            }
        }
    };
    module.exports = utils;複製代碼

  8. 添加路由文件routes.js,具體代碼以下:(讀取controller文件夾中的文件)

    /*依賴包*/
    const path = require("path");
    const fs = require("fs");
    const router =  require('koa-router')();
    /*配置文件*/
    const config = require('./config.js');
    const projectApiPrefix = config.API.PROJECT_INTERFACE_PREFIX;
    // 讀取controller文件夾中的文件
    fs.readdirSync(path.join(__dirname, 'controller')).forEach((file) => {
        if (~file.indexOf('.js')) {
            let controller = require(path.join(__dirname, 'controller', file));
            // 爲接口設置通用前綴
            router.use(`${projectApiPrefix}`, controller.routes(), controller.allowedMethods());
        }
    });
    module.exports = router;複製代碼

  9. 添加constants.js文件,具體代碼以下:(暫無內容,用來保存常量非配置信息)

    module.exports = {}複製代碼

  10. dao層添加china.js,login.js,uploadfile.js文件,具體代碼以下:

    // china.js
    const tableName = "china";
    module.exports = {
        getAllData:(ctx)=>{
            return ctx.execSql(`select * from ${tableName}`);
        }
    };複製代碼

    // login.js
    const tableName = "account";
    module.exports = {
        adminLogin:(ctx,postData)=>{
            return ctx.execSql(`select * from ${tableName} where phone = ? and password = ?`, [postData.phone, postData.psd]);
        }
    };複製代碼

    // uploadfile.js
    const tableName = "upload_file";
    module.exports = {
        uploadFile:(ctx,postData)=>{
            return ctx.execSql(`insert into ${tableName} values (?,?,?)`, [null,postData.url, postData.fileName]);
        },
        getAllFiles:(ctx)=>{
            return ctx.execSql(`select * from ${tableName}`);
        }
    };
    複製代碼
  11. service文件夾中添加文件china.js,login.js,uploadfile.js,具體代碼以下:

    // china.js
    const chinaDao = require("../dao/china");
    const util = require("../utilitys");
    /**
     * 後臺獲取全部城市接口邏輯
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.getAllCity = async(ctx) => {
        try {
            let result = await chinaDao.getAllData(ctx);
            ctx.body = util.resultSuccessJson(undefined,undefined,result);    } catch (err) {
            ctx.body = util.resultErrorJson(undefined,err,{});    }
    };
    複製代碼

    // login.js
    const loginDao = require("../dao/login");
    const util = require("../utilitys");
    /**
     * 後臺登陸接口業務邏輯
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.adminLogin = async(ctx) => {
        let phone = ctx.request.body.phone || '';
        let psd = ctx.request.body.password || '';
        if (!phone || !psd) {
            ctx.body = util.resultErrorJson(undefined,'手機號碼或密碼不能爲空',{});        return false;
        }
        try {
            let result = await loginDao.adminLogin(ctx,{phone,psd});
            if (result.length > 0) {
                ctx.body = util.resultSuccessJson(undefined,undefined,result);        } else {
                ctx.body = util.resultSuccessJson(undefined,'帳號或密碼錯誤',{})        }
        } catch (err) {
            ctx.body = util.resultErrorJson(undefined,err,{});    }
    };
    /**
     * 後臺登出接口業務邏輯
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.adminLoginOut = async(ctx) => {
        let phone = ctx.request.body.phone || '';
        let psd = ctx.request.body.password || '';
        if (!phone || !psd) {
            ctx.body = {
                success: false,
                message: '手機號碼或密碼不能爲空'
            };
            return false;
        }
        try {
            let result = await ctx.execSql(`select * from account where phone = ? and password = ?`, [phone, psd]);
            if (result.length > 0) {
                ctx.body = {
                    success: true,
                    userID: result[0].id,
                    message: ''
                };
            } else {
                ctx.body = {
                    success: false,
                    userID: 0,
                    message: '帳號或密碼錯誤'
                };
            }
        } catch (err) {
            ctx.body = {
                success: false,
                userID: 0,
                message: err
            };
        }
    }
    
    /**
     * 移動端登陸接口業務邏輯
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.mobileLogin = async(ctx) => {
        let phone = ctx.request.body.phone || '';
        let psd = ctx.request.body.password || '';
        if (!phone || !psd) {
            ctx.body = {
                success: false,
                message: '手機號碼或密碼不能爲空'
            };
            return false;
        }
        try {
            let result = await ctx.execSql(`select * from account where phone = ? and password = ?`, [phone, psd]);
            if (result.length > 0) {
                ctx.body = {
                    success: true,
                    userID: result[0].id,
                    message: ''
                };
            } else {
                ctx.body = {
                    success: false,
                    userID: 0,
                    message: '帳號或密碼錯誤'
                };
            }
        } catch (err) {
            ctx.body = {
                success: false,
                userID: 0,
                message: err
            };
        }
    }
    /**
     * 移動端登出接口業務邏輯
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.mobileLoginOut = async(ctx) => {
        let phone = ctx.request.body.phone || '';
        let psd = ctx.request.body.password || '';
        if (!phone || !psd) {
            ctx.body = {
                success: false,
                message: '手機號碼或密碼不能爲空'
            };
            return false;
        }
        try {
            let result = await ctx.execSql(`select * from account where phone = ? and password = ?`, [phone, psd]);
            if (result.length > 0) {
                ctx.body = {
                    success: true,
                    userID: result[0].id,
                    message: ''
                };
            } else {
                ctx.body = {
                    success: false,
                    userID: 0,
                    message: '帳號或密碼錯誤'
                };
            }
        } catch (err) {
            ctx.body = {
                success: false,
                userID: 0,
                message: err
            };
        }
    }
    複製代碼

    // uploadfile.js
    const path = require("path");
    const fs = require("fs");
    const uploadFileDao = require("../dao/uploadfile");
    const util = require("../utilitys");
    const config = require("../config");
    const uploadPath = config.PATH.UPLOAD_PATH;
    /**
     * 單個上傳文件接口
     * @param ctx
     * @returns {Promise<boolean>}
     * @constructor
     */
    exports.uploadFile = async(ctx) => {
        // 文件
        const file = ctx.request.files.file; // 獲取上傳文件
        // 獲取文件後綴名
        const fileName = util.splitFileName(file.name).name;
        // 獲取文件後綴名
        const suffix = util.splitFileName(file.name).suffix;
        // 新生成的文件名稱
        const newFileName =`${new Date().getTime()}.${suffix}`;
        // 文件上傳分類
        const category = ctx.request.body.category || '';
        // 建立可讀流
        const reader = fs.createReadStream(file.path);
        // 設置上傳文件路徑及名稱
        const filePath = path.join(__dirname, '..',uploadPath,category,`/${newFileName}`);
        // 服務器相對路徑
        const serviceUrl = `${uploadPath}/${category}/${newFileName}`;
        // 遞歸建立目錄 同步方法
        util.mkdirsSync(path.join(__dirname, '..',uploadPath,category));
        // 若是文件夾存在,則建立可寫流
        const upStream = fs.createWriteStream(filePath);
        try {
            // 可讀流經過管道寫入可寫流
            reader.pipe(upStream);
            let result = await uploadFileDao.uploadFile(ctx,{url:serviceUrl,fileName});
            ctx.body = util.resultSuccessJson(undefined,"上傳成功",{});    } catch (err) {
            ctx.body = util.resultErrorJson(undefined,err.message||"error",{});    }
    };
    /**
     * 獲取全部文件信息
     * @param ctx
     * @returns {Promise<void>}
     */
    exports.getAllFiles = async(ctx)=>{
        try {
            let result = await uploadFileDao.getAllFiles(ctx);
            ctx.body = util.resultSuccessJson(undefined,undefined,result);    } catch (err) {
            ctx.body = util.resultErrorJson(undefined,err,{});    }
    }
    複製代碼

  12. controller層添加china.js,login.js,uploadfile.js,具體代碼以下:

    // china.js
    /*路由*/
    const router = require('koa-router')();
    /*接口服務*/
    const chinaService = require('../service/china.js');
    // 通用獲取全部省市區接口
    router.get(`/getAllCity`, chinaService.getAllCity);
    
    module.exports = router;
    複製代碼

    // login.js
    /*路由*/
    const router = require('koa-router')();
    /*接口服務*/
    const loginService = require('../service/login.js');
    /*配置屬性*/
    const config = require('../config.js');
    const adminPrefix = config.API.ADMIN_INTERFACE_PREFIX;
    const mobilePrefix = config.API.MOBILE_INTERFACE_PREFIX;
    // 後臺-使用登陸控制器實現登陸接口
    router.post(`${adminPrefix}/login`, loginService.adminLogin);
    // 後臺-使用登陸控制器實現登出接口
    router.post(`${adminPrefix}/login/out`, loginService.adminLoginOut);
    
    // 移動端-使用登陸控制器實現登陸接口
    router.post(`${mobilePrefix}/login`, loginService.mobileLogin);
    // 移動端-使用登陸控制器實現登出接口
    router.post(`${mobilePrefix}/login/out`, loginService.mobileLoginOut);
    
    module.exports = router;
    複製代碼

    // uploadfile.js
    /*路由*/
    const router = require('koa-router')();
    /*接口服務*/
    const chinaService = require('../service/uploadfile.js');
    // 通用上傳文件接口
    router.post(`/uploadfile`, chinaService.uploadFile);
    // 獲取全部文件信息接口
    router.get(`/getAllFiles`, chinaService.getAllFiles);
    
    module.exports = router;複製代碼

5、修改app.js啓動項目

app.js代碼以下數據庫

/*項目依賴*/
const Koa = require('koa');
const koaJson = require('koa-json');    // get提交數據的中間件
const bodyParser = require('koa-bodyparser'); // post提交數據中間件
const koaBody = require('koa-body');   // 文件上傳
const http = require('http');
const routes = require('./routes')
/*工具方法*/
const util = require('./utilitys.js');
/*配置文件*/
const config = require("./config.js");
/*應用實例*/
const app = new Koa();

app.use(bodyParser());
app.use(koaJson());
app.use(koaBody({
    multipart: true,
    formidable: {
        maxFileSize: config.LIMIT.UPLOAD_IMG_SIZE    // 設置上傳文件大小最大限制,默認2M
    }
}));
app.use(async (ctx, next) => {
    ctx.execSql = util.query;
    await next();
});
/*配置屬性*/
const {SERVICE} = config;
/*路由配置*/
app.use(routes.routes());
/*web服務*/
http.createServer(app.callback())
    .listen(SERVICE.PORT)
    .on('listening', function () {
        console.log(`服務已開啓,端口:${SERVICE.PORT}`)
    });複製代碼

修改package.json文件添加命令,代碼以下:npm

"scripts": {
    "start": "node app.js",
    "debugger-start": "node --inspect-brk app.js"
  },複製代碼

執行命令json

npm start複製代碼

結果以下:windows



6、後臺接口跨域訪問其餘接口

const proxy = require('koa-server-http-proxy');
// 開啓代理
const proxyTable = {
    '/shsApi': {
        target: 'http://www.91vue.com:8081',
        pathRewrite: { '^/shsApi': 'shsApi/' },
        changeOrigin: true
    },
    // '/api': {
    //     target: 'https://news-at.zhihu.com',
    //     pathRewrite: { '^/api': 'api/4/' },
    //     changeOrigin: true
    // }
};
Object.keys(proxyTable).forEach((context) => {
    var options = proxyTable[context];
    app.use(proxy(context, options))
});複製代碼
相關文章
相關標籤/搜索