已完成用户APP首期功能开发
交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
@@ -57,6 +57,10 @@ const Map<int, String> _apiErrorMessages = {
|
||||
1713: '服务暂时不可用,请稍后重试',
|
||||
1714: '服务数据异常,请稍后重试',
|
||||
1715: '请先登录',
|
||||
2410: '短信验证码暂未开放',
|
||||
2511: '最多可创建20个设备分组',
|
||||
2512: '该分组名称已存在',
|
||||
2513: '该新增请求已处理,请关闭表单并刷新分组列表',
|
||||
};
|
||||
|
||||
final RegExp _chineseCharacterPattern = RegExp(r'[\u3400-\u9fff]');
|
||||
@@ -112,13 +116,23 @@ class ApiClient {
|
||||
final http.Client _client;
|
||||
final UnauthorizedCallback? onUnauthorized;
|
||||
|
||||
/// 捕获当前会话而不暴露令牌,供跨页面事件拒绝前一个账号的迟到响应。
|
||||
bool Function() captureSessionGuard() {
|
||||
final token = _tokenProvider();
|
||||
return () => token == _tokenProvider();
|
||||
}
|
||||
|
||||
Future<Object?> get(String path, {bool authenticated = true}) =>
|
||||
_send('GET', path, authenticated: authenticated);
|
||||
|
||||
/// 读取需要鉴权的二进制资源;资源不存在时返回空值。
|
||||
Future<Uint8List?> getBytes(String path) async {
|
||||
Future<Uint8List?> getBytes(
|
||||
String path, {
|
||||
String failureMessage = '头像加载失败',
|
||||
String accept = 'image/jpeg, image/png',
|
||||
}) async {
|
||||
final request = http.Request('GET', Uri.parse('$baseUrl$path'));
|
||||
request.headers['accept'] = 'image/jpeg, image/png';
|
||||
request.headers['accept'] = accept;
|
||||
final token = _tokenProvider();
|
||||
if (token.isNotEmpty) request.headers['authorization'] = token;
|
||||
final response = await _sendRequest(request);
|
||||
@@ -131,7 +145,7 @@ class ApiClient {
|
||||
}
|
||||
if (response.statusCode == 404) return null;
|
||||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||||
throw ApiException(response.statusCode, '头像加载失败');
|
||||
throw ApiException(response.statusCode, failureMessage);
|
||||
}
|
||||
return response.bodyBytes.isEmpty ? null : response.bodyBytes;
|
||||
}
|
||||
@@ -144,6 +158,24 @@ class ApiClient {
|
||||
|
||||
Future<Object?> put(String path, {Map<String, Object?>? body}) => _send('PUT', path, body: body);
|
||||
|
||||
/// 上传头像二进制,沿用统一鉴权、错误解析和会话失效处理。
|
||||
Future<Object?> uploadAvatar(Uint8List bytes, String filename) async {
|
||||
return uploadImage('/upload/avatar', bytes, filename);
|
||||
}
|
||||
|
||||
/// 仅由仓储传入固定受控端点,沿用统一鉴权与图片体积约束。
|
||||
Future<Object?> uploadImage(String path, Uint8List bytes, String filename) async {
|
||||
if (bytes.isEmpty || bytes.length > 2 * 1024 * 1024) {
|
||||
throw const ApiException(422, '请选择不超过 2MB 的 JPG 或 PNG 图片');
|
||||
}
|
||||
final request = http.MultipartRequest('POST', Uri.parse('$baseUrl$path'));
|
||||
final token = _tokenProvider();
|
||||
request.headers['accept'] = 'application/json';
|
||||
if (token.isNotEmpty) request.headers['authorization'] = token;
|
||||
request.files.add(http.MultipartFile.fromBytes('file', bytes, filename: filename));
|
||||
return _decode(await _sendRequest(request), authenticated: true, requestToken: token);
|
||||
}
|
||||
|
||||
Future<Object?> delete(String path, {Map<String, Object?>? body}) =>
|
||||
_send('DELETE', path, body: body);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user