超碰人人人人人,亚洲AV午夜福利精品一区二区,亚洲欧美综合区丁香五月1区,日韩欧美亚洲系列

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

C#怎樣實(shí)現(xiàn)http文件下載斷點(diǎn)續(xù)傳

admin
2024年3月12日 23:58 本文熱度 1123
這篇文章主要介紹了C#怎樣實(shí)現(xiàn)文件下載斷點(diǎn)續(xù)傳,對(duì)斷點(diǎn)續(xù)傳感興趣的同學(xué),可以參考下。
?
目錄
  • 前言

  • 文件下載-服務(wù)端

    • 使用a標(biāo)簽提供文件下載

    • 使用Response.TransmitFile提供文件下載

    • 其他方式文件下載

  • 文件下載-客戶端

    • 直接下載

    • 異步下載

    • 斷點(diǎn)續(xù)傳

    • 斷點(diǎn)續(xù)傳(服務(wù)端的支持)

    • 多線程同時(shí)下載(分片下載)

前言

老規(guī)矩,還是從最簡(jiǎn)單粗暴的開(kāi)始。那么多簡(jiǎn)單算簡(jiǎn)單?多粗暴算粗暴?我告訴你可以不寫(xiě)一句代碼,你信嗎?直接把一個(gè)文件往IIS服務(wù)器上一扔,就支持下載。還TM么可以斷點(diǎn)續(xù)傳(IIS服務(wù)端默認(rèn)支持)。

在貼代碼之前先來(lái)了解下什么是斷點(diǎn)續(xù)傳(這里說(shuō)的是下載斷點(diǎn)續(xù)傳)?怎么實(shí)現(xiàn)的斷點(diǎn)續(xù)傳?
斷點(diǎn)續(xù)傳就是下載了一半斷網(wǎng)或者暫停了,然后可以接著下載。不用從頭開(kāi)始下載。

很神奇嗎,其實(shí)簡(jiǎn)單得很,我們想想也是可以想到的。
首先客戶端向服務(wù)端發(fā)送一個(gè)請(qǐng)求(下載文件)。然后服務(wù)端響應(yīng)請(qǐng)求,信息包含文件總大小、文件流開(kāi)始和結(jié)束位置、內(nèi)容大小等。那具體是怎么實(shí)現(xiàn)的呢?
HTTP/1.1有個(gè)頭屬性Range。比如你發(fā)送請(qǐng)求的時(shí)候帶上Range:0-199,等于你是請(qǐng)求0到199之間的數(shù)據(jù)。然后服務(wù)器響應(yīng)請(qǐng)求Content-Range: bytes 0-199/250 ,表示你獲取了0到199之間的數(shù)據(jù),總大小是250。(也就是告訴你還有數(shù)據(jù)沒(méi)有下載完)。

我們來(lái)畫(huà)個(gè)圖吧。

是不是很簡(jiǎn)單?這么神奇的東西也就是個(gè)“約定”而已,也就是所謂的HTTP協(xié)議。
然而,協(xié)議這東西你遵守它就存在,不遵守它就不存在。就像民國(guó)時(shí)期的錢(qián)大家都信它,它就有用。如果大部分人不信它,也就沒(méi)卵用了。
這個(gè)斷點(diǎn)續(xù)傳也是這樣。你服務(wù)端遵守就支持,不遵守也就不支持?jǐn)帱c(diǎn)續(xù)傳。所以我們寫(xiě)下載工具的時(shí)候需要判斷響應(yīng)報(bào)文里有沒(méi)有Content-Range,來(lái)確定是否支持?jǐn)帱c(diǎn)續(xù)傳。
廢話夠多了,下面擼起袖子開(kāi)干。

文件下載-服務(wù)端

使用a標(biāo)簽提供文件下載

利用a標(biāo)簽來(lái)下載文件,也就是我們前面說(shuō)的不寫(xiě)代碼就可以實(shí)現(xiàn)下載。直接把文件往iis服務(wù)器上一扔,然后把鏈接貼到a標(biāo)簽上,完事。

<a href="/新建文件夾2.rar" rel="external nofollow" >下載</a>

簡(jiǎn)單、粗暴不用說(shuō)了。如真得這么好那大家也不會(huì)費(fèi)力去寫(xiě)其他下載邏輯了。這里有個(gè)致命的缺點(diǎn)。這種方式提供的下載不夠安全。誰(shuí)都可以下載,沒(méi)有權(quán)限控制,說(shuō)不定還會(huì)被人文件掃描(好像csdn就出過(guò)這檔子事)。

使用Response.TransmitFile提供文件下載

上面說(shuō)直接a標(biāo)簽提供下載不夠安全。那我們?cè)趺刺峁┫鄬?duì)安全的下載呢。asp.net默認(rèn)App_Data文件夾是不能被直接訪問(wèn)的,那我們把下載文件放這里面。然后下載的時(shí)候我們讀取文件在返回到響應(yīng)流。

//文件下載

public void FileDownload5()

{          

    //前面可以做用戶登錄驗(yàn)證、用戶權(quán)限驗(yàn)證等。

 

    string filename = "大數(shù)據(jù).rar";   //客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/大數(shù)據(jù).rar");//要被下載的文件路徑 

 

    Response.ContentType = "application/octet-stream";  //二進(jìn)制流

    Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);

    Response.TransmitFile(filePath); //將指定文件寫(xiě)入 HTTP 響應(yīng)輸出流

}

其他方式文件下載

在網(wǎng)上搜索C#文件下載一般都會(huì)搜到所謂的“四種方式”。其實(shí)那些代碼并不能拿來(lái)直接使用,有坑的。
第一種:(Response.BinaryWrite)

public void FileDownload2()

{

    string fileName = "新建文件夾2.rar";//客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/新建文件夾2.rar");//要被下載的文件路徑   

 

    Response.ContentType = "application/octet-stream";//二進(jìn)制流

    //通知瀏覽器下載文件而不是打開(kāi)  

    Response.AddHeader("Content-Disposition", "attachment;  filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));

 

    //以字符流的形式下載文件  

    using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))

    {

        Response.AddHeader("Content-Length", fs.Length.ToString());

        //這里容易內(nèi)存溢出

        //理論上數(shù)組最大長(zhǎng)度 int.MaxValue 2147483647 

        //(實(shí)際分不到這么多,不同的程序能分到值也不同,本人機(jī)器,winfrom( 2147483591 相差56)、iis(也差不多2G)、iis Express(只有100多MB))

        byte[] bytes = new byte[(int)fs.Length];

        fs.Read(bytes, 0, bytes.Length);

        Response.BinaryWrite(bytes);

    }

    Response.Flush();

    Response.End();

}

首先數(shù)組最大長(zhǎng)度為int.MaxValue,然后正常程序是不會(huì)分這么大內(nèi)存,很容易搞掛服務(wù)器。(也就是可以下載的文件,極限值最多也就2G不到。)【不推薦】

第二種:(Response.WriteFile)

public void FileDownload3()

{

    string fileName = "新建文件夾2.rar";//客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/新建文件夾2.rar");//要被下載的文件路徑  

    FileInfo fileInfo = new FileInfo(filePath);

    Response.Clear();

    Response.ClearContent();

    Response.ClearHeaders();

    Response.AddHeader("Content-Disposition", "attachment;filename=\"" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8) + "\"");

    Response.AddHeader("Content-Length", fileInfo.Length.ToString());//文件大小

    Response.AddHeader("Content-Transfer-Encoding", "binary");

    Response.ContentType = "application/octet-stream";

    Response.WriteFile(fileInfo.FullName);//大小參數(shù)必須介于零和最大的 Int32 值之間(也就是最大2G,不過(guò)這個(gè)操作非常耗內(nèi)存)

    //這里容易內(nèi)存溢出

    Response.Flush();

    Response.End();

}

問(wèn)題和第一種類似,也是不能下載大于2G的文件。然后下載差不多2G文件時(shí),機(jī)器也是處在被掛的邊緣,相當(dāng)恐怖?!静煌扑]】

第三種:(Response.OutputStream.Write)

public void FileDownload4()

{

    string fileName = "大數(shù)據(jù).rar";//客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/大數(shù)據(jù).rar");//要被下載的文件路徑   

 

    if (System.IO.File.Exists(filePath))

    {

        const long ChunkSize = 102400; //100K 每次讀取文件,只讀取100K,這樣可以緩解服務(wù)器的壓力  

        byte[] buffer = new byte[ChunkSize];

 

        Response.Clear();

        using (FileStream fileStream = System.IO.File.OpenRead(filePath))

        {

            long fileSize = fileStream.Length; //文件大小  

            Response.ContentType = "application/octet-stream"; //二進(jìn)制流

            Response.AddHeader("Content-Disposition", "attachment; filename=" + HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8));

            Response.AddHeader("Content-Length", fileStream.Length.ToString());//文件總大小

            while (fileSize > 0 && Response.IsClientConnected)//判斷客戶端是否還連接了服務(wù)器

            {

                //實(shí)際讀取的大小  

                int readSize = fileStream.Read(buffer, 0, Convert.ToInt32(ChunkSize));

                Response.OutputStream.Write(buffer, 0, readSize);

                Response.Flush();//如果客戶端 暫停下載時(shí),這里會(huì)阻塞。

                fileSize = fileSize - readSize;//文件剩余大小

            }

        }

        Response.Close();

    }

}

這里明顯看到了是在循環(huán)讀取輸出,比較機(jī)智。下載大文件時(shí)沒(méi)有壓力?!就扑]】

第四種:(Response.TransmitFile)
也就上開(kāi)始舉例說(shuō)的那種,下載大文件也沒(méi)有壓力?!就扑]】

public void FileDownload5()

{          

    //前面可以做用戶登錄驗(yàn)證、用戶權(quán)限驗(yàn)證等。

 

    string filename = "大數(shù)據(jù).rar";   //客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/大數(shù)據(jù).rar");//要被下載的文件路徑 

 

    Response.ContentType = "application/octet-stream";  //二進(jìn)制流

    Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);

    Response.TransmitFile(filePath); //將指定文件寫(xiě)入 HTTP 響應(yīng)輸出流

}

文件下載-客戶端

上面實(shí)現(xiàn)了文件下載的服務(wù)端實(shí)現(xiàn),接下來(lái)我們實(shí)現(xiàn)文件下載的客戶端實(shí)現(xiàn)??蛻舳说南螺d可以直接是瀏覽器提供的下載,也可以是迅雷或者我們自己寫(xiě)的下載程序。這里為了更好的分析,我們來(lái)用winfrom程序自己寫(xiě)個(gè)下載客戶端。

直接下載

private async void button1_ClickAsync(object sender, EventArgs e)

{

    using (HttpClient http = new HttpClient())

    {

        var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar");//發(fā)送請(qǐng)求 (鏈接是a標(biāo)簽提供的)

        var contentLength = httpResponseMessage.Content.Headers.ContentLength;//讀取文件大小

        using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())//讀取文件流

        {

            var readLength = 1024000;//1000K  每次讀取大小

            byte[] bytes = new byte[readLength];

            int writeLength;

            using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))//使用追加方式打開(kāi)一個(gè)文件流

            {

                while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)//分塊讀取文件流

                {

                    fs.Write(bytes, 0, writeLength);//追加寫(xiě)入文件

                    contentLength -= writeLength;

                    if (contentLength == 0)//如果寫(xiě)入完成 給出提示

                        MessageBox.Show("下載完成");

                }

            }

        }

    } 

}

看著這么漂亮的代碼,好像沒(méi)問(wèn)題。可現(xiàn)實(shí)往往事與愿違。

我們看到了一個(gè)異?!癝ystem.Net.Http.HttpRequestException:“不能向緩沖區(qū)寫(xiě)入比所配置最大緩沖區(qū)大小 2147483647 更多的字節(jié)?!保裁垂?,又是2147483647這個(gè)數(shù)字。因?yàn)槲覀兿螺d的文件大小超過(guò)了2G,無(wú)法緩沖下載。
可是“緩沖下載”下又是什么鬼。我也不知道。那我們?cè)囋嚳梢躁P(guān)掉這個(gè)東東呢?答案是肯定的。

var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar");//發(fā)送請(qǐng)求

改成下面就可以了

var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar",HttpCompletionOption.ResponseHeadersRead);//響應(yīng)一可用且標(biāo)題可讀時(shí)即應(yīng)完成的操作。 (尚未讀取的內(nèi)容。)

我們看到枚舉HttpCompletionOption的兩個(gè)值。一個(gè)是響應(yīng)讀取內(nèi)容,一個(gè)是響應(yīng)讀取標(biāo)題(也就是Headers里的內(nèi)容)。

【注意】:using (FileStream fs = new FileStream 要放在 while ((writeLength = 的外面,不然可能出現(xiàn)寫(xiě)入文件被占用的異常。

異步下載

我們發(fā)現(xiàn)在下載大文件的時(shí)候會(huì)造成界面假死。這是UI單線程程序的通病。當(dāng)然,這么差的用戶體驗(yàn)是我們不能容忍的。下面我們?yōu)橄螺d開(kāi)一個(gè)線程,避免造成UI線程的阻塞。

/// <summary>

/// 異步下載

/// </summary>

/// <param name="sender"></param>

/// <param name="e"></param>

private async void button2_ClickAsync(object sender, EventArgs e)

{

    //開(kāi)啟一個(gè)異步線程

    await Task.Run(async () =>

    {

        //異步操作UI元素

        label1.Invoke((Action)(() =>

                {

                    label1.Text = "準(zhǔn)備下載...";

                }));

 

        long downloadSize = 0;//已經(jīng)下載大小

        long downloadSpeed = 0;//下載速度

        using (HttpClient http = new HttpClient())

        {

            var httpResponseMessage = await http.GetAsync("http://localhost:813/新建文件夾2.rar", HttpCompletionOption.ResponseHeadersRead);//發(fā)送請(qǐng)求

            var contentLength = httpResponseMessage.Content.Headers.ContentLength;   //文件大小                

            using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())

            {

                var readLength = 1024000;//1000K

                byte[] bytes = new byte[readLength];

                int writeLength;

                var beginSecond = DateTime.Now.Second;//當(dāng)前時(shí)間秒

                //使用追加方式打開(kāi)一個(gè)文件流

                using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))

                {

                    while ((writeLength = stream.Read(bytes, 0, readLength)) > 0)

                    {                 

                         fs.Write(bytes, 0, writeLength);                

                        downloadSize += writeLength;

                        downloadSpeed += writeLength;

                        progressBar1.Invoke((Action)(() =>

                        {

                            var endSecond = DateTime.Now.Second;

                            if (beginSecond != endSecond)//計(jì)算速度

                            {

                                downloadSpeed = downloadSpeed / (endSecond - beginSecond);

                                label1.Text = "下載速度" + downloadSpeed / 1024 + "KB/S";

 

                                beginSecond = DateTime.Now.Second;

                                downloadSpeed = 0;//清空

                            }

                            progressBar1.Value = Math.Max((int)(downloadSize * 100 / contentLength), 1);

                        }));

                    }

               }

                label1.Invoke((Action)(() =>

                {

                    label1.Text = "下載完成";

                }));

            }

        }

    });

}

效果圖:

斷點(diǎn)續(xù)傳

上面的方式我們發(fā)現(xiàn),如果下載到一個(gè)半斷網(wǎng)了下次會(huì)重頭開(kāi)始下載。這和我們今天的主題明顯不符嘛。下面我們開(kāi)始正式進(jìn)入主題文件下載之?dāng)帱c(diǎn)續(xù)傳。把前面我們說(shuō)到的頭屬性Range用起來(lái)。

var request = new HttpRequestMessage { RequestUri = new Uri(url) };

request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【關(guān)鍵點(diǎn)】全局變量記錄已經(jīng)下載了多少,然后下次從這個(gè)位置開(kāi)始下載。

var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

完整代碼:

/// <summary>

/// 是否暫停

/// </summary>

static bool isPause = true;

/// <summary>

/// 下載開(kāi)始位置(也就是已經(jīng)下載了的位置)

/// </summary>

static long rangeBegin = 0; //(當(dāng)然,這個(gè)值也可以存為持久化。如文本、數(shù)據(jù)庫(kù)等)

 

private async void button3_ClickAsync(object sender, EventArgs e)

{

    isPause = !isPause;

    if (!isPause)//點(diǎn)擊下載

    {

        button3.Text = "暫停";

 

        await Task.Run(async () =>

        {

            //異步操作UI元素

            label1.Invoke((Action)(() =>

           {

               label1.Text = "準(zhǔn)備下載...";

           }));

 

            long downloadSpeed = 0;//下載速度

            using (HttpClient http = new HttpClient())

            {

                var url = "http://localhost:813/新建文件夾2.rar";

                var request = new HttpRequestMessage { RequestUri = new Uri(url) };

                request.Headers.Range = new RangeHeaderValue(rangeBegin, null); //【關(guān)鍵點(diǎn)】全局變量記錄已經(jīng)下載了多少,然后下次從這個(gè)位置開(kāi)始下載。

                var httpResponseMessage = await http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);

                var contentLength = httpResponseMessage.Content.Headers.ContentLength;//本次請(qǐng)求的內(nèi)容大小

                if (httpResponseMessage.Content.Headers.ContentRange != null) //如果為空,則說(shuō)明服務(wù)器不支持?jǐn)帱c(diǎn)續(xù)傳

                {

                    contentLength = httpResponseMessage.Content.Headers.ContentRange.Length;//服務(wù)器上的文件大小

                }

 

                using (var stream = await httpResponseMessage.Content.ReadAsStreamAsync())

                {

                    var readLength = 1024000;//1000K

                    byte[] bytes = new byte[readLength];

                    int writeLength;

                    var beginSecond = DateTime.Now.Second;//當(dāng)前時(shí)間秒

                    while ((writeLength = stream.Read(bytes, 0, readLength)) > 0 && !isPause)

                    {

                        //使用追加方式打開(kāi)一個(gè)文件流

                        using (FileStream fs = new FileStream(Application.StartupPath + "/temp.rar", FileMode.Append, FileAccess.Write))

                        {

                            fs.Write(bytes, 0, writeLength);

                        }

                        downloadSpeed += writeLength;

                        rangeBegin += writeLength;

                        progressBar1.Invoke((Action)(() =>

                        {

                            var endSecond = DateTime.Now.Second;

                            if (beginSecond != endSecond)//計(jì)算速度

                            {

                                downloadSpeed = downloadSpeed / (endSecond - beginSecond);

                                label1.Text = "下載速度" + downloadSpeed / 1024 + "KB/S";

 

                                beginSecond = DateTime.Now.Second;

                                downloadSpeed = 0;//清空

                            }

                            progressBar1.Value = Math.Max((int)((rangeBegin) * 100 / contentLength), 1);

                        }));

                    }

 

                    if (rangeBegin == contentLength)

                    {

                        label1.Invoke((Action)(() =>

                        {

                            label1.Text = "下載完成";

                        }));

                    }

                }

            }

        });

    }

    else//點(diǎn)擊暫停

    {

        button3.Text = "繼續(xù)下載";

        label1.Text = "暫停下載";

    }

}

效果圖:

到現(xiàn)在為止,你以為我們的斷點(diǎn)續(xù)傳就完成了嗎?

錯(cuò),你有沒(méi)有發(fā)現(xiàn)我們使用的下載鏈接是a標(biāo)簽的。也就是我們自己寫(xiě)服務(wù)端提供的下載鏈接是不是也可以支持?jǐn)帱c(diǎn)續(xù)傳呢?下面我換個(gè)下載鏈接試試便知。

斷點(diǎn)續(xù)傳(服務(wù)端的支持)

測(cè)試結(jié)果如下:

發(fā)現(xiàn)并不支持?jǐn)帱c(diǎn)續(xù)傳。為什么a標(biāo)簽鏈接可以直接支持,我們寫(xiě)的下載卻不支持呢。

a標(biāo)簽的鏈接指向的直接是iis上的文件(iis默認(rèn)支持),而我們寫(xiě)的卻沒(méi)有做響應(yīng)報(bào)文表頭Range的處理。(沒(méi)想象中的那么智能嘛 >_<)

前面我們說(shuō)過(guò),斷線續(xù)傳是HTTP的一個(gè)協(xié)議。我們遵守它,它就存在,我們不遵守它也就不存在。

那下面我們修改前面的文件下載代碼(服務(wù)端):

public void FileDownload5()

{          

    //前面可以做用戶登錄驗(yàn)證、用戶權(quán)限驗(yàn)證等。

 

    string filename = "大數(shù)據(jù).rar";   //客戶端保存的文件名  

    string filePath = Server.MapPath("/App_Data/大數(shù)據(jù).rar");//要被下載的文件路徑 

 

    var range = Request.Headers["Range"];

    if (!string.IsNullOrWhiteSpace(range))//如果遵守協(xié)議,支持?jǐn)帱c(diǎn)續(xù)傳

    {

        var fileLength = new FileInfo(filePath).Length;//文件的總大小

        long begin;//文件的開(kāi)始位置

        long end;//文件的結(jié)束位置

        long.TryParse(range.Split('=')[1].Split('-')[0], out begin);

        long.TryParse(range.Split('-')[1], out end);

        end = end - begin > 0 ? end : (fileLength - 1);// 如果沒(méi)有結(jié)束位置,那我們讀剩下的全部

 

        //表頭 表明  下載文件的開(kāi)始、結(jié)束位置 和文件總大小

        Response.AddHeader("Content-Range", "bytes " + begin + "-" + end + "/" + fileLength);

        Response.ContentType = "application/octet-stream";

        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);

        Response.TransmitFile(filePath, begin, (end - begin));//發(fā)送 文件開(kāi)始位置讀取的大小

    }

    else

    {

        Response.ContentType = "application/octet-stream";

        Response.AddHeader("Content-Disposition", "attachment;filename=" + filename);

        Response.TransmitFile(filePath);

    }

}

然后再測(cè)試斷點(diǎn)續(xù)傳,完美支持。

多線程同時(shí)下載(分片下載)

文件的斷點(diǎn)續(xù)傳已經(jīng)分析完了。不過(guò)中間有些細(xì)節(jié)的東西你可以根據(jù)實(shí)際需求去完善。如:文件命名、斷點(diǎn)續(xù)傳的文件是否發(fā)生了改變、下載完成后驗(yàn)證文件和服務(wù)器上的是否一致。

還有我們可以根據(jù)表頭屬性Range來(lái)實(shí)現(xiàn)多線程下載,不過(guò)這里就不貼代碼了,貼個(gè)效果圖吧。和上一篇文件上傳里的多線程上傳同理。您也可以根據(jù)提供的demo代碼下載查看,內(nèi)有完整實(shí)現(xiàn)。

以上就是C#怎樣實(shí)現(xiàn)文件下載斷點(diǎn)續(xù)傳的詳細(xì)內(nèi)容。


該文章在 2025/4/30 9:51:42 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved