
【场景】打印次数强并发控制方案
【背景知识】
打印次数的计算逻辑:[套打.常见问题.打印日志和打印次数更新逻辑](https://vip.kingdee.com/link/s/lQa2i)
【方案】
(0)手工增加表,设置业务对象标识和单据内码作为唯一约束,用作并发控制
```sql
--sqlserver
IF NOT EXISTS (SELECT 1 FROM (SELECT NAME AS TABLE_NAME, XTYPE AS TABLE_XTYPE FROM sysobjects WHERE XTYPE = 'U' OR XTYPE = 'V') AS KSQL_USERTABLES
WHERE TABLE_NAME = 'Tmp_PrintUniqueControl')
BEGIN
CREATE TABLE Tmp_PrintUniqueControl (FID VARCHAR (36) NOT NULL DEFAULT LOWER(NEWID()),
FObjectTypeId VARCHAR (50) NOT NULL DEFAULT ' ', FKey VARCHAR (50) NOT NULL DEFAULT ' ', FUSERID BIGINT NOT NULL DEFAULT 0, FCREATEDATE DATETIME NULL DEFAULT GETDATE())
END
;
IF NOT EXISTS (SELECT 1 FROM (SELECT sysobjects.NAME AS TABLE_NAME, sysindexes.NAME AS INDEX_NAME FROM sysobjects INNER JOIN sysindexes ON sysindexes.ID = sysobjects.ID) AS KSQL_INDEXES WHERE INDEX_NAME = 'UNIQUE_Tmp_PrintUniqueControl')
BEGIN
ALTER TABLE Tmp_PrintUniqueControl ADD CONSTRAINT UNIQUE_TEST_CONTROL UNIQUE (FObjectTypeId, FKey)
END
;
```
(1)增加业务对象绑定此表,用作当打印失败时供其他用户确认检查和清理的逻辑
业务对象——基础资料,绑定BOS_ObjectType
单据内码——文本
打印时间——长日期
用户——用户


(2)增加表单插件、列表插件,在打印菜单点击时加锁,打印完成收到前端指令返回时解锁



```csharp
using Kingdee.BOS;
using Kingdee.BOS.App.Data;
using Kingdee.BOS.Contracts;
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.Bill.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.List.PlugIn;
using Kingdee.BOS.Util;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Transactions;
namespace Fk.PlugInSample.FormPlugIn
{
[Kingdee.BOS.Util.HotUpdate]
[System.ComponentModel.Description("打印控制表单插件")]
public class PrintUniqueControlBillPlugIn : AbstractBillPlugIn
{
PrintUniqueHandler handler;
public override void OnInitialize(InitializeEventArgs e)
{
handler = new PrintUniqueHandler(this.Context, (msg) => this.View.ShowMessage(msg));
}
/// <summary>
/// 套打打印前命令
/// </summary>
/// <param name="e"></param>
public override void BeforeNotePrintCommand(BeforeNotePrintEventArgs e)
{
handler.BeforeNotePrintCommand(e);
}
/// <summary>
/// 打印完成后事件
/// </summary>
/// <param name="e"></param>
public override void OnAfterPrint(AfterPrintEventArgs e)
{
handler.AfterPrint(e);
}
}
[Kingdee.BOS.Util.HotUpdate]
[System.ComponentModel.Description("打印控制列表插件")]
public class PrintUniqueControlListPlugIn : AbstractListPlugIn
{
PrintUniqueHandler handler;
public override void OnInitialize(InitializeEventArgs e)
{
handler = new PrintUniqueHandler(this.Context, (msg) => this.View.ShowMessage(msg));
}
/// <summary>
/// 套打打印前命令
/// </summary>
/// <param name="e"></param>
public override void BeforeNotePrintCommand(BeforeNotePrintEventArgs e)
{
handler.BeforeNotePrintCommand(e);
}
/// <summary>
/// 打印完成后事件
/// </summary>
/// <param name="e"></param>
public override void OnAfterPrint(AfterPrintEventArgs e)
{
handler.AfterPrint(e);
}
}
public class PrintUniqueItem
{
public string Fid;
public string FormId;
public string FKey;
public override int GetHashCode()
{
return string.Concat(FormId, FKey).GetHashCode();
}
public override bool Equals(object obj)
{
PrintUniqueItem rhs = obj as PrintUniqueItem;
if (rhs == null)
return false;
return this.FormId == rhs.FormId && this.FKey == rhs.FKey;
}
}
public class PrintUniqueHandler
{
/*
--sqlserver
IF NOT EXISTS (SELECT 1 FROM (SELECT NAME AS TABLE_NAME, XTYPE AS TABLE_XTYPE FROM sysobjects WHERE XTYPE = 'U' OR XTYPE = 'V') AS KSQL_USERTABLES
WHERE TABLE_NAME = 'Tmp_PrintUniqueControl')
BEGIN
CREATE TABLE Tmp_PrintUniqueControl (FID VARCHAR (36) NOT NULL DEFAULT LOWER(NEWID()),
FObjectTypeId VARCHAR (50) NOT NULL DEFAULT ' ', FKey VARCHAR (50) NOT NULL DEFAULT ' ', FUSERID BIGINT NOT NULL DEFAULT 0, FCREATEDATE DATETIME NULL DEFAULT GETDATE())
END
;
IF NOT EXISTS (SELECT 1 FROM (SELECT sysobjects.NAME AS TABLE_NAME, sysindexes.NAME AS INDEX_NAME FROM sysobjects INNER JOIN sysindexes ON sysindexes.ID = sysobjects.ID) AS KSQL_INDEXES WHERE INDEX_NAME = 'UNIQUE_Tmp_PrintUniqueControl')
BEGIN
ALTER TABLE Tmp_PrintUniqueControl ADD CONSTRAINT UNIQUE_TEST_CONTROL UNIQUE (FObjectTypeId, FKey)
END
;
*/
private readonly Context Context;
private List<PrintUniqueItem> CurPrintingItem;
private readonly Action<string> ShowMsgAct;
public PrintUniqueHandler(Context ctx, Action<string> showMsgAct)
{
Context = ctx;
ShowMsgAct = showMsgAct;
CurPrintingItem = new List<PrintUniqueItem>();
}
private void ShowMessage(string msg)
{
if (ShowMsgAct == null)
return;
ShowMsgAct(msg);
}
/// <summary>
/// 打印前事件,增加网控
/// </summary>
/// <param name="e"></param>
public void BeforeNotePrintCommand(BeforeNotePrintEventArgs e)
{
e.ShowPreviewButton = false;
if (!IsPrintOp(e.PrintType))
return;
lock (this)
{
CheckCurPrintingExists();
if (CurPrintingItem.Count > 0)
{
ShowMessage("当前存在打印任务");
e.Cancel = true;
return;
}
HashSet<PrintUniqueItem> printUniqueItems = new HashSet<PrintUniqueItem>();
foreach (var printJob in e.PrintJobs)
{
if (printJob == null || printJob.PrintJobItems == null)
continue;
string formId = printJob.FormId;
foreach (var printJobItem in printJob.PrintJobItems)
{
string billId = printJobItem.BillId;
PrintUniqueItem item = new PrintUniqueItem()
{
Fid = Guid.NewGuid().ToString(),
FormId = formId,
FKey = billId
};
printUniqueItems.Add(item);
}
}
if (!AddControl(printUniqueItems))
{
ShowMessage("尝试增加单据网控异常,无法打印,请稍后重试;或打开打印冲突列表清除对应记录");
e.Cancel = true;
return;
}
CurPrintingItem.AddRange(printUniqueItems);
}
}
/// <summary>
/// 打印完成,解除网控
/// </summary>
/// <param name="e"></param>
public void AfterPrint(AfterPrintEventArgs e)
{
if (e.NoteIDPairs != null && e.NoteIDPairs.Any())
{
List<string> pkIds = CurPrintingItem.Select(x => x.Fid).ToList();
ClearByPkIds(pkIds);
lock(this)
{
CurPrintingItem.Clear();
}
}
}
/// <summary>
/// 支持用户通过打印冲突列表删除后继续打印
/// </summary>
private void CheckCurPrintingExists()
{
if (CurPrintingItem.Count <= 0)
return;
var pkIds = CurPrintingItem.Select(x=>x.Fid).Distinct().ToList();
List<SqlParam> sqlParams = new List<SqlParam>();
StringBuilder strSelect = new StringBuilder("SELECT PC.FID FROM Tmp_PrintUniqueControl PC ");
if (pkIds.Count == 1)
{
strSelect.Append("WHERE FID = @FID");
sqlParams.Add(new SqlParam("@FID", KDDbType.AnsiString, pkIds[0]