又到了计算机专业每年最难熬的开题季,我这两年帮着带过好几个学弟学妹的毕业设计,发现"健身网站"这类题目出现的频率越来越高。它不是那种烂大街的商城系统,功能复杂度又足够撑起一篇合格的毕业设计——前台有课程展示、教练介绍、在线预约,后台有会员管理、预约审核、数据统计,一圈做下来,.NET下的MVC路由、EF Core、Session权限控制这些核心技能基本都练到了。这篇就拿"基于.NET的健身网站"当样本来拆,从需求分析、技术选型、数据库设计,到核心功能编码、部署排错、答辩准备,把整条链路掰开讲清楚。正在做同类题目的同学,或者准备接手类似项目的人,可以照着这套思路落地。
1. 开题先想清楚:这个健身网站题目到底在考什么
1.1 题目拆解:net、阿正健身、设计与实现各是什么
很多同学拿到题目第一反应是"net是什么意思"。这里的net指的是 .NET 技术栈,对应到实现层面通常就是 ASP.NET 系列框架,而不是"网络"的缩写。阿正健身是站点品牌名,可以理解成一个叫阿正的私教或健身工作室的线上门户。设计与实现则意味着文档里要有完整的需求分析、数据库设计、系统架构设计,代码里要把这些设计跑通,缺一不可。
这个题目好在哪?健身网站既有内容展示型模块(课程、教练、资讯),又有业务型模块(预约、审核、会员管理),覆盖的知识点横跨了多数毕业设计的基本盘。你不需要像电商那样处理复杂的订单状态机,也不需要像社交产品那样考虑实时通讯,但"注册登录—浏览—预约—后台审核—个人中心查看结果"这条完整链路,和真实企业系统里常见的业务闭环非常接近,答辩时讲起来很有说服力。
1.2 三类用户角色与主业务链路
健身网站如果只做展示页,那工作量撑不起毕业设计。合理的角色划分应该是三种:普通访客/注册会员(前台)、管理员(后台)、课程教练(内容维护,可选)。三种角色对应三条不同的功能权限线,这就是系统设计的核心骨架。
主业务链路可以这样描述:用户打开网站浏览课程 → 注册登录后选择感兴趣的健身课程(减脂、增肌、瑜伽、搏击等)提交预约 → 后台管理员审核预约或直接确认 → 用户在个人中心看到预约状态 → 教练在课程结束后回填上课记录。围绕这条主线,再挂上公告、留言反馈、教练风采这些副线模块,功能就完整了。
1.3 功能清单和文档工作量怎么估算
做毕业设计最忌讳上来就写代码,先把功能清单列出来,你才知道工作量边界在哪。下面这个表是我建议的标准范围:
| 模块 | 前台功能 | 后台功能 | 工作量评估 |
|---|---|---|---|
| 用户 | 注册、登录、修改资料 | 会员列表、状态禁用 | 中 |
| 课程 | 分类浏览、详情、关键字搜索 | 课程增删改、上下架、图片上传 | 高 |
| 教练 | 教练列表、个人介绍页 | 教练资料的维护 | 中 |
| 预约 | 在线提交预约、查看记录 | 审核/拒绝预约、名额管理 | 高 |
| 公告 | 公告列表、详情 | 公告发布与删除 | 低 |
| 留言 | 留言、回复查看 | 留言审核、删除 | 低 |
| 统计 | 个人预约统计 | 预约量、课程热度图表 | 中 |
这七块做下来,代码量大概在五千到八千行之间,论文能写四个章节,演示可以跑二十分钟,属于一个标准且舒服的毕业设计体量。如果你学校要求更严,可以再加一个"会员卡购买"或"课程表导出"功能,难度也不会陡增。
2. 技术选型与项目分层:.NET这条线别掉进老框架的坑
2.1 版本之争:ASP.NET Core MVC 为什么胜出
我见过不少同学的参考代码还停留在 ASP.NET Web Forms,或者 .NET Framework 4.x 下的 ASP.NET MVC 5。这两种技术不是不能做,但放到2024年之后的环境里,属实有点尴尬。Web Forms那套控件拖拽的开发模式和现代Web理念差异太大,毕业设计里用起来吃力不讨好;.NET Framework 4.x则受限于跨平台和容器化,部署到新服务器上还得处理各种系统级依赖,比如在全新Windows上装 .NET Framework 3.5 或 4.8 时经常碰到0x80072f8f这类离线安装网络报错,犯不着给自己加戏。
所以我建议直接用 ASP.NET Core MVC,框架版本选 .NET 6 或 .NET 8(都是LTS长期支持版)。理由很实际:跨平台,开发机用Mac也行;可以发布成自包含(Self-Contained)模式,目标服务器上不用预装运行时,绕开一堆系统依赖问题;Razor视图、依赖注入、Session、EF Core整套生态都是现成的,学习和调试成本反而更低。
2.2 数据库选型:SQL Server 还是 MySQL
数据库这块常见的纠结是 SQL Server 还是 MySQL。我的建议很简单:跟着学校机房和论文模板走。如果你学校的数据库科目、实验环境、老师的论文模板都围绕 SQL Server,那就用 SQL Server Express(免费版,容量对毕设绰绰有余);如果周围同学和参考仓库普遍是 MySQL 搭配 Navicat,那就 MySQL。
从技术层面说,这两个库配 EF Core 的体验差距不大,SQL语句也基本通用,关键数据库设计思路是相通的。ORM选EF Core的Code First模式,直接写实体类再生成数据库,开发速度快,而且数据库设计文档可以从实体定义里倒推出来。也可以用原生的ADO.NET + SqlSugar这样的轻量ORM,但EF Core在答辩时更好讲,因为它把"对象关系映射"这个知识点天然地串起来了。
2.3 解决方案的目录结构参考
项目分层不需要太复杂,三到四层就够。我在实际帮忙调试时看到过有人把代码全堆在Controller里面,几万行代码看着头疼,答辩也讲不清楚。一个足够清晰的结构是这样的:
AzhengFitness.sln ├── AzhengFitness.Web // 表示层:Controllers、Views、wwwroot静态资源 ├── AzhengFitness.Core // 实体模型:User、Course、Appointment等 ├── AzhengFitness.Data // 数据访问:DbContext、仓储类、迁移文件 ├── AzhengFitness.Service // 业务逻辑:注册、预约、审核等规则实现 └── AzhengFitness.Utility // 公共工具:密码哈希、分页扩展、上传处理Web层只管接收请求和返回视图,业务规则放在Service层,数据访问收口在Data层,Controller保持轻量。答辩时老师问"你的系统是怎么分层的",你能顺着这个结构讲出"表现层-业务层-数据层"的耦合关系,这就是一个明确的得分点。
2.4 依赖注入和配置文件的几个习惯
ASP.NET Core 默认的依赖注入容器够用,不用额外引Autofac。在Program.cs里注册服务时,养成按生命周期区分的习惯:DbContext用AddDbContext注册(作用域生命周期),你自己的Service类注册成Scoped,和DbContext保持一致,避免并发时DbContext实例错乱。
连接字符串放在appsettings.json里,不要把数据库账号密码写死在代码中。开发环境用本地SQLite或LocalDB,发布时改成服务器上的SQL Server并替换连接串,这一句话说出来,老师就知道你不是只会照着教程抄的人。
3. 数据库设计:从功能清单反推ER模型和表结构
3.1 实体敲定:库表清单
数据库设计是整个系统能不能讲清楚的关键。我推荐先画ER图,再转表。核心实体有这些:用户表(Users)、课程分类表(CourseCategories)、课程表(Courses)、教练表(Coaches)、预约表(Appointments)、公告表(Announcements)、留言表(Messages)。
关系上:分类和课程是1对多,一个分类下面可以有多个课程;课程和教练多对1,一门课对应一个主教;用户和预约1对多,一个用户能预约多门课;课程和预约1对多。如果你加了会员卡购买,再增加订单表,和用户构成1对多关系。
3.2 核心表定义(SQL示例)
拿用户、课程、预约三张最核心的表举例,直接建表SQL如下:
CREATE TABLE Users ( Id INT IDENTITY(1,1) PRIMARY KEY, UserName NVARCHAR(50) NOT NULL UNIQUE, PasswordHash NVARCHAR(200) NOT NULL, NickName NVARCHAR(50) NULL, Phone NVARCHAR(20) NULL, AvatarUrl NVARCHAR(300) NULL, Role TINYINT NOT NULL DEFAULT 0, -- 0普通用户 1教练 2管理员 CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(), IsDeleted BIT NOT NULL DEFAULT 0 ); CREATE TABLE Courses ( Id INT IDENTITY(1,1) PRIMARY KEY, CategoryId INT NULL, CoachId INT NULL, Title NVARCHAR(100) NOT NULL, Description NVARCHAR(MAX) NULL, CoverUrl NVARCHAR(300) NULL, Price DECIMAL(10,2) NOT NULL DEFAULT 0, MaxStudents INT NOT NULL DEFAULT 20, Status TINYINT NOT NULL DEFAULT 1, -- 0草稿 1上架 2下架 CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME(), IsDeleted BIT NOT NULL DEFAULT 0 ); CREATE TABLE Appointments ( Id INT IDENTITY(1,1) PRIMARY KEY, UserId INT NOT NULL, CourseId INT NOT NULL, CourseTitle NVARCHAR(100) NULL, -- 课程名称快照 Price NVARCHAR(20) NULL, -- 预约时收费快照 Status TINYINT NOT NULL DEFAULT 0, -- 0待审核 1已确认 2已拒绝 3已完成 Remark NVARCHAR(200) NULL, CreatedAt DATETIME2 NOT NULL DEFAULT SYSUTCDATETIME() );3.3 字段设计的几条经验
第一,价格字段用 DECIMAL(10,2),绝对不要用 FLOAT 或 DOUBLE,浮点计算会出现0.1+0.2不等于0.3的问题,答辩现场露出来很尴尬。
第二,所有主表都留一个 IsDeleted 软删除标记,删除操作改成更新标记位。这不只是为了"看起来专业",而是预约历史、课程数据都有关联查询需求,物理删掉会让历史记录断链。
第三,预约表里加课程名称和价格的快照字段。课程标题修订后,历史预约记录仍然能显示当时的课程名,这是典型的"历史数据不可变"设计思路,面试和答辩都是加分项。
第四,外键约束要适度。用户和预约、课程和预约建议建外键,保证数据完整性;但分类表和教练表这种低频引用的外键不强求,避免删分类时被约束卡住。索引方面,给 Users.UserName 建唯一索引,给 Appointments(UserId, CourseId) 建组合索引,预约查重时会快很多。
3.4 EF Core 实体映射注意点
用Code First时,实体类和数据表要一一对准。小数、字段长度这类映射用数据注解最省事:
public class Course { [Key] public int Id { get; set; } [Required] [MaxLength(100)] public string Title { get; set; } [Column(TypeName = "decimal(10,2)")] public decimal Price { get; set; } public int MaxStudents { get; set; } public byte Status { get; set; } public bool IsDeleted { get; set; } public virtual ICollection<Appointment> Appointments { get; set; } }这里最容易翻车的是懒加载。EF Core 默认不启用 Lazy Loading,如果你在视图中直接用course.Appointments这类导航属性,多半会碰到空引用或者查询不出数据。解决方案有两个:要么在视图中只展示实体本身的字段,关联数据在Service层里提前查好放进ViewModel;要么查询时显式用Include加载:
var list = await _db.Courses.Include(c => c.Category).Include(c => c.Coach).Where(c => !c.IsDeleted).ToListAsync();我建议养成"查询即Include"的习惯,这也是答辩时能讲的查询优化知识点。
4. 核心功能落地:登录、权限和预约这条业务闭环
4.1 注册登录与Session权限
毕业设计级的系统,不推荐上完整版 ASP.NET Core Identity,那一套包含了角色、Claim、Token等大量概念,论文和演示时长根本讲不完,还容易把代码复杂度抬到失控。一个合理的做法是:自建Users表 + 框架自带的Session。
注册时密码绝不能存明文。简单的做法是加盐后再做SHA256哈希,完整一点可以引BCrypt.Net这个NuGet包:
public static string HashPassword(string password) { // 演示写法,实际项目建议用BCrypt或Rfc2898DeriveBytes using var md5 = System.Security.Cryptography.MD5.Create(); var bytes = Encoding.UTF8.GetBytes(password + GlobalConst.PasswordSalt); var hash = md5.ComputeHash(bytes); return Convert.ToHexString(hash); }登录成功后在Session里写入用户标识和角色:
[HttpPost] public IActionResult Login(LoginViewModel model) { if (!ModelState.IsValid) return View(model); var user = _userService.ValidateUser(model.UserName, model.Password); if (user == null) { ModelState.AddModelError("", "用户名或密码错误"); return View(model); } HttpContext.Session.SetInt32("UserId", user.Id); HttpContext.Session.SetString("UserName", user.UserName); HttpContext.Session.SetString("Role", user.Role.ToString()); return RedirectToAction("Index", "Home"); }需要提醒的是,Session默认存在内存中,站点重启会丢失,这在你自己的开发环境里不碍事。如果答辩演示时发现登录被踢下线,先检查是不是重启了应用,提前把演示流程跑一遍。
4.2 权限守住后台入口
后台管理的Controller不能只靠前端菜单隐藏来保护,必须在服务端做权限校验。写一个自制的过滤器最直观,也最容易讲:
public class AdminAuthorizeAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var role = context.HttpContext.Session.GetString("Role"); if (role != ((int)UserRole.Admin).ToString()) { context.Result = new RedirectToActionResult("Login", "Account", null); } } }然后在后台Controller或者Action上标[AdminAuthorize],非管理员会被直接打到登录页。同理,普通会员才能执行的预约动作,可以写一个MemberAuthorize,或者干脆在Service层做角色判断。权限校验做在Controller上是最基础的方案,答辩被问到时还能顺势说出"过滤器(Filter)是AOP思想的体现"——这一句能帮你把技术深度往上拉一档。
4.3 课程列表分页筛选
课程前台页面会遇到一个经典坑:一次性把几百条课程数据全部查出来再在内存里分页。体量小的时候看不出来,等数据库数据一多,页面直接变慢。正确做法是先用IQueryable拼条件,最后再Skip-Take分页:
var query = _db.Courses.AsNoTracking() .Where(c => !c.IsDeleted && c.Status == 1); if (!string.IsNullOrEmpty(keyword)) { query = query.Where(c => c.Title.Contains(keyword)); } if (categoryId.HasValue) { query = query.Where(c => c.CategoryId == categoryId.Value); } var total = await query.CountAsync(); var pageData = await query .OrderByDescending(c => c.CreatedAt) .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToListAsync();这里AsNoTracking()对只读展示是很好的习惯,会减少EF Core的跟踪开销。分页控件可以自己写一个简单的Pager泛型类,也可以在视图里用for循环生成页码链接,关键是讲清楚"为什么要数据库分页而不是内存分页"。
4.4 预约防重防超:事务怎么用
预约是整个系统业务复杂度最高的地方,也是最容易被答辩老师追问的功能。你需要在一次请求里判断三件事:课程是否上架、用户是否已经预约过、剩余名额是否充足。这三步存在竞态条件——也就是说,如果两个请求同时进来,理论上都可能通过检查,导致预约人数超过上限。
解决办法是给预约操作加数据库事务,并在名额检查时配合行级锁或通过查询实时判断:
[HttpPost] public async Task<IActionResult> Book(int courseId) { var course = await _db.Courses.FindAsync(courseId); if (course == null || course.Status != 1) return Json(new { ok = false, msg = "课程不存在或未开放预约" }); var userId = HttpContext.Session.GetInt32("UserId").Value; bool exists = await _db.Appointments .AnyAsync(a => a.UserId == userId && a.CourseId == courseId && !a.IsDeleted); if (exists) return Json(new { ok = false, msg = "你已经预约过这门课程" }); var booked = await _db.Appointments .CountAsync(a => a.CourseId == courseId && a.Status != 2); if (booked >= course.MaxStudents) return Json(new { ok = false, msg = "课程名额已满" }); await using var tx = await _db.Database.BeginTransactionAsync(); try { _db.Appointments.Add(new Appointment { UserId = userId, CourseId = courseId, CourseTitle = course.Title, Price = course.Price.ToString(), Status = 0, CreatedAt = DateTime.Now }); await _db.SaveChangesAsync(); await tx.CommitAsync(); return Json(new { ok = true, msg = "预约成功,等待管理员确认" }); } catch { await tx.RollbackAsync(); return Json(new { ok = false, msg = "预约失败,请重试" }); } }严格来说,在最高并发下这种写法仍可能存在超卖,需要数据库层面的唯一约束或者UPDLOCK这类锁手段。但毕业设计讲到"用事务保证预约数据一致性"这个层面,已经从"能用"跨到"知道为什么"了。如果老师再深挖,你再补一句"还可以给 Appointments(UserId, CourseId) 建唯一索引来兜底",这一问一答就非常漂亮。
4.5 统计报表的加分实现
前台业务做完后,系统可能还显得平淡。加一个数据统计页是性价比最高的"亮点工程":后台展示本周预约量、最热门课程Top5、各分类课程占比。实现方式很简单,写一个 ApiController 输出JSON,前台用 Chart.js 画图:
[HttpGet] public async Task<IActionResult> HotCourses() { var data = await _db.Appointments .Where(a => a.Status == 1 && !a.IsDeleted) .GroupBy(a => a.CourseTitle) .Select(g => new { name = g.Key, count = g.Count() }) .OrderByDescending(x => x.count) .Take(5) .ToListAsync(); return Json(data); }这个模块能让论文里的"系统的创新与特色"章节有话可说,也能让答辩演示最后一张页面不那么干巴巴。很多人觉得高深的报表技术,其实最核心就是对GROUP BY的理解,难度适中的同时又有肉眼可见的效果。
5. 前台页面与交互细节:让健身网站看起来专业
5.1 模板和静态资源:不推荐纯手写CSS
前端自己从零写一套CSS很费时间,而且审美很难保障。毕业设计更聪明的做法是基于 Bootstrap 5 选一个开源的行政/资讯类模板,把导航栏、卡片、表格这些基础组件直接用起来,然后在上面替换自己的配色和核心页面。
ASP.NET Core MVC 的 Razor 布局文件是关键:Views/Shared/_Layout.cshtml里统一放导航和Footer,课程列表、详情、个人中心这些页面通过@{ ViewData["Title"] = "课程中心"; }各自设置标题,再靠RenderSection扩展额外脚本区域。这套机制写顺手了,页面之间的公共结构维护成本会非常低。
5.2 Ajax用户名查重与局部刷新
注册页面最常见的交互是输入用户名后立刻提示是否被占用,这就是一个标准的Ajax异步校验。我在Controller里写一个校验端点:
[HttpPost] public async Task<IActionResult> CheckName(string userName) { bool exists = await _db.Users.AnyAsync(u => u.UserName == userName); return Json(new { ok = !exists }); }前端在用户名输入框失焦时发请求:
$("#UserName").blur(function () { var name = $.trim($(this).val()); if (!name) return; $.post("/Account/CheckName", { userName: name }, function (res) { if (res.ok) { $("#nameTip").text("用户名可用").css("color", "green"); } else { $("#nameTip").text("用户名已被占用").css("color", "red"); } }); });预约提交也同样用Ajax返回JSON,成功后在前端弹提示并跳转到个人中心,页面不用整体刷新。这个小细节可以在论文里写成"基于Ajax的局部刷新机制降低了服务器渲染压力",同时又体现你对前后端交互的理解。需要特别留神的是,Ajax调试时如果看到控制台报net::ERR_CONNECTION_TIMED_OUT或ERR_CONNECTION_REFUSED,八成是后端端口没起来或者IIS Express端口冲突,先检查应用是否在运行,再检查网关端口,最后看浏览器的开发者工具。
5.3 健身类页面的排版原则
健身网站要给人"专业、有活力"的感觉,排版上抓住几个关键点就行:首屏放一张全宽的高质量健身训练图做Hero区域,配合一句品牌标语;课程列表用卡片网格,三列或四列布局,卡片包含封面图、课程名、教练名、价格和名额余量;教练介绍区用圆形头像配一段人物简介;底部放营业时间、地址、电话这些联系信息。
图片资源别全放服务器本地,可以引用免费图床或者Unsplash上的健身类授权图片,但论文里要记得标注图片来源。上传课程封面时,后端一定要做文件类型和大小校验,别直接信任用户传的文件名,保存时用Guid重命名,避免路径穿越和重名覆盖问题。
6. 发布部署与答辩准备:毕业设计的最后两公里
6.1 发布到服务器:三步走和五个坑
本地能跑通了,还不算完。我见过太多演示现场翻车,最后都死在部署环节。给你一个可靠的三步流程:
第一步,dotnet publish -c Release发布Release版本,如果是自包含模式就加-r win-x64 --self-contained true,产出直接拷到服务器就能跑,不依赖目标机的 .NET 运行时版本——这一点尤其关键,很多新装的Windows服务器缺少运行时,或者运行时版本过老,报This application requires one of the following versions of the .NET Framework这类错误,自包含发布能一劳永逸。
第二步,服务器上安装 IIS,添加网站指向发布目录,应用程序池选择"无托管代码",然后在项目根目录放好web.config。ASP.NET Core 项目发布会自动生成web.config,如果你手动改过,注意aspNetCore节点里的hostingModel="inprocess"不要乱动,改成必应能找到的各种旧配置反而会崩。
第三步,确认数据库。开发时如果用SQLite,发布时换成SQL Server或MySQL,改appsettings.Production.json里的连接字符串,并确保服务器数据库账号有建表和读写权限。预览一下发布后的首页和后台,再用另一台电脑或手机连局域网地址测试一下,这一步能提前暴露80%的演示事故。
常见坑再集中列一下:静态资源404,看是不是wwwroot没有随发布一起输出;Session写不进去,检查浏览器是不是禁了Cookie;启动后端口冲突,注意appsettings.json里Kestrel端口与IIS绑定端口是否一致;数据库连接超时,多半是服务器防火墙没放行数据库端口。
6.2 高频报错与排查速查表
把我和学弟学妹们调试时遇到的高频问题整理成一张速查表,遇到问题可以直接对号入座:
| 报错现象 | 可能原因 | 排查与处理 |
|---|---|---|
页面提示ERR_CONNECTION_TIMED_OUT | 后端进程没启动或端口被占用 | 先dotnet run确认本地能访问,再查端口和防火墙 |
| 访问后台被弹回登录页 | Session丢失或角色判断失败 | 检查登录是否写入Session,重启应用后需重新登录 |
| 数据库表存在但查询报找不到 | EF Core迁移没有执行或连接串指错库 | 执行dotnet ef database update,核对连接字符串 |
| 上传图片后页面无法显示 | 静态文件没有配置或保存路径错误 | 确认图片存到wwwroot下,用相对路径访问 |
| 页面CS0016编译错误 | 应用程序池账户无写入权限 | 给发布目录授予IIS_IUSRS读写权限 |
| 课程分类下拉框为空 | FK外键关联未Include | 查询时使用Include(c => c.Category) |
排查时养成一个习惯:先看浏览器开发者工具Console和Network标签,再看服务器端日志窗口,最后查数据库。顺序反过来,你会浪费大量时间。
6.3 测试用例设计:把答辩的"安全性"问题提前堵住
毕业设计的测试不是要你交一份多标准的测试报告,而是要证明"系统关键路径不出错"。我建议优先设计下面几组用例,跑通后写进论文,答辩时老师怎么提问题你都有底气:
| 测试场景 | 前置条件 | 预期结果 |
|---|---|---|
| 用户名重复注册 | 已存在用户Azheng | 注册被拦截并提示用户名已占用 |
| 普通用户访问后台 | 以普通会员登录 | 页面跳转到登录页或提示无权限 |
| 重复预约同一课程 | 已预约过减脂课程 | 提示"已预约过",不产生第二条记录 |
| 课程名额已满 | 预约人数达到MaxStudents | 提示"名额已满",预约被驳回 |
| 管理员审核预约 | 有待审核预约 | 状态变为已确认,用户个人中心同步可见 |
| 删除的课程不再出现在前台 | 课程Status=2或IsDeleted=1 | 前台列表和详情页均不可见 |
这几条用例覆盖了系统最核心的登录、权限、预约、状态流转四条链路。只要能稳定演示,答辩"稳不稳"的问题就解决了一大半。
6.4 答辩演示的顺序和话术设计
答辩演示顺序建议固定成一条故事线:首页展示站点整体面貌和设计风格 → 注册新用户并登录 → 浏览课程分类并搜索 → 选一门热门课程提交预约 → 切换到管理员账号登录后台 → 在预约管理里确认刚才的预约 → 回到用户个人中心查看预约状态变化 → 最后打开数据统计页面展示图表。这条线完整展示了从前台到后台、从用户到管理员的闭环,也就是你整篇设计的血肉。
老师最爱问的几个问题,提前准备好答案:为什么选.NET Core而不是 .NET Framework?为什么用EF Core而不是原生SQL?Session权限控制和JWT有什么区别?预约防重是怎么做的?每个问题对应的知识我在前面都讲过了,你只要用自己的话复述出来,再加一个实际项目里的例子,就完全够用了。
最后再分享一个小技巧:从开发第一天开始,维护一份"问题记录文档",把每个Bug的报错信息、原因、解法写下来。这看起来麻烦,但到写论文和准备答辩时,你会发现最缺的不是代码,而是"你踩过的坑"这类第一手材料。这些记录不光是论文里"系统测试与调试"章节的素材,也是你从一个照本宣科的学生变成一个能独立解决问题的人最直观的证据。健身网站这个题目本身不复杂,把需求、设计、实现、测试这条链路踏踏实实走完,你收获的远远不止一个能通过答辩的系统。