Spring Boot+Vue+MySQL 全栈开发:从架构设计到落地的实战技巧

作为一名后端开发者,近年来我参与了多个基于 Spring Boot+Vue+MySQL 的全栈项目开发。从最初的技术选型踩坑,到后期的性能优化与业务落地,积累了不少实战经验。本文将从架构设计、核心技术实现、性能优化三个维度,分享这套技术栈的开发技巧与心得,附带可落地的代码实例,希望能给同路人带来帮助。

一、架构设计:先搭骨架,再填血肉

在全栈开发中,架构设计是基础。如果一开始就埋头写代码,后期很容易出现耦合度高、维护困难的问题。针对 Spring Boot+Vue+MySQL 的技术组合,我通常采用分层架构 + 前后端分离的设计思路。

1. 后端架构:分层解耦,职责单一

后端基于 Spring Boot 实现,遵循经典的分层架构,各层职责明确:

  • 实体层(Entity):对应 MySQL 数据库表结构,使用 JPA/Hibernate 实现 ORM 映射。
  • 数据访问层(Mapper/Repository):负责与数据库交互,可使用 MyBatis-Plus(简化 CRUD)或 Spring Data JPA。
  • 业务逻辑层(Service):处理核心业务逻辑,包含事务控制、业务规则校验等。
  • 控制层(Controller):接收前端请求,返回响应数据,处理跨域、参数校验等。
  • 工具层(Util):封装通用工具类,如日期处理、加密解密、结果返回封装等。

2. 前端架构:组件化 + 路由分层

前端基于 Vue3(搭配 Vite)实现,采用组件化开发 + 路由分层的思路:

  • 页面组件(Page):对应业务页面,如用户管理、订单列表等。
  • 通用组件(Component):可复用的 UI 组件,如分页、表单、弹窗等。
  • 路由(Router):按业务模块分层,如 /system(系统管理)、/business(业务模块)。
  • 请求封装(Axios):统一处理请求拦截、响应拦截、错误处理。
  • 状态管理(Pinia):存储全局状态,如用户信息、菜单权限等。

3. 数据库设计:规范先行,预留扩展

MySQL 数据库设计需遵循三大范式,同时兼顾性能:

  • 表名、字段名采用小写 + 下划线命名,如sys_useruser_name
  • 核心表添加主键(自增 / 雪花算法)、创建时间、更新时间、删除标记(逻辑删除)字段。
  • 针对高频查询字段建立索引,避免过度索引。

二、核心技术实现:代码实例落地

接下来,通过具体的代码实例,讲解从后端接口开发到前端调用的完整流程,以用户管理模块为例。

环境准备

  • 后端:Spring Boot 3.2.0、MyBatis-Plus 3.5.5、MySQL 8.0、JDK 17
  • 前端:Vue3、Vite、Element Plus、Axios、Pinia

1. 后端实现:用户 CRUD 接口开发

(1)数据库表设计

sql

CREATE TABLE `sys_user` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `user_name` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码(加密)',
  `nick_name` varchar(50) DEFAULT NULL COMMENT '昵称',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_deleted` tinyint DEFAULT 0 COMMENT '删除标记(0:未删,1:已删)',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';
(2)后端代码实现

① 实体类(SysUser.java)

java

运行

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_user")
public class SysUser {
    /**
     * 主键ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 密码(加密)
     */
    private String password;

    /**
     * 昵称
     */
    private String nickName;

    /**
     * 创建时间
     */
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 删除标记
     */
    @TableLogic
    private Integer isDeleted;
}

② Mapper 层(SysUserMapper.java)

java

运行

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface SysUserMapper extends BaseMapper<SysUser> {
}

③ Service 层(SysUserService.java + 实现类)

java

运行

// 接口
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.demo.entity.SysUser;

public interface SysUserService extends IService<SysUser> {
}

// 实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.entity.SysUser;
import com.example.demo.mapper.SysUserMapper;
import com.example.demo.service.SysUserService;
import org.springframework.stereotype.Service;

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {
}

④ 统一返回结果封装(Result.java)

java

运行

import lombok.Data;

@Data
public class Result<T> {
    /**
     * 状态码:200成功,500失败
     */
    private Integer code;

    /**
     * 提示信息
     */
    private String msg;

    /**
     * 数据
     */
    private T data;

    // 成功响应
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setCode(200);
        result.setMsg("操作成功");
        result.setData(data);
        return result;
    }

    // 失败响应
    public static <T> Result<T> error(String msg) {
        Result<T> result = new Result<>();
        result.setCode(500);
        result.setMsg(msg);
        result.setData(null);
        return result;
    }
}

⑤ Controller 层(SysUserController.java)

java

运行

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.example.demo.entity.SysUser;
import com.example.demo.result.Result;
import com.example.demo.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/sys/user")
@CrossOrigin // 跨域支持,生产环境建议统一配置
public class SysUserController {

    @Autowired
    private SysUserService sysUserService;

    /**
     * 新增用户
     */
    @PostMapping
    public Result<Boolean> save(@RequestBody SysUser sysUser) {
        // 实际项目中需添加密码加密逻辑,如BCrypt加密
        return Result.success(sysUserService.save(sysUser));
    }

    /**
     * 根据ID删除用户
     */
    @DeleteMapping("/{id}")
    public Result<Boolean> delete(@PathVariable Long id) {
        return Result.success(sysUserService.removeById(id));
    }

    /**
     * 修改用户
     */
    @PutMapping
    public Result<Boolean> update(@RequestBody SysUser sysUser) {
        return Result.success(sysUserService.updateById(sysUser));
    }

    /**
     * 根据ID查询用户
     */
    @GetMapping("/{id}")
    public Result<SysUser> getById(@PathVariable Long id) {
        return Result.success(sysUserService.getById(id));
    }

    /**
     * 查询所有用户
     */
    @GetMapping("/list")
    public Result<List<SysUser>> list() {
        LambdaQueryWrapper<SysUser> queryWrapper = Wrappers.lambdaQuery();
        queryWrapper.orderByDesc(SysUser::getCreateTime);
        return Result.success(sysUserService.list(queryWrapper));
    }
}

2. 前端实现:用户管理页面开发

(1)Axios 请求封装(request.js)

javascript

运行

import axios from 'axios';

// 创建axios实例
const service = axios.create({
  baseURL: 'http://localhost:8080', // 后端接口地址
  timeout: 5000 // 请求超时时间
});

// 请求拦截器
service.interceptors.request.use(
  (config) => {
    // 可添加token请求头,如config.headers['Token'] = localStorage.getItem('token');
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// 响应拦截器
service.interceptors.response.use(
  (response) => {
    const res = response.data;
    // 统一处理响应结果
    if (res.code !== 200) {
      ElMessage.error(res.msg || '请求失败');
      return Promise.reject(res);
    } else {
      return res;
    }
  },
  (error) => {
    ElMessage.error(error.message || '服务器错误');
    return Promise.reject(error);
  }
);

export default service;
(2)用户 API 封装(userApi.js)

javascript

运行

import request from './request';

// 用户管理相关API
export const userApi = {
  // 新增用户
  saveUser: (data) => request.post('/sys/user', data),
  // 删除用户
  deleteUser: (id) => request.delete(`/sys/user/${id}`),
  // 修改用户
  updateUser: (data) => request.put('/sys/user', data),
  // 根据ID查询用户
  getUserById: (id) => request.get(`/sys/user/${id}`),
  // 查询用户列表
  getUserList: () => request.get('/sys/user/list')
};
(3)用户管理页面(UserManage.vue)

vue

<template>
  <div class="user-manage">
    <!-- 搜索与新增按钮 -->
    <div class="search-bar">
      <el-button type="primary" @click="openAddDialog">新增用户</el-button>
    </div>

    <!-- 用户列表 -->
    <el-table :data="userList" border stripe style="width: 100%; margin-top: 10px">
      <el-table-column prop="id" label="ID" width="80" />
      <el-table-column prop="userName" label="用户名" width="120" />
      <el-table-column prop="nickName" label="昵称" width="120" />
      <el-table-column prop="createTime" label="创建时间" width="200" />
      <el-table-column label="操作" width="200">
        <template #default="scope">
          <el-button type="primary" size="small" @click="openEditDialog(scope.row)">编辑</el-button>
          <el-button type="danger" size="small" @click="deleteUser(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 新增/编辑用户弹窗 -->
    <el-dialog v-model="dialogVisible" title="用户信息" width="400px">
      <el-form :model="userForm" label-width="80px" ref="userFormRef">
        <el-form-item label="用户名" prop="userName" :rules="[{ required: true, message: '请输入用户名', trigger: 'blur' }]">
          <el-input v-model="userForm.userName" />
        </el-form-item>
        <el-form-item label="密码" prop="password" v-if="isAdd" :rules="[{ required: true, message: '请输入密码', trigger: 'blur' }]">
          <el-input v-model="userForm.password" type="password" />
        </el-form-item>
        <el-form-item label="昵称" prop="nickName">
          <el-input v-model="userForm.nickName" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dialogVisible = false">取消</el-button>
        <el-button type="primary" @click="submitForm">确定</el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { ref, onMounted, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { userApi } from '@/api/userApi';

// 响应式数据
const userList = ref([]);
const dialogVisible = ref(false);
const isAdd = ref(true); // 是否为新增操作
const userFormRef = ref(null);
const userForm = reactive({
  id: '',
  userName: '',
  password: '',
  nickName: ''
});

// 页面加载时查询用户列表
onMounted(() => {
  getUserList();
});

// 查询用户列表
const getUserList = async () => {
  try {
    const res = await userApi.getUserList();
    userList.value = res.data;
  } catch (error) {
    console.error(error);
  }
};

// 打开新增弹窗
const openAddDialog = () => {
  resetForm();
  isAdd.value = true;
  dialogVisible.value = true;
};

// 打开编辑弹窗
const openEditDialog = (row) => {
  resetForm();
  isAdd.value = false;
  // 赋值给表单
  Object.assign(userForm, row);
  dialogVisible.value = true;
};

// 重置表单
const resetForm = () => {
  userFormRef.value?.resetFields();
  Object.assign(userForm, {
    id: '',
    userName: '',
    password: '',
    nickName: ''
  });
};

// 提交表单(新增/编辑)
const submitForm = async () => {
  try {
    await userFormRef.value.validate();
    if (isAdd.value) {
      await userApi.saveUser(userForm);
      ElMessage.success('新增成功');
    } else {
      await userApi.updateUser(userForm);
      ElMessage.success('编辑成功');
    }
    dialogVisible.value = false;
    getUserList(); // 刷新列表
  } catch (error) {
    console.error(error);
  }
};

// 删除用户
const deleteUser = async (id) => {
  try {
    await ElMessageBox.confirm('确定要删除该用户吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    });
    await userApi.deleteUser(id);
    ElMessage.success('删除成功');
    getUserList(); // 刷新列表
  } catch (error) {
    ElMessage.info('已取消删除');
  }
};
</script>

<style scoped>
.user-manage {
  padding: 20px;
}
.search-bar {
  display: flex;
  justify-content: flex-end;
}
</style>

三、实战优化技巧:让项目更稳定、更高效

在实际项目开发中,光完成功能还不够,还需要考虑性能、安全、可维护性等问题。以下是我总结的几个关键优化技巧:

1. 后端优化

(1)密码加密存储

上述代码中密码是明文存储,实际项目中必须加密。可使用 Spring Security 的 BCrypt 加密:

java

运行

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

// 加密
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String encryptPassword = encoder.encode("123456");
// 验证
boolean matches = encoder.matches("123456", encryptPassword);
(2)统一跨域配置

单个 Controller 添加 @CrossOrigin 不够优雅,可在后端添加全局跨域配置:

java

运行

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

@Configuration
public class CorsConfig {
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedOriginPattern("*"); // 允许所有域名(生产环境指定具体域名)
        config.addAllowedHeader("*"); // 允许所有请求头
        config.addAllowedMethod("*"); // 允许所有请求方法
        config.setAllowCredentials(true); // 允许携带cookie

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}
(3)MyBatis-Plus 分页插件

查询列表时,分页是刚需,添加 MyBatis-Plus 分页插件:

java

运行

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

2. 前端优化

(1)路由懒加载

减少首屏加载时间,Vue 路由使用懒加载:

javascript

运行

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/user',
      name: 'user',
      component: () => import('@/views/UserManage.vue') // 懒加载
    }
  ]
});

export default router;
(2)表单数据校验增强

前端表单校验可添加更多规则,如用户名长度、密码复杂度等:

javascript

运行

// 示例:用户名校验规则
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在3-20个字符之间', trigger: 'blur' }

3. 数据库优化

(1)分页查询

避免使用SELECT *和全表查询,使用 MyBatis-Plus 的分页查询:

java

运行

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;

// 分页查询
Page<SysUser> page = new Page<>(1, 10); // 第1页,每页10条
IPage<SysUser> userPage = sysUserService.page(page, queryWrapper);
List<SysUser> records = userPage.getRecords(); // 数据列表
long total = userPage.getTotal(); // 总条数
(2)逻辑删除

使用 MyBatis-Plus 的逻辑删除,避免物理删除数据,便于数据恢复。

四、总结

Spring Boot+Vue+MySQL 是目前非常主流的全栈开发技术组合,其优势在于开发效率高、生态完善、社区活跃。在开发过程中,架构设计要注重解耦,代码实现要注重规范,功能完成后要注重优化。本文通过用户管理模块的实战案例,展示了从后端接口到前端页面的完整开发流程,并分享了一些优化技巧。

当然,实际项目中还会涉及更多复杂场景,如用户认证(JWT)、权限管理(RBAC)、Redis 缓存、文件上传等,这些内容我会在后续博客中逐一分享。如果本文对你有帮助,欢迎点赞、收藏、评论交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值