You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

REST API多格式响应的Swift解析最佳实践及API设计合理性咨询

嗨,我来帮你拆解这个问题,分两部分来解答:

API设计是否符合最佳实践?

简单说:不符合REST API的最佳实践,主要存在以下几个问题:

  • 同一接口返回完全不同的响应格式(纯文本vs JSON),破坏了接口的一致性,会大幅增加客户端的处理复杂度,也违背了REST风格中「统一接口」的核心原则。
  • 状态码的语义不够精准:200作为成功状态码,返回纯文本而非结构化数据,后续如果需要扩展返回字段(比如验证码有效期)会非常麻烦;400作为通用错误码,其实可以用更贴合场景的状态码(比如429表示请求频率超限,对应「已请求过OTP」的场景)。

优化建议

不管成功还是错误,统一返回JSON格式会更合理:

  • 成功场景(200):返回结构化的JSON对象,比如 {"otp_code": "dwaotpwaadd-dadwaawwdcwdadodwde", "expires_in": 300},后续扩展字段更灵活。
  • 错误场景:用精准的HTTP状态码(比如429表示请求频繁),同时返回统一结构的错误JSON,比如 {"code": "otp_already_requested", "message": "请在60秒后重试"},这样客户端可以统一处理所有错误响应。
Swift iOS中动态解析这种多格式响应

如果暂时无法修改API,我们可以在客户端通过判断HTTP状态码,分别处理不同的响应格式。下面是具体实现步骤:

1. 定义必要的结构体和错误类型

// 错误响应的结构体,匹配API返回的JSON格式
struct OTPErrorResponse: Codable {
    let error: String
    let message: String
}

// 自定义网络请求错误,方便后续处理
enum OTPRequestError: Error {
    case invalidHTTPResponse
    case invalidData
    case failedToParseText
    case apiError(String, String) // 存储error和message内容
    case unknownStatus(Int)
}

2. 封装网络请求方法

func requestOTP(phone: String, completion: @escaping (Result<String, OTPRequestError>) -> Void) {
    guard let baseURL = URL(string: "base_url/api/v1/get-otp-code") else {
        completion(.failure(.invalidData))
        return
    }
    
    var request = URLRequest(url: baseURL)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    // 构造请求参数
    let parameters = ["phone": phone]
    do {
        request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
    } catch {
        completion(.failure(.invalidData))
        return
    }
    
    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        // 处理底层网络错误
        if let _ = error {
            completion(.failure(.invalidData))
            return
        }
        
        // 校验HTTP响应的有效性
        guard let httpResponse = response as? HTTPURLResponse, let data = data else {
            completion(.failure(.invalidHTTPResponse))
            return
        }
        
        // 根据状态码分支处理响应
        switch httpResponse.statusCode {
        case 200:
            // 尝试解析为纯文本验证码
            guard let otpCode = String(data: data, encoding: .utf8) else {
                completion(.failure(.failedToParseText))
                return
            }
            completion(.success(otpCode))
            
        case 400:
            // 尝试解析为错误JSON
            do {
                let errorResponse = try JSONDecoder().decode(OTPErrorResponse.self, from: data)
                completion(.failure(.apiError(errorResponse.error, errorResponse.message)))
            } catch {
                completion(.failure(.invalidData))
            }
            
        default:
            completion(.failure(.unknownStatus(httpResponse.statusCode)))
        }
    }
    task.resume()
}

3. 调用示例

requestOTP(phone: "123456789") { result in
    switch result {
    case .success(let otp):
        print("获取到验证码:\(otp)")
        // 后续处理验证码逻辑
    case .failure(let error):
        switch error {
        case .apiError(let errorCode, let message):
            print("API错误提示:\(message)(错误码:\(errorCode))")
        case .failedToParseText:
            print("解析验证码文本失败")
        default:
            print("请求失败:\(error.localizedDescription)")
        }
    }
}

额外提示

如果API后续可能出现更多格式变化,也可以先把data转成String,再通过判断字符串开头(比如是否以{开头)来区分JSON和纯文本,但这种方式不如通过状态码判断可靠——毕竟状态码是HTTP协议明确约定的语义标识。

内容的提问来源于stack exchange,提问作者nikakirkitadze

火山引擎 最新活动