一. 项目的初始化
1. npm 初始化
npm init -y
//生成package.json文件:
2. git 初始化
git init
//生成'.git'隐藏文件夹, git 的本地仓库
3 . 创建 ReadMe.MD 文件
二. 搭建项目
1 安装 Koa 框架
npm install koa
2 编写最基本的 app
创建src/main.js
const Koa = require('koa')
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'hello world'
})
app.listen(3000, () => {
console.log('server is running on http://localhost:3000')
})
3 测试
在终端, 使用node src/main.js
三. 项目的基本优化
1 自动重启服务
安装 nodemon 工具
npm i nodemon -D
编写package.json脚本
"scripts": {
"dev": "nodemon ./src/main.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
执行npm run dev启动服务
2 读取配置文件
安装dotenv ,读取根目录中的.env文件, 将配置写到process.env中
创建 .env 文件
APP_PORT=8000
创建src/config/config.default.js
onst dotenv = require('dotenv')
dotenv.config()
// console.log(process.env.APP_PORT)
module.exports = process.env
改写main.js
const Koa = require('koa')
const { APP_PORT } = require('./config/config.default')
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'hello api'
})
app.listen(APP_PORT, () => {
console.log(`server is running on http://localhost:${APP_PORT}`)
})
四. 添加路由
路由: 根据不同的 URL, 调用对应处理函数
1 安装 koa-router
npm i koa-router
步骤:
-
导入包
-
实例化对象
-
编写路由
-
注册中间件
2 编写路由
创建src/router目录, 编写user.route.js
const Router = require('koa-router')
const router = new Router({ prefix: '/users' })
// GET /users/
router.get('/', (ctx, next) => {
ctx.body = 'hello users'
})
module.exports = router
3 改写 main.js
const Koa = require('koa')
const { APP_PORT } = require('./config/config.default')
const userRouter = require('./router/user.route')
const app = new Koa()
app.use(userRouter.routes())
app.listen(APP_PORT, () => {
console.log(`server is running on http://localhost:${APP_PORT}`)
})
五. 目录结构优化
1 将 http 服务和 app 业务拆分
创建src/app/index.js
const Koa = require('koa')
const userRouter = require('../router/user.route')
const app = new Koa()
app.use(userRouter.routes())
module.exports = app
改写main.js
const { APP_PORT } = require('./config/config.default')
const app = require('./app')
app.listen(APP_PORT, () => {
console.log(`server is running on http://localhost:${APP_PORT}` )
})
npm i koa-body
六.跨域
安装跨域
npm install koa-cors --save
在 main.js 引入和注册
const cors = require('koa-cors'); // 解决跨域
app.use(cors());
七. 连接数据库 nedb
1.安装中间键
npm install --save koa-bodyparser
改写main.js
const bodyParser = require('koa-bodyparser');
app.use(bodyParser());//(格式数据)
2.安装数据库模块
npm install nedb --save
八、使用 nedb( 增、删、改、查)
前端页面
增、删、改、查
接口规整
const Router = require('koa-router');
const {
register,
login,
changepassword,
unsubscribe,
} = require('../controller/user.controller');
const { userlist, deletedata } = require('../controller/userlist');
const router = new Router({ prefix: '/users' });
// 登录注册页
router.post('/register', register); //注册接口
router.post('/login', login); //登录接口
router.post('/changepassword', changepassword); //忘记密码接口
router.post('/unsubscribe', unsubscribe); //注销信息接口
module.exports = router;
var Datastore = require('nedb');
db = new Datastore({
filename: './src/dbdata/userlist.db', //文件从根目录出发,如果想要存在src文件里,那么就要./src/...,在这里读取文件时从外到里引入的文件,更加细致,也可以在这里直接写一个./xxx,依然从根目录出发,生成一个未创建的文件夹
autoload: true,
});
class userController {
//注册
async register(ctx, next) {
let registerData = await parseDataFun(ctx, 'register');
ctx.body = registerData;
}
//登录
async login(ctx, next) {
let loginData = await parseDataFun(ctx, 'login');
ctx.body = loginData;
}
//修改密码
async changepassword(ctx, next) {
let changepassword = await changeDataFun(ctx, 'changepassword');
ctx.body = changepassword;
}
//注销账户
async unsubscribe(ctx, next) {
let unsubscribedata = await removeDataFun(ctx, 'unsubscribeuser');
ctx.body = unsubscribedata;
}
}
//初始化传输给前端的数据
const returnData = {
code: 200,
data: {},
msg: '注册成功',
};
const phoneNumberreg =
/^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/;
// 解析上下文里node原生请求的POST参数(注册登录)
function parseDataFun(ctx, type) {
return new Promise((resolve, reject) => {
try {
let postdata = '';
ctx.req.addListener('data', (data) => {
postdata += data;
//格式化获取的数据
const jsdata = JSON.parse(postdata);
db.find({ phoneNumber: jsdata.phoneNumber }, function (err, doc) {
//如果数据库有
if (doc.length > 0) {
if (type == 'login') {
console.log(doc[0].password, jsdata.password, '登录成功');
if (doc[0].password == jsdata.password) {
returnData.code = 200;
returnData.msg = '登录成功';
resolve(returnData);
} else {
returnData.code = 500;
returnData.msg = '密码错误';
resolve(returnData);
}
} else if (type == 'register') {
returnData.code = 500;
returnData.msg = '手机号已存在';
resolve(returnData);
}
//如果数据库没有
} else if (doc.length == 0) {
if (type == 'login') {
returnData.code = 500;
returnData.msg = '手机号不存在,请注册';
resolve(returnData);
} else if (type == 'register') {
//判断确认密码和密码两个是否一致
//一致
if (
jsdata.checkpassword == jsdata.password &&
phoneNumberreg.test(jsdata.phoneNumber)
) {
// 提前将json文件转换成对象(因为只是用json的话,第二次或获取的数据将会是undefine),生成的数据先在后面生成_id=随机xxx,后转为json格式呈现,
db.insert(jsdata, function (inserterr, newDoc) {
console.log('插入成功:', newDoc);
});
returnData.code = 200;
returnData.msg = '注册成功';
resolve(returnData);
//不一致
} else if (jsdata.checkpassword != jsdata.password) {
returnData.code = 500;
returnData.msg = '两个密码不一致';
resolve(returnData);
//这么好则校验手机号(顺序有误)
} else if (!phoneNumberreg.test(jsdata.phoneNumber)) {
returnData.code = 500;
returnData.msg = '手机号错误';
resolve(returnData);
}
}
}
});
});
ctx.req.addListener('end', function () {
// resolve(returnData);
});
} catch (err) {
reject(err);
}
});
}
// 修改数据
//问题一:需要根据手机号,捕捉到对应原密码,然后修改原密码,但是目前没办法捕获到对应密码的手机号
// 解决1:在登录时,先直接保存对应的手机号
// 解决2:提示四个输入框:手机号,验证码,旧密码,新密码
function changeDataFun(ctx, type) {
return new Promise((resolve, reject) => {
try {
let postdata = '';
ctx.req.addListener('data', (data) => {
postdata += data;
// 1.格式化传输过来的数据
const JSONdata = JSON.parse(postdata);
console.log(JSONdata, 'postdata');
//检查数据库是否有该手机号
db.find({ phoneNumber: JSONdata.phoneNumber }, function (err, doc) {
console.log(doc, '');]
//如果没有
if (doc.length == 0) {
returnData.code = 500;
returnData.msg = '手机号错误';
resolve(returnData);
} else {
//如果有
//检查数据库的密码是否和拿到的密码相匹配
if (JSONdata.oldpassword != doc[0].password) {
returnData.code = 500;
returnData.msg = '旧密码不匹配';
resolve(returnData);
} else {
// 匹配 旧手机号和旧密码
const query = {
phoneNumber: doc[0].phoneNumber,
password: doc[0].password,
};
// 将匹配到的数据替换
const update = {
phoneNumber: doc[0].phoneNumber,
password: JSONdata.newpassword,
};
const options = { multi: true, returnUpdatedDocs: true };
db.update(
query,
update,
options,
(err, numAffected, affectedDocuments, upsert) => {
returnData.code = 200;
returnData.msg = '密码修改成功';
resolve(returnData);
console.log(
`修改了${numAffected}个文档,修改后的文档为:`,
affectedDocuments
);
}
);
}
}
});
});
ctx.req.addListener('end', function () {
// resolve(returnData);
});
} catch (err) {
reject(err);
}
});
}
// 注销账户
// 问题一:是否需要用户手机号,密码
function removeDataFun(ctx, type) {
return new Promise((resolve, reject) => {
try {
let removeData = '';
ctx.req.addListener('data', (data) => {
removeData += data;
// 1.格式化传输过来的数据
const jsremoveData = JSON.parse(removeData);
// 2.正则手机号判断
if (!phoneNumberreg.test(jsremoveData.phoneNumber)) {
//如果手机号格式错误
returnData.code = 500;
returnData.msg = '手机号错误';
resolve(returnData);
} else {
//如果手机号正确
// 3. 检查数据库是否有该手机号
db.find(
{ phoneNumber: jsremoveData.phoneNumber },
function (err, doc) {
console.log(doc, '');
//如果数据库没有
if (doc.length == 0) {
returnData.code = 500;
returnData.msg = '手机号不存在,请确认输入内容';
resolve(returnData);
} else {
// 如果有,删除一条数据库
db.remove(
{ phoneNumber: doc[0].phoneNumber },
{},
function (err, numRemoved) {
returnData.code = 200;
returnData.msg = '注销账户成功,请登录';
resolve(returnData);
}
);
}
}
);
}
});
ctx.req.addListener('end', function () {
resolve(returnData);
});
} catch (err) {
reject(err);
}
});
}
module.exports = new userController();
九.模块化 nedb 文 件
创建 src/dbconfig/index.js 文件
const Datastore = require('nedb');
userdb = new Datastore({
filename: './src/dbdata/userlist.db', //文件从根目录出发,如果想要存在src文件里,那么就要./src/...,在这里读取文件时从外到里引入的文件,更加细致,也可以在这里直接写一个./xxx,依然从根目录出发,生成一个未创建的文件夹
autoload: true,
noSort: true,//不排序
});
module.exports = { userdb };
使用
user.controller.js
// 登录注册接口
const { userdb } = require('../dbConfig/index');
userlist.js
// 用户数据列表接口
const { userdb } = require('../dbConfig/index');
十、用户列表
1.用户列表 get 接口
规整路由出口
const Router = require('koa-router');
const {
register,
login,
changepassword,
unsubscribe,
} = require('../controller/user.controller');
const { userlist, deletedata } = require('../controller/userlist');
const router = new Router({ prefix: '/users' });
// 登录注册页
router.post('/register', register); //注册接口
router.post('/login', login); //登录接口
router.post('/changepassword', changepassword); //忘记密码接口
router.post('/unsubscribe', unsubscribe); //注销信息接口
// 用户管理列表页
router.get('/userlist', userlist); //用户列表接口++++
router.post('/deletedata', deletedata); //删除列表接口+++
module.exports = router;
2.userlist.js
// 用户数据列表接口
// 引入数据库
const { userdb } = require('../dbConfig/index');
class userController {
async userlist(ctx, next) {
let userlistData = await getUserListFun(ctx, 'userlist');
ctx.body = userlistData;
}
async deletedata(ctx, next) {
let userdeleteData = await postdeleteFun(ctx, 'deletedata');
ctx.body = userdeleteData;
}
}
const getreturndata = {
code: 200,
data: {
list: [],
pageNum: 1,
pageSize: 10,
total: 0,
},
msg: 'OK',
};
function getUserListFun(ctx, type) {
return new Promise((resolve, reject) => {
try {
//1.初始化数据库总数
userdb.count({}, function (err, count) {
getreturndata.data.total = count;
});
//初始化传过来的值,如果没传,就使用默认的
getreturndata.data.pageNum = parseInt(ctx.request.query.pageNum) || 1; // 当前页码
getreturndata.data.pageSize = parseInt(ctx.request.query.pageSize) || 10; // 每页文档数量
const skipNum =
(getreturndata.data.pageNum - 1) * getreturndata.data.pageSize; // 要跳过的文档数量
const queryphoneNumber = parseInt(ctx.request.query.phoneNumber) || '';
console.log(queryphoneNumber, '');
//正则模糊查询
userdb
.find({ phoneNumber: new RegExp(queryphoneNumber, 'i') })
.skip(skipNum)
.limit(getreturndata.data.pageSize)
.exec(function (err, docs) {
getreturndata.data.list = docs;
//如果有在查询,就使用查询到的数据长度
if (queryphoneNumber) {
getreturndata.data.total = docs.length;
} else {
//否则使用数据库的总数据
userdb.count({}, function (err, count) {
getreturndata.data.total = count;
});
}
//将数据回馈给用户
resolve(getreturndata);
});
} catch (error) {
reject(error);
}
});
}
// 删除数据
function postdeleteFun(ctx, type) {
return new Promise((resolve, reject) => {
try {
let removeData = '';
ctx.req.addListener('data', (data) => {
removeData += data;
const jsremoveData = JSON.parse(removeData);
if (!jsremoveData.id) {
getreturndata.code = 500;
getreturndata.msg = 'id为必选参数';
getreturndata.data = {};
} else {
userdb.remove(
{ _id: jsremoveData.id },
{},
function (err, numRemoved) {
getreturndata.code = 200;
getreturndata.msg = '删除成功';
getreturndata.data = {};
resolve(getreturndata);
}
);
}
});
ctx.req.addListener('end', function () {
resolve(getreturndata);
});
} catch (err) {
reject(err);
}
});
}
module.exports = new userController();
nedb 坑:
1.一个 .db 不能引用两次
在 NeDB 中,同一个 db 文件只能被同一个实例引用,如果尝试在两个不同的文件或模块中引入同一个 db 文件,会出现数据混乱或文件被锁定的情况。
原因在于,NeDB 的读写操作需要对 db 文件进行独占式访问,因此当多个实例同时引用同一个 db 文件时,会出现数据互相覆盖或者文件被锁定的情况,导致应用程序无法正常运行。
为了避免这种情况发生,可以在一个模块中创建一个单例数据库实例并导出,其他模块只需要引用该实例即可。例如:
// db.js
const Datastore = require('nedb');
const db = new Datastore({ filename: 'db.db', autoload: true });
module.exports = db;
// module1.js
const db = require('./db');
// 使用 db 进行数据库操作
// module2.js
const db = require('./db');
// 使用 db 进行数据库操作