前言
本文档详细介绍了一个完整的 Flutter + Spring Boot 全栈项目,包含跨平台移动应用和后端服务的完整技术实现。
项目组成:
- 前端: Flutter 3.x 跨平台应用(iOS/Android/Web)
- 后端: Spring Boot 3.1.5 + Java 17 RESTful API
- 数据库: MySQL 8.0+
- 认证: JWT Token 认证
技术栈对比
为什么选择 Flutter?
核心优势:
- ✅ 一套代码,多端运行 - iOS、Android、Web 全覆盖
- ✅ 高性能渲染 - Skia 引擎,直接绘制到 Canvas
- ✅ 热重载开发 - 修改代码立即生效,开发效率极高
- ✅ 丰富的组件库 - Material Design 和 Cupertino 风格
- ✅ Dart 语言 - JIT(开发)+ AOT(生产)双模式编译
与原生对比:
| 特性 | Flutter | 原生 (Swift/Kotlin) |
|——|———|——————-|
| 开发效率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| 性能 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 跨平台 | ⭐⭐⭐⭐⭐ | ⭐ |
| 热重载 | ✅ | ❌ |
| 社区生态 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
为什么选择 Spring Boot?
核心优势:
- ✅ 约定优于配置 - 零配置启动
- ✅ 内嵌服务器 - Tomcat/Jetty 直接打包
- ✅ 自动配置 - 根据依赖自动装配
- ✅ 生产就绪 - 监控、指标、健康检查
- ✅ 强大生态 - Spring 全家桶支持
项目架构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| ┌──────────────────────┐ │ Flutter App │ │ (跨平台前端) │ │ - iOS/Android/Web │ │ - Dart 语言 │ │ - Provider 状态管理 │ └──────────┬───────────┘ │ HTTP/REST API │ JSON 数据 ▼ ┌──────────────────────┐ │ Java Service │ │ (Spring Boot 后端) │ │ - RESTful API │ │ - JWT 认证 │ │ - JPA 数据访问 │ └──────────┬───────────┘ │ JDBC ▼ ┌──────────────────────┐ │ MySQL 数据库 │ │ - users 表 │ │ - products 表 │ │ - orders 表 │ └──────────────────────┘
|
Flutter 前端详解
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| ios-flutter-project/ ├── pubspec.yaml ├── lib/ │ ├── main.dart │ ├── models/ │ │ ├── user.dart │ │ ├── product.dart │ │ ├── order.dart │ │ └── cart_item.dart │ ├── screens/ │ │ ├── main_shell.dart │ │ ├── login_page.dart │ │ ├── product_list_page.dart │ │ ├── cart_page.dart │ │ └── ... │ ├── services/ │ └── providers/ └── test/
|
核心数据模型
User 模型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| class User { final int? id; final String name; final String email; final String? phone; final DateTime? createdAt;
User({ this.id, required this.name, required this.email, this.phone, this.createdAt, });
factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], name: json['name'] ?? '', email: json['email'] ?? '', phone: json['phone'], createdAt: json['created_at'] != null ? DateTime.parse(json['created_at']) : null, ); }
Map<String, dynamic> toJson() { return { 'id': id, 'name': name, 'email': email, 'phone': phone, 'created_at': createdAt?.toIso8601String(), }; } }
|
状态管理 (Provider)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| class CartProvider with ChangeNotifier { final List<CartItem> _items = [];
List<CartItem> get items => _items;
double get totalAmount { return _items.fold(0.0, (sum, item) => sum + item.totalPrice); }
void addItem(CartItem item) { _items.add(item); notifyListeners(); }
void removeItem(int productId) { _items.removeWhere((item) => item.product.id == productId); notifyListeners(); } }
void main() { runApp( MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => CartProvider()), ], child: const MyApp(), ), ); }
class CartPage extends StatelessWidget { @override Widget build(BuildContext context) { final cart = context.watch<CartProvider>(); return Text('总价:¥${cart.totalAmount}'); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| MaterialApp └── MainShell (StatefulWidget) ├── NavigationBar (底部导航栏) │ ├── 首页 │ ├── 商品 │ ├── 订单 │ └── 我的 └── Scaffold ├── AppBar (顶部栏) ├── Body (内容区) │ └── ListView.builder │ └── ListTile └── BottomNavigationBar
|
热重载开发
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| flutter run
flutter devices
flutter run -d <device_id>
|
Spring Boot 后端详解
项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| java-service-project/ ├── pom.xml ├── src/main/java/com/example/crud/ │ ├── CrudApplication.java │ ├── controller/ │ │ ├── AuthController.java │ │ ├── UserController.java │ │ ├── ProductController.java │ │ └── OrderController.java │ ├── model/ │ │ ├── User.java │ │ ├── Product.java │ │ ├── Order.java │ │ └── OrderItem.java │ ├── repository/ │ │ ├── UserRepository.java │ │ ├── ProductRepository.java │ │ └── OrderRepository.java │ ├── config/ │ │ ├── SecurityConfig.java │ │ └── DataSeeder.java │ └── security/ │ ├── JwtUtil.java │ └── JwtAuthFilter.java └── src/main/resources/ └── application.properties
|
核心依赖 (pom.xml)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency>
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> </dependencies>
|
RESTful API 接口
1. 用户登录
1 2 3 4 5 6 7 8 9 10 11 12 13
| POST /api/auth/login Content-Type: application/json
{ "username": "admin", "password": "password123" }
Response: { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "username": "admin" }
|
2. 获取商品列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| GET /api/products?active=true Authorization: Bearer {token}
Response: [ { "id": 1, "name": "iPhone 15", "description": "Apple 最新智能手机", "price": 7999.00, "stock": 100, "active": true } ]
|
3. 创建订单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| POST /api/orders Content-Type: application/json Authorization: Bearer {token}
{ "userId": 1, "items": [ { "productId": 1, "quantity": 2, "price": 7999.00 } ] }
Response: 201 Created
|
JWT 认证流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 1. 用户登录 (POST /api/auth/login) ↓ 2. 验证用户名密码 ↓ 3. 生成 JWT Token ↓ 4. 返回 Token 给客户端 ↓ 5. 客户端保存 Token (SharedPreferences) ↓ 6. 后续请求携带 Token ↓ 7. JwtAuthFilter 验证 Token ↓ 8. 验证通过,访问资源
|
Spring Security 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| @Configuration @EnableWebSecurity @EnableMethodSecurity public class SecurityConfig { private final JwtAuthFilter jwtAuthFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/users/**").authenticated() .requestMatchers("/api/products/**").permitAll() .requestMatchers("/api/orders/**").authenticated() .anyRequest().authenticated() ) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }
|
数据库设计
ER 图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ┌─────────────┐ ┌─────────────┐ │ User │ │ Product │ ├─────────────┤ ├─────────────┤ │ id │ │ id │ │ name │ │ name │ │ email │ │ description │ │ phone │ │ price │ │ created_at │ │ stock │ │ updated_at │ │ active │ └─────────────┘ └─────────────┘ │ │ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Order │◄──────│ OrderItem │ ├─────────────┤ ├─────────────┤ │ id │ │ id │ │ user_id │ │ order_id │ │ total_amount│ │ product_id │ │ status │ │ quantity │ │ order_date │ │ price │ └─────────────┘ └─────────────┘
|
建表语句
users 表
1 2 3 4 5 6 7 8
| CREATE TABLE users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, phone VARCHAR(20), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
|
products 表
1 2 3 4 5 6 7 8 9 10
| CREATE TABLE products ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, stock INT DEFAULT 0, active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );
|
orders 表
1 2 3 4 5 6 7 8
| CREATE TABLE orders ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id BIGINT NOT NULL, total_amount DECIMAL(10,2) NOT NULL, status VARCHAR(50) NOT NULL, order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) );
|
构建和部署
Flutter 前端
前置条件
1 2 3 4 5 6 7 8
| flutter --version
flutter doctor
flutter pub get
|
运行应用
1 2 3 4 5 6 7 8 9 10 11 12 13
| flutter run
flutter run -d android
flutter run -d chrome
flutter build ios flutter build apk flutter build web
|
Java 后端
前置条件
- JDK 17+
- Maven 3.8+
- MySQL 8.0+
步骤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| git clone git@github.com:DavidYuanX/JavaService.git cd java-service-project
mysql -u root -p CREATE DATABASE crud_db;
spring.datasource.url=jdbc:mysql://localhost:3306/crud_db spring.datasource.username=root spring.datasource.password=your_password
mvn clean package
mvn spring-boot:run
java -jar target/crud-backend-1.0.0.jar
|
Docker 部署
Dockerfile (后端)
1 2 3 4 5 6 7 8 9
| FROM eclipse-temurin:17-jdk-alpine
WORKDIR /app
COPY target/crud-backend-1.0.0.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
|
构建和运行
1 2 3 4 5 6 7
| docker build -t crud-service .
docker run -p 8080:8080 \ -e SPRING_DATASOURCE_URL=jdbc:mysql://host.docker.internal:3306/crud_db \ crud-service
|
开发工具推荐
Flutter 开发
- IDE: VS Code / Android Studio
- 调试工具: Flutter DevTools
- API 测试: Postman / Insomnia
- 版本控制: Git + GitHub
Java 开发
- IDE: IntelliJ IDEA / Eclipse
- 构建工具: Maven
- 数据库管理: MySQL Workbench / DBeaver
- API 文档: Swagger / OpenAPI
性能优化建议
Flutter 前端
1. 列表性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return const ListTile( leading: Icon(Icons.person), ); }, );
ListTile( key: ValueKey(item.id), title: Text(item.name), )
|
2. 图片缓存
1 2 3 4 5 6
| CachedNetworkImage( imageUrl: product.imageUrl, placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Icon(Icons.error), )
|
Java 后端
1. 数据库索引
1 2 3
| CREATE INDEX idx_email ON users(email); CREATE INDEX idx_user_id ON orders(user_id); CREATE INDEX idx_active ON products(active);
|
2. 连接池配置
1 2 3 4
| spring.datasource.hikari.maximum-pool-size=10 spring.datasource.hikari.minimum-idle=5 spring.datasource.hikari.connection-timeout=20000
|
3. 缓存配置
1 2 3 4 5 6 7 8
| @Cacheable(value = "products", key = "#id") @GetMapping("/{id}") public ResponseEntity<Product> getProductById(@PathVariable Long id) { return productRepository.findById(id) .map(ResponseEntity::ok) .orElse(ResponseEntity.notFound().build()); }
|
常见问题排查
Flutter 前端
Q1: 热重载不生效
解决方案:
1
| flutter clean && flutter pub get && flutter run
|
Q2: iOS 构建失败
解决方案:
1 2 3 4
| cd ios pod install cd .. flutter build ios
|
Q3: 网络请求失败
解决方案:
1 2 3 4 5 6
| <key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>
|
Java 后端
Q1: 数据库连接错误
解决方案:
1 2 3 4 5 6 7
| spring.datasource.url=jdbc:mysql://localhost:3306/crud_db spring.datasource.username=root spring.datasource.password=correct_password
systemctl status mysql
|
Q2: JWT Token 验证失败
解决方案:
1 2 3
| jwt.secret=YourVeryLongAndSecureSecretKeyHere123456789 jwt.expiration=86400000
|
总结
通过本文档,你学习了:
- ✅ Flutter 跨平台开发 - 一套代码多端运行
- ✅ Spring Boot 后端开发 - RESTful API 设计
- ✅ JWT 认证机制 - 安全的 Token 认证
- ✅ 状态管理 - Provider 模式
- ✅ 数据库设计 - MySQL 表结构设计
- ✅ 构建部署 - Docker 容器化部署
参考资源
Flutter
Spring Boot
其他
文档版本: 1.0.0
更新日期: 2026-03-13
维护者: David