
简介这是一套基于C#与ASP.NET框架开发的供应商管理系统面向需要完成毕业设计或学习企业级Web开发的读者覆盖供应商分类、信息管理、评价、留言及用户权限等核心业务模块适合作为课程设计、毕业设计或入门MVC架构的实战参考。资源包共386个文件压缩后约1.77MB主要包含ASPX页面与CS后台代码、JS脚本、CSS样式、HTML页面及少量配置文件另有gif图片和数据库文件等类型覆盖了从界面展示到数据存储的完整Web项目结构。已有384人学习下载。系统的分类管理支持按类型、地区、产品等多维度组织供应商信息管理维护名称、资质、合作记录等台账评价模块记录服务质量、交货准时性等指标留言与用户管理则提供了内部沟通和权限控制机制。通过这份资源读者可以了解完整的ASP.NET WebForms实现思路掌握其数据交互、页面组织与权限设计为独立开发类似管理系统提供可直接参考的代码与结构。 做了几年企业信息化系统我最大的感受是供应商管理这块最容易被做成“Excel大战微信”。采购部手里一张表财务部手里一张表对账的时候邮箱里飞过来飞过去的还是Excel。所以当我接到一个ASP.NET供应商管理系统的需求时心里很清楚这不是在做一个简单的增删改查而是在做一套能把“人治”变成“流程治”的主数据平台。这个项目用成熟的ASP.NET MVC框架来承载目标很朴素让供应商自助注册、让采购员在线审核、让财务能直接拉出对账单、让管理层能看到供应商绩效排名。文章里我会把整套系统的设计思路、数据库模型、核心模块的实操细节、以及我在开发过程中踩过的坑全部捋一遍适合正在用.NET技术栈做企业管理系统、或者打算从零搭一套供应商信息平台的开发者参考。1. 项目整体设计思路与方案选型1.1 这个系统到底要解决什么问题在动手写第一行代码之前我做了一次业务摸底结果发现供应商管理表面的痛点是“数据散”根源是三块一是供应商信息没有统一入口各分公司各建各的同名不同号的情况非常普遍二是准入流程没有刚性约束业务员拍脑袋就能新增一个供应商没有资质审核三是供应商的报价、交期、质量表现没有数据沉淀到了年底评优全凭采购员印象。所以这套系统的核心定位不是“电子台账”而是“主数据管理平台”。关键维度有三个唯一性一个供应商在系统里只有一个档案编号通过统一社会信用代码做唯一性校验。流程化从注册、资质上传、审核到正式启用必须有状态流转。可追溯供应商的基本资料、联系人、报价、供货记录全部留痕能回溯历史版本。有了这三个维度的锚点后续所有功能模块的优先级就清晰了。最优先做的是供应商档案管理和准入审核其次是物料-供应商关系维护然后是订单协同与到货记录最后是绩效评分和对账导出。1.2 为什么选择ASP.NET MVC而不是别的技术栈选型的时候我也纠结过要不要上前后端分离最后决定用ASP.NET MVC jQuery Bootstrap的组合。原因很实在项目属于企业内部系统用户量几百人级别没有高并发压力开发周期紧需要快速交付维护团队对原生.NET技术栈最熟悉不需要额外引入Node或Vue的构建链路。ASP.NET MVC在这个场景下有天然优势。它跟Windows Server环境、SQL Server数据库配合最顺畅做Windows集成认证或者基于表单的登录体系都非常方便。路由机制让我可以清晰地把URL结构和业务模块映射起来比如/Supplier/Register是注册、/Supplier/Audit/{id}是审核一眼就知道这个地址在干什么。而且Razor语法写服务端渲染页面比手拼HTML字符串干净得多表单提交后的服务器端验证也写起来很顺手。这里也考虑过ASP.NET Web Forms但在复杂表单和多人协作场景下ViewState和事件模型会拖累代码可读性。MVC的Model-Binding机制配合数据注解让模型层可以直接承担校验职责这种做法更契合“数据驱动”的供应商管理系统。1.3 项目分层结构设计我采用的是经典的三层架构具体到代码结构上Supplier.Web表现层Controller、View、ViewModel、过滤器。Supplier.Core业务层领域模型、业务服务接口与实现、工作单元。Supplier.Data数据层EF Core的DbContext、实体映射、仓储实现、数据库迁移脚本。图新鲜用过度设计的分层最后只会增加沟通成本。这个项目里我就把事情控制在“Controller调服务服务访问仓储仓储操作EF Core”这条直线上。业务服务不直接暴露IQueryable而是返回DTO或领域对象避免视图层无意中触发奇怪的SQL。在UI层面我做了一个共享的布局页_Layout.cshtml左侧是模块导航右侧是内容区域。供应商列表、审核列表、对账查询都复用同一套样式。这个系统不追求酷炫追求的是业务人员能在一个小时内学会完整操作流程。2. 数据库设计供应商主数据模型2.1 供应商主表字段取舍与约束设计供应商主表是整个系统的地基字段设计我花了最多心思。核心表叫Supplier关键字段如下字段类型说明Idint主键自增SupplierCodenvarchar(32)内部档案编号格式为SUP年份四位流水UnifiedSocialCreditCodenvarchar(18)统一社会信用代码业务唯一键CompanyNamenvarchar(200)企业全称ShortNamenvarchar(50)简称SupplierTypeint供应商类型1生产物料、2辅料、3服务类Statusint0草稿、1待审核、2已准入、3已冻结、4已淘汰ContactPersonnvarchar(50)主要联系人ContactPhonenvarchar(20)联系电话BankAccountnvarchar(50)银行账号用于对账付款BankNamenvarchar(100)开户行CreatedAtdatetime创建时间LastUpdatedAtdatetime最后更新时间我在UnifiedSocialCreditCode上建了唯一索引同时在前端和后端都做了18位校验正则表达式类似^[0-9A-Z]{18}$。这个字段是查重的基础也是后续和采购、财务系统对接的关联键。SupplierCode的生成规则我放在服务层实现用事务包裹先查当年最大流水号加一后生成新编码再插入供应商记录。直接用数据库自增Id去拼容易在删除记录后出现重号用独立流水序列会更干净。2.2 关联表联系人、资质、物料与报价光有一张主表撑不起供应链业务我还设计了四张关联表SupplierContact一个供应商多个联系人区分主联系人包含职务、电话、邮箱。SupplierQualification资质证照表保存证书名称、证书编号、发证机构、有效期用文件路径关联到上传的PDF或图片。SupplierMaterial供应商和物料目录的关系表一个供应商可以供应多种物料一种物料也可以有多家供应商。SupplierQuotation历史报价表每次报价记录单价、币种、有效期起止日期。物料关系是供应商系统里特别容易乱的地方。同一个螺丝采购部叫“十字盘头螺丝”仓库叫“4分自攻钉”两边其实是一个东西。我在系统里维护一份标准物料目录让供应商选择物料时只能从目录里挑而不是自由填写。这样后续做比价才具备可比性。资质表我在ExpiryDate上加了索引每天跑一个定时任务把未来30天内到期的资质扫描出来推送给采购员去跟催。这个功能上线后公司供应商资质过期的投诉直接降为零。2.3 状态机设计供应商的完整生命周期供应商的状态变化我用一个状态机类来管理枚举值定义如下Draft供应商自助注册但尚未提交。PendingAudit已提交等待采购员初审。Approved审核通过成为正式供应商。Frozen合作中止、舞弊调查等情况下的冻结状态期间无法创建采购订单。Eliminated淘汰出局档案永久保留但不可再被选用。状态流转不是随意跳的。比如Frozen不能直接跳到Approved必须先变成PendingAudit再人工审核。我用了一个静态类SupplierStateMachine来定义合法的状态迁移路径所有地方的状态变更都统一走这个类校验。因为审核动作和状态变更是跨多个表联动的我用事务保证一致性并且在状态变化日志表里记录操作人、操作时间和变更前后的状态值。3. 核心功能模块的实操拆解3.1 供应商注册与自助资料维护供应商注册我设计成匿名页面不需要先登录。业务流程是供应商填写统一社会信用代码和企业名称系统自动查重如果已存在则提示“该企业已登记请联系采购部”如果不存在允许创建新档案生成一个GUID作为临时令牌放在Cookie和URL参数里供应商可以在七天之内凭此继续补充资料。注册页面的表单校验分成三层前端jQuery Validate做必填和格式校验后端数据注解再校验一遍业务服务层做最后把关比如校验统一社会信用代码的加权因子算法是否合法。这三层少了哪一个都不行只靠前端校验的话绕过页面直接POST上来脏数据就是分分钟的事。供应商在维护资质时文件上传我做了如下限制只允许jpg、png、pdf格式单个文件不超过5MB文件名统一重命名成“企业名称_证照类型_时间戳”。重命名规则很关键因为原始文件名经常是中文乱码或纯数字落到服务器之后根本没法对应到业务记录上。3.2 采购员审核待办通知与批量操作采购员登录系统后首页就是待办区。我用的是ActionFilter在每次请求时自动加载当前用户的待审核数量放在布局页右上角显示。审核页面分两部分左侧是供应商的历史信息、资质材料列表右侧是审核结论和意见填写区。审核意见我用一个下拉框加一个文本框的组合下拉框预置“资料齐全同意准入”“注册资本不符”“资质即将过期要求更新”“注册地址与营业执照不一致”等常见结论采购员也可以选择“自定义意见”手写。这样设计是为了后续做审核效率统计时可以直接按结论类型聚合分析。批量操作是这个模块里提升效率的重要细节。如果一家的三份资质文件都合规采购员不需要反复打开关闭页面直接在列表页勾选多行记录点“批量通过”按钮系统走同一个事务批量更新状态。为了保证安全我限制批量审核数量不能超过50条并在提交时再次提示“确认对选中的N家供应商执行准入操作”。3.3 物料-供应商关系维护这个模块是采购比价的根基我提的需求是每个物料可以配置多个合格供应商但必须指定一个“首选供应商”。页面提供两块面板左边是物料分类树右边是当前选中物料的供应商列表支持从已准入的供应商里搜索添加。报价处理有个细节同一供应商对同一物料在相同有效期内不允许出现两笔不同报价。这个唯一性约束我用数据库索引来实现索引列是MaterialId SupplierId EffectiveDate。如果数据库允许出现重复报价比价报表的呈现就毫无意义。物料-供应商关系变更时我会在SupplierMaterialChangeLog表里记录变更原因。供应商涨价、停产换替代料、新增物料配合试产这三种原因的处理方式完全不同记录下来之后业务分析人员才说得清楚为什么这个月某物料的主供应商切换了。3.4 接收JSON请求体StreamReader读取RequestBody的场景系统里有一个供应商对接接口第三方系统以HTTP POST方式推送供应商报价单过来。因为双方技术栈不同对方传过来的不是标准的application/x-www-form-urlencoded而是application/json的原始Body。一开始我直接用MVC的模型绑定去接怎么都接不到数据后来才意识到需要手动读取请求体。[HttpPost] public async TaskActionResult ReceiveQuotation() { string requestBody; using (var reader new StreamReader(Request.Body, Encoding.UTF8)) { requestBody await reader.ReadToEndAsync(); } var quotationDto JsonConvert.DeserializeObjectQuotationPushDto(requestBody); if (quotationDto null || quotationDto.Items.Count 0) { return Json(new { success false, message 请求体格式错误或内容为空 }); } var result _quotationAppService.ImportQuotation(quotationDto); return Json(new { success result.IsSuccess, message result.Message }); }这段代码的关键点在于Request.Body是一个流只能被读取一次所以要直接在Action内部用StreamReader读完并释放资源。ReadToEndAsync是异步方法Action也必须是异步的以避免线程阻塞。这里读完之后Request.Body默认会保持在流的末端如果后续还有别的中间件要读取需要Request.Body.Position 0重置位置但多数场景下不需要这么做。解析后的QuotationPushDto需要和自己的数据库模型做映射我不能直接把外部DTO塞进EF实体。JsonConvert.DeserializeObject得到的是不受数据注解校验的普通对象所以反序列化完成后还需要手动调用Validator.TryValidateObject做模型合法性检查不然脏数据落到库里后面的对账就会出大问题。3.5 供应商绩效评分与排名报表绩效模块的数据来源是系统的业务沉淀交期来自到货记录的延迟天数质量来自质检的不合格率价格来自报价与目标价的偏差配合采购员的手工评分合成一个综合绩效分。我采用的评分公式是综合绩效分 交期得分 * 0.35 质量得分 * 0.35 价格得分 * 0.2 配合度得分 * 0.1交期得分按延迟天数换算准时到货100分延迟1-3天80分延迟4-7天60分延迟超过7天0分。质量得分是(1 - 批次不合格率) * 100。价格得分是目标价与实际采购单价的比值乘以100。报表页面我用了一个简单的折线图和一个排名表格。折线图用ECharts渲染数据由后端Action返回JSON格式前端ajax拉取后填充。排名表格支持按季度、按供应商类型筛选也可以直接导出Excel。绩效数据是一种典型的“越沉淀越有价值”的数据系统运行半年后采购部做供应商淘汰、分配订单份额就有了量化依据。4. 权限与安全内控的关键防线4.1 基于角色的权限控制系统用户分四类供应商外部、采购员、采购经理、系统管理员。我用ASP.NET MVC自带的Authorize特性配合自定义RoleAuthorize过滤器实现权限控制。[RoleAuthorize(Roles 采购员,采购经理)] public ActionResult AuditList() { // ... }但只控制页面访问权限不够按钮级别的控制也要做。我在View里判断当前用户角色有权限才渲染“冻结供应商”“删除资质”等操作按钮。系统的原则是页面可以看操作不能乱做。菜单配置存在数据库的SysMenu表里不同角色加载不同的菜单树采购员看不到供应商的银行账号信息这是硬性隔离。密码存储方面我用的是ASP.NET Identity框架它默认用PBKDF2算法对密码做哈希加盐处理。即便数据库泄露攻击者也无法直接还原明文密码。供应商用户在注册时设置密码后台管理员不需要知道员工的密码所有重置密码操作都必须通过邮件里的临时链接完成。4.2 防跨站请求伪造与防SQL注入ASP.NET MVC内置了AntiForgeryToken机制。我在所有POST表单里都加了Html.AntiForgeryToken()对应Action标注[ValidateAntiForgeryToken]。如果没有这个防护攻击者可以构造第三方站点自动提交表单利用用户已经登录的会话做非法操作。Asp.Net的Token是绑定用户会话的不同用户的Token不一致这个保护在供应商这类外部用户可访问的系统中尤其重要。SQL注入的防护相对简单EF Core通过参数化查询执行SQL不拼接用户输入到SQL字符串。但在写原生SQL做复杂报表时我始终坚持用FromSqlRaw并传入SqlParameter而不是把过滤条件直接拼到SQL里。例如var parameters new SqlParameter(supplierType, supplierType); var list context.Suppliers .FromSqlRaw(SELECT * FROM Supplier WHERE SupplierType supplierType, parameters) .ToList();4.3 关键操作的审计日志供应商系统的审计日志我做了两级普通操作日志和敏感操作日志。普通操作日志记录谁在什么时间看了哪个供应商档案敏感操作日志记录查询或者导出银行账号、身份证号等敏感字段的行为这类日志保留至少一年。审计日志的数据结构就四个要素操作人、操作时间、操作类型、操作对象。操作对象的标识我统一用供应商编码而不是数据库自增Id因为自增Id无法跨系统关联而供应商编码在外面业务系统里也是同一个。日志表我按月份做分区定期归档避免单表数据量膨胀后拖慢查询。供应商管理系统的价值不止在业务流转它还承担着数据合规的责任审计日志就是那个“无声的监管者”。5. 常见问题与排查记录5.1 文件上传失败IIS请求大小限制第一次测试上传资质文件时超过1MB的PDF直接报“远程服务器返回错误 (413)”排查后发现是IIS默认限制请求体大小为30MB但还有一个ASP.NET自身的maxAllowedContentLength配置默认是30MB可是有的服务器被安全策略改小到1MB。我在Web.config里显式配置system.webServer security requestFiltering requestLimits maxAllowedContentLength10485760 / /requestFiltering /security /system.webServer这个配置的单位是字节10MB就是10485760。改完配置后杀掉应用池重新登录系统上传功能恢复正常。另外注意如果只改了maxAllowedContentLength而没改HttpRuntime的maxRequestLength大文件依然会被拦截两个都要调。5.2 日期格式化陷阱JSON日期少了8小时在供应商对接接口里我用DateTime.Now保存报价有效期然后序列化成JSON返回给第三方。结果对方反馈时间少了8小时。问题出在JSON序列化默认使用UTC时间而服务器在东八区。DateTime.Now的Kind是Local被序列化成UTC后对方再转本地时间自然出现8小时偏差。我的处理方案是统一约定系统内部所有数据库存储和接口传输都使用UTC时间展示层再做本地化转换。在Startup.cs里配置JsonConvert.DefaultSettings () new JsonSerializerSettings { DateTimeZoneHandling DateTimeZoneHandling.Utc };查询返回给页面展示时前端用JavaScript的new Date()自动转换成浏览器时区。如果第三方接口有特殊要求可以在DTO里加一个字符串字段提前格式化成yyyy-MM-dd HH:mm:ss传递过去避免歧义。5.3 大数据量下供应商列表卡顿供应商数量达到两万家以后没有分页的列表页加载时间接近十几秒。我做了两个优化第一服务端分页页面使用page和pageSize参数控制每页默认50条第二去掉列表页所有统计类查询比如“该供应商关联多少物料”改成点击详情时单独ajax加载。优化后在Chrome DevTools里看耗时列表页首屏从12秒降到400毫秒。这起案例说明一个道理列表页就是给人扫单用的不是用来分析数据的把大数据压在单页上属于典型的没想清楚使用场景。如果业务确实需要导出全量数据用异步任务导出Excel文件完成任务后邮件通知下载地址而不是同步生成大文件让浏览器干等着。5.4 状态被并发修改导致审核失误采购员A和采购员B同时打开同一个供应商的审核页面A先点了“通过”B之后点了“冻结”结果B的覆盖掉了A的操作。这个问题的根源是最后写入者胜出没有版本控制。我用乐观并发解决供应商表加了一个RowVersion时间戳字段SQL Server的rowversion类型更新时Where条件带上前端提交时的RowVersion值。如果更新影响行数为0说明记录已被别人修改直接提示“当前供应商信息已被其他操作员更新请刷新页面后重试”。这个改动以后审核争执基本不再发生。6. 最后再分享一点运营层面的体会系统开发完成只是第一步上线后能否用起来要看数据初始化到底有没有做扎实。我强烈建议在上线前组织一次集中的供应商数据清洗把各个分公司的Excel台账汇总按统一社会信用代码去重把错误的税号、失效的资质一并处理掉然后再导入系统。如果带着一堆脏数据直接推上线采购员一查一个错第二天就没人愿意打开这个系统了。在实际项目里我还养成了一个习惯给每个核心表都建好“最近新增”“最近变更”的查询视图每天抽十分钟扫一眼看有没有非工作时间大量修改数据的异常情况。这套系统跑了大半年供应商从最初的800家增长到2300家中间也经历过资质批量过期、重复注册、接口数据不一致各种问题但骨架没有动过。稳定的数据模型加合理的业务流程才是这套系统的根基。如果你也要做类似的系统我的建议是第一优先级永远是供应商档案的唯一性和状态流转没有这两个底子后面所有关于比价、绩效、供应商协同的功能都是空中楼阁。先把地基打稳再谈上层建筑顺序反了大概率要返工。本文还有配套的精品资源点击获取