Spring Boot + Vue + MySQL 全栈开发实战:从零到一的完整实施指南

前言

在当今快速发展的Web开发领域,全栈开发已成为技术人员的必备技能。本文将深入探讨如何使用Spring Boot、Vue.js和MySQL构建现代化的Web应用程序,通过实际项目案例,为你提供一套完整的实施技巧和最佳实践。

本文能帮你解决什么?​ 快速搭建一个具备用户管理、权限控制、前后端分离的生产级Web应用

你将学到什么?​ Spring Boot后端API开发、Vue 3前端工程化、JWT认证、Docker容器化部署

适合谁?​ 有一定Java/JavaScript基础,希望系统掌握全栈开发的中级开发者

前置知识:Java基础、Spring基础、Vue基础、MySQL基础

技术栈概览

后端架构

  • Spring Boot 2.7.x:简化Spring应用开发

  • Spring Security:安全认证框架

  • MyBatis Plus:ORM框架增强

  • JWT:无状态认证机制

  • Redis:缓存解决方案

前端架构

  • Vue 3.x:渐进式JavaScript框架

  • Vue Router:路由管理

  • Vuex:状态管理

  • Element Plus:UI组件库

  • Axios:HTTP客户端

数据存储

  • MySQL 8.0:关系型数据库

  • Redis:内存数据结构存储

🎯 开篇导读(图文引导)

“一张图胜过千言万语” —— 我们先用一张系统架构图,带你一眼看懂整个技术栈如何协同工作。

📊 系统架构图(原创设计)

plaintext

┌──────────────────────────────────────────────────────────────┐
│                      前端(Vue3 + ElementPlus)               │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐    │
│  │ 登录页       │  │ 用户列表页    │  │ 权限管理页        │    │
│  │ Login.vue   │  │ User.vue     │  │ Role.vue         │    │
│  └──────┬──────┘  └──────┬───────┘  └────────┬─────────┘    │
│         │                 │                    │               │
│         └─────────────────┴────────────────────┘               │
│                              │                               │
└──────────────────────────────┼───────────────────────────────┘
                               │ Axios
┌──────────────────────────────┼───────────────────────────────┐
│                              │                               │
│            API网关(Nginx + HTTPS + Gzip)                   │
│                              │                               │
└──────────────────────────────┼───────────────────────────────┘
                               │
┌──────────────────────────────┼───────────────────────────────┐
│                              │ Spring Boot 2.7.x             │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐    │
│  │ Controller  │  │ Service      │  │ Mapper (MyBatis) │    │
│  │ 用户接口     │  │ 业务逻辑      │  │ 数据访问          │    │
│  └──────┬──────┘  └──────┬───────┘  └────────┬─────────┘    │
│         │                 │                    │               │
│         └─────────────────┴────────────────────┘               │
│                              │                               │
└──────────────────────────────┼───────────────────────────────┘
                               │
┌──────────────────────────────┼───────────────────────────────┐
│                              │ 数据层                        │
│  ┌──────────────┐  ┌──────────────┐  ┌─────────────────┐    │
│  │ MySQL 8.0    │  │ Redis        │  │ 文件存储(OSS)  │    │
│  │ 主从复制     │  │ 缓存/会话     │  │ 头像/日志        │    │
│  └──────────────┘  └──────────────┘  └─────────────────┘    │
└──────────────────────────────────────────────────────────────┘

图解说明

  • 前端采用 Vue3 + ElementPlus,组件化开发,支持响应式布局

  • 后端使用 Spring Boot + MyBatis Plus,RESTful 接口设计

  • 数据层支持 主从复制 + Redis缓存 + OSS对象存储

  • 全链路支持 HTTPS、Gzip压缩、JWT认证、权限控制

项目架构设计

系统架构图

┌─────────────────────────────────────────────────────────┐
│                    前端层 (Vue.js)                      │
├─────────────────────────────────────────────────────────┤
│                  API网关 (Nginx)                        │
├─────────────────────────────────────────────────────────┤
│                  应用层 (Spring Boot)                   │
│  ┌─────────────┬──────────────┬─────────────────────┐  │
│  │   控制器    │   业务逻辑   │      数据访问       │  │
│  │  Controller │   Service    │      Mapper         │  │
│  └─────────────┴──────────────┴─────────────────────┘  │
├─────────────────────────────────────────────────────────┤
│                  数据层 (MySQL + Redis)                 │
└─────────────────────────────────────────────────────────┘

数据库设计示例

sql

-- 用户表
CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `status` tinyint DEFAULT '1' COMMENT '状态:1-正常,0-禁用',
  `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 COMMENT='用户表';

-- 角色表
CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `role_name` varchar(50) NOT NULL COMMENT '角色名称',
  `role_code` varchar(50) NOT NULL COMMENT '角色编码',
  `description` varchar(200) DEFAULT NULL COMMENT '描述',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';

后端开发实战

项目创建

# 使用Spring Initializr创建项目

# 访问 https://start.spring.io/

选择: # - Spring Boot: 2.7.x

# - Packaging: Jar

# - Java: 8/11/17

# - Dependencies: Web, Security, MyBatis, MySQL, Redis

1. Spring Boot项目初始化

xml

<!-- pom.xml 核心依赖 -->
<dependencies>
    <!-- Spring Boot Starter -->
    <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>
    
    <!-- MyBatis Plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    
    <!-- MySQL -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <!-- JWT -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.1</version>
    </dependency>
    
    <!-- Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

关键点说明

  • MyBatis Plus 3.x版本对MyBatis进行了深度封装,可减少80%的SQL编写

  • MySQL 8.0驱动类名为com.mysql.cj.jdbc.Driver,与5.x不同

  • JWT版本选择0.9.1,这是稳定且广泛使用的版本

2. 配置文件优化

yaml

# application.yml
server:
  port: 8080
  servlet:
    context-path: /api

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/fullstack_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
  
  redis:
    host: localhost
    port: 6379
    password: 
    timeout: 5000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
  
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

logging:
  level:
    com.example: debug

🔧 配置要点

  1. 时区问题:MySQL 8.0必须设置serverTimezone=Asia/Shanghai,否则时间会差8小时

  2. SSL警告:本地开发可设置useSSL=false,生产环境必须使用SSL

  3. 连接泄漏防护:HikariCP是Spring Boot 2.x默认连接池,性能优于DBCP2

3. 统一响应封装

java

// Result.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    private Integer code;
    private String message;
    private T data;
    private Long timestamp;
    
    public static <T> Result<T> success() {
        return success(null);
    }
    
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMessage("操作成功");
        result.setData(data);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }
    
    public static <T> Result<T> error(String message) {
        return error(500, message);
    }
    
    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        result.setTimestamp(System.currentTimeMillis());
        return result;
    }
}

为什么需要统一响应格式?

  1. 前端可以统一处理响应逻辑

  2. 便于日志记录和问题排查

  3. 标准化接口文档

4. JWT认证实现

java

// JwtTokenProvider.java
@Component
@Slf4j
public class JwtTokenProvider {
    
    @Value("${jwt.secret}")
    private String jwtSecret;
    
    @Value("${jwt.expiration}")
    private int jwtExpiration;
    
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("authorities", userDetails.getAuthorities().stream()
                .map(GrantedAuthority::getAuthority)
                .collect(Collectors.toList()));
        
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + jwtExpiration * 1000))
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }
    
    public String getUsernameFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }
    
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            log.error("JWT token validation error: {}", e.getMessage());
            return false;
        }
    }
}

5. 用户管理功能实现

java

// UserController.java
@RestController
@RequestMapping("/user")
@CrossOrigin
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @PostMapping("/login")
    public Result<Map<String, Object>> login(@RequestBody LoginDTO loginDTO) {
        try {
            Map<String, Object> result = userService.login(loginDTO);
            return Result.success(result);
        } catch (Exception e) {
            return Result.error(e.getMessage());
        }
    }
    
    @GetMapping("/page")
    @PreAuthorize("hasRole('ADMIN')")
    public Result<PageResult<UserVO>> getUserPage(
            @RequestParam(defaultValue = "1") int current,
            @RequestParam(defaultValue = "10") int size,
            @RequestParam(required = false) String username) {
        
        Page<UserVO> page = new Page<>(current, size);
        IPage<UserVO> userPage = userService.getUserPage(page, username);
        
        PageResult<UserVO> pageResult = new PageResult<>();
        pageResult.setRecords(userPage.getRecords());
        pageResult.setTotal(userPage.getTotal());
        pageResult.setCurrent(current);
        pageResult.setSize(size);
        
        return Result.success(pageResult);
    }
    
    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Result<String> addUser(@RequestBody @Valid UserDTO userDTO) {
        userService.addUser(userDTO);
        return Result.success("用户添加成功");
    }
    
    @PutMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public Result<String> updateUser(@PathVariable Long id, @RequestBody @Valid UserDTO userDTO) {
        userService.updateUser(id, userDTO);
        return Result.success("用户更新成功");
    }
    
    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN')")
    public Result<String> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return Result.success("用户删除成功");
    }
}

6. 全局异常处理

java

// GlobalExceptionHandler.java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public Result<String> handleException(Exception e) {
        log.error("系统异常:", e);
        return Result.error("系统内部错误");
    }
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<String> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        String errorMessage = bindingResult.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return Result.error(errorMessage);
    }
    
    @ExceptionHandler(BusinessException.class)
    public Result<String> handleBusinessException(BusinessException e) {
        return Result.error(e.getMessage());
    }
}

前端开发实战

1. Vue项目初始化

bash

# 使用Vite创建Vue3项目
npm create vite@latest fullstack-frontend --template vue

# 安装依赖
cd fullstack-frontend
npm install

# 安装UI组件库和工具
npm install element-plus axios vue-router pinia @element-plus/icons-vue

# 安装开发工具
npm install -D sass vite-plugin-compression

2. 项目结构组织

src/
├── api/           # API接口管理
├── assets/        # 静态资源
├── components/    # 公共组件
├── directives/    # 自定义指令
├── layout/        # 布局组件
├── router/        # 路由配置
├── store/         # 状态管理
├── styles/        # 全局样式
├── utils/         # 工具函数
├── views/         # 页面组件
└── main.js        # 入口文件

3. Axios请求封装

JavaScript

// utils/request.js
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/user'
import router from '@/router'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  timeout: 10000
})

// 请求拦截器
request.interceptors.request.use(
  config => {
    const userStore = useUserStore()
    if (userStore.token) {
      config.headers.Authorization = `Bearer ${userStore.token}`
    }
    return config
  },
  error => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  response => {
    const { code, message, data } = response.data
    if (code === 200) {
      return data
    } else {
      ElMessage.error(message || '请求失败')
      return Promise.reject(new Error(message || '请求失败'))
    }
  },
  error => {
    if (error.response?.status === 401) {
      const userStore = useUserStore()
      userStore.logout()
      router.push('/login')
    }
    ElMessage.error(error.message || '网络错误')
    return Promise.reject(error)
  }
)

export default request

4. Pinia状态管理

JavaScript

// store/user.js
import { defineStore } from 'pinia'
import { login, getUserInfo } from '@/api/user'
import router from '@/router'

export const useUserStore = defineStore('user', {
  state: () => ({
    token: localStorage.getItem('token') || '',
    userInfo: null,
    roles: []
  }),

  getters: {
    isLoggedIn: (state) => !!state.token,
    hasRole: (state) => (role) => state.roles.includes(role)
  },

  actions: {
    async login(loginForm) {
      try {
        const response = await login(loginForm)
        this.token = response.token
        localStorage.setItem('token', this.token)
        await this.getUserInfo()
        router.push('/')
      } catch (error) {
        throw error
      }
    },

    async getUserInfo() {
      try {
        const data = await getUserInfo()
        this.userInfo = data.userInfo
        this.roles = data.roles
      } catch (error) {
        this.logout()
        throw error
      }
    },

    logout() {
      this.token = ''
      this.userInfo = null
      this.roles = []
      localStorage.removeItem('token')
      router.push('/login')
    }
  }
})

5. 用户管理页面实现

vue

<!-- views/user/index.vue -->
<template>
  <div class="user-container">
    <el-card>
      <template #header>
        <div class="card-header">
          <span>用户管理</span>
          <el-button type="primary" @click="handleAdd">
            <el-icon><Plus /></el-icon>
            新增用户
          </el-button>
        </div>
      </template>

      <!-- 搜索表单 -->
      <el-form :inline="true" :model="searchForm" class="search-form">
        <el-form-item label="用户名">
          <el-input v-model="searchForm.username" placeholder="请输入用户名" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleSearch">
            <el-icon><Search /></el-icon>
            搜索
          </el-button>
          <el-button @click="handleReset">重置</el-button>
        </el-form-item>
      </el-form>

      <!-- 用户表格 -->
      <el-table :data="userList" v-loading="loading" border>
        <el-table-column prop="id" label="ID" width="80" />
        <el-table-column prop="username" label="用户名" />
        <el-table-column prop="email" label="邮箱" />
        <el-table-column prop="phone" label="手机号" />
        <el-table-column prop="status" label="状态">
          <template #default="{ row }">
            <el-tag :type="row.status === 1 ? 'success' : 'danger'">
              {{ row.status === 1 ? '正常' : '禁用' }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column prop="createTime" label="创建时间" />
        <el-table-column label="操作" width="200">
          <template #default="{ row }">
            <el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
            <el-button type="danger" link @click="handleDelete(row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 分页 -->
      <el-pagination
        v-model:current-page="pageParams.current"
        v-model:page-size="pageParams.size"
        :total="total"
        @current-change="getUserList"
        @size-change="getUserList"
        layout="total, sizes, prev, pager, next, jumper"
      />
    </el-card>

    <!-- 用户表单对话框 -->
    <UserFormDialog
      v-model="dialogVisible"
      :user-id="currentUserId"
      @success="getUserList"
    />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { getUserPage, deleteUser } from '@/api/user'
import UserFormDialog from './components/UserFormDialog.vue'

const loading = ref(false)
const userList = ref([])
const total = ref(0)
const dialogVisible = ref(false)
const currentUserId = ref(null)

const searchForm = reactive({
  username: ''
})

const pageParams = reactive({
  current: 1,
  size: 10
})

const getUserList = async () => {
  loading.value = true
  try {
    const params = {
      ...pageParams,
      ...searchForm
    }
    const data = await getUserPage(params)
    userList.value = data.records
    total.value = data.total
  } catch (error) {
    console.error('获取用户列表失败:', error)
  } finally {
    loading.value = false
  }
}

const handleSearch = () => {
  pageParams.current = 1
  getUserList()
}

const handleReset = () => {
  searchForm.username = ''
  handleSearch()
}

const handleAdd = () => {
  currentUserId.value = null
  dialogVisible.value = true
}

const handleEdit = (row) => {
  currentUserId.value = row.id
  dialogVisible.value = true
}

const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
      type: 'warning'
    })
    await deleteUser(row.id)
    ElMessage.success('删除成功')
    getUserList()
  } catch (error) {
    if (error !== 'cancel') {
      console.error('删除用户失败:', error)
    }
  }
}

onMounted(() => {
  getUserList()
})
</script>

<style scoped lang="scss">
.user-container {
  padding: 20px;
}

.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.search-form {
  margin-bottom: 20px;
}

.el-pagination {
  margin-top: 20px;
  justify-content: flex-end;
}
</style>

6. 用户表单组件

vue

<!-- views/user/components/UserFormDialog.vue -->
<template>
  <el-dialog
    v-model="visible"
    :title="userId ? '编辑用户' : '新增用户'"
    width="500px"
    @close="handleClose"
  >
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      label-width="80px"
    >
      <el-form-item label="用户名" prop="username">
        <el-input v-model="form.username" placeholder="请输入用户名" />
      </el-form-item>
      <el-form-item label="邮箱" prop="email">
        <el-input v-model="form.email" placeholder="请输入邮箱" />
      </el-form-item>
      <el-form-item label="手机号" prop="phone">
        <el-input v-model="form.phone" placeholder="请输入手机号" />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-radio-group v-model="form.status">
          <el-radio :label="1">正常</el-radio>
          <el-radio :label="0">禁用</el-radio>
        </el-radio-group>
      </el-form-item>
    </el-form>

    <template #footer>
      <el-button @click="handleClose">取消</el-button>
      <el-button type="primary" @click="handleSubmit" :loading="submitLoading">
        确定
      </el-button>
    </template>
  </el-dialog>
</template>

<script setup>
import { ref, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { addUser, updateUser, getUserDetail } from '@/api/user'

const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  userId: {
    type: [String, Number],
    default: null
  }
})

const emit = defineEmits(['update:modelValue', 'success'])

const visible = ref(false)
const formRef = ref(null)
const submitLoading = ref(false)

const form = reactive({
  username: '',
  email: '',
  phone: '',
  status: 1
})

const rules = {
  username: [
    { required: true, message: '请输入用户名', trigger: 'blur' },
    { min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }
  ],
  email: [
    { required: true, message: '请输入邮箱', trigger: 'blur' },
    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
  ],
  phone: [
    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
  ]
}

const getUserDetail = async () => {
  if (!props.userId) return
  
  try {
    const data = await getUserDetail(props.userId)
    Object.assign(form, data)
  } catch (error) {
    console.error('获取用户详情失败:', error)
  }
}

const handleSubmit = async () => {
  try {
    await formRef.value.validate()
    submitLoading.value = true
    
    if (props.userId) {
      await updateUser(props.userId, form)
      ElMessage.success('更新成功')
    } else {
      await addUser(form)
      ElMessage.success('添加成功')
    }
    
    emit('success')
    handleClose()
  } catch (error) {
    console.error('提交失败:', error)
  } finally {
    submitLoading.value = false
  }
}

const handleClose = () => {
  formRef.value?.resetFields()
  Object.assign(form, {
    username: '',
    email: '',
    phone: '',
    status: 1
  })
  emit('update:modelValue', false)
}

watch(() => props.modelValue, (val) => {
  visible.value = val
  if (val && props.userId) {
    getUserDetail()
  }
})

watch(visible, (val) => {
  emit('update:modelValue', val)
})
</script>

部署与优化

1. 生产环境配置

yaml

# application-prod.yml
server:
  port: 8080
  tomcat:
    threads:
      max: 200
      min-spare: 10

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 20000
  
  redis:
    cluster:
      nodes:
        - redis://127.0.0.1:7001
        - redis://127.0.0.1:7002
        - redis://127.0.0.1:7003
      max-redirects: 3

# 日志配置
logging:
  level:
    root: INFO
    com.example: INFO
  file:
    name: logs/application.log
    max-size: 100MB
    max-history: 30

2. 前端打包优化

JavaScript

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import viteCompression from 'vite-plugin-compression'

export default defineConfig({
  plugins: [
    vue(),
    viteCompression({
      verbose: true,
      disable: false,
      threshold: 10240,
      algorithm: 'gzip',
      ext: '.gz'
    })
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'element-plus': ['element-plus'],
          'vue-vendor': ['vue', 'vue-router', 'pinia']
        }
      }
    },
    chunkSizeWarningLimit: 1000
  }
})

3. Docker部署配置

dockerfile

# Dockerfile
FROM openjdk:8-jre-alpine
VOLUME /tmp
COPY target/fullstack-backend.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

yaml

# docker-compose.yml
version: '3.8'
services:
  mysql:
    image: mysql:8.0
    container_name: fullstack-mysql
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: fullstack_db
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql
  
  redis:
    image: redis:7-alpine
    container_name: fullstack-redis
    ports:
      - "6379:6379"
  
  backend:
    build: ./backend
    container_name: fullstack-backend
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis
    environment:
      SPRING_PROFILES_ACTIVE: prod
  
  frontend:
    image: nginx:alpine
    container_name: fullstack-frontend
    ports:
      - "80:80"
    volumes:
      - ./frontend/dist:/usr/share/nginx/html
      - ./nginx.conf:/etc/nginx/nginx.conf

volumes:
  mysql_data:

性能优化技巧

1. 数据库优化

sql

-- 添加索引
CREATE INDEX idx_username ON sys_user(username);
CREATE INDEX idx_create_time ON sys_user(create_time);

-- 分页查询优化
SELECT * FROM sys_user 
WHERE id > #{lastId} 
ORDER BY id ASC 
LIMIT #{pageSize};

2. Redis缓存策略

java

// RedisConfig.java
@Configuration
@EnableCaching
public class RedisConfig {
    
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        
        // 使用Jackson2JsonRedisSerializer序列化
        Jackson2JsonRedisSerializer<Object> serializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, 
            ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        
        template.setValueSerializer(serializer);
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        
        return template;
    }
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();
            
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .build();
    }
}

3. 前端性能优化

JavaScript

// 路由懒加载
const routes = [
  {
    path: '/user',
    component: () => import('@/views/user/index.vue'),
    meta: { title: '用户管理' }
  }
]

// 虚拟滚动
<template>
  <el-table-v2
    :columns="columns"
    :data="data"
    :width="700"
    :height="400"
    fixed
  />
</template>

安全最佳实践

1. 密码加密

java

// PasswordEncoderConfig.java
@Configuration
public class PasswordEncoderConfig {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder(12); // 强度12
    }
}

// 使用示例
@Service
public class UserService {
    
    @Autowired
    private PasswordEncoder passwordEncoder;
    
    public void addUser(UserDTO userDTO) {
        User user = new User();
        user.setUsername(userDTO.getUsername());
        user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        userMapper.insert(user);
    }
}

2. XSS防护

java

// XssFilter.java
@Component
@WebFilter(urlPatterns = "/*")
public class XssFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        XssHttpServletRequestWrapper xssRequest = 
            new XssHttpServletRequestWrapper(httpRequest);
        chain.doFilter(xssRequest, response);
    }
}

3. SQL注入防护

java

// 使用MyBatis Plus防止SQL注入
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    // 安全的查询方式
    @Select("SELECT * FROM sys_user WHERE username = #{username}")
    User findByUsername(@Param("username") String username);
}

总结

本文详细介绍了Spring Boot + Vue + MySQL全栈开发的完整实施过程,从项目架构设计到具体代码实现,再到部署优化和安全防护。通过这套技术栈,我们可以构建出高性能、可维护的现代化Web应用程序。

感谢你能读到这里。
如果这篇博客刚好解决了你的痛点,请点个【👍点赞】+【⭐收藏】,让它有机会帮到更多小伙伴;
如果还有疑问,评论区见,我必回复
也欢迎你【📬私信】晒出你的运行截图,我们一起把项目打磨得更丝滑。
愿你我都能写出优雅代码,不负时光,不负自己。

关键技术点回顾:

  1. 后端架构:采用Spring Boot快速开发,结合Spring Security实现安全认证,使用MyBatis Plus简化数据访问

  2. 前端架构:基于Vue 3的组件化开发,配合Element Plus提供优秀的用户体验

  3. 性能优化:通过Redis缓存、数据库索引、前端懒加载等手段提升系统性能

  4. 安全防护:JWT认证、密码加密、XSS防护、SQL注入防护等多重安全措施

后续学习建议:

  1. 深入学习微服务架构,将单体应用拆分为微服务

  2. 探索容器化部署,使用Kubernetes进行容器编排

  3. 学习前端进阶技术,如TypeScript、Vue 3 Composition API等

  4. 关注云原生技术,如Service Mesh、Serverless等新兴技术

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值