多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

U9 WebPart取数实战:路径选型、上下文与性能优化

U9 WebPart取数实战:路径选型、上下文与性能优化 做U9二次开发的朋友应该都有过这种经历项目好不容易上线了业务部门又提了个需求——我们想在U9的门户首页上加一块面板不用进单据列表直接就能看到订单执行到哪一步了。这个需求落到技术上八成就变成一句话在U9的WebPart界面上获取数据。说实话这件事看着简单我刚开始做的时候也觉得无非就是写个控件拉个数据绑上去真正动手才明白难点根本不在写代码而在把数据准确、安全、不卡地取出来。这篇文章我把最近一个项目里做WebPart取数的思路、踩过的坑和可复用的代码结构整理出来给正准备做U9二次开发的朋友一个参考。1. 先搞清楚WebPart到底跑在什么环境里1.1 U9门户里那一块块面板是怎么来的U9的门户端沿用了微软SharePoint时代的WebPart机制。你可以把WebPart理解成一个沙盒化的页面组件它被放到某个页面区域里页面本身不关心里面是什么内容WebPart自己负责生成HTML、绑定数据、响应用户操作。对U9这种大型ERP系统来说这个机制特别合适因为不同工厂、不同角色、不同业务板块需要在首页看到完全不同的内容但又不能每次都去改整个门户页面不然一个点炸了全场都跟着遭殃。我接触过的很多U9项目门户首页就是一堆WebPart拼出来的待办任务一块、预警消息一块、销售订单执行情况一块、库存周转一块。这些块背后往往都连着不同的数据源。有的读的就是U9业务库有的是调外部系统的服务还有的是从文件、消息通道里扒数据。所以做这个功能之前第一件事不是写代码而是先搞清楚你手头的WebPart要跑在哪个环境里。如果是老一点的U9门户WebPart就是一个继承特定基类的C#类库项目编译成DLL后放到门户站点的bin目录再在页面上拖拽挂载。如果你们用的是较新的U9C扩展平台虽然界面技术和部署方式变了但WebPart的核心逻辑依然是一套继承、渲染、回发、加载数据。1.2 取数这件事的核心矛盾WebPart取数据难在它处于ERP系统的夹层里。往上走页面要响应速度用户等不了三秒以上还白屏往下走系统要权限隔离不能谁打开网页都能把整个集团的订单拖出来。这个夹层位置决定了你怎么取数都有取舍。直接查数据库最快最直接可你把U9的数据权限规则绕过去了组织隔离怎么办账套隔离怎么办走U9的服务接口取数权限、组织边界、单据状态逻辑都在服务层里给你算好了但服务层往往又是一个比较重的SOA体系频繁调用时性能隐患很突出。我刚开始犯过一个典型的错图方便直接把数据库连接字符串写死在WebPart里一条SQL把订单表、客户表、发货表全Join出来数据倒是出来了结果用户换了组织操作页面数据纹丝不动——因为我压根没把当前上下文传进去。后来才明白在WebPart上取数不是会SQL就行而是要解决身份上下文 数据边界 性能这三个问题一起。1.3 三条取数路径先摆在一起看在实际项目里我总结下来U9 WebPart获取数据有三条常规路径取数路径实现方式优点典型风险服务接口调用U9发布的服务接口、OData/REST API、自定义SV接口权限、组织过滤、单据校验逻辑完整调用链长、接口设计不好时性能差数据库直读通过只读账号访问U9业务库的视图或表灵活、速度快、可做复杂聚合绕过了权限模型需要自己控制数据边界外部数据接入通过HTTP调用云接口、MQTT订阅、定时读取FTP文件能接入非U9系统的数据数据格式不统一、跨网络不稳定不能说哪条路一定好完全看你这个WebPart展示什么数据、谁看这些数据、数据量有多大。如果只是展示当前登录人在U9里的待办数量和待办标题走服务接口最稳妥如果要做一张多表聚合的销售分析看板服务接口往往满足不了复杂的聚合条件通常就得考虑在数据库层做视图再让WebPart只读查询如果展示的是车间设备状态、气象数据、第三方物流轨迹那本质上就是外部系统集成跟U9业务数据没多大关系走HTTP或消息通道就行。2. 取数路径的选型能用服务接口就别无脑直连数据库2.1 走U9服务接口最正统但绕不开上下文U9的SOA架构里业务数据访问基本都是通过服务接口暴露出来的。你在界面上看到的那些单据列表、卡片页底层也是调服务。WebPart里走服务接口的好处是U9的权限过滤器、组织交互规则、状态机校验都会在服务层帮你执行一遍你拿到的数据就是业务上允许这个用户看到的数据。但走服务接口很容易踩一个坑上下文Session怎么传。U9服务层里的很多查询都依赖当前组织、当前用户、当前账套这些上下文参数。你在WebPart里手工建了一个服务实例如果不主动赋值接口默认可能拿不到上下文结果就是要么报错要么返回空数据。我的做法是每次调用前先做一个上下文工具把当前门户登录用户映射到U9服务需要的用户信息上下文和业务组织上下文再发起调用。为什么非要这一步因为门户的登录态跟U9业务服务的登录态不是天然打通的尤其做了单点登录的项目两边的会话可能各是各的。漏掉这一步接口调用就会出现看起来没报错、实际查出来的数据是别人的组织甚至空数据这种诡异现象。还有一点服务接口的粒度要选对。一个WebPart如果一次要展示多类数据尽量不要在页面加载时串行调用七八个接口否则体验一定慢。更好的做法是把几类数据合并成一个专门为这个WebPart设计的查询接口或者用并行调用的方式把总耗时压下来。2.2 数据库直连快但要知道边界在哪里有些看板类WebPart光是聚合查询涉及的表就有七八张服务接口要么根本提供不了这么灵活的查询条件要么性能烂到你怀疑人生。这时候直连数据库几乎是必然选择。但直连数据库有几个边界必须守住这是我踩过坑之后立下的规矩第一不能把业务库的连接串直接暴露给WebPart。我见过有项目在WebPart的配置文件里写明文数据库账号密码还有权限管理员的权限这等于门户页面一旦被人摸清楚结构整个业务库的数据都能拖走。正确的做法是给WebPart单独建只读账号权限只授权到必要的视图和函数绝不能授权表级别的写入权限。第二查询必须走视图不要直接写面向基础表的特别复杂的即席SQL。为什么一是U9业务表的结构非常复杂多版本、多组织字段、多语言字段都有业务人员熟悉的字段名落到数据库里可能完全不是那回事二是WebPart一旦上线SQL是分散在代码里的将来U9升级补丁改了表结构你很难快速定位到底哪条SQL挂了。提前把查询逻辑收敛到数据库视图里后续维护会省很多事。第三一定要控制数据量。WebPart界面就那么点地方用户不会去滚动几千行取数时就要在视图或查询层面把TOP/分页条件加好不能让WebPart去捞一个百万行的结果集再到前端掉电分页。2.3 跨系统取数把FTP、MQTT、云接口的数据接进来近几年制造业企业的U9项目几乎没有一个只跟U9自己的数据库打交道的。最常见的三种接入场景车间现场的设备状态数据通过KingsCada这类组态软件采集然后用MQTT消息的方式推出来。虽然搜kingscada 如何获取mqtt数据的人多半是在做SCADA端的数据订阅但在U9 WebPart这一侧我们关注的是另一端怎么订阅到这条消息流把设备状态、产量数据接到ERP看板上。这个场景下WebPart的数据源就不是数据库了而是一个实时消息通道的数据快照。气象、物流、银行流水这类第三方数据往往要靠定时从公司的FTP服务器上拉取指定格式的文件再转成业务需要的结构化数据。比如气象服务器从公司FTP服务器获取气象数据后转成E文件格式内容就是典型的外部文件接入场景。U9项目里看到这类数据通常不会让WebPart每次加载都临时去FTP拉文件那样太慢也容易把FTP拖垮合理的做法是单独做一个定时任务统一拉取、落库、更新状态WebPart直接读本地结果表。从华为云、阿里云这类云平台拿数据一般是通过它们的REST API或消息队列。WebPart里通过HTTP调用时要注意Token过期、限流、超时三个问题。我建议把云接口的适配逻辑单独封装不要让WebPart代码里到处散落着HTTP调用和JSON解析。跨系统取数有条通用经验凡是能从外部实时拉取的数据宁可放到一个中间表里缓存也不要让页面实时请求外部系统。页面用户无法接受一次打开要等五秒而中间表缓存的代价只是延迟几秒到几十秒多数业务看板完全能接受。3. 实操过程做一个实时显示订单执行情况的WebPart3.1 工程准备项目和引用这个环节我直接讲完整流程照着做就行。开发一个U9门户WebPart你需要准备一个.NET类库项目目标框架要跟门户站点的运行时版本匹配。如果站点是.NET Framewerk 4.x你的项目也必须是4.x别拿一个.NET Core的类库去引用它部署时多半会出兼容问题。项目建好后引用几个关键程序集System.Web、System.Web.UI.WebControls.WebParts、System.Data以及U9门户相关的接口程序集。具体是哪几个和你用的U9版本强相关但System.Web.UI.WebControls.WebParts这个程序集是WebPart机制的公共底座属于必须引用的。我个人的建议是把取数逻辑和页面渲染逻辑分开放在两个类里。WebPart继承类只负责界面组装数据访问单独用一个DataProvider类负责。这样做的原因是WebPart在页面回发时生命周期比较特殊如果你把一堆数据库代码堆在CreateChildControls里后续想给同一个取数逻辑换展示方式会非常痛苦。3.2 界面加载时取数并渲染WebPart取数渲染的核心生命周期方法是CreateChildControls。这个方法是页面框架在加载WebPart时自动调用的你在这个方法里取数、创建子控件、把数据塞进去最终展示在页面上。下面是这套逻辑的演示性C#代码结构风格按我实际项目的习惯来写注意类名、命名空间和接口名要以你自己环境的U9版本为准public class OrderExecutionWebPart : System.Web.UI.WebControls.WebParts.WebPart { private string _orgId string.Empty; protected override void CreateChildControls() { base.CreateChildControls(); // 1. 先从门户上下文里取当前用户所属的组织信息 // 这一步非常关键我在后面会专门解释为什么 _orgId PortalContext.Current.OrgId; // 2. 调用数据提供器取数而不是在WebPart里直接写SQL OrderExecutionData data OrderDataProvider.GetRecentOrderExecution( orgId: _orgId, dateRangeDays: 30, maxRows: 50 ); // 3. 没有数据时也要有界面提示不能白屏 if (data null || data.Items.Count 0) { Controls.Add(new LiteralControl(div classempty-tip暂无数据/div)); return; } // 4. 用最简单的控件组装出展示效果 var grid new GridView(); grid.DataSource data.Items; grid.DataBind(); Controls.Add(grid); } }这段代码本身不难理解我想重点说的是第1步。以前我在WebPart里取数觉得反正都是同一个站点拿当前用户还不简单就直接读取日志用户的账户名结果在服务层查数据时发现组织字段对不上。后来统一改成从门户上下文读取组织ID、用户ID、账套ID三个要素数据才真正跟界面上的当前业务环境同步。第2步把数据访问拆到OrderDataProvider里是我强烈建议的做法。这个提供器内部既可以调U9服务接口也可以走数据库视图具体用哪种PageLoad之外的另一套配置去控制WebPart本身不必关心。3.3 参数控制超时、缓存、分页这部分是最容易被忽略但直接影响使用体验的地方。我见过太多WebPart取数不加任何参数控制数据量一上来页面直接超时。这里讲几个核心参数附上我对参数值的计算考量。第一个是超时时间。U9服务接口或数据库连接都建议显式设置超时。超时设多长不是拍脑袋定的你要先算一笔账用户能感知的等待极限大概是2到3秒页面加载还包括网络往返、WebPart渲染、其他页面元素的加载时间。所以取数这一环的时间预算最好控制在1.5秒以内超时时间就要比预算再留一点余量设成2到3秒比较合理。如果你发现一个查询稳定需要5秒以上解决思路不是把超时放大到10秒而是去优化查询逻辑或加缓存。第二个是缓存时间。WebPart里取数如果页面刷新频率不高、数据本身变化也不频繁就没必要每次加载都打数据库。我常用的策略是MemoryCache缓存时长按业务容忍度来定。比如订单执行情况业务上能容忍最多五分钟的延迟缓存时间设为5分钟如果是一个仓库库存看板业务可能容忍不了一分钟的偏差缓存时间就设成30秒。缓存代码注意加锁多个请求同时刷新时不要互相踩踏。第三个是分页和条数限制。WebPart展示区域小我通常会把最大返回条数硬编码为50到100条。查询语句在视图层加TOP而不是把所有数据取出来再在前端截断。这样数据库消耗小页面渲染也快。分页模式下总页数用向上取整公式总页数 (总记录数 每页条数 - 1) / 每页条数这个公式虽然简单但实际编码时很多人会忘记处理总记录数为0和正好整除的边界情况导致页面上出现多出来的空页。3.4 部署到U9页面并调试WebPart开发完要部署到U9门户页面常见步骤是先编译生成DLL登记为安全控件然后在页面上选择WebPart挂载。不同U9版本对这个流程的封装不太一样但核心思想一致WebPart需要在门户的白名单机制里被声明为安全控件否则页面会拒绝加载。实际部署时我最常遇到的坑是DLL版本冲突。U9门户本身会引用一堆官方程序集你项目里引用的版本如果跟站点全局程序集版本不一致部署上去可能直接报未能加载文件或程序集的错。经验是在开发环境专门搭一个跟U9门户同版本的程序集环境避免靠NuGet拉出来的版本对不上。调试WebPart也有技巧。直接附加到门户站点的w3wp进程上进行断点调试是最快定位问题的方式。环境上不方便附加调试时就在WebPart里用日志记录关键变量组织ID、用户ID、SQL语句、返回行数出问题时把日志拉出来看这些信息足够排查绝大多数问题。4. 常见问题与排查技巧实录4.1 数据一直为空页面却不报错这是我的WebPart开发经历里出现频率最高的疑难杂症。表现形式都是一样的页面正常打开了不红不报错但那一块就是空空如也。排查思路往两个方向去第一查组织上下文。很多情况下你取数时并没有拿到预期的组织ID可能是门户上下文的属性取错了也可能是当前页面没有正确传递组织参数。Step过程中最常见的做法是在代码里把组织ID和用户ID记到日志里跑一次看看到底取到了什么值。第二查数据权限。你取数是走的服务接口接口内部可能对当前用户做了权限过滤该用户恰好没有对应单据的查看权限最终返回空集合。这种空数据跟真没数据从界面上完全看不出来只有去看接口的返回统计才分辨得出。所以我建议在数据提供器里返回一个包含总条数的结构体哪怕页面上只显示前50条记录里也要能看到实际有多少条方便你判断是不是权限把数据过滤光了。4.2 权限与上下文不同组织看到不同数据这个问题在WebPart取数里太常见了。同一个WebPartA工厂的人打开看到A工厂的数据B工厂的人打开也看到A工厂的数据那肯定出问题了。原因基本可以锁定在取数时没有使用当前上下文或者用了全局默认组织。解决这个问题没有取巧的余地就是老老实实在每次取数前把当前上下文的三个要素传递完整用户ID、组织ID、业务账套。我写过一个小工具类专门从门户上下文提取这三个要素任何进入WebPart的请求都先过一遍它。千万别图省事在类里搞一个静态字段存组织IDWebPart是多用户共享的静态字段会让大家串数据。4.3 性能问题页面卡死、加载转圈、接口超时WebPart取数性能差大多数不是数据库本身慢而是非法的大查询或者热接口串行调用导致。我做过一个销售看板首次加载要18秒监控数据库发现那个查询Join了20多张表。后来做了一个物化视图把聚合结果提前算好再用缓存顶上去首屏降到1.8秒。性能排查时要在浏览器开发者工具里看请求耗时分布区分是服务端渲染慢还是接口本身慢。如果确认是取数接口慢优先看两条路一是把复杂的统计逻辑下推到数据库视图二是考虑用异步加载模式先给用户一个骨架图数据到了再局部刷新。但要记住一点异步加载会提升体验也会增加开发复杂度如果数据本身能在一秒内查到别为了炫技强行异步。4.4 部署后WebPart不显示或显示异常这类问题我总结过一个相对完整的排查顺序表可以对照操作现象可能原因排查思路页面上看不到WebPart未在安全控件白名单注册检查部署文档里是否遗漏注册步骤WebPart显示红叉/加载异常DLL版本不匹配或依赖程序集缺失打开门户日志看具体异常堆栈指向哪个程序集代码改了没生效站点缓存了旧DLL确认部署时是否释放了进程对DLL的占用重新编译部署后再强刷页面只有我能看到别人不行页面级或WebPart级权限配置问题查看门户页面的权限配置确认是否对特定角色开放这个表的核心是强调一点WebPart部署问题看日志不靠猜。U9门户的系统日志里一般会记录WebPart加载时的异常信息按时间倒序找通常能直接看到具体是哪个类、哪一行出了什么问题。拿到异常信息再去搜索引擎或内部知识库查效率比自己臆测高太多。4.5 再补一个隐藏坑刷新和回发状态丢失WebPart和普通Web页面一样存在PostBack。用户点了分页、筛选、刷新按钮如果没处理好视图状态你会发现数据重新取了一遍但用户之前选择的筛选条件却丢了体验非常割裂。处理方案是WebPart里需要持久化的用户选择要放进ViewState或控件自身的状态管理里。我之前在WebPart里做日期筛选没有把起止日期保存到ViewState每次回发日期范围就重置用户选完时间再点下一页日期就回到了默认值被业务部门吐槽了很久。自从统一用ViewState保存筛选条件后问题就再没出现过。结尾的几个真实经验写在最后的这几个体会是从实际项目里一次次折腾出来的。WebPart取数这件事看起来是个编码工作量很小的小功能实际上80%的时间花在搞清楚数据口径、权限边界、上下文来源这三件事上真正写代码的时间不到20%。我现在的习惯是不管功能多简单先花半小时把取数路径画个草图哪条路走服务接口、哪条路走数据库视图、哪条路接外部系统再动手写。这个习惯帮我省掉的返工时间远超想象。另外一个小技巧很实用所有WebPart取数逻辑上线之前先在后台写一个最小化的测试入口把同样的数据查询先跑通一遍确认拿到正确的结果了再移到WebPart里去渲染。很多人直接跳到WebPart调试分不清是界面问题还是取数问题排查起来事倍功半。先验证数据源、再验证展示层是U9 WebPart开发里最保险的执行顺序。
返回列表