1.单文件模式
web刚开始发展的时候,没有专门的前端工程师, web开发人员经常是服务端,客户端开发一把梭。那时候,不管是业务逻辑,还是界面交互,都喜欢把某个业务的前后端代码放在同一个文件。例如,jsp,asp,php均是这种模式。
例如,下面的订单管理页面,主要负责订单的增删查改
<?php
// 连接数据库
try {$pdo = new PDO('mysql:host=localhost;dbname=php', 'root', '123456');$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {die("数据库连接失败:" . $e->getMessage());
}// 获取所有订单
function getAllOrders()
{global $pdo;$sql = "SELECT * FROM orders";$stmt = $pdo->query($sql);return $stmt->fetchAll(PDO::FETCH_ASSOC);
}// 添加订单
function addOrder($customerName, $productName, $quantity, $totalPrice)
{global $pdo;$sql = "INSERT INTO orders (customer_name, product_name, quantity, total_price) VALUES (:customer_name, :product_name, :quantity, :total_price)";$stmt = $pdo->prepare($sql);$stmt->bindParam(':customer_name', $customerName);$stmt->bindParam(':product_name', $productName);$stmt->bindParam(':quantity', $quantity);$stmt->bindParam(':total_price', $totalPrice);$stmt->execute();
}// 更新订单
function updateOrder($id, $customerName, $productName, $quantity, $totalPrice)
{global $pdo;$sql = "UPDATE orders SET customer_name = :customer_name, product_name = :product_name, quantity = :quantity, total_price = :total_price WHERE id = :id";$stmt = $pdo->prepare($sql);$stmt->bindParam(':id', $id);$stmt->bindParam(':customer_name', $customerName);$stmt->bindParam(':product_name', $productName);$stmt->bindParam(':quantity', $quantity);$stmt->bindParam(':total_price', $totalPrice);$stmt->execute();
}// 删除订单
function deleteOrder($id)
{global $pdo;$sql = "DELETE FROM orders WHERE id = :id";$stmt = $pdo->prepare($sql);$stmt->bindParam(':id', $id);$stmt->execute();
}// 根据请求类型进行处理
if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (isset($_POST['action']) && $_POST['action'] === 'add') {addOrder($_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'update') {updateOrder($_POST['id'], $_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'delete') {deleteOrder($_POST['id']);}header('Location: index.php');exit;
}$orders = getAllOrders();
?>
<!DOCTYPE html>
<html><head><title>订单管理系统</title><style>body {font-family: Arial, sans-serif;}.container {max-width: 800px;margin: 0 auto;padding: 20px;}table {width: 100%;border-collapse: collapse;}th,td {padding: 8px;border: 1px solid #ddd;}th {background-color: #f2f2f2;}.btn {padding: 6px 12px;border: none;border-radius: 4px;cursor: pointer;}.btn-primary {background-color: #007bff;color: #fff;}.btn-danger {background-color: #dc3545;color: #fff;}.modal {display: none;position: fixed;z-index: 1000;top: 0;left: 0;width: 100%;height: 100%;overflow: auto;background-color: rgba(0, 0, 0, 0.4);}.modal-content {background-color: #fff;margin: 10% auto;padding: 20px;border: 1px solid #888;width: 80%;}.modal-header,.modal-footer {padding: 10px;}.modal-title {margin: 0;}.form-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;}input[type="text"],input[type="number"] {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;}</style>
</head><body><div class="container"><h1>订单管理系统</h1><table><thead><tr><th>订单编号</th><th>客户名称</th><th>产品名称</th><th>数量</th><th>总价</th><th>操作</th></tr></thead><tbody><?php foreach ($orders as $order): ?><tr><td><?php echo $order['id']; ?></td><td><?php echo $order['customer_name']; ?></td><td><?php echo $order['product_name']; ?></td><td><?php echo $order['quantity']; ?></td><td><?php echo $order['total_price']; ?></td><td><button class="btn btn-primary btn-sm edit-btn" data-id="<?php echo $order['id']; ?>" data-customer_name="<?php echo $order['customer_name']; ?>" data-product_name="<?php echo $order['product_name']; ?>" data-quantity="<?php echo $order['quantity']; ?>" data-total_price="<?php echo $order['total_price']; ?>">编辑</button><button class="btn btn-danger btn-sm delete-btn" data-id="<?php echo $order['id']; ?>">删除</button></td></tr><?php endforeach; ?></tbody></table><button class="btn btn-success" onclick="showAddModal()">添加订单</button></div><!-- 添加订单模态框 --><div id="addModal" class="modal"><div class="modal-content"><div class="modal-header"><h5 id="addModalLabel">添加订单</h5><button type="button" class="close" onclick="hideModal('addModal')">×</button></div><div class="modal-body"><form id="addForm"><div class="form-group"><label for="customer_name">客户名称</label><input type="text" class="form-control" id="customer_name" required></div><div class="form-group"><label for="product_name">产品名称</label><input type="text" class="form-control" id="product_name" required></div><div class="form-group"><label for="quantity">数量</label><input type="number" class="form-control" id="quantity" required></div><div class="form-group"><label for="total_price">总价</label><input type="number" class="form-control" id="total_price" required></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" onclick="hideModal('addModal')">关闭</button><button type="button" class="btn btn-primary" onclick="addOrder()">添加</button></div></div></div><!-- 编辑订单模态框 --><div id="editModal" class="modal"><div class="modal-content"><div class="modal-header"><h5 id="editModalLabel">编辑订单</h5><button type="button" class="close" onclick="hideModal('editModal')">×</button></div><div class="modal-body"><form id="editForm"><input type="hidden" id="edit_id"><div class="form-group"><label for="edit_customer_name">客户名称</label><input type="text" class="form-control" id="edit_customer_name" required></div><div class="form-group"><label for="edit_product_name">产品名称</label><input type="text" class="form-control" id="edit_product_name" required></div><div class="form-group"><label for="edit_quantity">数量</label><input type="number" class="form-control" id="edit_quantity" required></div><div class="form-group"><label for="edit_total_price">总价</label><input type="number" class="form-control" id="edit_total_price" required></div></form></div><div class="modal-footer"><button type="button" class="btn btn-secondary" onclick="hideModal('editModal')">关闭</button><button type="button" class="btn btn-primary" onclick="updateOrder()">保存</button></div></div></div><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script>function showAddModal() {document.getElementById('addModal').style.display = 'block';}function hideModal(modalId) {document.getElementById(modalId).style.display = 'none';}// 添加订单function addOrder() {const customerName = document.getElementById('customer_name').value;const productName = document.getElementById('product_name').value;const quantity = document.getElementById('quantity').value;const totalPrice = document.getElementById('total_price').value;$.ajax({type: 'POST',url: 'index.php',data: {action: 'add',customer_name: customerName,product_name: productName,quantity: quantity,total_price: totalPrice},success: function() {hideModal('addModal');location.reload();}});}// 编辑按钮点击事件document.addEventListener('click', function(event) {if (event.target.classList.contains('edit-btn')) {const id = event.target.dataset.id;const customerName = event.target.dataset.customer_name;const productName = event.target.dataset.product_name;const quantity = event.target.dataset.quantity;const totalPrice = event.target.dataset.total_price;document.getElementById('edit_id').value = id;document.getElementById('edit_customer_name').value = customerName;document.getElementById('edit_product_name').value = productName;document.getElementById('edit_quantity').value = quantity;document.getElementById('edit_total_price').value = totalPrice;document.getElementById('editModal').style.display = 'block';}});// 保存编辑function updateOrder() {const id = document.getElementById('edit_id').value;const customerName = document.getElementById('edit_customer_name').value;const productName = document.getElementById('edit_product_name').value;const quantity = document.getElementById('edit_quantity').value;const totalPrice = document.getElementById('edit_total_price').value;$.ajax({type: 'POST',url: 'index.php',data: {action: 'update',id: id,customer_name: customerName,product_name: productName,quantity: quantity,total_price: totalPrice},success: function() {hideModal('editModal');location.reload();}});}// 删除按钮点击事件document.addEventListener('click', function(event) {if (event.target.classList.contains('delete-btn')) {const id = event.target.dataset.id;if (confirm('确定要删除该订单吗?')) {$.ajax({type: 'POST',url: 'index.php',data: {action: 'delete',id: id},success: function() {location.reload();}});}}});</script>
</body></html>
这种模式,虽然界面写起来可能不是很漂亮(毕竟术业有专攻),但开发效率还是非常高的。因为服务端客户端都是同一个开发,接口联调如丝般柔顺,减少了沟通的成本。
2.前后端分离模式
伴随javascript的强势发展,现在的web工程基本采用前后端分离的模式,服务端只负责数据的处理,由客户端专门做交互界面。
将下面的代码分成两个文件
2.1.前端界面
<!DOCTYPE html>
<html><head><title>订单管理系统</title><meta charset="UTF-8" /><style>body {font-family: Arial, sans-serif;}.container {max-width: 800px;margin: 0 auto;padding: 20px;}table {width: 100%;border-collapse: collapse;}th,td {padding: 8px;border: 1px solid #ddd;}th {background-color: #f2f2f2;}.btn {padding: 6px 12px;border: none;border-radius: 4px;cursor: pointer;}.btn-primary {background-color: #007bff;color: #fff;}.btn-danger {background-color: #dc3545;color: #fff;}.modal {display: none;position: fixed;z-index: 1000;top: 0;left: 0;width: 100%;height: 100%;overflow: auto;background-color: rgba(0, 0, 0, 0.4);}.modal-content {background-color: #fff;margin: 10% auto;padding: 20px;border: 1px solid #888;width: 80%;}.modal-header,.modal-footer {padding: 10px;}.modal-title {margin: 0;}.form-group {margin-bottom: 15px;}label {display: block;margin-bottom: 5px;}input[type="text"],input[type="number"] {width: 100%;padding: 8px;border: 1px solid #ddd;border-radius: 4px;}</style></head><body><div class="container"><h1>订单管理系统</h1><table><thead><tr><th>订单编号</th><th>客户名称</th><th>产品名称</th><th>数量</th><th>总价</th><th>操作</th></tr></thead><tbody id="orderTableBody"></tbody></table><button class="btn btn-success" onclick="showAddModal()">添加订单</button></div><!-- 添加订单模态框 --><div id="addModal" class="modal"><div class="modal-content"><div class="modal-header"><h5 id="addModalLabel">添加订单</h5><button type="button" class="close" onclick="hideModal('addModal')">×</button></div><div class="modal-body"><form id="addForm"><div class="form-group"><label for="customer_name">客户名称</label><inputtype="text"class="form-control"id="customer_name"required/></div><div class="form-group"><label for="product_name">产品名称</label><inputtype="text"class="form-control"id="product_name"required/></div><div class="form-group"><label for="quantity">数量</label><inputtype="number"class="form-control"id="quantity"required/></div><div class="form-group"><label for="total_price">总价</label><inputtype="number"class="form-control"id="total_price"required/></div></form></div><div class="modal-footer"><buttontype="button"class="btn btn-secondary"onclick="hideModal('addModal')">关闭</button><button type="button" class="btn btn-primary" onclick="addOrder()">添加</button></div></div></div><!-- 编辑订单模态框 --><div id="editModal" class="modal"><div class="modal-content"><div class="modal-header"><h5 id="editModalLabel">编辑订单</h5><button type="button" class="close" onclick="hideModal('editModal')">×</button></div><div class="modal-body"><form id="editForm"><input type="hidden" id="edit_id" /><div class="form-group"><label for="edit_customer_name">客户名称</label><inputtype="text"class="form-control"id="edit_customer_name"required/></div><div class="form-group"><label for="edit_product_name">产品名称</label><inputtype="text"class="form-control"id="edit_product_name"required/></div><div class="form-group"><label for="edit_quantity">数量</label><inputtype="number"class="form-control"id="edit_quantity"required/></div><div class="form-group"><label for="edit_total_price">总价</label><inputtype="number"class="form-control"id="edit_total_price"required/></div></form></div><div class="modal-footer"><buttontype="button"class="btn btn-secondary"onclick="hideModal('editModal')">关闭</button><button type="button" class="btn btn-primary" onclick="updateOrder()">保存</button></div></div></div><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script>// 加载订单数据function loadOrders() {$.ajax({type: "GET",url: "http://localhost/demo/order_handler.php",success: function (data) {$("#orderTableBody").html("");JSON.parse(data).forEach(function (order) {$("#orderTableBody").append("<tr><td>" +order.id +"</td><td>" +order.customer_name +"</td><td>" +order.product_name +"</td><td>" +order.quantity +"</td><td>" +order.total_price +'</td><td><button class="btn btn-primary btn-sm edit-btn" data-id="' +order.id +'" data-customer_name="' +order.customer_name +'" data-product_name="' +order.product_name +'" data-quantity="' +order.quantity +'" data-total_price="' +order.total_price +'">编辑</button><button class="btn btn-danger btn-sm delete-btn" data-id="' +order.id +'">删除</button></td></tr>');});},});}loadOrders();function showAddModal() {document.getElementById("addModal").style.display = "block";}function hideModal(modalId) {document.getElementById(modalId).style.display = "none";}// 添加订单function addOrder() {const customerName = document.getElementById("customer_name").value;const productName = document.getElementById("product_name").value;const quantity = document.getElementById("quantity").value;const totalPrice = document.getElementById("total_price").value;$.ajax({type: "POST",url: "http://localhost/demo/order_handler.php",data: {action: "add",customer_name: customerName,product_name: productName,quantity: quantity,total_price: totalPrice,},success: function () {hideModal("addModal");loadOrders();},});}// 编辑按钮点击事件document.addEventListener("click", function (event) {if (event.target.classList.contains("edit-btn")) {const id = event.target.dataset.id;const customerName = event.target.dataset.customer_name;const productName = event.target.dataset.product_name;const quantity = event.target.dataset.quantity;const totalPrice = event.target.dataset.total_price;document.getElementById("edit_id").value = id;document.getElementById("edit_customer_name").value = customerName;document.getElementById("edit_product_name").value = productName;document.getElementById("edit_quantity").value = quantity;document.getElementById("edit_total_price").value = totalPrice;document.getElementById("editModal").style.display = "block";}});// 保存编辑function updateOrder() {const id = document.getElementById("edit_id").value;const customerName =document.getElementById("edit_customer_name").value;const product_name = document.getElementById("edit_product_name").value;const quantity = document.getElementById("edit_quantity").value;const totalPrice = document.getElementById("edit_total_price").value;$.ajax({type: "POST",url: "/orders/update",data: {action: "update",id: id,customer_name: customerName,product_name: product_name,quantity: quantity,total_price: totalPrice,},success: function () {hideModal("editModal");loadOrders();},});}// 删除按钮点击事件document.addEventListener("click", function (event) {if (event.target.classList.contains("delete-btn")) {const id = event.target.dataset.id;if (confirm("确定要删除该订单吗?")) {$.ajax({type: "POST",url: "http://localhost/demo/order_handler.php",data: {action: "delete",id: id,},success: function () {loadOrders();},});}}});</script></body>
</html>
可以看到,CRUD的路径都是http://localhost/demo/order_handler.php这个php文件,通过action定义子类型。
2.2.后端代码
<?php
require_once 'router.php';
// 连接数据库
try {$pdo = new PDO('mysql:host=localhost;dbname=php', 'root', '123456');$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {die("数据库连接失败:" . $e->getMessage());
}// 获取所有订单
function getAllOrders()
{global $pdo;$sql = "SELECT * FROM orders";$stmt = $pdo->query($sql);return $stmt->fetchAll(PDO::FETCH_ASSOC);
}// 添加订单
function addOrder($customerName, $productName, $quantity, $totalPrice)
{global $pdo;$sql = "INSERT INTO orders (customer_name, product_name, quantity, total_price) VALUES (:customer_name, :product_name, :quantity, :total_price)";$stmt = $pdo->prepare($sql);$stmt->bindParam(':customer_name', $customerName);$stmt->bindParam(':product_name', $productName);$stmt->bindParam(':quantity', $quantity);$stmt->bindParam(':total_price', $totalPrice);$stmt->execute();
}// 更新订单
function updateOrder($id, $customerName, $productName, $quantity, $totalPrice)
{global $pdo;$sql = "UPDATE orders SET customer_name = :customer_name, product_name = :product_name, quantity = :quantity, total_price = :total_price WHERE id = :id";$stmt = $pdo->prepare($sql);$stmt->bindParam(':id', $id);$stmt->bindParam(':customer_name', $customerName);$stmt->bindParam(':product_name', $productName);$stmt->bindParam(':quantity', $quantity);$stmt->bindParam(':total_price', $totalPrice);$stmt->execute();
}// 删除订单
function deleteOrder($id)
{global $pdo;$sql = "DELETE FROM orders WHERE id = :id";$stmt = $pdo->prepare($sql);$stmt->bindParam(':id', $id);$stmt->execute();
}// 根据请求类型进行处理
if ($_SERVER['REQUEST_METHOD'] === 'GET') {$orders = getAllOrders();echo json_encode($orders);
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (isset($_POST['action']) && $_POST['action'] === 'add') {addOrder($_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'update') {updateOrder($_POST['id'], $_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'delete') {deleteOrder($_POST['id']);}header('Location: index.php');exit;
}
这里有一段代码,根据不同的请求方法调用相应的函数,就是非常简单(丑陋)的消息路由逻辑了。
// 根据请求类型进行处理
if ($_SERVER['REQUEST_METHOD'] === 'GET') {$orders = getAllOrders();echo json_encode($orders);
} else if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (isset($_POST['action']) && $_POST['action'] === 'add') {addOrder($_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'update') {updateOrder($_POST['id'], $_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);} elseif (isset($_POST['action']) && $_POST['action'] === 'delete') {deleteOrder($_POST['id']);}header('Location: index.php');exit;
}
3.实现简单路由
3.1.springmvc基于注解的路由
了解过springmvc(或者其他编程语言的mvc框架)的同学,可能对这种代码嗤之以鼻,确实太丑了。springmvc通过类注解,方法注解,定义消息路由,非常漂亮!
@RestController
@RequestMapping("/player")
public class PlayerController {@GetMapping(value = "/getProgress")public Response getProgress(@RequestHeader Map<String, String> headers, @Valid ReqQueryProgress req) {return Response.success(userGameData.getProgress());}@PostMapping(value = "/saveProgress")public Response saveProgress(@RequestHeader Map<String, String> headers, @Valid @RequestBody ReqSaveProgress req) {return Response.success(userGameData.getProgress());}
}
由于php不支持注解,无法做到springmvc的模式,即使使用了消息路由,也只能手动注册url与对应的执行函数。
3.2.路由函数(router.php)
<?php
// 定义路由和相关函数等内容$routes = [];
function route($method, $path, $callback)
{global $routes;$routes[$method][$path] = $callback;
}function dispatch()
{global $routes;$method = $_SERVER['REQUEST_METHOD'];$path = $_SERVER['REQUEST_URI'];// 本项目放在htdocs目录下的子目录demo,所以将url前缀进行替换$path = str_replace("/demo", "", $path);if (isset($routes[$method][$path])) {$callback = $routes[$method][$path];return $callback();} else {// 404处理header('HTTP/1.0 404 Not Found');echo '404 - Page not found';}
}
3.3.注册业务路由
order_handler.php那段丑陋的代码,现在可以删掉了,改为下面的代码
// 获取订单的路由
route('GET', '/orders', function () {$orders = getAllOrders();echo json_encode($orders);
});
// 添加订单的路由
route('POST', '/orders/add', function () {if (isset($_POST['action']) && $_POST['action'] === 'add') {addOrder($_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);}header('Location: index.php');exit;
});
// 更新订单的路由
route('POST', '/orders/update', function () {if (isset($_POST['action']) && $_POST['action'] === 'update') {updateOrder($_POST['id'], $_POST['customer_name'], $_POST['product_name'], $_POST['quantity'], $_POST['total_price']);}header('Location: index.php');exit;
});
// 删除订单的路由
route('POST', '/orders/delete', function () {if (isset($_POST['action']) && $_POST['action'] === 'delete') {deleteOrder($_POST['id']);}header('Location: index.php');exit;
});
3.4.统一请求入口(网关)
我们希望对order的所有请求转发到order_handler.php这个文件来,这里可以利用.htaccess来定义路由规则,在php代码的同级目录下,新建.htaccess文件,编辑下面的内容。将所有请求转发到index.php。
RewriteEngine On
RewriteRule ^(.*)$ index.php [QSA,L]
在index.php文件,加载路由文件,订单业务处理文件,
<?php
// 引入包含路由定义和相关函数(如addOrder、updateOrder等)的文件
require_once 'router.php';
require_once 'order_handler.php';// 调用dispatch函数处理请求
dispatch();// 跨域设置
// 允许任何来源访问
header('Access-Control-Allow-Origin: *');
// 允许的请求方法
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
// 允许的请求头
header('Access-Control-Allow-Headers: Content-Type');
修改客户端请求的url,由统一路径改为具体的路径,如http://localhost/demo/orders/add, action字段也可以去掉了。
3.5.另外一种路由:一个url对应一个文件
对于业务是对各种数据库表作增删查改,则可以换一种方式作映射。对于一张表,增删查改代表四种动作,对应不同的http方法,每一个动作对应一个php文件,采用下面的映射文件。
{"/api/order/": {"get": "api/order/query.php","post": "api/order/create.php","put": "api/order/edit.php","delete": "api/order/delete.php",},"/api/user/": {"get": "api/user/query.php","post": "api/user/create.php","put": "api/user/edit.php","delete": "api/user/delete.php",},
}
通过解析这个文件,得到url与对应的php文件。每次请求,提取出url与method类型,找到对应的php文件,加载后得到计算结果。代码如下:
// 启动一个输出缓冲区
ob_start();
// 根据url,method获取对应的php文件路径
$apiPath = mapAPI($path, $method);
// 加载php文件
include $apiPath;
// 获取缓冲区的结果
$body = ob_get_clean();// 返回给客户端
return ['statusCode' => 200,'headers' => $headers,'body' => $body
];
这种模式有两个很不好的地方:
- 如果接口很多,文件数据也相应非常多
- 同一张表的增删查改逻辑可能很关联性,拆分成多个文件不利于代码的复用