<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>运达&#039;s  blog &#187; Go</title>
	<atom:link href="https://www.yunda51.com/?cat=177&#038;feed=rss2" rel="self" type="application/rss+xml" />
	<link>https://www.yunda51.com</link>
	<description>运达的博客</description>
	<lastBuildDate>Wed, 12 Nov 2025 07:58:26 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=4.0.19</generator>
	<item>
		<title>基于 Golang 实现大文件断点续传</title>
		<link>https://www.yunda51.com/?p=2027</link>
		<comments>https://www.yunda51.com/?p=2027#comments</comments>
		<pubDate>Wed, 12 Nov 2025 07:56:49 +0000</pubDate>
		<dc:creator><![CDATA[运达]]></dc:creator>
				<category><![CDATA[Go]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[断点续传]]></category>
		<category><![CDATA[服务器]]></category>

		<guid isPermaLink="false">http://www.yunda51.com/?p=2027</guid>
		<description><![CDATA[最近工作中遇到一个实际问题：在将 BPF 数据转换为 Entier 格式时，生成的文件体积异常庞大，部分文件甚<a href="https://www.yunda51.com/?p=2027" class="read-more">Continue Reading</a>]]></description>
				<content:encoded><![CDATA[<p>最近工作中遇到一个实际问题：在将 BPF 数据转换为 Entier 格式时，生成的文件体积异常庞大，部分文件甚至达到 10G 级别。这类大文件上传至服务器时，不仅会占用大量带宽，还需耗费极长时间，一旦中途因网络波动或设备故障中断，就得重新上传，效率极低。<br />
为此，我基于 Golang 开发了一套网页版断点续传工具 —— 通过前端页面交互配合后端逻辑，实现了文件分片传输与断点记录功能。即便夜间上传过程中出现中断，次日也能从断点处继续传输，极大提升了大文件上传的便捷性与稳定性。代码本人亲测（我设置了最大可上传单文件大小为20G），可以直接用。废话不多说了，咱们就言归正传，你需要创建main.go和index.html两个文件。<br />
<code>代码如下：main.go</code></p>
<pre class="wp-code-highlight prettyprint">
package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;io&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;os&quot;
	&quot;path/filepath&quot;
	&quot;strconv&quot;
	&quot;sync&quot;
	&quot;time&quot;
)

// 文件上传状态记录
type UploadStatus struct {
	TotalChunks int       `json:&quot;total_chunks&quot;`
	Uploaded    []bool    `json:&quot;uploaded&quot;`
	Filename    string    `json:&quot;filename&quot;`
	UUID        string    `json:&quot;uuid&quot;`
	Path        string    `json:&quot;path&quot;`
	Completed   bool      `json:&quot;completed&quot;`
	Size        int64     `json:&quot;size&quot;`
	UploadedAt  time.Time `json:&quot;uploaded_at&quot;`
}

// 全局上传状态记录
var uploadsMutex sync.Mutex
var uploads = make(map[string]*UploadStatus)

func main() {
	// 创建上传目录
	os.MkdirAll(&quot;/home/datawork/uploads&quot;, 0755)
	os.MkdirAll(&quot;/home/datawork/chunks&quot;, 0755)

	// 注册路由
	http.HandleFunc(&quot;/api/upload&quot;, handleUpload)
	http.HandleFunc(&quot;/api/status&quot;, handleStatus)
	http.HandleFunc(&quot;/api/merge&quot;, handleMerge)
	http.HandleFunc(&quot;/&quot;, serveIndex)

	// 启动服务器
	fmt.Println(&quot;服务器启动在 http://10.25.77.4:8999&quot;)
	log.Fatal(http.ListenAndServe(&quot;:8999&quot;, nil))
}

// 处理文件块上传
func handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, &quot;方法不允许&quot;, http.StatusMethodNotAllowed)
		return
	}

	// 解析表单数据
	err := r.ParseMultipartForm(32 &lt;&lt; 20) // 32MB 缓冲区
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// 获取文件信息
	file, _, err := r.FormFile(&quot;file&quot;)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	defer file.Close()

	// 获取上传参数
	uuid := r.FormValue(&quot;uuid&quot;)
	chunkIndex := r.FormValue(&quot;chunkIndex&quot;)
	totalChunks := r.FormValue(&quot;totalChunks&quot;)
	filename := r.FormValue(&quot;filename&quot;)
	fileSize := r.FormValue(&quot;fileSize&quot;)

	// 验证参数
	if uuid == &quot;&quot; || chunkIndex == &quot;&quot; || totalChunks == &quot;&quot; || filename == &quot;&quot; || fileSize == &quot;&quot; {
		http.Error(w, &quot;缺少必要参数&quot;, http.StatusBadRequest)
		return
	}

	// 转换参数类型
	index, err := strconv.Atoi(chunkIndex)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	total, err := strconv.Atoi(totalChunks)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	size, err := strconv.ParseInt(fileSize, 10, 64)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	// 创建文件块目录
	chunkDir := filepath.Join(&quot;/home/datawork/chunks&quot;, uuid)
	os.MkdirAll(chunkDir, 0755)

	// 保存文件块
	chunkPath := filepath.Join(chunkDir, fmt.Sprintf(&quot;%d&quot;, index))
	out, err := os.Create(chunkPath)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer out.Close()

	_, err = io.Copy(out, file)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// 更新上传状态
	uploadsMutex.Lock()
	defer uploadsMutex.Unlock()

	// 初始化上传状态（如果不存在）
	if _, exists := uploads[uuid]; !exists {
		uploads[uuid] = &amp;UploadStatus{
			TotalChunks: total,
			Uploaded:    make([]bool, total),
			Filename:    filename,
			UUID:        uuid,
			Path:        filepath.Join(&quot;/home/datawork/uploads&quot;, filename),
			Completed:   false,
			Size:        size,
			UploadedAt:  time.Now(),
		}
	}

	// 标记当前块已上传
	if index &lt; len(uploads[uuid].Uploaded) {
		uploads[uuid].Uploaded[index] = true
	}

	// 检查是否所有块都已上传
	allUploaded := true
	for _, uploaded := range uploads[uuid].Uploaded {
		if !uploaded {
			allUploaded = false
			break
		}
	}

	if allUploaded {
		uploads[uuid].Completed = true
	}

	// 返回当前上传状态
	w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
	json.NewEncoder(w).Encode(uploads[uuid])
}

// 获取上传状态
func handleStatus(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		http.Error(w, &quot;方法不允许&quot;, http.StatusMethodNotAllowed)
		return
	}

	uuid := r.URL.Query().Get(&quot;uuid&quot;)
	if uuid == &quot;&quot; {
		http.Error(w, &quot;缺少uuid参数&quot;, http.StatusBadRequest)
		return
	}

	uploadsMutex.Lock()
	defer uploadsMutex.Unlock()

	if status, exists := uploads[uuid]; exists {
		w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
		json.NewEncoder(w).Encode(status)
	} else {
		http.Error(w, &quot;找不到上传记录&quot;, http.StatusNotFound)
	}
}

// 合并文件块
func handleMerge(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, &quot;方法不允许&quot;, http.StatusMethodNotAllowed)
		return
	}

	uuid := r.FormValue(&quot;uuid&quot;)
	if uuid == &quot;&quot; {
		http.Error(w, &quot;缺少uuid参数&quot;, http.StatusBadRequest)
		return
	}

	uploadsMutex.Lock()
	defer uploadsMutex.Unlock()

	if status, exists := uploads[uuid]; exists {
		if !status.Completed {
			http.Error(w, &quot;文件尚未上传完成&quot;, http.StatusBadRequest)
			return
		}

		// 创建目标文件
		out, err := os.Create(status.Path)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		defer out.Close()

		// 按顺序合并文件块
		chunkDir := filepath.Join(&quot;/home/datawork/chunks&quot;, uuid)
		for i := 0; i &lt; status.TotalChunks; i++ {
			chunkPath := filepath.Join(chunkDir, fmt.Sprintf(&quot;%d&quot;, i))
			chunk, err := os.Open(chunkPath)
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			_, err = io.Copy(out, chunk)
			chunk.Close()
			if err != nil {
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}

			// 删除已合并的块
			os.Remove(chunkPath)
		}

		// 删除临时目录
		os.RemoveAll(chunkDir)

		// 返回成功信息
		w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
		json.NewEncoder(w).Encode(map[string]string{
			&quot;status&quot;: &quot;success&quot;,
			&quot;path&quot;:   status.Path,
		})
	} else {
		http.Error(w, &quot;找不到上传记录&quot;, http.StatusNotFound)
	}
}

// 提供前端页面
func serveIndex(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, &quot;./index.html&quot;)
}

</pre>
<p><code>网页代码如下：index.html</code></p>
<pre class="wp-code-highlight prettyprint">
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;zh-CN&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
    &lt;title&gt;大文件上传与断点续传&lt;/title&gt;
    &lt;script src=&quot;https://cdn.tailwindcss.com&quot;&gt;&lt;/script&gt;
    &lt;link href=&quot;https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css&quot; rel=&quot;stylesheet&quot;&gt;
    
    &lt;script&gt;
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: &#039;#3B82F6&#039;,
                        secondary: &#039;#10B981&#039;,
                        danger: &#039;#EF4444&#039;,
                        warning: &#039;#F59E0B&#039;,
                        dark: &#039;#1F2937&#039;,
                    },
                    fontFamily: {
                        inter: [&#039;Inter&#039;, &#039;system-ui&#039;, &#039;sans-serif&#039;],
                    },
                }
            }
        }
    &lt;/script&gt;
    
    &lt;style type=&quot;text/tailwindcss&quot;&gt;
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .bg-gradient-blue {
                background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
            }
            .shadow-blue {
                box-shadow: 0 10px 25px -5px rgba(59, 130, 246, 0.2);
            }
            .progress-animation {
                transition: width 0.3s ease-in-out;
            }
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body class=&quot;font-inter bg-gray-50 min-h-screen flex flex-col&quot;&gt;
    &lt;!-- 导航栏 --&gt;
    &lt;header class=&quot;bg-gradient-blue text-white shadow-md sticky top-0 z-50&quot;&gt;
        &lt;div class=&quot;container mx-auto px-4 py-4 flex justify-between items-center&quot;&gt;
            &lt;h1 class=&quot;text-2xl font-bold flex items-center&quot;&gt;
                &lt;i class=&quot;fa fa-cloud-upload mr-2&quot;&gt;&lt;/i&gt; 大文件上传系统
            &lt;/h1&gt;
            &lt;nav&gt;
                &lt;ul class=&quot;flex space-x-6&quot;&gt;
                    &lt;li&gt;&lt;a href=&quot;#&quot; class=&quot;hover:text-gray-200 transition-colors duration-300&quot;&gt;&lt;i class=&quot;fa fa-home mr-1&quot;&gt;&lt;/i&gt; 首页&lt;/a&gt;&lt;/li&gt;
                    &lt;li&gt;&lt;a href=&quot;#&quot; class=&quot;hover:text-gray-200 transition-colors duration-300&quot;&gt;&lt;i class=&quot;fa fa-question-circle mr-1&quot;&gt;&lt;/i&gt; 帮助&lt;/a&gt;&lt;/li&gt;
                &lt;/ul&gt;
            &lt;/nav&gt;
        &lt;/div&gt;
    &lt;/header&gt;

    &lt;!-- 主要内容区 --&gt;
    &lt;main class=&quot;flex-grow container mx-auto px-4 py-8&quot;&gt;
        &lt;div class=&quot;max-w-3xl mx-auto bg-white rounded-xl shadow-lg p-6 md:p-8 transform transition-all duration-300 hover:shadow-blue&quot;&gt;
            &lt;div class=&quot;text-center mb-8&quot;&gt;
                &lt;h2 class=&quot;text-[clamp(1.5rem,3vw,2.5rem)] font-bold text-dark mb-2&quot;&gt;上传大文件&lt;/h2&gt;
                &lt;p class=&quot;text-gray-600&quot;&gt;支持超大文件上传和断点续传，最大支持 20GB 文件&lt;/p&gt;
            &lt;/div&gt;

            &lt;!-- 文件上传区域 --&gt;
            &lt;div class=&quot;border-2 border-dashed border-gray-300 rounded-lg p-8 text-center mb-8 hover:border-primary transition-colors duration-300&quot; id=&quot;dropZone&quot;&gt;
                &lt;i class=&quot;fa fa-cloud-upload text-5xl text-gray-400 mb-4&quot;&gt;&lt;/i&gt;
                &lt;h3 class=&quot;text-xl font-semibold text-gray-700 mb-2&quot;&gt;拖放文件到此处上传&lt;/h3&gt;
                &lt;p class=&quot;text-gray-500 mb-4&quot;&gt;或点击选择文件&lt;/p&gt;
                &lt;label for=&quot;fileInput&quot; class=&quot;inline-block bg-primary hover:bg-primary/90 text-white font-medium py-3 px-6 rounded-lg shadow-lg transition-all duration-300 hover:shadow-lg hover:-translate-y-0.5&quot;&gt;
                    &lt;i class=&quot;fa fa-folder-open mr-2&quot;&gt;&lt;/i&gt; 选择文件
                &lt;/label&gt;
                &lt;input type=&quot;file&quot; id=&quot;fileInput&quot; class=&quot;hidden&quot; accept=&quot;*&quot;&gt;
            &lt;/div&gt;

            &lt;!-- 上传进度区域 --&gt;
            &lt;div id=&quot;uploadProgressContainer&quot; class=&quot;hidden&quot;&gt;
                &lt;div class=&quot;flex justify-between items-center mb-2&quot;&gt;
                    &lt;h3 class=&quot;font-semibold text-dark&quot; id=&quot;fileName&quot;&gt;文件名&lt;/h3&gt;
                    &lt;span class=&quot;text-sm text-gray-500&quot; id=&quot;fileSize&quot;&gt;0 MB&lt;/span&gt;
                &lt;/div&gt;
                
                &lt;div class=&quot;w-full bg-gray-200 rounded-full h-3 mb-2&quot;&gt;
                    &lt;div id=&quot;uploadProgressBar&quot; class=&quot;bg-primary h-3 rounded-full progress-animation&quot; style=&quot;width: 0%&quot;&gt;&lt;/div&gt;
                &lt;/div&gt;
                
                &lt;div class=&quot;flex justify-between text-sm mb-4&quot;&gt;
                    &lt;span id=&quot;uploadPercentage&quot;&gt;0%&lt;/span&gt;
                    &lt;span id=&quot;uploadSpeed&quot;&gt;0 KB/s&lt;/span&gt;
                    &lt;span id=&quot;uploadTimeLeft&quot;&gt;计算中...&lt;/span&gt;
                &lt;/div&gt;
                
                &lt;div class=&quot;flex space-x-3&quot;&gt;
                    &lt;button id=&quot;pauseUploadBtn&quot; class=&quot;flex-1 bg-warning hover:bg-warning/90 text-white py-2 px-4 rounded-lg transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed&quot;&gt;
                        &lt;i class=&quot;fa fa-pause mr-2&quot;&gt;&lt;/i&gt; 暂停
                    &lt;/button&gt;
                    &lt;button id=&quot;resumeUploadBtn&quot; class=&quot;flex-1 bg-secondary hover:bg-secondary/90 text-white py-2 px-4 rounded-lg transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed&quot;&gt;
                        &lt;i class=&quot;fa fa-play mr-2&quot;&gt;&lt;/i&gt; 继续
                    &lt;/button&gt;
                    &lt;button id=&quot;cancelUploadBtn&quot; class=&quot;flex-1 bg-danger hover:bg-danger/90 text-white py-2 px-4 rounded-lg transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed&quot;&gt;
                        &lt;i class=&quot;fa fa-times mr-2&quot;&gt;&lt;/i&gt; 取消
                    &lt;/button&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;!-- 历史上传记录 --&gt;
            &lt;div class=&quot;mt-10&quot;&gt;
                &lt;h3 class=&quot;text-xl font-semibold text-dark mb-4 flex items-center&quot;&gt;
                    &lt;i class=&quot;fa fa-history mr-2 text-primary&quot;&gt;&lt;/i&gt; 上传历史
                &lt;/h3&gt;
                &lt;div id=&quot;uploadHistory&quot; class=&quot;space-y-4&quot;&gt;
                    &lt;!-- 上传历史将通过JavaScript动态添加 --&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/main&gt;

    &lt;!-- 页脚 --&gt;
    &lt;footer class=&quot;bg-dark text-white py-6 mt-10&quot;&gt;
        &lt;div class=&quot;container mx-auto px-4 text-center&quot;&gt;
            &lt;p&gt;© 2023 大文件上传系统 | 支持断点续传和超大文件上传&lt;/p&gt;
            &lt;div class=&quot;mt-2 text-sm text-gray-400&quot;&gt;
                &lt;p&gt;系统支持最大 20GB 文件上传，适用于各种大文件传输场景&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/footer&gt;

    &lt;script&gt;
        // 全局变量
        let file = null;
        let uploadStatus = null;
        let isUploading = false;
        let isPaused = false;
        let chunks = [];
        let currentChunkIndex = 0;
        let totalChunks = 0;
        let uploadStartTime = 0;
        let uploadedBytes = 0;
        let chunkSize = 10 * 1024 * 1024; // 10MB 分块大小
        
        // DOM 元素
        const dropZone = document.getElementById(&#039;dropZone&#039;);
        const fileInput = document.getElementById(&#039;fileInput&#039;);
        const uploadProgressContainer = document.getElementById(&#039;uploadProgressContainer&#039;);
        const uploadProgressBar = document.getElementById(&#039;uploadProgressBar&#039;);
        const uploadPercentage = document.getElementById(&#039;uploadPercentage&#039;);
        const uploadSpeed = document.getElementById(&#039;uploadSpeed&#039;);
        const uploadTimeLeft = document.getElementById(&#039;uploadTimeLeft&#039;);
        const fileName = document.getElementById(&#039;fileName&#039;);
        const fileSize = document.getElementById(&#039;fileSize&#039;);
        const pauseUploadBtn = document.getElementById(&#039;pauseUploadBtn&#039;);
        const resumeUploadBtn = document.getElementById(&#039;resumeUploadBtn&#039;);
        const cancelUploadBtn = document.getElementById(&#039;cancelUploadBtn&#039;);
        const uploadHistory = document.getElementById(&#039;uploadHistory&#039;);
        
        // 初始化
        document.addEventListener(&#039;DOMContentLoaded&#039;, () =&gt; {
            // 初始化拖拽上传
            initDragDrop();
            
            // 初始化文件选择
            fileInput.addEventListener(&#039;change&#039;, handleFileSelect);
            
            // 初始化按钮事件
            pauseUploadBtn.addEventListener(&#039;click&#039;, pauseUpload);
            resumeUploadBtn.addEventListener(&#039;click&#039;, resumeUpload);
            cancelUploadBtn.addEventListener(&#039;click&#039;, cancelUpload);
            
            // 加载上传历史
            loadUploadHistory();
        });
        
        // 初始化拖拽上传
        function initDragDrop() {
            // 阻止默认行为
            [&#039;dragenter&#039;, &#039;dragover&#039;, &#039;dragleave&#039;, &#039;drop&#039;].forEach(eventName =&gt; {
                dropZone.addEventListener(eventName, preventDefaults, false);
                document.body.addEventListener(eventName, preventDefaults, false);
            });
            
            // 添加拖拽样式
            [&#039;dragenter&#039;, &#039;dragover&#039;].forEach(eventName =&gt; {
                dropZone.addEventListener(eventName, highlight, false);
            });
            
            [&#039;dragleave&#039;, &#039;drop&#039;].forEach(eventName =&gt; {
                dropZone.addEventListener(eventName, unhighlight, false);
            });
            
            // 处理文件拖放
            dropZone.addEventListener(&#039;drop&#039;, handleDrop, false);
        }
        
        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }
        
        function highlight() {
            dropZone.classList.add(&#039;border-primary&#039;);
            dropZone.classList.add(&#039;bg-primary/5&#039;);
        }
        
        function unhighlight() {
            dropZone.classList.remove(&#039;border-primary&#039;);
            dropZone.classList.remove(&#039;bg-primary/5&#039;);
        }
        
        function handleDrop(e) {
            const dt = e.dataTransfer;
            const droppedFile = dt.files[0];
            
            if (droppedFile) {
                handleFile(droppedFile);
            }
        }
        
        // 处理文件选择
        function handleFileSelect(e) {
            const selectedFile = e.target.files[0];
            
            if (selectedFile) {
                handleFile(selectedFile);
            }
        }
        
        // 处理文件
        function handleFile(selectedFile) {
            file = selectedFile;
            
            // 显示文件信息
            fileName.textContent = file.name;
            fileSize.textContent = formatFileSize(file.size);
            
            // 显示进度条
            uploadProgressContainer.classList.remove(&#039;hidden&#039;);
            
            // 检查是否有之前的上传记录
            checkUploadStatus(file);
        }
        
        // 检查上传状态
        function checkUploadStatus(file) {
            // 生成文件唯一标识符
            const fileUUID = generateUUID(file);
            
            // 从本地存储获取上传状态
            const savedStatus = localStorage.getItem(`uploadStatus_${fileUUID}`);
            
            if (savedStatus) {
                uploadStatus = JSON.parse(savedStatus);
                
                // 检查服务器上的状态
                fetch(`/api/status?uuid=${uploadStatus.UUID}`)
                    .then(response =&gt; response.json())
                    .then(data =&gt; {
                        // 更新上传状态
                        uploadStatus = data;
                        
                        // 更新UI
                        updateProgressUI();
                        
                        // 显示上传历史
                        addToUploadHistory(uploadStatus);
                        
                        // 询问用户是否继续上传
                        if (!uploadStatus.Completed) {
                            if (confirm(`检测到未完成的上传任务，是否继续上传？\n已上传: ${calculateProgress(uploadStatus)}%`)) {
                                startUpload();
                            }
                        }
                    })
                    .catch(error =&gt; {
                        console.error(&#039;获取上传状态失败:&#039;, error);
                        // 状态可能已过期，重新开始上传
                        uploadStatus = null;
                        startUpload();
                    });
            } else {
                // 开始新的上传
                uploadStatus = {
                    UUID: fileUUID,
                    Filename: file.name,
                    TotalChunks: Math.ceil(file.size / chunkSize),
                    Uploaded: Array(Math.ceil(file.size / chunkSize)).fill(false),
                    Completed: false,
                    Size: file.size,
                    UploadedAt: new Date().toISOString()
                };
                
                // 更新UI
                updateProgressUI();
                
                // 开始上传
                startUpload();
            }
        }
        
        // 开始上传
        function startUpload() {
            if (!file) return;
            
            isUploading = true;
            isPaused = false;
            
            // 更新按钮状态
            pauseUploadBtn.disabled = false;
            resumeUploadBtn.disabled = true;
            cancelUploadBtn.disabled = false;
            
            // 记录开始时间
            uploadStartTime = Date.now();
            uploadedBytes = 0;
            
            // 准备分块
            prepareChunks();
            
            // 开始上传第一个块
            uploadNextChunk();
        }
        
        // 准备文件分块
        function prepareChunks() {
            chunks = [];
            totalChunks = Math.ceil(file.size / chunkSize);
            
            for (let i = 0; i &lt; totalChunks; i++) {
                const start = i * chunkSize;
                const end = Math.min(start + chunkSize, file.size);
                const chunk = file.slice(start, end);
                
                chunks.push({
                    index: i,
                    blob: chunk,
                    size: chunk.size,
                    uploaded: uploadStatus ? uploadStatus.Uploaded[i] : false
                });
            }
            
            // 设置当前块索引为第一个未上传的块
            currentChunkIndex = 0;
            while (currentChunkIndex &lt; chunks.length &amp;&amp; chunks[currentChunkIndex].uploaded) {
                currentChunkIndex++;
                uploadedBytes += chunks[currentChunkIndex - 1].size;
            }
        }
        
        // 上传下一个块
        function uploadNextChunk() {
            if (currentChunkIndex &gt;= chunks.length || isPaused || !isUploading) {
                if (currentChunkIndex &gt;= chunks.length &amp;&amp; isUploading) {
                    // 所有块都已上传，检查是否所有块都成功
                    checkAllChunksUploaded();
                }
                return;
            }
            
            const chunk = chunks[currentChunkIndex];
            
            // 创建表单数据
            const formData = new FormData();
            formData.append(&#039;file&#039;, chunk.blob);
            formData.append(&#039;uuid&#039;, uploadStatus.UUID);
            formData.append(&#039;chunkIndex&#039;, chunk.index);
            formData.append(&#039;totalChunks&#039;, totalChunks);
            formData.append(&#039;filename&#039;, file.name);
            formData.append(&#039;fileSize&#039;, file.size);
            
            // 上传块
            const xhr = new XMLHttpRequest();
            xhr.open(&#039;POST&#039;, &#039;/api/upload&#039;, true);
            
            // 监听进度
            xhr.upload.addEventListener(&#039;progress&#039;, (e) =&gt; {
                if (e.lengthComputable) {
                    // 计算当前块的上传进度
                    const chunkProgress = e.loaded / e.total;
                    
                    // 更新总进度
                    const totalProgress = (uploadedBytes + e.loaded) / file.size;
                    
                    // 更新UI
                    updateProgressUI(totalProgress);
                    
                    // 计算上传速度和剩余时间
                    const elapsedTime = (Date.now() - uploadStartTime) / 1000; // 秒
                    const uploadSpeedValue = (uploadedBytes + e.loaded) / elapsedTime; // 字节/秒
                    
                    // 更新速度和剩余时间
                    uploadSpeed.textContent = formatFileSize(uploadSpeedValue) + &#039;/s&#039;;
                    
                    if (uploadSpeedValue &gt; 0) {
                        const remainingBytes = file.size - (uploadedBytes + e.loaded);
                        const remainingSeconds = remainingBytes / uploadSpeedValue;
                        uploadTimeLeft.textContent = formatTime(remainingSeconds);
                    }
                }
            });
            
            // 上传完成
            xhr.onload = () =&gt; {
                if (xhr.status === 200) {
                    // 更新上传状态
                    uploadedBytes += chunk.size;
                    chunks[currentChunkIndex].uploaded = true;
                    
                    // 更新服务器状态
                    uploadStatus.Uploaded[currentChunkIndex] = true;
                    
                    // 保存状态到本地存储
                    localStorage.setItem(`uploadStatus_${uploadStatus.UUID}`, JSON.stringify(uploadStatus));
                    
                    // 更新历史记录
                    addToUploadHistory(uploadStatus);
                    
                    // 上传下一个块
                    currentChunkIndex++;
                    uploadNextChunk();
                } else {
                    console.error(&#039;上传失败:&#039;, xhr.status, xhr.statusText);
                    // 重试当前块
                    setTimeout(() =&gt; uploadNextChunk(), 1000);
                }
            };
            
            // 上传错误
            xhr.onerror = () =&gt; {
                console.error(&#039;上传错误:&#039;, xhr.status, xhr.statusText);
                // 重试当前块
                setTimeout(() =&gt; uploadNextChunk(), 1000);
            };
            
            // 发送请求
            xhr.send(formData);
        }
        
        // 检查是否所有块都已上传
        function checkAllChunksUploaded() {
            let allUploaded = true;
            
            for (let i = 0; i &lt; chunks.length; i++) {
                if (!chunks[i].uploaded) {
                    allUploaded = false;
                    break;
                }
            }
            
            if (allUploaded) {
                // 所有块都已上传，合并文件
                mergeChunks();
            } else {
                // 有块上传失败，重新上传
                uploadNextChunk();
            }
        }
        
        // 合并文件块
        function mergeChunks() {
            // 显示正在合并的消息
            uploadSpeed.textContent = &#039;正在合并文件...&#039;;
            uploadTimeLeft.textContent = &#039;请稍候...&#039;;
            
            // 发送合并请求
            fetch(&#039;/api/merge&#039;, {
                method: &#039;POST&#039;,
                headers: {
                    &#039;Content-Type&#039;: &#039;application/x-www-form-urlencoded&#039;,
                },
                body: `uuid=${uploadStatus.UUID}`,
            })
            .then(response =&gt; response.json())
            .then(data =&gt; {
                if (data.status === &#039;success&#039;) {
                    // 更新上传状态
                    uploadStatus.Completed = true;
                    localStorage.setItem(`uploadStatus_${uploadStatus.UUID}`, JSON.stringify(uploadStatus));
                    
                    // 更新UI
                    updateProgressUI(1.0);
                    uploadSpeed.textContent = &#039;上传完成&#039;;
                    uploadTimeLeft.textContent = &#039;用时: &#039; + formatTime((Date.now() - uploadStartTime) / 1000);
                    
                    // 更新历史记录
                    addToUploadHistory(uploadStatus);
                    
                    // 禁用控制按钮
                    pauseUploadBtn.disabled = true;
                    resumeUploadBtn.disabled = true;
                    cancelUploadBtn.disabled = true;
                    
                    // 显示成功消息
                    alert(&#039;文件上传完成!&#039;);
                } else {
                    console.error(&#039;合并文件失败:&#039;, data);
                    alert(&#039;合并文件失败，请重试&#039;);
                }
            })
            .catch(error =&gt; {
                console.error(&#039;合并文件错误:&#039;, error);
                alert(&#039;合并文件时发生错误，请重试&#039;);
            });
        }
        
        // 暂停上传
        function pauseUpload() {
            isPaused = true;
            isUploading = false;
            
            // 更新按钮状态
            pauseUploadBtn.disabled = true;
            resumeUploadBtn.disabled = false;
            cancelUploadBtn.disabled = false;
            
            // 更新UI
            uploadSpeed.textContent = &#039;已暂停&#039;;
            uploadTimeLeft.textContent = &#039;点击继续&#039;;
        }
        
        // 继续上传
        function resumeUpload() {
            if (!file) return;
            
            isUploading = true;
            isPaused = false;
            
            // 更新按钮状态
            pauseUploadBtn.disabled = false;
            resumeUploadBtn.disabled = true;
            cancelUploadBtn.disabled = false;
            
            // 记录开始时间
            uploadStartTime = Date.now();
            
            // 开始上传下一个块
            uploadNextChunk();
        }
        
        // 取消上传
        function cancelUpload() {
            if (!confirm(&#039;确定要取消上传吗？&#039;)) return;
            
            isUploading = false;
            isPaused = false;
            
            // 重置UI
            uploadProgressBar.style.width = &#039;0%&#039;;
            uploadPercentage.textContent = &#039;0%&#039;;
            uploadSpeed.textContent = &#039;已取消&#039;;
            uploadTimeLeft.textContent = &#039;&#039;;
            
            // 更新按钮状态
            pauseUploadBtn.disabled = true;
            resumeUploadBtn.disabled = true;
            cancelUploadBtn.disabled = true;
            
            // 隐藏进度条
            setTimeout(() =&gt; {
                uploadProgressContainer.classList.add(&#039;hidden&#039;);
            }, 1000);
            
            // 清空文件
            file = null;
            fileInput.value = &#039;&#039;;
        }
        
        // 更新进度UI
        function updateProgressUI(progress = null) {
            if (progress === null &amp;&amp; uploadStatus) {
                progress = calculateProgress(uploadStatus);
            }
            
            if (progress !== null) {
                const percentage = Math.round(progress * 100);
                uploadProgressBar.style.width = `${percentage}%`;
                uploadPercentage.textContent = `${percentage}%`;
            }
        }
        
        // 计算上传进度
        function calculateProgress(status) {
            if (!status) return 0;
            
            let uploadedChunks = 0;
            for (let i = 0; i &lt; status.Uploaded.length; i++) {
                if (status.Uploaded[i]) {
                    uploadedChunks++;
                }
            }
            
            return uploadedChunks / status.TotalChunks;
        }
        
        // 生成文件唯一标识符
        function generateUUID(file) {
            // 使用文件名、大小和修改日期生成唯一标识符
            return `${file.name}-${file.size}-${file.lastModified}`;
        }
        
        // 格式化文件大小
        function formatFileSize(bytes) {
            if (bytes === 0) return &#039;0 Bytes&#039;;
            
            const k = 1024;
            const sizes = [&#039;Bytes&#039;, &#039;KB&#039;, &#039;MB&#039;, &#039;GB&#039;, &#039;TB&#039;];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + &#039; &#039; + sizes[i];
        }
        
        // 格式化时间
        function formatTime(seconds) {
            const hours = Math.floor(seconds / 3600);
            const minutes = Math.floor((seconds % 3600) / 60);
            const secs = Math.floor(seconds % 60);
            
            let timeStr = &#039;&#039;;
            if (hours &gt; 0) timeStr += `${hours}h `;
            if (minutes &gt; 0) timeStr += `${minutes}m `;
            timeStr += `${secs}s`;
            
            return timeStr;
        }
        
        // 添加到上传历史
        function addToUploadHistory(status) {
            // 从本地存储获取所有上传历史
            let history = JSON.parse(localStorage.getItem(&#039;uploadHistory&#039;) || &#039;[]&#039;);
            
            // 查找是否已存在相同的上传记录
            const index = history.findIndex(item =&gt; item.UUID === status.UUID);
            
            if (index === -1) {
                // 添加新记录
                history.push(status);
            } else {
                // 更新现有记录
                history[index] = status;
            }
            
            // 保存到本地存储
            localStorage.setItem(&#039;uploadHistory&#039;, JSON.stringify(history));
            
            // 更新UI
            renderUploadHistory(history);
        }
        
        // 加载上传历史
        function loadUploadHistory() {
            const history = JSON.parse(localStorage.getItem(&#039;uploadHistory&#039;) || &#039;[]&#039;);
            renderUploadHistory(history);
        }
        
        // 渲染上传历史
        function renderUploadHistory(history) {
            // 清空历史记录
            uploadHistory.innerHTML = &#039;&#039;;
            
            // 按上传时间排序（最新的在前）
            history.sort((a, b) =&gt; new Date(b.UploadedAt) - new Date(a.UploadedAt));
            
            // 显示最多10条记录
            const displayHistory = history.slice(0, 10);
            
            if (displayHistory.length === 0) {
                uploadHistory.innerHTML = `
                    &lt;div class=&quot;bg-gray-50 p-4 rounded-lg text-center text-gray-500&quot;&gt;
                        &lt;i class=&quot;fa fa-history text-2xl mb-2&quot;&gt;&lt;/i&gt;
                        &lt;p&gt;暂无上传记录&lt;/p&gt;
                    &lt;/div&gt;
                `;
                return;
            }
            
            // 渲染每条记录
            displayHistory.forEach(status =&gt; {
                const progress = calculateProgress(status);
                const percentage = Math.round(progress * 100);
                const date = new Date(status.UploadedAt).toLocaleString();
                
                let statusClass = &#039;bg-warning&#039;;
                let statusText = &#039;上传中&#039;;
                
                if (status.Completed) {
                    statusClass = &#039;bg-secondary&#039;;
                    statusText = &#039;已完成&#039;;
                } else if (percentage === 0) {
                    statusClass = &#039;bg-gray-400&#039;;
                    statusText = &#039;未开始&#039;;
                }
                
                const historyItem = document.createElement(&#039;div&#039;);
                historyItem.className = &#039;bg-white rounded-lg shadow p-4 border border-gray-100 transition-all duration-300 hover:shadow-md&#039;;
                historyItem.innerHTML = `
                    &lt;div class=&quot;flex justify-between items-start mb-2&quot;&gt;
                        &lt;div class=&quot;flex-1&quot;&gt;
                            &lt;h4 class=&quot;font-medium text-dark truncate&quot;&gt;${status.Filename}&lt;/h4&gt;
                            &lt;p class=&quot;text-sm text-gray-500&quot;&gt;${formatFileSize(status.Size)}&lt;/p&gt;
                        &lt;/div&gt;
                        &lt;span class=&quot;px-2 py-1 rounded-full text-xs font-medium ${statusClass.replace(&#039;bg-&#039;, &#039;text-&#039;)} ${statusClass}&quot;&gt;
                            ${statusText}
                        &lt;/span&gt;
                    &lt;/div&gt;
                    
                    &lt;div class=&quot;w-full bg-gray-200 rounded-full h-2 mb-1&quot;&gt;
                        &lt;div class=&quot;h-2 rounded-full ${statusClass} progress-animation&quot; style=&quot;width: ${percentage}%&quot;&gt;&lt;/div&gt;
                    &lt;/div&gt;
                    
                    &lt;div class=&quot;flex justify-between text-xs text-gray-500&quot;&gt;
                        &lt;span&gt;${percentage}%&lt;/span&gt;
                        &lt;span&gt;${date}&lt;/span&gt;
                    &lt;/div&gt;
                    
                    &lt;div class=&quot;mt-3 flex space-x-2&quot;&gt;
                        ${status.Completed ? &#039;&#039; : `
                            &lt;button class=&quot;text-primary hover:text-primary/80 text-sm flex items-center transition-colors duration-300&quot; 
                                    onclick=&quot;resumeUploadFromHistory(&#039;${status.UUID}&#039;)&quot;&gt;
                                &lt;i class=&quot;fa fa-play-circle mr-1&quot;&gt;&lt;/i&gt; 继续
                            &lt;/button&gt;
                        `}
                        &lt;button class=&quot;text-danger hover:text-danger/80 text-sm flex items-center transition-colors duration-300&quot;
                                onclick=&quot;deleteUploadHistory(&#039;${status.UUID}&#039;)&quot;&gt;
                            &lt;i class=&quot;fa fa-trash mr-1&quot;&gt;&lt;/i&gt; 删除
                        &lt;/button&gt;
                    &lt;/div&gt;
                `;
                
                uploadHistory.appendChild(historyItem);
            });
        }
        
        // 从历史记录继续上传
        window.resumeUploadFromHistory = function(uuid) {
            const history = JSON.parse(localStorage.getItem(&#039;uploadHistory&#039;) || &#039;[]&#039;);
            const status = history.find(item =&gt; item.UUID === uuid);
            
            if (status &amp;&amp; !status.Completed) {
                // 这里需要用户重新选择文件
                alert(&#039;请重新选择文件以继续上传&#039;);
                fileInput.click();
                
                // 存储要恢复的上传UUID
                localStorage.setItem(&#039;resumeUploadUUID&#039;, uuid);
            }
        };
        
        // 删除上传历史
        window.deleteUploadHistory = function(uuid) {
            if (!confirm(&#039;确定要删除此上传记录吗？&#039;)) return;
            
            let history = JSON.parse(localStorage.getItem(&#039;uploadHistory&#039;) || &#039;[]&#039;);
            history = history.filter(item =&gt; item.UUID !== uuid);
            localStorage.setItem(&#039;uploadHistory&#039;, JSON.stringify(history));
            
            // 重新渲染历史记录
            renderUploadHistory(history);
        };
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>到此就结束了，剩下的就是你放飞自我的去上传吧，还可以看上传历史呦！<br />
效果图：<a href="http://www.yunda51.com/wp-content/uploads/2025/11/upload.png"><img src="http://www.yunda51.com/wp-content/uploads/2025/11/upload.png" alt="upload" width="2414" height="1492" class="alignnone size-full wp-image-2036" /></a><br />
转载请注明转自:<a href="http://www.yunda51.com">运达's blog</a>  原文地址：<a href="https://www.yunda51.com/?p=2027">https://www.yunda51.com/?p=2027</a></p>
]]></content:encoded>
			<wfw:commentRss>https://www.yunda51.com/?feed=rss2&#038;p=2027</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MacOs下fabric2.4搭建部署</title>
		<link>https://www.yunda51.com/?p=1951</link>
		<comments>https://www.yunda51.com/?p=1951#comments</comments>
		<pubDate>Thu, 27 Oct 2022 02:53:37 +0000</pubDate>
		<dc:creator><![CDATA[运达]]></dc:creator>
				<category><![CDATA[docker]]></category>
		<category><![CDATA[fabric]]></category>
		<category><![CDATA[Go]]></category>
		<category><![CDATA[golang]]></category>
		<category><![CDATA[MacOs]]></category>

		<guid isPermaLink="false">http://www.yunda51.com/?p=1951</guid>
		<description><![CDATA[一、环境准备 安装git brew install git 安装vim brew install vim 安装<a href="https://www.yunda51.com/?p=1951" class="read-more">Continue Reading</a>]]></description>
				<content:encoded><![CDATA[<p><strong>一、环境准备</strong></p>
<pre class="wp-code-highlight prettyprint">
安装git brew install git
安装vim brew install vim
安装curl brew install curl
安装wget brew install wget
安装go1.14 以及以上版本
安装docker 17.06.2-ce 以及以上版本
安装docker-compose 1.14.0 以及以上版本
git 拉下 fabric 项目
下载docker image镜像
</pre>
<p>具体操作：<br />
1.go安装<br />
下载压缩包 无法翻墙 可以去这个网站：https://studygolang.com/dl<br />
下载好之后依次执行以下命令：</p>
<pre class="wp-code-highlight prettyprint">tar -xzf go1. 17.7 darwin -amd64.tar.gz // 解压你下载的go包
sudo mv go /usr/local    	//移动
cd /Users/liuyunda		//进入自己账户目录下
mkdir -p workspace/go/	    //建立go工作空间
cd /Users/ liuyunda /workspace/go
mkdir src bin pkg			//建立go工作空间
cd src				//存放源码的地方mkdir github.com/hyperledger 	//一定要这样建立目录，然后在这目录下拉fabric源码，不然报错
</pre>
<p><strong>路径配置：</strong><br />
执行 vim ~/.bash_profile ,然后把下面路径配置到其中,具体路径要按照你电脑的来，我这里是我自己的路径<br />
编辑完成后记得 source ~/.bash_profile<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/1.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/1.png" alt="1" width="864" height="140" class="alignnone size-full wp-image-1952" /></a><br />
到此GO语言部分准备完毕</p>
<p><strong>二、docker安装：</strong><br />
mac用户 直接 brew install docker 即可，或者去docker官网下载安装<br />
mac 安装docker就自动安装了docker-compose<br />
然后 docker version 、docker-compose version 看版本<br />
像这样就没问题：<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/2.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/2.png" alt="2" width="864" height="636" class="alignnone size-full wp-image-1953" /></a><br />
<strong>三、docker镜像下载：</strong></p>
<pre class="wp-code-highlight prettyprint">
cd /users/apple/privatespace/go/src/github.com/hyperledger  //进入源码存放目录
git clone https://github.com/hyperledger/fabric.git  		//下载fabric源码
cd fabric/scripts		//进入这个目录，里面有一个 bootstrap.sh 文件
sudo ./bootstrap.sh 	//执行这个文件 会下载fabric-samples 和 镜像
</pre>
<p>镜像下载有这些如图：<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/3.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/3.png" alt="3" width="862" height="640" class="alignnone size-full wp-image-1954" /></a><br />
上面的工作完成后，当前目录多了一个fabric-samples文件夹，我们进去该目录下的test-network目录测试下搭建的环境是否成功：</p>
<pre class="wp-code-highlight prettyprint">
cd fabric-samples/
cd test-network/
</pre>
<p>由于国内被墙，修改go代理</p>
<pre class="wp-code-highlight prettyprint">
go env -w GOPROXY=https://goproxy.cn,direct
</pre>
<p>然后启动我们的测试网络：</p>
<pre class="wp-code-highlight prettyprint">
sudo ./network.sh up
</pre>
<p>记得加sudo保平安哦。开始了一堆代码之后出现（如下图所示，表示启动成功）:<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/5.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/5.png" alt="5" width="864" height="394" class="alignnone size-full wp-image-1955" /></a><br />
同样道理：</p>
<pre class="wp-code-highlight prettyprint">
sudo ./network.sh down  // 关闭
</pre>
<p>如果出现权限不够的情况</p>
<pre class="wp-code-highlight prettyprint">
直接 cd /users/liuyunda /go/src/github.com/
然后 sudo chmod -R 777 hyperledger
</pre>
<p>到此基本没问题了</p>
<p>接下来开始跟着官网文档走<br />
一、使用fabric test network<br />
<strong>1.启动:</strong><br />
首先进入对应目录：cd fabric-samples/test-network<br />
然后启动： ./network.sh up<br />
出现这个代表成功<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/6.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/6.png" alt="6" width="864" height="414" class="alignnone size-full wp-image-1956" /></a><br />
接着可以看看docker容器:docker ps -a<br />
应该有刚刚启动出来的order 和peer<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/7.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/7.png" alt="7" width="864" height="478" class="alignnone size-full wp-image-1957" /></a><br />
<strong>2、创建通道:</strong><br />
./network.sh createChannel     默认名mychannel<br />
也可以自定义名字创建： ./network.sh createChannel -c channel2 自定义名字channel2<br />
<strong>3、测试:</strong><br />
首先执行：./network.sh deployCC<br />
可能会出现如下图所示的错误，别着急：<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/8.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/8.png" alt="8" width="864" height="488" class="alignnone size-full wp-image-1958" /></a><br />
我们继续执行如下命令：
<pre class="wp-code-highlight prettyprint">
sudo ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-go -ccl go
</pre>
<p>这样就搞定了，如下图所示，表示执行成功。<br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/9.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/9.png" alt="9" width="864" height="484" class="alignnone size-full wp-image-1959" /></a><br />
<a href="http://www.yunda51.com/wp-content/uploads/2022/10/10.png"><img src="http://www.yunda51.com/wp-content/uploads/2022/10/10.png" alt="10" width="864" height="514" class="alignnone size-full wp-image-1960" /></a></p>
]]></content:encoded>
			<wfw:commentRss>https://www.yunda51.com/?feed=rss2&#038;p=1951</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
