为什么:权威文档(AGENTS.md / CODING_STANDARDS.md)强制网络层用 dio,差异清单 A 组第 1 题
用户裁定选 A;原来用官方 http 是在无文档状态下按口头要求选的。
- ApiClient 改用 dio + BaseOptions(validateStatus 恒真、8s 超时、ResponseType.bytes 自行 UTF-8 解码)
- 新增 AuthHeaderInterceptor(Authorization / X-Device-Id)与 EnvelopeInterceptor
(解包 {code,message,data} + 错误码映射 + 401 判定)
- 401 → 刷新 → 重发一次在 ApiClient 里编排,刻意不放在拦截器内重发:dio 的排队拦截器
在回调里再发请求会自锁
- 接口层(auth_api/catalog_api)签名、全部 controller/logic/page 未改动
- 测试:新增假 dio adapter 替代 MockClient,断言口径一条未改;用例名改为 should_<期望>_when_<条件>;
新增 api_client_test(解包/错误映射/请求头/查询参数/跳过重试)与 live_auth_test(真实后端 POST 链路)
- pubspec:加 dio、移除已无引用的 http
- 实测:dart analyze lib test 零告警;flutter test 155 全过;真实联调(8081)GET/POST 链路均通
47 lines
1.2 KiB
Dart
47 lines
1.2 KiB
Dart
// Author: fruit.com.cn
|
|
|
|
import 'dart:convert';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:dio/dio.dart';
|
|
|
|
/// 假响应:[body] 会按 UTF-8 编码成字节,[statusCode] 默认 200。
|
|
class FakeHttpResponse {
|
|
FakeHttpResponse(this.body, [this.statusCode = 200]);
|
|
|
|
final String body;
|
|
final int statusCode;
|
|
}
|
|
|
|
/// dio 的测试替身:把请求交给回调处理,全程不联网。
|
|
///
|
|
/// 顺带记录每个收到的请求,测试可以直接断言"发了几次、带了什么头"。
|
|
class FakeHttpAdapter implements HttpClientAdapter {
|
|
FakeHttpAdapter(this.handler);
|
|
|
|
final Future<FakeHttpResponse> Function(RequestOptions options) handler;
|
|
|
|
/// 按顺序记录收到的请求,用于断言请求次数与请求头。
|
|
final List<RequestOptions> requests = [];
|
|
|
|
@override
|
|
Future<ResponseBody> fetch(
|
|
RequestOptions options,
|
|
Stream<Uint8List>? requestStream,
|
|
Future<void>? cancelFuture,
|
|
) async {
|
|
requests.add(options);
|
|
final response = await handler(options);
|
|
return ResponseBody.fromBytes(
|
|
utf8.encode(response.body),
|
|
response.statusCode,
|
|
headers: const {
|
|
'content-type': ['application/json; charset=utf-8'],
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
void close({bool force = false}) {}
|
|
}
|