print# Spring Boot 3.0 + React + TypeScript 构建现代化全栈应用:从零搭建到部署上线
引言
在现代Web开发中,构建高性能、可维护的全栈应用已成为开发者的核心技能。本文将详细介绍如何使用Spring Boot 3.0后端框架配合React前端技术栈,构建一个现代化的全栈应用。我们将从项目初始化开始,逐步深入到API设计、状态管理、CI/CD部署等关键环节,为初学者提供一份完整的技术指南。
项目架构概述
技术栈选择理由
Spring Boot 3.0
Spring Boot 3.0基于Java 17,引入了众多新特性,包括对Jakarta EE 9的原生支持、新的安全配置方式、以及更高效的性能优化。它简化了Spring应用的初始搭建和开发过程,提供了自动配置、内嵌服务器等强大功能。
React + TypeScript
React作为流行的前端库,配合TypeScript的静态类型检查,能够显著提升代码质量和开发体验。TypeScript在编译时就能发现类型错误,减少运行时bug,提高团队协作效率。
项目结构设计
modern-fullstack-app/
├── backend/ # Spring Boot 后端
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/com/example/app/
│ │ │ │ ├── controller/
│ │ │ │ ├── service/
│ │ │ │ ├── repository/
│ │ │ │ ├── model/
│ │ │ │ └── config/
│ │ │ └── resources/
│ │ └── test/
│ └── pom.xml
├── frontend/ # React 前端
│ ├── src/
│ │ ├── components/
│ │ ├── pages/
│ │ ├── services/
│ │ ├── store/
│ │ ├── types/
│ │ └── utils/
│ └── package.json
└── docker-compose.yml
后端开发:Spring Boot 3.0 实战
项目初始化
首先,我们使用Spring Initializr创建Spring Boot 3.0项目。在项目配置中选择以下依赖:
<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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
数据模型设计
创建用户实体类,使用JPA注解进行数据映射:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
@NotBlank(message = "Username is required")
private String username;
@Column(nullable = false)
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@Column(nullable = false)
@NotBlank(message = "Password is required")
private String password;
@Column(name = "created_at")
@CreationTimestamp
private LocalDateTime createdAt;
@Column(name = "updated_at")
@UpdateTimestamp
private LocalDateTime updatedAt;
// Getters and Setters
}
Repository层实现
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
Service层业务逻辑
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User createUser(CreateUserRequest request) {
if (userRepository.existsByUsername(request.getUsername())) {
throw new RuntimeException("Username is already taken");
}
if (userRepository.existsByEmail(request.getEmail())) {
throw new RuntimeException("Email is already in use");
}
User user = new User();
user.setUsername(request.getUsername());
user.setEmail(request.getEmail());
user.setPassword(passwordEncoder.encode(request.getPassword()));
return userRepository.save(user);
}
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
Controller层API接口
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "*")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<?> createUser(@Valid @RequestBody CreateUserRequest request) {
try {
User user = userService.createUser(request);
return ResponseEntity.ok(user);
} catch (RuntimeException e) {
return ResponseEntity.badRequest().body(e.getMessage());
}
}
@GetMapping("/{id}")
public ResponseEntity<?> getUserById(@PathVariable Long id) {
try {
User user = userService.getUserById(id);
return ResponseEntity.ok(user);
} catch (RuntimeException e) {
return ResponseEntity.notFound().build();
}
}
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.getAllUsers();
return ResponseEntity.ok(users);
}
}
安全配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/api/users/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
前端开发:React + TypeScript 构建
项目初始化
使用Vite创建React项目,选择TypeScript模板:
npm create vite@latest frontend -- --template react-ts
cd frontend
npm install
TypeScript 类型定义
创建用户类型定义:
// src/types/user.ts
export interface User {
id: number;
username: string;
email: string;
createdAt: string;
updatedAt: string;
}
export interface CreateUserRequest {
username: string;
email: string;
password: string;
}
API服务层封装
// src/services/userService.ts
import { User, CreateUserRequest } from '../types/user';
const API_BASE_URL = 'http://localhost:8080/api';
export class UserService {
static async createUser(userData: CreateUserRequest): Promise<User> {
const response = await fetch(`${API_BASE_URL}/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(userData),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
static async getUsers(): Promise<User[]> {
const response = await fetch(`${API_BASE_URL}/users`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
static async getUserById(id: number): Promise<User> {
const response = await fetch(`${API_BASE_URL}/users/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
}
组件开发
创建用户列表组件:
// src/components/UserList.tsx
import React, { useState, useEffect } from 'react';
import { User } from '../types/user';
import { UserService } from '../services/userService';
const UserList: React.FC = () => {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchUsers = async () => {
try {
const userData = await UserService.getUsers();
setUsers(userData);
} catch (err) {
setError('Failed to fetch users');
console.error(err);
} finally {
setLoading(false);
}
};
fetchUsers();
}, []);
if (loading) return <div>Loading users...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div className="user-list">
<h2>Users</h2>
<ul>
{users.map(user => (
<li key={user.id}>
<strong>{user.username}</strong> - {user.email}
</li>
))}
</ul>
</div>
);
};
export default UserList;
状态管理:使用Context API
// src/context/UserContext.tsx
import React, { createContext, useContext, useReducer } from 'react';
import { User } from '../types/user';
interface UserState {
users: User[];
loading: boolean;
error: string | null;
}
interface UserAction {
type: 'FETCH_USERS_START' | 'FETCH_USERS_SUCCESS' | 'FETCH_USERS_ERROR';
payload?: any;
}
const initialState: UserState = {
users: [],
loading: false,
error: null,
};
const userReducer = (state: UserState, action: UserAction): UserState => {
switch (action.type) {
case 'FETCH_USERS_START':
return { ...state, loading: true, error: null };
case 'FETCH_USERS_SUCCESS':
return { ...state, loading: false, users: action.payload };
case 'FETCH_USERS_ERROR':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
const UserContext = createContext<{
state: UserState;
dispatch: React.Dispatch<UserAction>;
}>({
state: initialState,
dispatch: () => null,
});
export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(userReducer, initialState);
return (
<UserContext.Provider value={{ state, dispatch }}>
{children}
</UserContext.Provider>
);
};
export const useUserContext = () => {
const context = useContext(UserContext);
if (!context) {
throw new Error('useUserContext must be used within a UserProvider');
}
return context;
};
开发环境配置
数据库配置
# application.yml
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true
h2:
console:
enabled: true
开发工具配置
使用Docker进行本地开发环境搭建:
# docker-compose.yml
version: '3.8'
services:
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- db
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/myapp
- SPRING_DATASOURCE_USERNAME=postgres
- SPRING_DATASOURCE_PASSWORD=password
frontend:
build: ./frontend
ports:
- "3000:3000"
depends_on:
- backend
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
测试策略
后端测试
@SpringBootTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class UserServiceIntegrationTest {
@Autowired
private TestEntityManager entityManager;
@Autowired
private UserRepository userRepository;
@Test
void shouldCreateUserSuccessfully() {
// Given
CreateUserRequest request = new CreateUserRequest();
request.setUsername("testuser");
request.setEmail("test@example.com");
request.setPassword("password123");
// When
User savedUser = userService.createUser(request);
// Then
assertThat(savedUser.getId()).isNotNull();
assertThat(savedUser.getUsername()).isEqualTo("testuser");
assertThat(savedUser.getEmail()).isEqualTo("test@example.com");
}
}
前端测试
// src/components/UserList.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UserList from './UserList';
// Mock the API service
jest.mock('../services/userService');
describe('UserList Component', () => {
const mockUsers = [
{ id: 1, username: 'user1', email: 'user1@example.com', createdAt: '2023-01-01', updatedAt: '2023-01-01' },
{ id: 2, username: 'user2', email: 'user2@example.com', createdAt: '2023-01-02', updatedAt: '2023-01-02' }
];
beforeEach(() => {
(UserService.getUsers as jest.Mock).mockResolvedValue(mockUsers);
});
test('renders user list correctly', async () => {
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('Users')).toBeInTheDocument();
expect(screen.getByText('user1')).toBeInTheDocument();
expect(screen.getByText('user2')).toBeInTheDocument();
});
});
});
性能优化
后端优化
使用Spring Boot的缓存机制:
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
@CacheEvict(value = "users", key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}
}
前端优化
使用React.memo和useCallback优化组件性能:
// src/components/UserListItem.tsx
import React, { memo, useCallback } from 'react';
import { User } from '../types/user';
interface UserListItemProps {
user: User;
onEdit: (user: User) => void;
}
const UserListItem: React.FC<UserListItemProps> = memo(({ user, onEdit }) => {
const handleEdit = useCallback(() => {
onEdit(user);
}, [user, onEdit]);
return (
<div className="user-item">
<span>{user.username}</span>
<span>{user.email}</span>
<button onClick={handleEdit}>Edit</button>
</div>
);
});
export default UserListItem;
CI/CD 部署流程
GitHub Actions 配置
# .github/workflows/ci-cd.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build backend
run: |
cd backend
./mvnw clean package
- name: Run tests
run: |
cd backend
./mvnw test
- name: Build frontend
run: |
cd frontend
npm install
npm run build
- name: Deploy to production
if: github.ref == 'refs/heads/main'
run: |
# Deployment scripts here
echo "Deploying to production..."
Docker 部署配置
# backend/Dockerfile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# frontend/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "preview"]
安全最佳实践
JWT认证实现
@Component
public class JwtTokenProvider {
@Value("${app.jwtSecret}")
private String jwtSecret;
@Value("${app.jwtExpirationInMs}")
private int jwtExpirationInMs;
public String generateToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date expiryDate = new Date(System.currentTimeMillis() + jwtExpirationInMs);
return Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
}
输入验证
public class CreateUserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 20, message = "Username must be between 3 and 20 characters")
private String username;
@NotBlank(message = "Email is required")
@Email(message = "Email should be valid")
private String email;
@NotBlank(message = "Password is required")
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
// Getters and Setters
}
监控与日志
日志配置
# application.yml
logging:
level:
com.example.app: DEBUG
org.springframework.web: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file:
name: logs/app.log
Actuator监控
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
总结
本文详细介绍了如何使用Spring Boot 3.0 + React + TypeScript构建现代化全栈应用的完整流程。从项目初始化、架构设计、核心功能实现,到测试、部署和安全优化,为开发者提供了一套完整的实践指南。
通过本文的学习,读者可以掌握:
- 现代化全栈应用的架构设计原则
- Spring Boot 3.0的核心特性和最佳实践
- React + TypeScript的开发模式和性能优化技巧
- CI/CD自动化部署流程
- 安全性和监控的最佳实践
这个技术栈组合不仅提供了良好的开发体验,还具备优秀的可扩展性和维护性,适合构建企业级的现代化Web应用。随着技术的不断发展,这套技术栈将继续保持其在现代Web开发中的重要地位。

评论 (0)