套打.常见问题.打印次数强并发控制方案
【场景】打印次数强并发控制方案
【背景知识】
打印次数的计算逻辑:[套打.常见问题.打印日志和打印次数更新逻辑](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
单据内码——文本
打印时间——长日期
用户——用户
![image.webp](/download/01004bce75600a264548b9e9d42d7db20757.webp)
![image.webp](/download/010093910cfa162045a4aee1943be6debba5.webp)
(2)增加表单插件、列表插件,在打印菜单点击时加锁,打印完成收到前端指令返回时解锁
![image.webp](/download/0100680950cf8a754813a970e8bb4dc96b25.webp)
![image.webp](/download/0100a29ca7ade0724198955a1e235f46ced2.webp)
![20240516 1617.webp](/download/01001bfcc17f48e44aeb8713667d7b5ec97c.webp)
```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]));
}
else if (pkIds.Count <= 20)
{
strSelect.AppendFormat("WHERE FID IN ({0})", string.Join(",", pkIds.Select(id => string.Format("'{0}'", id))));
}
else
{
var tableName = StringUtils.GetSqlWithCardinality(pkIds.Count, "@FID", 2, true);
strSelect.AppendFormat("INNER JOIN {0} tmpfid on tmpfid.FID = PC.FID", tableName);
sqlParams.Add(new SqlParam("@FID", KDDbType.udt_varchartable, pkIds));
}
HashSet<string> existIds = new HashSet<string>();
using (var dr = Kingdee.BOS.App.Data.DBUtils.ExecuteReader(Context, strSelect.ToString(), sqlParams))
{
while(dr.Read())
{
existIds.Add(dr.GetString("FID"));
}
}
for (int i = CurPrintingItem.Count - 1; i >= 0; --i)
{
string fid = CurPrintingItem[i].Fid;
if (existIds.Contains(fid))
continue;
CurPrintingItem.RemoveAt(i);
}
}
///
/// 是否打印操作
///
/// <param name="printType"></param>
/// <returns></returns>
private static bool IsPrintOp(string printType)
{
if (printType == null)
return false;
const string print = "print";
const string PrintMerge = "PrintMerge";
if (string.Equals(printType, print, StringComparison.OrdinalIgnoreCase) ||
string.Equals(printType, PrintMerge, StringComparison.OrdinalIgnoreCase))
return true;
return false;
}
private bool AddControl(IEnumerable<PrintUniqueItem> printUniqueItems)
{
string strSql = "INSERT INTO Tmp_PrintUniqueControl VALUES(@FID, @FObjectTypeId, @FKey, @FUSERID, GETDATE())";
List<SqlObject> sqlObjects = new List<SqlObject>();
foreach(var printUniqueItem in printUniqueItems)
{
List<SqlParam> sqlParams = new List<SqlParam>();
sqlParams.Add(new SqlParam("@FID",KDDbType.AnsiString, printUniqueItem.Fid));
sqlParams.Add(new SqlParam("@FObjectTypeId", KDDbType.AnsiString, printUniqueItem.FormId));
sqlParams.Add(new SqlParam("@FKey", KDDbType.AnsiString, printUniqueItem.FKey));
sqlParams.Add(new SqlParam("@FUSERID", KDDbType.Int64, Context.UserId));
sqlObjects.Add(new SqlObject(strSql, sqlParams));
}
try
{
using (var scope = new KDTransactionScope(TransactionScopeOption.RequiresNew))
{
DBUtils.ExecuteBatch(Context, sqlObjects);
scope.Complete();
}
return true;
}
catch(Exception e)
{
return false;
}
}
public void ClearTimeoutRecord()
{
//清理一天前的打印唯一控制
DateTime deleteTime = DateTime.Now.Subtract(TimeSpan.FromDays(1));
//按照主键删除,而非按照时间删除,避免锁表的逻辑
string strSql = "SELECT TOP 50 FID FROM Tmp_PrintUniqueControl WHERE FCREATEDATE <= @FCREATEDATE";
SqlParam sqlParam = new SqlParam("@FCREATEDATE", KDDbType.DateTime, deleteTime);
List<string> pkIds = new List<string>();
while (true)
{
pkIds.Clear();
using (var dr = Kingdee.BOS.App.Data.DBUtils.ExecuteReader(Context, strSql, sqlParam))
{
while (dr.Read())
{
pkIds.Add(dr.GetString("FID"));
}
}
if (pkIds.Count <= 0)
break;
ClearByPkIds(pkIds);
}
}
/// <summary>
/// 按照主键删除
/// </summary>
/// <param name="pkIds"></param>
private void ClearByPkIds(List<string> pkIds)
{
if (pkIds == null || pkIds.Count <= 0)
return;
pkIds = pkIds.Distinct().ToList();
List<SqlParam> sqlParams = new List<SqlParam>();
StringBuilder strDelete = new StringBuilder("DELETE PC FROM Tmp_PrintUniqueControl AS PC ");
if (pkIds.Count == 1)
{
strDelete.Append("WHERE FID = @FID");
sqlParams.Add(new SqlParam("@FID", KDDbType.AnsiString, pkIds[0]));
}
else if (pkIds.Count <= 20)
{
strDelete.AppendFormat("WHERE FID IN ({0})", string.Join(",", pkIds.Select(id => string.Format("'{0}'", id))));
}
else
{
var tableName = StringUtils.GetSqlWithCardinality(pkIds.Count, "@FID", 2, false);
strDelete.AppendFormat("WHERE EXISTS ({0} WHERE b.FID = PC.FID)", tableName);
sqlParams.Add(new SqlParam("@FID", KDDbType.udt_varchartable, pkIds));
}
Kingdee.BOS.App.Data.DBUtils.Execute(Context, strDelete.ToString(), sqlParams);
}
}
public class PrintUniqueControlClearSchedule : IScheduleService
{
public void Run(Context ctx, Schedule schedule)
{
new PrintUniqueHandler(ctx, null).ClearTimeoutRecord();
}
}
}
```
(3)增加执行计划,定期将1天前的打印网控清理
![image.webp](/download/01009bdbcdb02197445da03926df5aa5c57f.webp)
套打.常见问题.打印次数强并发控制方案
【场景】打印次数强并发控制方案【背景知识】打印次数的计算逻辑:[套打.常见问题.打印日志和打印次数更新逻辑](https://vip.kingdee.com/l...
点击下载文档
本文2024-09-23 04:16:39发表“云星空知识”栏目。
本文链接:https://wenku.my7c.com/article/kingdee-k3cloud-164275.html
您需要登录后才可以发表评论, 登录登录 或者 注册
最新文档
热门文章