一、系统环境
操作系统: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))
});