本文简要介绍了如何将存储在数据库中的物理文件(附件)迁移至文件服务器中,以缓解数据库存储压力。文本说明了附件字段如何以Base64字符串形式存储文件信息,并通过示例代码展示了如何遍历这些文件信息,将其从Base64字符串转换为二进制数据并上传到文件服务器。示例代码以采购订单单据(单据编号为CGDD000401)为例,展示了如何筛选单据数据、转换并上传文件,并在文件服务器成功上传后保存附件信息到数据库。此外,还提到了迁移过程中需注意的事项,如分批迁移、迁移后的数据清理等。
本示例演示将附件(数据库)里的物理文件,迁移至附件菜单的附件列表下。
【附件(数据库)】属于常规字段,物理文件存储于数据库表格字段下,单据中可放表头表体,用以上传并存储 UploadWhiteList白名单允许的文件,通过设置允许多选属性可批量上传多个文件,其中这多个文件的文件名、文件大小以及文件内容等信息由 JSONArray 数组对象转 Base64 字符串后直接存储于数据库字段下,数据实体对象的属性类型为字符串类型。
当单据附件上传量过大时,容易造成数据库越来越大,现提供个简单的示例代码用以将“上传文件字段”下的附件迁移至文件服务器中。本例以采购订单单据编号为CGDD000401的单据为例(可根据实际需要自定义自己的过滤条件,建议是分批次进行迁移),对单据头上的“F_FF_FILEUPDATE 附件(数据库)”进行迁移,迁移前需配置并启用文件服务器。
备注:早期版本字段名称为“上传文件字段”,后改名为“附件(数据库)”。
示例代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using Kingdee.BOS;
using Kingdee.BOS.Core.DynamicForm.PlugIn;
using Kingdee.BOS.Core.DynamicForm.PlugIn.Args;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.Core.SqlBuilder;
using Kingdee.BOS.JSON;
using Kingdee.BOS.Orm.DataEntity;
using Kingdee.BOS.ServiceHelper;
using Kingdee.BOS.Util;
using Kingdee.BOS.Core;
using Kingdee.BOS.FileServer.Core;
using Kingdee.BOS.FileServer.Core.Object;
using Kingdee.BOS.FileServer.Core.Object.Enum;
using Kingdee.BOS.FileServer.ProxyService;
using Kingdee.BOS.Log;
namespace Running.Sample.PlugIn.BusinessPlugIn.DynamicForm
{
/// <summary>
/// “上传文件字段”迁移至文件服务器示例代码。
/// </summary>
[Description("“上传文件字段”迁移至文件服务器示例代码。")]
public class P20190307TransferFileUpdateFieldPlugIn : AbstractDynamicFormPlugIn
{
private const string BillFormId = "PUR_PurchaseOrder"; //待迁移业务对象唯一标志。
private const string FilterString = "FBILLNO = 'CGDD000401'"; //待迁移单据数据筛选条件。
/// <summary>
/// 待迁移单据的单据主键、单据编号以及文件上传字段的字段名。
/// </summary>
private readonly string[] _selectFieldKeys = new string[] { "FID", "FBILLNO", "F_FF_FILEUPDATE" };
private FormMetadata _attachmentMetadata; //附件明细元数据对象。
public override void BarItemClick(BarItemClickEventArgs e)
{
_attachmentMetadata = MetaDataServiceHelper.Load(this.View.Context, "BOS_Attachment") as FormMetadata;
Debug.Assert(_attachmentMetadata != null, "_attachmentMetadata != null");
//根据业务对象唯一标志、筛选条件及字段列表,返回待迁移的附件数据,并逐条开始遍历迁移。
QueryBuilderParemeter queryParameter = new QueryBuilderParemeter
{
FormId = BillFormId,
SelectItems = SelectorItemInfo.CreateItems(_selectFieldKeys),
//TODO: 自行定义筛选策略,建议分批筛选进行迁移,本次以采购订单单据编号为CGDD000401的单据为例。
FilterClauseWihtKey = FilterString
};
DynamicObjectCollection billDynObjCol = QueryServiceHelper.GetDynamicObjectCollection(this.View.Context, queryParameter);
foreach (DynamicObject billDynObj in billDynObjCol)
{
try
{
//逐条遍历单据数据,进行迁移。
TransferFilesByBillDynObj(billDynObj);
}
catch (Exception ex)
{
Logger.Error("TransferFileUpdateField",
string.Format("FormId:{0}, FID:{1}的文件迁移失败。", BillFormId, billDynObj["FId"]), ex); /*untrans*/
}
}
}
/// <summary>
/// 传入单据实体数据对象,对其下上传文件字段进行迁移。
/// </summary>
/// <param name="billDynObj">单据实体数据对象。</param>
private void TransferFilesByBillDynObj(DynamicObject billDynObj)
{
//将文件信息由Base64字符串转为Json数组,因为一个字段值下可能同时挂多个附件,所以需对单个字段值下多个附件逐个遍历上传。
string filesInfo = ObjectUtils.Object2String(billDynObj["F_ff_FileUpdate"]);
if (filesInfo.IsNullOrEmptyOrWhiteSpace()) return;
JSONArray filesObj = SerializatonUtil.DeserializeFromBase64<JSONArray>(filesInfo);
if (filesObj == null || filesObj.Count <= 0) return;
//逐个遍历附件,获取文件字节数组并转为内存流,逐个上传至文件服务器。
List<DynamicObject> attachmentDataList = new List<DynamicObject>();
foreach (object fileObj in filesObj)
{
JSONObject fileJsonObj = (JSONObject)fileObj;
if (fileJsonObj["FileName"].IsNullOrEmptyOrWhiteSpace() || fileJsonObj["FileContent"] == null) continue;
MemoryStream fileContentStream = new MemoryStream((byte[])fileJsonObj["FileContent"]);
TFileInfo tFileInfo = new TFileInfo()
{
CTX = this.View.Context,
FileId = string.Empty,
FileName = fileJsonObj["FileName"].ToString(),
Last = true,
Stream = fileContentStream
};
FileUploadResult uploadRes = new UpDownloadService().UploadAttachment(tFileInfo);
Logger.Info("TransferFileUpdateField",
string.Format("FFileId:{0}, Success:{1}, Msg:{2}", uploadRes.FileId, uploadRes.Success, uploadRes.Message));
if (!uploadRes.Success) continue;
//判断上传成功后,收集附件数据,准备批量保存进附件信息表。
attachmentDataList.Add(CollectAttachmentData(billDynObj, uploadRes));
}
if (attachmentDataList.Count > 0)
BusinessDataServiceHelper.Save(this.View.Context, attachmentDataList.ToArray());
//TODO: 上传成功后是否直接删除数据库文件,需自行定义策略,建议等人工确认附件迁移成功删除历史附件。
}
private DynamicObject CollectAttachmentData(DynamicObject billDynObj, FileUploadResult uploadRes)
{
DynamicObject attachmentData = new DynamicObject(_attachmentMetadata.BusinessInfo.GetDynamicObjectType());
attachmentData["BillType"] = BillFormId; //单据唯一标志。
attachmentData["InterID"] = billDynObj["FId"]; //单据主键。
attachmentData["EntryKey"] = " "; //单据体实体标志,若附件挂单据头上,此处赋值为空即可。
attachmentData["AttachmentName"] = uploadRes.FileName; //文件名。
attachmentData["BillNo"] = billDynObj["FBillNo"]; //单据编号。
attachmentData["AttachmentSize"] = Math.Round(Convert.ToDecimal(uploadRes.FileSize) / 1024, 2); //文件大小,KB为单位。
attachmentData["CreateTime"] = DateTime.Now; //创建时间。
attachmentData["FBillStatus"] = "A"; //单据状态(无效字段)。
attachmentData["CreateMen_Id"] = this.View.Context.UserId;
attachmentData["EntryInterID"] = "-1"; //单据体内码。
attachmentData["ExtName"] = Path.GetExtension(uploadRes.FileName); //文件后缀。
attachmentData["FileId"] = uploadRes.FileId; //文件内码。
attachmentData["FileStorage"] = "1"; //文件服务器存储方式。
attachmentData["IsAllowDownLoad"] = 0; //是否禁止下载。
return attachmentData;
}
}
}
推荐阅读