init method

void init({
  1. required String baseUrl,
  2. required String aesKey,
  3. Duration? connectTimeout,
  4. String? acceptLanguage,
})

初始化 Dio,baseUrl 例如 http://127.0.0.1:9501 acceptLanguage 默认 zh-CN,有值则以客户端传入为准。

Implementation

void init({
  required String baseUrl,
  required String aesKey,
  Duration? connectTimeout,
  String? acceptLanguage,
}) {
  if (aesKey.isEmpty) {
    throw GoChatException('aesKey 不能为空(须与后端 app.aes_key 一致)');
  }
  _baseUrl = baseUrl.endsWith('/')
      ? baseUrl.substring(0, baseUrl.length - 1)
      : baseUrl;
  _aesKey = aesKey;
  _acceptLanguage = (acceptLanguage == null || acceptLanguage.isEmpty)
      ? defaultAcceptLanguage
      : acceptLanguage;
  _dio = Dio(
    BaseOptions(
      baseUrl: _baseUrl,
      connectTimeout: connectTimeout ?? const Duration(seconds: 15),
      receiveTimeout: const Duration(seconds: 30),
      sendTimeout: const Duration(seconds: 30),
      contentType: Headers.jsonContentType,
      responseType: ResponseType.json,
      headers: {
        'Accept': 'application/json',
        'Accept-Language': _acceptLanguage,
      },
    ),
  );
  _dio!.interceptors.add(
    InterceptorsWrapper(
      onRequest: (options, handler) async {
        options.headers['Accept-Language'] = _acceptLanguage;
        if (_token != null && _token!.isNotEmpty) {
          options.headers['Authorization'] = 'Bearer $_token';
        }
        final data = options.data;
        // FormData 明文 multipart:不加密、不带 AES 头,交由 Dio 设置 boundary
        if (data is FormData) {
          options.headers.remove(Headers.contentTypeHeader);
          options.headers.remove(kAesContentEncodingHeader);
        } else {
          options.headers[kAesContentEncodingHeader] =
              kAesContentEncodingValue;
          if (data != null) {
            final plain = data is String
                ? data
                : jsonEncode(data is Map ? data : {'data': data});
            try {
              options.data = await AesEnvelope.encrypt(plain, _aesKey);
            } catch (e, st) {
              handler.reject(
                DioException(
                  requestOptions: options,
                  error: e,
                  stackTrace: st,
                ),
              );
              return;
            }
          }
        }
        GoChatLogger.d('HTTP → ${options.method} ${options.uri}');
        handler.next(options);
      },
      onResponse: (response, handler) async {
        // 滑动刷新 Token
        final newToken = response.headers.value('Refresh-Access-Token');
        if (newToken != null && newToken.isNotEmpty) {
          setToken(newToken);
          onTokenRefreshed?.call(newToken);
        }
        try {
          response.data = await _maybeDecrypt(response);
          handler.next(response);
        } catch (e, st) {
          handler.reject(
            DioException(
              requestOptions: response.requestOptions,
              response: response,
              error: e,
              stackTrace: st,
            ),
          );
        }
      },
      onError: (error, handler) async {
        final status = error.response?.statusCode;
        if (status == 401) {
          onUnauthorized?.call();
        }
        if (error.response?.data != null) {
          try {
            error.response!.data = await _maybeDecrypt(error.response!);
          } catch (_) {}
        }
        handler.next(error);
      },
    ),
  );
}