
简介在线二手商品交易平台是一份基于C#语言开发的完整毕业设计/课程设计项目适用于计算机专业学生、开发者以及需要参考实际Web系统案例的学习者。项目覆盖需求分析、系统设计、编码实现、数据库管理与测试部署等关键环节以MVC架构组织代码使用C#处理业务逻辑涉及SQL Server或MySQL数据库设计及ADO.NET、Entity Framework等数据访问技术并提供ASP.NET页面与多模块用户交互界面。资源包共572个文件主要包含cs源码、aspx页面、js脚本、css样式、jpg/gif图片素材以及dll、数据库文件等包大小约6.31MB目录结构完整便于按功能模块拆解学习。目前已有59人学习下载。借助本资源可以梳理二手交易平台的完整实现思路掌握C# Web开发、数据库建模、安全防护与测试部署的实战方法是快速开展课题设计或巩固工程能力的实用参考。1. 为什么“基于C#的在线二手商品交易平台”是毕设里最容易被问倒的题又到毕业设计集中开题的时候二手交易平台这个题目在计算机专业里几乎年年出现C# 也是老师最常指定的语言。这个题看着门槛不高真正答辩时被问住的恰恰是细节商品图片到底存在哪里、订单状态怎么流转、买家卖家身份靠什么区分再往下问一句“数据库为什么这么设计”很多人就答不圆了。这篇笔记就围绕一个可运行的 C# 在线二手商品交易平台把技术选型、建表、核心代码、部署和答辩要点串成一条能直接照做的路线。适合做毕业设计或课程设计也适合想快速搭一个能演示的 Web 项目的同学。别指望下载一个压缩包就能交差真正有价值的是把每一步在本地跑通并能讲清楚每一个设计取舍。2. 选型先行C# 交易平台用 MVC 还是 WinForm数据放 SQL Server 还是 Access2.1 两条路线MVC 网页版与 WinForm 桌面版怎么选我接触过不少做这类题目的学生一开始都会问“用 WinForm 行不行”。行但要看场景。WinForm 是桌面程序打开就是窗体按钮、表格、图片控件拖一拖三周就能做出一个看起来完整的管理系统很多课程设计也确实是这么交的。可标题里写的是“在线”二手商品交易平台“在线”这两个字用 WinForm 不太容易圆过去——总不能说别人在你电脑上买卖东西吧。所以我的建议很直接优先做 ASP.NET Core MVC 网页版浏览器访问能部署到服务器上给其他人演示答辩说服力强得多。维度ASP.NET Core MVC EF Core SQL ServerWinForm SQL Server / Access访问方式浏览器局域网或服务器部署后即可访问本机安装演示依赖电脑环境开发工作量前端视图 控制器 服务层量稍大界面拖拽快代码集中在事件里答辩观感更像真正的“在线平台”可远程演示像工具软件和“在线”有距离数据访问EF Core LINQ可讲分层和依赖注入ADO.NET / DataSet 比较老亮点少兜底能力适合毕业设计时间允许时间只剩两周时的保底方案配套数据库也一样优先 SQL Server。如果学校机房只装了 Access那不是不能用但 Access 是文件型数据库写多读多、并发上来就开始出锁问题而且答辩时老师很容易追问“你这套设计能支撑多大访问量”。把数据放 SQL Server至少能讲清楚主键、外键、事务、索引这些数据库基本功。一句话网页版 SQL Server 才是这个标题下的标准配置。2.2 搭骨架dotnet new 三层结构与 Program.cs 配置假设你已经装好了 Visual Studio 2022 或新版 JetBrains Rider也有 .NET 6 或更高版本的 SDK。先用命令行把项目骨架拉出来这一步比在 IDE 里点半天菜单更快dotnet new sln -n SecondHand dotnet new mvc -n SecondHand.Web -o src/SecondHand.Web dotnet new classlib -n SecondHand.Domain -o src/SecondHand.Domain dotnet new classlib -n SecondHand.Infrastructure -o src/SecondHand.Infrastructure dotnet sln add src/SecondHand.Web src/SecondHand.Domain src/SecondHand.Infrastructure我一般会建三个项目而不是把所有代码堆在 Web 层SecondHand.Web 放控制器、视图、ViewModelSecondHand.Domain 放实体类和枚举SecondHand.Infrastructure 放 DbContext 和数据访问配置。这样做的直接好处是答辩时能讲“分层架构”而且后面改数据库、换 ORM 都不用动页面代码。很多刚入门的朋友喜欢把实体类直接写在 Models 文件夹里项目小的时候没问题但一旦要在类库里复用订单状态机、再写单元测试拆开就省事了。新建的 MVC 项目默认自带 Program.cs这是 .NET 6 之后的主入口。网上大量 C# 教程还在讲传统的 Startup.cs 两段式遇到新版模板反而对不上。直接看关键配置var builder WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(); builder.Services.AddDbContextAppDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(DefaultConnection))); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options { options.LoginPath /Account/Login; options.AccessDeniedPath /Account/Denied; }); var app builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler(/Home/Error); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllerRoute( name: default, pattern: {controllerHome}/{actionIndex}/{id?}); app.Run();重点看中间件顺序UseRouting 之后必须依次是 UseAuthentication、UseAuthorization再是 MapControllerRoute。这个顺序不能乱很多人登录跳转出问题就是认证中间件没注册或者位置放错。AddDbContext 注册的是 Scoped 生命周期每个请求一个实例这样 EF Core 的上下文不会跨请求串数据。LoginPath 和 AccessDeniedPath 是 Cookie 认证的两个参数前者决定未登录用户跳去哪里后者决定权限不足时跳去哪两个路径指向的控制器不能再加 [Authorize]否则会死循环。2.3 配置文件里的关键参数连接串、上传目录与分页大小项目模板生成后会有一个 appsettings.json这就是整个平台的配置文件。把数据库连接、图片上传目录、分页大小都放在这里比写死在代码里好维护得多这也是 C# 后端项目里最常见的 JSON 配置习惯{ ConnectionStrings: { DefaultConnection: Serverlocalhost;DatabaseSecondHandDB;User Idsa;PasswordChangeThisPassword;TrustServerCertificateTrue }, UploadFolder: wwwroot/uploads, PageSize: 12 }连接字符串里的 TrustServerCertificateTrue 建议保留。新版 SQL Server 默认强制加密本地开发如果不加这个连接时经常报证书校验失败。UploadFolder 是商品封面图的物理目录PageSize 控制商品列表每页显示多少条答辩时可以说“这个参数改一行配置就能调整不用重新编译”。还有一个容易忽略的问题不要把这套配置里带密码的文件直接提交到 Git 仓库哪怕只是课设养成用 .gitignore 排除敏感文件的习惯将来工作面试也能加分。3. 数据库设计先行核心表结构与 EF Core 映射先把一致性立住3.1 六张表的事用户、分类、商品、订单、订单明细、收藏在线二手交易平台的实体其实比很多同学想象的要少但表之间关系比图书管理复杂。最核心的是用户、商品、订单。用户分买家和卖家但在二手平台上一个人既可以是买家也可以是卖家所以不要建两张用户表一张 Users 表加角色字段就行。商品属于某个卖家也属于某个分类订单连接买家和商品。这就形成了最基本的四表结构。再加两张辅助表订单明细用于将来扩展“一个订单买多件商品”收藏表用于记录买家收藏。收藏表在答辩时是个可讲的亮点它能引出多对多关系也能做简单的推荐逻辑。这里需要注意一个容易犯的建模错误订单和商品要不要直接外键我的做法是 Orders 表里同时存 BuyerId、SellerId、ProductId还要冗余存一份 UnitPrice 和 TotalAmount。原因很简单商品价格会变动如果订单表只关联商品以后卖家改了价格历史订单金额就跟着变了。冗余字段在这类业务里是合理的不是设计缺陷。答辩时如果老师问“为什么要冗余”这个答案就是现成的。3.2 从零建库可直接执行的 SQL Server 建表脚本下面这份脚本可以完整建出核心表结构我按项目里最常用的字段习惯来写。执行前先确认 SQL Server 实例能连上然后用 sa 或一个有建库权限的账号执行CREATE DATABASE SecondHandDB; GO USE SecondHandDB; GO CREATE TABLE dbo.Users ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, UserName nvarchar(40) NOT NULL, PasswordHash nvarchar(256) NOT NULL, NickName nvarchar(50) NOT NULL, Phone nvarchar(20) NULL, IsAdmin bit NOT NULL DEFAULT 0, CreatedAt datetime2(7) NOT NULL DEFAULT SYSUTCDATETIME() ); CREATE TABLE dbo.Categories ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, Name nvarchar(40) NOT NULL, ParentId int NULL ); CREATE TABLE dbo.Products ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, SellerId int NOT NULL FOREIGN KEY REFERENCES dbo.Users(Id), CategoryId int NOT NULL FOREIGN KEY REFERENCES dbo.Categories(Id), Title nvarchar(80) NOT NULL, Description nvarchar(2000) NULL, Price decimal(18,2) NOT NULL, Quantity int NOT NULL DEFAULT 1, CoverImage nvarchar(256) NOT NULL, Status int NOT NULL DEFAULT 1, SoftDeleted bit NOT NULL DEFAULT 0, CreatedAt datetime2(7) NOT NULL DEFAULT SYSUTCDATETIME(), UpdatedAt datetime2(7) NULL ); CREATE TABLE dbo.Orders ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, OrderNo nvarchar(32) NOT NULL UNIQUE, BuyerId int NOT NULL FOREIGN KEY REFERENCES dbo.Users(Id), SellerId int NOT NULL FOREIGN KEY REFERENCES dbo.Users(Id), ProductId int NOT NULL FOREIGN KEY REFERENCES dbo.Products(Id), Quantity int NOT NULL DEFAULT 1, UnitPrice decimal(18,2) NOT NULL, TotalAmount decimal(18,2) NOT NULL, Status int NOT NULL, CreatedAt datetime2(7) NOT NULL DEFAULT SYSUTCDATETIME(), PaidAt datetime2(7) NULL, ShippedAt datetime2(7) NULL, CompletedAt datetime2(7) NULL ); CREATE TABLE dbo.OrderItems ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, OrderId int NOT NULL FOREIGN KEY REFERENCES dbo.Orders(Id), ProductId int NOT NULL FOREIGN KEY REFERENCES dbo.Products(Id), ProductName nvarchar(80) NOT NULL, UnitPrice decimal(18,2) NOT NULL, Quantity int NOT NULL ); CREATE TABLE dbo.Favorites ( Id int IDENTITY(1,1) NOT NULL PRIMARY KEY, UserId int NOT NULL FOREIGN KEY REFERENCES dbo.Users(Id), ProductId int NOT NULL FOREIGN KEY REFERENCES dbo.Products(Id), CreatedAt datetime2(7) NOT NULL DEFAULT SYSUTCDATETIME(), CONSTRAINT UQ_User_Product UNIQUE (UserId, ProductId) );几个字段要特别注意。Price 用 decimal(18,2) 而不是 float避免浮点数精度问题Product 表加了 SoftDeleted 位字段表示逻辑删除。二手交易最怕卖家删商品后历史订单关联不上所以不要用 DELETE 物理删商品而是置 SoftDeleted1。Orders 表用独立 OrderNo 并加 UNIQUE 约束不用自增 Id 当订单号展示给用户外部用户看到自增 Id 会猜测平台单量这是商业常识。Favorites 表有 UserId ProductId 唯一约束避免同一个人重复收藏同一件商品。3.3 Fluent API 映射精度、索引与外键行为SQL 脚本建完表后还需要让 EF Core 知道如何映射。新手常用的做法是给实体类加一堆 Data Annotation比如[Column(TypeName decimal(18,2))]但一旦字段规则复杂 annotation 会让实体类变得很乱。我习惯把映射规则集中到独立的配置类里用 Fluent API 写。下面拿 Product 做例子public class ProductConfiguration : IEntityTypeConfigurationProduct { public void Configure(EntityTypeBuilderProduct builder) { builder.ToTable(Products); builder.HasKey(p p.Id); builder.Property(p p.Title) .HasMaxLength(80) .IsRequired(); builder.Property(p p.Price) .HasPrecision(18, 2); builder.Property(p p.Description) .HasMaxLength(2000); builder.HasIndex(p p.SellerId); builder.HasIndex(p p.CategoryId); builder.HasOne(p p.Category) .WithMany() .HasForeignKey(p p.CategoryId) .OnDelete(DeleteBehavior.Restrict); } }HasPrecision(18, 2) 是给 SQL Server 的 decimal(18,2) 用的比在实体上写 Column(TypeName) 更类型安全。HasIndex 给 SellerId、CategoryId 建了索引商品列表页按卖家或分类筛选时性能才有保障。OnDelete(DeleteBehavior.Restrict) 的作用是删除分类时不把商品一起删掉。默认的 Cascade 行为如果不分清删一个分类可能导致所有商品被级联删除这个事故我见过不止一次。DbContext 那边只要在 OnModelCreating 里注册配置类即可protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly); }ApplyConfigurationsFromAssembly 会自动扫描程序集里所有 IEntityTypeConfiguration 实现后续每加一张新表只需要写一个配置类不用回 DbContext 改一行。这种写法老师看到会觉得你是有工程习惯的不是把代码堆在一起跑通就完事。4. 核心功能怎么写认证、上架、下单三段代码按业务闭环走4.1 注册与登录用 Cookie 认证把买卖双方身份立起来在线交易平台必须先解决“你是谁”。ASP.NET Core 里最常用的方案是 Cookie 认证用户登录成功后服务端签发一个加密 Cookie之后每次请求框架自动解析身份。注册逻辑不能把密码明文存进数据库这个底线要守住。我推荐直接用 BCrypt 做密码哈希使用方法很简单[HttpPost] [ValidateAntiForgeryToken] public async TaskIActionResult Register(RegisterViewModel model) { if (!ModelState.IsValid) return View(model); var exists await _db.Users.AnyAsync(u u.UserName model.UserName); if (exists) { ModelState.AddModelError(, 用户名已被占用); return View(model); } var user new User { UserName model.UserName, NickName model.NickName, PasswordHash BCrypt.Net.BCrypt.HashPassword(model.Password) }; _db.Users.Add(user); await _db.SaveChangesAsync(); await SignInAsync(user); return RedirectToAction(Index, Home); } private async Task SignInAsync(User user) { var claims new ListClaim { new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()), new Claim(ClaimTypes.Name, user.UserName), new Claim(ClaimTypes.Role, user.IsAdmin ? Admin : User) }; var identity new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(identity)); }注意我这里的 SignInAsync 没有用 Identity 框架而是手动构造 Claims。NameIdentifier 里放用户 Id后面写订单、判断商品归属时都用User.FindFirstValue(ClaimTypes.NameIdentifier)取当前登录人不要再用 Session 存储用户信息。最前面的[ValidateAntiForgeryToken]是 MVC 自带的防跨站请求伪造验证视图里要配合Html.AntiForgeryToken()一起用。这个特性平时看不出用但答辩时能讲出安全设计是很加分的点。4.2 发布商品与封面图上传文件名、大小与扩展名三个边界发布商品是卖家侧最核心的操作也是答辩必问的模块。图片上传一定要处理三个边界文件扩展名、文件大小、文件名。很多示例代码直接保存file.FileName一旦用户传一个包含路径符的文件名就可能出问题。我的做法是重命名后保存[HttpPost] [Authorize] [ValidateAntiForgeryToken] public async TaskIActionResult Create(CreateProductViewModel model) { if (!ModelState.IsValid) return View(model); var fileName await SaveUploadedImageAsync(model.CoverImage); if (fileName null) { ModelState.AddModelError(CoverImage, 只接受 jpg/png/webp 图片且不超过 5MB); return View(model); } var product new Product { SellerId int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier)!), CategoryId model.CategoryId, Title model.Title, Description model.Description, Price model.Price, CoverImage fileName, Status 1, CreatedAt DateTime.UtcNow }; _db.Products.Add(product); await _db.SaveChangesAsync(); return RedirectToAction(Detail, new { id product.Id }); } private async Taskstring? SaveUploadedImageAsync(IFormFile? file) { if (file is null || file.Length 0) return null; var ext Path.GetExtension(file.FileName).ToLowerInvariant(); if (ext is not (.jpg or .jpeg or .png or .webp)) return null; if (file.Length 5 * 1024 * 1024) return null; var fileName ${Guid.NewGuid():N}{ext}; var savePath Path.Combine(_env.WebRootPath, uploads, fileName); await using var stream System.IO.File.Create(savePath); await file.CopyToAsync(stream); return fileName; }文件名用 Guid 重命名既避免中文文件名在部分服务器上乱码也防止路径穿越攻击。Path.Combine(_env.WebRootPath, uploads, fileName)保证了文件一定落在 wwwroot/uploads 下这个目录会被 UseStaticFiles 托管浏览器访问/uploads/xxxx.jpg就能拿到图。我在 CreateProductViewModel 里已经加了[Range(0.01, 999999.00)]约束价格还在 Controller 里整体校验一次 ModelState两层校验让非法价格没有机会落到数据库。注意上传限制不只在代码层面IIS 或 Kestrel 默认请求体大小有限如果部署后发现大图总是上传失败还要同步调服务端请求大小限制。4.3 订单状态机从待付款到已完成用枚举控制流转订单是全平台数据一致性要求最高的地方。很多同学用字符串存状态“待付款”“已付款”随手中文存进去后期统计时大小写不统一、中英文混杂查数据查到怀疑人生。正确做法是定义成枚举数据库存 int 值。订单状态迁移不要散落在各个 Controller 里我习惯把迁移逻辑放到一个 OrderService 中收敛所有入口。public enum OrderStatus { PendingPayment 0, PendingShipment 1, PendingReceipt 2, Completed 3, Canceled 4 }卖家发货的示例public async Task(bool Success, string Error) ShipAsync(int orderId, int sellerId) { var order await _db.Orders .FirstOrDefaultAsync(o o.Id orderId o.SellerId sellerId); if (order is null) return (false, 订单不存在); if (order.Status ! OrderStatus.PendingShipment) return (false, 当前状态不能发货需要买家先付款); order.Status OrderStatus.PendingReceipt; order.ShippedAt DateTime.UtcNow; await _db.SaveChangesAsync(); return (true, ); }返回类型是值元组(bool Success, string Error)调用方解构时一眼看清成功与否这是 C# 值元组解构的典型场景。可能有人问为什么不直接抛异常我的习惯是业务预期内的错误用返回值真正异常才抛避免把“买家没付款”当成程序故障。买家付款以后涉及库存扣减。二手商品通常数量是 1但如果存在卖多件的情况并发下单就可能超卖。网上很多代码是“先 SELECT 再判断再 UPDATE”这在大并发下不可靠。正确姿势是把扣减和判断放进同一条 UPDATE 语句并配合事务写订单using var transaction await _db.Database.BeginTransactionAsync(); var affected await _db.Database.ExecuteSqlRawAsync( UPDATE dbo.Products SET Quantity Quantity - 1 WHERE Id {0} AND Quantity 1, productId); if (affected 0) { await transaction.RollbackAsync(); return (false, 库存不足); } _db.Orders.Add(order); await _db.SaveChangesAsync(); await transaction.CommitAsync();Quantity 1这个条件让数据库在更新本身执行原子判断如果没有可用库存UPDATE 影响行数为 0事务回滚订单不会写进去。用 ExecuteSqlRawAsync 而不是先查再说是因为并发下“先查再改”永远存在时间差。答辩时如果能讲出这个点老师会认可你不是纯背代码而是真的考虑过并发和一致性。5. 五个高频踩坑点不是“能不能跑”而是跑起来以后哪里会翻车5.1 上传的图片发布后 404本地却正常现象开发环境图片显示正常部署到学校服务器或 IIS 后商品详情页的图全部打不开F12 看都是 404。原因最常见的是把上传目录写成了自定义的绝对路径比如D:\uploads或者项目根目录下的 uploads而不是wwwroot/uploads。wwwroot 是 ASP.NET Core 静态文件托管的根目录只有它里面的文件能通过 URL 直接访问。解决保存路径统一用Path.Combine(_env.WebRootPath, uploads, fileName)_env 是注入的 IWebHostEnvironment。页面输出图片时用Url.Content(~/uploads/ product.CoverImage)不要手写死/uploads/前缀因为部署到虚拟目录时路径会变。之后无论是本地开发还是发布到服务器只要 wwwroot 跟着站点走图片就不会丢。5.2 登录成功后被弹回登录页来回死循环现象用户输入用户名密码后页面跳了一下又回到登录页甚至刷新后还是一样像是“没记住登录状态”。原因两种可能。一是认证中间件顺序不对Program.cs 里 UseRouting 之后没有调用 UseAuthentication导致后续请求解析不了 Cookie二是 LoginPath 指向的控制器或 Action 自己加了[Authorize]用户去登录页时又被要求认证形成死循环。还有一种隐蔽情况开发时用了 HTTP登录 Cookie 被标记为 Secure浏览器就不保存。解决把 Program.cs 的中间件顺序改成 UseRouting → UseAuthentication → UseAuthorization → MapControllerRoute。登录页所在 Controller 记得加[AllowAnonymous]。如果本地用 HTTP需要检查 Cookie 配置里是否误开了 RequireSecure。按顺序排查大概率一两分钟就能定位。5.3 接口突然报循环引用JSON 序列化卡死在导航属性上现象写了一个返回 JSON 的接口比如获取商品详情浏览器直接 500或者接口长时间不返回后台日志全是“JSON circular reference”。原因EF Core 加载实体时导航属性会自动把关联对象带上。Product 里有 Seller 对象Seller 里又有他发布的 Products 集合序列化时 A→B→A 无限递归JsonSerializer 在 .NET 5 之后默认会直接抛异常。解决最简单的临时方案是在 AddControllersWithViews 里配置ReferenceHandler.IgnoreCycles但我不推荐只靠它保命。正确做法是给前端返回 ViewModel而不是直接返回 EF 实体。比如商品详情需要返回 ProductDto里面只放商品字段、卖家昵称、分类名不放导航属性集合序列化自然干净。这个习惯不但能解决循环引用还能避免把不需要的字段暴露给前端属于顺手的用户体验改进。5.4 两个买家同时拍同一件商品库存超卖现象用多个浏览器同时下单同一件商品两边都提示下单成功但库存只剩 1数据库出现两条订单对一件商品。原因代码里是“先查 Quantity大于 0 再 UPDATE”两个请求同时读到 1同时通过判断然后各自减库存最终超卖。这是典型的检查与执行分离问题和线程数量无关只要请求并发就会触发。解决像前面 4.3 一样把库存判断和扣减合并为一条 UPDATE 语句利用受影响行数判断成功。必要时再包一个事务。二手商品平台并发量不一定高但答辩时主动讲出“我考虑了并发”和被动被老师问出来效果完全不一样。5.5 DbContext 被静态共享请求一多就报 “connection in use”现象网站刚跑起来没事测试几个人同时访问后开始偶发报错错误信息类似 “The connection is already in use” 或者“不能同时使用多个 DbContext 实例”。原因初学时常犯的错误是把 DbContext 存到静态字段或单例里复用。ADO.NET 时代连接池管理方式深入人心到了 EF Core 就不一样DbContext 不是线程安全的它内部有状态跟踪两个请求同时操作同一个实例就会互相干扰。解决不要手动 new DbContext也不要在静态类里挂 DbContext。直接在构造函数里注入让依赖注入容器按 Scoped 生命周期管理。换句话说每个请求进来都获得独立的 AppDbContext 实例请求结束自动释放。只要是用 AddDbContext 注册的默认行为就是对的千万不要自己加 Singleton。6. 验收与答辩进阶用一套演示剧本把“项目亮点”讲出来6.1 演示剧本从发布商品到收货确认的完整链路答辩演示不要只点几个页面。我建议准备两个不同的浏览器窗口一个登录卖家一个用无痕窗口登录买家完整跑一遍交易闭环。开始前先在 SQL Server Management Studio 里打开 Products 表和 Orders 表让老师能看到数据变化。演示顺序这样安排卖家在卖家中心发布一件商品填标题、分类、价格上传封面图提交后立刻切到数据库看 Products 多了一条记录注意 CoverImage 字段是 Guid 重命名后的文件名。然后切到买家窗口搜索或进入列表看到这件商品点击下单订单状态在 Orders 表里从 0 变为 1。接着切回卖家窗口点发货状态变 2。最后买家确认收货状态变 3。整个过程一分钟左右却把发布、订单、状态机、数据库设计全部展示完了。演示时一边操作一边说“每次操作落库时订单状态会变化接下来我查数据库给大家看”比单纯点页面有说服力得多。这个环节还有一个加分动作在 Program.cs 中临时打开 SQL 日志在控制台输出 EF Core 生成的 SQL 命令让老师看到 LINQ 背后真实转换成什么语句。网上很多 C# 教程会用LogTo(Console.WriteLine, LogLevel.Information)来打日志这也是我平常调试最常用的手段之一。但出发前一定记得在演示环境验证日志能正常打印别等到答辩现场才发现控制台没输出。6.2 自测清单与评分点功能模块测试输入预期结果注册空用户名、超长用户名、重复用户名表单校验提示无 500 错误登录正确/错误密码错误密码不跳转正确密码进入首页发布商品价格 0、负价格、超长标题页面拦截不进数据库图片上传非 jpg/png/webp、超过 5MB明确错误提示权限控制未登录直接访问发布/订单接口跳转到登录页库存变化库存 1 时并发下单只有一条成功商品删除删除后访问详情历史订单不受影响按照我个人经验毕业设计答辩老师最看重的并不是功能多少而是“这个项目里有没有你真正自己思考过的点”。哪怕你没有做支付、没有做聊天只要能讲清 Cookie 认证流程、订单为什么用枚举、导航属性为什么要用 ViewModel 规避循环引用、并发下库存怎么保护这几点已经足以撑起一场答辩。我当年做类似项目时图省事把订单状态直接存了中文字符串后面统计别人下了多少单时全表刷了几十分钟 SQL 才把脏数据改干净。所以这篇文章里最希望你能带走的一句话是能跑通的代码不等于能答辩的代码提前把边界和坑踩一遍比临场祈祷管用得多。希望帮到你。本文还有配套的精品资源点击获取