一、前言
1. 目前V5.0版本的文件服务器接口部分未进行封装,需要自己写代码实现上传与下载的功能。
2. 本篇将详细介绍移动端附件上传到数据库和文件服务器的实现。
二、正文
附件上传涉及以下三个步骤:
选择文件 => 上传至本地临时目录 => 上传至(数据库 or 文件服务器)
以下将介绍如果新建一张表单,并写插件来实现附件上传功能。
从新建一张移动表单开始
1. 登陆BOS IDE
2. 打开子系统
3. 新建移动表单
再添加一些控件
1. 添加流式布局,用于放置上传按钮。
2. 添加按钮(通用控件),用于点击时上传附件
3. 添加多行文本,用于记录上传日志
4. 添加附件控件(通用控件),用于选择要上传的文件
至此界面显示部分完成,接下来使用插件来实现具体的上传逻辑
1. 先来掌握一些重点概念,最后面会贴出完整的插件代码供大家参考。
1.1 点击上传按钮时调用控件的UploadFieldBatch方法,将附件上传至临时目录
// 将附件上传至临时目录
this.View.GetControl<FileUploadControl>("FFileUpdate").UploadFieldBatch();
复制代码
1.2 然后我们要知道怎么获取临时目录
// 获取文件上传的临时目录
string tempDirPath = HttpContext.Current.Request.PhysicalApplicationPath + KeyConst.TEMPFILEPATH;
复制代码
1.3 上传到临时目录完成后,会触发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.4 附件的属性数据(如文件名、大小等)是保存在数据库中的,并且会有一个关联标识InterId,来识别这个附件是与那张单据or基础资料关联。在我们的例子中,为了简化涉及面,我们虚拟一个InterId,只演示如何进行附件的上传与下载。
dyn["InterID"] = "D00001"; // 虚拟的InterId,这个ID将作为我们下载的识别标识
复制代码
1.5 附件可以有多种存储方式,例如数据库、文件服务器、各种云服务器等,这个可以从FileStorage这个字段来进行识别。
FileStorage 文件存储类型,0为数据库;1为文件服务器
复制代码
1.6 到了上传部分了,这里不同于V6.1版本,我们需要手动去获取文件服务器地址,以此来判断是否启用了文件服务器。
// 获取文件服务器地址
var fileServerUrl = BusinessDataServiceHelper.GetFileServerUrl(this.Context);
bool usedFileServer = !fileServerUrl.IsNullOrEmptyOrWhiteSpace();
if (usedFileServer && !fileServerUrl.EndsWith("/"))
{
fileServerUrl += "/";
}
复制代码
2. 以下是完整示例代码,供大家参考。
using Kingdee.BOS.Core;
using Kingdee.BOS.Core.Metadata;
using Kingdee.BOS.FileServer.Core;
using Kingdee.BOS.FileServer.Web;
using Kingdee.BOS.JSON;
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.Net;
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>
/// 上传类型
/// </summary>
private enum SubmitType
{
DB,
FileServer
}
private SubmitType submitType;
/// <summary>
/// 表单上的按钮点击事件
/// </summary>
/// <param name="e"></param>
public override void ButtonClick(Core.DynamicForm.PlugIn.Args.ButtonClickEventArgs e)
{
if ("FSubmit2DB".EqualsIgnoreCase(e.Key))
{
submitType = SubmitType.DB;
// 将附件上传至临时目录
this.View.GetControl<FileUploadControl>("FFileUpdate").UploadFieldBatch();
}
else if ("FSubmit2FileServer".EqualsIgnoreCase(e.Key))
{
// 检查是否启用了文件服务器
if (BusinessDataServiceHelper.GetFileServerUrl(this.Context).IsNullOrEmptyOrWhiteSpace())
{
this.View.ShowMessage("未启用文件服务器,无法实现上传。");
return;
}
submitType = SubmitType.FileServer;
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);
// 获取文件服务器地址
var fileServerUrl = BusinessDataServiceHelper.GetFileServerUrl(this.Context);
bool usedFileServer = !fileServerUrl.IsNullOrEmptyOrWhiteSpace();
if (usedFileServer && !fileServerUrl.EndsWith("/"))
{
fileServerUrl += "/";
}
List<DynamicObject> dynList = new List<DynamicObject>();
StringBuilder sb = new StringBuilder();
if (!usedFileServer)
{
sb.AppendLine("未启用文件服务器。");
}
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. AttachmentSize 系统统一按照KB单位进行显示,所以需要除以1024
* 2. EntryKey 单据体的标识,这里我们只演示单据头,考虑到各位数据库都不同,不能保持为null,至少应输入空格
* 3. EntryInterID 关联的单据体ID,这里我们只演示单据头,所以固定设置为-1
* 4. BillType 关联的模型的FormId
* 5. BillNo 关联的单据编号,用于确定此附件是属于哪张单据
* 6. BillStatus 关联的单据状态
* 7. InterID 关联的单据/基础资料ID,附件列表就是根据这个ID进行加载
* 8. FileStorage 文件存储类型,0为数据库;1为文件服务器;
*/
var dataBuff = System.IO.File.ReadAllBytes(fileName);
var dyn = new DynamicObject(formMetadata.BusinessInfo.GetDynamicObjectType());
if (submitType == SubmitType.FileServer)
{
if (!usedFileServer)
{
// 未启用文件服务器,直接跳过
continue;
}
// 将数据上传至文件服务器,并返回上传结果
var result = this.UploadAttachment(fileServerUrl, file.FileName, dataBuff);
if (!result.Success)
{
// 上传失败,收集失败信息
sb.AppendLine(string.Format("附件:{0},上传失败:{1}", file.FileName, result.Message));
// 跳过该附件,继续上传下一个附件
continue;
}
// 通过这个FileId就可以从文件服务器下载到对应的附件
dyn["FileId"] = result.FileId;
dyn["FileStorage"] = '1';// 文件服务器,参照属性解读
}
else
{
// 数据库的方式是直接保存附件数据
dyn["Attachment"] = dataBuff;
dyn["FileStorage"] = '0';// 数据库,参照属性解读
}
// 上传文件服务器成功后才加入列表
dyn["AttachmentName"] = file.FileName;
dyn["AttachmentSize"] = Math.Round(dataBuff.Length / 1024.0, 2);
dyn["ExtName"] = System.IO.Path.GetExtension(file.FileName);
dyn["EntryKey"] = ' ';// 参照属性解读
dyn["EntryInterID"] = -1;// 参照属性解读
// 此处我们不绑定到特定的单据,为了简化示例,只实现单纯的文件上传与下载,使用InterID作为下载标识
dyn["BillType"] = "Test_MOB_Accessory"; // 演示用FormId
dyn["BillNo"] = "A00001"; // 演示用单据编号
dyn["BillStatus"] = "A";// 演示用单据状态
dyn["InterID"] = "D00001"; // 演示中内码标识,这个ID将作为我们下载的识别标识
//// 而实际插件开发可以从移动单据中获取到这些数据
//dyn["BillType"] = this.View.BillView.BusinessInfo.GetForm().Id;
//dyn["BillNo"] = this.View.BillModel.DataObject["BillNo"];
//dyn["BillStatus"] = this.View.BillModel.DataObject["DocumentStatus"];
//dyn["InterID"] = this.View.BillModel.DataObject["Id"];
dyn["CreateMen_Id"] = Convert.ToInt32(this.Context.UserId);
dyn["ModifyTime"] = dyn["CreateTime"] = TimeServiceHelper.GetSystemDateTime(this.Context);
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 serverUrl, string fileName, byte[] dataBuff)
{
// 初始化上传下载服务
int len = 0, less = 0;
string fileId = null;
byte[] buff = null;
bool isLast = false;
string urlFormat = serverUrl + "FileService.svc/upload2attachment/?fileName={0}&fileId={1}&dbId={2}&last={3}";
using (WebClient client = new WebClient())
{
while (!isLast)
{
// 文件应采用分段上传,每次上传4096字节, 最后一次如果不够则上传剩余长度
less = (dataBuff.Length - len) >= 4096 ? 4096 : (dataBuff.Length - len);
buff = new byte[less];
Array.Copy(dataBuff, len, buff, 0, less);
len += less;
isLast = (len >= dataBuff.Length);
string url = string.Format(urlFormat, fileName, fileId, this.Context.DBId, isLast);
var returnData = client.UploadData(url, "POST", buff);
JSONObject jObj = JSONObject.Parse(Encoding.UTF8.GetString(returnData));
JSONObject uploadResult = jObj["Upload2AttachmentResult"] as JSONObject;
var result = new FileUploadResult()
{
Success = Convert.ToBoolean(uploadResult["Success"].ToString()),
FileId = uploadResult["FileId"].ToString(),
Message = uploadResult["Message"].ToString()
};
// 注意点:上传时fileId传入null为新文件开始,会返回一个文件的fileId,后续采用这个fileId标识均为同一文件的不同片段。
fileId = result.FileId;
if (!result.Success)
{
return result;
}
}
}
return new FileUploadResult()
{
Success = true,
FileId = fileId
};
}
}
}
复制代码
三、移动附件上传开发过程中可能遇到的问题的QA部分
1. 待更新...
四、相关链接
1.【分享】移动附件下载——V5.0版本
2.【分享】K3 Cloud文件服务器配置指南
3.【分享】K3 Cloud移动产品相关文档下载(3g.k3cloud.kingdee.com/m/doc_dowload.htm)
4.【分享】移动附件上传——V6.1版本
5.【分享】移动附件下载——V6.1版本
推荐阅读