商城系统php实战方法:提示词、素材与细节优化

运营资讯4小时前发布 jdkjadmin
0 00
商城系统php实战方法:提示词、素材与细节优化

商城系统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 驱动和配置应在目标环境测试。若需要页码,还应校验页码范围,并计算合理的偏移量。

八、常见错误与修正方法

  1. 信任浏览器传来的价格或折扣。浏览器数据可以被修改。下单时从服务端可信数据重新读取价格和适用规则,并保存订单明细中的成交单价,方便后续核对。
  2. 只做前端校验。前端校验改善体验,但请求可以绕开页面直接发送。所有影响库存、金额、权限和数据完整性的校验都必须在服务端执行。
  3. 认为 PDO 预处理自动解决所有安全问题。预处理主要用于安全传递数据值;动态表名、列名和排序表达式要用白名单控制。输出到 HTML 时仍须按上下文进行编码。
  4. 用浮点数保存金额。浮点数表示可能存在精度误差。常见做法是以最小货币单位保存整数,并把币种和舍入规则纳入设计;若涉及复杂税务或多币种,应另行评估。
  5. 把“库存大于零”当作库存并发方案。先读后写可能在并发下发生竞态。要采用事务、条件更新或适合目标数据库的锁策略,并用并发测试验证。
  6. 把加购物车当成扣库存。这会让废弃购物车长期占用库存。是否预占应由业务规则决定,并设计过期释放机制。
  7. 请求里只要 JSON 就不需要 CSRF 防护。内容类型或请求格式本身不是完整防线。基于 Cookie 的登录状态可能涉及 CSRF 风险,应结合 SameSite Cookie、CSRF Token、Origin 检查和应用框架机制评估。
  8. 把示例代码直接部署。示例缺少用户认证、授权、请求限流、幂等、监控、备份、支付与运维配置。上线前要进行代码审查、安全测试和故障演练。
  9. 让 AI 自行补全商品事实。生成内容可能把猜测写成事实。对价格、规格、库存、认证、质保和退换政策,应使用已核实资料,并由业务负责人确认。

九、如何验证 AI 生成的商城代码

先从能重复执行的检查开始。每个功能至少准备正常、边界和失败用例。例如购物车接口要测有效商品、下架商品、数量为零、数量超过上限、非法类型和缺少字段;订单逻辑要测库存不足、商品已删除、金额溢出边界,以及并发购买最后一件商品的情况。

可以为项目建立以下检查清单:

  • 能否在目标 PHP 版本和实际数据库驱动上运行;
  • 数据库查询是否参数化,动态 SQL 标识符是否来自白名单;
  • 服务端是否校验身份、权限、商品状态、数量、价格和库存;
  • 失败时事务是否回滚,接口是否返回稳定且不泄露内部信息的响应;
  • HTML 输出是否编码,文件上传是否有类型、大小和存储策略;
  • 订单与支付状态是否区分,支付通知是否验证签名并具备幂等处理;
  • 日志、密钥、备份和生产配置是否与代码分离。

AI 给出的测试也需要人工核对:测试是否覆盖真正的业务边界,是否只验证“代码跑完”,是否使用了和生产相同的数据库特性。尤其是事务与并发行为,不能因为单线程本地测试通过就认为生产环境不会超卖。

结语:让提示词、素材和代码形成闭环

商城开发中,提示词负责把任务说清楚,素材负责提供可信业务事实,代码负责在服务端落实规则,测试负责证明边界确实有效。最稳妥的实践不是一次生成整套商城,而是按商品、购物车、订单、支付和售后拆分任务;每一步都先定义输入、输出和失败行为,再实现、审查和验证。

如果记住三个原则,就能避开不少返工:客户端数据不能决定价格和权限;数据库事务和并发策略不能靠页面提示代替;AI 生成的内容与代码都需要验证。以这些原则为基础,提示词才不只是“让模型写代码”,而是团队把业务知识转化为可检查实现的一种工作方式。

© 版权声明

相关文章