嫁吱裨 发表于 2025-5-30 01:11:08

DiscuzNT使用Silverlight进行多文件上传

      注:本文的HTM页面均位于Discuz.Web项目中,大家可以到官方下面最终的程序。
      在去年我曾写过一篇文章:“推荐一个Silverlight多文件(大文件)上传的开源项目”。之后
有不少朋友询问这个项目示例在开发和配置上的一些问题。当时因为时间有限没有做过多的说明,
导致有些问题在大家下载完源码之后运行时才出现。今天就以这个项目为原型,简要介绍一下在
DiscuzNT上是如果在该项目基本上,通过完善权限管理,文件大小控制,添加缩略图效果等功能
来大体阐述一下如果开发一个真正的silverlight应用,而不是一个简单的DEMO.
      当然本文中所列出的源码是通过reflector获取并添加相应注释的。最终的源码还是要以开放
出来的为准,呵呵:)

      好了,开始今天的正文吧!
   
      首先,看一下这个插件在DiscuzNT中的实际运行效果:    
    
    
    
    
    

    
 
     
      当我们在网页中点击“批量上传”按钮时,会运行如下JS脚本(文件位于Discuz.Web\
templates\default_postattachments.htm):
function LoadSilverlight(pluginID, max) {
     
     Silverlight.createObject(
         "silverlight/UploadFile/ClientBin/MultiFileUpload.xap", 
         $("silverlightControlHost"),         
         pluginID,                         
         {     
             width: '500',
             height: '440',
             inplaceInstallPrompt: 'true',
             isWindowless: 'true',
             background: 'transparent',
             version: '2.0',
             autoUpgrade: 'true'
         },
         {
             onLoad: onLoad, 
             onError: onSilverlightError
         },
         
         string authToken = Discuz.Common.DES.Encode(oluserinfo.Olid.ToString() + "," + 
         oluserinfo.Username.ToString(), oluserinfo.Password.Substring(0, 10)).Replace("+", "[");
          
         "forumid={forumid},authToken={authToken},max=" + max,
         "");
}
 
    其会将当前版块id(forumid),认证Token,最大上传数等信息以参数形式传给SL插件,而我专门定
义了一个方法用于获取相应的参数并绑定到sl变量,如下(Page.xaml.cs): 

/// 
 /// 加载配置参数
 /// 
 /// 
 private void LoadConfiguration(IDictionary initParams)
 {
     string tryTest = string.Empty;

     //加载定制配置信息串
     _customParams = initParams["forumid"];

     if (initParams.ContainsKey("MaxUploads") && !string.IsNullOrEmpty(initParams["MaxUploads"]))
         int.TryParse(initParams["MaxUploads"], out _maxUpload);            

     if (initParams.ContainsKey("MaxFileSizeKB") && !string.IsNullOrEmpty(initParams["MaxFileSizeKB"]))
     {
         if (int.TryParse(initParams["MaxFileSizeKB"], out _maxFileSize))
             _maxFileSize = _maxFileSize * 1024;
     }

     if (initParams.ContainsKey("FileFilter") && !string.IsNullOrEmpty(initParams["FileFilter"]))
         _fileFilter = initParams["FileFilter"];          

     if (initParams.ContainsKey("forumid") && !string.IsNullOrEmpty(initParams["forumid"]))
         _forumid = Utils.StrToInt(initParams["forumid"], 0);

     if (initParams.ContainsKey("max") && !string.IsNullOrEmpty(initParams["max"]))
         _maxAttachments = Utils.StrToInt(initParams["max"], 0);

     CredentialInfo _creInfo= Utils.GetCredentialInfo();
     if (_creInfo.UserID  0)
                sbAttachmentTypeSelect.Append(" AND ");

            sbAttachmentTypeSelect.Append(" in (");
            sbAttachmentTypeSelect.Append(forum.Attachextensions);
            sbAttachmentTypeSelect.Append(")");
        }
        string attachextensions = Discuz.Forum.Attachments.GetAttachmentTypeArray(sbAttachmentTypeSelect.ToString());
        string attachextensionsnosize = Discuz.Forum.Attachments.GetAttachmentTypeString(sbAttachmentTypeSelect.ToString());

        //得到今天允许用户上传的附件总大小(字节)
        int MaxTodaySize = 0;
        if (creinfo.UserID > 0)
            MaxTodaySize = Discuz.Forum.Attachments.GetUploadFileSizeByuserid(creinfo.UserID);

        int attachsize = usergroupinfo.Maxsizeperday - MaxTodaySize;//今天可上传大小

        bool canpostattach = false; //是否允许上传附件
        //是否有上传附件的权限
        if (Discuz.Forum.Forums.AllowPostAttachByUserID(forum.Permuserlist, creinfo.UserID))
            canpostattach = true;
        else
        {
            if (forum.Postattachperm == "")
            {
                if (usergroupinfo.Allowpostattach == 1)
                    canpostattach = true;
            }
            else
            {
                if (Discuz.Forum.Forums.AllowPostAttach(forum.Postattachperm, usergroupinfo.Groupid))
                    canpostattach = true;
            }
        }
        return new UploadSetInfo(attachextensions, attachextensionsnosize, MaxTodaySize, attachsize, 
                            canpostattach , usergroupinfo.Maxattachsize, "");
    }

    return new UploadSetInfo("", "", 0, 0, false, 0, "当前用户信息无效,请尝试刷新");
}

 
     该方法的首先会访问AuthenticateUser方法来进行用户身份验证:
   
///
/// WEB权限认证

/// 
/// 认证信息
/// 是否通过验正
private bool AuthenticateUser(CredentialInfo creinfo)
{
    if (creinfo.ForumID > 0)
    {
        int olid = Discuz.Forum.OnlineUsers.GetOlidByUid(creinfo.UserID);
        if (olid > 0)
        {
            OnlineUserInfo oluserinfo = Discuz.Forum.OnlineUsers.GetOnlineUser(olid);
            if (oluserinfo.Userid == creinfo.UserID && 
                Utils.UrlEncode(Discuz.Forum.ForumUtils.SetCookiePassword(oluserinfo.Password, 
                 GeneralConfigs.GetConfig().Passwordkey)) == creinfo.Password &&//检测用户id和口令
                creinfo.AuthToken == DES.Encode(string.Format("{0},{1}", oluserinfo.Olid.ToString(), 
                  oluserinfo.Username.ToString()), oluserinfo.Password.Substring(0, 10)).Replace("+", "["))//检查认证信息
            {
                return true;
            }
        }
    }
    return false;
}
    
    其会对用户的UserId与用户在线表中的数据进行比对,以确保其信息有效,同时还会检查AuthToken
来避免用户通过伪造用户信息来进行信息提交。当上面方法返回TRUE时,则将对用户所在版块的权限信息
进行获取,并返回一个名为UploadSetInfo类实例,其包括:
    1.用户可以上传的文件类型
    2.用户可以上传的文件类型(不带上传数据大小)
    3.得到今天允许用户上传的附件总大小(字节)
    4.是否允许上传附件
    5.单个附件大小
    6.最大允许上传的附件数
    7.错误信息     
#region 上传设置信息类
    /// 
    /// 上传设置信息类
    /// 
    public class UploadSetInfo
    {
        public UploadSetInfo()
        { }

        public UploadSetInfo(string attachExtensions, string attachExtensionsNoSize, int maxTodaySize, 
                  int attachSize, bool canPostAttach, int maxAttachSize, string errMessage)
        {
            m_attachExtensions = attachExtensions;
            m_attachExtensionsNoSize = attachExtensionsNoSize;
            m_maxTodaySize = maxTodaySize;
            m_attachSize = attachSize;
            m_canPostAttach = canPostAttach;
            m_maxAttachSize = maxAttachSize;
            m_errMessage = errMessage;
            m_maxAttachments = GeneralConfigs.GetConfig().Maxattachments;
        }

        private string m_attachExtensions;
        /// 
        /// 用户可以上传的文件类型
        /// 
        public string AttachExtensions
        {
            get { return m_attachExtensions; }
            set { m_attachExtensions = value; }
        }

        private string m_attachExtensionsNoSize;
        /// 
        /// 用户可以上传的文件类型(不带上传数据大小)
        /// 
        public string AttachExtensionsNoSize
        {
            get { return m_attachExtensionsNoSize; }
            set { m_attachExtensionsNoSize = value; }
        }

        private int m_maxTodaySize;
        /// 
        /// 得到今天允许用户上传的附件总大小(字节)
        /// 
        public int MaxTodaySize
        {
            get { return m_maxTodaySize; }
            set { m_maxTodaySize = value; }
        }

        private int m_attachSize;
        /// 
        /// 今天可上传的大小
        /// 
        public int AttachSize
        {
            get { return m_attachSize; }
            set { m_attachSize = value; }
        }

        private bool m_canPostAttach;
        /// 
        /// 是否允许上传附件
        /// 
        public bool CanPostAttach
        {
            get { return m_canPostAttach; }
            set { m_canPostAttach = value; }
        }

        private int m_maxAttachSize;
        /// 
        /// 单个附件大小
        /// 
        public int MaxAttachSize
        {
            get { return m_maxAttachSize; }
            set { m_maxAttachSize = value; }
        }

        private string m_errMessage;
        /// 
        /// 错误信息
        /// 
        public string ErrMessage
        {
            get { return m_errMessage; }
            set { m_errMessage = value; }
        }

        private int m_maxAttachments;
        /// 
        /// 最大允许上传的附件数
        /// 
        public int Maxattachments
        {
            get { return m_maxAttachments; }
            set { m_maxAttachments = value; }
        }
    }
    #endregion

 
     如果一切顺利,客户端会获取相应的UploadSetInfo实例信息来进行SL插件的信息绑定,也就是之前第
二张图中所说的信息内容(红框部分)。

     如果当前用户通过验证,就可以通过SL上传附件了,因为用户上传的附件要进行实时统计,以即时更新
已上传附件的总和大小,来防止用户上传过量的附件),所以我在打开文件对话框事件中加入了到已上传
附件大小的统计以便进行控件:
/// 
/// 选择文件对话框事件
/// 
/// 
/// 
private void SelectFilesButton_Click(object sender, RoutedEventArgs e)
{
    if (AttachmentList.Count >= _maxAttachments)
    {
        ShowMessageBox("\r\n您上传的文件数已达到系统规定的上限: " + _maxAttachments + ".");
        return;
    }

    OpenFileDialog ofd = new OpenFileDialog();
    ofd.Multiselect = true;

    try
    {
        if(!string.IsNullOrEmpty(_fileFilter))
            ofd.Filter = _fileFilter;
    }
    catch(ArgumentException ex)
    {
        ShowMessageBox("错误的文件过滤配置: " + ex.Message);
    }

    if (ofd.ShowDialog() == true)
    {
        if (filecount == 0)
            filecount = AttachmentList.Count;

        foreach (FileInfo file in ofd.Files)
        {
            if ((filecount + 1) > _maxAttachments)
            {
                ShowMessageBox("\r\n您上传的文件数已达到系统规定的上限: " + _maxAttachments + ".");
                return;
            }
            filecount++;

            string fileName = file.Name;
            UserFile userFile = new UserFile();
            userFile.FileName = file.Name;
            userFile.FileStream = file.OpenRead();
            userFile.ViewStream = file.OpenRead();

            //总上传值在规定范围内时
            if (_todayAttachSize  _maxFileSize)
            {
                ShowMessageBox("\r\n当前附件大小: " + Math.Round((decimal)userFile.FileStream.Length / 1024 / 1024, 2)
                + " MB, 而单个附件允许最大尺寸为: " + Math.Round((decimal)_maxFileSize / 1024 / 1024, 2) + " MB.\r\n");
                break;
            }

            //向文件列表中添加文件信息
            _files.Add(userFile);
            _wantUploadSize += userFile.FileStream.Length;  
        }
    }
}       

    
     这样就从选择附件方面杜绝了上述情况的发生。    
    
     之后,当用户选择了上传的附件,并加载到上传列表后点击上传按钮时,SL会将当前要上传的文件进行
切块(4 * 4096 byte),并分块上传,代码如下:
 
/// 
/// 上传文件
/// 
private void UploadAdvanced()
{
    
    byte[] buffer = new byte;
    int bytesRead = _file.FileStream.Read(buffer, 0, buffer.Length);

    //文件是否上传完毕?
    if (bytesRead != 0)
    {
        _dataSent += bytesRead;

        if (_dataSent == _dataLength)
            _lastChunk = true;//是否是最后一块数据,这样WCF会在服务端根据该信息来决定是否对临时文件重命名

        //上传当前数据块
        _client.StoreFileAdvancedAsync(_file.FileName, buffer, bytesRead, _initParams, _firstChunk, _lastChunk, 
                 Utils.GetCredentialInfo());

        //在第一条消息之后一直为false
        _firstChunk = false;

        //通知上传进度修改
        OnProgressChanged();
    }
    else
    {
        //当上传完毕后
        _file.FileStream.Dispose();
        _file.FileStream.Close();

        _client.ChannelFactory.Close();          
    }
}

       
     注意上面的StoreFileAdvancedAsync方法就是要请求的服务端代码,下面即是其服务端代码:     

 /// 
 /// 上传附件
 /// 
 /// 文件名称
 /// 上传的字节数据
 /// 数据长度
 /// 上传参数
 /// 是否第一块数据
 /// 是否最后一块数据
 
 public AttachmentInfo StoreFileAdvanced(string fileName, byte[] data, int dataLength, string parameters, 
                                        bool firstChunk, bool lastChunk, CredentialInfo creinfo)
 {
     if (AuthenticateUser(creinfo))
     {
         UploadSetInfo uploadSetInfo = GetAttachmentUploadSet(creinfo);
         string fileextname = Utils.CutString(fileName, fileName.LastIndexOf(".") + 1).ToLower();

         if (uploadSetInfo.CanPostAttach && uploadSetInfo.AttachExtensionsNoSize.IndexOf(fileextname) >= 0 && 
             uploadSetInfo.AttachSize > dataLength && Utils.StrIsNullOrEmpty(uploadSetInfo.ErrMessage))
         {
             string uploadFolder = GetUploadFolder(fileName, creinfo.ForumID.ToString());
             string tempFileName = fileName + _tempExtension;

             if (firstChunk)
             {
                 //删除临时文件
                 if (File.Exists(@HostingEnvironment.ApplicationPhysicalPath + "/upload/temp/" + tempFileName))
                     File.Delete(@HostingEnvironment.ApplicationPhysicalPath + "/upload/temp/" + tempFileName);

                 //删除目录文件
                 if (File.Exists(uploadFolder + "/" + fileName))
                     File.Delete(uploadFolder + "/" + fileName);
             }


             FileStream fs = File.Open(@HostingEnvironment.ApplicationPhysicalPath + "/upload/temp/" + 
                                        tempFileName, FileMode.Append);
             fs.Write(data, 0, dataLength);
             fs.Close();

             if (lastChunk)
             {
                 File.Move(HostingEnvironment.ApplicationPhysicalPath + "/upload/temp/" + tempFileName, 
                                       uploadFolder + "/" + fileName);
                 return Discuz.Forum.Attachments.GetAttachmentInfo(AddAttachment(fileName, creinfo));
             }
         }
     }
     return null;
 }

     上面代码会将客户端发送过来的分块数据进行组装到临时文件中,当该文件的所有分块数据传输完毕,
 就会将该临时文件移动到指定的附件文件夹中,同时删除相应临时数据。大家注意到了,在该方法开始部分
 还调用了AuthenticateUser方法(之前说明),这主要就是对安全性的考虑,必定HTTP是一种无状态协议呀。
 
     上面的代码中这一行判断:


if (uploadSetInfo.CanPostAttach && uploadSetInfo.AttachExtensionsNoSize.IndexOf(fileextname) >= 0 
    && uploadSetInfo.AttachSize > dataLength &&
             Utils.StrIsNullOrEmpty(uploadSetInfo.ErrMessage))             
     即是对当前用户上传信息(包括已上传附件大小,数量等)进行检验,以免出现上传数据超过系统限制
的情况。
     在该方法的最后一行,会调用AddAttachment(fileName, creinfo)来进行相应附件信息的初始化绑定,
因为论坛中每个主题包括回帖都可以有附件,只不过对附件的扩展名和大小会有限制,所以这里通过该方法
进行封装,下面是其核心代码:

 
/// 
/// 添加附件
/// 
/// 文件名称
/// 认证信息
/// 返回当前插入的附件id
private int AddAttachment(string fileName, CredentialInfo creinfo)
{
    string UploadDir = GetUploadFolder(fileName, creinfo.ForumID.ToString());
    AttachmentInfo attachmentinfo = new AttachmentInfo();
    string fileextname = Utils.CutString(fileName, fileName.LastIndexOf(".") + 1).ToLower();
    string newfilename = (Environment.TickCount & int.MaxValue).ToString() + new Random().Next(1000, 9999) 
                       + "." + fileextname;

    try
    {
        // 如果是bmp jpg png图片类型
        if ((fileextname == "bmp" || fileextname == "jpg" || fileextname == "jpeg" || fileextname == "png"))
        {
            if (Discuz.Common.Utils.FileExists(UploadDir + fileName))
            {
                System.Drawing.Image img = System.Drawing.Image.FromFile(UploadDir + fileName);

                if (config.Attachimgmaxwidth > 0 && img.Width > config.Attachimgmaxwidth)
                    attachmentinfo.Sys_noupload = "图片宽度为" + img.Width.ToString() + ", 系统允许的最大宽度为" 
                                   + config.Attachimgmaxwidth.ToString();

                if (config.Attachimgmaxheight > 0 && img.Height > config.Attachimgmaxheight)
                    attachmentinfo.Sys_noupload = "图片高度为" + img.Width.ToString() + ", 系统允许的最大高度为" 
                                   + config.Attachimgmaxheight.ToString();

                if (config.Watermarkstatus == 0)
                    attachmentinfo.Filesize = new FileInfo(UploadDir + fileName).Length;
                else
                {
                    if (config.Watermarktype == 1 && File.Exists(Utils.GetMapPath(BaseConfigs.GetForumPath 
                                           + "watermark/" + config.Watermarkpic)))
                        Discuz.Forum.ForumUtils.AddImageSignPic(img, UploadDir + newfilename, 
                                        Utils.GetMapPath(BaseConfigs.GetForumPath + "watermark/" + config.Watermarkpic), 
                                        config.Watermarkstatus, config.Attachimgquality, config.Watermarktransparency);
                    else
                    {
                        string watermarkText;
                        watermarkText = config.Watermarktext.Replace("{1}", config.Forumtitle);
                        watermarkText = watermarkText.Replace("{2}", "http://" + DNTRequest.GetCurrentFullHost() + "/");
                        watermarkText = watermarkText.Replace("{3}", Utils.GetDate());
                        watermarkText = watermarkText.Replace("{4}", Utils.GetTime());

                        Discuz.Forum.ForumUtils.AddImageSignText(img, UploadDir + newfilename, watermarkText,
                            config.Watermarkstatus, config.Attachimgquality, config.Watermarkfontname, config.Watermarkfontsize);
                    }
                    System.IO.File.Delete(UploadDir + fileName);
                    // 获得加水印后的文件长度
                    attachmentinfo.Filesize = new FileInfo(UploadDir + newfilename).Length;
                }
            }
        }
        else
        {
            System.IO.File.Move(UploadDir + fileName, UploadDir + newfilename);
            attachmentinfo.Filesize = new FileInfo(UploadDir + newfilename).Length;
        }
    }
    catch{}

    if (Discuz.Common.Utils.FileExists(UploadDir + fileName))
    {
        attachmentinfo.Filesize = new FileInfo(UploadDir + fileName).Length;
        attachmentinfo.Filename = GetDirInfo(fileName, creinfo.ForumID.ToString()) + fileName;
    }

    if (Discuz.Common.Utils.FileExists(UploadDir + newfilename))
    {
        attachmentinfo.Filesize = new FileInfo(UploadDir + newfilename).Length;
        attachmentinfo.Filename = GetDirInfo(newfilename, creinfo.ForumID.ToString()) + newfilename;
    }
    
    attachmentinfo.Uid = creinfo.UserID;
    attachmentinfo.Description = fileextname;
    attachmentinfo.Filetype = GetContentType(fileextname);
    attachmentinfo.Attachment = fileName;
    attachmentinfo.Downloads = 0;
    attachmentinfo.Postdatetime = DateTime.Now.ToString();
    attachmentinfo.Sys_index = 0;

    return Discuz.Data.DatabaseProvider.GetInstance().CreateAttachment(attachmentinfo);
}

 
    通过上面的方法就实现了将附件与相应的主题进行绑定功能同时为相应的图片附件加上水印。   
    
    到这里,主要的代码就介绍的差不多了。
   
    当用户完成上传之后,点击“返回”按钮时,会触发下面事件:   

/// 
/// 返回按钮事件
/// 
/// 
/// 
private void FinishUpload_Click(object sender, RoutedEventArgs e)
{
    GetAttachmentList();
}


public void GetAttachmentList()
{
    StringBuilder sb_attachments = new StringBuilder();
    sb_attachments.Append("[");
    foreach (AttachmentInfo attachmentInfo in AttachmentList)
    {
        sb_attachments.Append(string.Format("{{'aid' : {0}, 'attachment' : '{1}', 'description' : '{2}',  'filename' : 
                        '{3}', 'filesize' :{4}, 'filetype' : '{5}', 'Uid' : {6}}},",
            attachmentInfo.Aid,
            attachmentInfo.Attachment,
            attachmentInfo.Description.Trim(),
            attachmentInfo.Filename.Trim(),
            attachmentInfo.Filesize,
            attachmentInfo.Filetype,
            attachmentInfo.Uid
            ));
    }
    if (sb_attachments.ToString().EndsWith(","))
        sb_attachments.Remove(sb_attachments.Length - 1, 1);

    sb_attachments.Append("]");

    //调用js端注册事件
    javaScriptableObject.OnUploadAttchmentList(JsonCharFilter(sb_attachments.ToString()));
}

 
     GetAttachmentList方法会调用页面的JS事件并将“已上传”的数据发给WEB页面,而相应的页面
事件绑定代码如下(文件位于Discuz.Web\templates\default_postattachments.htm):
function onLoad(plugin, userContext, sender) {
     //只读属性,标识 Silverlight 插件是否已经加载。
     if (sender.getHost().IsLoaded) {
         $("MultiUploadFile").content.JavaScriptObject.UploadAttchmentList = getAttachmentList;         
     }
 }
 
//获取silverlight插件已经上传的附件列表
function getAttachmentList(sender, args) {
     var attachment = args.AttchmentList;
     if (isUndefined(attachment) || attachment == '[]') {
         BOX_remove('silverlightControlHost');
         return;
     }
     var attachmentList = eval("(" + attachment + ")");

     BOX_remove('silverlightControlHost');
     addAttachUploaded(attachmentList);     
 }

 
     这样一个SL多文件上传插件就算基本完成了。当然我还写了“缩略图”功能,因为代码很简单就不
多说了。
    
     好了,今天的内容就先到这里了。
     原文链接: http://www.cnblogs.com/daizhj/archive/2009/04/07/1431090.html
     作者: daizhj, 代震军
     Tags: silverlight,discuznt,uploadfile,文件上传  
     网址: http://daizhj.cnblogs.com/  


来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: DiscuzNT使用Silverlight进行多文件上传