@@ -15,11 +15,13 @@ type ProgressReader struct {
1515 Total int64
1616 Current int64
1717 FilePath string
18- UpdateInterval int64 // 更新间隔,单位秒
18+ UpdateInterval int64 // 秒
1919 lastUpdatedTime int64
2020}
2121
22- // Read 实现了 io.Reader 接口
22+ var gitversion string
23+
24+ // Read 实现 io.Reader 接口
2325func (pr * ProgressReader ) Read (p []byte ) (n int , err error ) {
2426 n , err = pr .Reader .Read (p )
2527 pr .Current += int64 (n )
@@ -33,7 +35,37 @@ func (pr *ProgressReader) Read(p []byte) (n int, err error) {
3335 return
3436}
3537
38+ // URL 和路径下载文件,支持断点续传和分片下载
3639func DownloadFile (url , filePath string ) error {
40+ if gitversion == "" {
41+ gitversion = "NaN"
42+ }
43+ const minChunkSize = 10 * 1024 * 1024 // 10MB
44+ const chunkSize = 2 * 1024 * 1024 // 2MB
45+
46+ resp , err := http .Head (url )
47+ if err != nil {
48+ return fmt .Errorf ("无法获取文件信息: %w" , err )
49+ }
50+ defer resp .Body .Close ()
51+
52+ if resp .StatusCode != http .StatusOK {
53+ return fmt .Errorf ("HEAD 请求失败,状态码: %d" , resp .StatusCode )
54+ }
55+
56+ totalSize := resp .ContentLength
57+ useChunk := totalSize > minChunkSize && ! startsWith (url , "https://bmclapi2.bangbang93.com" )
58+
59+ if useChunk {
60+ fmt .Println ("启用分片下载..." )
61+ return chunkDownload (url , filePath , totalSize , chunkSize )
62+ }
63+
64+ return normalDownload (url , filePath , totalSize )
65+ }
66+
67+ // 普通下载 + 断点续传
68+ func normalDownload (url , filePath string , totalSize int64 ) error {
3769 const maxRetries = 3
3870
3971 fileInfo , err := os .Stat (filePath )
@@ -55,11 +87,10 @@ func DownloadFile(url, filePath string) error {
5587 if start > 0 {
5688 req .Header .Set ("Range" , "bytes=" + strconv .FormatInt (start , 10 )+ "-" )
5789 }
58- var gitversion string
59- req . Header . Set ( "User-Agent" , "AutoInstall" + gitversion )
90+ req . Header . Set ( "User-Agent" , "AutoInstall/" + gitversion )
91+
6092 client := & http.Client {}
6193 resp , err := client .Do (req )
62-
6394 if err != nil {
6495 fmt .Printf ("尝试 %d/%d 下载失败: %v\n " , i + 1 , maxRetries , err )
6596 continue
@@ -72,38 +103,31 @@ func DownloadFile(url, filePath string) error {
72103 }
73104
74105 if start > 0 && resp .StatusCode != http .StatusPartialContent {
75- fmt .Printf ("服务器不支持断点续传,状态码: %d,尝试重新下载 \n " , resp .StatusCode )
106+ fmt .Printf ("服务器不支持断点续传,状态码: %d,重新下载... \n " , resp .StatusCode )
76107 start = 0
77- os .Remove (filePath ) // 删除已存在的文件,重新下载
108+ os .Remove (filePath )
78109 continue
79110 }
80111
81112 if start == 0 && resp .StatusCode != http .StatusOK {
82- fmt .Printf ("尝试 %d/%d, HTTP 状态码: %d\n " , i + 1 , maxRetries , resp .StatusCode )
113+ fmt .Printf ("HTTP 状态码: %d\n " , resp .StatusCode )
83114 continue
84115 }
85116
86- total := resp .ContentLength + start
87- if total <= 0 {
88- fmt .Println ("无法获取文件大小,将不显示进度" )
89- }
90-
91117 var outFile * os.File
92- var createErr error
93118 if start > 0 {
94- outFile , createErr = os .OpenFile (filePath , os .O_APPEND | os .O_WRONLY , 0644 )
119+ outFile , err = os .OpenFile (filePath , os .O_APPEND | os .O_WRONLY , 0644 )
95120 } else {
96- outFile , createErr = os .Create (filePath )
121+ outFile , err = os .Create (filePath )
97122 }
98-
99- if createErr != nil {
100- return fmt .Errorf ("创建/追加文件失败: %w" , createErr )
123+ if err != nil {
124+ return fmt .Errorf ("打开文件失败: %w" , err )
101125 }
102126 defer outFile .Close ()
103127
104128 reader := & ProgressReader {
105129 Reader : resp .Body ,
106- Total : total ,
130+ Total : totalSize ,
107131 Current : start ,
108132 FilePath : filePath ,
109133 UpdateInterval : 3 ,
@@ -119,5 +143,54 @@ func DownloadFile(url, filePath string) error {
119143 return nil
120144 }
121145
122- return fmt .Errorf ("多次尝试下载失败 (共 %d 次)" , maxRetries )
146+ return fmt .Errorf ("多次尝试下载失败" )
147+ }
148+
149+ // 分片下载
150+ func chunkDownload (url , filePath string , totalSize int64 , chunkSize int64 ) error {
151+ outFile , err := os .Create (filePath )
152+ if err != nil {
153+ return fmt .Errorf ("创建文件失败: %w" , err )
154+ }
155+ defer outFile .Close ()
156+
157+ var downloaded int64 = 0
158+ client := & http.Client {}
159+
160+ for downloaded < totalSize {
161+ end := downloaded + chunkSize - 1
162+ if end >= totalSize {
163+ end = totalSize - 1
164+ }
165+
166+ req , _ := http .NewRequest ("GET" , url , nil )
167+ req .Header .Set ("Range" , fmt .Sprintf ("bytes=%d-%d" , downloaded , end ))
168+ req .Header .Set ("User-Agent" , "AutoInstall/" + gitversion )
169+
170+ resp , err := client .Do (req )
171+ if err != nil {
172+ return fmt .Errorf ("分片请求失败: %w" , err )
173+ }
174+ if resp .StatusCode != http .StatusPartialContent {
175+ return fmt .Errorf ("服务器不支持分片下载,状态码: %d" , resp .StatusCode )
176+ }
177+
178+ n , err := io .Copy (outFile , resp .Body )
179+ resp .Body .Close ()
180+ if err != nil {
181+ return fmt .Errorf ("写入分片失败: %w" , err )
182+ }
183+
184+ downloaded += n
185+ percent := float64 (downloaded ) / float64 (totalSize ) * 100
186+ fmt .Printf ("下载进度: %.2f%% (%s)\n " , percent , filePath )
187+ }
188+
189+ fmt .Println ("下载完成!" )
190+ return nil
191+ }
192+
193+ // 判断字符串前缀
194+ func startsWith (s , prefix string ) bool {
195+ return len (s ) >= len (prefix ) && s [:len (prefix )] == prefix
123196}
0 commit comments