本文介绍了如何在移动端实现附件上传功能,包括将附件上传至数据库或文件服务器的详细步骤。文章首先概述了上传功能的实现路径,随后详细讲解了从新建移动表单到上传附件至临时目录,再到最终上传至数据库或文件服务器的整个流程。文中还提供了代码示例,展示了如何通过插件实现附件上传的逻辑,包括处理上传至临时目录后的回调函数、判断文件服务器是否启用、文件分段上传等。此外,还提到了附件属性数据的保存和关联方式,以及可能遇到的问题和相关链接。
一、前言
1. 为了明晰上传与下载功能的讲解,这里将分两篇文章来分别讲解上传与下载功能。
2. 本篇将完整讲解如何实现移动端的附件上传功能:
2.1 将附件上传至数据库
2.2 将附件上传至文件服务器
3. 相关链接详见文章末尾。
二、移动附件上传开发教程
附件上传涉及以下三个步骤:
选择文件 => 上传至本地临时目录 => 上传至(数据库 or 文件服务器)
以下将进行逐步构建和讲解:
从新建一张移动表单开始
1. 登陆BOS IDE
2. 打开子系统
3. 新建移动表单
再添加一些控件
1. 添加流式布局,用于放置上传按钮。
2. 添加按钮(通用控件),用于点击时上传附件
3. 添加多行文本,用于记录上传日志
4. 添加附件控件(通用控件),用于选择要上传的文件
我们想要的功能,通过插件来实现
1. 附件上传逻辑涉及以下几个点:
1.1 点击上传按钮时调用控件的UploadFieldBatch方法,将附件上传至临时目录
[code]// 将附件上传至临时目录
this.View.GetControl
[code]// 检查是否启用了文件服务器
if (!Kingdee.BOS.ServiceHelper.FileServer.FileServerHelper.UsedFileServer(this.Context))
{
this.View.ShowMessage("未启用文件服务器,无法实现上传。");
return;
}[/code]1.3 我们要先知道怎么获取临时目录
[code]// 获取文件上传的临时目录
string tempDirPath = HttpContext.Current.Request.PhysicalApplicationPath + KeyConst.TEMPFILEPATH;[/code]1.4 上传到临时目录完成后,会触发AfterMobileUpload回调函数,并可以通过参数e.FileNameArray获取到附件名称列表,我们就可以在这个回调函数中将临时目录中的附件上传至数据库或文件服务器。
[code]///
/// 附件上传至本地临时目录后的回调函数
///
///
public override void AfterMobileUpload(PlugIn.Args.MobileUploadEventArgs e)
{
foreach (FiledUploadEntity file in e.FileNameArray)
{
// TODO:将附件上传至数据库或文件服务器
}
}[/code]1.5 附件的属性数据(如文件名、大小等)是保存在数据库中的,并且会关联FormId和标识InterId,来识别这个附件是与那张单据or基础资料关联。在我们的例子中,为了简化涉及面,我们使用虚拟的FormId和InterId,只演示如何进行附件的上传与下载。[code]dyn["BillType"] = "Test_MOB_Accessory"; // 虚拟FormId
dyn["InterID"] = "D00001"; // 虚拟的InterId,这个ID将作为我们下载的识别标识[/code]1.6 附件可以有多种存储方式,例如数据库、文件服务器、各种云服务器等,这个可以从FileStorage这个字段来进行识别[code]FileStorage 文件存储类型,0为数据库,1为文件服务,2为亚马逊云,3为金蝶云[/code]1.7 文件服务器目前采用分段上传的方式,每次只上传一个文件片段,通过FileId来标识多个片段属于同一个文件。最后一个片段需要将Last属性置为true
[code]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;[/code]2. 以下是完整的插件代码,供大家参考[code]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
{
///
/// 附件上传测试
///
///
/// 附件上传涉及以下步骤:
/// 1. 附件控件将选取文件拷贝到临时目录
/// 2. 触发AfterMobileUpload函数并传入所需参数
/// 3. 从临时目录将文件保存至数据库或文件服务器
///
[Description("测试附件上传")]
public class TestAccessoryUpload : AbstractMobilePlugin
{
///
/// 上传类型, 0为数据库,1为文件服务,2为亚马逊云,3为金蝶云
///
private int submitType = 0;
///
/// 按钮点击事件
///
///
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
base.ButtonClick(e);
}
///
/// 附件上传至本地临时目录后的回调函数
///
///
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
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);
}
///
/// 上传附件方法
///
///
///
///
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
};
}
///
/// 获取用户
///
///
///
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();
}
}
}
[/code]
三、移动附件上传开发过程中可能遇到的问题的QA部分
1. 待更新...
四、相关链接
1.【知识分享】移动附件_下载篇
2.【分享】K3 Cloud文件服务器配置指南
3.【分享】K3 Cloud移动产品相关文档下载(3g.k3cloud.kingdee.com/m/doc_dowload.htm)
-_-!!我还不能分享外部链接~~只能手打奉上了...Org
添加上传至数据库按钮.jpg(260.67KB)
添加上传至文件服务器按钮.jpg(266.88KB)
添加多行文本.jpg(245.45KB)
添加流式布局.jpg(233.46KB)
添加附件上传控件.jpg(217.02KB)
新建单据.jpg(122.43KB)
推荐阅读