
简介这是一个基于ASP.NET与SQLServer的葡萄酒网上商城销售系统完整项目资源面向学习Web开发的学生或需要搭建类似电商网站的开发者。系统设计了普通会员、商户会员和管理员三种角色涵盖商品管理、订单处理、购物车、商户信息维护等功能同时支持留言反馈与农村资讯发布能够作为课程设计或毕业设计的参考原型。资源包共1277个文件压缩包大小约56.25MB其中包含582个gif、227个jpg、136个png等大量页面图片资源以及109个cs后端代码、47个aspx页面、61个js脚本、32个css样式表并附带SQL数据库脚本和mdb数据库文件基本覆盖了网站开发所需的前端、后端与数据库层面。已有34人学习下载。通过这份资源读者可以直观了解ASP.NET Web窗体项目的目录结构、页面与代码分离模式、SQLServer数据库表设计思路以及商城类系统业务逻辑的实现方式。配套的图片和UI素材也可直接用于界面展示。内容预览中涉及商品添加、商户编辑、订单列表、购物车等多个功能页便于按模块查阅学习。 做葡萄酒电商这个项目缘起其实挺实际一个做进口酒贸易的朋友找到我说他们线下的渠道越来越难走想搞一个网上商城把酒庄直采的资源直接对到消费者端。我起初以为就是个普通电商套壳真动手才发现葡萄酒这个品类在电商系统里的坑比想象中多得多——SKU维度多、价格体系复杂、库存批次要求严格还涉及防伪溯源和年龄验证这些特殊需求。花了两个月把整套基于.NET的商城系统从0到1搭完踩了不少坑今天把这套系统的设计思路和落地细节完整梳理一遍给正在做类似垂直电商项目的朋友一个参考。1. 先搞明白葡萄酒电商和普通电商的差异再动手写代码很多做电商系统的人有个误区以为把商品表、订单表、购物车表一建套个通用模板就完事了。但葡萄酒这个品类有几个特点直接在架构层面影响数据表设计和业务逻辑如果前期不考虑后面改起来就是伤筋动骨。1.1 葡萄酒SKU的属性空间大到你难以想象一瓶红酒的完整属性至少要包含酒庄、产区国家/大区/子产区、葡萄品种可能有多个、年份Vintage、类型干红/干白/甜白/起泡、酒精度、容量、酿造工艺、陈年潜力、口感特征、获奖记录、进口商信息。这些属性还要分维度去筛——用户可能按法国波尔多赤霞珠2018年来找酒也可能按口感偏果香度数适中来搜索。我当时的做法是建立属性-属性值双层结构用一张ProductAttribute表和一张ProductAttributeValue表把可枚举属性产区、品种、类型和自由文本属性品酒笔记、酿造工艺分开存。可枚举属性走关联表做筛选自由文本走全文索引。这样做的代价是查询时多几个Join但换来了新增属性类型时不用改表结构的灵活性。葡萄酒电商的选品范围会持续变你今天只卖法国酒明年可能加智利、加澳洲、加新西兰属性字典必须能动态扩展。1.2 库存和批次是葡萄酒电商的命根子普通电商管库存只需要管SKU总量但葡萄酒不行。同一款酒不同年份是不同批次同一年份不同进口批次也可能有细微口感差异。更麻烦的是葡萄酒的库存周转天然就慢一批货从国外酒庄直采到清关入仓可能要长达半年资金占用极其严重。系统必须能追踪到每一批货的成本才能算清楚毛利率。所以我没有用传统的Inventory表记一个总数而是设计了批次库存模型每个SKU对应多个StockBatch每个批次记录采购价、入库时间、库位、数量。前台展示的是所有批次可用数量之和但后端下单扣减时指定批次先进先出。这个设计在后面做促销活动和成本核算时省了特别多事。1.3 年龄验证不是可选项是合规底线葡萄酒在线销售有个绕不开的环节购买者必须是成年人。这个在国内的电商平台可能不强制但作为垂直品类的自营官网我还是做了两层验证注册时要求填写出生日期前端计算年龄不满18周岁直接拒绝注册下单支付前再次弹窗确认我已年满18周岁并留痕。虽然不能做到金融级实名认证但至少要尽到形式审查义务这一块法务建议是很有必要的。2. 技术选型.NET生态里我是这么搭配的这个项目我选择了ASP.NET Core 8作为主框架原因很直接本身是Windows技术栈出身团队对C#最熟而且.NET 8的AOT、性能调优和中大型业务系统的工程化成熟度都已经相当能打。有人问为什么不考虑Java或者Go答案很简单——项目周期短、团队技术栈决定了什么用什么硬换语言不叫技术选型叫自找麻烦。2.1 后端框架和ORM的取舍ASP.NET Core带原生的依赖注入、中间件管道和配置系统我用起来非常顺。ORM层面我用的是EF Core 8配合SQL Server 2019。EF Core在这个体量的项目里完全够用而且迁移Migration机制对于迭代频繁的商城系统是刚需——每次加个字段、改个索引不像以前写SQL脚本还要手动维护版本。不过EF Core有个坑得提醒默认的Include行为在深层级联查询时会产生巨大的笛卡尔积。比如我查一个订单需要带出用户信息、订单明细、每个明细的商品和SKU、SKU的酒庄与产区如果用Include一层层加生成的SQL会膨胀得很快。我的解法是拆分查询模式Split Query或者对于复杂的报表查询直接写原生SQL映射到DTO别懒。2.2 前端页面和交互方案商城前台一开始考虑过前后端分离VueWeb API。但最后我选了Server-Side Rendering服务端渲染 Razor Pages的方案主要原因有两个第一商城前台需要SEO虽然现在谷歌能抓取JS渲染的内容但百度对SPA的收录依然不友好第二项目人力有限SSR模式下一个人同时搞定后端和页面不用维护两套代码和跨域联调。页面上用了Bootstrap 5做基础样式配合少量原生JavaScript和jQuery做交互。真正用到复杂前端交互的地方其实不多——购物车加减、商品筛选联动、订单状态轮询这些jQuery完全能胜任。Bootstrap的好处是组件齐、响应式开箱即用移动端适配基本不用额外操心。2.3 缓存与中间件组件缓存方面用了Redis主要缓存三类数据商品详情页的HTML片段因为商品信息基本不变可以整页缓存1小时、热门搜索词和筛选结果集、登录用户的Session分布式环境下多实例共享Session必需。其他中间件我挑了这几个Serilog做结构化日志文件数据库双写、FluentValidation做参数校验比DataAnnotations灵活可以跨类复用规则、AutoMapper做对象映射虽然有时觉得映射代码写起来更累但项目后期DTO字段多了之后AutoMapper能减少大量肉眼判断的重复工作量。3. 数据模型和核心业务逻辑的设计细节这一块我必须说花的时间最长、改动的次数也最多。商城系统最忌讳的就是一上来就建一堆表结果字段跟实际业务对不上。我建议从核心链路倒推数据模型用户登录 → 浏览商品 → 加入购物车 → 下单 → 支付 → 发货 → 确认收货 → 评价每个环节涉及的数据结构先画清楚再落库。3.1 核心表结构一览经过两轮重构最终的核心表结构是这样的Users用户主表字段包括用户名、密码Hash采用PBKDF2算法不存明文、手机号、邮箱、出生日期、注册时间、会员等级。Products / ProductSkus / ProductAttributeValues商品三级模型。Products存商品通用信息标题、主图、详情富文本ProductSkus存具体可售卖的SKU关联酒庄、年份、容量、条码、市场价、销售价ProductAttributeValues存某个SKU具体拥有的属性值集合。StockBatches / InventoryTransactions:StockBatches是批次库存表记录每个批次的数量、锁定数量、采购成本、入库时间InventoryTransactions是库存流水表每次入库、扣减、锁定、解锁、退货回填都记一条用于审计和异常排查。Carts / CartItems购物车主表和明细表。购物车设计中比较关键的是把商品信息和价格存一份快照到CartItems中。因为用户加入购物车后可能三个月后才下单那时候商品价格、上下架状态可能都变了如果不下单时读取最新价格容易引发价格争议。下单时再做一次价格校验和更新。Orders / OrderItems / Payments / Shipments订单域四张主表。Orders存订单总金额、优惠金额、实付金额、状态OrderItems快照了下单时的商品名称、单价、数量、所属批次IDPayments记录每次支付的流水号和状态Shipments记录物流公司和运单号。3.2 订单状态机的核心流转订单状态我设计成状态机模式重点防两个问题状态跳变无校验和状态流转不落日志。比如一个已取消的订单理论上不能回到待发货状态但如果没有状态机硬约束代码里一个Bug就可能把状态改错到时候对账就全乱了。订单状态转移我塞在一个接口里每个状态定义允许的下一状态集合非法状态跳转直接抛异常。同时在状态变更时记录操作人、操作时间、旧状态、新状态、操作原因到OrderStatusLogs表。这样出问题的时候能追溯到是哪一步、谁干的、为什么。3.3 库存扣减的并发方案这是整个系统里我花心思最多的一个模块。商城系统高并发场景下超卖是必须避免的大事故。我采用了两层方案第一层是Redis预扣库存。用户提交订单时先用Redis的DECR命令对商品库存Key做预扣如果返回负数就说明库存不足直接拒绝下单。这层保证了大部分请求在进入数据库之前就被拦截掉比如一次秒杀活动1万库存来100万请求Redis先挡掉99万个。第二层是数据库乐观锁兜底。Redis是内存操作万一宕机会丢数据所以真正生成订单时必须用数据库行锁校验库存UPDATE StockBatches SET LockedQuantity LockedQuantity quantity WHERE Id batchId AND (AvailableQuantity - LockedQuantity) quantity如果影响行数为0说明这个批次当前可用库存不够订单创建失败。这样Redis和数据层的库存逻辑形成了双重保险。等用户支付成功后再从LockedQuantity转到实际的扣减如果超时未支付则释放锁定数量。锁库存和释放库存都走InventoryTransactions流水保证每笔变动可审计。4. 支付对接里的三个坑每个都是真金白银买来的教训支付环节是商城系统的命门也是最容易出错、出错后果最严重的地方。我对接的是通用支付网关可以理解为支付宝微信这类渠道的聚合模式整个联调和测试过程花了不小精力踩了三个比较典型的坑。4.1 支付回调的幂等处理不能靠状态判断支付成功异步通知来了之后第一件事是判断订单当前状态。如果订单已是已支付状态就直接返回成功不再重复处理。当初我天真地认为这就是幂等直到一次支付网关连着重试了三次通知数据库因为延迟导致三次回调同时在执行状态判断都看到订单是待支付结果把用户余额入账了三次。解决方案是给支付回调表加唯一约束PaymentId TransactionId并且回调处理逻辑包在数据库事务里先查回调记录是否存在不存在才插入并更新订单。数据库唯一约束保证了并发情况下只有一个请求能插入成功。4.2 金额计算统一用decimal禁止用float/double听起来像常识但代码里稍微不注意就会踩中。浮点数在二进制里无法精确表示0.1加0.2不等于0.3。用在金额上轻则几分钱差异重则对账不平。我在项目里做了一个强约束所有涉及金额的字段全部用decimal类型DTO也用decimal禁止任何隐式转换为double。EF Core这边直接配置HasColumnType(decimal(18,2))。另一个和金额相关的坑是折扣精度。满减、折扣、会员价等多重优惠叠加时计算顺序不同可能导致最终金额差一分钱。我的规则是先算单品折扣价 → 再算满减优惠 → 最后算会员折扣每一步四舍五入保留两位最终实付金额和支付网关回调金额完全一致后用整数分去比对避免浮点误差。4.3 支付回调的验签必须失败也要记录日志验签失败不代表是黑客攻击更常见的是商户密钥配置错误、参数编码不一致、或者回调里多了个字段导致签名串拼接错误。我见过不少系统的处理方式是验签失败直接丢弃请求什么都不记。我的建议是验签无论成败都打日志尤其是失败的记录完整原文、签名串、签名值、验签结果。这样联调时遇到问题日志里直接能定位到是拼接顺序错了还是密钥错了不用一遍遍让支付渠道那边重发通知。4.4 弱网场景下的支付超时处理用户在支付网关那边完成了付款但因为网络原因支付结果没有正常跳转回商城用户浏览器的请求一直转圈然后不小心关了页面。这种情况用户大概率会认为支付失败了转身去别家店重买或者找客服投诉。但钱其实已经扣了。我这边做了两个缓解措施一个是在下单后的支付等待页加一个轮询接口每3秒查一次订单状态只要异步通知到了就能自动刷新页面状态另一个是在用户主动查询订单详情时后端实时向支付网关发起主动查询确认支付结果而不是只依赖被动通知。这两个措施跑下来因为支付了但不显示导致的客诉降了大约九成。5. 商品检索和展示的优化策略商城系统做得是否好用检索体验占了一半。葡萄酒领域用户习惯用多维度筛选有时候精确关键词反而不重要。比如我想找波尔多甜型2016年价格200到500这是典型的组合筛选场景。5.1 用缓存避免让数据库扛所有筛选压力组合筛选如果直接拼SQL去查数据库任何一个维度稍微热点都可能把数据库打垮。我做了两级缓存第一级是商品基础数据全量缓存到Redis用Hash结构存商品概要信息标题、品牌、价格、主图、评分只保留最常用于列表和筛选的字段。第二级是针对每日热门的筛选组合把筛选结果的主键列表缓存在Redis的ZSet里配合分页返回。5.2 全文搜索库的引入时机项目初期SKU量只有几百个EF Core的Contains模糊查询完全够用。但后面扩展SKU到三千多个、加上长文本描述字段后模糊查询的速度明显下降。我在项目中期引入了Elasticsearch做商品搜索数据同步通过RabbitMQ异步写入。这套组合的好处是商品详情页的大文本字段品酒笔记、酿造工艺也能参与搜索比如用户搜黑李子气息能把匹配的酒款全部带出来这种体验是数据库Like查询给不了的。5.3 图片资源的分级处理葡萄酒的商品主图一般都很讲究高清原图动辄几兆。如果直接丢给前端去加载移动端用户体验会非常差。我的做法是上传原图后自动生成三档尺寸缩略图100x100列表页、中等图400x400详情页主图、原图详情页高清图点击放大看。三个尺寸在存储层就提前生成好CDN按URL规则分发前端只加载对应尺寸的图。6. 部署、安全加固和一些收尾经验从开发到正式上线部署和安全是最后一道关也是很多人的薄弱环节。我分享几个实际落地中总结出的经验。6.1 部署架构最终部署方案是两台Windows Server IIS前置Nginx做负载均衡和HTTPS终结。为什么不直接用Linux Docker因为项目里有两个老旧的Windows依赖组件一个报表组件和一个第三方打印插件迁移成本太高评估后保留Windows环境反而更经济。.NET应用发布用Visual Studio的Publish Profile直接发布到文件共享目录IIS站点指向这个目录配合Redis做Session共享两台机器来回切换没有任何登录态丢失问题。发布脚本用PowerShell写成一键发布目前团队其他成员也能用不再依赖我一个人手动操作。6.2 必须做好的几项安全加固第一是HTTPS全站强制开启用Nginx统一做443终结HTTP 80端口直接301跳转。第二是上线前对全站做了一次安全扫描重点关注SQL注入、XSS、越权访问几类问题。EF Core的参数化查询天然防SQL注入但在几个用原生SQL拼接的报表模块里我还是重新审查了一遍确认没有直接拼接用户输入。第三是后台管理地址改成了非标准路径并且加了一套独立的IP白名单机制只有公司出口IP能访问后台从入口降低被爆破的风险。6.3 线上问题排查的经验系统上线后遇到一个比较头疼的问题每天早上九点到十点订单接口响应时间突然翻倍但服务器的CPU和内存都不高。一开始怀疑是数据库慢查询查了一圈发现不是。后来偶然看到Redis监控面板每天清晨这个时段GET操作量激增。排查后发现后台有个定时任务会在每天早上刷新所有商品的热度评分一刷新就批量删除并重建部分Redis缓存Key结果在业务高峰期形成了缓存穿透——大量请求打到数据库上而数据库的执行计划缓存又因为并发SQL多变而失效导致整体响应变慢。解决方案是优化定时任务的执行时间错开到凌晨业务低谷同时把缓存重建改成分批懒加载模式。修改之后那根刺眼的响应时间曲线彻底消失了。7. 一点实用的收尾建议如果让我回头重写一次这个系统我会在第一周就去把支付联调和日志体系搭建好而不是等到功能开发得差不多才考虑。支付是整个系统里最容易出问题也最难排查的模块给它预留足够长的测试时间比多写两个后台页面有价值得多。第二个建议是所有涉及金额、库存、积分这类关键状态的修改一律走数据库事务加行级锁不要为了省一条SQL而舍弃数据一致性。一次微小的并发Bug可能带来几千甚至几万元的损失相比之下那点性能开销完全不值一提。第三个建议是商城系统的后台管理别急着堆功能先把订单、商品、库存、会员这四块的权限细分做好。运营每次操作能追溯到人晚上睡觉都踏实。这个项目从接手到上线用了大约十周核心业务代码量在2万行左右。说实话用.NET做垂直电商系统开发效率比我预想的高平台本身的稳定性和生态支持也都撑得起中等规模商城的体量。希望这篇项目分享能给准备做类似系统的朋友一些参考少走几个弯路。本文还有配套的精品资源点击获取