你有没有发现一个现象:网上商城类的项目教程,十个里有九个是Java写的,剩下一个还是Python。真到了.NET这边,能一口气打通从数据库设计、后端接口、后台管理到部署上线的完整案例,反而成了稀缺资源。我自己当初接这个葡萄酒商城的需求时,翻了半天资料,最后干脆自己边做边踩坑,把整个流程走了一遍。这篇文章不属于教科书,属于那种“你照着做大概能少熬两个通宵”的实战记录。
这项目名字挺直白:基于.NET的葡萄酒网上商城销售系统。做出来之后,它的本质其实就是一套典型的B2C电商系统,商品、购物车、订单、支付、会员、后台管理一样不缺,但选品是葡萄酒这个垂直品类。为什么选葡萄酒而不做综合百货?因为垂直品类有一个天然优势:商品SKU少但属性维度深,葡萄酒要管产地、年份、葡萄品种、酒精度、容量、等级、醒酒时间、配餐建议,这种深度结构化数据比百货那种“只分颜色和尺码”的商品更能体现系统的数据建模水平,拿去面试或做课程设计,反而容易讲出亮点。
考虑到技术栈已经锁定在.NET生态,核心关键词其实是C#、ASP.NET Core、EF Core或者SqlSugar、SQL Server或MySQL、Redis缓存、JWT认证、Bootstrap或Vue。下面的内容我会把整套系统从业务建模到代码落地的关键节点都拆开讲,重点是那些常规文档里不会写、但我实际做项目时才发现的细节。
1. 为什么是葡萄酒商城:垂直品类带来的独特业务逻辑
1.1 葡萄酒商品的特殊性对系统的影响
刚开始做需求分析时,如果你把葡萄酒商城当成普通杂货铺去做,后面改起来会非常痛苦。建议你先花半天时间梳理葡萄酒这个垂类到底特殊在哪,这直接决定你的数据库表怎么建。
葡萄酒商品有几个特征:
- 属性层级深:酒的款式Name、酒庄Chateau、产区Region、年份Vintage、葡萄品种GrapeVariety、酒精度Alcohol、容量Volume、甜度Sweetness、评分Score。这些信息缺一不可,而且用户下单时基本都会关心。
- 库存批次差异大:同一款酒不同年份价格可能差几倍,2010年的拉菲和2015年的拉菲完全是两个价格体系。所以库存必须能定位到具体年份的具体SKU。
- 图片和详情要求高:葡萄酒讲究视觉呈现,瓶身图、酒标特写、酒液颜色图,一个商品可能五六张图。
- 促销场景特殊:整箱购买优惠、会员折扣、限时闪购、跨店满减,这些玩法跟服装鞋帽其实差不多,但葡萄酒多一个“年份清仓”的促销逻辑。
所以建表时不能简单搞一张Product表加一张ProductImage表就完事,那是最偷懒的写法,但对代码的可维护性伤害很大。我的建议是拆成:Product(商品主体)、ProductAttribute(属性键值对)、ProductSku(具体SKU库存和价格)、ProductImage(图集)。
1.2 从零梳理业务需求清单
这个项目如果要写得有东西,规划时就把下面这些模块列清楚,每一项都对应后面的代码结构和数据库表:
- 用户端:注册登录、浏览酒款、按产区/品种筛选、搜索、加入购物车、结算下单、订单列表、个人中心、收藏酒款。
- 管理端:商品管理(从上架到下架)、分类管理、SKU库存管理、订单处理(发货、取消)、会员管理、促销活动配置、数据统计看板。
- 通用能力:图片上传、登录鉴权、日志记录、缓存处理。
你把这几个模块写进README里,项目看起来就正规很多。当然,实际工作量最大的还是前两个,特别是后台订单处理和库存联动,最容易出bug。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型复盘:.NET系各组件怎么搭配才不踩坑
2.1 框架版本的选择逻辑:.NET 6还是.NET 8?
现在做新项目,我的建议是直接**.NET 8**。原因不复杂:
- .NET 8是LTS(长期支持)版本,技术支持到2026年11月,比.NET 6更晚到期。
- 性能上有明显提升,特别是原生AOT编译和JIT改进,对商城这种IO密集场景友好。
- VS2022对.NET 8支持很好,创建项目、调试、发布都很顺畅。
如果你还在纠结“.NET Framework 4.8还是.NET 8”,别想,直接.NET 8。这个项目的所有代码我都默认基于ASP.NET Core 8。Windows服务器上只要装了对应的.NET 8 Hosting Bundle就能跑,不用像老Framework那样装单独运行时。
提示:如果你最后部署的服务器比较老(比如Windows Server 2012),注意确认它是否支持.NET 8的运行时。Server 2012需要打补丁才能装高版本.NET,这个细节经常让人卡在部署环节。
2.2 数据访问层的取舍:EF Core还是SqlSugar?
数据访问层的选择是这个项目的一个分岔口。
EF Core是微软官方的东西,跟ASP.NET Core配套,用Code First模式,一顿dotnet ef migrations add就能把表建出来。它的优点是很“正规”,面试时提EF Core是加分项。缺点是延迟加载容易踩坑,导航属性忘了处理会循环序列化,然后报一个你找半天都找不出原因的错误。还有,如果你对SQL比较熟,EF Core生成的查询有时候会让你觉得“这写的是什么玩意儿”,得手动FromSqlRaw去干预。
SqlSugar是国产ORM,上手更快,文档基本是中文,API设计也很直观。我见过不少.NET项目用它开发效率特别高。
我自己的经验是:如果你这个商城项目是用于毕业设计或者给公司做内部系统,用EF Core是稳妥的;如果是急着上线、你自己对SQL更有把握,SqlSugar也很香。两个方案我都试过,下面代码示例我会混合给出,关键思路一样。
2.3 前端方案:服务端渲染还是前后端分离?
这是很多人纠结的问题,我的结论很直接:别一步到位搞前后端分离。
如果你自己一个人开发,前端再用Vue/React另起一个项目,那意味着你要维护两套代码、处理跨域、做两套部署。对于这个葡萄酒商城项目,用Razor视图引擎做服务端渲染是效率极高的选择,尤其是管理后台,为了那点交互体验去搞前后端分离,成本完全不成比例。
但如果你希望项目“高级感”更强,并且你本来就会Vue,那可以用Web API + Vue + ElementUI做一个独立前端工程,后端完全按API接口输出。两个方案我都走过:第一个方案开发速度大概快40%,但第二个方案简历上好看一点。看你的目标取舍。
我这次实际采用的是折中方案:前端商城页面用Razor,后台管理页面用简单的Vue + Axios调用API。这样既不会太累,又能体现前后端交互能力。
2.4 中间件清单
这个项目用到的中间件/第三方库,我列一张表给你参考:
| 组件 | 用途 | 备注 |
|---|---|---|
| Autofac | 依赖注入容器 | 如果项目规模不大也可以用原生DI,但Autofac做模块化很舒服 |
| JWT Bearer | 用户登录鉴权 | 商城API和Razor共用一套鉴权逻辑 |
| Swashbuckle/Swagger | API文档 | 开发调试必备 |
| Serilog | 日志记录 | 文件日志为主,不用上ELK |
| StackExchange.Redis | 缓存和验证码存储 | 需要准备一个Redis环境 |
| AutoMapper | DTO映射 | 减少写一堆赋值代码 |
| FluentValidation | 参数验证 | 在API层做参数校验,比手写if优雅 |
| QRCoder | 生成支付二维码 | 如果需要模拟扫码支付 |
3. 核心模块实现:从商品库到下单支付的完整链路
3.1 商品模块的正确做法:不要把鸡蛋全放在一张表里
先说商品表的建表思路。我做这个项目时总结了一套通用模型,它的核心是把公共字段和扩展字段分开。
sql复制-- 商品主体表
CREATE TABLE Product (
Id INT PRIMARY KEY IDENTITY,
Name NVARCHAR(200) NOT NULL, -- 酒款名称
CategoryId INT NOT NULL, -- 分类Id
Description NVARCHAR(MAX), -- 商品详情
Status INT NOT NULL DEFAULT 0, -- 0草稿 1上架 2下架
CreateTime DATETIME DEFAULT GETDATE()
);
-- SKU表(具体款式:年份/容量/价格/库存)
CREATE TABLE ProductSku (
Id INT PRIMARY KEY IDENTITY,
ProductId INT NOT NULL,
Spec NVARCHAR(200) NOT NULL, -- 例如:2018年份 750ml
Price DECIMAL(10,2) NOT NULL, -- 售价
OriginalPrice DECIMAL(10,2), -- 划线价
Stock INT NOT NULL DEFAULT 0, -- 库存
Sales INT NOT NULL DEFAULT 0 -- 销量
);
之前提到ProductAttribute,那是用来存“产地、葡萄品种、酒精度”这类信息的,用键值对方式存,将来加属性不用改表结构:
sql复制CREATE TABLE ProductAttribute (
Id INT PRIMARY KEY IDENTITY,
ProductId INT NOT NULL,
AttrName NVARCHAR(50) NOT NULL,
AttrValue NVARCHAR(200) NOT NULL
);
查询主界面时,直接JOIN ProductSku取最低价和总库存,列表页展示用。详情页再按ProductId去查Attribute拼SVG或者Table展示。
用代码实现列表查询时,需要分页。我推荐你自己封装一个PagedResult<T>,不要每次都返回整张List,那是写Demo才干的。
csharp复制public class PagedResult<T>
{
public int Total { get; set; }
public int PageIndex { get; set; }
public int PageSize { get; set; }
public List<T> Items { get; set; }
}
3.2 用户注册与JWT鉴权:别拿Session糊弄事了
既然用了ASP.NET Core,鉴权首选JWT,原因很简单:Razor页面和API混合架构下,Session在多服务器部署时很麻烦,JWT是无状态的,还能直接给App端复用。
注册时密码存储需要用BCrypt.Net或者微软自带的PasswordHasher<T>。千万别明文存密码,这是底线。我用的是Microsoft.AspNetCore.Identity的PasswordHasher,简单可靠:
csharp复制var passwordHasher = new PasswordHasher<User>();
user.PasswordHash = passwordHasher.HashPassword(user, dto.Password);
登录成功之后签发JWT:
csharp复制var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.UserName),
new Claim(ClaimTypes.Role, user.Role)
}),
Expires = DateTime.UtcNow.AddHours(12),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
注意:JWT的Key一定不要写在代码里硬编码,放
appsettings.json里,并且在部署时通过环境变量覆盖。GitHub泄漏Key的事情发生过太多次,别当新闻看。
3.3 购物车实现:Redis还是数据库?
有人会把购物车做成数据库表,订单里CREATE一个Cart,加购就Insert。这样做问题不大,但用户每次请求都要查库,而且购物车这个数据本来就具有“临时性”,放到Redis更合适。
Redis里我用Hash结构存储,Key为cart:{userId},Field是SKU Id,Value存数量和加购时间:
csharp复制var key = $"cart:{userId}";
var field = skuId.ToString();
var count = 1;
await _redis.HashIncrementAsync(key, field, count);
结算时,从Redis里扫出全部Items,然后拿着SKU Id列表去数据库查最新价格和库存,重新计算购物车金额。这里的关键点是:结算价格不能直接信任购物车里的缓存价格,必须去数据库读实时价格,防止恶意篡改或者后台改价没同步。
购物车转订单的流程:
- 锁定Redis购物车。
- 批量查SKU最新价格和剩余库存。
- 校验库存充足性。
- 计算总金额,生成主订单和订单明细。
- 扣除库存。
- 删除Redis购物车记录。
- 如果支付失败/超时,回滚库存(在这个项目里我用的是定时任务扫描超时订单恢复库存)。
3.4 下单与库存扣减:避免超卖的唯一方案
超卖是商城最重要的技术题。Java那边常说悲观锁、乐观锁、分布式锁,.NET这边的原理一模一样。
我推荐最稳妥的方案:下单前用数据库行锁锁定SKU行,扣减时加库存条件,EF Core实现:
csharp复制var sku = await _db.ProductSkus
.FromSqlRaw("SELECT * FROM ProductSku WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}", skuId)
.FirstOrDefaultAsync();
if (sku.Stock < buyCount)
{
throw new Exception("库存不足");
}
sku.Stock -= buyCount;
await _db.SaveChangesAsync();
这里WITH (UPDLOCK, ROWLOCK)是关键,SQL Server会锁定这一行直到事务提交,两个并发请求同时买最后一个库存时,第二个请求会因为锁等待到第一个提交后才读到新的库存值,自然就不会超卖。
如果并发量不大,这个方案完全够用。如果真的搞秒杀级别的并发,那才需要考虑Redis预扣库存+异步队列。对于葡萄酒店铺,这个场景不太现实。
3.5 订单状态机:防止订单状态乱跳
订单状态是这个系统里最容易写乱的地方。我画了一个简单的状态流转逻辑:
- 待支付(0) → 支付后 → 已支付/待发货(1)
- 已支付/待发货(1) → 商家发货 → 已发货(2)
- 已发货(2) → 用户确认 → 已完成(3)
- 待支付(0) → 用户取消/超时 → 已关闭(4)
在代码里不要到处直接改Order.Status,而是封装一个OrderStatusService来转发状态。这样后续加“申请退款”“售后”状态时,只需要在状态机里加节点,不会牵连其他业务。我见过太多项目因为订单状态在Controller里到处order.Status = xxx,最后改一个需求要翻遍整个代码库。
4. 权限与安全:一个商城系统最容易忽视的细节
4.1 区分用户身份:会员和管理员不能走在同一条路上
商城有前台用户(Customer)和后台管理员(Admin),这个项目里我用一张User表存所有账号,通过Role字段区分:customer和admin。
登录接口返回的JWT里带上Role,然后在每个Admin的Controller上加上特性:
csharp复制[Authorize(Roles = "admin")]
public class AdminController : ControllerBase
这样非管理员调用后台API会直接返回403。Razor页面方面,为了省事我写了一个基类AdminBasePageModel,在OnGet里判断JWT是否存在且有效,无效就重定向到登录页。
经验:如果你管理后台和安全页面没有做任何角色控制,一旦被扫到后台地址,分分钟被拖库改价。务必每个后台接口都过一遍权限。
4.2 防SQL注入、XSS和CSRF:不写裸SQL不是终点
项目中用EF Core的LINQ查询,SQL注入风险天然很低。但如果你习惯写FromSqlRaw,记得用参数化查询,无论如何都不要拼接字符串。这是我见过新手最容易踩的坑:
csharp复制// 危险写法,千万别学
var sql = $"SELECT * FROM Product WHERE Name LIKE '%{keyword}%'";
// 安全写法
var sql = "SELECT * FROM Product WHERE Name LIKE @keyword";
XSS方面,Razor视图默认会编码,所以显示用户输入内容时直接用@Model.Note是安全的。但在某些场景下需要用Html.Raw,比如商品详情富文本,这时候必须对白名单标签进行过滤,推荐用HtmlSanitizer库清洗后再入库。
CSRF(跨站请求伪造)在ASP.NET Core里默认有AntiForgeryToken机制,在表单里加@Html.AntiForgeryToken(),后端[ValidateAntiForgeryToken]即可。Razor页面这块容易忽略,但务必加上。
5. 性能优化与部署:让系统真正能跑起来
5.1 列表页为什么慢?缓存和索引一个都不能少
葡萄酒商城首页、分类页是流量大头。如果每个请求都去查数据库、拼接DTO、渲染视图,数据库压力会非常大。我的解决办法:
-
Redis缓存热点数据:分类下的商品列表7天都不会变几次,完全可以缓存10分钟。Key设计为
product:list:{categoryId}:{pageIndex}:{pageSize},读取时先查Redis,没命中再查数据库并回填。 -
给外键和常用查询字段加索引:Product表的CategoryId、ProductSku表的ProductId、Order表的UserId和CreateTime,这些都是查询高频字段,建索引后查询性能立竿见影。不建索引,数据量几百条没感觉,到几千条分页就开始变慢。
-
分页查询不要用Skip大偏移量:当页码很大时,
Skip(10000).Take(20)会越来越慢。实际开发中可以用键集分页(keyset pagination),用WHERE Id > @lastId ORDER BY Id LIMIT 20代替。
5.2 图片处理:不要直接存Base64到数据库
商城系统图片多,葡萄酒的照片又要求高清。我建议图片用wwwroot/uploads/目录存储,或者放阿里云OSS/腾讯云COS,数据库只存路径。
如果你图省事把图片Base64直接存到数据库,用不了多久数据库就会膨胀到不可收拾。我做这个项目时就犯过这个错,测试阶段传了几张酒标照片,数据库直接从200KB涨到500MB,后来全部清理改成文件存储,才算正常。
上传接口用IFormFile接收文件,限制大小和格式:
csharp复制[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
var allowed = new[] { ".jpg", ".jpeg", ".png", ".webp" };
var ext = Path.GetExtension(file.FileName).ToLower();
if (!allowed.Contains(ext))
return BadRequest("不支持的图片格式");
if (file.Length > 5 * 1024 * 1024)
return BadRequest("图片大小不能超过5MB");
var uploadDir = Path.Combine(_env.WebRootPath, "uploads");
if (!Directory.Exists(uploadDir))
Directory.CreateDirectory(uploadDir);
var fileName = $"{Guid.NewGuid():N}{ext}";
var filePath = Path.Combine(uploadDir, fileName);
await using var stream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
return Ok(new { url = $"/uploads/{fileName}" });
}
5.3 发布部署:从本机到服务器的完整流程
开发完开始部署时,很多人会卡在“本地能跑,服务器跑不起来”。我建议你用下面这套流程,出错率最低:
- 在项目根目录执行
dotnet publish -c Release -o ./publish。 - 把publish文件夹整个上传到服务器。
- 在服务器上安装.NET 8 Hosting Bundle(必装,否则IIS无法托管ASP.NET Core程序)。
- IIS上新建网站,物理路径指向publish文件夹,应用程序池设置为“无托管代码”。
- 给网站绑定域名或端口,设置web.config自动生成即可。
web.config里最关键的是aspNetCore节点:
xml复制<system.webServer>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet"
arguments=".\WineShop.Web.dll"
stdoutLogEnabled="true"
stdoutLogFile=".\logs\stdout"
hostingModel="inprocess" />
</system.webServer>
stdoutLogEnabled="true"这句务必打开,第一次部署时如果出现500错误,去logs目录看标准输出日志,比在事件查看器里翻错误快太多。我看到过太多人在IIS部署后遇到空白页、500.30、500.31这类错误,其实绝大多数都是这个日志里写明了原因。
6. 踩坑实录:部署和调试阶段我遇到的一半问题
6.1 部署后500.30错误的排查链路
第一次在Windows Server上发布这个商城时,我遇到了经典的HTTP Error 500.30 - ANCM In-Process Start Failure。
排查链路是这样的:
- 先看Windows事件查看器:应用程序日志里会记录托管模块的异常详细信息。果然发现了:
System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.Extensions.Hosting.Abstractions'。 - 这个异常的原因通常是发布时没有带上运行时依赖,或者服务器上的运行时版本不对。
- 重新确认服务器装了.NET 8 Hosting Bundle,并且发布命令用了
-c Release而不是Debug。 - 在项目里清理打包,使用
dotnet publish -c Release -r win-x64 --self-contained false,重新发布。 - 重启站点,500.30解决。
注意:如果你服务器的.NET 8 Desktop Runtime没装,有些组件会加载失败。建议把.NET 8 Desktop Runtime和ASP.NET Core Runtime 8.0都装上,一劳永逸。另外安装顺序有讲究:先装运行时,再装Hosting Bundle,最后装Desktop Runtime。反了会互相覆盖,导致某些环境下IIS还是起不来。
6.2 Swagger上线后不显示,原因吓我一跳
开发环境Swagger打开很正常,发布到生产服务器后只有404。查了半个多小时,最后发现是Swagger中间件默认只在Development环境启用。
csharp复制if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
如果你希望在生产环境也能浏览API文档,加上:
csharp复制app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "WineShop API v1");
});
当然,出于安全考虑,生产环境建议给Swagger加个访问密码或者只在内网开启。别公开裸奔,不然别人直接拿你API文档研究怎么攻击。
6.3 图片上传后404,物理路径的权限坑
图片上传成功了,返回了路径,但浏览器访问图片404。这个坑的根源是IIS应用程序池的工作进程对wwwroot/uploads目录没有写权限,或者网站目录的物理路径权限配置不对。
解决方法:确认wwwroot目录允许IIS_IUSRS用户读写,或者具体给应用池名对应的虚拟账户授权。右键目录 → 属性 → 安全 → 编辑 → 添加IIS_IUSRS → 勾选完全控制。这一步做完,图片上传下载就通了。
6.4 用户并发下单导致订单号重复
订单号我最初用的是DateTime.Now.ToString("yyyyMMddHHmmssfff"),结果测试时用脚本并发下单,竟然出现了两个同样的订单号。
后来改成全局唯一ID生成器,用雪花算法或者.NET 8内置的Guid.CreateVersion7()。Guid v7是时间有序的,对数据库索引也友好:
csharp复制order.OrderNo = Guid.CreateVersion7().ToString("N");
这种订单号不可猜测,而且在高并发下不会重复。别再自己拼接“当前时间+随机数”了,坑太大。
7. 一些可以让你少想很久的管理后台设计参考
7.1 商品管理的界面逻辑
管理后台商品管理,我分成了三个Tab:全部商品、已上架、已下架。每个商品行显示缩略图、名称、SKU数量、总库存、状态、上下架按钮。点击编辑时,打开一个展示商品基本信息、属性列表、图片列表、SKU列表的编辑页面,SKU部分单独做一个动态表格,支持在页面上新增/删除SKU行,保存时整体提交到后端。
后端更新商品的保存逻辑用事务包裹:
csharp复制using var transaction = await _db.Database.BeginTransactionAsync();
try
{
// 1. 更新Product表
// 2. 删除旧属性,插入新属性
// 3. 删除旧SKU,插入新SKU
// 4. 更新图片列表
await _db.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
这样避免修改到一半时程序崩溃导致数据不完整。注意详情页里的商品列表缓存,保存后一定要删除对应缓存的Key,否则前台看到的还是旧数据。
7.2 订单处理
管理后台订单列表,我按状态筛选,每个订单展开能看到明细。发货操作需要填入物流公司和快递单号,保存后自动给用户发一条站内信(这里我用的是向用户消息表插入一条记录,前台右上角红色气泡提示)。
7.3 数据统计
这个模块不需要引入复杂的BI组件。用Chart.js画三个简单图表就行:最近30天销售额折线图、各分类销售额占比饼图、热销SKU Top10柱状图。SQL用GROUP BY就能搞定:
sql复制SELECT
CONVERT(DATE, o.CreateTime) AS d,
SUM(o.TotalAmount) AS amount
FROM Orders o
WHERE o.CreateTime >= DATEADD(DAY, -30, GETDATE())
AND o.Status = 1
GROUP BY CONVERT(DATE, o.CreateTime)
ORDER BY d;
管理后台看起来不那么空洞,这页是加分项。
8. 写在最后的个人心得
坦白说,商城系统是软件工程里最“古老”也最“经典”的业务场景之一,但它依然值得我们用.NET重写一遍。做完这个葡萄酒商城,我最深的体会有三个:
第一,业务理解永远是第一位的。如果你不懂葡萄酒SKU的复杂性,不知道订单状态为什么要严格流转,你写出来的代码再优雅,在真实运营面前也是空中楼阁。
第二,.NET 8做Web项目比想象中顺手。它不像老.NET Framework那样跑起来很重,也不像某些框架需要东拼西凑一堆黑科技。它把Web API、依赖注入、配置系统、日志系统这些底层都给你了,你只需专心想业务流程即可。
第三,做项目不要追求一次写完美。先把整条购买流程走通,再逐步补权限、缓存、日志、统计这些细节。因为只有主链路通了,你才有信心继续优化,不然在细节里卡住,项目很容易烂尾。
如果你也想做一个类似的项目,建议拿到需求后别急着敲代码,先花一周把数据库的表设计透,把订单状态机捋清楚,然后动手写。项目做完之后记得写个项目文档或README,记录选型理由和踩过的坑,这个文档的价值有时候比代码本身还大。
