前言
在现代Web应用开发中,前后端分离架构已经成为主流趋势。Spring Boot作为Java生态中的明星框架,配合Vue.js这一优秀的前端框架,能够构建出高性能、可维护的现代化Web应用。本文将系统性地介绍从项目初始化到生产环境部署的完整开发流程,涵盖后端API设计、前端组件化开发、认证授权、数据库优化等核心要点。
一、项目架构概述
1.1 技术栈选择
本项目采用以下技术栈:
- 后端:Spring Boot 2.x + Spring Security + JWT + MySQL + MyBatis Plus
- 前端:Vue.js 3.x + Vue Router + Vuex + Element Plus + Axios
- 构建工具:Maven + Webpack + Vite
- 部署环境:Docker + Nginx + Linux服务器
1.2 架构设计原则
采用微服务思想的单体应用架构,遵循以下设计原则:
- 高内聚低耦合
- 单一职责原则
- RESTful API设计规范
- 前后端完全分离
- 统一异常处理机制
二、后端开发环境搭建
2.1 Spring Boot项目初始化
使用Spring Initializr创建基础项目:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>backend-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>backend-demo</name>
<properties>
<java.version>11</java.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<jwt.version>0.9.1</jwt.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.2 核心配置文件
application.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/demo_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
jpa:
hibernate:
ddl-auto: update
show-sql: true
jwt:
secret: mySecretKeyForTokenGeneration
expiration: 86400000
logging:
level:
com.example.demo: debug
三、后端核心功能实现
3.1 JWT认证授权系统
JWT工具类
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
}
3.2 用户认证控制器
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
final UserDetails userDetails = userService.loadUserByUsername(loginRequest.getUsername());
final String token = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(token));
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body("用户名或密码错误");
}
}
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody RegisterRequest registerRequest) {
try {
userService.register(registerRequest);
return ResponseEntity.ok("注册成功");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(e.getMessage());
}
}
}
3.3 用户服务层
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword())
.authorities("USER")
.build();
}
public void register(RegisterRequest request) {
User existingUser = userMapper.findByUsername(request.getUsername());
if (existingUser != null) {
throw new RuntimeException("用户名已存在");
}
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setEmail(request.getEmail());
user.setCreateTime(new Date());
userMapper.insert(user);
}
}
四、数据库设计与ORM实现
4.1 数据库表结构设计
-- 用户表
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 角色表
CREATE TABLE `role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '角色名称',
`description` varchar(200) DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 用户角色关联表
CREATE TABLE `user_role` (
`user_id` bigint NOT NULL,
`role_id` bigint NOT NULL,
PRIMARY KEY (`user_id`, `role_id`),
KEY `fk_role_id` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.2 MyBatis Plus配置
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true);
paginationInnerInterceptor.setMaxLimit(500L);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
4.3 实体类与Mapper
@TableName("user")
@Data
@EqualsAndHashCode(callSuper = false)
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String username;
private String password;
private String email;
private Date createTime;
private Date updateTime;
}
@Mapper
public interface UserMapper extends BaseMapper<User> {
User findByUsername(String username);
}
五、前端开发环境搭建
5.1 Vue.js项目初始化
使用Vite创建Vue 3项目:
npm create vite@latest frontend-demo --template vue
cd frontend-demo
npm install
package.json依赖
{
"name": "frontend-demo",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.1.2",
"element-plus": "^2.2.6",
"pinia": "^2.0.28",
"vue": "^3.2.45",
"vue-router": "^4.1.6"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.0.0",
"vite": "^4.1.0"
}
}
5.2 项目目录结构
src/
├── assets/ # 静态资源
├── components/ # 公共组件
├── views/ # 页面组件
├── router/ # 路由配置
├── store/ # 状态管理
├── utils/ # 工具函数
├── api/ # API接口
├── App.vue # 根组件
└── main.js # 入口文件
六、前端核心功能实现
6.1 Axios封装与拦截器
// src/utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080',
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
console.error('请求错误:', error)
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
ElMessage({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
console.error('响应错误:', error)
ElMessage({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
6.2 API接口封装
// src/api/auth.js
import request from '@/utils/request'
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
export function register(data) {
return request({
url: '/auth/register',
method: 'post',
data
})
}
export function getUserInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
export function logout() {
return request({
url: '/auth/logout',
method: 'post'
})
}
6.3 路由配置
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/store/user'
const routes = [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/Login.vue'),
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
// 路由守卫
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next('/login')
} else if (to.path === '/login' && token) {
next('/dashboard')
} else {
next()
}
})
export default router
6.4 Pinia状态管理
// src/store/user.js
import { defineStore } from 'pinia'
import { login, logout, getUserInfo } from '@/api/auth'
export const useUserStore = defineStore('user', {
state: () => ({
token: localStorage.getItem('token') || '',
userInfo: null
}),
actions: {
async login(loginForm) {
try {
const response = await login(loginForm)
this.token = response.data.token
localStorage.setItem('token', response.data.token)
return response
} catch (error) {
throw error
}
},
async getUserInfo() {
try {
const response = await getUserInfo()
this.userInfo = response.data
return response
} catch (error) {
throw error
}
},
logout() {
this.token = ''
this.userInfo = null
localStorage.removeItem('token')
}
}
})
七、安全与性能优化
7.1 安全配置
Spring Security配置
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authConfig) throws Exception {
return authConfig.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authz -> authz
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
7.2 数据库连接池优化
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 60000
7.3 缓存优化
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Cacheable(value = "users", key = "#username")
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
@CacheEvict(value = "users", key = "#user.username")
public void updateUser(User user) {
userMapper.updateById(user);
}
}
八、测试策略
8.1 单元测试
@SpringBootTest
class UserServiceTest {
@Autowired
private UserService userService;
@MockBean
private UserMapper userMapper;
@Test
void testRegisterSuccess() {
RegisterRequest request = new RegisterRequest();
request.setUsername("testuser");
request.setPassword("password");
request.setEmail("test@example.com");
when(userMapper.findByUsername("testuser")).thenReturn(null);
assertDoesNotThrow(() -> userService.register(request));
}
@Test
void testRegisterDuplicateUser() {
RegisterRequest request = new RegisterRequest();
request.setUsername("testuser");
request.setPassword("password");
request.setEmail("test@example.com");
when(userMapper.findByUsername("testuser")).thenReturn(new User());
assertThrows(RuntimeException.class, () -> userService.register(request));
}
}
8.2 前端测试
// src/components/LoginForm.spec.js
import { mount } from '@vue/test-utils'
import LoginForm from '@/components/LoginForm.vue'
describe('LoginForm', () => {
it('renders form fields correctly', () => {
const wrapper = mount(LoginForm)
expect(wrapper.find('input[name="username"]').exists()).toBe(true)
expect(wrapper.find('input[name="password"]').exists()).toBe(true)
})
it('emits login event when form is submitted', async () => {
const wrapper = mount(LoginForm)
await wrapper.find('input[name="username"]').setValue('testuser')
await wrapper.find('input[name="password"]').setValue('password')
await wrapper.find('form').trigger('submit.prevent')
expect(wrapper.emitted('login')).toBeTruthy()
})
})
九、部署方案
9.1 Docker构建
Dockerfile
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
docker-compose.yml
version: '3.8'
services:
backend:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
depends_on:
- mysql
networks:
- app-network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: demo_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
networks:
- app-network
frontend:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./dist:/usr/share/nginx/html
depends_on:
- backend
networks:
- app-network
volumes:
mysql_data:
networks:
app-network:
driver: bridge
9.2 Nginx配置
server {
listen 80;
server_name localhost;
location /api/ {
proxy_pass http://backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}
}
9.3 生产环境配置
application-prod.yml
spring:
datasource:
url: jdbc:mysql://mysql:3306/demo_db?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
username: root
password: rootpassword
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 15
minimum-idle: 3
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
logging:
level:
com.example.demo: info
org.springframework.web: info
server:
port: 8080
jwt:
secret: ${JWT_SECRET:prodSecretKey}
expiration: 86400000
十、最佳实践总结
10.1 开发规范
- 代码风格统一:使用Lombok简化POJO,遵循阿里巴巴Java开发手册
- API设计规范:采用RESTful风格,统一响应格式
- 异常处理机制:全局异常处理器统一处理业务异常和系统异常
- 日志管理:合理使用日志级别,便于问题排查
10.2 性能优化要点
- 数据库优化:合理设计索引,避免N+1查询问题
- 缓存策略:适当使用Redis缓存热点数据
- 连接池配置:根据实际负载调整连接池参数
- 异步处理:对于耗时操作使用异步处理机制
10.3 安全防护措施
- 认证授权:JWT token机制,避免session依赖
- 输入验证:后端严格校验所有输入参数
- CORS配置:合理配置跨域资源共享策略
- 密码安全:使用BCrypt加密存储用户密码
10.4 部署运维建议
- 容器化部署:Docker化部署提高环境一致性
- 监控告警:集成Prometheus + Grafana进行系统监控
- 日志管理:使用ELK进行日志收集和分析
- 备份策略:定期备份数据库和重要配置文件
结语
本文详细介绍了Spring Boot + Vue.js全栈开发的完整流程,从项目初始化到生产环境部署,涵盖了前后端分离架构的核心技术要点。通过实际的代码示例和最佳实践,为开发者提供了可直接参考的开发方案。
在实际项目中,还需要根据具体业务需求进行相应的调整和优化。建议团队在开发过程中建立完善的测试体系,注重代码质量,持续优化系统性能,确保应用的稳定性和可维护性。随着技术的不断发展,我们也要保持学习的态度,及时跟进新技术和新框架,不断提升开发效率和产品质量。

评论 (0)