curl库应用-c++客户端示例及golang服务端应用示例

curl库应用-c++客户端示例及golang服务端应用示例

1. curl简介

curl是一个开源的命令行工具和库,用于在各种协议(如HTTP、HTTPS、FTP等)上进行数据传输。它支持多种平台,包括Windows、Linux和macOS,是许多应用程序进行网络通信的重要工具。

在本项目中,我们使用libcurl库来实现HTTP请求功能,包括GET和POST请求、文件上传和下载等功能。libcurl提供了简单易用的API,允许我们在C++应用程序中轻松地与Web服务器进行通信。

libcurl的主要特点:

  • 支持多种协议:HTTP、HTTPS、FTP、FTPS、SCP、SFTP等
  • 跨平台支持:Windows、Linux、macOS等
  • 多种编程语言接口
  • 稳定可靠,广泛应用于各种项目中
  • 支持SSL/TLS加密通信

2. curl下载安装配置

下载和安装

  1. 访问curl官方网站 curl.se/download.ht... 下载适用于您系统的预编译版本。
  2. 对于Windows平台,推荐下载预编译的二进制包,例如Win64 - Generic (带有SSL)版本。
  3. 解压下载的压缩包到指定目录,例如 C:/MinGW/curl-8.15.0_4-win64-mingw

配置

在本项目中,curl已经通过CMake进行了配置。主要配置项包括:

  1. 包含头文件路径:#include <curl/curl.h>
  2. 链接库文件:在CMakeLists.txt中链接curl库
cmake 复制代码
# 查找并链接curl库
find_package(CURL REQUIRED)
target_link_libraries(${PROJECT_NAME} ${CURL_LIBRARIES})

3. HttpClient中curl的封装及使用示例

3.1 HttpClient类结构

HttpClient类是对curl库的封装,提供了以下主要方法:

3.2 GET和POST方法封装及调用示例

HttpClient.h头文件定义
cpp 复制代码
#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H
#include <wx/wx.h>
#include <string>
#include <curl/curl.h>

// 用于存储 cURL 接收的数据
struct MemoryStruct {
    char* memory;
    size_t size;
};

class HttpClient {
    public:
    // 服务器地址常量
    static const std::string SERVER_ADDRESS;
    HttpClient();
    ~HttpClient();

    // 原MyFrame中的网络相关方法
    std::string HttpPost(const std::string& url, const std::string& jsonData);
    std::string HttpGet(const std::string& url);
    bool HttpUploadFile(const std::string& url, const wxString& filePath);
    bool HttpDownloadFile(const std::string& url, const std::string& filePath);
    wxString URLEncode(const wxString& str);
    // 合并URL的方法
    std::string BuildUrl(const std::string& relativePath) const;
    
    private:
        // 原MyFrame中的回调函数
    static size_t WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp);
    static size_t WriteFileCallback(void* contents, size_t size, size_t nmemb, FILE* fp);
};

#endif // HTTPCLIENT_H
GET方法封装示例
cpp 复制代码
// 发送 GET 请求下载 JSON 数据
std::string HttpClient::HttpGet(const std::string& url) {
    // 构建完整URL
    std::string fullUrl = BuildUrl(url);
    CURL* curl;
    CURLcode res;
    MemoryStruct chunk;

    // 初始化内存块
    chunk.memory = static_cast<char*>(malloc(1)); // 初始化为空字符串
    chunk.size = 0;

    curl = curl_easy_init();
    if (curl) {
        // 设置 URL
        curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());

        // 设置回调函数以接收响应数据
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&chunk);

        // 执行请求
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            // 请求失败,记录错误信息
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
        }
        else {
         // 请求成功,chunk.memory 中存储了响应数据
         // 将响应数据转换为 std::string
            std::string response(chunk.memory);
            std::cout << "Response: " << response << std::endl;

            // 返回响应数据
            return response;
        }

        // 清理
        curl_easy_cleanup(curl);
    }
    else {
     // curl 初始化失败
        std::cerr << "curl_easy_init() failed" << std::endl;
    }

    free(chunk.memory); // 释放内存

    // 如果发生错误,返回空字符串
    return "";
}
POST方法封装示例
cpp 复制代码
// 发送 POST 请求
std::string HttpClient::HttpPost(const std::string& url, const std::string& jsonData) {
    // 构建完整URL
    std::string fullUrl = BuildUrl(url);
    CURL* curl;
    CURLcode res;
    MemoryStruct chunk;
    std::string returnData = ""; // 用于存储返回的 JSON 字符串
    chunk.memory = static_cast<char*>(malloc(1)); // 初始化为空字符串
    chunk.size = 0;

    curl = curl_easy_init();
    if (curl) {
        // 设置 URL
        curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());

        // 设置 POST 数据
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonData.c_str());

        // 设置回调函数以接收响应数据
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&chunk);

        // 设置 HTTP 头(可选,例如设置 Content-Type 为 application/json)
        struct curl_slist* headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json; charset=utf-8");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        // 执行请求
        res = curl_easy_perform(curl);
        if (res != CURLE_OK) {
            // 请求失败,记录错误信息
            std::string errorMsg = "curl_easy_perform() failed: " + std::string(curl_easy_strerror(res));
            wxLogError(wxString::FromUTF8(errorMsg.c_str()));
            returnData = errorMsg; // 返回错误信息
            return returnData;
        }
        else {
         // 请求成功,chunk.memory 中存储了响应数据
            std::string response(chunk.memory); // 将响应数据转换为 std::string

            return response;
        }

        // 清理
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
    else {
     // curl 初始化失败
        std::string curlInitErrorMsg = "curl_easy_init() failed";
        wxLogError(wxString::FromUTF8(curlInitErrorMsg.c_str()));
        returnData = curlInitErrorMsg; // 返回初始化失败信息
        return returnData;
    }

    free(chunk.memory); // 释放内存

    return ""; // 返回响应数据或错误信息
}
回调函数实现
cpp 复制代码
// cURL 的回调函数,用于接收数据
size_t HttpClient::WriteMemoryCallback(void* contents, size_t size, size_t nmemb, void* userp) {
    size_t totalSize = size * nmemb;
    MemoryStruct* mem = static_cast<MemoryStruct*>(userp);

    char* ptr = static_cast<char*>(realloc(mem->memory, mem->size + totalSize + 1));
    if (!ptr) {
        std::cerr << "Not enough memory (realloc returned NULL)" << std::endl;
        return 0;
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, totalSize);
    mem->size += totalSize;
    mem->memory[mem->size] = 0; // Null-terminate the string

    return totalSize;
}

3.3 文件上传和下载方法封装及调用示例

文件上传方法封装
cpp 复制代码
// 上传文件到服务器
bool HttpClient::HttpUploadFile(const std::string& url, const wxString& filePath) {
    // 构建完整URL
    std::string fullUrl = BuildUrl(url);
    // 使用curl上传文件
    CURL* curl;
    CURLcode res;
    struct curl_httppost* formpost = NULL;
    struct curl_httppost* lastptr = NULL;
    struct curl_slist* headerlist = NULL;
    
    // 获取文件名
    wxFileName fileName(filePath);
    wxString fileNameStr = fileName.GetFullName();
    
    // 初始化curl
    curl_global_init(CURL_GLOBAL_DEFAULT);
    curl = curl_easy_init();

    if (curl) {
        // 创建表单数据
        // 对中文路径进行处理
        std::string utf8FilePath = wxString::FromUTF8(filePath.ToUTF8());
        // 调试:输出转换后的路径(方便排查问题)
        wxLogMessage("转换后的文件路径: %s", utf8FilePath.c_str());
        
        curl_formadd(&formpost,
                    &lastptr,
                    CURLFORM_COPYNAME, "file",
                    CURLFORM_FILE, utf8FilePath.c_str(),
                    CURLFORM_END);
                    
        // 对中文文件名进行特殊处理
        wxString encodedFileName = fileNameStr;
        // 将文件名转换为UTF-8格式的std::string
        std::string utf8FileName = URLEncode(wxString::FromUTF8(fileNameStr.ToUTF8()));
        wxLogMessage("转换后的文件名: %s", utf8FileName.c_str());

        curl_formadd(&formpost,
                    &lastptr,
                    CURLFORM_COPYNAME, "filename",
                    CURLFORM_COPYCONTENTS, utf8FileName.c_str(),
                    CURLFORM_END);

        // 设置请求头,明确指定UTF-8编码
        headerlist = curl_slist_append(headerlist, "Accept: application/json");
        headerlist = curl_slist_append(headerlist, "Content-Type: multipart/form-data; charset=utf-8");
        headerlist = curl_slist_append(headerlist, "Expect:"); // 禁用100-Continue预期

        // 设置curl选项
        curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());
        curl_easy_setopt(curl, CURLOPT_HTTPPOST, formpost);
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); // 仅用于测试,生产环境需启用
        curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 仅用于测试,生产环境需启用
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 60L);

        // 执行上传请求
        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            wxString errorMsg = wxString::Format("文件上传失败: %s", curl_easy_strerror(res));
            wxLogError(errorMsg);
            wxMessageBox(errorMsg, "错误", wxOK | wxICON_ERROR);
            
            // 清理资源
            curl_formfree(formpost);
            curl_slist_free_all(headerlist);
            curl_easy_cleanup(curl);
            curl_global_cleanup();
            return false;
        }
        else {
            long response_code;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

            if (response_code == 200) {
                wxString successMsg = wxString::Format("文件上传成功: %s", fileNameStr);
                wxLogMessage(successMsg);
                wxMessageBox(successMsg, "成功", wxOK | wxICON_INFORMATION);
                return true;
            }
            else {
                wxString errorMsg = wxString::Format("文件上传失败,服务器返回状态码: %d", response_code);
                wxLogError(errorMsg);
                wxMessageBox(errorMsg, "错误", wxOK | wxICON_ERROR);
                return false;
            }
        }

        // 清理资源
        curl_formfree(formpost);
        curl_slist_free_all(headerlist);
        curl_easy_cleanup(curl);
    }
    else {
        wxLogError("curl初始化失败");
        wxMessageBox("curl初始化失败", "错误", wxOK | wxICON_ERROR);
        return false;
    }

    curl_global_cleanup();
    return true;
}
文件下载方法封装
cpp 复制代码
// 为文件下载创建专用的写入回调函数
size_t HttpClient::WriteFileCallback(void* contents, size_t size, size_t nmemb, FILE* fp) {
    size_t totalSize = size * nmemb;
    if (fwrite(contents, size, nmemb, fp) != nmemb) {
        std::cerr << "Failed to write to file" << std::endl;
        return 0;
    }
    return totalSize;
}

// 下载文件到本地
bool HttpClient::HttpDownloadFile(const std::string& url, const std::string& filePath) {
    // 构建完整URL
    std::string fullUrl = BuildUrl(url);
    CURL* curl;
    CURLcode res;
    FILE* fp;

    // 初始化libcurl
    curl = curl_easy_init();

    if (curl) {
        // 打开文件以写入二进制数据
        fp = fopen(filePath.c_str(), "wb");
        if (!fp) {
            std::cerr << "Failed to open file for writing: " << filePath << std::endl;
            curl_easy_cleanup(curl);
            return false;
        }

        // 设置 URL
        curl_easy_setopt(curl, CURLOPT_URL, fullUrl.c_str());

        // 设置写入文件的回调函数和数据
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteFileCallback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);

        // 设置跟随重定向
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);

        // 设置用户代理
        curl_easy_setopt(curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");

        // 设置连接超时
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L);

        // 设置接受PDF内容类型
        curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");

        // 执行请求
        res = curl_easy_perform(curl);

        // 检查执行结果
        if (res != CURLE_OK) {
            std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res) << std::endl;
            fclose(fp);
            // 删除可能创建的空文件
            remove(filePath.c_str());
            curl_easy_cleanup(curl);
            return false;
        }
        else {
         // 获取HTTP响应码
            long response_code;
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);

            if (response_code == 200) {
                std::cout << "File downloaded successfully: " << filePath << std::endl;
            }
            else {
                std::cerr << "HTTP error: " << response_code << std::endl;
                fclose(fp);
                // 删除可能创建的无效文件
                remove(filePath.c_str());
                curl_easy_cleanup(curl);
                return false;
            }
        }

        // 清理
        fclose(fp);
        curl_easy_cleanup(curl);
    }
    else {
        std::cerr << "Failed to initialize curl" << std::endl;
        return false;
    }

    return true; // 返回是否成功
}
URL编码方法
cpp 复制代码
// URL编码函数,用于处理包含中文的参数
wxString HttpClient::URLEncode(const wxString& str) {
    wxString encodedStr;

    // 将整个字符串转换为UTF-8
    wxCharBuffer utf8Buf = str.ToUTF8();
    const char* utf8Data = utf8Buf.data();
    if (!utf8Data) return "";

    size_t utf8Len = strlen(utf8Data);

    // 对UTF-8字节序列中的每个字节进行处理
    for (size_t i = 0; i < utf8Len; ++i) {
        unsigned char byte = static_cast<unsigned char>(utf8Data[i]);

        // 对于字母、数字和一些特殊字符,直接添加
        if ((byte >= 'A' && byte <= 'Z') ||
            (byte >= 'a' && byte <= 'z') ||
            (byte >= '0' && byte <= '9') ||
            byte == '-' || byte == '_' || byte == '.' || byte == '~') {
            encodedStr += static_cast<wxChar>(byte);
        }
        else {
         // 对于其他字符,进行百分号编码
            wxString hex;
            hex.Printf("%%%02X", byte);
            encodedStr += hex;
        }
    }

    return encodedStr;
}
URL构建方法
cpp 复制代码
// 合并URL的方法
std::string HttpClient::BuildUrl(const std::string& relativePath) const {
    // 如果relativePath已经是完整URL,则直接返回
    if (relativePath.find("http://") == 0 || relativePath.find("https://") == 0) {
        return relativePath;
    }

    // 确保SERVER_ADDRESS末尾没有斜杠
    std::string base = SERVER_ADDRESS;
    if (!base.empty() && base.back() == '/') {
        base.pop_back();
    }

    // 确保relativePath开头有斜杠
    std::string path = relativePath;
    if (path.empty()) {
        return base;
    }

    if (path.front() != '/') {
        path = "/" + path;
    }

    return base + path;
}

4. MyFrame.cpp中调用HttpClient示例

在MyFrame.cpp中,HttpClient被作为成员变量使用,通过调用其方法实现各种网络操作。

4.1 POST请求调用示例

cpp 复制代码
// Post方式发送Json数据
void MyFrame::OnPost(wxCommandEvent& event) {
    // 示例 JSON 数据
    std::string jsonData = R"({"name": "John", "age": 30})";

    // 目标 URL
    std::string url = "/json";

    // 发送 POST 请求
    std::string response = httpClient.HttpPost(url, jsonData);

    if (response.empty()) {
        // 如果返回的响应为空,可能是请求失败
        wxLogError("POST request failed or returned empty response.");
        m_textCtrl->SetValue("POST Request Failed: Empty response.");
        return;
    }
    
    // 处理响应...
}

4.2 GET请求调用示例

cpp 复制代码
// Get方式获取Json数据
void MyFrame::OnGet(wxCommandEvent& event) {
    // 目标 URL
    std::string url = "/json";

    // 发送 GET 请求
    std::string response = httpClient.HttpGet(url);

    if (response.empty()) {
        // 如果返回的响应为空,可能是请求失败
        wxLogError("GET request failed or returned empty response.");
        m_textCtrl->SetValue("GET Request Failed: Empty response.");
        return;
    }
    
    // 处理响应...
}

4.3 文件上传调用示例

cpp 复制代码
// 上传文件
void MyFrame::OnUploadFile(wxCommandEvent& event) {
    // 获取文件路径
    wxString filePath = m_textCtrl_FilePath->GetValue();
    if (filePath.IsEmpty()) {
        wxMessageBox("请先选择文件", "提示", wxOK | wxICON_INFORMATION);
        return;
    }
    
    // 检查文件是否存在
    if (!wxFile::Exists(filePath)) {
        wxMessageBox("选择的文件不存在", "错误", wxOK | wxICON_ERROR);
        return;
    }
    
    // 构造上传URL
    std::string uploadUrl = "/upload";
    
    // 上传文件
    if (httpClient.HttpUploadFile(uploadUrl, filePath)) {
        // 上传成功后刷新文件列表
        OnGetFileLists(event);
    }
    else {
        m_textCtrl->SetValue("File download failed.");
    }
}

4.4 文件下载调用示例

cpp 复制代码
// 下载文件
void MyFrame::OnDownloadFile(wxCommandEvent& event) {
    wxString selectedFile = m_comboBox->GetStringSelection();
    if (selectedFile.IsEmpty()) {
        wxMessageBox("请先选择文件", "提示", wxOK | wxICON_INFORMATION);
        return;
    }

    // 获取文件名
    wxFileName fileName(selectedFile);
    std::string utf8FileName = httpClient.URLEncode(wxString::FromUTF8(fileName.ToUTF8()));
    wxLogMessage("转换后的文件名: %s", utf8FileName.c_str());
    
    // 构造下载URL,将文件名作为查询参数传递
    std::string fileUrl = wxString::Format("/download?filename=%s", utf8FileName.c_str());
    
    // 保存文件的本地路径 - 使用绝对路径
    std::string filePath = wxGetCwd().ToStdString() + "/download/" + wxFileName::GetPathSeparator() + fileName.ToStdString();
    
    // 输出日志
    wxLogMessage("Saving file to: %s", filePath);
    
    // 下载文件
    if (httpClient.HttpDownloadFile(fileUrl, filePath)) {
        // 处理下载后的文件...
    }
}

总结

以上展示了在本项目中如何使用libcurl库实现HTTP通信功能。通过HttpClient类的封装,我们可以方便地进行GET/POST请求以及文件上传下载操作。这些功能对于现代C++应用程序来说至关重要,特别是在需要与Web服务进行交互的场景中。

在实际使用中,需要注意以下几点:

  1. 正确处理内存分配和释放,避免内存泄漏
  2. 对于包含中文的内容,需要正确进行编码转换
  3. 合理设置超时时间,提高程序健壮性
  4. 错误处理要全面,提供友好的错误提示
  5. 在MyFrame类中通过成员变量的方式使用HttpClient,实现了良好的封装性

通过这种设计模式,我们将网络通信功能与UI逻辑分离,提高了代码的可维护性和可扩展性。

Go服务端应用示例

1. 目录结构

csharp 复制代码
goServer/
├── files/                      # 上传文件存储目录
│   ├── *.pdf
│   ├── *.xlsx
│   ├── *.md
│   └── *.txt
├── static/                     # 静态资源目录
│   ├── css/
│   ├── js/
│   └── views/
├── go.mod                      # Go模块定义文件
├── go.sum                      # Go模块校验和文件
└── main.go                     # 主程序文件

2. main.go主程序文件

2.1 导入包和数据结构定义

go 复制代码
package main

import (
    // "encoding/json"
    "errors"
    "fmt"
    "io"
    "mime"
    "net/http"
    "net/url"
    "os"
    "path/filepath"
    "strings"
    "time"

    _ "github.com/denisenkom/go-mssqldb"
    "github.com/kataras/iris/v12"
    "github.com/kataras/iris/v12/mvc"
    "xorm.io/xorm"

    "github.com/xuri/excelize/v2"
)

// 定义一个简单的 JSON 数据结构,用于接收客户端发送的 POST 数据
type Data struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

// 在main函数中添加新的处理函数之前,先定义一个用于表示文件信息的结构体
type FileInfo struct {
    Name string `json:"name"`
    Size string `json:"size"`
    Path string `json:"path"`
}

// User 用户模型
type User struct {
    Id       int64  `json:"id" xorm:"bigint pk autoincr"`
    Name     string `json:"name" xorm:"varchar(255)"`
    Email    string `json:"email" xorm:"varchar(255)"`
    Age      int    `json:"age" xorm:"int"`
}

2.2 主函数实现

go 复制代码
func main() {
    // 初始化XORM引擎连接MSSQL数据库
    // 需要替换为实际的数据库连接信息
    engine, err := xorm.NewEngine("mssql", "server=localhost,1888;user id=sa;password=19801122;database=invoice;encrypt=disable")
    if err != nil {
        fmt.Printf("Failed to connect to database: %v\n", err)
        return
    }
    defer engine.Close()
    
    // 显示SQL语句
    engine.ShowSQL(true)
    
    // 同步表结构
    err = engine.Sync2(new(User))
    if err != nil {
        fmt.Printf("Failed to sync database: %v\n", err)
        return
    }

    app := iris.New()
    // 然后在路由配置中使用这个中间件
    app.Use(noCacheMiddleware)
    // 提供静态文件服务,将 resources 目录映射为 /static/ 路径
    app.HandleDir("/static", iris.Dir("static"))

    // 设置用户控制器的MVC路由
    userRouter := mvc.New(app.Party("/users"))
    userRouter.Register(engine)
    userRouter.Handle(new(UserController))
    
    // 添加自定义路由支持按姓名、邮箱和年龄查询
    users := app.Party("/users")
    users.Get("/name/{name}", func(ctx iris.Context) {
        name := ctx.Params().Get("name")
        userController := &UserController{Engine: engine}
        ctx.JSON(userController.GetByName(name))
    })
    
    users.Get("/email/{email}", func(ctx iris.Context) {
        email := ctx.Params().Get("email")
        userController := &UserController{Engine: engine}
        ctx.JSON(userController.GetByEmail(email))
    })
    
    users.Get("/age/{age:int}", func(ctx iris.Context) {
        age, _ := ctx.Params().GetInt("age")
        userController := &UserController{Engine: engine}
        ctx.JSON(userController.GetByAge(age))
    })

    // 设置文件控制器的MVC路由
    fileRouter := mvc.New(app.Party("/"))
    fileRouter.Handle(new(FileController))

    // 启动服务器
    app.Listen(":8080")
}

3. 服务端GET和POST接收curl上传的数据示例代码

3.1 GET请求处理示例

go 复制代码
// GetJson 处理 GET 请求,返回 JSON 数据
func (fc *FileController) GetJson(ctx iris.Context) {
    // 原来的JSON数据返回功能
    jsonData := []map[string]interface{}{
        {"name": "John", "age": 30},
        {"name": "Mike", "age": 25},
    }
    
    // 返回一个简单的 JSON 数据
    response := map[string]interface{}{
        "message": "Get from Go server!",
        "data": map[string]interface{}{
            "status":   "success",
            "code":     200,
            "datalist": jsonData,
        },
    }

    ctx.JSON(response)
}

3.2 POST请求处理示例

go 复制代码
// PostJson 处理 POST 请求,接收 JSON 数据
func (fc *FileController) PostJson(ctx iris.Context) {
    // 解析客户端发送的 JSON 数据
    var data Data
    err := ctx.ReadJSON(&data)
    if err != nil {
        ctx.StatusCode(iris.StatusBadRequest)
        ctx.JSON(map[string]string{"error": "Invalid JSON"})
        return
    }

    // 打印接收到的 JSON 数据
    fmt.Printf("Received JSON data: %+v\n", data)

    // 返回响应,表示数据已接收
    jsonData := []map[string]interface{}{
        {"name": "赵大", "age": 46},
        {"name": "钱二", "age": 45},
        {"name": "张三", "age": 35},
        {"name": "李四", "age": 44},
        {"name": "周五", "age": 39},
        {"name": "吴六", "age": 42},
        {"name": "郑七", "age": 38},
        {"name": "王八", "age": 42},
    }
    
    // 返回一个简单的 JSON 数据
    response := map[string]interface{}{
        "message": "Post from Go server!",
        "data": map[string]interface{}{
            "status":   "success",
            "code":     200,
            "datalist": jsonData,
        },
    }
    ctx.JSON(response)
}

4. 接收curl上传的文件或curl下载文件的示例代码

4.1 文件上传处理示例

go 复制代码
// PostUpload 处理 POST 请求,接收上传的文件
func (fc *FileController) PostUpload(ctx iris.Context) {
    // 解析表单数据,最大内存为 32MB
    err := ctx.Request().ParseMultipartForm(32 << 20)
    if err != nil {
        ctx.StatusCode(iris.StatusBadRequest)
        ctx.Text("File upload error")
        return
    }

    // 获取上传的文件
    file, fileHeader, err := ctx.FormFile("file")
    if err != nil {
        ctx.StatusCode(iris.StatusBadRequest)
        ctx.Text("File upload error")
        return
    }
    defer file.Close()

    // 获取文件名
    filename := ctx.FormValue("filename")
    // 进行URL解码,将c++上传的中文文件名进行解码
    decodedStr, err := url.QueryUnescape(filename)
    if err != nil {
        fmt.Printf("解码失败: %v\n", err)
        ctx.StatusCode(iris.StatusBadRequest)
        ctx.Text("Filename decode error")
        return
    }
    fmt.Printf("解码前: %s\n", filename)
    fmt.Printf("解码后: %s\n", decodedStr) // 输出: 王明
    // 示例:打印文件名
    println("接收的文件名:", decodedStr)

    // 创建目标文件路径
    // 注意:这里将文件保存到 files 目录下
    // 在实际应用中,你可能需要根据文件类型或用户信息创建子目录
    dst, err := os.Create("./files/" + decodedStr)
    if err != nil {
        ctx.StatusCode(iris.StatusInternalServerError)
        ctx.Text("File save error")
        return
    }
    defer dst.Close()

    // 将上传的文件内容复制到目标文件
    _, err = io.Copy(dst, file)
    if err != nil {
        ctx.StatusCode(iris.StatusInternalServerError)
        ctx.Text("File save error")
        return
    }

    // 返回成功响应
    ctx.JSON(map[string]interface{}{
        "message": "File uploaded successfully",
        "data:": map[string]interface{}{
            "status":   "success",
            "code":     200,
            "filename": decodedStr,
            "size":     fileHeader.Size,
        },
    })

    fmt.Printf("File uploaded: %s\n", decodedStr)
}

4.2 文件下载处理示例

go 复制代码
// GetDownload 处理 GET 请求,提供文件下载功能
func (fc *FileController) GetDownload(ctx iris.Context) {
    // 从查询参数获取文件名
    fileName := ctx.URLParam("filename")
    if fileName == "" {
        fileName = "default.txt" // 默认文件名
    }

    // 进行URL解码,将c++上传的中文文件名进行解码
    filePath, err := url.QueryUnescape(fileName)
    if err != nil {
        fmt.Printf("解码失败: %v\n", err)
        return
    }
    fmt.Printf("解码前: %s\n", fileName)
    fmt.Printf("解码后: %s\n", filePath) // 输出: 王明
    // 示例:打印文件名
    println("接收的文件名:", filePath)
    filePath = "./files/" + filePath
    // 构造文件路径(这里可以根据需要调整)

    // 检查文件是否存在
    if _, err := os.Stat(filePath); os.IsNotExist(err) {
        // 如果文件不存在,创建一个示例文件并写入内容
        err := createSampleFile(filePath)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Text("Failed to create sample file")
            return
        }
    }

    // 打开文件
    file, err := os.Open(filePath)
    if err != nil {
        ctx.StatusCode(iris.StatusNotFound)
        ctx.Text("File not found")
        return
    }
    defer file.Close()

    // 获取文件信息
    fileInfo, err := file.Stat()
    if err != nil {
        ctx.StatusCode(iris.StatusInternalServerError)
        ctx.Text("Error getting file info")
        return
    }

    // 设置响应头,指定文件下载
    ctx.Header("Content-Disposition", "attachment; filename="+fileInfo.Name())
    contentType := getContentTypeWithMime(filePath)
    ctx.ContentType(contentType)
    ctx.Header("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))

    // 将文件内容写入响应
    _, err = io.Copy(ctx.ResponseWriter(), file)
    if err != nil {
        ctx.StatusCode(iris.StatusInternalServerError)
        ctx.Text("Error sending the file")
        return
    }

    fmt.Printf("File downloaded: %s\n", fileInfo.Name())
}

4.3 获取Content-Type的辅助函数

go 复制代码
// 使用mime包根据扩展名获取Content-Type
func getContentTypeWithMime(filePath string) string {
    ext := strings.ToLower(filepath.Ext(filePath))
    contentType := mime.TypeByExtension(ext)

    // 如果找不到对应的MIME类型,使用默认值
    if contentType == "" {
        return "application/octet-stream"
    }

    return contentType
}

4.4 文件预览处理示例

go 复制代码
// GetPreview 处理文件预览请求
func (fc *FileController) GetPreview(ctx iris.Context) {
    // 从查询参数获取文件名
    fileName := ctx.URLParam("filename")
    if fileName == "" {
        ctx.StatusCode(iris.StatusBadRequest)
        ctx.Text("Filename not specified")
        return
    }

    // 获取action参数
    queryAction := ctx.URLParam("action")

    // 构造文件路径
    filePath := fileName

    // 检查文件是否存在
    if _, err := os.Stat("./files/" + filePath); os.IsNotExist(err) {
        ctx.StatusCode(iris.StatusNotFound)
        ctx.Text("File not found")
        return
    }

    // 根据文件扩展名处理不同类型的文件
    ext := strings.ToLower(filepath.Ext(filePath))

    switch ext {
    case ".html", ".htm":
        // HTML文件直接显示,但确保编码正确
        content, err := os.ReadFile("./files/" + filePath)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Text("Error reading the file")
            return
        }
        ctx.ContentType("text/html; charset=utf-8")
        ctx.Write(content)
    case ".md", ".markdown":
        // Markdown文件转换为HTML显示
        file, err := os.Open("./files/" + filePath)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Text("Error opening file")
            return
        }
        defer file.Close()

        content, err := io.ReadAll(file)
        if err != nil {
            ctx.StatusCode(iris.StatusInternalServerError)
            ctx.Text("Error reading the file")
            return
        }

        // 对content中的`进行转义\`
        newContent := []byte(strings.ReplaceAll(string(content), "`", "&#96;"))
        // 包装在完整的HTML页面中
        html := convertMarkdownToHTML(string(newContent))

        ctx.ContentType("text/html; charset=utf-8")
        ctx.Write([]byte(html))
    case ".xlsx":
        // XLSX文件处理
        if queryAction == "edit" {
            // 编辑模式
            htmlContent, err := convertXlsxToEditableHTML(filePath, fileName)
            if err != nil {
                ctx.StatusCode(iris.StatusInternalServerError)
                ctx.Text("%s", "Error converting XLSX to editable HTML: " + err.Error())
                return
            }

            ctx.ContentType("text/html; charset=utf-8")
            ctx.Write([]byte(htmlContent))
        } else {
            // 预览模式
            htmlContent, err := convertXlsxToPreviewHTML(filePath)
            if err != nil {
                ctx.StatusCode(iris.StatusInternalServerError)
                ctx.Text("%s", "Error converting XLSX to HTML: " + err.Error())
                return
            }

            ctx.ContentType("text/html; charset=utf-8")
            ctx.Write([]byte(htmlContent))
        }
    default:
        // 其他文件类型处理
        // 检查是否是文本文件
        contentType := getContentTypeWithMime("./files/" + filePath)
        if strings.HasPrefix(contentType, "text/") {
            // 文本文件直接显示
            content, err := os.ReadFile("./files/" + filePath)
            if err != nil {
                ctx.StatusCode(iris.StatusInternalServerError)
                ctx.Text("Error reading the file")
                return
            }
            ctx.ContentType("text/plain; charset=utf-8")
            ctx.Write(content)
        } else {
            // 其他类型文件作为附件下载
            ctx.Header("Content-Disposition", "attachment; filename="+fileName)
            ctx.ContentType(contentType)
            ctx.ServeFile("./files/" + filePath)
        }
    }
}

4.5 Go服务端HTML转换函数示例

4.5.1. convertMarkdownToHTML函数
go 复制代码
// 简单的Markdown转HTML函数(实际项目中建议使用专业的库如github.com/russross/blackfriday)
func convertMarkdownToHTML(markdown string) string {
    html := `<!DOCTYPE html>
<html lang="zh">


<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>Markdown 预览</title>
   <script src="/static/js/marked.min.js"></script>
   <script src="/static/js/purify.min.js"></script>
   <link rel="stylesheet" href="/static/css/default.min.css">
   <script src="/static/js/highlight.min.js"></script>
   <style>
      body {
         font-family: Arial, sans-serif;
         margin: 20px;
         line-height: 1.6;
      }

      #content {
         padding: 10px;
      }

      pre {
         background: #f5f5f5;
         padding: 10px;
         border-radius: 3px;
         overflow-x: auto;
      }

      code {
         font-family: Consolas, monospace;
      }
   </style>
</head>

<body>
   <div id="content"></div>
   <script>
      marked.setOptions({
         highlight: function (code, lang) {
            if (lang && hljs.getLanguage(lang)) {
               return hljs.highlight(code, { language: lang }).value;
            }
            return hljs.highlightAuto(code).value;
         }
      });

      const markdownContent = ` + "`" + markdown + "`;" + `
      document.getElementById('content').innerHTML =
         DOMPurify.sanitize(marked.parse(markdownContent));
      hljs.highlightAll();
   </script>
</body>

</html>`
    return html
}

该函数用于将Markdown格式的文本转换为带有语法高亮的HTML页面。主要特点包括:

  1. 使用marked.js库解析Markdown语法
  2. 使用DOMPurify库进行HTML净化,防止XSS攻击
  3. 使用highlight.js库实现代码语法高亮
  4. 包含基础的CSS样式以提升可读性
4.5.2. convertXlsxToPreviewHTML函数
go 复制代码
// 将XLSX文件转换为预览HTML
func convertXlsxToPreviewHTML(filePath string) (string, error) {
    f, err := excelize.OpenFile("./files/" +filePath)
    if err != nil {
        return "", err
    }
    defer f.Close()

    // 获取所有工作表名称
    sheets := f.GetSheetList()

    html := `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>XLSX文件预览</title>
    <style>
        body { 
            font-family: Arial, sans-serif; 
            margin: 20px; 
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .sheet-tabs {
            display: flex;
            margin-bottom: 20px;
            border-bottom: 1px solid #ddd;
        }
        .sheet-tab {
            padding: 10px 20px;
            cursor: pointer;
            border: 1px solid #ddd;
            border-bottom: none;
            border-radius: 5px 5px 0 0;
            background-color: #f0f0f0;
            margin-right: 5px;
        }
        .sheet-tab.active {
            background-color: #007bff;
            color: white;
        }
        .sheet-content {
            display: none;
        }
        .sheet-content.active {
            display: block;
        }
        table {
            border-collapse: collapse;
            width: 100%;
            margin-bottom: 20px;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        .edit-link {
            background-color: #28a745;
            color: white;
            padding: 8px 16px;
            text-decoration: none;
            border-radius: 4px;
        }
        .edit-link:hover {
            background-color: #218838;
        }
    </style>
</head>
<body>
    <div class="container">
`
    html += fmt.Sprintf(
        `<li><h1>XLSX文件预览<a href="/preview?filename=%s&action=edit" class="edit-link">编辑</a></h1></li>`+"\n",
        filePath)

    html += ` <div class="sheet-tabs">`

    // 生成工作表标签
    for i, sheet := range sheets {
        class := ""
        if i == 0 {
            class = "active"
        }
        html += fmt.Sprintf(`<div class="sheet-tab %s" onclick="showSheet('%s')">%s</div>`, class, sheet, sheet)
    }

    html += `
        </div>
`

    // 为每个工作表生成内容
    for i, sheet := range sheets {
        class := ""
        if i == 0 {
            class = "active"
        }

        rows, err := f.GetRows(sheet)
        if err != nil {
            return "", err
        }

        html += fmt.Sprintf(`<div id="%s" class="sheet-content %s">`, sheet, class)
        html += "<table>"

        // 生成表格行
        for rowNum, row := range rows {
            html += "<tr>"
            // 添加行号
            html += fmt.Sprintf("<td style='background-color: #f2f2f2; font-weight: bold;'>%d</td>", rowNum+1)

            for _, cell := range row {
                html += "<td>" + cell + "</td>"
            }
            html += "</tr>"
        }

        html += "</table>"
        html += "</div>"
    }

    html += `
    </div>
    <script>
        function showSheet(sheetName) {
            // 隐藏所有工作表内容
            var contents = document.getElementsByClassName('sheet-content');
            for (var i = 0; i < contents.length; i++) {
                contents[i].classList.remove('active');
            }
            
            // 移除所有标签的活动状态
            var tabs = document.getElementsByClassName('sheet-tab');
            for (var i = 0; i < tabs.length; i++) {
                tabs[i].classList.remove('active');
            }
            
            // 显示选中的工作表内容
            document.getElementById(sheetName).classList.add('active');
            
            // 设置选中标签为活动状态
            event.target.classList.add('active');
        }
    </script>
</body>
</html>`

    return html, nil
}

该函数用于将XLSX文件转换为只读预览的HTML页面。主要特点包括:

  1. 使用excelize库读取XLSX文件内容
  2. 支持多工作表展示,通过标签页切换
  3. 为每个工作表生成表格形式的展示
  4. 提供编辑链接,可以跳转到编辑页面
  5. 包含美观的CSS样式和交互式JavaScript
4.5.3. convertXlsxToEditableHTML函数
go 复制代码
// 将XLSX文件转换为可编辑的HTML
func convertXlsxToEditableHTML(filePath, fileName string) (string, error) {
    f, err := excelize.OpenFile("./files/" +filePath)
    if err != nil {
        return "", err
    }
    defer f.Close()

    // 获取所有工作表名称
    sheets := f.GetSheetList()

    html := `<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>编辑XLSX文件</title>
    <style>
        body { 
            font-family: Arial, sans-serif; 
            margin: 0; 
            background-color: #f5f5f5;
        }
        .header {
            background-color: #007bff;
            color: white;
            padding: 20px;
        }
        .container {
            background-color: white;
            padding: 20px;
            margin: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        .controls {
            margin-bottom: 20px;
        }
        .btn {
            padding: 8px 16px;
            background-color: #6c757d;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 10px;
        }
        .btn:hover {
            background-color: #5a6268;
        }
        .btn-success {
            background-color: #28a745;
        }
        .btn-success:hover {
            background-color: #218838;
        }
        .btn-danger {
            background-color: #dc3545;
        }
        .btn-danger:hover {
            background-color: #c82333;
        }
        .sheet-tabs {
            display: flex;
            margin-bottom: 20px;
            border-bottom: 1px solid #ddd;
        }
        .sheet-tab {
            padding: 10px 20px;
            cursor: pointer;
            border: 1px solid #ddd;
            border-bottom: none;
            border-radius: 5px 5px 0 0;
            background-color: #f0f0f0;
            margin-right: 5px;
        }
        .sheet-tab.active {
            background-color: #007bff;
            color: white;
        }
        .sheet-content {
            display: none;
        }
        .sheet-content.active {
            display: block;
        }
        table {
            border-collapse: collapse;
            width: 100%;
            margin-bottom: 20px;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        input[type="text"] {
            width: 100%;
            box-sizing: border-box;
            border: none;
            background: transparent;
        }
    </style>
</head>
<body>
    <div class="header">
        <h1>编辑XLSX文件</h1>
    </div>
    <div class="container">
        <h1>编辑XLSX文件: ` + fileName + `</h1>
        <div class="controls">
            <button class="btn btn-success" onclick="saveChanges()">保存更改</button>
            <button class="btn" onclick="addRow()">添加行</button>
            <button class="btn" onclick="addColumn()">添加列</button>
`
    html += fmt.Sprintf(
        `<button class="btn btn-danger" onclick="window.location.href='/preview?filename=%s'">取消编辑</button>`,
        fileName)
    html += `</div><div class="sheet-tabs">`

    // 生成工作表标签
    for i, sheet := range sheets {
        class := ""
        if i == 0 {
            class = "active"
        }
        html += fmt.Sprintf(`<div class="sheet-tab %s" onclick="showSheet('%s')">%s</div>`, class, sheet, sheet)
    }

    html += `
        </div>
`

    // 为每个工作表生成可编辑内容
    for i, sheet := range sheets {
        class := ""
        if i == 0 {
            class = "active"
        }

        rows, err := f.GetRows(sheet)
        if err != nil {
            return "", err
        }

        html += fmt.Sprintf(`<div id="%s" class="sheet-content %s" data-sheet="%s">`, sheet, class, sheet)
        html += "<table>"

        // 生成表格行
        for rowNum, row := range rows {
            html += "<tr>"
            // 添加行号
            html += fmt.Sprintf("<td style='background-color: #f2f2f2; font-weight: bold;'>%d</td>", rowNum+1)

            for colNum, cell := range row {
                // 转换列号为字母形式 (A, B, C, ...)
                colName, _ := excelize.ColumnNumberToName(colNum + 1)
                cellName := colName + fmt.Sprintf("%d", rowNum+1)
                html += fmt.Sprintf("<td><input type='text' value='%s' data-cell='%s'></td>", cell, cellName)
            }
            html += "</tr>"
        }

        html += "</table>"
        html += "</div>"
    }

    html += `
    </div>
    <script>
        function showSheet(sheetName) {
            // 隐藏所有工作表内容
            var contents = document.getElementsByClassName('sheet-content');
            for (var i = 0; i < contents.length; i++) {
                contents[i].classList.remove('active');
            }
            
            // 移除所有标签的活动状态
            var tabs = document.getElementsByClassName('sheet-tab');
            for (var i = 0; i < tabs.length; i++) {
                tabs[i].classList.remove('active');
            }
            
            // 显示选中的工作表内容
            document.getElementById(sheetName).classList.add('active');
            
            // 设置选中标签为活动状态
            event.target.classList.add('active');
        }
        
        function saveChanges() {
            var activeSheet = document.querySelector('.sheet-content.active');
            var sheetName = activeSheet.getAttribute('data-sheet');
            var inputs = activeSheet.querySelectorAll('input');
            
            var changes = [];
            for (var i = 0; i < inputs.length; i++) {
                var input = inputs[i];
                var cell = input.getAttribute('data-cell');
                var value = input.value;
                changes.push({cell: cell, value: value});
            }
            
            // 发送更改到服务器
            fetch('/save/xlsx', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    filename: '` + fileName + `',
                    sheet: sheetName,
                    changes: changes
                })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    alert('保存成功');
                } else {
                    alert('保存失败: ' + data.message);
                }
            })
            .catch(error => {
                console.error('Error:', error);
                alert('保存失败: ' + error.message);
            });
        }
        
        function addRow() {
            var activeSheet = document.querySelector('.sheet-content.active');
            var table = activeSheet.querySelector('table');
            var rows = table.querySelectorAll('tr');
            var cellCount = rows[0].querySelectorAll('td').length - 1; // 减去行号列
            
            var newRow = document.createElement('tr');
            // 添加行号
            var rowNumber = rows.length;
            newRow.innerHTML = '<td style="background-color: #f2f2f2; font-weight: bold;">' + rowNumber + '</td>';
            
            // 添加单元格
            for (var i = 0; i < cellCount; i++) {
                var colName = String.fromCharCode(65 + i); // A, B, C...
                var cellName = colName + rowNumber;
                newRow.innerHTML += '<td><input type="text" data-cell="' + cellName + '"></td>';
            }
            
            table.appendChild(newRow);
        }
        
        function addColumn() {
            var activeSheet = document.querySelector('.sheet-content.active');
            var table = activeSheet.querySelector('table');
            var rows = table.querySelectorAll('tr');
            var cellCount = rows[0].querySelectorAll('td').length - 1; // 减去行号列
            
            // 计算新的列名 (如: A, B, ..., Z, AA, AB...)
            var colName = "";
            var colIndex = cellCount;
            while (colIndex >= 0) {
                colName = String.fromCharCode(65 + (colIndex % 26)) + colName;
                colIndex = Math.floor(colIndex / 26) - 1;
            }
            
            // 为每一行添加新列
            for (var i = 0; i < rows.length; i++) {
                var rowNum = i + 1;
                var cellName = colName + rowNum;
                var newCell = document.createElement('td');
                if (i === 0) {
                    // 表头行
                    newCell.style.backgroundColor = '#f2f2f2';
                    newCell.style.fontWeight = 'bold';
                    newCell.textContent = colName;
                } else {
                    // 数据行
                    newCell.innerHTML = '<input type="text" data-cell="' + cellName + '">';
                }
                rows[i].appendChild(newCell);
            }
        }
    </script>
</body>
</html>`

    return html, nil
}
  • 该函数用于将XLSX文件转换为可编辑的HTML页面。主要特点包括:
  1. 使用excelize库读取XLSX文件内容
  2. 为每个单元格生成可编辑的输入框
  3. 支持多工作表展示和切换
  4. 提供保存、添加行、添加列等功能
  5. 通过JavaScript实现前端交互和后端通信
  • 这三个函数分别处理了不同类型的文件转换需求:
  1. convertMarkdownToHTML - 专门处理Markdown文档,转换为带有语法高亮的HTML页面
  2. convertXlsxToPreviewHTML - 将XLSX文件转换为只读预览页面,支持多工作表展示
  3. convertXlsxToEditableHTML - 将XLSX文件转换为可编辑页面,支持在线编辑和保存功能

这些函数共同构成了Go服务端的文件预览和编辑功能,为用户提供了一个完整的在线文档处理解决方案。

总结

以上展示了Go服务端如何处理curl客户端发送的GET/POST请求以及文件上传下载功能。通过Iris框架,我们可以方便地处理各种HTTP请求,并根据文件类型提供不同的处理方式。

主要特点包括:

  1. 支持JSON数据的接收和返回
  2. 支持文件上传和下载
  3. 支持多种文件类型的预览(HTML、Markdown、XLSX等)
  4. 对中文文件名进行URL解码处理
  5. 根据文件扩展名设置正确的Content-Type
  6. 提供错误处理和日志记录功能

这些功能使得Go服务端能够与使用curl库的C++客户端进行有效通信,实现完整的文件传输和数据交互功能。

相关推荐
Lei_3359673 小时前
[算法]背包DP(01背包、完全背包问题、多重背包、分组背包、混合背包问题、有依赖的背包问题等)
c++·算法
-芒果酱-3 小时前
对中兴光猫zteOnu.exe项目的简单分析(提供下载地址)
go
wolfseek3 小时前
opencv模版匹配
c++·人工智能·opencv·计算机视觉
仰泳的熊猫4 小时前
LeetCode:773. 滑动谜题
数据结构·c++·算法·leetcode
千里马-horse5 小时前
Boost.Iostreams 简介
开发语言·c++·boost
陌路205 小时前
C17值类别概念
开发语言·c++
shark_dev5 小时前
C++新特性—— 智能指针(shared_ptr/unique_ptr/weak_ptr)
c++
liu****5 小时前
笔试强训(十三)
开发语言·c++·算法·1024程序员节
老王熬夜敲代码6 小时前
ES安装和简单讲解
c++·微服务