SEO技术

SEO技术

Products

当前位置:首页 > SEO技术 >

购物车数据如何从前端传至后端数据库?

96SEO 2026-08-02 19:41 0


GitHub仓库github.com//…说起来,

|前端人第一次打开 Spring Boot 项目。应该先看哪里,

购物车数据如何从前端传至后端数据库?

|从 pnpm dev 到 Spring Boot 启动:后端服务到底怎么跑起来?

|Axios 请求进了后端之后:Controller、Request、Response 是怎么接住它的?

|前端状态为什么不够用?从页面数据到 MySQL 持久化

|不手写一堆 SQL,后端怎么操作数据库?MyBatis-Plus 入门

|登录后端到底在做什么?JWT、Spring Security 和权限链路

|为什么后端也要缓存?从前端缓存思维理解 Redis

|一个商品详情接口背后的完整链路:HTTP、Redis、MySQL 与 JSON

|商品为什么不能随便上下架?后端状态机思维入门

|库存扣减为什么最容易出事故?SKU、并发与原子更新

. 这篇解决什么问题

使用者痛点:

  • 不知道前端的数组 push 与后端持久化之间的差异。
  • 担心自己传的 userId 被信任导致越权。
  • 不清楚为何只能保存 skuId 而不是只保存 productId
  • 对重复加入购物车时数据库唯一索引的作用感到迷茫。
  • 查询购物车时出现 N+ 查询导致性能问题。
  • 误以为购物车金额就是订单金额。

  1. 前端购物车状态和后端购物车表有什么区别;
  2. UserId 必须来自登录态,不能由前端提交;
  3. 加入购物车为什么保存 SkuId,而不是只保存 ProductId;
  4. CARTAddRequest 只有 SkuId  和 TotalQty ;
  5. 后端加入购物车之前如何校验商品、SKU、价格和库存;
  6. 同一使用者同一 SKU 为何不能重复加入,数据库唯一索引如何兜底;
  7. 查询购物车时如何批量查询 SKU 和商品,避免 N+;话说回来,
  8. <
  9. 为何购物车金额只是预估金额。真正订单金额必须在创建订单时重新计算。

. 用前端知识类比——先把痛点映射过去

User Pain Point: “我在页面里维护一个数组就行了啊”。这个数组只能在当前页面/浏览器有效,一旦换设备或刷新,就会丢失。下面先展示典型的 “仅靠数组” 实现,接下来对比后面的持久化方案。

前端侧示意代码:页面内购物车状态

// 前端侧示意代码
interface CartItemView {
skuId的观点是,number;productTitle: string;specText: string;currentUnitPrice: string;不过,quantity: number;checked: boolean;availableStock: number;saleable: boolean;}
const cartItems = ref;

本地去重只能体验更好,不能替代后置约束

// 本地去重示例
function addLocalCartItem {
const exists = cartItems.value.some;if {
toast,return;}
cartItems.value.push;}

User Pain Point: “我把 userId 放进请求体里就行了”。再看实际情况是,The backend never trusts a user‑id that comes from client.

. 后端主要概念讲解

Shopping Cart is a User Resource

The API path is simply /api/cart/items. No {userId}. The server extracts current user from JWT token:

sequenceDiagram
participant UI as 前端页面
participant API as CartController
participant Auth as CurrentUserService
participant DB as mall_cart_item
UI->API: GET /api/cart/items
Authorization: Bearer token
API->Auth: requireCurrentUserId
Auth-->API: userId=当前登录使用者
API->DB: SELECT * FROM mall_cart_item WHERE user_id=?怎么说呢,DB-->API: 当前使用者购物车行
API-->UI: CartResponse

This design prevents a malicious client from changing path to anor user's ID. It mirrors front‑end routing like “/profile” – you never need to pass user id because it’s already known from login state.

保存 SKU 而非 ProductID

  • User selects a concrete specification . The same product can have many SKUs with different prices & stock.
  • If only a product_id  were stored,back‑end could not know which specification user actually wants.

金额只是预估 —— 为什么不能直接提交给订单服务

  • The front‑end shows an “estimated total”. Between “cart page → submit order” price or stock may change.
  • The order service must re‑query SKU price & stock,lock inventory and store a snapshot. Only that snapshot becomes final transaction amount.

. 项目对应文件清单

文件方法 & 名称职责说明关联痛点
/backend/service/src/main/java/com/example/fullstackmall/service/cart/CartController.java- HTTP入口 - 提供「加入」和「查询」两个方法- 痛点1:为何没有userId参数
/backend/contract/src/main/java/com/example/fullstackmall/contract/cart/ICartFacade.java- 用例契约 - 明确业务入口必须使用登录态- 痛点2:userId来源
/backend/service/src/main/java/com/example/fullstackmall/service/cart/CartFacade.java- 主要业务编排 - 校验 SKU、商品状态 - 检查重复并写入 DB- 痛点4/5/6
/backend/contract/src/main/java/com/example/fullstackmall/contract/cart/CartAddRequest.java- DTO。仅包含 skuId 与 quantity- 痛点3:为何不让客户端传price/userI d
/backend/contract/src/main/java/com/example/fullstackmall/contract/cart/CartItemResponse.java- 响应结构,包括实时 price 、stock 、saleable 标记- 痛点7:显示不可售行
/backend/service/src/main/java/com/example/fullstackmall/service/cart/entity/CartItemEntity.java- 对应表 mall_cart_item - 最小化存储字段- 痛点8:为何只存 sku_id 和 qty
/backend/service/src/main/java/com/example/fullstackmall/service/cart/service/CartItemDbService.java- 所有查询必带 user_id - 防止越权读取或删除- 痛点9:忘记加user_id导致跨使用者泄露

. 全链路图 — 加入 & 查询

加入购物车链路

sequenceDiagram
participant UI as 商品详情页
participant Sec as Spring Security
participant C as CartController
participant F as CartFacade
participant User as CurrentUserService
participant Sku as SkuDbService
participant Prod as ProductDbService
participant Cart as CartItemDbService
UI->Sec: POST /api/cart/items {skuId,quantity}
Sec->Sec: 校验 JWT 登录态
从Sec->C来看,addItem
C->C的观点是,@Valid 参数校验
C->F的观点是,addItem
F->User: requireCurrentUserId
至于F->Sku,requireById
F->Prod: requireById
说到F->F,校验商品上架 + price + stock 可售性
F->Cart: existsByUserIdAndSkuId
F->DB的观点是,INSERT mall_cart_item
F-->C的观点是,CartItemResponse
C--&g t;返回 Created + ApiResponse

查询我的购物车链路

mermaid sequenceDiagram participant UI as 前段页 participant C as CartController participant F as CartFacade participant U s er as CurrentUserService participant CI t emDbService participant S ku as SkuDbService participant P roductDbService UI -> C : GET /api/cart/items C -> F : queryMyCart F -> U : requireCurrentUserI d F -> CI t emDbS ervice : listByUserI d F -> S ku : listExistingByIds F -> P roductD bServi ce : listExistingByIds F -> F : assemble item + saleable + lineAmount F --> C : CartResponse C --> UI : ApiResponse

. 源码逐段阅读与痛点对应解释

Controller — 方法即资源。不接受 user_id 参数

@RestController
@RequestMapping
@Tag
public class CartController {
@PostMapping
@Operation
public ResponseEntity addItem(
@Valid @RequestBody CartAddRequest request,HttpServletRequest servletRequest) {
CartItemResponse response = cartFacade.addItem;return ResponseEntity.status
.body));}
@GetMapping
@Operation
public ApiResponse. queryMyCart {
return ApiResponse.success(
cartFacade.queryMyCa rt,TraceI dContext.get);

} }

  • 两条接口共用同一方法,仅通过 HTTP 方法区分新增 vs 查询。
  • **Pain point** — 如果你习惯在请求体里传 `userID`。这里根本没有对应字段,说明设计者已把安全边界提前锁定。
  • **Pain point** — 忘记加 `@Valid` 会导致请求体错误未被捕获,这正是很多新人忽视的数据校验层。

Request — 为什么只有 sku_id 与 quantity?

java public class CartAddRequest { @NotNull private Long skuId;

@NotNull
@Min
@Max
private Integer quantity;}
  • 没有 `userID` —— **pain point** — 前段再传也会被抹掉。
  • 没有 `price` —— **pain point** — 前段若尝试改价根本无效,因为价格来源于 SKU 的实时数据。说起来,
  • 没有 `checked` —— **pain point** — 新增默认勾选。让 UI 能立即看到已选效果,而不需要额外字段。

至于前段示例代码,

ts interface CartAddPayload { skuId:number;quantity:number } async function addToCart{ return request.post;}

Entity – 极简表结构防止冗余不一致

java @Data @TableName public class CartItemEntity { @TableI d private Long id;

 @TableField
private Long userI d;@TableField
private Long skuI d;private Integer quantity;private Integer checked;@TableField
private LocalDateTime createdAt;老实说,@TableField
private LocalDateTime updatedAt;}
  • 只保存最小事实—— **pain point** — 开发者常想把标题、价格等直接写进去,却会导致数据不同步。不过,
  • `checked` 默认值为已勾选—— **pain point** — 如果忘记设置默认值。会出现未勾选却计入合计的问题。

SQL – 唯一索引保障并发安全

sql CREATE TABLE IF NOT EXISTS mall_cart_item( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '购物车行 ID',user_id BIGINT UNSIGNED NOT NULL COMMENT '所属使用者 ID',sku_id BIGINT UNSIGNED NOT NULL COMMENT 'SKU ID',quantity INT UNSIGNED NOT NULL COMMENT '购买数量'。checked TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '是否勾选',created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY,UNIQUE KEY uk_mall_cart_item_user_sku,KEY idx_mall_cart_item_user_updated_at,CONSTRAINT chk_mall_cart_item_quantity CHECK,CONSTRAINT chk_mall_cart_item_checked CHECK ) );

  • `uk_mall_cart_item_user_sku` 防止同一使用者重复添加相同 SKU—— **pain point** — 即使业务层已检查,仍需靠 DB 索引防止高并发冲突。
  • `idx_..._updated_at` 调整「我的购物车」查询—— **pain point** — 忘记加索引会导致分页慢甚至超时。

Facade – 加入逻辑完整实现

java @Override public CartItemResponse addItem{

Long userId = currentUserService.requireCurrentUserI d;

SkuEntity sku = skuDbService.requireBy Id);

ProductEntity product= productDbServi ce.requireBy Id);

requireSaleableProductAndSku;

if>sku.getAvailableStock){ throw conflict;}

if)){ throw conflict;}

LocalDateTime now=LocalDateTime.now;CartIt emEntity entity=new Car tIt emEntity;entity.setU serI d;entity.setSku I d); entity.setQuantity); entity.setChecked;entity.setCreatedAt;entity.setUpdatedAt; 怎么说呢,

try{ cartIt emD bSe rvice.save;}catch{ throw conflict;} return assembleItem;}

  • `requireCurrentUser...` 把 *登录态* 当成唯一可信来源—— **pain point** — 切勿把 token 换成自定义 header 再自行解析,否则失去统一鉴权保障。
  • `requireSaleable...` 确认商品上架+价格+库存—— **pain point** — 前段隐藏按钮并不代表后台可以省略校验。
  • `exists...` + 捕获 `DataIntegrityViolationException` 双保险—— **pain point** — 并发场景下两次检查仍可能冲突,此时依赖唯一索引抛异常回滚事务即可。

    可售校验细节

    java private void requireSaleableProductAndSku{ boolean productOnSale=ProductStatus.ON_SALE.name.equals);boolean positivePrice=sku.getSalePrice!=null && sku.getSalePrice.compareTo>0;boolean hasStock=sku.getAvailableStock!=null && sku.getAvailableStock>0;if{ throw conflict;}} *Pain points*: 商品草稿 → 不可加;价格为负 → 不可加,库存为零 → 不可加。
    java Long userI d=currentUserSer vice.requireCurrentUs erI d;Listdlist);if){ return CardRes ponse.builder .items) .totalQuantity .estimatedTotal) .build;}

    批量获取 SKU 与 Product

    java Sets k uIds=cartItems.stream .map.collect);Maps k uMap= s k uD bSer v ice.listExistingB yIds.stream .collect));SetP roductIds=s k uMap.values.stream .map.collect);MapP roductMap= p roductD bSer vice.listExistingB yIds.stream .collect));*Pain point*: 若忘记批量查。只用 `for each cartRow -> queryOneSku` 将产生 N 次 SQL,严重拖慢响应。老实说,

    行组装

    java private CardIt emResp onse assembleIt em{ BigDecimal unitPrice=s ku==null?null:s ku.getSa lePri ce;BigDecimal lineAmt=?null: unitPrice.multiply)) .setScale;boolean saleable=s ku!=null && p rod uct!=null && ProductStatus.ON_SALE.name.equals) && unitPrice!=null && unitPrice.compareTo>0 && s ku.getAvailableSto ck!=null && s ku.getAvailableSto ck>=item.ge tQuantity;
     return Card ItEmResp onse.builder
    .id)
    .sku Id)
    .product Id)
    .productTitle)
    .skuCode)
    .specText)
    .currentUnitPri ce
    .quantity)
    .availableSto ck)
    .checked.equals))
    .saleable
    .lineAmount
    .updatedAt)
    .build;}
    
    *Pain points*:
    • saleable=false 会在 UI 中展示「已下架」或「库存不足」提示,而不是直接删掉行。
    • lineAmount=null 表示当前不可买,不参与 estimatedTotal

    汇总统计

    java int totalQty=respon ses.stream.mapToInt.sum;BigDecimal estTotal=respon ses.stream .filter)) .filter)) .map .reduce,BigDecimal::add);说起来,

    Pain points: 未勾选或不可售行不计入合计。否则使用者会看到「看不到但被算进去」的错误金额。


    DbService – 所有查询必须携带 UserID

    java /** * Shopping‑cart DB Service – every query MUST contain a user_id condition */

    public ListdlistByUs erIde{ return lambdaQuery.eq .orderDesc . list;}

    public Boolean existsByUs erIdeAn dsKuIde{ return lambdaQuery.eq . eq . ne xists;}

    Pain point: 若遗漏 eq 就会出现跨使用者数据泄漏,是最常见的安全漏洞。


    本地运行 &amp,amp;amp,amp;amp,amp;amp,amp;amp,amp;amp,amp;a curl 验证

    登录获取 token

    bash curl -sS -X POST http://localhost:/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"username":"xiaoming","password":"Mall123456"}'

    export USER_TOKEN='eyJhbGciOiJIUz...'

    未登录尝试加入

    bash curl -i -X POST http://localhost:/api/cart/items \ -H 'Content-Type: application/json' \ -d '{"skuId":101。"quantity":1}'

    正常加入并验证 userId 不可信

    SELECT id,userid,skuid FROM mallcartitem WHERE sku_id=102;

    数量超限验证

    重复加入报错

    商品下架后的表现

    将商品下架后 查询这方面,

    bash curl -i http://localhost:/api/cart/items \ -H "Authorization: Bearer $USER_TOKEN"


    常见错误清单

    错误场景 对应痛点 正确做法
    在 Request 中接收 userId 痛点① 永远使用 CurrentUserService.requireCurrentUserId
    productId 写进 cart 表 痛点② 保存 sku_id因为规格决定价格与库存
    前端自行提交 price 并依赖它 痛点③ 后端统一从 SKU 实时读取
    未检查商品上架状态就写入 痛点④ 调用 requireSaleableProductAndSku
    完全依赖本地去重。不建唯一索引 痛点⑤ 在业务层提前判断,同时建唯一索引防并发
    循环单条查询 SKU/Product 导致 N+ 痛点⑥ 批量收集 ID,一次性查询
    删除不可售行而非标记 痛点⑦ 保留记录并返回 saleable=false 给前端提示
    estimatedTotal 当作订单最终金额提交 痛点⑧ 创建订单阶段重新计算并锁定库存

    小练习与自检

    1. 绘制关系图画出 MALL_CART_ITEM ↔ MALL_PRODUCT_SKU ↔ MALL_PRODUCT​. 标明外键方向还有为何 cart 表只保存 sku_id。
    2. 解释 UserID 来源阅读 CartAddRequest,CartFacade.addItem。测试 shouldAddSaleableSkuForCurrentUserAndIgnoreSubmittedUserI d. JWT 如何成为唯一可信来源。
    3. Curl 验证未登录限制执行未带 Authorization 的 POST。请记录返回码与业务码,对比普通参数错误码 的区别。
    4. 验证唯一索引防重复对同一使用者多次发送相同 sku 的请求。观察返回码,并解释数据库约束如何保证幂等性。
    5. EstimatedTotal 算法拆解阅读 queryMyCart 中对流的过滤条件,说明只有 checked 且 saleable 的行才计入预估总额。

    下一章预告 — 从“意向”到“承诺”

    本章节已经完成:

    • 使用者身份安全边界;
    • 最小化持久化模型;
    • 完整的可售校验;
    • 幂等性与唯一约束;
    • 高效批量查询;
    • 金额预估 vs 实际成交;

    下一篇将进入真正交易链路:

    1️⃣ 从已勾选的购物车项生成订单;2️⃣ 在事务中重新校验价格 &amp,amp;amp,amp;a 库存,3️⃣ 锁定库存 并生成订单快照;4️⃣ 成功后删除已下单的 cart 行。

    这一步骤是保证“一致性”和“防止半成功状态”的关键,也是从“页面交互”迈向“业务事务”的必经之路。


    本章要点

    1️⃣ 后端购物车是 使用者维度 持久化资源,而非浏览器内存数组;2️⃣ /api/cart/items 不接受任何来自客户端的 userID;当前使用者只能从 JWT 中解析;3️⃣ 保存 S Ku_ID​;因为规格决定实际购买属性;4️⃣ 加入请求仅允许提交 S Ku_ID​ + quantity​;所有其它信息均由服务器生成或组装;5️⃣ 加入之前必须校验 SKU 是否存在、商品是否上架、价格是否正数还有库存是否充足;6️⃣ 同一使用者同一 SKU 不得重复插入——业务层提前判断 + 数据库唯一索引双保险;说起来,7️⃣ 查询时一次性批量获取所有相关 SKU 与商品。以避免 N+ 查询,8️⃣ 商品下架或库存不足时保留记录但标记 S ale able​=false​;怎么说呢,前段负责展示原因而非直接删除记录;不过,9️⃣ TotalQuantity​> 包含全部行数。用于角标显示,E stimatedTotal​> 则仅统计已勾选且可售的行;10️⃣ Shopping‑cart 金额仅作 预估 使用,真正成交金额必须在创建订单阶段重新计算并持久化为快照。

    如果你能够清晰回答:“为什么即使请求体里有 UserID​= 我们仍然把物品放进当前登录使用者的购物车?”还有 “为什么不可售行仍然保留但不会计入 E stimatedTotal​> ” 那么你已经彻底掌握了本章节所强调的两大主要能力——使用者资源隔离动态组装视图数据。祝贺你迈出了从 Front‑End 到 Full‑Stack 的关键一步!


标签: 维度

SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback