用C#仿旺旺皮肤实现--[上]

今天,我们要讲的是如何用C#实现旺旺皮肤效果之服务端实现。我们的服务端采用的是WCF,因此我们接下来所讲的都是与WCF相关的。服务端涉及的主要内容是文件上传和下载,我们需要用到一个MessageContract(消息契约),主要由两部分构成,分别是Header、Body,两者构成了SOAP的信封。通常Body用于保存具体的数据内容,而Header则是保存一些上下文信息或关键信息。我们的服务端实现的响应客户文件上传/下载请求,服务端采用流的方式进行文件上/下载,这边除了以流的方式传输以二进制表示的文件内容外,还需要传输一个额外的基于文件属性的信息,比如文件格式、文件大小、回传结果等。如此的功能采用 MessageContract来实现更好不过。以下简单介绍消息契约
MessageContractAttribute--对控制消息头和消息体元素提供了强力支持
  • 所支持的属性:MessageHeaderAttribute、MessageBodyMemberAttribute
  • 用处:添加自定义头(custom headers)、控制消息是否被包装、控制签名与加密。
如下面的代码:
     /// <summary>
    /// 上传文件
    /// </summary>
    [MessageContract(IsWrapped = true, ProtectionLevel = ProtectionLevel.Sign)]
    public class UpFile
    {
        /// <summary>
        /// 文件大小
        /// </summary>
        [MessageHeader(ProtectionLevel = ProtectionLevel.EncryptAndSign)]
        public long FileSize { get; set; }

        /// <summary>
        /// 文件名称
        /// </summary>
        [MessageHeader]
        public string FileName { get; set; }

        /// <summary>
        /// 目录名称
        /// </summary>
        [MessageHeader]
        public string DictoryName { get; set; }

        /// <summary>
        /// 是否拷贝
        /// </summary>
        [MessageHeader]
        public bool IsCopy { get; set; }

        /// <summary>
        /// 文件流
        /// </summary>
        [MessageBodyMember]
        public Stream FileStream { get; set; }

        /// <summary>
        /// 客户代码
        /// </summary>
        [MessageHeader]
        public string CustomerCode { get; set; }
    }
1、 MessageContract--将一个类型转换为SOAP消息,类型可以包含消息头和消息体的元素, 特性含有以下几个属性:
  • IsWrapped:是否为定义的主体成员(一个或者多个)添加一个额外的根节点。
  • WrapperName:根节点的名称。
  • WrapperNamespace:根节点的命名空间。
  • ProtectionLevel:表示保护级别,WCF中通过System.Net.Security.ProtectionLevel枚举定义消息的保护级别。一般有3种可选的保护级别:None、Sign和EncryptAndSign。
2、 MessageHeader--应用到消息契约的域(fields)或者(properties), 为创建自定义头提供了简单的方法,该特性包含下面几个属性:
  • Actor:为一个URI值,表示处理该报头的目标节点。
  • MustUnderstand:bool类型,表明Actor定义的节点是否必须理解并处理该节点。
  • Name:名称。
  • Namespace:命名空间。
  • ProtectionLevel:表示保护级别。
  • Relay:表明该报头是否需要传递到下一个SOAP节点。
3、 MessageBody--应用到消息契约的域(fields)或者属性(properties);能够拥有多个body元素【等价于在操作中拥有多个参数、返回多个复杂类型数据的唯一方法】 该特性包含下面几个属性:
  • Order:Order属性用来控制成员在SOAP主体部分中出现的位置,默认按字母顺序排列。
  • Name:名称。
  • Namespace:命名空间。
  • ProtectionLevel:表示保护级别。

介绍完消息契约的相关特性,接下来我们将详细介绍如何应用。我们的服务端采用三层结构,我们的定义的接口放在IDAL中,为了方便,我们相对应的MessageContract创建在IDAL接口类中。我们需要定义下载模型、下载结果以及上传模型/上传结果。对应的代码如下:[备注:我们这边不需要对应的特性,以下代码相对应减去]

    /// <summary>
    /// 下载模型
    /// </summary>
    [MessageContract]
    public class DownFile
    {
        /// <summary>
        /// 下载文件名称
        /// </summary>  
        [MessageHeader]
        public string FileName { get; set; }

        /// <summary>
        /// 客户代码
        /// </summary>
        [MessageHeader]
        public string CustomerCode { get; set; }
    }
    /// <summary>
    /// 上传结果
    /// </summary>
    [MessageContract]
    public class UpFileResult
    {
        /// <summary>
        /// 是否上传成功
        /// </summary>
        [MessageHeader]
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 返回消息
        /// </summary>
        [MessageHeader]
        public string Message { get; set; }

        /// <summary>
        /// 拷贝文件名称
        /// </summary>
        [MessageHeader]
        public string CopyFileName { get; set; }

    }
    /// <summary>
    /// 上传文件
    /// </summary>
    [MessageContract]
    public class UpFile
    {
        /// <summary>
        /// 文件大小
        /// </summary>
        [MessageHeader]
        public long FileSize { get; set; }

        /// <summary>
        /// 文件名称
        /// </summary>
        [MessageHeader]
        public string FileName { get; set; }

        /// <summary>
        /// 目录名称
        /// </summary>
        [MessageHeader]
        public string DictoryName { get; set; }

        /// <summary>
        /// 是否拷贝
        /// </summary>
        [MessageHeader]
        public bool IsCopy { get; set; }

        /// <summary>
        /// 文件流
        /// </summary>
        [MessageBodyMember]
        public Stream FileStream { get; set; }

        /// <summary>
        /// 客户代码
        /// </summary>
        [MessageHeader]
        public string CustomerCode { get; set; }
    }

    /// <summary>
    /// 下载文件结果
    /// </summary>
    [MessageContract]
    public class DownFileResult
    {
        /// <summary>
        /// 文件大小
        /// </summary>
        [MessageHeader]
        public long FileSize { get; set; }

        /// <summary>
        /// 下载是否成功
        /// </summary>
        [MessageHeader]
        public bool IsSuccess { get; set; }

        /// <summary>
        /// 返回消息
        /// </summary>
        [MessageHeader]
        public string Message { get; set; }

        /// <summary>
        /// 文件流
        /// </summary>
        [MessageBodyMember]
        public Stream FileStream { get; set; }
    }

如果我们想传递多个参数我们可以在相对应的模型中新增自己的参数,这是因为我们的接口不可以定义其他参数,只能接收MessageContract定义的。那我们接口又该如何定义?不急,看下面的代码。

 /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="filestream">上传文件模型</param>
        /// <returns></returns>
        [OperationContract]
        UpFileResult UpLoadFile(UpFile filestream);

        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="downfile">下载文件模型</param>
        /// <returns></returns>
        [OperationContract]
        DownFileResult DownLoadFile(DownFile downfile);

我们在定义接口的时候需要在方法的头部定义一个协议--OperationContract,这样我们的接口才可访问,上方代码中的Result是我们的回调结果值。IDAL定义好了,接下来是接口实现,我们这边直接在BLL层对功能进行实现。这边的实现分为两部分,文件上传和文件下载。

文件上传我们可以根据自己项目的需要定义文件存储路径,注:在指定文件存放路径的时候需要判断文件夹是否存在。然后读取文件流保存,功能实现比较简单,这边直接贴出代码,需要说明的是我们filedata.DictoryName,这边是客户端定义的存储路径,项目需要以"客户代码/用户ID/"形式路径来存储文件。

        /// <summary>
        /// 上传文件
        /// </summary>
        /// <param name="filedata"></param>
        /// <returns></returns>
        public UpFileResult UpLoadFile(UpFile filedata)
        {
            UpFileResult result = new UpFileResult();
            string _baseDirectory = System.AppDomain.CurrentDomain.BaseDirectory;
            string path = @"\" + filedata.DictoryName + @"\";
            string _Path = _baseDirectory + @"\" + string.Format(path, filedata.CustomerCode);
            if (!Directory.Exists(_Path))
            {
                Directory.CreateDirectory(_Path);
            }
            byte[] buffer = new byte[filedata.FileSize];
            FileStream fs = new FileStream(_Path + filedata.FileName, FileMode.Create, FileAccess.Write);
            int count = 0;
            while ((count = filedata.FileStream.Read(buffer, 0, buffer.Length)) > 0)
            {
                fs.Write(buffer, 0, count);
            }
            //清空缓冲区
            fs.Flush();
            //关闭流
            fs.Close();
            string filename = string.Empty;
            if (filedata.IsCopy)
            {
                try
                {
                    string aLastName = filename.Substring(filename.LastIndexOf(".") + 1, (filename.Length - filename.LastIndexOf(".") - 1));   //扩展名
                    filename = DateTime.Now.ToString("yyyyMMddmmffff") + "." + aLastName + "";
                    File.Copy(_Path + filedata.FileName, _Path + filename);
                    result.CopyFileName = filename;
                }
                catch (Exception)
                {
                    result.CopyFileName = "";
                }
            }
            result.IsSuccess = true;
            result.Message = path + filedata.FileName;
            return result;
        }
文件的下载则是上传的逆过程,我们依然需要判断文件是否存在于服务目录。我们需要定义两个方法来实现下载功能,一个是单个文件下载,一个是将对应用户的所上传的文件打包压缩。正常我们客户端是判断用户本地缓存文件夹是否存在文件夹,如果没有存在直接请求下载整个压缩包,反之则判断对应的文件是否存在,不存在单独请求指定文件。这两者的文件下载请求是一样的,不一样的是文件打包压缩。文件下载的实现代码如下, 注:文件流位置需要置为0,不然客户端保存会出现问题。
        /// <summary>
        /// 下载文件
        /// </summary>
        /// <param name="filedata"></param>
        /// <returns></returns>
        public DownFileResult DownLoadFile(DownFile filedata)
        {
            DownFileResult result = new DownFileResult();
            string path = System.AppDomain.CurrentDomain.BaseDirectory + @"\" + string.Format(filedata.FileName, filedata.CustomerCode);
            // string path =  filedata.FileName;

            if (!File.Exists(path))
            {
                result.IsSuccess = false;
                result.FileSize = 0;
                result.Message = "服务器不存在此文件";
                result.FileStream = new MemoryStream();
                return result;
            }
            Stream ms = new MemoryStream();
            FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
            fs.CopyTo(ms);
            ms.Position = 0;  //重要,不为0的话,客户端读取有问题
            result.IsSuccess = true;
            result.FileSize = ms.Length;
            result.FileStream = ms;
            fs.Flush();
            fs.Close();
            return result;
        }

文件解/压缩完整代码如下,这边的文件压缩只压缩指定文件夹目录下一级文件,如果指定文件夹内又有文件夹,那么这个文件夹下的文件不打包压缩。该帮助类需要引用ICSharpCode.SharpZipLib.dll类库。

  /// <summary>
    /// 压缩帮助类
    /// </summary>
    public class ZipHelper
    {
        #region 新创建压缩文件
        /// <summary>
        /// 创建压缩文件  例如CreateZipFile(@"d:\", @"d:\a.zip");
        /// </summary>
        /// <param name="filesPath">文件位置</param>
        /// <param name="zipFilePath">压缩后文件存放位置</param>
        public static bool CreateZipFile(string filesPath, string zipFilePath)
        {
            bool result = true;
            if (!Directory.Exists(filesPath))
            {
                Console.WriteLine("Cannot find directory '{0}'", filesPath);
                result = false;
            }
            try
            {
                string[] filenames = Directory.GetFiles(filesPath);
                using (ZipOutputStream s = new ZipOutputStream(File.Create(zipFilePath)))
                {
                    s.SetLevel(9); // 压缩级别 0-9
                    //s.Password = "123"; //Zip压缩文件密码
                    byte[] buffer = new byte[4096]; //缓冲区大小
                    foreach (string file in filenames)
                    {
                        ZipEntry entry = new ZipEntry(Path.GetFileName(file));
                        entry.DateTime = DateTime.Now;
                        s.PutNextEntry(entry);
                        using (FileStream fs = File.OpenRead(file))
                        {
                            int sourceBytes;
                            do
                            {
                                sourceBytes = fs.Read(buffer, 0, buffer.Length);
                                s.Write(buffer, 0, sourceBytes);
                            } while (sourceBytes > 0);
                        }
                    }
                    s.Finish();
                    s.Close();
                }
                result = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("Exception during processing {0}", ex);
                result = false;
            }
            return result;
        }
        #endregion

        #region 新解压压缩包
        /// <summary>
        /// 解压压缩包 例如 UnZipFile(@"d:\a.zip");
        /// </summary>
        /// <param name="zipFilePath">压缩包位置</param>
        private static bool UnZipFile(string zipFilePath)
        {
            bool result = true;
            if (!File.Exists(zipFilePath))
            {
                Console.WriteLine("Cannot find file '{0}'", zipFilePath);
                result = false;
            }
            try
            {
                using (ZipInputStream s = new ZipInputStream(File.OpenRead(zipFilePath)))
                {
                    ZipEntry theEntry;
                    while ((theEntry = s.GetNextEntry()) != null)
                    {
                        Console.WriteLine(theEntry.Name);
                        string directoryName = Path.GetDirectoryName(theEntry.Name);
                        string fileName = Path.GetFileName(theEntry.Name);
                        // create directory
                        if (directoryName.Length > 0)
                        {
                            Directory.CreateDirectory(directoryName);
                        }
                        if (fileName != String.Empty)
                        {
                            using (FileStream streamWriter = File.Create(theEntry.Name))
                            {
                                int size = 2048;
                                byte[] data = new byte[2048];
                                while (true)
                                {
                                    size = s.Read(data, 0, data.Length);
                                    if (size > 0)
                                    {
                                        streamWriter.Write(data, 0, size);
                                    }
                                    else
                                    {
                                        break;
                                    }
                                }
                            }
                        }
                    }
                }
                result = true;
            }
            catch (Exception ex)
            {
                Console.WriteLine("错误信息 '{0}'", ex.Message);
                result = false;
            }
            return result;
        }
        #endregion
    }

请求下载压缩包的时候需要先判断是否压缩成功再执行操作,调用的方法如下:ZipHelper.CreateZipFile(path, zipedFile);path为需要压缩文件夹位置,zipedFile为压缩后文件存放位置[文件后缀名为.zip]。

至此我们的C#仿旺旺皮肤实现上半部分以介绍完毕,下一篇我们将介绍客户端的实现。

猜你喜欢

转载自blog.csdn.net/qq_21726707/article/details/80740560