1. 项目背景与核心需求
择期手术排程与预约系统是医院信息化建设中的重要组成部分。作为计算机专业的毕业设计选题,这个项目既具有实际应用价值,又能充分展示学生的技术能力。我选择基于.NET技术栈开发这个系统,主要考虑到以下几个因素:
首先,医疗行业对系统的稳定性和安全性要求极高。.NET Framework提供了强大的类型安全检查和内存管理机制,能够有效避免常见的安全漏洞。其次,医院信息系统通常需要与多种设备和系统对接,.NET的互操作性优势明显。最后,Visual Studio提供的丰富工具链可以显著提高开发效率,这对时间有限的毕业设计项目尤为重要。
这个系统的核心功能需求包括:
- 医生端:手术申请、排期查询、患者信息管理
- 护士站:手术室资源管理、排程冲突检测
- 患者端:预约查询、提醒通知
- 管理端:数据统计分析、系统配置
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型与架构设计
2.1 技术栈选择
基于项目需求和开发效率考虑,我采用了以下技术组合:
- 后端:ASP.NET Core 6.0(考虑长期支持版本)
- 前端:Blazor Server(实现前后端统一开发)
- 数据库:SQL Server 2019(医疗数据关系性强)
- ORM:Entity Framework Core 6.0
- 消息队列:RabbitMQ(用于异步处理预约通知)
- 缓存:Redis(提升排程查询性能)
选择Blazor Server而非传统MVC架构的主要原因是:
- 减少前后端分离带来的开发复杂度
- 实时更新UI无需手动刷新(对排程系统很重要)
- 可以直接使用C#编写前端逻辑
- 内置的SignalR支持实现实时通知
2.2 系统架构设计
采用分层架构设计:
- 表现层:Blazor组件库
- 应用层:业务逻辑服务
- 领域层:核心业务模型
- 基础设施层:数据访问、外部服务集成
关键设计决策:
-
使用领域驱动设计(DDD)划分限界上下文
- 排程上下文
- 资源管理上下文
- 患者管理上下文
-
实现CQRS模式分离读写操作
- 命令:变更排程状态
- 查询:获取排程视图
-
采用事件溯源记录排程变更历史
- 满足医疗系统的审计要求
- 支持排程冲突回溯
3. 核心功能实现细节
3.1 排程冲突检测算法
排程系统的核心挑战是高效检测时间冲突。我设计了三层冲突检测机制:
csharp复制public class ScheduleConflictDetector
{
// 第一层:基础时间重叠检测
public bool HasTimeConflict(Schedule newSchedule, IEnumerable<Schedule> existingSchedules)
{
return existingSchedules.Any(s =>
(newSchedule.StartTime < s.EndTime) &&
(newSchedule.EndTime > s.StartTime));
}
// 第二层:资源冲突检测(同一手术室、同一医生等)
public bool HasResourceConflict(Schedule newSchedule, IEnumerable<Schedule> existingSchedules)
{
return existingSchedules.Any(s =>
s.OperatingRoomId == newSchedule.OperatingRoomId ||
s.SurgeonId == newSchedule.SurgeonId ||
s.AnesthetistId == newSchedule.AnesthetistId);
}
// 第三层:业务规则冲突(如专科手术室限制)
public bool HasBusinessRuleConflict(Schedule newSchedule)
{
// 实现专科手术室使用规则等
}
}
3.2 实时通知实现
使用SignalR实现排程变更的实时通知:
csharp复制public class ScheduleHub : Hub
{
private readonly IScheduleService _scheduleService;
public ScheduleHub(IScheduleService scheduleService)
{
_scheduleService = scheduleService;
}
public async Task SubscribeToScheduleChanges(int scheduleId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"Schedule-{scheduleId}");
}
public async Task NotifyScheduleChange(int scheduleId)
{
var schedule = await _scheduleService.GetScheduleAsync(scheduleId);
await Clients.Group($"Schedule-{scheduleId}")
.SendAsync("ReceiveScheduleUpdate", schedule);
}
}
前端Blazor组件中集成:
csharp复制@inject IJSRuntime JSRuntime
@implements IAsyncDisposable
private HubConnection hubConnection;
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl("/scheduleHub")
.Build();
hubConnection.On<Schedule>("ReceiveScheduleUpdate", schedule =>
{
// 更新UI
StateHasChanged();
});
await hubConnection.StartAsync();
await hubConnection.InvokeAsync("SubscribeToScheduleChanges", scheduleId);
}
4. 数据库设计与优化
4.1 核心表结构
sql复制CREATE TABLE [Schedule] (
[Id] INT PRIMARY KEY IDENTITY,
[PatientId] INT NOT NULL,
[SurgeonId] INT NOT NULL,
[AnesthetistId] INT NULL,
[OperatingRoomId] INT NOT NULL,
[ProcedureId] INT NOT NULL,
[StartTime] DATETIME2 NOT NULL,
[EndTime] DATETIME2 NOT NULL,
[Status] TINYINT NOT NULL, -- 0:待确认,1:已确认,2:已完成,3:已取消
[CreatedAt] DATETIME2 DEFAULT GETDATE(),
[UpdatedAt] DATETIME2 DEFAULT GETDATE(),
FOREIGN KEY (PatientId) REFERENCES Patient(Id),
FOREIGN KEY (SurgeonId) REFERENCES Staff(Id),
FOREIGN KEY (OperatingRoomId) REFERENCES OperatingRoom(Id),
FOREIGN KEY (ProcedureId) REFERENCES MedicalProcedure(Id)
);
-- 手术室资源表
CREATE TABLE [OperatingRoom] (
[Id] INT PRIMARY KEY IDENTITY,
[Name] NVARCHAR(50) NOT NULL,
[Type] TINYINT NOT NULL, -- 1:普通,2:专科
[Equipment] NVARCHAR(MAX) NULL, -- JSON格式存储设备配置
[IsActive] BIT DEFAULT 1
);
4.2 查询性能优化
针对高频查询场景添加索引:
sql复制-- 按日期范围查询排程
CREATE NONCLUSTERED INDEX [IX_Schedule_DateRange]
ON [Schedule] ([StartTime], [EndTime])
INCLUDE ([OperatingRoomId], [Status]);
-- 医生排程查询
CREATE NONCLUSTERED INDEX [IX_Schedule_Surgeon]
ON [Schedule] ([SurgeonId], [StartTime])
INCLUDE ([OperatingRoomId], [ProcedureId]);
使用计算列优化常用查询:
sql复制ALTER TABLE [Schedule]
ADD [ScheduleDate] AS CONVERT(DATE, [StartTime]) PERSISTED;
CREATE NONCLUSTERED INDEX [IX_Schedule_ScheduleDate]
ON [Schedule] ([ScheduleDate]);
5. 开发中的关键问题与解决方案
5.1 时区处理问题
医疗系统需要严格记录事件发生的准确时间。解决方案:
- 数据库统一使用UTC时间存储
- 应用层根据用户偏好转换显示
- 使用NodaTime库处理复杂时区逻辑
csharp复制// 使用NodaTime处理时区
var london = DateTimeZoneProviders.Tzdb["Europe/London"];
var instant = SystemClock.Instance.GetCurrentInstant();
var londonTime = instant.InZone(london);
5.2 并发冲突处理
多个医生可能同时修改同一排程,采用乐观并发控制:
csharp复制public async Task<IActionResult> UpdateSchedule(int id, ScheduleUpdateDto dto)
{
var schedule = await _context.Schedules.FindAsync(id);
if (schedule == null) return NotFound();
// 检查并发令牌
if (dto.RowVersion != null &&
!schedule.RowVersion.SequenceEqual(dto.RowVersion))
{
return Conflict("该排程已被其他用户修改");
}
// 更新逻辑...
await _context.SaveChangesAsync();
return Ok();
}
5.3 批量排程导入性能
处理批量排程导入时,原始方案性能较差。优化措施:
- 使用SqlBulkCopy替代逐条插入
- 实现分批处理(每批500条)
- 临时禁用索引和约束
csharp复制public async Task BulkImportSchedules(IEnumerable<Schedule> schedules)
{
using var transaction = await _context.Database.BeginTransactionAsync();
try
{
// 禁用索引
await _context.Database.ExecuteSqlRawAsync(
"ALTER INDEX [IX_Schedule_DateRange] ON [Schedule] DISABLE");
// 使用SqlBulkCopy
using var bulkCopy = new SqlBulkCopy(
_context.Database.GetDbConnection() as SqlConnection);
bulkCopy.DestinationTableName = "Schedule";
await bulkCopy.WriteToServerAsync(ConvertToDataTable(schedules));
// 重建索引
await _context.Database.ExecuteSqlRawAsync(
"ALTER INDEX [IX_Schedule_DateRange] ON [Schedule] REBUILD");
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
6. 系统安全与合规考虑
6.1 数据加密方案
- 传输层:强制HTTPS(配置HSTS)
- 敏感数据加密:
- 使用ASP.NET Core Data Protection API
- 患者敏感信息使用列级加密
csharp复制// 配置Data Protection
services.AddDataProtection()
.PersistKeysToDbContext<DataProtectionDbContext>()
.SetApplicationName("SchedulingSystem")
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
6.2 权限控制设计
基于角色的访问控制(RBAC)与声明式权限结合:
csharp复制[Authorize(Roles = "Surgeon")]
public class SurgeonController : Controller
{
[ResourceBasedAuthorize(typeof(ScheduleAuthorizationHandler))]
public IActionResult EditSchedule(int id)
{
// 只有排程关联的医生可以编辑
}
}
public class ScheduleAuthorizationHandler :
AuthorizationHandler<ResourceRequirement, Schedule>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ResourceRequirement requirement,
Schedule resource)
{
if (context.User.IsInRole("Admin") ||
resource.SurgeonId == context.User.GetUserId())
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
6.3 审计日志实现
记录关键操作日志以满足医疗合规要求:
csharp复制public class AuditLog
{
public int Id { get; set; }
public string UserId { get; set; }
public string Action { get; set; }
public string EntityType { get; set; }
public string EntityId { get; set; }
public string OldValues { get; set; } // JSON
public string NewValues { get; set; } // JSON
public DateTime Timestamp { get; set; }
}
// 使用拦截器自动记录
public class AuditInterceptor : DbCommandInterceptor
{
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = eventData.Context;
var auditEntries = new List<AuditEntry>();
foreach (var entry in context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Modified)
{
var auditEntry = new AuditEntry
{
OldValues = entry.OriginalValues.ToObject(),
NewValues = entry.CurrentValues.ToObject()
};
auditEntries.Add(auditEntry);
}
}
// 保存审计日志...
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
7. 部署与性能调优
7.1 容器化部署方案
使用Docker Compose定义多容器环境:
yaml复制version: '3.8'
services:
web:
image: scheduling-web
build:
context: .
dockerfile: Dockerfile
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__DefaultConnection=Server=db;Database=Scheduling;User=sa;Password=YourStrong@Passw0rd;
ports:
- "8080:80"
depends_on:
- db
- redis
- rabbitmq
db:
image: mcr.microsoft.com/mssql/server:2019-latest
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=YourStrong@Passw0rd
volumes:
- sql_data:/var/opt/mssql
redis:
image: redis:alpine
ports:
- "6379:6379"
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
volumes:
sql_data:
7.2 性能优化措施
- 应用层缓存策略:
- 高频查询结果缓存5分钟
- 使用Redis分布式缓存
csharp复制services.AddStackExchangeRedisCache(options =>
{
options.Configuration = Configuration.GetConnectionString("Redis");
options.InstanceName = "Scheduling_";
});
- 数据库连接池优化:
- 设置合理的连接池大小
- 实现连接 resiliency
csharp复制services.AddDbContext<SchedulingDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"),
sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromSeconds(30),
errorNumbersToAdd: null);
}));
- 前端资源优化:
- Blazor组件懒加载
- 预渲染关键页面
- 启用响应式压缩
csharp复制services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(
new[] { "application/octet-stream" });
});
8. 测试策略与质量保障
8.1 自动化测试体系
- 单元测试:覆盖核心业务逻辑
- 排程冲突检测算法
- 业务规则验证
csharp复制[Fact]
public void Should_Detect_Time_Conflict()
{
// Arrange
var detector = new ScheduleConflictDetector();
var newSchedule = new Schedule
{
StartTime = DateTime.Parse("2023-01-01 10:00"),
EndTime = DateTime.Parse("2023-01-01 12:00")
};
var existing = new List<Schedule>
{
new Schedule
{
StartTime = DateTime.Parse("2023-01-01 11:00"),
EndTime = DateTime.Parse("2023-01-01 13:00")
}
};
// Act
var hasConflict = detector.HasTimeConflict(newSchedule, existing);
// Assert
Assert.True(hasConflict);
}
-
集成测试:验证组件协作
- API端点测试
- 数据库交互测试
-
E2E测试:使用Playwright模拟用户操作
- 完整的排程流程
- 并发操作场景
8.2 性能测试方案
使用Locust模拟高并发场景:
python复制from locust import HttpUser, task, between
class SchedulingUser(HttpUser):
wait_time = between(1, 5)
@task
def view_schedule(self):
self.client.get("/api/schedules?date=2023-01-01")
@task(3)
def create_schedule(self):
self.client.post("/api/schedules", json={
"patientId": 1,
"procedureId": 1,
"startTime": "2023-01-01T10:00:00",
"endTime": "2023-01-01T11:00:00"
})
关键性能指标:
- 排程查询响应时间 < 500ms (P99)
- 排程创建吞吐量 > 100 req/s
- 系统支持并发用户数 > 500
8.3 混沌工程实践
使用Azure Chaos Studio模拟故障场景:
- 随机停止容器实例
- 模拟网络延迟和丢包
- 数据库连接失败测试
验证系统的容错能力:
- 自动重试机制是否有效
- 降级方案是否按预期工作
- 故障恢复时间是否符合SLA
9. 项目总结与扩展方向
在开发这个系统的过程中,我深刻体会到医疗信息系统对可靠性和数据一致性的极高要求。几个关键经验教训:
-
时间处理比想象中复杂:必须从一开始就明确时区策略,统一使用UTC时间存储,仅在表现层转换。
-
并发控制不可或缺:即使是非金融系统,医疗数据也需要严格的并发控制,乐观并发模式配合ETag是实用方案。
-
审计日志不是可选项:医疗系统的每个重要操作都必须记录完整的操作历史,事件溯源模式非常适合这种需求。
未来可能的扩展方向:
-
人工智能辅助排程:利用历史数据训练模型,建议最优排程方案。
-
移动端深度集成:开发专门的移动应用,支持扫码确认、实时通知推送。
-
手术室物联网集成:对接智能设备,自动记录手术开始/结束时间。
-
电子病历系统对接:实现与医院HIS系统的深度集成,减少数据重复录入。
这个毕业设计项目让我全面实践了.NET技术栈在现代医疗信息系统中的应用,从需求分析到架构设计,从编码实现到性能优化,每个环节都收获颇丰。特别是对医疗行业特殊要求的理解,是教科书上难以学到的宝贵经验。
