ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#在线二手交易平台开发详解:从架构设计到避坑指南

C#在线二手交易平台开发详解:从架构设计到避坑指南 简介基于C#语言开发的在线二手商品交易平台面向毕业设计、课程设计场景适合需要完成完整Web项目实践的学生与开发者。项目覆盖软件工程需求分析、系统设计、编码实现、数据库管理及测试部署等关键环节能够帮助读者将理论知识转化为实际工程能力。压缩包共572个文件大小6.31MB主要包含C#后端代码cs、aspx、ascx、前端页面html、css、js、界面素材gif、jpg、png以及数据库文件db等目录结构清晰便于按模块查阅。平台融合MVC架构、ASP.NET Web Forms、ADO.NET或Entity Framework等主流技术并设计了用户注册、商品信息、订单管理、管理员后台等核心功能页面。已有59人学习浏览。通过该项目可掌握在线交易平台的业务建模、数据库表设计、用户认证与安全防护思路是一份实践性很强的课程设计与毕设参考资料。1. 一个 C# 在线二手交易平台凭什么撑起一份优质毕业设计每年毕业设计的选题里在线二手商品交易平台都是常青树——用户、商品、订单三条线一拉前端展示、后端接口、数据库设计全都能练到。而标题点名用 C#图的是 ASP.NET Core MVC 在表单绑定、模型验证、依赖注入上的成熟度同样是做增删改查强类型和编译期检查能帮你少踩一堆运行时才爆的雷。这篇文章写给两类人手里拿着这个题目、想找一条能跑通路的学生课程设计拿到同款题、想把它做得能演示能答辩的开发者。下面从架构怎么搭、表怎么建、核心功能怎么写一直讲到实际翻过车的细节照做两三天就能把这平台跑起来。2. 技术选型和项目骨架三层架构怎么搭、EF Core 还是 Dapper拿到这个标题第一步不是写代码是先定技术栈。最稳的组合是 ASP.NET Core MVC.NET 6/8 都行 EF Core SQL Server。为什么这么选往下拆。2.1 三层架构为什么是这类项目的地基先分项目再写代码很多初学者拿到这种项目喜欢一个 Web 项目里把页面、业务、数据库访问全写了图省事。但这就像数组和集合的区别——数组长度写死集合可以动态增删单项目看着固定省事业务一加就到处改。一个控制器里同时出现 SQL 连接、业务判断和 ViewBag前期爽后期改一个需求要动三个地方。毕业设计的常规做法是拆成四个项目Web 层ASP.NET Core MVC控制器、视图、模型绑定BLL 层业务规则比如订单状态能不能流转DAL 层EF Core 的 DbContext 和仓储方法Model 层实体类User、Product、OrderCommon 层加密、字符串处理这类工具分层还有个实际好处排查问题不用全局搜索。订单状态不对直接去 BLL 的 OrderService 里找而不是在一个 3000 行的控制器里翻。课程设计规模小分层的好处短期不明显但答辩老师一眼就能看出你有没有工程意识。我见过不少同学用单层结构答辩被问业务逻辑和页面展示怎么解耦就卡住了。2.2 数据访问三选一EF Core、Dapper、原生 ADO.NET 的取舍这是每个做 C# 的人都会纠结一下的问题。我的判断标准很简单毕设项目优先 EF Core。方案开发速度调试难度学习成本适合场景EF Core快中中中小后台、增删改查为主Dapper较快低低查询复杂、想把 SQL 攥在自己手里ADO.NET慢中高教学演示底层原理EF Core 的好处是 LINQ 写查询语句在编译期就有类型检查不像拼接 SQL 那样容易把引号拼错。对 C# 入门阶段来说EF Core 的 LINQ 查询跟写集合操作很像把数组和集合那套 Select/Where 的思维搬过来就能上手。Dapper 也不是不能用它更适合报表这种 SQL 占比高的场景如果答辩时老师问SQL 怎么写你能答上来 Dapper 和参数化查询反而加分。但日常开发效率EF Core 更高。2.3 命令行 5 分钟搭出解决方案从空目录到能编译我一般用 dotnet CLI 建项目比在 Visual Studio 里点向导快而且项目结构一目了然mkdir SecondHandPlatform cd SecondHandPlatform dotnet new sln -n SecondHandPlatform dotnet new mvc -n SecondHandPlatform.Web dotnet new classlib -n SecondHandPlatform.Model dotnet new classlib -n SecondHandPlatform.DAL dotnet new classlib -n SecondHandPlatform.BLL dotnet new classlib -n SecondHandPlatform.Common dotnet sln add SecondHandPlatform.Web SecondHandPlatform.Model \ SecondHandPlatform.DAL SecondHandPlatform.BLL SecondHandPlatform.Common这里的关键是引用方向Web 引用 BLLBLL 引用 DAL 和 ModelDAL 引用 ModelCommon 谁都能引用但 DAL 绝不能反向引用 Web。引用关系乱了编译期不报错运行期会出各种诡异的循环依赖。然后在 Web 项目的 Program.cs 里注册 EF Core 和 Sessionbuilder.Services.AddDbContextAppDbContext(options options.UseSqlServer(builder.Configuration.GetConnectionString(Default))); builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options { options.IdleTimeout TimeSpan.FromMinutes(30); options.Cookie.HttpOnly true; options.Cookie.IsEssential true; }); var app builder.Build(); app.UseSession(); // 必须在 UseRouting 之后、控制器之前参数说明UseSession() 没调用的话Session 就会悄悄失效这是最隐蔽的坑IdleTimeout 30 分钟对毕设演示够用正式项目一般再配一个滑动过期。连接串放 appsettings.json{ ConnectionStrings: { Default: Server.;DatabaseSecondHandDB;User Idsa;Password你的密码;TrustServerCertificateTrue;EncryptFalse } }注意 EncryptFalse 和 TrustServerCertificateTrue这是新版 .NET 连 SQL Server 时最常见的两个坑不加的话报证书错误的概率极高。如果你本机没装 SQL Server用 LocalDB 也能跑连接串改成Server(localdb)\\MSSQLLocalDB;DatabaseSecondHandDB;Trusted_ConnectionTrue即可。演示环境我建议装 SQL Server Express因为答辩现场的机器很可能没有 LocalDB。注意UseSession() 的位置很关键。放在 UseRouting() 之后、MapControllers 之前顺序错了 Session 就是读不到。3. 数据库设计五张表怎么建字段和索引一次到位表结构是这个项目的命脉。很多人的项目做到一半推倒重来不是代码写不下去而是表设计时漏了状态字段后面业务逻辑越写越挤牙膏。3.1 核心表梳理用户、商品、订单、订单明细与留言一个最小可用的二手交易平台五张表够了Users用户表账密、昵称、联系方式Products商品表标题、描述、价格、图片、状态Orders订单表买家、卖家、商品、金额、状态OrderItems订单明细表一个订单对应一条商品记录Messages站内留言表买家咨询卖家用为什么不把 OrderItems 并进 Orders因为订单和明细是主从关系订单存一次快照成交价、成交时间明细存商品引用。二手交易里商品可能被下架甚至删除订单里必须冗余一份成交价不能只靠关联商品表去查——商品删了历史订单就变成一笔糊涂账。3.2 建表 SQL主外键、唯一索引与状态字段一起落这类项目交付时一般会带一份初始化 SQL。我习惯把建表脚本写成可重复执行的用 IF NOT EXISTS 包一层避免第二次运行报错CREATE TABLE dbo.Users ( Id INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(50) NOT NULL, PasswordHash NVARCHAR(64) NOT NULL, Salt NVARCHAR(16) NOT NULL, Phone NVARCHAR(20) NULL, CreatedAt DATETIME NOT NULL DEFAULT GETDATE() ); CREATE TABLE dbo.Products ( Id INT IDENTITY(1,1) PRIMARY KEY, SellerId INT NOT NULL CONSTRAINT FK_Products_Users FOREIGN KEY REFERENCES dbo.Users(Id), Title NVARCHAR(100) NOT NULL, Description NVARCHAR(MAX) NULL, Price DECIMAL(10,2) NOT NULL, OriginalPrice DECIMAL(10,2) NULL, ImageUrl NVARCHAR(200) NULL, Status INT NOT NULL DEFAULT 0, -- 0在售 1已售 2下架 CreatedAt DATETIME NOT NULL DEFAULT GETDATE() ); CREATE INDEX IX_Products_Status ON dbo.Products(Status, CreatedAt DESC);字段设计上有几个点别省UserName 用 NVARCHAR 而不是 VARCHAR不然中文用户名直接变问号PasswordHash 存加盐后的 MD5长度 64 是因为 MD5 十六进制输出正好 32 个字符我习惯把盐拼进去一起算CreatedAt 默认 GETDATE()插入时不用手动赋值。浏览量、SKU 这类看似有用但毕设阶段用不上的字段先不加别把简单项目做臃肿。如果你习惯把建表脚本塞进 C# 代码里执行注意 SqlCommand 一次执行多句 SQL 时GO 批处理分隔符是 SSMS 的工具语法ADO.NET 驱动不认。常见做法是按 GO 把脚本拆成多个批次或者干脆用 EF Core 的 Migrate 方法建库省掉手动拆分。3.3 两个二手交易特有的设计商品状态机与图片存路径不存字节第一个是商品状态。二手交易里一个商品的生命周期是在售 → 有人下单付款 → 已售 → 下架。这个状态千万别用布尔 IsSold 来表示因为还有下架但不售出的情况。用 INT 状态位0/1/2 对应不同阶段后面订单状态机也跟它对上业务逻辑会清晰很多。第二个是图片存储。常见错误是数据库里存 byte[] 二进制图片理由是防删除。但在 Web 应用里这会导致每次列表页都要把全表图片读进内存页面又慢又占带宽。正解是图片文件存 wwwroot/uploads 目录数据库只存相对路径字符串——展示时拼到 img 标签的 src 上就行。列表页建议再做一层缩略图用 ImageSharp 生成 300x300 的小图存到 uploads/thumbs首屏加载会快很多答辩时也算一个能说的优化点。4. 核心功能实现注册登录、商品发布、搜索分页与订单流转四张牌打完项目核心链路就完整了。我按用户实际操作顺序来讲。4.1 注册登录加盐 MD5 和 Session别把整个用户对象塞进会话密码存储是答辩必问题。明文存密码在任何场合都说不过去正确做法是加盐哈希public static string GenerateSalt(int length 8) { const string chars ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789; return new string(Enumerable.Repeat(chars, length) .Select(s s[Random.Shared.Next(s.Length)]).ToArray()); } public static string ToMd5(string input, string salt) { byte[] buffer Encoding.UTF8.GetBytes(input salt); byte[] hash MD5.HashData(buffer); return Convert.ToHexString(hash).ToLower(); }GenerateSalt 生成 8 位随机盐ToMd5 把密码 盐整体做哈希。注册时存 Salt 和 PasswordHash 两个字段登录时取出该用户的盐重算哈希再比对。这样数据库泄漏了攻击者拿到的也是加盐后的散列彩虹表直接失效。想更稳可以换 SHA256 或 BCrypt但毕设里 MD5 加盐已经能把原理讲明白。提示生产环境建议直接用 ASP.NET Core Identity加盐哈希和 Cookie 认证全包了这里手写一遍是为了让答辩讲得清原理。登录成功后Session 里只放用户 Id 和用户名两个标量别放整个 User 实体HttpContext.Session.SetInt32(UserId, user.Id); HttpContext.Session.SetString(UserName, user.UserName);把实体塞进 Session 的问题在于Session 默认存内存实体被修改后和数据库不同步而且序列化开销大。存 Id每次请求按需查库数据永远是最新的。4.2 商品发布与图片上传格式校验、重命名与保存路径发布页是表单加文件上传的典型场景。ASP.NET Core 里用 IFormFile 接文件[HttpPost] [RequestSizeLimit(5 * 1024 * 1024)] public async TaskIActionResult Publish(ProductViewModel model, IFormFile imageFile) { if (!ModelState.IsValid) return View(model); if (imageFile is { Length: 0 }) { if (imageFile.Length 2 * 1024 * 1024) { ModelState.AddModelError(, 图片不能超过 2MB); return View(model); } string ext Path.GetExtension(imageFile.FileName).ToLower(); string[] allowed { .jpg, .jpeg, .png, .gif, .webp }; if (!allowed.Contains(ext)) { ModelState.AddModelError(, 仅支持 jpg / jpeg / png / gif / webp 格式); return View(model); } string uploadDir Path.Combine(_env.WebRootPath, uploads, products); Directory.CreateDirectory(uploadDir); string fileName Guid.NewGuid().ToString(N) ext; string savePath Path.Combine(uploadDir, fileName); await using var stream new FileStream(savePath, FileMode.Create); await imageFile.CopyToAsync(stream); model.ImageUrl /uploads/products/ fileName; } // 写入 Products 表这里省略仓储代码 return RedirectToAction(nameof(Detail), new { id saved.Id }); }两个参数很关键RequestSizeLimit(5MB) 控制请求体上限防止有人传大文件把内存打爆上传文件重命名为 Guid 加原扩展名一是避免中文文件名 URL 编码问题二是天然防重名。扩展名白名单校验也必不可少——只改后缀的伪装文件虽然拦不住但能把绝大多数误操作挡在外面。4.3 商品搜索与分页从 Contains 模糊查询到 Skip/Take搜索是最常被问到的一个点。二手手机这种关键词EF Core 里直接用 Contains 翻译成 LIKE %二手手机%public async TaskIActionResult Search(string keyword, int categoryId, int page 1) { const int pageSize 12; var query _db.Products.AsNoTracking() .Where(p p.Status ProductStatus.OnSale); if (!string.IsNullOrWhiteSpace(keyword)) { query query.Where(p p.Title.Contains(keyword) || p.Description.Contains(keyword)); } if (categoryId 0) { query query.Where(p p.CategoryId categoryId); } int total await query.CountAsync(); var list await query .OrderByDescending(p p.CreatedAt) .ThenByDescending(p p.Id) .Skip((page - 1) * pageSize) .Take(pageSize) .ToListAsync(); ViewBag.TotalPages (int)Math.Ceiling((double)total / pageSize); ViewBag.CurrentPage page; return View(list); }这里说三点。第一AsNoTracking() 对只读查询不是必须的但加上能省掉 EF 的变更跟踪开销列表页习惯性加上准没错。第二分页必须 OrderBy且排序字段后面要跟一个唯一字段——只按 CreatedAt 排序两条记录时间一样时翻页会出现重复或漏数据这个坑后面避坑章专门讲。第三Contains 在几千条数据时没问题数据量过十万就要考虑全文索引了毕设阶段放心用。4.4 订单状态机用委托和事件解耦状态变更通知订单是交易平台最核心的业务。我的实现方式是一个状态枚举加一个静态转移校验public enum OrderStatus : int { PendingPay 0, // 待付款 Paid 1, // 已付款 Shipped 2, // 已发货 Completed 3, // 已完成 Cancelled 4, // 已取消 Refunding 5 // 退款中 } public static bool CanTransfer(OrderStatus now, OrderStatus next) { return (now, next) switch { (OrderStatus.PendingPay, OrderStatus.Paid) true, (OrderStatus.PendingPay, OrderStatus.Cancelled) true, (OrderStatus.Paid, OrderStatus.Shipped) true, (OrderStatus.Shipped, OrderStatus.Completed) true, (OrderStatus.Shipped, OrderStatus.Refunding) true, (OrderStatus.Refunding, OrderStatus.Completed) true, (OrderStatus.Refunding, OrderStatus.Cancelled) true, _ false }; }状态转移表写死之后任何非法流转比如已取消的订单直接变已完成在进入服务层之前就被拦住了。这个设计答辩时非常好讲画一条状态链告诉老师每一步转移都经过校验。然后是用 C# 委托和事件把状态变了这个动作解耦出去public class OrderService { public event EventHandlerOrderStatusChangedEventArgs? OrderStatusChanged; public bool UpdateStatus(int orderId, OrderStatus target) { var order _db.Orders.Find(orderId); if (order is null || !CanTransfer((OrderStatus)order.Status, target)) return false; order.Status (int)target; _db.SaveChanges(); OrderStatusChanged?.Invoke(this, new OrderStatusChangedEventArgs(order.Id, target)); return true; } }调用方只需要订阅事件卖家发货后给买家发站内信、订单完成后给双方发通知这些逻辑不用写在 UpdateStatus 里业务代码不会越积越乱。委托和事件是 C# 里特别适合这种场景的特性比硬编码 if-else 通知列表干净得多。5. 避坑排查这类项目里我翻过车的五个现场挑五个真实出现过的坑按现象、原因、解决三段式讲。前四个是我自己做项目踩过的第五个是帮别人排查时发现的每一个都能省下半天排查时间。5.1 现象Session 反复失效登录态坚持不过十分钟登录后没几分钟就被踢回登录页刷新一下又好再刷又掉堪称玄学。原因有三个常见来源一是 Program.cs 里注册了 AddSession 却没调用 app.UseSession()二是 IdleTimeout 设得太短三是 Session 里存了带循环引用的实体对象取的时候序列化失败被异常处理逻辑当成未登录踢出去。解决先确认 UseSession() 的位置必须在 UseRouting() 之后、控制器执行之前。再把 IdleTimeout 调到 30 分钟以上Session 键只存 userId、userName 这种基础类型。改完重启重新登录验证。5.2 现象图片上传成功页面就是 404 打不开发布商品后图片裂了直接访问 /uploads/products/xx.jpg 返回 404但文件明明在 wwwroot 目录里。原因一是保存路径和访问路径不一致用了 Server.MapPath 拼出的路径和实际 wwwroot 对不上二是文件名带中文或空格浏览器 URL 编码后找不到文件三是部署到 IIS 后应用程序池对这个目录没有读取权限。解决统一用 Path.Combine(_env.WebRootPath, uploads, products) 保存数据库里存相对 URL展示时直接拼 img 的 src。文件名一律 Guid 重命名中文空格问题直接消失。部署 IIS 时给站点应用程序池加对应账户的读取权限。5.3 现象搜索框输入引号接口直接报错搜索手机没问题一搜1 OR 11这种内容页面直接 500有的项目甚至报 SQL 语法错误。原因典型的 SQL 注入特征。老代码用字符串拼接 SQLSELECT * FROM Products WHERE Title LIKE % keyword %引号一闭合就出语法错误恶意一点就是数据泄露。解决EF Core 的 LINQ 天然参数化Dapper 用new { keyword kw }传参。只要代码里看不到拼接 SQL 的痕迹这问题就实实在在解决了。答辩被问就把参数化查询四个字讲清楚参数和 SQL 语句分开传输数据库永远把参数当数据不当代码。5.4 现象中文乱码插入变问号导出变空白页面上输入手机壳存进数据库变成????导出的 CSV 打开是乱码。原因表字段用了 VARCHAR 而不是 NVARCHAR或者连接串没有指定编码CSV 导出用了系统默认编码而不是 UTF-8。解决SQL Server 里中文、emoji 这类字符一律用 NVARCHAR 字段建表时把 Title、Description、UserName 全部写成 NVARCHARCSV 导出时指定 UTF-8 带 BOMnew StreamWriter(path, false, new UTF8Encoding(true))这样 Excel 打开不乱码。这是建表时就该定的规矩后面改字段类型要动一堆代码。5.5 现象分页翻到第二页数据跟第一页重复列表页第一页和第二页有重复商品有的商品翻三页都找不到。原因分页查询只按 CreatedAt 排序而 CreatedAt 精确到秒同一秒插入的多条记录排序不稳定SQL Server 每次返回的顺序可能不一样Skip/Take 自然就乱了。解决排序加唯一字段兜底.OrderByDescending(p p.CreatedAt).ThenByDescending(p p.Id)。这是分页的通用规则——排序字段里必须包含唯一列否则分页结果就是黑匣子。以后加按价格排序也一样后面必须再跟一个 Id。6. 交付前的一小时从能跑到能演、能答辩的过关清单功能写完只能算第一步能演示和能答辩才是及格线。我一般会在交付前用一整块时间做回归零散测试最容易漏掉状态流转这种跨页面逻辑。6.1 十分钟回归测试清单把主链路完整走一遍按真实用户操作顺序过一遍操作预期结果常见失败点注册新用户跳转登录页能正常登录盐没存导致密码比对失败发布带图片商品列表页能看到缩略图图片路径或 wwwroot 权限搜索商品结果与关键词相关LIKE 通配符转义买家下单订单状态变待付款事务没提交卖家确认发货状态按状态机流转非法跳转校验漏了买家确认收货订单完成卖家收到通知事件订阅没注册卖家下架商品前台列表不再展示Status 条件没过滤跑完这七步主链路基本稳了。然后打开浏览器开发者工具看 Network 面板有没有红色请求、Console 有没有 JS 报错——这两个是演示现场最常见的翻车点。6.2 两个容易被追问的设计点并发下单与图片防重答辩老师大概率会问两个用户同时买同一件商品怎么办。最简方案是下单时用 UPDATE 带条件的方式占住商品UPDATE dbo.Products SET Status 1 WHERE Id productId AND Status 0; -- 影响行数为 1 说明抢到了继续创建订单否则提示商品已被买走这条 SQL 是原子的数据库行锁会帮你挡住并发比先 SELECT 再 UPDATE可靠得多。图片防重同理Guid 文件名天然防重不用额外判断。最后说一个习惯我每次交付前都会做一次从零恢复——删掉数据库从初始化 SQL 建库再完整跑一遍主链路。数据库建不出来或者初始化脚本跑不通的项目演示时比代码 bug 还致命因为现场没人有时间帮你排查环境问题。希望这个习惯和前面这些细节能帮到你。本文还有配套的精品资源点击获取
返回列表