一、系统环境
操作系统:windows10 64bit
node:v10.15.3
npm:6.4.1
koa:2.7.0
mariadb:10.2.14
二、项目初始化
- 进入要初始化的项目目录,执行命令
npm init复制代码
- 安装koa核心依赖库
npm install koa复制代码
- 开启服务,在项目根目录下创建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`) });复制代码
- 进入项目目录,执行命令,查看结果,如图:
三、数据库(mysql)准备
- 解压数据库文件到安装目录,此处目录为D:\db\mariadb\10.2.14(已安装好mysql的可以根据情况忽略下面几步)
- 添加环境变量,如图(我的电脑->右键->属性->高级系统设置->系统变量->选择Path->新建)其他环境请自行百度
- 创建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复制代码
- 创建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复制代码
- 创建start.bat文件(用来开启服务),服务名称需要与安装的名称一样
net start MariaDB.10复制代码
- 创建stop.bat文件(用来关闭服务),服务名称需要与安装的名称一样
net stop MariaDB.10复制代码
- 创建完成之后,先执行install.bat文件(只需要执行一次,之后不管重启与否只要没卸载改服务也不需要再次执行),再执行start.bat文件
- 打开mysql连接工具(此处使用SQLyog,其他工具类似),输入配置信息,输入完成之后点击测试连接(默认root密码为空,连接之后可通过管理工具添加账户或者修改root密码):
- 创建数据库及表(任意表都行),如图:(account表示账户表,chia表示中国省市区的表)
四、接口开发
项目结构
- 添加dao文件夹 -- 数据访问层,用来连接数据库,通过sql语句返回数据供service层使用
- 添加service文件夹 -- 业务服务层,执行业务逻辑并且通过dao层获取数据供controller层使用
- 添加controller文件夹 -- 控制器层,编写接口,通过service层获取数据供接口返回
- 添加public/upload文件夹 -- 用来保存上传的文件内容
- 安装依赖
// koa-json -- get提交数据的中间件 // koa-bodyparser -- post提交数据的中间件 // koa-body -- 文件上传的中间件 // koa-router -- 路由中间件(接口地址) // mysql -- mysql数据库连接中间件 npm install koa-json koa-bodyparser koa-body koa-router mysql复制代码
- 添加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 } };复制代码
- 添加通用方法文件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;复制代码
- 添加路由文件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;复制代码
- 添加constants.js文件,具体代码如下:(暂无内容,用来保存常量非配置信息)
module.exports = {}复制代码
- 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}`); } }; 复制代码
- 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,{}); } } 复制代码
- 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;复制代码
五、修改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文件添加命令,代码如下:
"scripts": {
"start": "node app.js",
"debugger-start": "node --inspect-brk app.js"
},复制代码
执行命令
npm start复制代码
结果如下:
六、后台接口跨域访问其他接口
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))
});复制代码