【电商项目数据库设计分析】

张开发
2026/5/11 2:43:18 15 分钟阅读

分享文章

【电商项目数据库设计分析】
一.关键部分表的总览1.0总览1.1付款和订单部分1.2商品部分1.3用户部分二.设计分析Business to Consumer : B2C型电商2.1数据库表设计概览数据库采用了典型的电商系统架构主要分为以下几个核心模块1. 用户模块User Domainuser_info用户基本信息表包含用户名、密码、昵称、头像、性别、手机号等核心字段以及 openid、unionid 用于第三方登录user_address用户收货地址表支持多地址管理包含省市区代码和完整地址有默认地址标识user_browse_history用户浏览历史记录表记录用户浏览的 SKU 商品user_collect用户收藏表记录用户收藏的 SKU 商品2. 商品模块Product Domainproduct商品主表包含商品名称、品牌、多级分类category1_id、category2_id、category3_id、单位、状态、审核状态等product_details商品详情表存储商品的图片 URL 和创建时间product_sku商品 SKU 表包含 SKU 编码、名称、缩略图、销售价格、市场价、成本价、规格、重量、体积等product_spec商品规格表定义规格名称和规格值如颜色红色、白色product_unit商品单位表如个、件、箱sku_stockSKU 库存表记录总库存、可用库存、已售数量和库存状态3. 订单模块Order Domainorder_info订单主表包含用户信息、订单编号、金额信息总额、优惠券、运费、收货人信息、订单状态、支付时间、发货时间、取消时间等完整订单生命周期order_item订单明细表记录订单中的每个商品项包含 SKU 信息、购买数量、价格order_log订单日志表记录订单处理过程和状态变更4. 支付模块Payment Domainpayment_info支付信息表记录支付方式、交易流水号、支付金额、支付状态、回调时间和回调内容设计特点采用微服务架构的数据库设计每个模块相对独立用户服务管理用户相关信息商品服务管理商品和库存订单服务处理订单流程支付服务处理支付回调通用字段规范所有表都有 id 作为主键大部分表包含 create_time、update_time 用于审计大部分表包含 create_by、update_by 用于追踪操作人使用 del_flag 实现逻辑删除而非物理删除部分表有 remark 字段用于备注电商核心业务支持支持商品 SKU/SPU 分离设计支持多级商品分类支持完整的订单流程下单、支付、发货、取消支持用户地址管理、浏览历史、收藏功能支持支付回调处理这是一个标准的 B2C 电商系统的数据库设计方案。2.2视图对象VO2.2.1 TradeVo类package com.spzx.order.domain.vo; import com.spzx.order.api.domain.OrderItem; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.math.BigDecimal; import java.util.List; /** * 交易页面数据回显的数据模型类 */ Data Schema(description 结算实体类) public class TradeVo { Schema(description 结算总金额) private BigDecimal totalAmount; Schema(description 结算商品列表) private ListOrderItem orderItemList; Schema(description 交易号) private String tradeNo; }2.2.2 ItemVo类package com.spzx.channel.domain; import com.alibaba.fastjson2.JSONArray; import com.spzx.product.api.domain.Product; import com.spzx.product.api.domain.ProductSku; import com.spzx.product.api.domain.vo.SkuPrice; import com.spzx.product.api.domain.vo.SkuStockVo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.util.List; import java.util.Map; Data Schema(description 商品详情对象) public class ItemVo { Schema(description 商品sku信息) private ProductSku productSku; Schema(description 商品信息) private Product product; Schema(description 最新价格信息) private SkuPrice skuPrice; Schema(description 商品轮播图列表) private ListString sliderUrlList; Schema(description 商品详情图片列表) private ListString detailsImageUrlList; Schema(description 商品规格信息) private JSONArray specValueList; Schema(description 商品库存信息) private SkuStockVo skuStockVo; Schema(description 商品规格对应商品skuId信息) private MapString,Long skuSpecValueMap; }2.2.3 SkuStockVo类package com.spzx.product.api.domain.vo; import lombok.Data; Data public class SkuStockVo { /** * 商品ID */ private Long skuId; /** * 可用库存数 */ private Integer availableNum; /** * 销量 */ private Integer saleNum; }2.2.4 UserInfoVo类Data Schema(description 用户类) public class UserInfoVo { Schema(description 昵称) private String nickName; Schema(description 头像) private String avatar; }三.Redis存储策略3.1存了什么3.1.1 登陆token3.1.2存储从Redis中没找到就从mysql中调到Redis中product:sku: skuId3.1.3分布式锁product:sku:stock: skuIdOverride public ListSkuPrice getSkuPriceList(ListLong skuIdList) { if (CollectionUtils.isEmpty(skuIdList)) { return new ArrayListSkuPrice(); } ListProductSku skuList productSkuMapper .selectList(new LambdaQueryWrapperProductSku().in(ProductSku::getId, skuIdList) .select(ProductSku::getId, ProductSku::getSalePrice, ProductSku::getMarketPrice)); if (CollectionUtils.isEmpty(skuList)) { return new ArrayListSkuPrice(); } return skuList.stream().map((sku) - { SkuPrice skuPrice new SkuPrice(); skuPrice.setSkuId(sku.getId()); skuPrice.setSalePrice(sku.getSalePrice()); skuPrice.setMarketPrice(sku.getMarketPrice()); return skuPrice; }).toList(); } /** * 服务提供者6个接口来服务于商品详情查询。需要进行优化提供查询效率。 * 需要使用redis来提高性能。 */ Override public ProductSku getProductSkuBySkuId(Long skuId) { try { //设置向Redis中存放商品Sku信息的key String productSkuKey product:sku: skuId; //从Redis中获取商品Sku信息 ProductSku productSku (ProductSku) redisTemplate.opsForValue().get(productSkuKey); if (null ! productSku) { log.info(命中缓存); return productSku; } //设置向Redis中添加分布式锁的key String lockKey product:sku:lock: skuId; //使用UUID随机生成一个字符串作为分布式锁的值 String lockValue UUID.randomUUID().toString().replaceAll(-, ); //添加分布式锁 Boolean flag redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS); if (flag) { try { //上锁成功 //再查询一次缓存有可能其他线程之前上过分布式锁已经查询过数据库并且放入了缓存 productSku (ProductSku) redisTemplate.opsForValue().get(productSkuKey); if(null ! productSku){ return productSku; } log.info(未命中缓存查询数据库); //查询数据卷 productSku getProductSkuByDB(skuId); //将数据存入到Redis中 redisTemplate.opsForValue().set(productSkuKey,productSku); return productSku; } catch (Exception e) { throw new RuntimeException(e); } finally { //创建Lua脚本 String luaScript if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end ; //释放锁 RedisScriptBoolean redisScript RedisScript.of(luaScript, Boolean.class); redisTemplate.execute(redisScript,Arrays.asList(lockKey),lockValue); } } else { try { //上锁失败等待 Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } //再次查询 return getProductSkuBySkuId(skuId); } } catch (Exception e) { log.info(连接Redis出现异常); //查询数据库 ProductSku productSkuByDB getProductSkuByDB(skuId); return productSkuByDB; } } //根据skuId查询数据库 public ProductSku getProductSkuByDB(Long skuId) { ProductSku productSku productSkuMapper.selectById(skuId); return productSku; }3.2持久化策略1. RDB 持久化快照方式第 115-116 行save 3600 1 300 100 60 10000900 秒15 分钟内至少 1 个 key 变化就保存300 秒5 分钟内至少 100 个 key 变化就保存60 秒1 分钟内至少 10000 个 key 变化就保存第 207-208 行dbfilename dump.rdbRDB 文件名第 299-300 行rdbcompression yes启用压缩第 141-142 行rdbchecksum yes启用校验和2. AOF 持久化追加日志方式第 67-68 行appendonly no ❌ AOF 未开启第 37-38 行appendfsync everysec如果开启 AOF每秒同步一次第 101-102 行appendfilename appendonly.aofAOF 文件名第 133-134 行aof-use-rdb-preamble yesAOF 使用 RDB preamble3. 数据存储目录第 35-36 行dir /data数据保存在容器的/data 目录结论你的 Redis 数据断电后还在是因为RDB 持久化在工作。Redis 会根据 save 规则定期生成数据快照dump.rdb 文件即使断电也能恢复到最后一次快照的状态。不过注意你的 AOF 是关闭的appendonly no。如果想要更安全的持久化数据丢失更少建议开启 AOF这样可以记录每次写操作最多只丢失 1 秒的数据。

更多文章