diff --git a/lib/config/cache.dart b/lib/config/cache.dart new file mode 100644 index 0000000..c5cd7b9 --- /dev/null +++ b/lib/config/cache.dart @@ -0,0 +1,8 @@ +// 是否启用缓存 +const CacheEnable = false; + +// 缓存的最长时间,单位(秒) +const CacheMaxAge = 1000; + +// 最大缓存数 +const CacheMaxCount = 100; \ No newline at end of file diff --git a/lib/config/server.dart b/lib/config/server.dart new file mode 100644 index 0000000..700de6c --- /dev/null +++ b/lib/config/server.dart @@ -0,0 +1,4 @@ +/// Api地址 +const String ServerApiUrl = "http://172.31.163.87:4523/m1/2998542-0-default"; + +const CryptoSalt = "E1pWsyfiy@R@X#qn17!StJNdZK1fFF8iV6ffN!goZkqt#JxO"; \ No newline at end of file diff --git a/lib/config/storage.dart b/lib/config/storage.dart new file mode 100644 index 0000000..f4cd3fd --- /dev/null +++ b/lib/config/storage.dart @@ -0,0 +1,14 @@ +/// 用户 - 配置信息 +const String StorageUserProfileKey = 'user_profile'; + +/// 用户 - 配置信息 +const String StorageUserTokenKey = 'user_token'; + +/// 设备是否第一次打开 +const String StorageDeviceFirstOpenKey = 'device_first_open'; + +/// 首页新闻cacheKey +const String StorageIndexNewsCacheKey= 'cache_index_news'; + +/// 多语言 +const String StorageLanguageCode = 'language_code'; \ No newline at end of file diff --git a/lib/data/model/sign_in.dart b/lib/data/model/sign_in.dart deleted file mode 100644 index f2ff4b8..0000000 --- a/lib/data/model/sign_in.dart +++ /dev/null @@ -1,6 +0,0 @@ -class UserLoginRequest { - String email; - String password; - - UserLoginRequest({required this.email, required this.password}); -} diff --git a/lib/data/model/user.dart b/lib/data/model/user.dart new file mode 100644 index 0000000..3cd738a --- /dev/null +++ b/lib/data/model/user.dart @@ -0,0 +1,70 @@ +// 注册请求 +class UserRegisterRequest { + String email; + String password; + + UserRegisterRequest({ + required this.email, + required this.password, + }); + + factory UserRegisterRequest.fromJson(Map json) => + UserRegisterRequest( + email: json["email"], + password: json["password"], + ); + + Map toJson() => { + "email": email, + "password": password, + }; +} + +// 登录请求 +class UserLoginRequest { + String email; + String password; + + UserLoginRequest({ + required this.email, + required this.password, + }); + + factory UserLoginRequest.fromJson(Map json) => + UserLoginRequest( + email: json["email"], + password: json["password"], + ); + + Map toJson() => { + "email": email, + "password": password, + }; +} + +// 登录返回 +class UserLoginResponse { + String? accessToken; + String? displayName; + List? channels; + + UserLoginResponse({ + this.accessToken, + this.displayName, + this.channels, + }); + + factory UserLoginResponse.fromJson(Map json) => + UserLoginResponse( + accessToken: json["access_token"], + displayName: json["display_name"], + channels: List.from(json["channels"].map((x) => x)), + ); + + Map toJson() => { + "access_token": accessToken, + "display_name": displayName, + "channels": + channels == null ? [] : List.from(channels!.map((x) => x)), + }; +} diff --git a/lib/data/provider/user.dart b/lib/data/provider/user.dart new file mode 100644 index 0000000..c818133 --- /dev/null +++ b/lib/data/provider/user.dart @@ -0,0 +1,40 @@ +// 用户 +import 'package:news_getx/data/model/user.dart'; +import 'package:news_getx/utils/http.dart'; + +class UserAPI { + /// 登录 + static Future login(UserLoginRequest? data) async { + UserLoginResponse response = await HttpUtil().post( + "/user/login", + data: data?.toJson(), + ); + return response; + } + + /// 注册 + static Future register(UserRegisterRequest request, { + UserRegisterRequest? data, + }) async { + var response = await HttpUtil().post( + '/user/register', + data: data?.toJson(), + ); + return UserRegisterRequest.fromJson(response); + } + + /// Profile + static Future profile() async { + var response = await HttpUtil().post( + '/user/profile', + ); + return UserLoginResponse.fromJson(response); + } + + /// Logout + static Future logout() async { + return await HttpUtil().post( + '/user/logout', + ); + } +} diff --git a/lib/data/repository/user_repository.dart b/lib/data/repository/user_repository.dart new file mode 100644 index 0000000..903cb0c --- /dev/null +++ b/lib/data/repository/user_repository.dart @@ -0,0 +1,23 @@ +import 'package:news_getx/data/model/user.dart'; +import 'package:news_getx/data/provider/user.dart'; + +class UserRepository { + Future login(UserLoginRequest request) { + return UserAPI.login(request); + } + + Future register( + UserRegisterRequest request, { + UserRegisterRequest? data, + }) { + return UserAPI.register(request, data: data); + } + + Future profile() { + return UserAPI.profile(); + } + + Future logout() { + return UserAPI.logout(); + } +} diff --git a/lib/data/services/config.dart b/lib/data/services/config.dart new file mode 100644 index 0000000..a7b47d9 --- /dev/null +++ b/lib/data/services/config.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:news_getx/config/storage.dart'; +import 'package:news_getx/data/services/storage.dart'; +import 'package:package_info_plus/package_info_plus.dart'; + +class ConfigService extends GetxService { + static ConfigService get to => Get.find(); + + bool isFirstOpen = false; + PackageInfo? _platform; + + String get version => _platform?.version ?? "-"; + bool get isRelease => bool.fromEnvironment("dart.vm.product"); + Locale locale = Locale("en", "US"); + + List languages = [ + Locale('en', 'US'), + Locale('zh', 'CN'), + ]; + + @override + void onInit() { + super.onInit(); + isFirstOpen = StorageService.to.getBool(StorageDeviceFirstOpenKey); + } + + Future getPlatform() async { + _platform = await PackageInfo.fromPlatform(); + } + + // 标记用户已打开APP + Future saveAlreadyOpen() { + return StorageService.to.setBool(StorageDeviceFirstOpenKey, false); + } + + void onInitLocale() { + String langCode = StorageService.to.getString(StorageLanguageCode); + if (langCode .isEmpty) return; + + int index = languages.indexWhere((element) { + return element.languageCode == langCode; + }); + if (index < 0) return; + locale = languages[index]; + } + + void onLocaleUpdate(Locale value) { + locale = value; + // 更新app的语言 + Get.updateLocale(value); + StorageService.to.setString(StorageLanguageCode, value.languageCode); + } +} \ No newline at end of file diff --git a/lib/data/services/storage.dart b/lib/data/services/storage.dart new file mode 100644 index 0000000..9239efa --- /dev/null +++ b/lib/data/services/storage.dart @@ -0,0 +1,41 @@ +import 'package:get/get.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + + +class StorageService extends GetxService { + static StorageService get to => Get.find(); + late final SharedPreferences _prefs; + + Future init() async { + _prefs = await SharedPreferences.getInstance(); + return this; + } + + Future setString(String key, String value) async { + return await _prefs.setString(key, value); + } + + Future setBool(String key, bool value) async { + return await _prefs.setBool(key, value); + } + + Future setList(String key, List value) async { + return await _prefs.setStringList(key, value); + } + + String getString(String key) { + return _prefs.getString(key) ?? ''; + } + + bool getBool(String key) { + return _prefs.getBool(key) ?? false; + } + + List getList(String key) { + return _prefs.getStringList(key) ?? []; + } + + Future remove(String key) async { + return await _prefs.remove(key); + } +} diff --git a/lib/data/services/user.dart b/lib/data/services/user.dart new file mode 100644 index 0000000..acbeb79 --- /dev/null +++ b/lib/data/services/user.dart @@ -0,0 +1,61 @@ +import 'dart:convert'; + +import 'package:get/get.dart'; +import 'package:news_getx/config/storage.dart'; +import 'package:news_getx/data/model/user.dart'; +import 'package:news_getx/data/provider/user.dart'; +import 'package:news_getx/data/services/storage.dart'; + +class UserService extends GetxService { + static UserService get to => Get.find(); + + // 是否登录 + var _isLogin = false.obs; + + // 令牌 token + String token = ""; + + // 用户 profile + var _profile = UserLoginResponse().obs; + + bool get hasToken => token.isNotEmpty; + + @override + void onInit() { + super.onInit(); + token = StorageService.to.getString(StorageUserTokenKey); + var profileOffline = StorageService.to.getString(StorageUserProfileKey); + if (profileOffline.isNotEmpty) { + _profile(UserLoginResponse.fromJson(jsonDecode(profileOffline))); + } + } + + // 保存 token + Future setToken(String value) async { + await StorageService.to.setString(StorageUserTokenKey, value); + token = value; + } + + // 获取 profile + Future getProfile() async { + if (token.isEmpty) return; + UserLoginResponse result = await UserAPI.profile(); + _profile(result); + _isLogin.value = true; + StorageService.to.setString(StorageUserProfileKey, jsonEncode(result)); + } + + // 保存 profile + Future saveProfile(UserLoginResponse profile) async { + _isLogin.value = true; + await StorageService.to.setString(StorageUserProfileKey, jsonEncode(profile)); + } + + // 注销 + Future onLogout() async { + if (_isLogin.value) await UserAPI.logout(); + await StorageService.to.remove(StorageUserTokenKey); + _isLogin.value = false; + token = ''; + } +} diff --git a/lib/modules/not_found/not_found_controller.dart b/lib/modules/not_found/not_found_controller.dart index 7c3953f..21e8d45 100644 --- a/lib/modules/not_found/not_found_controller.dart +++ b/lib/modules/not_found/not_found_controller.dart @@ -1,5 +1,9 @@ import 'package:get/get.dart'; class NotFoundController extends GetxController { + // title + final _title = "".obs; + set title(value) => _title.value = value; + get title => _title.value; } diff --git a/lib/modules/not_found/not_found_page.dart b/lib/modules/not_found/not_found_page.dart index 16c543a..ba7a6c6 100644 --- a/lib/modules/not_found/not_found_page.dart +++ b/lib/modules/not_found/not_found_page.dart @@ -8,6 +8,10 @@ class NotFoundPage extends GetView { @override Widget build(BuildContext context) { - return Container(); + return Obx(() { + return Center( + child: Obx(() => Text(controller.title)), + ); + }); } } diff --git a/lib/modules/sign_in/sign_in_binding.dart b/lib/modules/sign_in/sign_in_binding.dart index 54aeab2..fb1ac0d 100644 --- a/lib/modules/sign_in/sign_in_binding.dart +++ b/lib/modules/sign_in/sign_in_binding.dart @@ -1,10 +1,15 @@ import 'package:get/get.dart'; +import 'package:news_getx/data/repository/user_repository.dart'; import 'sign_in_controller.dart'; class SignInBinding extends Bindings { @override void dependencies() { - Get.lazyPut(() => SignInController()); + Get.lazyPut( + () => SignInController( + repository: UserRepository(), + ), + ); } } diff --git a/lib/modules/sign_in/sign_in_controller.dart b/lib/modules/sign_in/sign_in_controller.dart index 2d7e602..b57c517 100644 --- a/lib/modules/sign_in/sign_in_controller.dart +++ b/lib/modules/sign_in/sign_in_controller.dart @@ -1,17 +1,26 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; +import 'package:news_getx/data/model/user.dart'; +import 'package:news_getx/data/repository/user_repository.dart'; +import 'package:news_getx/data/services/user.dart'; import 'package:news_getx/modules/widgets/toast.dart'; import 'package:news_getx/routes/app_pages.dart'; +import 'package:news_getx/utils/security.dart'; import 'package:news_getx/utils/validator.dart'; class SignInController extends GetxController { + final UserRepository repository; + // email的控制器 final TextEditingController emailController = TextEditingController(); + // 密码的控制器 final TextEditingController passwordController = TextEditingController(); + SignInController({required this.repository}); + // 跳转 注册界面 - handleNavSignUp(){ + handleNavSignUp() { // TODO 注册页面 Get.toNamed(AppRoutes.Signup); } @@ -28,11 +37,20 @@ class SignInController extends GetxController { return; } - if(!checkStringLength(passwordController.text, 6)){ + if (!checkStringLength(passwordController.text, 6)) { toastInfo(msg: '密码不能小于6位'); return; } + UserLoginRequest request = UserLoginRequest( + email: emailController.text, + password: cryptoSha256(passwordController.text), + ); + + UserLoginResponse response = await repository.login(request); + + UserService.to.saveProfile(response); + Get.offAndToNamed(AppRoutes.Application); } diff --git a/lib/modules/sign_up/sign_up_binding.dart b/lib/modules/sign_up/sign_up_binding.dart index 61bf20d..c36f7e2 100644 --- a/lib/modules/sign_up/sign_up_binding.dart +++ b/lib/modules/sign_up/sign_up_binding.dart @@ -1,10 +1,13 @@ import 'package:get/get.dart'; +import 'package:news_getx/data/repository/user_repository.dart'; import 'sign_up_controller.dart'; class SignUpBinding extends Bindings { @override void dependencies() { - Get.lazyPut(() => SignUpController()); + Get.lazyPut(() => SignUpController( + repository: UserRepository(), + )); } } diff --git a/lib/modules/sign_up/sign_up_controller.dart b/lib/modules/sign_up/sign_up_controller.dart index c621688..5caa0d5 100644 --- a/lib/modules/sign_up/sign_up_controller.dart +++ b/lib/modules/sign_up/sign_up_controller.dart @@ -1,23 +1,32 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:news_getx/data/model/user.dart'; +import 'package:news_getx/data/repository/user_repository.dart'; import 'package:news_getx/modules/widgets/toast.dart'; import 'package:news_getx/routes/app_pages.dart'; +import 'package:news_getx/utils/security.dart'; import 'package:news_getx/utils/validator.dart'; class SignUpController extends GetxController { + final UserRepository repository; + // email的控制器 final TextEditingController fullNameController = TextEditingController(); + // email的控制器 final TextEditingController emailController = TextEditingController(); + // 密码的控制器 final TextEditingController passwordController = TextEditingController(); + SignUpController({required this.repository}); + // 返回上一级 handleNavPop() { Get.back(); } - handleRegister() { + handleRegister() async { if (!checkStringLength(fullNameController.text, 5)) { toastInfo(msg: '用户名不能小于5位'); return; @@ -31,7 +40,12 @@ class SignUpController extends GetxController { return; } - toastInfo(msg: '注册成功'); + UserRegisterRequest params = UserRegisterRequest( + email: emailController.text, password: cryptoSha256(passwordController.text), + ); + + await repository.register(params); + Get.back(); } diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 8f1a93e..2fcfb82 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -1,4 +1,6 @@ import 'package:get/get.dart'; +import 'package:news_getx/modules/not_found/not_found_binding.dart'; +import 'package:news_getx/modules/not_found/not_found_page.dart'; import 'package:news_getx/modules/sign_in/sign_in_binding.dart'; import 'package:news_getx/modules/sign_in/sign_in_page.dart'; import 'package:news_getx/modules/sign_up/sign_up_binding.dart'; @@ -10,6 +12,7 @@ part './app_routes.dart'; abstract class AppPages { static final pages = [ + // 免登陆 GetPage( name: AppRoutes.Initial, page: () => WelcomePage(), @@ -24,6 +27,11 @@ abstract class AppPages { name: AppRoutes.Signup, page: () => SignUpPage(), binding: SignUpBinding(), - ) + ), + GetPage( + name: AppRoutes.NotFound, + page: () => NotFoundPage(), + binding: NotFoundBinding(), + ), ]; } diff --git a/lib/utils/http.dart b/lib/utils/http.dart new file mode 100644 index 0000000..e8d41cd --- /dev/null +++ b/lib/utils/http.dart @@ -0,0 +1,379 @@ +import 'package:dio/dio.dart'; +import 'package:get/get.dart' hide FormData; +import 'package:news_getx/config/cache.dart'; +import 'package:news_getx/config/server.dart'; +import 'package:cookie_jar/cookie_jar.dart'; +import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:news_getx/data/services/user.dart'; +import 'package:news_getx/utils/loading.dart'; + +/* + * http 操作类 + * + * 手册 + * https://github.com/cfug/dio/blob/main/dio/README-ZH.md + * + * 从 4 升级到 5 + * https://github.com/cfug/dio/blob/main/dio/migration_guide.md +*/ +class HttpUtil { + late Dio dio; + CancelToken cancelToken = CancelToken(); + + // 每次调用都会重新生成一个dio对象 + static HttpUtil _instance = HttpUtil._internal(); + + factory HttpUtil() => _instance; + + HttpUtil._internal() { + // BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 + BaseOptions options = BaseOptions( + // 请求基地址,可以包含子路径 + baseUrl: ServerApiUrl, + //连接服务器超时时间,单位是毫秒. + connectTimeout: Duration(seconds: 10), + // 响应流上前后两次接受到数据的间隔,单位为毫秒。 + receiveTimeout: Duration(seconds: 5), + // Http请求头. + headers: {}, + + /// 请求的Content-Type,默认值是"application/json; charset=utf-8". + /// 如果您想以"application/x-www-form-urlencoded"格式编码请求数据, + /// 可以设置此选项为 `Headers.formUrlEncodedContentType`, 这样[Dio] + /// 就会自动编码请求体. + contentType: "application/json; charset=utf-8", + + /// [responseType] 表示期望以那种格式(方式)接受响应数据。 + /// 目前 [ResponseType] 接受三种类型 `JSON`, `STREAM`, `PLAIN`. + /// + /// 默认值是 `JSON`, 当响应头中content-type为"application/json"时,dio 会自动将响应内容转化为json对象。 + /// 如果想以二进制方式接受响应数据,如下载一个二进制文件,那么可以使用 `STREAM`. + /// + /// 如果想以文本(字符串)格式接收响应数据,请使用 `PLAIN`. + responseType: ResponseType.json, + ); + + dio = Dio(options); + + // Cookie管理 + CookieJar cookieJar = CookieJar(); + // 增加拦截器CookieManager + dio.interceptors.add(CookieManager(cookieJar)); + + // 添加拦截器 + dio.interceptors.add(InterceptorsWrapper( + onRequest: (RequestOptions options, RequestInterceptorHandler handler) { + // Do something before request is sent + return handler.next(options); + // 如果你想完成请求并返回一些自定义数据,你可以resolve一个Response对象 `handler.resolve(response)`。 + // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response. + // + // 如果你想终止请求并触发一个错误,你可以返回一个`DioException`对象,如`handler.reject(error)`, + // 这样请求将被中止并触发异常,上层catchError会被调用。 + }, + onResponse: (response, handler) { + // Do something with response data + return handler.next(response); + // 如果你想终止请求并触发一个错误,你可以 reject 一个`DioException`对象,如`handler.reject(error)`, + // 这样请求将被中止并触发异常,上层catchError会被调用。 + }, + onError: (DioException err, ErrorInterceptorHandler handler) { + // Do something with response error + Loading.dismiss(); + ErrorEntity entity = createErrorEntity(err); + onError(entity); + return handler.next(err); + // 如果你想完成请求并返回一些自定义数据,可以resolve 一个`Response`,如`handler.resolve(response)`。 + // 这样请求将会被终止,上层then会被调用,then中返回的数据将是你的自定义response. + }, + )); + } + + /// 统一错误处理 + void onError(ErrorEntity errorEntity) { + print( + 'error.code -> ${errorEntity.code}, error.message -> ${errorEntity.message}'); + switch (errorEntity.code) { + case 401: + // 会话过期 返回到登录页 + UserService.to.onLogout(); + EasyLoading.showError(errorEntity.message); + break; + default: + EasyLoading.showError('未知错误'); + break; + } + } + + // 错误信息 + ErrorEntity createErrorEntity(DioException error) { + switch (error.type) { + case DioExceptionType.cancel: + return ErrorEntity(code: -1, message: "请求取消"); + case DioExceptionType.connectionTimeout: + return ErrorEntity(code: -1, message: "连接超时"); + case DioExceptionType.sendTimeout: + return ErrorEntity(code: -1, message: "请求超时"); + case DioExceptionType.receiveTimeout: + return ErrorEntity(code: -1, message: "响应超时"); + case DioExceptionType.badResponse: + { + try { + int errCode = + error.response != null ? error.response!.statusCode! : -1; + // String errMsg = error.response.statusMessage; + // return ErrorEntity(code: errCode, message: errMsg); + switch (errCode) { + case 400: + return ErrorEntity(code: errCode, message: "请求语法错误"); + case 401: + return ErrorEntity(code: errCode, message: "没有权限"); + case 403: + return ErrorEntity(code: errCode, message: "服务器拒绝执行"); + case 404: + return ErrorEntity(code: errCode, message: "无法连接服务器"); + case 405: + return ErrorEntity(code: errCode, message: "请求方法被禁止"); + case 500: + return ErrorEntity(code: errCode, message: "服务器内部错误"); + case 502: + return ErrorEntity(code: errCode, message: "无效的请求"); + case 503: + return ErrorEntity(code: errCode, message: "服务器挂了"); + case 505: + return ErrorEntity(code: errCode, message: "不支持HTTP协议请求"); + default: + { + // return ErrorEntity(code: errCode, message: "未知错误"); + return ErrorEntity( + code: errCode, + message: error.response != null + ? error.response!.statusMessage! + : "", + ); + } + } + } on Exception catch (_) { + return ErrorEntity(code: -1, message: "未知错误"); + } + } + default: + { + return ErrorEntity(code: -1, message: error.message ?? "未定义错误"); + } + } + } + + /// 取消请求 + /// 同一个cancel token 可以用于多个请求,当一个cancel token取消时,所有使用该cancel token的请求都会被取消。 + /// 所以参数可选 + void cancelRequests(CancelToken token) { + token.cancel("cancelled"); + } + + /// 读取本地配置 + Map? getAuthorizationHeader() { + var headers = {}; + if (Get.isRegistered() && UserService.to.hasToken) { + headers['Authorization'] = 'Bearer ${UserService.to.token}'; + } + return headers; + } + + /// restful get 操作 + /// refresh 是否下拉刷新 默认 false + /// noCache 是否不缓存 默认 true + /// list 是否列表 默认 false + /// cacheKey 缓存key + /// cacheDisk 是否磁盘缓存 + Future get( + String path, { + Map? queryParameters, + Options? options, + bool refresh = false, + bool noCache = !CacheEnable, + bool list = false, + String cacheKey = '', + bool cacheDisk = false, + }) async { + Options requestOptions = options ?? Options(); + // 设置自定义字段 用于后续使用 + requestOptions.extra ??= { + "refresh": refresh, + "noCache": noCache, + "list": list, + "cacheKey": cacheKey, + "cacheDisk": cacheDisk, + }; + // 设置头信息 + requestOptions.headers ??= {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + + var response = await dio.get( + path, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful post 操作 + Future post( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + var response = await dio.post( + path, + data: data, + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful put 操作 + Future put( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + var response = await dio.put( + path, + data: data, + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful patch 操作 + Future patch( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + var response = await dio.patch( + path, + data: data, + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful delete 操作 + Future delete( + String path, { + dynamic data, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + var response = await dio.delete( + path, + data: data, + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful post form 表单提交操作 + Future postForm( + String path, { + required Map data, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + var response = await dio.post( + path, + data: FormData.fromMap(data), + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } + + /// restful post Stream 流数据 + Future postStream( + String path, { + dynamic data, + int dataLength = 0, + Map? queryParameters, + Options? options, + }) async { + Options requestOptions = options ?? Options(); + requestOptions.headers = requestOptions.headers ?? {}; + Map? authorization = getAuthorizationHeader(); + if (authorization != null) { + requestOptions.headers!.addAll(authorization); + } + requestOptions.headers!.addAll({ + Headers.contentLengthHeader: dataLength.toString(), + }); + var response = await dio.post( + path, + data: Stream.fromIterable(data.map((e) => [e])), + queryParameters: queryParameters, + options: requestOptions, + cancelToken: cancelToken, + ); + return response.data; + } +} + +class ErrorEntity implements Exception { + int code = -1; + String message = ""; + + ErrorEntity({required this.code, required this.message}); + + @override + String toString() { + if (message.isEmpty) return "Exception"; + return "Exception: code $code, $message"; + } +} diff --git a/lib/utils/loading.dart b/lib/utils/loading.dart new file mode 100644 index 0000000..c312bc1 --- /dev/null +++ b/lib/utils/loading.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; + +class Loading { + Loading() { + // 调整loading的配置 + EasyLoading.instance + ..displayDuration = const Duration(milliseconds: 2000) + ..indicatorType = EasyLoadingIndicatorType.ring + ..loadingStyle = EasyLoadingStyle.custom + ..indicatorSize = 35.0 + ..lineWidth = 2 + ..radius = 10.0 + ..progressColor = Colors.white + ..backgroundColor = Colors.black.withOpacity(0.7) + ..indicatorColor = Colors.white + ..textColor = Colors.white + ..maskColor = Colors.black.withOpacity(0.6) + ..userInteractions = true + ..dismissOnTap = false + ..maskType = EasyLoadingMaskType.custom; + } + + static void show([String? text]) { + // 应在显示加载时允许用户交互 + EasyLoading.instance.userInteractions = false; + EasyLoading.show(status: text ?? "Loading..."); + } + + static void toast(String text) { + EasyLoading.showToast(text); + } + + static void dismiss() { + EasyLoading.instance.userInteractions = true; + EasyLoading.dismiss(); + } +} \ No newline at end of file diff --git a/lib/utils/security.dart b/lib/utils/security.dart new file mode 100644 index 0000000..3870dcf --- /dev/null +++ b/lib/utils/security.dart @@ -0,0 +1,10 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; +import 'package:news_getx/config/server.dart'; + +String cryptoSha256(String input) { + var bytes = utf8.encode(input + CryptoSalt); + var digest = sha256.convert(bytes); + return digest.toString(); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 5cefca1..acf7fdf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: "direct main" description: name: cookie_jar - sha256: d1cc6516a190ba667941f722b6365d202caff3dacb38de24268b8d6ff1ec8a1d + sha256: "1024f3a52bee181fc8f10f4a359085471587d7406323090a53a7171bc0aa5c37" url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "4.0.5" crypto: dependency: "direct main" description: @@ -109,18 +109,18 @@ packages: dependency: "direct main" description: name: dio - sha256: "7d328c4d898a61efc3cd93655a0955858e29a0aa647f0f9e02d59b3bb275e2e8" + sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8 url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.6" + version: "5.2.1+1" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: ed7ee3ba6cdb54599c8984d5a4ce09675c553ead6c28608eb54e38eec5b4f954 + sha256: c4b7a693aa09efd694a5c5e12065daa5e026647b106245281ed1042b3ebefb8f url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "3.1.0" fake_async: dependency: transitive description: @@ -317,14 +317,22 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" - package_info: + package_info_plus: dependency: "direct main" description: - name: package_info - sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75" + name: package_info_plus + sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "4.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" path: dependency: transitive description: @@ -594,6 +602,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.2" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 75e90f7..742dbae 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,9 +46,9 @@ dependencies: flutter_screenutil: ^5.7.0 # http - dio: ^4.0.0 - dio_cookie_manager: ^2.0.0 - cookie_jar: ^3.0.1 + dio: ^5.2.1+1 + dio_cookie_manager: ^3.1.0 + cookie_jar: ^4.0.5 # 日期 intl: ^0.18.0 @@ -80,7 +80,7 @@ dependencies: device_info: ^2.0.0 # 包信息 - package_info: ^2.0.0 + package_info_plus: ^4.0.2 # 路径查询 path_provider: ^2.0.1