ARTICLE DETAIL

资讯详情

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

asp.net网上自动点餐系统毕设跑通指南:从Web Forms到订单事务

asp.net网上自动点餐系统毕设跑通指南:从Web Forms到订单事务 简介一份基于C#与ASP.NET开发的网上自动点餐系统毕业答辩项目面向Web方向学生、毕业生及初学ASP.NET开发者适合课程设计、项目实战与答辩参考。系统覆盖用户注册、菜单展示、菜品选择、订单提交与记录、留言反馈等完整流程兼具前台界面与后台业务逻辑能体现需求分析、数据库设计、页面复用到编码实现的主要环节。压缩包共855个文件约4.73MB以gif图片、aspx页面、cs逻辑代码、css样式、xml配置为主并含mdf/ldf数据库文件与dll程序集目录结构完整。已有255人学习浏览。资源包含用户控件、数据库及完整页面文件便于还原运行环境也可对照学习ASP.NET Web Forms的分层组织与用户控件复用方式对毕业设计或C# Web开发能力提升有实际帮助。1. 这套 asp.net 网上自动点餐系统是毕设包里最容易被“跑不起来”卡住的一类拿到这个「asp.net网上自动点餐系统完整版毕业答辩项目.zip」第一反应别急着解压。这个标题在高校毕设里几乎是个标准品类B/S架构、Web Forms页面、SQL Server数据库、订单从生成到出餐的完整流程。它要解决的是一件事——让顾客在网页上完成“看菜、加购、下单、查状态”这一个闭环同时让后台能维护菜品和订单。“自动”两个字不是指机器人炒菜而是订单提交后状态能自动流转到后厨不用人拿小本子登记。适合两类人一类是正在做毕业设计、需要快速把系统跑起来答辩的学生另一类是想在简历上写“独立完成过完整业务系统”的初级开发者。这套东西技术栈偏老但业务链路完整用来学习“数据库设计事务会话管理”非常划算。真正要小心的是它跑起来之前的那些坑。2. 网上点餐系统的架构与数据库Web Forms 三层结构加 8 张核心表2.1 为什么这个题目还值得做B/S 三层架构正好卡在本科毕设的评审点上很多学生拿到这个标题会犹豫asp.net Web Forms 都算老古董了现在企业不是用 asp.net core 就是前后端分离做它还有什么价值我的看法是本科毕设的评审老师看重的不是框架新不新而是“你有没有把业务做完整”。网上自动点餐系统的业务链条足够长用户注册登录、菜品分类展示、购物车、订单提交、库存扣减、订单状态流转这已经覆盖了一个信息管理系统该有的全部模块。再加上 Web Forms 天然把页面和后台逻辑分开UI层、业务层、数据访问层三层结构非常好讲清楚。常见的做法是项目里按 Model / DAL / BLL / Web 四个目录组织代码DAL 只写 SQL 访问BLL 写业务规则Web 放 aspx 页面。这样分层有个直接好处答辩时老师问“如果菜品下架了购物车里还没提交的数据怎么办”你可以直接指出问题发生在 BLL 层而不是在页面里然后演示代码怎么处理。这个“分层业务规则”的组合才是这套毕设题目真正的得分点。我一般会建议先把数据库表结构理清楚因为后面所有代码都是围绕表来的表设计不合理写多少代码都是打补丁。2.2 数据库设计8 张核心表、2 个不能省的索引、1 条外键铁律这套系统的表结构在多数版本里是大同小异的核心业务逃不开用户、菜品、购物车、订单、订单明细这几个实体。常见的完整版包里数据库脚本一般是一整个 db_Order.sql 或者 RestaurantDB.sql里面建了至少 8 张表。表名用途必填字段示例tb_user顾客和管理员账号UserId, UserName, Password, IsAdmintb_category菜品分类CategoryId, CategoryName, SortOrdertb_dish菜品表DishId, CategoryId, DishName, Price, Stock, ImageUrl, IsOnSaletb_cart购物车选做CartId, UserId, DishId, Quantity, AddTimetb_order订单主表OrderId, OrderNo, UserId, TotalPrice, Status, OrderTimetb_order_detail订单明细表DetailId, OrderId, DishId, DishName, Price, Quantitytb_desk桌号堂食场景DeskId, DeskName, IsFreetb_review评价表ReviewId, OrderId, UserId, Content, Rate表格里最值得注意的就是 tb_order_detail 的 DishName 和 Price 两个字段。很多人建表时会把订单明细直接关联 tb_dish只存一个 DishId觉得这样精简。这就是最容易翻车的设计菜品后来改了名、改了价甚至被删掉历史订单显示就会跟着变财务对不上账。正确做法是下单那一刻把菜名、单价、数量原样快照进订单明细DishId 只作为一个追溯线索不做外键强约束。这条“快照铁律”不仅在点餐系统里适用电商、进销存系统里同样成立。另外两个索引不能省tb_order 的 OrderNo 要建唯一索引因为它要支撑“根据订单号查状态”这个高频操作tb_order_detail 的 OrderId 要建普通索引否则后台查某个订单的明细会全表扫描。这两个索引在数据量只有几千条时感觉不出来一旦真拿去演示并发下单性能差别立竿见影。建表脚本大概是这个风格CREATE TABLE tb_order ( OrderId INT IDENTITY(1,1) PRIMARY KEY, OrderNo NVARCHAR(32) NOT NULL, UserId INT NOT NULL, TotalPrice DECIMAL(10,2) NOT NULL, Status TINYINT NOT NULL DEFAULT 0, OrderTime DATETIME NOT NULL DEFAULT GETDATE() ); GO CREATE UNIQUE INDEX IX_Order_OrderNo ON tb_order(OrderNo); CREATE INDEX IX_OrderDetail_OrderId ON tb_order_detail(OrderId);这段 SQL 里 OrderNo 用 NVARCHAR(32) 而不是 VARCHAR是因为订单号一般由日期随机数组成里面带中文或特殊符号的情况虽然少见但用 NVARCHAR 编码更稳。Status 我用 TINYINT 表示订单状态0 待支付、1 已支付、2 制作中、3 已送达而不是直接用字符串是为了后续用 switch 判断状态时更省事也方便在数据库里按数字范围统计。2.3 本机跑通的最小配置IIS、SQL Server 和连接字符串的先后顺序拿到源码先别急着双击 .sln先确认环境。这套系统常见的目标框架是 .NET Framework 4.0 或 4.5跑起来需要三样Visual Studio2015 以上都行、SQL Server2008 R2 到 2019 都可以、IIS可选调试时用 IIS Express 就够。很多新手在这里卡了两三天就是因为少了“先附加数据库”这一步。我拿到这种打包项目一般按这个顺序检查打开 SQL Server Management Studio先执行 zip 里的数据库脚本生成库或者在“数据库”右键选择“附加”把 .mdf 文件挂上去。然后打开 web.config确认 connectionString 里的服务器名、用户名、密码和本机一致。最常见的配置是这样的connectionStrings add nameOrderDB connectionStringData Source.;Initial CatalogRestaurantDB;User IDsa;Password123456;MultipleActiveResultSetsTrue; providerNameSystem.Data.SqlClient / /connectionStrings /xml这里的 Data Source. 表示本机默认实例换成 (local) 或者 localhost 也可以Initial Catalog 必须是数据库的真实名字。MultipleActiveResultSetsTrue 这个参数最好保留因为 asp.net 页面里常见的使用方式是一个 SqlDataReader 还没关闭就去打开第二个连接打开这个选项能减少一部分“连接正忙”的报错。如果连接串写的是 Data SourceWIN-XXXXXXXX\SQLEXPRESS 这种说明原项目是在别人电脑上开发的你要改成自己的实例名。配置完成后启动调试先在浏览器里打开登录页拿 zip 里说明文档给的测试账号登录一次如果报“用户 sa 登录失败”先回 SQL Server 确认是否启用了 SQL Server 身份验证模式右键实例选择“属性→安全性”里修改。这步做完系统能登录后面才有调代码的意义。3. 把点餐核心流程写成代码购物车、订单提交与状态流转3.1 菜单列表和“加入购物车”从 DataReader 到页面绑定的写法页面端常见的做法是一个 Menu.aspx 在 Page_Load 里调用 BLL 的 GetDishList()返回 DataTable 绑定到 Repeater 控件。这是一个非常经典的 Web Forms 写法核心代码在 DAL 层public DataTable GetDishList(int categoryId) { string sql SELECT DishId, DishName, Price, ImageUrl, Stock, IsOnSale FROM tb_dish WHERE IsOnSale1; if (categoryId 0) { sql AND CategoryIdCategoryId; } using (SqlConnection conn new SqlConnection(ConfigurationManager.ConnectionStrings[OrderDB].ConnectionString)) using (SqlCommand cmd new SqlCommand(sql, conn)) { if (categoryId 0) { cmd.Parameters.AddWithValue(CategoryId, categoryId); } SqlDataAdapter da new SqlDataAdapter(cmd); DataTable dt new DataTable(); da.Fill(dt); return dt; } }注意这里用 SqlDataAdapter 而不是 SqlDataReader原因很简单DataTable 要在页面回发后继续使用而 DataReader 是只进流关掉连接后就没法再读取了。Adapter 会把结果一次性载入内存适合数据量不大的菜单展示。参数化查询必须用 Parameters.AddWithValue不要拼字符串进 SQL否则一旦用户在分类参数里传了恶意代码整张表都可能被拖走。加购按钮的点击事件里用一个 Session 字典来存菜品和数量。很多毕业设计版本是直接用 Cookie 存这在小规模演示时也能跑但 Session 更贴近真实场景。代码大概是protected void btnAdd_Click(object sender, EventArgs e) { int dishId Convert.ToInt32(hiddenDishId.Value); int quantity 1; ListCartItem cart Session[Cart] as ListCartItem; if (cart null) { cart new ListCartItem(); } CartItem existing cart.Find(i i.DishId dishId); if (existing ! null) { existing.Quantity quantity; } else { cart.Add(new CartItem { DishId dishId, Quantity quantity }); } Session[Cart] cart; }CartItem 是一个自定义实体类至少包含 DishId、DishName、Price、Quantity。这里第一次加载菜品信息时就应该把 DishName 和 Price 存进购物车而不是只存 DishId原因和订单快照一样——Session 里的数据可能在用户停留期间与数据库不一致。购物车界面显示总价时直接遍历 Session 累加就行不用查库。3.2 购物车用 Session 还是数据库两种方案的取舍很多模板代码里会建一张 tb_cart 表把购物车数据写进数据库理由是“用户换一台电脑购物车还在”。这个理由在真实互联网产品里成立但在这套毕业设计里是错误的优先级引入数据库购物车意味着每次加购、删购、改数量都要写库还需要处理游客未登录时用什么身份标识购物车复杂度翻倍。我的建议是课堂演示和答辩演示用 Session 购物车然后在论文“系统优化”章节写一句“生产环境可升级为数据库购物车以支持多端同步”。这样既控制开发量答辩也有话可说。Session 购物车最大的坑是进程回收后会丢IIS 默认空闲超时 20 分钟回收工作进程Session 就没了。真遇到演示时购物车突然清空临时解决办法是延长 IIS 空闲超时把 web.config 里的 sessionState 配置改一下system.web sessionState modeInProc timeout120 / /system.webtimeout 单位是分钟120 是两小时。这只适合演示生产环境 Session 放内存的扩展性很差所以论文里别把这个当优点写。3.3 订单提交的唯一事务订单主表和明细表必须一起写这是整套系统里最值得细看的一段代码也是答辩时最容易被追问的地方。提交订单时要做的事有 4 件生成订单号、插入订单主表、插入订单明细、扣减菜品库存。这 4 件事必须在一个数据库事务里完成否则会出现“订单建好了库存没扣”或者反过来“扣了库存订单失败”的脏数据。常见写法是 Page_Load 里收集购物车数据然后在 BLL 层用 TransactionScope 或 SqlTransaction 包裹using (SqlConnection conn new SqlConnection(connString)) { conn.Open(); using (SqlTransaction tran conn.BeginTransaction()) { try { string orderNo DateTime.Now.ToString(yyyyMMddHHmmss) new Random().Next(1000, 9999); string sqlOrder INSERT INTO tb_order(OrderNo, UserId, TotalPrice, Status, OrderTime) VALUES(OrderNo, UserId, TotalPrice, 1, GETDATE()); SELECT SCOPE_IDENTITY();; SqlCommand cmdOrder new SqlCommand(sqlOrder, conn, tran); cmdOrder.Parameters.AddWithValue(OrderNo, orderNo); cmdOrder.Parameters.AddWithValue(UserId, userId); cmdOrder.Parameters.AddWithValue(TotalPrice, totalPrice); int orderId Convert.ToInt32(cmdOrder.ExecuteScalar()); string sqlDetail INSERT INTO tb_order_detail(OrderId, DishId, DishName, Price, Quantity) VALUES(OrderId, DishId, DishName, Price, Quantity);; string sqlStock UPDATE tb_dish SET Stock Stock - Quantity WHERE DishIdDishId AND Stock Quantity;; foreach (CartItem item in cart) { SqlCommand cmdDetail new SqlCommand(sqlDetail, conn, tran); // 参数赋值后 ExecuteNonQuery() SqlCommand cmdStock new SqlCommand(sqlStock, conn, tran); // 参数赋值后 ExecuteNonQuery() } tran.Commit(); } catch { tran.Rollback(); throw; } } }这段代码里最关键的不是 Insert而是 UPDATE tb_dish ... WHERE Stock Quantity。这句是数据库层面的“库存够才扣”约束如果影响行数为 0说明库存不足事务会回滚订单不会生成。这比先在代码里查一次库存再下单要安全得多因为两个并发请求同时读到库存为 1 时后一个 UPDATE 会因为条件不满足而失败而不是把库存扣成负数。订单号生成用了时间戳加随机数在高并发的真实场景下不够唯一但在毕设演示里够用。数据库里 OrderNo 已经建了唯一索引万一随机数撞了插入会报错并回滚也算兜底。3.4 订单状态流转从“已支付”到“已送达”的代码实现自动点餐系统的核心表现力全在状态流转上。用户下单后订单状态是“已支付”后厨看到后改成“制作中”出餐后改成“待配送”最后“已送达”。这个流程要用代码实现而不是直接改数据库否则后台管理系统就白做了。一个简洁的状态机是在 BLL 层写一个枚举和对应的方法public enum OrderStatus { PendingPayment 0, Paid 1, Making 2, Delivering 3, Done 4, Canceled 9 } public bool UpdateOrderStatus(int orderId, OrderStatus targetStatus) { string allowed GetAllowedNextStatus(orderId); // 校验 targetStatus 是否在允许的下一步里 string sql UPDATE tb_order SET StatusStatus WHERE OrderIdOrderId AND StatusCurrentStatus; // 执行更新返回影响行数 0 }这里 UPDATE 语句里带上了当前状态作为条件是为了防止两个管理员同时操作把状态从“制作中”又改回“已支付”。这种乐观锁的思想在答辩里提一句老师会觉得你是真写过代码的人。用户端的“查看我的订单”页面只需要根据 UserId 查订单主表再联查订单明细表展示。注意如果订单很多分页不能漏掉用 SqlDataSource 自带的分页或者自己写 ROW_NUMBER() 分页都行关键是别把几千条订单一次性全绑到页面上。4. 点餐系统避坑清单并发抢菜、重复提交与售罄下架4.1 并发下单导致库存变成负数锁和事务隔离级别怎么配现象两个用户同时买同一个只剩 1 份的菜两个人都下单成功库存变成 -1。原因代码里“先查库存再扣库存”两步之间没有加锁事务隔离级别默认是 Read Committed两个请求同时读到库存 1都认为可以扣。解决把扣库存的 UPDATE 语句作为唯一入口并用条件判断优化或者在 SELECT 时加 UPDLOCK 提示。BEGIN TRANSACTION; UPDATE tb_dish SET Stock Stock - 1 WHERE DishIdDishId AND Stock 1; IF ROWCOUNT 0 BEGIN ROLLBACK TRANSACTION; -- 返回“库存不足” END ELSE BEGIN INSERT INTO tb_order(...) VALUES(...); COMMIT TRANSACTION; END这个方案的核心是 UPDATE 自带行锁两个并发事务同时执行时第二个会等待第一个提交或回滚然后检查 Stock 1 时会发现不满足直接回滚。如果没有这一句条件只写 SET Stock Stock - 1库存就会变成 -1。真实场景里还可以把隔离级别调成 Read Committed 加 UPDLOCK但毕业设计用 UPDATE 条件判断就够了这段 SQL 可以直接写进存储过程作为加分项。4.2 用户双击提交按钮订单库里瞬间多出两条一样的订单现象订单页面加载慢用户等不及连点了三次“提交订单”最后一查数据库有三条一模一样的数据。原因按钮没有在点击后禁用而服务端又没有做防重判断。解决前端先禁用按钮后端再用唯一约束兜底。前端在 ASP.NET Web Forms 里常见做法是放一个 PlaceHolder 或者直接给按钮加 OnClientClickasp:Button IDbtnSubmit runatserver Text提交订单 OnClientClickthis.disabledtrue; this.value提交中...; /如果用户在点击后 F5 刷新页面这个按钮状态会恢复所以服务端也必须防。最简单的服务端防重是在订单表里加一个字段存客户端生成的“唯一提交令牌”GUID 写在页面 hidden field 里提交时带上。如果 tb_order 的 SubmitToken 有唯一索引重复提交第二次插入就会报唯一键冲突代码捕获后直接跳转“订单已存在”页面。这在答辩里演示一下比嘴上说“我做了防重复”有说服力得多。4.3 菜品售罄了菜单页还在显示“加入购物车”现象后台把某道菜库存改成 0也在菜品列表里下架了但前台菜单页还能看到它而且能加购。原因菜单页在第一次访问时把菜品列表缓存到了 Application 或 OutputCache后台更新没有触发缓存刷新。解决如果用了 OutputCache在菜品表更新时调用 Response.RemoveOutputCacheItem或者干脆菜单页不缓存每次加载查库。这个坑在很多版本里都存在因为做毕设的人喜欢用缓存提高性能但忘了更新入口。我的建议是第一个版本不要加任何缓存等答辩演示正常后再把“加了缓存如何保证同步”作为优化项写进论文。否则你自己都会忘记缓存失效策略演示时当场翻车。4.4 中文乱码和时间格式错乱两处最不起眼又最容易扣分的细节现象菜品名称在页面显示成 “???”或者订单时间显示成 “2024/1/5 9:05” 这种没补零的格式。原因数据库排序规则和页面编码不一致以及 DateTime 直接 ToString()。解决连接字符串里去掉不合适的 charset 设置页面统一 UTF-8时间格式化用自定义格式串。litTime.Text order.OrderTime.ToString(yyyy-MM-dd HH:mm:ss);这行代码看起来简单但很多模板项目里写的是 Convert.ToString(order.OrderTime)输出格式会跟随服务器区域性设置答辩时换一台电脑显示就变了。统一用指定格式是最稳妥的做法。如果你拿到项目后发现页面乱码先检查 web.config 里的 globalization 节globalization requestEncodingutf-8 responseEncodingutf-8 fileEncodingutf-8 /这三个属性同时设置后绝大多数乱码问题能解决。至于数据库里的数据已经乱了的只能在 SQL Server 里改排序规则或者重建那几张表没有后悔药。5. 这个 zip 里的非代码部分数据库脚本、论文骨架和答辩 PPT5.1 连接字符串和附加数据库拿着源码跑不起来的八成输在这里很多人解压这个 zip 之后第一件事是打开 Visual Studio 直接编译结果报一堆 SqlException然后怀疑代码缺文件。实际上这类打包项目里最容易出问题的不是代码而是 SQL Server 数据库没有正确挂载。压缩包里通常带 .bak 备份文件或 .mdf 数据文件你需要用 SSMS 把库恢复或附加进来。附加数据库失败时最常见报错是“无法打开物理文件 .mdf操作系统错误 5拒绝访问”这是 SQL Server 进程没有权限读取你把文件放到的文件夹把 .mdf 复制到 SQL Server 的默认数据目录C:\Program Files\Microsoft SQL Server\MSSQL\DATA 或按版本对应再附加一般就能解决。另一个高发问题是日志文件 .ldf 和 .mdf 分开放附加时选了主文件后系统找不到日志文件。把它俩放在同一个目录再附加一次即可。5.2 论文怎么写才能自圆其说从需求分析到测试用例的顺序评价一套论文与代码是否匹配老师最高频的一个动作是翻开数据库设计章节找表名再翻源码找这些表对应哪段代码。所以论文最忌讳的是自己画一堆功能框图却没有任何 SQL 脚本和页面对应。常见写法是五章结构需求分析、系统设计、系统实现、系统测试、总结。系统设计里放 E-R 图和数据表清单系统实现里按“登录模块、菜品管理、购物车、订单管理”逐个贴页面截图加关键代码测试章节一定要有测试用例表。测试模块测试步骤预期结果是否通过登录模块输入错误密码 3 次提示账号锁定是购物车模块同一菜品加购 2 次数量累加是订单模块两个账号同时购买同一库存为 1 的菜品一个成功一个库存不足是这张表里如果写出“并发购买”的测试场景论文质量会明显上一个台阶因为这个测试说明你不只是跑通了界面还考虑到了数据一致性。测试环境一栏写“Windows 10 SQL Server 2019 VS2019”这类信息就行。5.3 答辩 PPT 的 5 页节奏别照着论文念要讲你做了什么答辩 PPT 一般控制在 10 页以内核心页其实只有 5 页。第 1 页讲选题背景要点是“网上点餐节省人力、提高效率”两句话收住。第 2 页放系统功能结构图画出角色、游客/用户/管理员各自的权限。第 3 页放数据库 E-R 图然后把订单明细表的“快照字段”专门圈出来讲一下。第 4 页放核心代码截图不要整段贴只放订单提交那段事务处理的代码。第 5 页放演示视频或现场演示这页最关键演示时先把数据库服务打开别当着老师面改配置。这 5 页下来老师能看到的明显产出就是“业务完整、数据库有设计、难点自己能说清”。最差的做法是把需求分析整章抄进 PPT念完就是灾难。6. 毕业之后想做成“能上线”的点餐系统从 Web Forms 迁移到 asp.net core 的加减法如果你做完这个毕设还想把它改成真正的产品迁移路线不是逐页面翻写而是要做减法。优先把订单提交和库存扣减这组逻辑抽成核心服务其余页面尽量不动。Web Forms 里的 Session、Repeater、Click 事件这三大件在 asp.net core 里都不存在了常见做法是先建一个 Razor Pages 项目把登录、点餐、购物车三个页面搬过去后台管理暂时保留旧项目不动。迁移时值得重点关注的是数据访问层。原来满屏幕的 SqlConnection 和 SqlCommand 你会写得痛不欲生建议直接引入 Dapper 这种轻量 ORM把核心的订单事务代码缩到十行以内。购物车逻辑可以继续用 Session但要在 Program.cs 里显式注册builder.Services.AddDistributedMemoryCache(); builder.Services.AddSession(options { options.IdleTimeout TimeSpan.FromMinutes(120); });这个配置解决的是 asp.net core 默认不启用 Session 的问题很多从 Web Forms 过来的人在这里栽跟头。加了这两行Session[Cart] 的用法和旧项目几乎一样。想更进一步的话把前端菜单页改成微信小程序或 Blazor 都可以但我会劝你先别动。你刚迁移完时最值钱的成果是清单、下单接口、订单状态查询接口 这几个 Web API 能独立工作。小程序只是换一层皮调用接口而已。我当年做这类重构时最大的教训是试图一次性把前端和后端全换成新的结果改了两周还在调按钮样式。先保住核心业务链路把下单一环跑顺这是最有价值的二十个小时。希望帮到你。本文还有配套的精品资源点击获取
返回列表