视觉上位机、PLC 通信和 Web 看板的积累,这一篇把“全栈架构、登录注册、数据库交互、设备通信”串成一个可落地的视觉系统全栈工程方案。
一、全栈架构:四层分离,各司其职
一套视觉上位机系统如果只写成一个 WinForms 的Form1.cs,随着业务增长会迅速失控。正确的做法是严格分层:
┌─────────────────────────────────────────────────────┐ │ 表示层 (WPF/WinForms) │ │ ├─ 登录/注册界面 ├─ 主操作界面 ├─ 设置界面 │ └───────────────────────┬─────────────────────────────┘ │ 调用 Service ┌───────────────────────▼─────────────────────────────┐ │ 业务逻辑层 (BLL) │ │ ├─ 用户认证服务 ├─ 检测结果管理 │ │ ├─ PLC 通信服务 ├─ 数据上传服务 │ └───────────────────────┬─────────────────────────────┘ │ 调用 Repository / DeviceClient ┌───────────────────────▼─────────────────────────────┐ │ 数据访问层 (DAL) │ 设备通信层 │ │ ├─ EF Core DbContext │ ├─ Snap7 Client │ │ ├─ UserRepository │ ├─ SerialPort 封装 │ │ └─ InspectionRepository │ └─ HttpClient (MES) │ └───────────────────────┬────────┴──────────┬──────────┘ │ │ ┌───────────────────────▼───────────────────▼──────────┐ │ 基础设施 │ │ ├─ SQL Server / SQLite ├─ 西门子 S7-1200/1500 │ │ └─ 日志 (Serilog) └─ MES HTTP API │ └─────────────────────────────────────────────────────┘核心原则:表示层不直接碰数据库,业务层不直接碰 PLC 字节流。每层只与相邻层通过接口交互。
二、数据库交互:EF Core 管理用户与检测记录
1. 安装与配置
使用 EF Core 统一管理 SQL Server 或 SQLite:
# 安装包dotnetaddpackage Microsoft.EntityFrameworkCore.SqlServer dotnetaddpackage Microsoft.EntityFrameworkCore.Tools// 实体定义publicclassUser{publicintId{get;set;}[Required,MaxLength(50)]publicstringUsername{get;set;}[Required]publicstringPasswordHash{get;set;}[Required]publicstringSalt{get;set;}publicUserRoleRole{get;set;}=UserRole.Operator;publicDateTimeCreatedAt{get;set;}=DateTime.Now;}publicclassInspectionRecord{publiclongId{get;set;}publicstringSn{get;set;}publicstringStation{get;set;}publicboolIsOk{get;set;}publicdoubleX{get;set;}publicdoubleY{get;set;}publicdoubleAngle{get;set;}publicDateTimeTimestamp{get;set;}=DateTime.Now;publicint?UserId{get;set;}// 操作员}// DbContextpublicclassVisionDbContext:DbContext{publicDbSet<User>Users{get;set;}publicDbSet<InspectionRecord>Inspections{get;set;}protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptions){options.UseSqlServer("Server=localhost;Database=VisionDB;Trusted_Connection=True;");}}2. 生成数据库
Add-MigrationInitialCreateUpdate-Database迁移会自动创建Users和Inspections表,无需手写 SQL。
三、登录/注册:安全是底线
1. 密码存储的硬性要求
绝不使用 MD5 或 SHA1 直接哈希密码,也绝不明文存储。正确做法是使用PBKDF2(Rfc2898DeriveBytes),每个用户独立随机盐值,迭代次数至少 60 万次:
publicclassPasswordHasher{privateconstintIterations=600_000;privateconstintSaltSize=16;privateconstintHashSize=32;public(stringhash,stringsalt)Hash(stringpassword){byte[]salt=RandomNumberGenerator.GetBytes(SaltSize);byte[]hash=Rfc2898DeriveBytes.Pbkdf2(password,salt,Iterations,HashAlgorithmName.SHA256,HashSize);return(Convert.ToBase64String(hash),Convert.ToBase64String(salt));}publicboolVerify(stringpassword,stringstoredHash,stringstoredSalt){byte[]salt=Convert.FromBase64String(storedSalt);byte[]hash=Convert.FromBase64String(storedHash);byte[]computed=Rfc2898DeriveBytes.Pbkdf2(password,salt,Iterations,HashAlgorithmName.SHA256,HashSize);// 恒定时间比较,防计时攻击returnCryptographicOperations.FixedTimeEquals(hash,computed);}}FixedTimeEquals是关键——普通的==比较会因提前返回而泄露哈希信息。
2. 登录与注册服务
publicclassAuthService{privatereadonlyVisionDbContext_db;privatereadonlyPasswordHasher_hasher=new();publicasyncTask<User>LoginAsync(stringusername,stringpassword){varuser=await_db.Users.FirstOrDefaultAsync(u=>u.Username==username);if(user==null)returnnull;if(!_hasher.Verify(password,user.PasswordHash,user.Salt))returnnull;returnuser;}publicasyncTask<bool>RegisterAsync(stringusername,stringpassword){if(await_db.Users.AnyAsync(u=>u.Username==username))returnfalse;var(hash,salt)=_hasher.Hash(password);_db.Users.Add(newUser{Username=username,PasswordHash=hash,Salt=salt});returnawait_db.SaveChangesAsync()>0;}}3. WPF 界面调用
privateasyncvoidBtnLogin_Click(objectsender,RoutedEventArgse){varuser=await_authService.LoginAsync(TxtUser.Text,TxtPass.Password);if(user==null){MessageBox.Show("用户名或密码错误");return;}// 记录操作员,后续检测记录关联CurrentUser=user;newMainWindow().Show();this.Close();}四、上位机与 PLC 通信:Snap7 方案
1. Snap7 的优势
Snap7 是开源的西门子 S7 通信库,免费、无需改 PLC 程序、对生产零干扰。一个实际项目用它稳定运行两年、采集逾百万条数据,期间对原 PLC 系统零影响。
注意:S7-1200/1500 的目标 DB 块需取消“优化的块访问”,否则无法按绝对地址读写。
2. C# 封装
usingSnap7;publicclassS7PlcClient:IDisposable{privatereadonlyS7Client_client=new();privatereadonlystring_ip;publicS7PlcClient(stringip){_ip=ip;}publicintConnect(){// S7-1200/1500: 机架0,槽1return_client.ConnectTo(_ip,0,1);}publicintReadDb(intdbNumber,intstart,intsize,byte[]buffer){return_client.DBRead(dbNumber,start,size,buffer);}publicintWriteDb(intdbNumber,intstart,intsize,byte[]buffer){return_client.DBWrite(dbNumber,start,size,buffer);}publicvoidDispose()=>_client.Disconnect();}3. 视觉结果写入(含握手)
// DB100 内存布局:0.0=ResultsValid, 0.1=InspectionAck, 2=X, 4=Y, 6=AnglepublicasyncTaskWriteResultAsync(shortx,shorty,shortangle){// 写数据byte[]data=newbyte[6];BitConverter.GetBytes(x).CopyTo(data,0);BitConverter.GetBytes(y).CopyTo(data,2);BitConverter.GetBytes(angle).CopyTo(data,4);_plc.WriteDb(100,2,6,data);// 置握手位byte[]flag={1};_plc.WriteDb(100,0,1,flag);}五、上位机与服务器通信:HTTP + SignalR
视觉系统需要把结果同时“落地”到本地数据库和远程 MES/看板。
HTTP 上报 MES(POST JSON):
publicclassMesClient{privatereadonlyHttpClient_http;publicMesClient(IHttpClientFactoryfactory){_http=factory.CreateClient("MesApi");}publicasyncTaskUploadAsync(InspectionRecordrecord){varpayload=new{record.Sn,record.IsOk,record.Timestamp};usingvarresp=await_http.PostAsJsonAsync("/api/inspection",payload);resp.EnsureSuccessStatusCode();}}SignalR 推送 Web 看板(实时更新):MES 系统常用 SignalR 将生产状态实时推送到 Web 看板,确保看板数据始终最新。视觉上位机在本地写完数据库后,也可以通过IHubContext向看板广播:
await_hub.Clients.All.SendAsync("ReceiveInspection",record);六、完整数据流
操作员登录 ↓ WPF 界面 (登录验证 → SQL Server) ↓ 启动检测,从 PLC 读取触发信号 (Snap7) ↓ 相机采集 → 算法处理 → 判定 OK/NG ↓ ┌───────────────────┬────────────────────┐ │ 写入本地数据库 │ POST 到 MES │ │ (EF Core) │ (HttpClient) │ └───────────────────┴────────────────────┘ ↓ SignalR 推送到 Web 看板 (实时更新)七、一句话总结
全栈视觉系统的核心是分层解耦:WPF 只管界面,EF Core 只管数据,Snap7 只管 PLC,HttpClient 只管 MES,SignalR 只管推送。每一层独立测试、独立替换,系统才可维护、可扩展。