# my_salary **Repository Path**: liyuncc/my_salary ## Basic Information - **Project Name**: my_salary - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-08-03 - **Last Updated**: 2025-10-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # my_salary ## 📑 目录 - [my_salary](#my_salary) - [📑 目录](#-目录) - [📖 项目简介](#-项目简介) - [🧩 项目架构模块介绍](#-项目架构模块介绍) - [⚙️ 环境准备](#️-环境准备) - [🚀 构建与运行](#-构建与运行) - [JDK配置修改 (可选/删除)](#jdk配置修改-可选删除) - [初始化依赖](#初始化依赖) - [清理缓存](#清理缓存) - [运行调试 (或使用Android Studio Run)](#运行调试-或使用android-studio-run) - [打包发布](#打包发布) - [生成应用图标](#生成应用图标) - [查看 Flutter 项目的依赖关系图](#查看-flutter-项目的依赖关系图) - [网络安全配置(Android)](#网络安全配置android) - [🏗️ 项目架构](#️-项目架构) - [🔑 项目初始化](#-项目初始化) - [🖼️ 页面架构](#️-页面架构) - [🌐 网络请求封装](#-网络请求封装) - [📦 响应结构](#-响应结构) - [✅ 通用响应结构](#-通用响应结构) - [✅ 分页响应结构](#-分页响应结构) - [🚨 异常封装及处理](#-异常封装及处理) - [🧠 ViewModel 使用方式](#-viewmodel-使用方式) - [🔗 WebView \& RPC 通信](#-webview--rpc-通信) - [使用 WebViewWidget 组件加载网页](#使用-webviewwidget-组件加载网页) - [📝 代码规范 \& Git 规范](#-代码规范--git-规范) - [代码规范](#代码规范) - [Git 规范](#git-规范) - [🧪 测试与 QA](#-测试与-qa) - [单元测试](#单元测试) - [集成测试](#集成测试) - [QA 流程](#qa-流程) - [⚡ CI/CD 与发布流程](#-cicd-与发布流程) - [🤝 贡献指南](#-贡献指南) - [❓ FAQ](#-faq) - [📚 推荐阅读](#-推荐阅读) ## 📖 项目简介 my_salary 是一款面向薪水记账的综合应用,基于 Flutter 构建,采用 Provider + ViewModel 架构,支持跨平台运行(Android / iOS)。 --- ## 🧩 项目架构模块介绍 **core/:通用基础设施,提供应用运行所需的底层能力。** - core/auth/:认证相关,包含 token 管理、用户会话、用户信息。 - core/capabilities/:设备能力封装,如生物识别。 - core/concurrency/:并发与后台任务管理。 - core/config/:环境配置。 - core/crypto/:加解密工具方法。 - core/log/:日志打印工具。 - core/secure/:安全存储封装。 - app_config.dart / app_initializer.dart:全局配置与应用初始化入口。 **http/:网络层封装,统一管理 HTTP 客户端、拦截器、协议模型。** - http/interceptor/:拦截器(日志、REST API)。 - HTTP 客户端实例、基础模型、服务配置。 **api/:REST 接口调用层,定义请求/响应模型,直接对接后端。** - api/models/:请求/响应模型。 - 具体接口调用文件。 **service/:业务服务层,组合多个 API 和存储,封装跨模块逻辑。** - 本地通知服务、推送消息服务。 **storage/:应用级存储(缓存、偏好、持久化)。** - 包含缓存管理、偏好设置管理、存储键管理。 **exception/:异常集中管理。** - 包含应用异常、认证异常、全局错误处理。 **rpc/:远程调用协议与传输层。** - rpc/protocol/:协议定义与客户端封装。 - rpc/transport/:传输实现(如 in-app webview、通用传输)。 **pages/:UI 层,按业务模块组织,包含页面和 ViewModel。** - 遵循 MVVM,UI 与逻辑分离。 - 按业务域划分,降低耦合。 - 抽屉菜单、启动页、业务模块页面等。 **component/:可复用 UI 组件。** - component/card/:卡片组件。 - component/dialog/:对话框组件。 - component/shortcut_grid/:快捷网格组件。 - component/title/:标题组件。 - component/web/:WebView 页面与组件。 - 图标等。 **skeleton/:骨架屏组件。** - 包含列表、卡片、网格、包装器等骨架屏。 **style/:设计系统,包含主题、tokens、响应式适配。** - style/colors/:颜色定义。 - style/theme/:主题、响应式、扩展。 - style/tokens/:设计 tokens(颜色、字体、间距、尺寸、半径、文本样式等)。 **l10n/:国际化资源文件。** - 包含多语言 arb 文件。 **route/:路由集中管理。** - 包含路由表与路由工具。 **provider/:第三方 Provider 封装。** - provider/jpush/:极光推送 Provider。 **utils/:轻量工具函数。** - 包含颜色工具、字符串工具。 **入口文件:** - main.dart:应用启动入口。 - app.dart:应用根组件,挂载全局配置与路由 --- ## ⚙️ 环境准备 在开始之前,请确保本地环境满足以下要求: - Flutter SDK:推荐版本 3.24.0 - Dart SDK:随 Flutter 安装 - Android Studio / VS Code:安装 Flutter 插件 - JDK:版本 17 - Gradle Wrapper:版本 8.7(项目已内置) - Android Gradle Plugin (AGP):8.5.0 - Kotlin Gradle Plugin:2.0.21 - 依赖工具:git、node(如需前端工具链)、CocoaPods(iOS 构建) 快速检查环境: ```shell sh check_env.sh ``` --- ## 🚀 构建与运行 ### JDK配置修改 (可选/删除) 修改 `android/gradle.properties` jdk配置 ```properties ## use local jdk, not from envs, because envs may be changed by other tools org.gradle.java.home=D\:\\Packages\\Java\\TemurinJDK17 ``` ### 初始化依赖 ```shell flutter pub get ``` ### 清理缓存 ```shell flutter clean && (cd android && ./gradlew clean) ``` ### 运行调试 (或使用Android Studio Run) ```shell flutter run ``` ### 打包发布 ```shell # Android Release APK 禁用图标摇树优化 flutter build apk --release --no-tree-shake-icons # iOS Release flutter build ios --release ``` ### 生成应用图标 ```shell flutter pub get dart run flutter_launcher_icons ``` ### 查看 Flutter 项目的依赖关系图 ```shell flutter pub deps flutter pub deps --style=compact > deps.txt flutter pub deps --style=compact --dot > deps.dot ``` ### 网络安全配置(Android) 启用明文流量,在 `res/xml/network_security_config.xml`允许的域名/IP: ```xml 192.168.1.7 ``` --- ## 🏗️ 项目架构 ```text lib/ ├── api/ # 接口模型与参数定义 ├── component/ # 通用 UI 组件(跨页面复用) ├── core/ # 核心配置(常量、初始化、环境) ├── exception/ # 异常处理(错误模型、统一封装) ├── http/ # 网络请求封装(Dio、拦截器) ├── l10n/ # 国际化资源(Flutter Gen 自动生成) ├── pages/ # 页面模块(按业务功能划分) ├── provider/ # 全局 Provider 注入与组合 ├── route/ # 路由定义与跳转逻辑 ├── rpc/ # Rpc通信(flutter与js通信封装) ├── service/ # 第三方服务封装(如推送、定位) ├── skeleton/ # 骨架屏(加载) ├── storage/ # 本地存储(如 SharedPreferences、Hive) ├── style/ # 主题、颜色、字体、尺寸等视觉规范 ├── utils/ # 工具类(如日期、字符串、权限判断) ├── app.dart # 应用入口配置(如 MaterialApp) └── main.dart # 程序主入口(main 函数) ``` --- ## 🔑 项目初始化 `app_initializer.dart` app核心初始化配置,`logger`、`store`、`theme`、`locale`、`screen`、`jpush`等核心配置全由该文件加载配置。 ```dart class AppInitializer { AppInitializer._(); static final ThemeController themeController = ThemeController(); static final LocaleController localeController = LocaleController(); static late SharedPreferences prefs; static late CacheManager cacheManager; static late Logger logger; /// .... } ``` 打印日志: ```dart AppInitializer.logger.i("通知被点击: $message"); ``` 本地存储: ```dart AppPrefsManager.set(PrefsKeys.language, locale.languageCode); AppPrefsManager.get(PrefsKeys.themeMode); AppCacheManager.get>>(cacheKey); AppCacheManager.set>>( cacheKey, cacheData, ttl: const Duration(hours: 1), ); ``` --- ## 🖼️ 页面架构 项目采用 Provider + ViewModel 架构,页面结构建议如下: ```text pages/ └── home/ ├── home_page.dart # 页面入口(StatelessWidget/StatefulWidget) ├── home_vm.dart # 页面 ViewModel(ChangeNotifier) ├── home_body.dart # 页面主体(UI 构建) └── widgets/ # 页面私有组件 ├── shortcut_grid.dart ├── stat_card.dart └── category_card.dart ``` ViewModel 负责状态管理与业务逻辑,页面仅负责展示与触发回调。 --- ## 🌐 网络请求封装 项目使用 `dio`,统一封装为 `DioFactory`,维护 `service_config`, 按服务名创建并缓存独立的 `Dio` 实例, 支持`请求响应日志拦截`、`全局异常处理`、`token注入`、 `HMAC签名`、`token刷新`。 维护多服务实例: `lib/http/service_config.dart` ```dart class ServiceConfig { /// 服务名常量(建议统一管理,避免硬编码) static const String authCenter = "auth-center"; static const String waterQuality = "water-quality"; /// 服务名 → Base URL 映射 static final Map _baseUrls = { authCenter: "http://182.244.0.12:9901", waterQuality: "http://113.249.103.73:9888", }; /// 获取 Base URL static String getBaseUrl(String serviceName) { if (!_baseUrls.containsKey(serviceName)) { throw Exception("ServiceConfig: 未配置服务 [$serviceName] 的 baseUrl"); } return _baseUrls[serviceName]!; } } ``` 封装服务请求实例:`lib/api/api_xxx.dart` ```dart class ApiAuthCenter { static const String serverName = "auth-center"; ApiAuthCenter._(); static ApiAuthCenter api = ApiAuthCenter._(); /// 获取密码加密公钥 Future getPublicKey() async { Response response = await DioFactory.getInstance(serverName) .get("/api/auth/sm2/publicKey?action=passwordEncrypt"); final gRsp = GeneralResponse.fromJson( response.data, (json) => PublicKeyResponse.fromJson(json)); return gRsp.data; } } ``` --- ## 📦 响应结构 ### ✅ 通用响应结构 所有接口返回结构统一封装 `GeneralResponse`,包含: ```dart /// 通用响应 class GeneralResponse extends AbstractResponse { /// 泛型数据 T? data; GeneralResponse({ this.data, num? code, String? message, String? timestamp, }) { this.code = code; this.message = message; this.timestamp = timestamp; } /// 业务成功判断 bool get success => code == 200 || code == 0; /// 泛型 fromJson factory GeneralResponse.fromJson( Map json, T Function(dynamic json) fromJsonT, ) { return GeneralResponse( code: json['code'], message: json['message'], timestamp: json['timestamp'], data: json['data'] != null ? fromJsonT(json['data']) : null, ); } /// toJson Map toJson(Map Function(T value) toJsonT) { return { 'code': code, 'message': message, 'timestamp': timestamp, 'data': data != null ? toJsonT(data as T) : null, }; } } ``` --- ### ✅ 分页响应结构 分页接口返回结构为 `PageableResponse`,包含: ```dart /// 通用分页响应 class PageableResponse extends AbstractResponse { Pagination pagination; List data; PageableResponse({ required this.pagination, required this.data, }); factory PageableResponse.fromJson( Map json, T Function(dynamic) fromJsonT, ) { return PageableResponse( pagination: Pagination.fromJson(json['pagination'] ?? {}), data: (json['data'] as List? ?? []) .map((e) => fromJsonT(e)) .toList(), ) ..code = json['code'] ..message = json['message'] ..timestamp = json['timestamp']; } Map toJson(Map Function(T) toJsonT) { return { 'code': code, 'message': message, 'timestamp': timestamp, 'pagination': pagination.toJson(), 'data': data.map((e) => toJsonT(e)).toList(), }; } } /// Pagination class Pagination { int total; int pageNum; int pageSize; int totalPages; bool hasNext; bool hasPrev; int first; int last; Pagination({ required this.total, required this.pageNum, required this.pageSize, required this.totalPages, required this.hasNext, required this.hasPrev, required this.first, required this.last, }); factory Pagination.fromJson(Map json) { return Pagination( total: json['total'] ?? 0, pageNum: json['pageNum'] ?? 1, pageSize: json['pageSize'] ?? 20, totalPages: json['totalPages'] ?? 0, hasNext: json['hasNext'] ?? false, hasPrev: json['hasPrev'] ?? false, first: json['first'] ?? 1, last: json['last'] ?? 1, ); } Map toJson() { return { 'total': total, 'pageNum': pageNum, 'pageSize': pageSize, 'totalPages': totalPages, 'hasNext': hasNext, 'hasPrev': hasPrev, 'first': first, 'last': last, }; } } ``` 在 `api_xxx` 、`viewModel` 中使用方式示例: ```dart /// 通用响应 Future getPublicKey() async { Response response = await DioFactory.getInstance(serverName) .get("/api/auth/sm2/publicKey?action=passwordEncrypt"); final gRsp = GeneralResponse.fromJson( response.data, (json) => PublicKeyResponse.fromJson(json)); return gRsp.data; } /// 分页响应 Future> searchAllTodos( Map data) async { Response response = await DioFactory.getInstance(serverName) .post("/waterqua/rest/app/api/todos/search", data: data); return PageableResponse.fromJson( response.data, (json) => TodoItemModel.fromJson(json), ); } /// viewModel 中调用 PageableResponse pResp = await ApiWaterQuality.api.searchAllTodos(queryBody); ``` --- ## 🚨 异常封装及处理 分为系统异常 `core_exceptions.dart` 和业务异常 `exception_model.dart`; 全局异常捕获使用实例: ```dart try { // do something } catch (e) { // 这里可以直接 showToast 或者用全局错误提示 GlobalErrorHandler().handleError(e); } ``` 最终异常信息会议toast的形式提示: ```dart void handleError(dynamic error) { String message = '未知错误'; // handler error type logger.e("GlobalErrorHandler: $message", error: error); showToast(message); } ``` --- ## 🧠 ViewModel 使用方式 所有异步逻辑封装在 ViewModel 中,状态字段包括: - `isLoading` - `isError` - `errorMessage` - `isEmpty` 示例: ```dart Future fetchTodos({bool append = false}) async { _isLoading = true; notifyListeners(); try { var queryBody = { "searchInput": "", "startTime": _query.filter?.startDate?.toIso8601String() ?? "", "endTime": _query.filter?.endDate?.toIso8601String() ?? "", "pageNum": _query.page, "pageSize": _query.pageSize, "orderByColumn": _query.sortField.label, "isAsc": _query.sortOrder.name, }; PageableResponse pResp = await ApiWaterQuality.api.searchAllTodos(queryBody); if (append) { _todos.addAll(pResp.data); } else { _todos = pResp.data; } _isEmpty = _todos.isEmpty; _isError = false; _hasMore = pResp.pagination.hasNext; } catch (e) { _isError = true; GlobalErrorHandler().handleError(e); } finally { _isLoading = false; notifyListeners(); } } ``` ## 🔗 WebView & RPC 通信 ### 使用 WebViewWidget 组件加载网页 ```dart @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final cs = Theme.of(context).colorScheme; final tt = Theme.of(context).textTheme; return Scaffold( appBar: AppBar( title: Text(l10n.menuFormFill, style: tt.titleLarge), backgroundColor: cs.surface, elevation: 0.5, ), body: SafeArea( child: WebViewWidget( webViewType: WebViewType.URL, loadResource: "http://192.168.55.61:8080", onBeforeLoad: (controller) async { // 在加载前注入脚本 await controller.evaluateJavascript( source: "console.log('before load')"); }, onWebViewCreated: (controller) { // 1. 创建 Transport transport = InAppWebViewTransport(controller); // 2. 创建 RpcClient _rpc = JsonRpcClient(transport!); // 3. 注册 Dart 方法供 JS 调用 _rpc!.register('v1.app.echo', (params) async { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('JS send message: $params')), ); return {'echo': params['text'] ?? ''}; }); }, onRpcMessage: (payload) { transport?.onMessage?.call(payload); }, ), ), floatingActionButton: FloatingActionButton( onPressed: () async { final res = await _rpc!.call('v1.js.sum', params: {'a': 2, 'b': 3}); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('JS sum result: $res')), ); }, child: const Icon(Icons.send), ), ); } ``` ## 📝 代码规范 & Git 规范 ### 代码规范 - 遵循 **Dart 官方风格指南**,启用 `flutter_lints`。 - 文件命名:统一使用 **小写+下划线**(如 `home_page.dart`)。 - 类命名:使用 **大驼峰**(如 `HomeViewModel`)。 - 变量命名:使用 **小驼峰**(如 `isLoading`)。 - 常量命名:使用 **大写+下划线**(如 `MAX_PAGE_SIZE`)。 - 每个页面模块必须包含:`xxx_page.dart`、`xxx_vm.dart`、`xxx_body.dart`、`widgets/`。 - 网络请求必须通过 `DioFactory`,禁止直接实例化 `Dio`。 - 异常必须通过 `GlobalErrorHandler` 统一处理。 ### Git 规范 - 分支策略:推荐 **Git Flow** 或 **Feature Branch** 模式。 - `main`:稳定分支,对应线上版本。 - `develop`:开发分支,合并新功能。 - `feature/*`:功能分支,如 `feature/login-page`。 - `hotfix/*`:紧急修复分支。 - Commit Message 规范(Conventional Commits): - `feat:` 新功能 - `fix:` 修复 bug - `docs:` 文档更新 - `style:` 格式调整(不影响逻辑) - `refactor:` 重构 - `test:` 测试相关 - `chore:` 构建/依赖/工具变更 示例: ```shell feat: 新增用户登录页面 fix: 修复网络请求超时导致的崩溃 ``` --- ## 🧪 测试与 QA ### 单元测试 ```bash flutter test ``` - 所有 ViewModel 必须编写单元测试,覆盖核心逻辑。 - 使用 `mockito` 或 `mocktail` 模拟依赖。 ### 集成测试 ```bash flutter test integration_test ``` - 使用 `integration_test` 包编写端到端测试。 - 覆盖关键业务流程(登录、表单提交、数据查询)。 ### QA 流程 - 每次提交前运行 `flutter analyze` 和 `flutter test`。 - CI/CD 中自动执行测试,保证主分支稳定。 - 重要功能上线前需进行手动回归测试。 --- ## ⚡ CI/CD 与发布流程 - CI 会自动执行 `flutter analyze`、`flutter test`、`sh check_env.sh` - Android 发布:`flutter build apk --release` - iOS 发布:`flutter build ios --release` - 内测分发:目前使用apk内部提醒升级,部分用户可选是否升级新版本 --- ## 🤝 贡献指南 - 提交前请运行 `flutter analyze` 和 `flutter test` - 遵循 [代码规范 & Git 规范](#-代码规范--git-规范) - 新功能请从 `develop` 分支拉取 `feature/*` 分支开发 --- ## ❓ FAQ **Q1: 构建时报 JDK 版本错误怎么办?** A: 确认 `android/gradle.properties` 中的 `org.gradle.java.home` 指向 JDK 17。 **Q2: 网络请求报错 403/401?** A: 检查 Token 是否过期,确认 `DioFactory` 是否正确注入了认证头。 **Q3: iOS 构建失败,提示 CocoaPods 错误?** A: 运行 `cd ios && pod install --repo-update`,确保本地 Pod 仓库最新。 **Q4: Flutter 版本不一致导致依赖冲突?** A: 使用 `./check_env.sh` 检查环境,确保 Flutter 版本为推荐版本。 --- ## 📚 推荐阅读 - [Flutter 官方文档](https://docs.flutter.dev/) - [Provider 官方文档](https://pub.dev/packages/provider) - [Dio 网络库](https://pub.dev/packages/dio) - [Json Serializable](https://pub.dev/packages/json_serializable) - [Effective Dart](https://dart.dev/guides/language/effective-dart) - [Conventional Commits](https://www.conventionalcommits.org/zh-hans/v1.0.0/) - [JSON-RPC 2.0 Specification](https://www.jsonrpc.org/specification)