本文介绍了如何在移动端实现附件上传功能,包括将附件上传至数据库或文件服务器的详细步骤。首先,通过BOS IDE新建移动表单并添加相关控件,如流式布局、按钮、多行文本和附件控件。然后,通过插件实现附件上传逻辑,包括点击上传按钮将附件上传至临时目录、检查是否启用文件服务器、获取临时目录中的附件并上传至目标存储位置(数据库或文件服务器)。文章还提供了完整的插件代码示例,并解释了附件上传过程中涉及的关键属性和步骤,如文件存储类型、分段上传等。
一、前言
1. 为了明晰上传与下载功能的讲解,这里将分两篇文章来分别讲解上传与下载功能。
2. 本篇将完整讲解如何实现移动端的附件上传功能:
2.1 将附件上传至数据库
2.2 将附件上传至文件服务器
3. 相关链接详见文章末尾。
二、移动附件上传开发教程
附件上传涉及以下三个步骤:
选择文件 => 上传至本地临时目录 => 上传至(数据库 or 文件服务器)
以下将进行逐步构建和讲解:
从新建一张移动表单开始
1. 登陆BOS IDE
2. 打开子系统
3. 新建移动表单
再添加一些控件
1. 添加流式布局,用于放置上传按钮。
2. 添加按钮(通用控件),用于点击时上传附件
3. 添加多行文本,用于记录上传日志
4. 添加附件控件(通用控件),用于选择要上传的文件
我们想要的功能,通过插件来实现
1. 附件上传逻辑涉及以下几个点:
1.1 点击上传按钮时调用控件的UploadFieldBatch方法,将附件上传至临时目录
// 将附件上传至临时目录
this.View.GetControl<FileUploadControl>("FFileUpdate").UploadFieldBatch();
复制代码
1.2 如果是上传到文件服务器,还需要判断是否启用了文件服务器
// 检查是否启用了文件服务器
if (!Kingdee.BOS.ServiceHelper.FileServer.FileServerHelper.UsedFileServer(this.Context))
{
this.View.ShowMessage("未启用文件服务器,无法实现上传。");
return;
}
复制代码
1.3 我们要先知道怎么获取临时目录
// 获取文件上传的临时目录
string tempDirPath = HttpContext.Current.Request.PhysicalApplicationPath + KeyConst.TEMPFILEPATH;
复制代码
1.4 上传到临时目录完成后,会触发AfterMobileUpload回调函数,并可以通过参数e.FileNameArray获取到附件名称列表,我们就可以在这个回调函数中将临时目录中的附件上传至数据库或文件服务器。
/// <summary>
/// 附件上传至本地临时目录后的回调函数
/// </summary>
/// <param name="e"></param>
public override void AfterMobileUpload(PlugIn.Args.MobileUploadEventArgs e)
{
foreach (FiledUploadEntity file in e.FileNameArray)
{
// TODO:将附件上传至数据库或文件服务器
}
}
复制代码
1.5 附件的属性数据(如文件名、大小等)是保存在数据库中的,并且会关联FormId和标识InterId,来识别这个附件是与那张单据or基础资料关联。在我们的例子中,为了简化涉及面,我们使用虚拟的FormId和InterId,只演示如何进行附件的上传与下载。
dyn["BillType"] = "Test_MOB_Accessory"; // 虚拟FormId
dyn["InterID"] = "D00001"; // 虚拟的InterId,这个ID将作为我们下载的识别标识
复制代码
1.6 附件可以有多种存储方式,例如数据库、文件服务器、各种云服务器等,这个可以从FileStorage这个字段来进行识别
FileStorage 文件存储类型,0为数据库,1为文件服务,2为亚马逊云,3为金蝶云
复制代码
1.7 文件服务器目前采用分段上传的方式,每次只上传一个文件片段,通过FileId来标识多个片段属于同一个文件。最后一个片段需要将Last属性置为true
TFileInfo tFile = new TFileInfo()
{
FileId = fileId,
FileName = fileName,
CTX = this.Context,
Last = len >= dataBuff.Length, //标记是否为文件的最后一个片段
Stream = ms //每次只上传一个文件片段
};
var result = service.UploadAttachment(tFile);
// 注意点:上传时fileId传入null为新文件开始,会返回一个文件的fileId,后续采用这个fileId标识均为同一文件的不同片段。
fileId = result.FileId;
复制代码
2. 以下是完整的插件代码,供大家参考
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.FileServer.Core;
using Kingdee.BOS.FileServer.Core.Object;
using Kingdee.BOS.FileServer.ProxyService;
using Kingdee.BOS.KDThread;
using Kingdee.BOS.Mobile.Metadata.ControlDataEntity;
using Kingdee.BOS.Mobile.PlugIn;
using Kingdee.BOS.Mobile.PlugIn.ControlModel;
using Kingdee.BOS.Orm.DataEntity;
using Kingdee.BOS.ServiceHelper;
using Kingdee.BOS.Util;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
namespace Kingdee.BOS.Mobile.FormPlugIns.Test
{
/// <summary>
/// 附件上传测试
/// </summary>
/// <remarks>
/// 附件上传涉及以下步骤:
/// 1. 附件控件将选取文件拷贝到临时目录
/// 2. 触发AfterMobileUpload函数并传入所需参数
/// 3. 从临时目录将文件保存至数据库或文件服务器
/// </remarks>
[Description("测试附件上传")]
public class TestAccessoryUpload : AbstractMobilePlugin
{
/// <summary>
/// 上传类型, 0为数据库,1为文件服务,2为亚马逊云,3为金蝶云
/// </summary>
private int submitType = 0;
/// <summary>
/// 按钮点击事件
/// </summary>
/// <param name="e"></param>
public override void ButtonClick(Core.DynamicForm.PlugIn.Args.ButtonClickEventArgs e)
{
if ("FSubmit2DB".EqualsIgnoreCase(e.Key))
{
// 使用数据库进行附件存取
submitType = 0;
}
else if ("FSubmit2FileServer".EqualsIgnoreCase(e.Key))
{
// 采用文件服务器方式进行附件存取
// 检查是否启用了文件服务器
if (!Kingdee.BOS.ServiceHelper.FileServer.FileServerHelper.UsedFileServer(this.Context))
{
this.View.ShowMessage("未启用文件服务器,无法实现上传。");
return;
}
// 取Cloud服务器配置的存储方式进行附件存储
submitType = Kingdee.BOS.ServiceHelper.FileServer.FileServerHelper.GetFileStorgaeType(this.Context);
}
// 先将客户端附件上传至临时目录
this.View.GetControl<FileUploadControl>("FFileUpdate").UploadFieldBatch();
base.ButtonClick(e);
}
/// <summary>
/// 附件上传至本地临时目录后的回调函数
/// </summary>
/// <param name="e"></param>
public override void AfterMobileUpload(PlugIn.Args.MobileUploadEventArgs e)
{
// 获取服务器临时目录
string tempDirPath = HttpContext.Current.Request.PhysicalApplicationPath + KeyConst.TEMPFILEPATH;
// 获取附件表的元数据类
var formMetadata = (FormMetadata)MetaDataServiceHelper.Load(this.Context, FormIdConst.BOS_Attachment);
List<DynamicObject> dynList = new List<DynamicObject>();
StringBuilder sb = new StringBuilder();
foreach (FiledUploadEntity file in e.FileNameArray)
{
// 检查文件是否成功上传到临时目录
if (!file.IsSuccess)
{
continue;
}
// 检查文件是否存在于临时目录
var fileName = System.IO.Path.Combine(tempDirPath, file.FileName);
if (!System.IO.File.Exists(fileName))
{
continue;
}
/**
* 此处几个关键属性解读:
* 1. BillType 关联的模型的FormId
* 2. BillNo 关联的单据编号,用于确定此附件是属于哪张单据
* 3. InterID 关联的单据/基础资料ID,附件列表就是根据这个ID进行加载
* 4. EntryInterID 关联的单据体ID,这里我们只演示单据头,所以固定设置为-1
* 5. AttachmentSize 系统统一按照KB单位进行显示,所以需要除以1024
* 6. FileStorage 文件存储类型,0为数据库,1为文件服务,2为亚马逊云,3为金蝶云
*/
var dataBuff = System.IO.File.ReadAllBytes(fileName);
var dyn = new DynamicObject(formMetadata.BusinessInfo.GetDynamicObjectType());
if (submitType != 0)
{
// 将数据上传至文件服务器,并返回上传结果
var result = this.UploadAttachment(file.FileName, dataBuff);
if (!result.Success)
{
// 上传失败,收集失败信息
sb.AppendLine(string.Format("附件:{0},上传失败:{1}", file.FileName, result.Message));
continue;
}
// 通过这个FileId就可以从文件服务器下载到对应的附件
dyn["FileId"] = result.FileId;
}
else
{
// 数据库的方式是直接保存附件数据
dyn["Attachment"] = dataBuff;
}
// 此处我们不绑定到特定的单据,为了简化示例,只实现单纯的文件上传与下载
dyn["BillType"] = "Test_MOB_Accessory"; // 虚拟FormId
dyn["BillNo"] = "A00001"; // 虚拟的单据编号
dyn["InterID"] = "D00001"; // 虚拟的InterId,这个ID将作为我们下载的识别标识
// 上传文件服务器成功后才加入列表
dyn["AttachmentName"] = file.FileName;
dyn["AttachmentSize"] = Math.Round(dataBuff.Length / 1024.0, 2);
dyn["EntryInterID"] = -1;// 参照属性解读
dyn["CreateMen_Id"] = Convert.ToInt32(this.Context.UserId);
dyn["CreateMen"] = GetUser(this.Context.UserId.ToString());
dyn["ModifyTime"] = dyn["CreateTime"] = TimeServiceHelper.GetSystemDateTime(this.Context);
dyn["ExtName"] = System.IO.Path.GetExtension(file.FileName);
dyn["FileStorage"] = submitType.ToString();
dynList.Add(dyn);
}
if (dynList.Count > 0)
{
// 所有数据加载完成后再一次性保存全部
BusinessDataServiceHelper.Save(this.Context, dynList.ToArray());
sb.AppendLine();
sb.AppendLine(string.Join(",", dynList.Select(dyn => dyn["AttachmentName"].ToString()).ToArray()) + ",上传成功");
}
this.Model.SetValue("FLog", sb.ToString());
base.AfterMobileUpload(e);
}
/// <summary>
/// 上传附件方法
/// </summary>
/// <param name="fileName"></param>
/// <param name="dataBuff"></param>
/// <returns></returns>
private FileUploadResult UploadAttachment(string fileName, byte[] dataBuff)
{
// 初始化上传下载服务,这个Service会根据Cloud配置自动上传到对应的文件服务器
var service = new UpDownloadService();
int len = 0, less = 0;
string fileId = null;
byte[] buff = null;
while (len < dataBuff.Length)
{
// 文件服务器采用分段上传,每次上传4096字节, 最后一次如果不够则上传剩余长度
less = (dataBuff.Length - len) >= 4096 ? 4096 : (dataBuff.Length - len);
buff = new byte[less];
Array.Copy(dataBuff, len, buff, 0, less);
len += less;
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(buff))
{
TFileInfo tFile = new TFileInfo()
{
FileId = fileId,
FileName = fileName,
CTX = this.Context,
Last = len >= dataBuff.Length,//标记是否为文件的最后一个片段
Stream = ms
};
var result = service.UploadAttachment(tFile);
// 注意点:上传时fileId传入null为新文件开始,会返回一个文件的fileId,后续采用这个fileId标识均为同一文件的不同片段。
fileId = result.FileId;
if (!result.Success)
{
return result;
}
}
}
return new FileUploadResult()
{
Success = true,
FileId = fileId
};
}
/// <summary>
/// 获取用户
/// </summary>
/// <param name="userID"></param>
/// <returns></returns>
DynamicObject GetUser(string userID)
{
OQLFilter filter = OQLFilter.CreateHeadEntityFilter(string.Format("FUSERID={0}", userID));
return BusinessDataServiceHelper.Load(this.View.Context, FormIdConst.SEC_User, null, filter).FirstOrDefault();
}
}
}
复制代码
推荐阅读