简介本资源为北风网出品的C# Winform下C/S架构办公OA系统开发课程配套PDF面向具备一定C#基础、希望提升分层架构与实战能力的技术人员。内容以Winform技术复刻WEB版OA系统涵盖登录、递归菜单树、窗体通信、日志记录、机构部门管理、DataGridView应用、动态控件、自定义控件、角色权限分配、内部短消息、信箱及文件上传下载删除等模块并涉及代码生成工具、VSS源代码管理与编码规范。资源包共1个PDF文件约900KB便于随时查阅课程讲义与难点解析。目前已有186人学习。读者可借此掌握多层架构开发、SQL Server数据库操作、存储过程与流转技术理解从需求分析到项目实施的完整流程积累企业级开发中权限设计与控件自开发的实用经验。1. 从一份 C# Winform 的 C-S 架构 OA 源码说起它到底能解决什么问题很多做企业信息化的朋友第一次接触 OA 系统往往是从一份「北风网-基于C#Winform下C-S架构的办公OA系统开发」的源码包开始的。它不是一个玩具 Demo而是一套完整的、跑在局域网里的 C-S 架构办公自动化系统客户端用 Winform 做界面服务端处理业务逻辑和数据持久化中间靠 Socket 或 Remoting 通信。这套东西能解决的核心问题很具体——中小型企业内网环境下需要一套不依赖浏览器、响应快、能直接调用本地硬件打印机、扫描仪、串口设备的办公流程系统。适合谁适合刚入行 C#、想通过一个真实项目把 Winform 控件、多线程、数据库访问、C-S 通信串起来的开发者也适合需要维护老 OA 系统、想理解其架构脉络的一线工程师。热词里的「winform项目案例」「c#上位机」「c#入门」都指向同一个诉求要一个能跑通、能改、能拆的完整参照物。这份源码的价值不在于代码多优雅而在于它把 C-S 架构下 OA 的典型模块——登录鉴权、工作流审批、文档管理、消息通知——用最直白的 Winform 方式实现了出来你能看到每个按钮背后到底连着什么。2. 拆开这套 C-S 架构 OA从登录到审批的数据是怎么流动的2.1 客户端与服务端的职责边界在哪里C-S 架构的 OA 系统最容易翻车的地方就是职责划分不清。我见过不少 Winform 项目把数据库连接字符串直接写在客户端结果一部署就出问题。这套源码的做法是客户端只负责界面渲染、用户输入校验、本地缓存所有业务规则、权限判断、数据读写全部放在服务端。客户端通过一个通信层常见做法是封装 Socket 或 .NET Remoting发送请求对象服务端处理完返回结果对象。具体到代码层面客户端会有一个ServiceProxy类它把每个业务操作包装成一个方法。比如登录// 客户端 ServiceProxy.cs 片段 public class ServiceProxy { private TcpClient _client; private NetworkStream _stream; public LoginResult Login(string userId, string password) { // 构造请求对象包含操作码和参数 var request new Request { Action Login, Params new Dictionarystring, string { { UserId, userId }, { Password, password } } }; // 序列化后发送等待服务端返回 Send(request); return ReceiveLoginResult(); } }这段代码的关键在于客户端不查数据库不比对密码只负责把用户输入打包发出去。服务端收到Action Login后才会去查用户表、验证密码哈希、生成会话令牌。参数说明Action是服务端路由的依据Params用字典传递方便扩展不同操作的参数个数。这样做的代价是通信层要处理序列化和异常好处是业务逻辑集中后期改权限规则不用重新部署几百个客户端。2.2 工作流审批的数据库表怎么设计才不坑OA 系统的核心是审批流。这套源码里审批流不是硬编码的 if-else而是用三张表撑起来的Flow_Template流程模板、Flow_Node节点定义、Flow_Instance运行实例。我一般会建议刚上手的朋友先看Flow_Node表的结构它决定了审批怎么流转。字段名类型说明NodeIdint节点主键TemplateIdint所属流程模板NodeNamenvarchar(50)节点名称如「部门经理审批」NodeTypeint0开始1审批2条件分支3结束ApproverRolenvarchar(50)审批角色标识关联角色表NextNodeIdint默认下一节点条件分支时为空ConditionExprnvarchar(200)条件表达式如Amount 5000设计上的坑在于NextNodeId只能指向一个节点遇到条件分支就得靠ConditionExpr在代码里解析。常见做法是服务端写一个FlowEngine类根据当前节点和表单数据计算下一节点。参数怎么设ConditionExpr不要用复杂的脚本引擎用简单的字段比较就够了否则后期维护的人根本看不懂。这套源码里用的是最朴素的字符串解析虽然不够优雅但胜在调试方便——你可以在服务端打日志看到底匹配了哪个条件。2.3 用 Winform 做多文档界面时怎么避免卡顿Winform 做 OA 客户端绕不开多文档界面MDI。热词里「c#控件多致winfrom卡」说的就是这个问题主窗体里开十几个子窗体每个子窗体又拖了几十个控件界面响应直接变玄学。这套源码的优化思路值得抄子窗体不用时Hide()而不是Close()避免反复创建销毁数据加载放在BackgroundWorker里不阻塞 UI 线程。// 子窗体数据加载的典型写法 private void LoadDocumentList() { // 显示进度条禁用操作按钮 progressBar.Visible true; btnRefresh.Enabled false; backgroundWorker.RunWorkerAsync(); } private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e) { // 在后台线程查数据库或调服务端接口 e.Result _serviceProxy.GetDocumentList(currentUser); } private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { // 回到 UI 线程更新控件 dataGridView.DataSource e.Result as ListDocumentInfo; progressBar.Visible false; btnRefresh.Enabled true; }逻辑说明DoWork里不能碰任何 UI 控件否则会抛跨线程异常。参数说明RunWorkerAsync可以传参数在DoWork里通过e.Argument取。这套源码里还用了DoubleBuffered true来减少 DataGridView 的闪烁虽然是小技巧但实际用起来差别很明显。3. 把源码跑起来环境配置、数据库还原和第一个可调试的登录请求3.1 开发环境与依赖组件的版本选择这套源码基于 .NET Framework用 Visual Studio 打开。热词里「vs2015」出现频率很高但实际用 VS2017 或 VS2019 也能打开只是目标框架要确认是 .NET Framework 4.5 或 4.6。我一般会先看解决方案里的项目数通常分OA.Client、OA.Server、OA.Common三个。OA.Common放实体类和通信协议定义两边都要引用。需要额外装的组件如果服务端用了 SQL Server确保本机装了 SQL Server Express 或完整版如果通信层用了 Remoting检查System.Runtime.Remoting引用是否存在。常见做法是先用 NuGet 还原一遍包虽然老项目可能没有packages.config那就手动把缺失的 DLL 从lib文件夹里引进来。3.2 数据库还原与连接字符串的修改位置数据库脚本一般在Database或SQL文件夹里是一个.sql文件。还原步骤打开 SQL Server Management Studio新建一个空数据库名字比如OADB然后执行脚本。执行完检查表是否都建好了重点看Sys_User表里有没有默认管理员账号。连接字符串的位置通常在服务端的App.config里connectionStrings add nameOAConnection connectionStringData Source.;Initial CatalogOADB;Integrated SecurityTrue providerNameSystem.Data.SqlClient / /connectionStrings参数说明Data Source.表示本机默认实例如果是 Express 版要写成.\SQLEXPRESS。Integrated SecurityTrue用 Windows 身份验证如果 SQL Server 只开了 SQL 验证要改成User IDsa;Password你的密码。改完记得把服务端项目设为启动项目先跑服务端再跑客户端。3.3 用日志确认第一个登录请求走通了跑起来之后别急着点界面。先在服务端的登录处理方法里加一行日志确认请求真的到了。// 服务端 LoginHandler.cs 片段 public LoginResult HandleLogin(Dictionarystring, string Params) { string userId Params[UserId]; string password Params[Password]; // 加日志确认收到的用户名和密码长度 File.AppendAllText(server_log.txt, $收到登录请求{userId}密码长度{password.Length}\n); // 后续查库验证... }逻辑说明日志写在服务端能区分「客户端没发出去」和「服务端没处理」。如果日志文件没出现检查客户端ServiceProxy里的 IP 和端口是否指向了本机。参数说明密码长度只是用来确认参数没丢不要打印明文密码。这一步走通说明 C-S 通信链路是活的后面调业务才有意义。4. 避坑与排查C-S 架构 OA 开发中最容易翻车的 5 个地方4.1 现象客户端能登录但换台机器就提示连接超时原因服务端绑定的是127.0.0.1或localhost只监听本机回环地址。解决把服务端的监听地址改成0.0.0.0或本机局域网 IP。在TcpListener初始化时写IPAddress.Any同时检查 Windows 防火墙有没有放行对应端口。这个坑血泪经验最多因为开发机上永远正常一部署就翻车。4.2 现象审批流走到某个节点后卡住日志显示「找不到下一节点」原因Flow_Node表里NextNodeId填了 0 或指向了不存在的节点。解决服务端在加载流程模板时做一次校验把所有节点的NextNodeId遍历一遍发现无效就抛异常并记录模板 ID。另外条件分支节点的ConditionExpr如果解析失败也会导致找不到下一节点建议在FlowEngine里加默认兜底分支。4.3 现象DataGridView 绑定数据后修改单元格值不生效原因绑定的数据源是ListT它不实现IBindingList所以单元格编辑不会回写到对象。解决换成BindingListT或DataTable。如果坚持用ListT就得手动处理CellEndEdit事件把值写回对象。这个坑在 Winform 里非常常见热词「winform做简单表格」背后往往就是这个问题。4.4 现象服务端跑一段时间后内存持续上涨最后崩溃原因每次客户端请求都new一个TcpClient或NetworkStream但没有在finally里Dispose。解决用using包裹或者确保连接复用。另外如果用了BinaryFormatter反序列化它会把类型信息缓存起来大量不同请求对象会导致内存泄漏。常见做法是换成Json或Protobuf序列化。4.5 现象客户端界面文字模糊高 DPI 屏幕上尤其明显原因Winform 默认不感知 DPI系统缩放后整个窗体被拉伸。解决在app.manifest里启用 DPI 感知或者在Program.cs里调用Application.SetHighDpiMode(HighDpiMode.SystemAware).NET Framework 4.7 以上支持。如果框架版本太低就手动设置窗体的AutoScaleMode AutoScaleMode.Dpi但效果有限。这个坑在笔记本外接显示器时特别突出。5. 从能跑到好用给这套 OA 加一个可配置的审批链和操作日志5.1 把硬编码的审批角色改成从配置读取源码里审批角色可能写死在代码里比如if (node.ApproverRole Manager)。要让它好用得把角色和人员的映射放到数据库或配置文件。我一般会加一张Sys_RoleMember表字段是RoleCode和UserId。然后FlowEngine在确定审批人时先查这个表拿到该角色下所有用户再根据表单里的部门字段过滤。// 根据角色和部门获取审批人列表 public Liststring GetApprovers(string roleCode, string department) { string sql SELECT UserId FROM Sys_RoleMember WHERE RoleCode RoleCode AND UserId IN (SELECT UserId FROM Sys_User WHERE Dept Dept); // 执行查询返回用户 ID 列表 // 如果列表为空回退到角色下所有用户 }参数说明RoleCode来自流程节点定义Dept来自当前表单的申请部门。这样改审批人不用改代码运维人员自己就能配。注意空列表的兜底逻辑否则流程会卡死。5.2 用 SQL 触发器还是代码写操作日志操作日志是 OA 的后悔药。常见做法有两种数据库触发器、服务端代码埋点。我倾向代码埋点因为触发器对批量操作不友好而且调试麻烦。在服务端的每个业务处理方法里加一个LogHelper.Write调用记录操作人、时间、操作类型、关键参数。// 服务端统一日志入口 public static class LogHelper { public static void Write(string userId, string action, string detail) { string sql INSERT INTO Sys_Log (UserId, Action, Detail, LogTime) VALUES (UserId, Action, Detail, GETDATE()); // 用独立连接执行避免影响主事务 } }逻辑说明日志写失败不能影响主业务所以用try-catch包住并且用独立数据库连接。参数说明Detail字段建议存 JSON 字符串方便后期检索。如果日志量很大考虑按月分表但中小型 OA 一天几千条单表足够。5.3 验证审批链是否按预期流转的笨办法改完审批链配置后怎么确认它真的按你想的走我的习惯是在FlowEngine的每个节点处理前后打日志输出当前节点 ID、审批人列表、计算出的下一节点 ID。然后跑一个测试流程从发起到结束看日志里的节点顺序对不对。如果中间有跳转异常日志会直接暴露是条件表达式写错了还是审批人列表为空。验证项预期结果检查位置发起申请生成 Flow_Instance 记录数据库 Flow_Instance 表第一审批节点审批人收到待办客户端待办列表条件分支按金额走不同分支服务端日志的 NextNodeId审批完成实例状态变为「已结束」Flow_Instance.Status 字段这套笨办法看起来原始但比任何自动化测试都快因为你能直接看到数据在哪一步不对。我自己的习惯是每次改完流程配置先跑一遍这个表里的四项确认无误再交给测试。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?