目录
1引言
本文分析和演示如何在Camstar的Web服务层加入缓存,具体的缓存方式有多种选择,本文选择使用Redis作为缓存中间件。并以一个具体的实例(模拟真实情况),演示用缓存的好处。本篇博文的理论基础是另一篇博文:Camstar 开发:缓存的作用与分析
2实例描述
假设已有生产订单从SAP系统调用MES系统的接口发推送到了MES系统,接口数据如表1所示。现需要在MES系统中开发一个接收页面,由生产调度员选择需要生产的订单,接收并下发到生产线。
| 字段名 | 字段类型 | 字段描述 |
| Order | string | 订单号 |
| State | string | 订单状态,默认未接受 |
| OtherInfo | string | 订单信息(其他信息不重要,统一成一个字段) |
原型设计界面如图1所示。

图1
上方区域为待接收的生产订单的过滤条件,下方区域为推送到MES系统的生产订单信息。用户点击查询按钮,根据筛选条件查询符合条件的生产订单;用户选择表格生产订单点击接收,将选择的生产订单接收并下发到生产线。
3开发分析
开发背景:目前接口具体数据尚未明确,表1中的数据只是预计必有的一些字段,系统接口实际也未搭建,即数据来源不确定。订单接收后数据流向无需考虑,本页面只需修改订单的状态从“未接受”到“已接收”。
本页面功能很简单,从开发角度来说,就是一个查询功能和一个修改功能。困难点在于上游数据不确定,作为Camstar开发很难确定在Designer中的建模。假设本页面所有操作都做缓存处理,不需要穿透到数据库,并且缓存不过期。那么,可以设计如表2的缓存结构。
| Key | Value |
| Order | State和OtherInfo等信息的对象 |
4 实例类图
经过分析该实例,画出实例类图,如图2。可以看出,系统类从通常的1个变成了5个,类数量大幅度增加,但是各个类的职责更加明确,实际代码并没有增加很多。

图2
整体结构中,主要使用了桥接模式,将穿透到Camstar服务的代码和访问缓存的代码解耦;在抽象类AbsReceiveOrder中又使用模板方法模式,控制整个服务的执行逻辑骨架。而具体的逻辑则交给子类实现抽象方法,钩子方法和接口实现来反向控制。
根据实例类图,具体实现代码,项目工程树如图3所示。

图3
5 代码分析
5.1 RedisHelper
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Reflection; using ServiceStack.Redis; /// /// Summary description for RedisHelper /// public class RedisHelper { #region Single private static RedisHelper instance; private static readonly object locker = new object(); public static RedisHelper Instance { get { if (instance == null) lock (locker) { if (instance == null) instance = new RedisHelper(); } return instance; } } private RedisHelper() { // // TODO: Add constructor logic here // } #endregion private RedisClient redisClient = new RedisClient("localhost", 6379); public RedisClient RedisClient { get { return redisClient; } } }
RedisHelper是采用单例模式设计的Redis辅助类,为了保证同一个Web服务端只有一个Redis的客户端。其中,将Redis服务地址和端口以及密码等信息写入配置文件,可以在运行期指向不同的Redis服务实例。 本类中硬编码了。
5.2 ReceiveOrderCache
using System; using System.Collections.Generic; using System.Linq; using System.Web; /// /// Summary description for ReceiveOrderCacheh /// public class ReceiveOrderCache { public ReceiveOrderCache() { // // TODO: Add constructor logic here // } //订单号 key private string order; //订单类型 private string orderType; //任务号 private string taskNo; //产品名称 private string productDesc; //产品代号 private string productType; //产品型号 private string productCode; //零部件图号 private string productName; //零部件名称 private string partName; //零部件版本 private string partRev; //工艺编号 private string workflow; //工艺版本 private string workflowRev; //计划数量 private int planQty; //计划开始日期 private DateTime planStartDate; //计划完成日期 private DateTime planEndDate; //计划状态 private string state; //接收时间 private DateTime receiveDate; //车间调度员 private string productionControler; //主制车间 private string factory; public string ProductionControler { get { return productionControler; } set { productionControler = value; } } public DateTime ReceiveDate { get { return receiveDate; } set { receiveDate = value; } } public string State { get { return state; } set { state = value; } } public DateTime PlanEndDate { get { return planEndDate; } set { planEndDate = value; } } public DateTime PlanStartDate { get { return planStartDate; } set { planStartDate = value; } } public int PlanQty { get { return planQty; } set { planQty = value; } } public string WorkflowRev { get { return workflowRev; } set { workflowRev = value; } } public string Workflow { get { return workflow; } set { workflow = value; } } public string PartRev { get { return partRev; } set { partRev = value; } } public string PartName { get { return partName; } set { partName = value; } } public string ProductName { get { return productName; } set { productName = value; } } public string ProductCode { get { return productCode; } set { productCode = value; } } public string ProductType { get { return productType; } set { productType = value; } } public string ProductDesc { get { return productDesc; } set { productDesc = value; } } public string TaskNo { get { return taskNo; } set { taskNo = value; } } public string OrderType { get { return orderType; } set { orderType = value; } } public string Order { get { return order; } set { order = value; } } public string Factory { get { return factory; } set { factory = value; } } }
ReceiveOrderCache为接口数据交互类,是我直接从原有代码复制的,所以属性较多。本例用到的属性只有Order,State。其他信息同一用OtherInfo代表。
5.3 IReceiveOrder
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Camstar.WCF.ObjectStack; /// /// Summary description for IReceiveOrder /// public interface IReceiveOrder { Dictionary
GetDataFromCamstar(); ResultStatus UpdateOrderInfo(string order, string state); }
在IReceiveOrder接口中定义需要穿透到Camstar服务的方法,其中GetDataFromCamstar()方法用于返回订单信息,UpdateOrderInfo()方法用于修改订单信息(修改订单状态)。
5.4 AbsReceiveOrder
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Camstar.WCF.ObjectStack; using Camstar.WebPortal.WebPortlets; /// /// Summary description for ReceiveOrderDemo /// public abstract class AbsReceiveOrder:MatrixWebPart { private IReceiveOrder camstar; public IReceiveOrder Camstar { get { if (camstar == null) camstar = new ReceiveOrderImpl(); return camstar; } } private Dictionary
orderInfo; public Dictionary
OrderInfo { get { if(orderInfo == null) { orderInfo = GetDataFromCahce(); if(orderInfo == null && Hook()) { orderInfo = Camstar.GetDataFromCamstar(); } } return orderInfo; } } protected abstract Dictionary
GetDataFromCahce(); protected virtual bool Hook() { return false; } protected abstract ResultStatus ReceiveOrder(); public AbsReceiveOrder() { // // TODO: Add constructor logic here // } }
抽象类定义了整个业务逻辑的骨架,其中获取OrderInfo中使用了模板方法模式,定义逻辑:如果缓存为空则穿透。
值得一提的是,在该模板方法种还定义了Hook()钩子方法,即便是缓存为空,如果钩子方法没有重写,那么可能依然不会穿透。在本例中,钩子方法默认返回了false,实际上,应该将这个返回值写入配置文件中,这样,子类就可以有3种不同的处理方式:
1)什么都不做,那么将默认读取配置文件中的值,这个值可以是全局控制系统是否读缓存。
2)重写返回false,意味着该业务永不穿透到Camstar服务。
3)重写返回True,这是最有可能的情况,意味着穿透到Camstar服务的代码已经有相关开发者实现了,那么当缓存未命中时候,则穿透到Camstar服务。
该类种还组合使用了桥接模式,将穿透Camstar服务的代码和访问缓存的代码解耦,这样,就可以让俩边的代码交给不同的开发者开发了。其中写死了camstar = new ReceiveOrderImpl();这里只要采用反射的方式实例化,即可完全解耦。代码中采用New的方式是为了演示方便。
5.5 ConcreteReceiveOrder
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Camstar.WCF.ObjectStack; using Camstar.WebPortal.FormsFramework.WebGridControls; using System.Data; /// /// Summary description for ConcreteReceiveOrder /// public class ConcreteReceiveOrder : AbsReceiveOrder { protected virtual JQDataGrid Grid_list { get { return Page.FindCamstarControl("grid_list") as JQDataGrid; } } public ConcreteReceiveOrder() { // // TODO: Add constructor logic here // } public void Select() { DataTable dt = new DataTable(); dt.Columns.Add("ORDER", typeof(string)); dt.Columns.Add("STATE", typeof(string)); dt.Columns.Add("OTHERINFO", typeof(string)); foreach (string key in OrderInfo.Keys) { DataRow dr = dt.NewRow(); dr["ORDER"] = key; dr["STATE"] = OrderInfo[key].State; dr["OTHERINFO"] = OrderInfo[key].ReceiveDate; dt.Rows.Add(dr); } Grid_list.Data = dt; } public void Receive() { //结果展示在页面 Page.DisplayMessage(ReceiveOrder()); //刷新页面 Select(); } protected override Dictionary
GetDataFromCahce() { Dictionary
data = new Dictionary
(); RedisHelper.Instance.RedisClient.Db = 0; byte[][] byteKeys = RedisHelper.Instance.RedisClient.Keys("receivedOrder:*"); foreach (byte[] key in byteKeys) { string keyStr = System.Text.Encoding.Default.GetString(key); data.Add(keyStr, RedisHelper.Instance.RedisClient.Get
(keyStr)); } return data; } protected override ResultStatus ReceiveOrder() { //页面被选定的订单信息,此处懒得写代码获取页面被选定的行,写死,看得懂就行。 ReceiveOrderDTO data = new ReceiveOrderDTO(); data.OtherInfo = "略略略"; data.State = "已接收"; string orderChecked = "Order66"; //穿透到Camstar服务 ResultStatus rs = Camstar.UpdateOrderInfo(orderChecked, "已接收"); //如果执行成功,则更新缓存 if (rs.IsSuccess) { RedisHelper.Instance.RedisClient.Set
("ReceiveOrder:" + orderChecked, data); } return rs; } }
子类具体实现访问缓存的方法,以及定义Select和Receive方法用于和前端交互。
5.6 ReceiveOrderImpl
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Camstar.WCF.ObjectStack; using System.Data; /// /// Summary description for ReceiveOrderFromCamstar /// public class ReceiveOrderImpl:IReceiveOrder { public ReceiveOrderImpl() { // // TODO: Add constructor logic here // } public Dictionary
GetDataFromCamstar() { throw new NotImplementedException(); } public ResultStatus UpdateOrderInfo(string order,string state) { ResultStatus rs = new ResultStatus(); rs.IsSuccess = true; rs.Message = "默认调用成功"; return rs; } }
接口具体实现访问Camstar服务的方法。这里由于笔者时间有限,并没有实际调用Camstar服务。
6 运行测试
6.1 辅助代码
由于代码默认不穿透到数据库,因此,需要手动添加缓存。这里采用简单的控制台循环插入不过期的缓存数据。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using ServiceStack.Redis; namespace RedisDataHelper { class Program { static void Main(string[] args) { RedisClient redisClient = new RedisClient("localhost", 6379); for (int i = 0; i < 100; i++) { ReceiveOrderCache data = new ReceiveOrderCache(); data.Order = "order" + i; data.OrderType = i % 2 == 0 ? "STDA" : "STDB"; data.PartName = "PartName" + i; data.PartRev = "PartRev" + i; data.PlanEndDate = DateTime.Now.AddDays(i + i); data.PlanQty = i + 10; data.PlanStartDate = DateTime.Now.AddDays(i); data.ProductCode = "ProductCode" + i; data.ProductDesc = "ProductDesc" + i; data.ProductName = "ProductName" + i; data.ProductType = "ProductType" + i; data.ReceiveDate = DateTime.Now; data.State = "未接受"; data.TaskNo = "TaskNo" + i; data.Workflow = "Workflow" + i; data.WorkflowRev = "WorkflowRev" + i; data.Factory = i % 2 == 0 ? "Test" : "PRO"; redisClient.Set
("receivedOrder:" + data.Order, data); } } } }
运行Camstar页面,点击查询按钮。

代码能正常运行,并且数据是从缓存中获取的。如果需要穿透到数据库,只需要修改配置文件即可,无需修改任何代码。且穿透到Camstar服务的代码也可以完全交给熟悉Camstar的开发人员。
7 总结
由于笔者的个人时间问题,实例并没有将修改完全做出来,而是采用伪代码的形式。但是这个例子想表达的效果相信已经可以完全体现。Camstar 开发:缓存的设计于分析
后续有空我会把源码整理出来上传到资源共享中。并附上连接
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/206111.html原文链接:https://javaforall.net
