# 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)