学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Swift 文件上传

之前在项目中实现Swift文件上传时踩了一个坑,后面复盘分析原因,需求讲解时未在现场,不知道传入文件的参数name由后端接口返回,另外就是第三方后端在接口联调时不配合,导致浪费不少时间定位问题。本来想用原生方法实现,因为后端的不配合,无法定位具体的参数问题,后改用Alamofire封装APi调用,调通后才发现是之前传参的name问题,之前原生实现部分也可正常运行.

开发者前后端都会很有必要,可以减少沟通成本

上传参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
data =
{
filePath = "/var/mobile/Containers/Data/Application/23076D0F-D012-4655-9921-EA4DE65FDDDE/Documents/fileShare/\U4e2d\U53bf\U5e72\U90e8.pdf";
formData = {
fileName = "\U4e2d\U53bf\U5e72\U90e8.pdf";
};
header = {
Authorization = "bearer 806b3975-28a5-4bc8-8fd9-f32e63b10fe0";
"client_id" = webApp;
"client_secret" = webApp;
};
name = file;
url = "https://newapi.bantbox.cn/api-poi/print/uploadFiles";
}

方式一 Alamofire

1
2
NSDictionary *jsResult = (NSDictionary *)data;
[[VKDocumentShareManager shared] uploadFileAlamoreWithParam:jsResult
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@objc func uploadFileAlamore(param: [String: Any], successHandle:@escaping((Any) -> Void), FailHandler:@escaping((String) -> Void)) {

guard let header = param["header"] as? [String: Any] else {
FailHandler("")
return
}

guard let formData = param["formData"] as?[String: Any] else {
FailHandler("")
return
}

if let urlString = param["url"] as? String,
let filePath = param["filePath"] as? String,
let autho = header["Authorization"] as? String,
let client_id = header["client_id"] as? String,
let client_secret = header["client_secret"] as? String,
let fileName = formData["fileName"] as? String,
let paramName = param["name"] as? String {

let httpHeaders = HTTPHeaders(["Authorization": autho, "client_id": client_id, "client_secret": client_secret])
let fileUrl = URL.init(fileURLWithPath: filePath)
Alamofire.AF.upload(multipartFormData: { multiPart in
multiPart.append(fileUrl, withName: paramName, fileName: fileName, mimeType: "application/octet-stream")
multiPart.append(fileName.data(using: String.Encoding.utf8)!, withName: "fileName")
}, to: urlString, method: .post, headers: httpHeaders).uploadProgress(queue: .main) { _ in

}.responseData { response in
switch response.result {
case .success(let data):
do {
let asJSON = try JSONSerialization.jsonObject(with: data)
successHandle(asJSON)
} catch {
FailHandler(error.localizedDescription)
}
case .failure(let error):
FailHandler(error.localizedDescription)

}
}
}
}

方式二 原生

1
2
NSDictionary *jsResult = (NSDictionary *)data;
[[VKDocumentShareManager shared] startUploadWithParam:jsResult];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
 @objc func startUpload(param: [String: Any]) {

guard let header = param["header"] as? [String: Any] else {
return
}
guard let formData = param["formData"] as?[String: Any] else {
return
}

if let urlString = param["url"] as? String,
let filePath = param["filePath"] as? String,
let autho = header["Authorization"] as? String,
let client_id = header["client_id"] as? String,
let client_secret = header["client_secret"] as? String,
let fileName = formData["fileName"] as? String,
let paramName = param["name"] as? String {

let fileUrl = URL.init(fileURLWithPath: filePath)

// 分隔线
let boundary = "Boundary-\(UUID().uuidString)"
// 传递的参数
let parameters = [
"fileName": fileName
]

// 传递的文件
let files = [
(
name: paramName,
path: fileUrl.path
)
]

// 上传地址
let url = URL(string: urlString)!
var request = URLRequest(url: url)
// 请求类型为POST
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)",
forHTTPHeaderField: "Content-Type")

request.setValue(autho, forHTTPHeaderField: "Authorization")
request.setValue(client_id, forHTTPHeaderField: "client_id")
request.setValue(client_secret, forHTTPHeaderField: "client_secret")
// 创建表单body
request.httpBody = try? createBody(with: parameters, files: files, boundary: boundary)

// 创建一个表单上传任务
let session = URLSession.shared
let uploadTask = session.dataTask(with: request, completionHandler: {
(data, _, error) -> Void in
// 上传完毕后
if error != nil {
print(error!)
} else {
let str = String(data: data!, encoding: String.Encoding.utf8)
print("--- 上传完毕 ---\(str!)")
}
})

// 使用resume方法启动任务
uploadTask.resume()
}
}

// 创建表单body
private func createBody(with parameters: [String: String]?,
files: [(name: String, path: String)],
boundary: String) throws -> Data {
var body = Data()
// 添加普通参数数据
if parameters != nil {
for (key, value) in parameters! {
// 数据之前要用 --分隔线 来隔开 ,否则后台会解析失败
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.append("\(value)\r\n")
}
}

// 添加文件数据
for file in files {
let url = URL(fileURLWithPath: file.path)
let filename = url.lastPathComponent
let data = try Data(contentsOf: url)
let mimetype = mimeType(pathExtension: url.pathExtension)

// 数据之前要用 --分隔线 来隔开 ,否则后台会解析失败
body.append("--\(boundary)\r\n")
body.append("Content-Disposition: form-data; "
+ "name=\"\(file.name)\"; filename=\"\(filename)\"\r\n")
body.append("Content-Type: \(mimetype)\r\n\r\n") // 文件类型
body.append(data) // 文件主体
body.append("\r\n") // 使用\r\n来表示这个这个值的结束符
}

// --分隔线-- 为整个表单的结束符
body.append("--\(boundary)--\r\n")
return body
}

// 根据后缀获取对应的Mime-Type
func mimeType(pathExtension: String) -> String {
if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
pathExtension as NSString,
nil)?.takeRetainedValue() {
if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?
.takeRetainedValue() {
return mimetype as String
}
}
// 文件资源类型如果不知道,传万能类型application/octet-stream,服务器会自动解析文件类
return "application/octet-stream"
}

// 扩展Data
extension Data {
// 增加直接添加String数据的方法
mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
if let data = string.data(using: encoding) {
append(data)
}
}
}

参考

使用原生方式同时上传多个文件和参数