package app_manage import ( "bytes" "crypto/md5" "encoding/hex" "encoding/json" "fmt" "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/model/app_manage/node" "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/errors" "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/pkg/response" "git.inspur.com/sbg-jszt/cfn/cfn-schedule/internal/service/app_manage/common" "git.inspur.com/sbg-jszt/cfn/cfn-schedule/pkg/log" "github.com/gin-gonic/gin" "io" "io/ioutil" "mime/multipart" "os" "path/filepath" "sort" "strconv" "strings" "time" ) func GetWorkspaceId(c *gin.Context) string { return c.GetHeader("workspaceId") } var ShardingIdMap = make(map[string]bool, 200) func TryGetNode(body map[string]interface{}) (*node.Node, error) { nodeId, _ := body["nodeId"].(string) n := &node.Node{} n.Id = nodeId err := n.GetById(n) if err != nil { return nil, err } return n, nil } // String[] removeKeys, String... appendData // application/json类型请求 func TryRequestNode(machineId string, body map[string]interface{}, nodeUrl string, pars ...string) *response.Response { n, _ := TryGetNode(body) marshal, _ := json.Marshal(body) if n != nil { resp, err := common.Request5(n, nodeUrl, bytes.NewReader(marshal)) if err != nil { log.Errorf("发送请求失败: %s", n.Url+"/"+nodeUrl) resp = &response.Response{ HttpCode: errors.ServerError, Result: &response.Result{ Msg: "发送请求失败:" + err.Error(), Data: nil, Cost: "", Now: time.Now(), Code: errors.ServerError, }, } return resp } return resp } else { if len(machineId) > 0 { m := &node.MachineNode{} m.Id = machineId err := m.GetById(m) if err != nil { log.Errorf("发送请求失败: %s", machineId+":"+nodeUrl) resp := &response.Response{ HttpCode: errors.ServerError, Result: &response.Result{ Msg: "发送请求失败:" + err.Error(), Data: nil, Cost: "", Now: time.Now(), Code: errors.ServerError, }, } return resp } resp, err := common.Request(m, "", nodeUrl, nil, nil, bytes.NewReader(marshal)) if err != nil { log.Errorf("发送请求失败: %s", n.Url+"/"+nodeUrl) resp = &response.Response{ HttpCode: errors.ServerError, Result: &response.Result{ Msg: "发送请求失败:" + err.Error(), Data: nil, Cost: "", Now: time.Now(), Code: errors.ServerError, }, } return resp } return resp } else { log.Errorf("没有找到对应的机器: %s", machineId) resp := &response.Response{ HttpCode: errors.ServerError, Result: &response.Result{ Msg: fmt.Sprintf("没有找到对应的机器: %s", machineId), Data: nil, Cost: "", Now: time.Now(), Code: errors.ServerError, }, } return resp } } } /** * 上传保存分片信息 * * @param file 上传的文件信息 * @param tempPath 临时保存目录 * @param sliceId 分片id * @param totalSlice 累积分片 * @param nowSlice 当前分片 * @param fileSumMd5 文件签名信息 * @throws IOException 异常 */ func UploadShardingImpl(file multipart.File, filename, tempPath, sliceID, totalSliceStr, nowSliceStr, fileSumMD5 string, extNames []string) error { if fileSumMD5 == "" { return fmt.Errorf("没有文件签名信息") } if sliceID == "" { return fmt.Errorf("没有分片 id 信息") } totalSlice, err := strconv.Atoi(totalSliceStr) if err != nil { return fmt.Errorf("上传信息不完整:totalSlice %s", err) } nowSlice, err := strconv.Atoi(nowSliceStr) if err != nil { return fmt.Errorf("上传信息不完整:nowSlice %s", err) } if totalSlice <= 0 || nowSlice < 0 || totalSlice < nowSlice { return fmt.Errorf("当前上传的分片信息错误") } slicePath := filepath.Join(tempPath, "slice", sliceID) sliceItemPath := filepath.Join(slicePath, "items") if err := os.MkdirAll(sliceItemPath, 0755); err != nil { return fmt.Errorf("创建保存路径失败:%s", err) } originalFilename := filename // 截断序号 xxxxx.avi.1 realName := subBefore(originalFilename, ".", true) if len(extNames) > 0 { extName := filepath.Ext(realName) for _, allowedExt := range extNames { if strings.ToLower(extName) == strings.ToLower(allowedExt) { break } } if !strings.Contains(strings.ToLower(extName), strings.ToLower(strings.Join(extNames, ","))) { return fmt.Errorf("不支持的文件类型:%s", extName) } } slice := filepath.Join(sliceItemPath, originalFilename) if err := os.MkdirAll(filepath.Dir(slice), 0755); err != nil { return fmt.Errorf("创建分片保存路径失败:%s", err) } // 将文件写入到slice targetFile, err := os.Create(slice) if err != nil { return fmt.Errorf("创建目标文件失败:%s", err) } defer targetFile.Close() _, err = io.Copy(targetFile, file) if err != nil { return fmt.Errorf("保存分片文件失败:%s", err) } return nil } /** * 合并分片 * * @param tempPath 临时保存目录 * @param sliceId 上传id * @param totalSlice 累积分片 * @param fileSumMd5 文件签名 * @return 合并后的文件 * @throws IOException io */ func ShardingTryMergeImpl(tempPath, sliceID string, totalSlice int, fileSumMD5 string) (*os.File, error) { if fileSumMD5 == "" { return nil, fmt.Errorf("没有文件签名信息") } if sliceID == "" { return nil, fmt.Errorf("没有分片 id 信息") } if totalSlice == 0 { return nil, fmt.Errorf("上传信息不完成:totalSlice") } // 保存路径 slicePath := filepath.Join(tempPath, "slice", sliceID) sliceItemPath := filepath.Join(slicePath, "items") // 获取分片文件 files, err := ioutil.ReadDir(sliceItemPath) if err != nil { return nil, err } if len(files) != totalSlice { return nil, fmt.Errorf("文件上传失败,存在分片丢失的情况, %d != %d", len(files), totalSlice) } // 获取文件真实名称 name := files[0].Name() name = subBefore(name, ".", true) // 创建合并后的文件 successFile, err := os.OpenFile(filepath.Join(slicePath, name), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755) if err != nil { return nil, err } defer successFile.Close() // 排序并合并分片 fileOrder := make([]int, len(files)) for i, file := range files { ext := filepath.Ext(file.Name()) fileOrder[i] = mustToInt(ext) } sort.Slice(fileOrder, func(i, j int) bool { return fileOrder[i] < fileOrder[j] }) for _, index := range fileOrder { partFile := filepath.Join(sliceItemPath, fmt.Sprintf("%s.%d", name, index)) part, err := os.Open(partFile) if err != nil { return nil, err } if _, err = io.Copy(successFile, part); err != nil { part.Close() return nil, err } part.Close() } // 删除分片文件 if err = os.RemoveAll(sliceItemPath); err != nil { return nil, err } // 计算新文件的MD5 // 读取整个文件内容 file, err := os.Open(successFile.Name()) if err != nil { return nil, fmt.Errorf("打开文件失败: %s", err.Error()) } defer file.Close() hasher := md5.New() if _, err := io.Copy(hasher, file); err != nil { return nil, fmt.Errorf("md5Sum error: %s", err.Error()) } newMD5 := hex.EncodeToString(hasher.Sum(nil)) if !strings.EqualFold(newMD5, fileSumMD5) { log.Infof("文件合并异常 %s:%x -> %s", successFile.Name(), newMD5, fileSumMD5) return nil, fmt.Errorf("文件合并后异常,文件不完整可能被损坏") } return successFile, nil } func subBefore(s, sep string, isLastSeparator bool) string { if sep == "" { return "" } pos := -1 if isLastSeparator { pos = strings.LastIndex(s, sep) } else { pos = strings.Index(s, sep) } if pos == -1 { return s } else if pos == 0 { return "" } return s[:pos] } // todo 确定是16进制还是字符串 func md5Sum(content []byte) string { // 创建一个新的md5哈希器 hasher := md5.New() // 获取哈希值并转换为16进制字符串 hashBytes := hasher.Sum(content) md5Hash := hex.EncodeToString(hashBytes) return md5Hash } func mustToInt(s string) int { num, _ := strconv.Atoi(strings.Trim(s, ".")) return num }