C#在线更新程序

参考文章

通用的在线升级程序。更新的内容包括一些dll或exe或、配置文件。升级的大致流程是这样的,从服务器获取一个更新的配置文件,经过核对后如有新的更新,则会从服务器下载相应的文件更新到被升级的程序目录下。如果被升级的程序在升级之前已经启动,程序则会强制关闭它,待到升级完成之后重新启动相应的程序。在升级之前程序会自动备份一次,以防升级失败造成程序不能运行。

新建一窗体应用程序UpdateWindow

数据实体

class FileEntry
    {
        public string FileFullName { get; set; }
        public string Src { get; set; }
        public string Version { get; set; }
        public int Size { get; set; }
        public UpdateOption Option { get; set; }

    }
    public enum UpdateOption
    {
        add,
        del
    }

程序配置类
窗体代码文件中添加using System.Configuration;引用

 

/// <summary>
    /// 程序类,包括系统配置参数
    /// </summary>
    public class AppParameter
    {
        /// <summary>
        /// 备份路径
        /// </summary>
        public static string BackupPath = ConfigurationManager.AppSettings["backupPath"];
        /// <summary>
        /// 更新服务器的URL
        /// </summary>
        public static string ServerUrl = ConfigurationManager.AppSettings["serverURL"];
        /// <summary>
        /// 本地更新文件全名
        /// </summary>
        public static string LocalUpdateConfig = ConfigurationManager.AppSettings["localUpdateConfig"];
        /// <summary>
        /// 版本号
        /// </summary>
        public static string Version = ConfigurationManager.AppSettings["version"];
        /// <summary>
        /// 更新程序路径
        /// </summary>
        public static string LocalPath = AppDomain.CurrentDomain.BaseDirectory;
        /// <summary>
        /// 主程序路径
        /// </summary>
        public static string MainPath = ConfigurationManager.AppSettings["mainPath"];
        /// <summary>
        /// 有否启动主程序
        /// </summary>
        public static bool IsRunning = false;
        /// <summary>
        /// 主程序名
        /// </summary>
        public static List<string> AppNames = ConfigurationManager.AppSettings["appName"].Split(';').ToList();
    }


主程序

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Configuration;
using System.Threading;
using System.IO;
namespace UpdateWindow
{
    public partial class Form1 : Form
    {
        private bool isDelete = true;//是否要删除升级配置
        private bool runningLock = false;//是否正在升级
        private Thread thread;//升级的线程载入窗体时需要检查更新,如果有更新就先进行备份,备份失败的话提示错误
        //退出更新,如果没有更新就提示”暂时无更新“
        public Form1()
        {
            InitializeComponent();
        }
        protected void CloseApp()
        {
            #region  Process Handling

            List<string> processNames = new List<string>();
            string mainPro = string.Empty;
            processNames.AddRange(AppParameter.AppNames);
            for (int i = 0; i < processNames.Count; i++)
            {
                processNames[i] = processNames[i].Substring(processNames[i].LastIndexOf('\\')).Trim('\\').Replace(".exe", "");
            }
            mainPro = processNames.FirstOrDefault();
            AppParameter.IsRunning = ProcessHelper.IsRunningProcess(mainPro);
            if (AppParameter.IsRunning)
            {
                MessageBox.Show("此操作需要关闭要更新的程序,请保存相关数据按确定继续", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                foreach (string item in processNames)
                    ProcessHelper.CloseProcess(item);
            }

            #endregion
        }

        protected void StartApp()
        {
            try
            {
                if (AppParameter.IsRunning) ProcessHelper.StartProcess(AppParameter.AppNames.First());
            }
            catch (Exception ex)
            {

                MessageBox.Show("程序无法启动!" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            if (CheckUpdate())
              {
                  if (!Backup())
                  {
                      MessageBox.Show("备份失败!");
                      btnStart.Enabled = false;
                      isDelete = true;
                      return;
                  }
 
             }
             else
             {
                 MessageBox.Show("暂时无更新");
                 this.btnFinish.Enabled = true;
                 this.btnStart.Enabled = false;
                 isDelete = false;
                 this.Close();
             }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (runningLock)
            {
                if (MessageBox.Show("升级还在进行中,中断升级会导致程序不可用,是否中断",
                          "提示", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) == DialogResult.Yes)
                {
                    if (thread != null) thread.Abort();
                    isDelete = true;
                    AppParameter.IsRunning = false;
                }
                else
                {
                    e.Cancel = true;
                    return;
                }
            }
            if (isDelete) File.Delete(AppParameter.LocalUpdateConfig);

            StartApp();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            btnStart.Enabled = false;
            UpdateApp();
        }

        private void btnFinish_Click(object sender, EventArgs e)
        {
            this.Close();
        }
        /// <summary>
        /// 更新
        /// </summary>
        public void UpdateApp()
        {
            //bool flag = false;
            int successCount = 0;
            int failCount = 0;
            int itemIndex = 0;
            List<FileEntry> list = ConfigHelper.GetUpdateList();
            if (list.Count == 0)
            {
                MessageBox.Show("版本已是最新", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                this.btnFinish.Enabled = true;
                this.btnStart.Enabled = false;
                isDelete = false;
                this.Close();
                return;
            }
            //Thread[] arrThread = new Thread[5];
            //for (int i = 0; i < arrThread.Length; i++)
            //{
            //    arrThread[i] = new Thread(new ThreadStart(delegate()
            //    {
            thread = new Thread(new ThreadStart(delegate
            {
                #region thread method

                FileEntry ent = null;

                while (true)
                {
                    lock (this)
                    {
                        if (itemIndex >= list.Count)
                            break;
                        ent = list[itemIndex];


                        //PrintResultDelegate pd = PrintResult;
                        string msg = string.Empty;
                        if (ExecUpdateItem(ent))
                        {
                            msg = ent.FileFullName + "更新成功";
                            successCount++;
                        }
                        else
                        {
                            msg = ent.FileFullName + "更新失败";
                            failCount++;
                        }

                        if (this.InvokeRequired)
                        {
                            //this.Invoke(pd, msg,
                            //   (int)Math.Ceiling(1f / list.Count * 100));
                            this.Invoke((Action)delegate()
                            {
                                listBox1.Items.Add(msg);
                                int val = (int)Math.Ceiling(1f / list.Count * 100);
                                progressBar1.Value = progressBar1.Value + val > 100 ? 100 : progressBar1.Value + val;
                            });
                        }


                        itemIndex++;
                        if (successCount + failCount == list.Count && this.InvokeRequired)
                        {
                            string finishMessage = string.Empty;
                            //SetButtonDelegate sbtn = SetButton;
                            //this.Invoke(sbtn);
                            if (this.InvokeRequired)
                            {
                                this.Invoke((Action)delegate()
                                {
                                    btnFinish.Enabled = true;
                                });
                            }
                            isDelete = failCount != 0;
                            if (!isDelete)
                            {
                                AppParameter.Version = list.Last().Version;
                                ConfigHelper.UpdateAppConfig("version", AppParameter.Version);
                                finishMessage = "升级完成,程序已成功升级到" + AppParameter.Version;
                            }
                            else
                                finishMessage = "升级完成,但不成功";
                            MessageBox.Show(finishMessage, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
                            runningLock = false;
                        }
                    }
                }
                #endregion
            }));
            //    }));
            //}
            runningLock = true;
            thread.Start();
            //foreach (Thread t in arrThread)
            //{
            //    t.Start();
            //}
        }

        /// <summary>
        /// 执行单个更新
        /// </summary>
        /// <param name="ent"></param>
        /// <returns></returns>
        public bool ExecUpdateItem(FileEntry ent)
        {
            bool result = true;

            try
            {

                if (ent.Option == UpdateOption.del)
                    File.Delete(ent.FileFullName);
                else
                    HttpHelper.DownLoadFile(ent.Src, Path.Combine(AppParameter.MainPath, ent.FileFullName));
            }
            catch { result = false; }
            return result;
        }

        /// <summary>
        /// 检查更新 有则提示用户 确认后下载新的更新配置
        /// </summary>
        /// <returns>用户确认信息</returns>
        public static bool CheckUpdate()
        {
            bool result = false;

            HttpHelper.DownLoadFile(AppParameter.ServerURL, AppParameter.LocalPath + "temp_config.xml");
            if (!File.Exists(AppParameter.LocalUpdateConfig))
                result = true;
            else
            {
                long localSize = new FileInfo(AppParameter.LocalUpdateConfig).Length;
                long tempSize = new FileInfo(AppParameter.LocalPath + "temp_config.xml").Length;

                if (localSize >= tempSize) result = false;

                else result = true;
            }

            if (result)
            {
                if (File.Exists(AppParameter.LocalUpdateConfig)) File.Delete(AppParameter.LocalUpdateConfig);
                File.Copy(AppParameter.LocalPath + "temp_config.xml", AppParameter.LocalUpdateConfig);
            }
            else
                result = false;

            File.Delete(AppParameter.LocalPath + "temp_config.xml");
            return result;
        }

        /// <summary>
        /// 备份
        /// </summary>
        public static bool Backup()
        {
            string sourcePath = Path.Combine(AppParameter.BackupPath, DateTime.Now.ToString("yyyy-MM-dd HH_mm_ss")+"_v_"+AppParameter.Version + ".rar");
            return ZipHelper.Zip(AppParameter.MainPath.Trim() , sourcePath);
        }

        //private void PrintResult(string msg, int val)
        //{
        //    listBox1.Items.Add(msg);
        //    progressBar1.Value = progressBar1.Value + val > 100 ? 100 : progressBar1.Value + val;
        //}

        //private void SetButton()
        //{
        //    btnFinish.Enabled = true;
        //}
    }
    
   }

ConfigHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Configuration;

namespace UpdateWindow
{
    public class ConfigHelper
    {
        public static List<FileEntry> GetUpdateList()
        {
            List<FileEntry> list = new List<FileEntry>();

            XmlDocument xml = new XmlDocument();
            xml.Load(AppParameter.LocalUpdateConfig);

            XmlNodeList nodeList = xml.SelectNodes("/updateFiles/file[@version>" + AppParameter.Version + "]");

            FileEntry ent = null;
            foreach (XmlNode node in nodeList)
            {
                ent = new FileEntry();

                ent.FileFullName = node.Attributes["name"].Value;
                ent.Src = node.Attributes["src"].Value;
                ent.Version = node.Attributes["version"].Value;
                ent.Size =Convert.ToInt32( node.Attributes["size"].Value);
                ent.Option = (UpdateOption)Enum.Parse(typeof(UpdateOption), node.Attributes["option"].Value);


                list.Add(ent);
            }

            return list;
        }

        public static void UpdateAppConfig(string key,string value)
        {
            Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
            config.AppSettings.Settings[key].Value = value;
            config.Save(ConfigurationSaveMode.Full);
            ConfigurationManager.RefreshSection("appSettings");
        }
    }
}


HttpHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;

namespace UpdateWindow
{
    public class HttpHelper
    {
        public static void DownLoadFile(string url,string fileName)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            WebResponse response = request.GetResponse();
            Stream stream = response.GetResponseStream();

            //if (!response.ContentType.ToLower().StartsWith("text/"))
            //{
                byte[] buffer = new byte[1024];
                Stream outStream = CreateFile(fileName);
                Stream inStream = response.GetResponseStream();

                int l;
                do
                {
                    l = inStream.Read(buffer, 0, buffer.Length);
                    if (l > 0)
                        outStream.Write(buffer, 0, l);
                }
                while (l > 0);

                outStream.Close();
                inStream.Close();
            //}

        }

        private static FileStream  CreateFile(string fileName)
        {
            string filePath = Path.GetDirectoryName(fileName);
            if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath);
            return File.Create(fileName);
        }
    }
}


ZipHelper

using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using ICSharpCode.SharpZipLib;
using ICSharpCode.SharpZipLib.Checksums;
using ICSharpCode.SharpZipLib.Zip;

namespace UpdateWindow
{
    public class ZipHelper
    {
        #region 压缩文件
        /// 递归压缩文件夹方法 
        public static bool ZipFileDictory(string FolderToZip, ZipOutputStream s, string ParentFolderName)
        {
            bool res = true;
            string[] folders, filenames;
            ZipEntry entry = null;
            FileStream fs = null;
            Crc32 crc = new Crc32();

            try
            {

                //创建当前文件夹
                entry = new ZipEntry(Path.Combine(ParentFolderName, Path.GetFileName(FolderToZip) + "/")); //加上 “/” 才会当成是文件夹创建
                s.PutNextEntry(entry);
                s.Flush();


                //先压缩文件,再递归压缩文件夹 
                filenames = Directory.GetFiles(FolderToZip);
                foreach (string file in filenames)
                {
                    //打开压缩文件
                    fs = File.OpenRead(file);

                    byte[] buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, buffer.Length);
                    entry = new ZipEntry(Path.Combine(ParentFolderName, Path.GetFileName(FolderToZip) + "/" + Path.GetFileName(file)));

                    entry.DateTime = DateTime.Now;
                    entry.Size = fs.Length;
                    fs.Close();

                    crc.Reset();
                    crc.Update(buffer);

                    entry.Crc = crc.Value;

                    s.PutNextEntry(entry);

                    s.Write(buffer, 0, buffer.Length);
                }
            }
            catch
            {
                res = false;
            }
            finally
            {
                if (fs != null)
                {
                    fs.Close();
                    fs = null;
                }
                if (entry != null)
                {
                    entry = null;
                }
                GC.Collect();
                GC.Collect(1);
            }


            folders = Directory.GetDirectories(FolderToZip);
            foreach (string folder in folders)
            {
                if (!ZipFileDictory(folder, s, Path.Combine(ParentFolderName, Path.GetFileName(FolderToZip))))
                {
                    return false;
                }
            }

            return res;
        }

        /// <summary>
        /// 压缩目录
        /// </summary>
        /// <param name="FolderToZip">待压缩的文件夹,全路径格式</param>
        /// <param name="ZipedFile">压缩后的文件名,全路径格式</param>
        /// <param name="Password"></param>
        /// <returns></returns>
        public static bool ZipFileDictory(string FolderToZip, string ZipedFile, String Password)
        {
            bool res;
            if (!Directory.Exists(FolderToZip))
            {
                return false;
            }

            ZipOutputStream s = new ZipOutputStream(File.Create(ZipedFile));
            s.SetLevel(6);
            s.Password = Password;

            res = ZipFileDictory(FolderToZip, s, "");

            s.Finish();
            s.Close();

            return res;
        }

        /// <summary>
        /// 压缩文件
        /// </summary>
        /// <param name="FileToZip">要进行压缩的文件名</param>
        /// <param name="ZipedFile">压缩后生成的压缩文件名</param>
        /// <param name="Password"></param>
        /// <returns></returns>
        public static bool ZipFile(string FileToZip, string ZipedFile, String Password)
        {
            //如果文件没有找到,则报错
            if (!File.Exists(FileToZip))
            {
                throw new System.IO.FileNotFoundException("指定要压缩的文件: " + FileToZip + " 不存在!");
            }
            //FileStream fs = null;
            FileStream ZipFile = null;
            ZipOutputStream ZipStream = null;
            ZipEntry ZipEntry = null;

            bool res = true;
            try
            {
                ZipFile = File.OpenRead(FileToZip);
                byte[] buffer = new byte[ZipFile.Length];
                ZipFile.Read(buffer, 0, buffer.Length);
                ZipFile.Close();

                ZipFile = File.Create(ZipedFile);
                ZipStream = new ZipOutputStream(ZipFile);
                ZipStream.Password = Password;
                ZipEntry = new ZipEntry(Path.GetFileName(FileToZip));
                ZipStream.PutNextEntry(ZipEntry);
                ZipStream.SetLevel(6);

                ZipStream.Write(buffer, 0, buffer.Length);
            }
            catch
            {
                res = false;
            }
            finally
            {
                if (ZipEntry != null)
                {
                    ZipEntry = null;
                }
                if (ZipStream != null)
                {
                    ZipStream.Finish();
                    ZipStream.Close();
                }
                if (ZipFile != null)
                {
                    ZipFile.Close();
                    ZipFile = null;
                }
                GC.Collect();
                GC.Collect(1);
            }

            return res;
        }

        /// <summary>
        /// 压缩文件 和 文件夹
        /// </summary>
        /// <param name="FileToZip">待压缩的文件或文件夹,全路径格式</param>
        /// <param name="ZipedFile">压缩后生成的压缩文件名,全路径格式</param>
        /// <param name="Password"></param>
        /// <returns></returns>
        public static bool Zip(String FileToZip, String ZipedFile, String Password)
        {
            if (Directory.Exists(FileToZip))
            {
                return ZipFileDictory(FileToZip, ZipedFile, Password);
            }
            else if (File.Exists(FileToZip))
            {
                return ZipFile(FileToZip, ZipedFile, Password);
            }
            else
            {
                return false;
            }
        }

        public static bool Zip(String FileToZip, String ZipedFile)
        {
            return Zip(FileToZip, ZipedFile, "");
        }

        #endregion

        #region 解压文件
        /// <summary>
        /// 解压功能(解压压缩文件到指定目录)
        /// </summary>
        /// <param name="FileToUpZip">待解压的文件</param>
        /// <param name="ZipedFolder">指定解压目标目录</param>
        /// <param name="Password"></param>
        public static void UnZip(string FileToUpZip, string ZipedFolder, string Password)
        {
            if (!File.Exists(FileToUpZip))
            {
                return;
            }

            if (!Directory.Exists(ZipedFolder))
            {
                Directory.CreateDirectory(ZipedFolder);
            }

            ZipInputStream s = null;
            ZipEntry theEntry = null;

            string fileName;
            FileStream streamWriter = null;
            try
            {
                s = new ZipInputStream(File.OpenRead(FileToUpZip));
                s.Password = Password;
                while ((theEntry = s.GetNextEntry()) != null)
                {
                    if (theEntry.Name != String.Empty)
                    {
                        fileName = Path.Combine(ZipedFolder, theEntry.Name);
                        fileName = fileName.Replace("/", "\\");//change by Mr.HopeGi
                        /**/
                        ///判断文件路径是否是文件夹
                        if (fileName.EndsWith("/") || fileName.EndsWith("\\"))
                        {
                            Directory.CreateDirectory(fileName);
                            continue;
                        }

                        streamWriter = File.Create(fileName);
                        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;
                            }
                        }
                    }
                }
            }
            finally
            {
                if (streamWriter != null)
                {
                    streamWriter.Close();
                    streamWriter = null;
                }
                if (theEntry != null)
                {
                    theEntry = null;
                }
                if (s != null)
                {
                    s.Close();
                    s = null;
                }
                GC.Collect();
                GC.Collect(1);
            }
        }

        public static void UnZip(string FileToUpZip, string ZipedFolder)
        {
            UnZip(FileToUpZip, ZipedFolder, "");
        }

        #endregion
    }
}


ProcessHelper

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;


namespace UpdateWindow
{
    public class ProcessHelper
    {
        public static bool IsRunningProcess(string processName)
        {
            Process[] arrPro = GetProcess();
            foreach (Process p in arrPro)
            {
                if (p.ProcessName == processName) return true;
            }
            return false;
        }

        public static void CloseProcess(string processName)
        {
            Process process = Process.GetProcessesByName(processName).FirstOrDefault();
            process.CloseMainWindow();
            //if (!result) process.Kill();
            if (Process.GetProcessesByName(processName).Length != 0)
                process.Kill();
            //return result;
        }

        public static void StartProcess(string fileName)
        {
            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(fileName);
            process.Start();

        }

        public static Process[] GetProcess(string ip = "")
        {
            if (string.IsNullOrEmpty(ip))
                return Process.GetProcesses();
            return Process.GetProcesses(ip);
        }

    }
}


配置文件

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="backupPath" value="D:\我的文档\Visual Studio 2010\Projects\UpdateWindow\UpdateWindow\backup"/>
    <add key="serverURL" value="D:\我的文档\Visual Studio 2010\Projects\UpdateWindow\UpdateWindow\test"/>
    <add key="localUpdateConfig" value="D:\我的文档\Visual Studio 2010\Projects\UpdateWindow\UpdateWindow\bin\Debug\updateconfig.xml"/>
    <add key="version" value="2"/>
    <add key="mainPath" value="D:\我的文档\Visual Studio 2010\Projects\UpdateWindow\UpdateWindow\bin\Debug"/>
    <add key="appName" value="D:\我的文档\Visual Studio 2010\Projects\UpdateWindow\UpdateWindow\bin\Debug\UpdateWindow.exe"/>
  </appSettings>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>


服务器下载的更新配置文件

<?xml version="1.0" encoding="utf-8"?>
<updateFiles>
    <file name="doc\clientaccesspolicy.xml"  src="http://localhost:8015/clientaccesspolicy.xml" version="3" size="1394007" option="add"/>
    <file name="doc\CnemcWebService.asmx"  src="http://localhost:8015/CnemcWebService.asmx" version="3" size="1394007" option="add"/>
    <file name="doc\default.aspx"  src="http://localhost:8015/default.aspx" version="3" size="1191936" option="add"/>
    <file name="doc\Silverlight.js"  src="http://localhost:8015/Silverlight.js" version="3" size="1191936" option="add"/>
    <file name="doc\index.html"  src="http://localhost:8015/index.html" version="3" size="1191936" option="add"/>
    <file name="Index.aspx"  src="http://localhost:8015/Index.aspx" version="3" size="14336" option="add"/>
<file name="ClientBin\Images\Login\CNEMCLogo.png"  src="http://localhost:8015/ClientBin/Images/Login/CNEMCLogo.png" version="3" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\back.jpg"  src="http://localhost:8015/ClientBin/Images/MainVisual/back.jpg" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\Flag.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/Flag.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\help_icon.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/help_icon.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\help_icon_big.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/help_icon_big.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\loading.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/loading.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\logout_icon.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/logout_icon.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\message_icon.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/message_icon.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\point.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/point.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\profile_edit_icon.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/profile_edit_icon.png" version="4" size="14336" option="add"/>
<file name="ClientBin\Images\MainVisual\square.png"  src="http://localhost:8015/ClientBin/Images/MainVisual/square.png" version="4" size="14336" option="add"/>
</updateFiles>



 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值