本文主要介绍使用express编写简易服务器,并与mysql数据库进行连接,编写对mysql数据进行操作的路由,最后经过ajax请求实现对前端页面数据的操作。
1.project项目初始化
本项目使用脚手架进行,
脚手架使用:
1. npm install express-generator -g (初始化一次即可)
2. express 项目名 --view=ejs ------使用express脚手架生成express项目架构
3. cd 项目名 ------进入到目标目录下
4. npm install ------添加项目的依赖包
5. npm start ------运行项目
初始化完毕,安装依赖包,之后再安装mysql2,本项目的package.json文件的依赖包如下:
{
"name": "project",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "nodemon ./bin/www"
},
"dependencies": {
"cookie-parser": "~1.4.4",
"cors": "^2.8.5",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"http-errors": "~1.6.3",
"morgan": "~1.9.1",
"mysql2": "^3.11.0"
}
}
本项目的目录文件如下所示:(内容过长,这里分开截图)
数据库配置如下:
sales表配置如下:
users表配置如下:
2.编写登录和注册功能的路由(routes文件以及routesControl文件)
(1)对文件dbConfig.json进行配置
{
"host": "127.0.0.1",
"user": "root",
"password": "你的密码",
"database": "my_db_01"
}
(2)编写loginControl.js文件
首先导入mysql2和dbConfig.json,使用es6进行封装。
本文件主要是根据数据库的数据来进行用户的登录校验以及注册校验。
主要代码以及注释如下:
const mysql = require("mysql2/promise")
const db = require("../dbData/dbConfig.json")
class login {
constructor(username, password) {
this.username = username;
this.password = password
}
connect() {
return mysql.createPool(db);
}
// 登录功能
async verify(username, password) {
try {
// 第一步:检查用户名是否存在
const userStr = 'SELECT * FROM users WHERE username=?';
let res = await this.connect().query(userStr, [username]);
if (res[0].length === 0) {
// 如果没有找到用户,返回用户名不存在的消息
return "用户名不存在";
}
// 如果有用户,检查密码
const user = res[0][0];
if (user.password == password) {
// 密码匹配,返回登录成功
return "登录成功";
} else {
// 密码不匹配
return "密码错误";
}
} catch (error) {
// 捕获并处理任何数据库连接或查询错误
console.error('数据库查询错误:', error);
return "数据库查询错误";
}
}
// 注册功能
async register(username, password) {
try {
// 第一步:检查用户名是否存在
let res = await this.connect().query('SELECT * FROM users WHERE username=?', [username]);
if (res[0].length != 0) {
return "用户名已存在";
}else{
// 第二步:若用户名不存在,则将注册用户添加至数据库的users表中
let res = await this.connect().query('insert into users (username,password) values (?,?)',[username,password])
return "注册成功"
}
} catch (error) {
// 捕获并处理任何数据库连接或查询错误
console.error('数据库查询错误:', error);
return "数据库查询错误";
}
}
}
module.exports = login
注意:这里使用到了mysql2/promise,为异步程序,所以使用了async和await进行处理。
(3)编写login.ejs和register.ejs文件
在编写login.js文件之前,先配置好ejs文件,为之后定义路由时提供渲染文件。
login.ejs文件代码如下:
<!DOCTYPE html>
<html>
<head>
<title>
<%= title %>
</title>
<link rel='stylesheet' href='/stylesheets/login.css' />
</head>
<body>
<header class="main">
<h1>
用户<%= title %>
</h1>
<% if (msg) {%>
<p id="msg">
<%= msg %>
</p>
<%}else{%>
<p>
请输入你的用户名和密码:
</p>
<%} %>
<form id="userManager" action="/login" method="post">
<input type="text" name="username" id="username" placeholder="用户名">
<input type="password" name="password" id="password" placeholder="密码">
<button id="btn">登录</button>
</form>
<a href="/register" id="register">注册</a>
</header>
</body>
</html>
register.ejs文件代码如下:
<!DOCTYPE html>
<html>
<head>
<title>
<%= title %>
</title>
<link rel='stylesheet' href='/stylesheets/login.css' />
</head>
<body>
<header class="main">
<h1>
用户<%= title %>
</h1>
<% if (msg) {%>
<p id="msg">
<%= msg %>
</p>
<%}else{%>
<p>
请输入你的用户名和密码:
</p>
<%} %>
<form id="userManager" action="/register" method="post">
<input type="text" name="username" id="username" placeholder="用户名">
<input type="password" name="password" id="password" placeholder="密码">
<button id="btn">注册</button>
</form>
<a href="/" id="register">登录</a>
</header>
</body>
</html>
css文件如下:
* {
margin: 0;
padding: 0;
text-decoration: none;
box-sizing: border-box;
}
body {
background: #f9f9f9;
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
h1{
height: 50px;
font-size: 40px;
text-align: center;
margin-top: 10px;
}
p{
color: #ccc;
text-align: center;
margin-bottom: 10px;
}
#msg{
color: red;
}
form{
flex-direction: column;
display: flex;
}
.main {
background: #fff;
margin: 0 auto;
right: 430px;
display: flex;
flex-direction: column;
width: 500px;
height: 400px;
}
.main #username,
.main #password {
width: 440px;
height: 80px;
background: #f9f9f9;
font-size: 25px;
margin-left: 30px;
margin-bottom: 20px;
border: 1px solid #ccc;
border-radius: 3px;
font-weight: 500;
padding-left: 10px;
}
.main #btn{
width: 445px;
height: 78px;
margin-left: 30px;
line-height: 78px;
font-size: 28px;
background: #0fad07;
color: #333;
}
.main #register{
height: 20px;
margin-top: 10px;
margin-left: 20px;
color: #333;
}
#register:hover{
color: crimson;
}
(4)编写login.js文件
var express = require('express');
const login = require("../routesControl/loginControl.js")
var router = express.Router();
let msg = '';
/* GET login and register page. */
router.get('/', function (req, res) {
res.render('login', { title: '登录', msg: msg });
});
router.post('/login', (req, res) => {
new login().verify(req.body.username, req.body.password).then(results => {
msg = results;
if (msg == "登录成功") {
// 登录成功返回主界面index
return res.redirect('/html/index.html')
}
return res.render('login', { title: '登录', msg: msg });
})
});
router.get('/register', (req, res) => {
res.render('register', { title: '注册', msg: msg })
})
router.post('/register', (req, res) => {
new login().register(req.body.username, req.body.password).then(results => {
msg = results;
if (msg == '注册成功') {
// 注册成功返回登录界面
return res.redirect("/")
}
return res.render('register', { title: '注册', msg: msg });
})
})
module.exports = router;
(5)效果演示
npm start 启动服务
启动服务以及登录注册功能都可以正确执行,可以自行演示。
3.编写主界面功能模块
编辑index文件
(1)编写前端html页面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>销售管理系统</title>
<link rel="stylesheet" href="../stylesheets/index.css">
</head>
<body>
<div class="container">
<header>
<h1>销售管理系统</h1>
<nav>
<ul>
<li><a href="#home">首页</a></li>
<li><a href="#products">添加商品信息</a></li>
<li><a href="#sales">销售数据</a></li>
</ul>
</nav>
</header>
<section id="products">
<h2>添加商品信息</h2>
<div class="product-list">
<div class="product-item">
<form id="add-product" action="/index/add" method="post">
商品名:<input type="text" name="product" placeholder="商品名" id="product"><br>
单 价:<input type="text" name="price" placeholder="商品单价" id="price"><br>
销售额:<input type="text" name="total" placeholder="商品销售数" id="total"><br>
<input type="button" id="btn" value="提交"></input>
</form>
</div>
<div class="product-item">
<form id="update-product" action="/update" method="post">
商品名:<input type="text" name="product" placeholder="商品名" id="product-update"><br>
单 价:<input type="text" name="price" placeholder="商品单价" id="price-update"><br>
销售额:<input type="text" name="total" placeholder="商品销售数" id="total-update"><br>
<button type="button" id="btn-update">修改</button>
</form>
</div>
<!-- 更多 -->
</div>
</section>
<section id="sales">
<h2>销售数据</h2>
<ul>
<li><span>名称</span><span>单价(元)</span><span>销售额(个/件)</span></li>
</ul>
<ul id="sales-list">
</ul>
</section>
<footer>
<p>销售管理系统</p>
</footer>
</div>
</body>
<script src="../javascripts/JQuery/jquery-3.6.1.min.js"></script>
<script type="module">
import { Index } from "../javascripts/index.js"
let index = new Index(".container")
</script>
</html>
(2)编写indexControl.js
实现响应前端Ajax的请求的后端操作数据库的功能.
const mysql = require("mysql2/promise")
const db = require("../dbData/dbConfig.json")
class Sales {
constructor(id, product, price, total) {
this.id = id;
this.product = product;
this.price = price;
this.total = total;
}
// 连接数据库
connect() {
return mysql.createPool(db);
}
// 获取所有数据
async getList() {
try {
let results = await this.connect().query("select * from sales order by id desc")
return results[0]
} catch (error) {
console.log(error);
}
}
// 修改根据id
async update(id, product, price, total) {
try {
await this.connect().query('UPDATE sales SET product=?, price=?, total=? WHERE id=?', [product, price, total, id])
} catch (error) {
console.log(error);
throw error; // 重新抛出错误以便在路由处理器中捕获
}
}
// 增加
async add(product, price, total) {
try {
await this.connect().query("insert into sales(product,price,total) values (?,?,?)", [product, price, total])
} catch (error) {
console.log(error);
}
}
// 删除根据id
async del(id) {
try {
await this.connect().query("delete from sales where id=?", [id])
} catch (error) {
console.log(error);
}
}
}
module.exports = Sales
(3)编写index.js
该模块主要实现响应Ajax的请求url,并根据请求方式来调用indexControl.js的方法
var express = require('express');
var router = express.Router();
const sales = require("../routesControl/indexControl")
// 获取所有数据
router.get('/', (req, res) => {
new sales().getList().then(results => res.send(results))
})
// 修改
router.patch('/update/:id', async (req, res) => {
try {
const { id, product, price, total } = req.body;
await new sales().update(id, product, parseFloat(price), parseInt(total));
res.send('Patch success');
} catch (error) {
res.status(500).send('Server error');
}
})
// 增加
router.post('/add',async (req,res)=>{
await new sales().add(req.body.product,req.body.price,req.body.total)
res.send('Post success')
})
// 删除
router.delete('/del/:id',async (req,res)=>{
await new sales().del(req.params.id)
res.send('delete success')
})
module.exports = router;
(4)编写用于前端请求Ajax的index.js文件
export class Index {
constructor(ele) {
this.ele = $(ele)
this.flag = false;
this.editbtn = false
this.proList = []
this.init()
}
init() {
$("#btn").on("click", (e) => {
e.preventDefault(); // 阻止表单的默认提交行为
this.add({
product: $('#product').val(),
price: $('#price').val(),
total: $('#total').val()
})
})
this.getList()
}
async getList() {
let res = await $.get("http://127.0.0.1/index")
this.proList = res
this.render(res)
}
// 渲染数据
render(data) {
let res = data.map(item => `<li id="${item.id}">
<span>${item.product}</span><span>${item.price}</span><span>${item.total}</span>
<button type="button" id="del">删除</button>
<button type="button" id="edit">编辑</button>
</li>`)
$('#sales-list').html(res)
this.getButton()
}
// 获取按钮
getButton() {
if (!this.flag) {
$('#sales-list').on("click", (e) => {
// 利用事件委派的原理
if ($(e.target).attr('id') == 'edit') {
this.editbtn = false
this.editBackValue(+$(e.target).parent().attr('id'))
} else {
this.del(+$(e.target).parent().attr('id'))
}
})
this.flag = true
}
}
// 将目标编写数据回显
editBackValue(id) {
let target = this.proList.find(item => item.id == id)
$('#product-update').val(target.product)
$('#price-update').val(target.price)
$('#total-update').val(target.total)
let a = 0
$('#btn-update').on('click', (e) => {
e.preventDefault();
if (a == 0) {
this.edit({
id: target.id,
product: $('#product-update').val(),
price: $('#price-update').val(),
total: $('#total-update').val()
})
a++;
}
})
}
// 编辑
async edit(obj) {
await $.ajax({
url: `/index/update/${obj.id}`,
method: "PATCH",
headers: { "content-type": "application/json" },
data: JSON.stringify(obj),
})
this.getList()
// 清空输入框
// location.reload()
$('#product-update').val('')
$('#price-update').val('')
$('#total-update').val('')
}
// 删除
async del(id) {
await $.ajax({ url: `/index/del/${id}`, method: "DELETE" })
this.getList()
}
// 增加
async add(obj) {
await $.ajax({
url: '/index/add',
method: "POST",
data: JSON.stringify(obj),
headers: { "content-type": "application/json" }
})
this.getList()
$('#product').val(''),
$('#price').val(''),
$('#total').val('')
}
}
4.app.js文件的编写
主要就是将之前编写的路由导入进来,以及跨域的解决(使用cors第三方包)。
var createError = require('http-errors');
var express = require('express');
var path = require('path');
const cors = require("cors")
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var loginRouter = require('./routes/login');
var indexRouter = require('./routes/index')
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
// 解决跨域
app.use(cors())
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', loginRouter);
app.use('/index',indexRouter)
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
5.主界面的效果
点击编辑时会将数据回显至右侧的表单。
测试功能都已成功。
6.可以进行优化的地方
1.本系统可以在不修改时也进行表单的输入,可以进行一个按钮的判断,默认将右侧表单禁用,只有当用户执行编辑时才可以使用。
2.进行输入的数据的校验。
3.当数据渲染失败的时候,客户端页面没有错误提示,只有在后端控制台可以看见错误。