
商城系统php实战方法:提示词、素材与细节优化
用 AI 辅助开发 PHP 商城,难点通常不在“生成一段代码”,而在于把需求、数据、业务规则和安全边界说清楚。提示词写得再漂亮,如果商品价格由浏览器决定、下单时不校验库存,生成的页面也不能安全上线。反过来,先拆清场景、准备准确素材,再逐步实现并验证,AI 才能真正节省时间。
本文面向 PHP 8.1 及以上版本,适合个人项目、原型验证、已有商城的局部改造,以及希望借助 AI 编写后台或接口的开发者。示例使用 PDO 和 SQLite 演示关键逻辑,便于本地运行;正式项目可以替换为 MySQL 等数据库,但事务、并发控制和部署配置需要按具体环境验证。本文不提供可直接上线的完整商城,也不把 AI 生成结果视为经过审计的安全代码。
一、先判断任务适不适合用 AI 辅助
AI 适合帮助整理需求、生成表结构草案、制作表单和列表页面、补充校验代码、解释报错,以及为已有功能编写测试。它尤其适合范围明确、输入输出清楚的任务,例如“为商品列表增加分类筛选,并保留分页条件”。
以下工作不能只靠提示词或生成代码完成:
- 支付与退款:需要对接真实支付渠道,处理签名校验、异步通知、幂等、退款状态和对账。仅凭浏览器跳转或页面显示“支付成功”,不能证明款项到账。
- 库存与并发:需要结合数据库事务、条件更新、锁策略和业务规则设计。多个用户同时购买最后一件商品时,必须防止超卖。
- 身份与权限:后台角色、商家隔离、敏感操作授权需要在服务端逐项校验,不能只隐藏按钮。
- 隐私和合规:手机号、地址、订单记录等数据的收集、存储、展示和保留期限,应由项目负责人结合适用法规和业务要求确认。
小型展示型商品目录可以先用静态数据验证页面;涉及真实下单、库存或支付时,就应尽早明确数据来源、权限、失败处理和审计要求。先确定“这一轮要交付什么”,不要一条提示词要求 AI 同时生成商城、支付、物流、优惠券和后台权限系统。
二、把提示词写成可验收的开发任务
有效提示词不是越长越好,而是能减少歧义。建议至少提供以下信息:现有技术栈、目标用户、功能范围、输入输出、业务规则、安全要求、禁止事项和验收标准。对已有项目,还要说明目录结构、框架版本、数据库类型,以及哪些文件允许修改。
下面是一份可直接改写的提示词模板:
你是 PHP 后端开发助手。请基于 PHP 8.1、PDO 和现有的原生 PHP 项目实现“加入购物车”接口。
项目现状:
- 数据表 products(id, name, price_cents, stock, active)
- 数据库连接由现有的 $pdo 提供,不要新建连接
- 会话中保存购物车,格式为 [商品ID => 数量]
功能要求:
- 接收 JSON:{"product_id": 12, "quantity": 2}
- 商品必须存在且 active = 1
- quantity 必须是 1 到 99 的整数
- 只能使用数据库中的商品价格,不能信任客户端提交的价格
- 本任务只更新购物车,不扣减库存、不创建订单
安全要求:
- 使用 PDO 预处理语句
- 不输出数据库密码、堆栈或内部路径
- 说明需要的 CSRF 防护和会话配置,不要声称仅有 JSON 就能防 CSRF
输出:
1. 修改文件清单
2. 可运行的 PHP 代码
3. 两个正常用例和三个失败用例
4. 说明并发、库存检查和正式下单尚未实现
这类提示词做了两件重要的事:把“购物车”和“正式下单”分开;要求明确说明未覆盖的风险。没有边界时,模型可能擅自把库存扣减写在加入购物车阶段,或者相信客户端传来的价格,造成后续难以排查的问题。
迭代时一次只改一个主要目标。先让 AI 解释方案和数据流,再请求实现,再检查差异和测试结果。出现报错时,提供最小复现、错误信息和相关代码,不要把数据库密码、真实用户数据、支付密钥或生产环境日志原样粘贴到第三方服务中。
三、素材准备:减少内容错误,而不是堆文件
商城素材不仅是图片,还包括商品名称、规格、价格、库存口径、分类、运费规则、售后政策和品牌用语。AI 可以帮助整理和润色,但不能替业务方确认事实。
建议为每个商品维护结构化资料,例如:
- 稳定的商品编号、名称、分类和上下架状态;
- 价格使用明确币种,并确认数据库以元还是最小货币单位保存;
- 规格、单位、库存口径和可售范围;
- 经授权使用的图片文件名、来源和替代文本;
- 可以公开的功能说明,以及必须保留的限制、保质期或适用人群信息。
图片文件名可以按商品编号和用途整理,例如 SKU100-detail-01.webp。文件格式、尺寸和质量应根据页面用途实际测试,不要假设某个格式在所有浏览器、图片处理库和部署环境中都可用。上传功能还需要服务端检查实际文件内容、限制大小、随机生成存储名,并将文件放在不能直接执行脚本的位置;只检查文件扩展名不足以保证安全。
给 AI 的素材中,区分“事实”和“表达偏好”。例如:“重量约 500 克(已核实)”“语气简洁,不使用绝对化宣传”比“请写得高级一点”更可控。若素材不足,应要求输出待确认项,而不是让模型自行补造材质、认证、库存或效果承诺。
四、最小可运行示例:PDO 连接和商品读取
以下示例使用 SQLite,PHP 需要启用 PDO SQLite 扩展。保存为 demo.php 后可通过 PHP 内置开发服务器运行。演示数据库放在系统临时目录,适合本地验证;它不是生产环境的数据库部署方案。
<?php
declare(strict_types=1);
$databasePath = sys_get_temp_dir() . '/php_shop_demo.sqlite';
$pdo = new PDO('sqlite:' . $databasePath);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$pdo->exec(
'CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price_cents INTEGER NOT NULL CHECK (price_cents >= 0),
stock INTEGER NOT NULL CHECK (stock >= 0),
active INTEGER NOT NULL DEFAULT 1
)'
);
$count = (int) $pdo->query('SELECT COUNT(*) FROM products')->fetchColumn();
if ($count === 0) {
$insert = $pdo->prepare(
'INSERT INTO products (name, price_cents, stock, active)
VALUES (:name, :price, :stock, 1)'
);
$insert->execute(['name' => '示例商品', 'price' => 1299, 'stock' => 10]);
}
$stmt = $pdo->prepare(
'SELECT id, name, price_cents
FROM products
WHERE active = 1
ORDER BY id DESC'
);
$stmt->execute();
$products = $stmt->fetchAll();
header('Content-Type: text/html; charset=utf-8');
foreach ($products as $product) {
$name = htmlspecialchars(
(string) $product['name'],
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
$price = number_format(((int) $product['price_cents']) / 100, 2);
echo '<p>' . $name . ':¥' . $price . '</p>';
}
代码用预处理语句传递数据值,并对输出到 HTML 的商品名称进行转义。预处理语句不能代替输出编码:数据库中也可能存有恶意或格式异常的文本。反过来,HTML 转义也不能代替 SQL 参数绑定。两者解决的是不同问题。
价格示例以整数分保存,可以避免用二进制浮点数直接进行金额运算时的精度问题。真实业务还要明确币种、税费、折扣和舍入规则;不能把“保留两位小数”当作完整的财务规则。展示价格时应依据业务币种格式化,而不是在所有项目中固定加人民币符号。
五、输入校验:客户端便于使用,服务端负责裁决
下面的函数检查商品编号和购买数量。它只验证输入格式和范围,不代表用户有权购买该商品,也不代表商品有库存。商品是否有效仍需查询数据库。
<?php
function readCartInput(array $input): array
{
$productId = filter_var(
$input['product_id'] ?? null,
FILTER_VALIDATE_INT
);
$quantity = filter_var(
$input['quantity'] ?? null,
FILTER_VALIDATE_INT
);
if ($productId === false || $productId === null || $productId < 1) {
throw new InvalidArgumentException('商品编号无效');
}
if ($quantity === false || $quantity === null || $quantity < 1 || $quantity > 99) {
throw new InvalidArgumentException('数量必须是 1 到 99 的整数');
}
return [$productId, $quantity];
}
$rawBody = file_get_contents('php://input');
$input = json_decode($rawBody, true);
if (!is_array($input)) {
http_response_code(400);
exit('请求格式无效');
}
try {
[$productId, $quantity] = readCartInput($input);
} catch (InvalidArgumentException $e) {
http_response_code(422);
exit($e->getMessage());
}
这里的 99 是示例业务上限,不是 PHP 或 HTTP 的固定限制。应根据商品类型、库存策略和接口设计设置合理上限,并同时考虑请求体大小限制。若使用框架,应优先采用框架提供的请求校验能力,并确保校验规则与数据库约束一致。
数量限制不等于防止恶意请求,也不负责权限控制。若接口依赖登录身份,需要在服务端从可信会话或令牌中取得用户身份,不能相信请求体里的 user_id。如果请求会改变登录用户的状态,还要结合身份验证、授权、会话设置及合适的 CSRF 防护。
六、从购物车到订单:价格必须在服务端重新计算
购物车适合保存商品编号和数量,不应把客户端传来的总价当成订单金额。创建订单时,应重新查询商品状态和当前价格,检查库存,在数据库事务中写入订单并扣减库存。购物车阶段通常只表示“意向”,库存是否预留要由业务规则明确。
下面是一个简化的 SQLite 下单函数。为便于理解,订单和订单明细表也在示例中创建。调用时传入已经校验过的商品编号到数量映射。示例没有处理用户身份、运费、优惠券、税费、支付、订单过期和幂等键;它不能直接作为完整电商下单服务。
<?php
$pdo->exec(
'CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
total_cents INTEGER NOT NULL,
created_at TEXT NOT NULL
)'
);
$pdo->exec(
'CREATE TABLE IF NOT EXISTS order_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
product_id INTEGER NOT NULL,
quantity INTEGER NOT NULL,
unit_price_cents INTEGER NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id)
)'
);
function createOrder(PDO $pdo, array $cart): int
{
if ($cart === [] || count($cart) > 50) {
throw new InvalidArgumentException('购物车内容无效');
}
foreach ($cart as $productId => $quantity) {
if (!is_int($productId) || $productId < 1
|| !is_int($quantity) || $quantity < 1 || $quantity > 99) {
throw new InvalidArgumentException('商品或数量无效');
}
}
$pdo->beginTransaction();
try {
$find = $pdo->prepare(
'SELECT id, price_cents, stock
FROM products
WHERE id = :id AND active = 1'
);
$decrease = $pdo->prepare(
'UPDATE products
SET stock = stock - :quantity
WHERE id = :id AND active = 1 AND stock >= :quantity'
);
$items = [];
$total = 0;
foreach ($cart as $productId => $quantity) {
$find->execute(['id' => $productId]);
$product = $find->fetch();
if (!$product || (int) $product['stock'] < $quantity) {
throw new RuntimeException('商品不存在、已下架或库存不足');
}
$unitPrice = (int) $product['price_cents'];
if ($unitPrice > intdiv(PHP_INT_MAX, $quantity)) {
throw new RuntimeException('订单金额超出允许范围');
}
$lineTotal = $unitPrice * $quantity;
if ($total > PHP_INT_MAX - $lineTotal) {
throw new RuntimeException('订单金额超出允许范围');
}
$total += $lineTotal;
$items[] = [$productId, $quantity, $unitPrice];
}
foreach ($items as [$productId, $quantity, $unitPrice]) {
$decrease->execute([
'quantity' => $quantity,
'id' => $productId
]);
if ($decrease->rowCount() !== 1) {
throw new RuntimeException('库存已变化,请重新下单');
}
}
$order = $pdo->prepare(
'INSERT INTO orders (total_cents, created_at)
VALUES (:total, :created_at)'
);
$order->execute([
'total' => $total,
'created_at' => gmdate('Y-m-d H:i:s')
]);
$orderId = (int) $pdo->lastInsertId();
$addItem = $pdo->prepare(
'INSERT INTO order_items
(order_id, product_id, quantity, unit_price_cents)
VALUES (:order_id, :product_id, :quantity, :price)'
);
foreach ($items as [$productId, $quantity, $unitPrice]) {
$addItem->execute([
'order_id' => $orderId,
'product_id' => $productId,
'quantity' => $quantity,
'price' => $unitPrice
]);
}
$pdo->commit();
return $orderId;
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
}
库存更新使用带库存条件的 SQL,并检查受影响行数,而不是只在 PHP 中读取一次库存后无条件扣减。事务保证本示例涉及的订单和库存写入要么一起提交,要么一起回滚。不过,不同数据库和隔离级别对并发行为有差异;生产环境应根据所用数据库验证锁、死锁重试和并发下单测试。
此代码将库存立即扣减,适用于一种简化的“下单即占库存”模型。若业务要等支付成功才最终扣减,或要在支付期限内预留库存,就需要设计库存预留记录、超时释放任务和订单状态机。不要通过删除事务或把库存更新挪到浏览器来“解决”并发问题。
七、图片、页面和接口的细节优化
商品列表页面的优化先从真实问题出发:图片是否过大、数据库查询是否重复、手机端信息是否易读、页面是否能键盘操作。无需一开始就引入缓存、消息队列或复杂搜索服务。记录页面响应时间、SQL 数量和图片体积,再根据测量结果改进。
- 图片呈现:为内容图片提供有意义的替代文本;纯装饰图片使用空替代文本。不要把商品图片里的文字当作唯一的规格说明。
- 表单体验:服务端校验失败时保留可安全回填的信息,显示具体但不过度暴露内部实现的错误说明。
- 分页和筛选:验证页码、排序字段和筛选值。排序字段通常不能用普通 SQL 参数占位符绑定,应从服务端允许列表中选取列名。
- 错误处理:面向用户返回通用错误,服务端记录必要上下文。日志中避免记录密码、支付凭据和不必要的个人信息。
- 可访问性:使用语义化标题、标签和按钮,保证焦点可见,不能只靠颜色传递库存不足等状态。
商品搜索的 SQL 可以使用参数化查询,但要注意占位符绑定的是数据值,不是表名、列名或 SQL 片段。对于动态排序,可采用明确的白名单:
<?php
$allowedSorts = [
'newest' => 'id DESC',
'price_low' => 'price_cents ASC',
'price_high' => 'price_cents DESC'
];
$sortKey = $_GET['sort'] ?? 'newest';
$orderBy = $allowedSorts[$sortKey] ?? $allowedSorts['newest'];
$sql = 'SELECT id, name, price_cents
FROM products
WHERE active = :active
ORDER BY ' . $orderBy . '
LIMIT :limit';
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':active', 1, PDO::PARAM_INT);
$stmt->bindValue(':limit', 20, PDO::PARAM_INT);
$stmt->execute();
$rows = $stmt->fetchAll();
$orderBy 来自开发者定义的固定映射,而不是直接拼接用户输入,因此用户不能通过排序参数注入任意 SQL。LIMIT 在示例中绑定整数;具体 PDO 驱动和配置应在目标环境测试。若需要页码,还应校验页码范围,并计算合理的偏移量。
八、常见错误与修正方法
- 信任浏览器传来的价格或折扣。浏览器数据可以被修改。下单时从服务端可信数据重新读取价格和适用规则,并保存订单明细中的成交单价,方便后续核对。
- 只做前端校验。前端校验改善体验,但请求可以绕开页面直接发送。所有影响库存、金额、权限和数据完整性的校验都必须在服务端执行。
- 认为 PDO 预处理自动解决所有安全问题。预处理主要用于安全传递数据值;动态表名、列名和排序表达式要用白名单控制。输出到 HTML 时仍须按上下文进行编码。
- 用浮点数保存金额。浮点数表示可能存在精度误差。常见做法是以最小货币单位保存整数,并把币种和舍入规则纳入设计;若涉及复杂税务或多币种,应另行评估。
- 把“库存大于零”当作库存并发方案。先读后写可能在并发下发生竞态。要采用事务、条件更新或适合目标数据库的锁策略,并用并发测试验证。
- 把加购物车当成扣库存。这会让废弃购物车长期占用库存。是否预占应由业务规则决定,并设计过期释放机制。
- 请求里只要 JSON 就不需要 CSRF 防护。内容类型或请求格式本身不是完整防线。基于 Cookie 的登录状态可能涉及 CSRF 风险,应结合 SameSite Cookie、CSRF Token、Origin 检查和应用框架机制评估。
- 把示例代码直接部署。示例缺少用户认证、授权、请求限流、幂等、监控、备份、支付与运维配置。上线前要进行代码审查、安全测试和故障演练。
- 让 AI 自行补全商品事实。生成内容可能把猜测写成事实。对价格、规格、库存、认证、质保和退换政策,应使用已核实资料,并由业务负责人确认。
九、如何验证 AI 生成的商城代码
先从能重复执行的检查开始。每个功能至少准备正常、边界和失败用例。例如购物车接口要测有效商品、下架商品、数量为零、数量超过上限、非法类型和缺少字段;订单逻辑要测库存不足、商品已删除、金额溢出边界,以及并发购买最后一件商品的情况。
可以为项目建立以下检查清单:
- 能否在目标 PHP 版本和实际数据库驱动上运行;
- 数据库查询是否参数化,动态 SQL 标识符是否来自白名单;
- 服务端是否校验身份、权限、商品状态、数量、价格和库存;
- 失败时事务是否回滚,接口是否返回稳定且不泄露内部信息的响应;
- HTML 输出是否编码,文件上传是否有类型、大小和存储策略;
- 订单与支付状态是否区分,支付通知是否验证签名并具备幂等处理;
- 日志、密钥、备份和生产配置是否与代码分离。
AI 给出的测试也需要人工核对:测试是否覆盖真正的业务边界,是否只验证“代码跑完”,是否使用了和生产相同的数据库特性。尤其是事务与并发行为,不能因为单线程本地测试通过就认为生产环境不会超卖。
结语:让提示词、素材和代码形成闭环
商城开发中,提示词负责把任务说清楚,素材负责提供可信业务事实,代码负责在服务端落实规则,测试负责证明边界确实有效。最稳妥的实践不是一次生成整套商城,而是按商品、购物车、订单、支付和售后拆分任务;每一步都先定义输入、输出和失败行为,再实现、审查和验证。
如果记住三个原则,就能避开不少返工:客户端数据不能决定价格和权限;数据库事务和并发策略不能靠页面提示代替;AI 生成的内容与代码都需要验证。以这些原则为基础,提示词才不只是“让模型写代码”,而是团队把业务知识转化为可检查实现的一种工作方式。








