在当今 Web 应用开发领域,Spring Boot、Vue、MySQL 的组合凭借其高效、灵活的特性,成为众多开发者的首选方案。然而,从技术选型到项目落地,过程中往往会遇到环境配置冲突、接口联调不畅、性能瓶颈等问题。本文将结合实际开发经验,从环境搭建、核心功能实现、性能优化、部署运维四个维度,分享全栈开发中的实用技巧与可落地的代码实例,帮助开发者少走弯路,提升项目开发效率与质量。

一、环境搭建:避坑指南与标准化配置

环境搭建是项目开发的基础,若配置不规范,后续会频繁出现兼容性问题。以下是经过实战验证的标准化配置方案,适用于大多数中小型 Web 项目。

1.1 后端 Spring Boot 环境(JDK 17 + Maven 3.9.6)

核心依赖管理(pom.xml 关键配置)

>

.springframework.boot -starter-parent

.2.0> 兼容JDK17 -->

</parent>

<dependencies>

Web核心依赖 -->

<dependency>

<groupId>org.springframework.boot>

spring-boot-starter-web</artifactId>

>

(适配8.0+版本) -->

<dependency>

>com.mysql>

mysql-connector-j>

runtime </dependency>

MyBatis-Plus(简化CRUD,提升开发效率) -->

<dependency>

>com.baomidou</groupId>

<artifactId>mybatis-plus-boot-starter 5.5

参数合法性检查) -->

<groupId>org.springframework.boot</groupId>

>spring-boot-starter-validation </dependency>

跨域与JSON处理 -->

.alibaba 2 .44

</dependencies>

Maven编译配置(指定JDK版本) -->

<build>

>

.boot aven-plugin <source>17</source>

>17

</plugins>

>

避坑技巧:
  • MySQL 版本适配:若使用 MySQL 5.7,需将驱动改为mysql-connector-java,版本指定为5.1.49,同时 URL 添加useSSL=false&serverTimezone=UTC;
  • Maven 依赖冲突:通过mvn dependency:tree命令查看依赖树,排除冲突依赖(如 Spring Boot 自带的jackson与fastjson2冲突时,需排除jackson);
  • 端口占用问题:在application.yml中配置server.port=8081(自定义端口),避免与前端 Vue 或其他服务冲突。

1.2 前端 Vue 环境(Vue 3 + Vite + Axios)

初始化与核心依赖安装

# 初始化Vue项目(使用Vite,比Vue CLI更快)

npm create vite@latest my-vue-app -- --template vue-ts

# 进入项目目录

cd my-vue-app

# 安装核心依赖

npm install axios@1.6.2 vue-router@4.2.5 pinia@2.1.7 element-plus@2.5.1

全局配置(main.ts)

import { createApp } from 'vue'

import App from './App.vue'

import router from './router' // 路由配置

import { createPinia } from 'pinia' // 状态管理

import ElementPlus from 'element-plus' // UI组件库

import 'element-plus/dist/index.css'

import axios from './utils/axios' // 封装后的Axios

const app = createApp(App)

// 全局挂载

app.use(router)

app.use(createPinia())

app.use(ElementPlus)

app.config.globalProperties.$axios = axios

app.mount('#app')

避坑技巧:
  • Vite 跨域配置:在vite.config.ts中配置代理,解决开发环境跨域问题:

export default defineConfig({

server: {

proxy: {

'/api': {

target: 'http://localhost:8081', // 后端服务地址

rewrite: (path) => path.replace(/^\/api/, ''),

changeOrigin: true

}

}

}

})

  • TypeScript 类型报错:Vue 3 推荐使用 TypeScript,若遇到 “类型 “undefined” 不能赋值给类型 XXX”,需在tsconfig.json中开启"strictNullChecks": false(开发初期可临时关闭,后期逐步优化类型)。

二、核心功能实现:从接口到前端联调

以 “用户管理模块” 为例,展示后端接口开发、前端页面实现的完整流程,包含参数校验、异常处理、数据渲染等关键环节。

2.1 后端接口开发(基于 MyBatis-Plus)

1. 实体类定义(User.java)

import com.baomidou.mybatisplus.annotation.IdType;

import com.baomidou.mybatisplus.annotation.TableId;

import com.baomidou.mybatisplus.annotation.TableName;

import jakarta.validation.constraints.Email;

import jakarta.validation.constraints.NotBlank;

import lombok.Data;

@Data

@TableName("sys_user") // 对应MySQL表名

public class User {

@TableId(type = IdType.AUTO) // 自增主键

private Long id;

@NotBlank(message = "用户名不能为空") // 数据校验

private String username;

@NotBlank(message = "密码不能为空")

private String password;

@Email(message = "邮箱格式不正确")

private String email;

private Integer status; // 1-启用,0-禁用

}

2. Mapper 接口(UserMapper.java)

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.example.demo.entity.User;

import org.apache.ibatis.annotations.Mapper;

@Mapper // 标记为MyBatis Mapper接口

public interface UserMapper extends BaseMapper {

// 无需手动写SQL,BaseMapper已提供CRUD方法

}

3. 服务层实现(UserService.java + UserServiceImpl.java)

// UserService.java(接口)

import com.baomidou.mybatisplus.extension.service.IService;

import com.example.demo.entity.User;

public interface UserService extends IService> {

// 扩展自定义方法(如根据用户名查询)

User getByUsername(String username);

}

// UserServiceImpl.java(实现类)

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.example.demo.entity.User;

import com.example.demo.mapper.UserMapper;

import com.example.demo.service.UserService;

import org.springframework.stereotype.Service;

@Service

public class UserServiceImpl extends ServiceImpl, User> implements UserService {

@Override

public User getByUsername(String username) {

// 使用MyBatis-Plus的条件构造器

return lambdaQuery().eq(User::getUsername, username).one();

}

}

4. 控制器(UserController.java)

import com.example.demo.entity.User;

import com.example.demo.service.UserService;

import jakarta.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.http.HttpStatus;

import org.springframework.http.ResponseEntity;

import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController

@RequestMapping("/api/user") // 接口前缀,与前端代理对应

public class UserController {

@Autowired

private UserService userService;

// 1. 查询所有用户

@GetMapping

public ResponseEntity<ListUsers() {

List<User> users = userService.list();

return new ResponseEntity.OK);

}

// 2. 根据ID查询用户

@GetMapping("/{id}")

public ResponseEntity getUserById(@PathVariable Long id) {

User user = userService.getById(id);

return user != null ?

new ResponseEntity<>(user, HttpStatus.OK) :

new ResponseEntity<>(HttpStatus.NOT_FOUND);

}

// 3. 新增用户(参数校验)

@PostMapping

public ResponseEntity(@Valid @RequestBody User user) {

// 密码加密(后续章节会讲,此处先占位)

// user.setPassword(passwordEncoder.encode(user.getPassword()));

boolean success = userService.save(user);

return success ?

new ResponseEntity成功", HttpStatus.CREATED) :

new ResponseEntity HttpStatus.INTERNAL_SERVER_ERROR);

}

// 4. 修改用户

@PutMapping("/{id}")

public ResponseEntity(@PathVariable Long id, @Valid @RequestBody User user) {

user.setId(id); // 确保ID与路径一致

boolean success = userService.updateById(user);

return success ?

new ResponseEntity("修改成功", HttpStatus.OK) :

new ResponseEntity HttpStatus.INTERNAL_SERVER_ERROR);

}

// 5. 删除用户

@DeleteMapping("/{id}")

public ResponseEntity(@PathVariable Long id) {

boolean success = userService.removeById(id);

return success ?

new ResponseEntity", HttpStatus.OK) :

new ResponseEntity删除失败", HttpStatus.INTERNAL_SERVER_ERROR);

}

}

2.2 前端页面实现(用户列表页)

1. 路由配置(router/index.ts)

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

import UserList from '../views/UserList.vue'

const routes: RouteRecordRaw[] = [

{

path: '/user',

name: 'UserList',

component: UserList

}

]

const router = createRouter({

history: createWebHistory(import.meta.env.BASE_URL),

routes

})

export default router

2. Axios 封装(utils/axios.ts)

import axios from 'axios'

const service = axios.create({

baseURL: import.meta.env.VITE_APP_BASE_API, // 环境变量(.env文件配置)

timeout: 5000

})

// 请求拦截器(添加Token等)

service.interceptors.request.use(

(config) => {

const token = localStorage.getItem('token')

if (token) {

config.headers.Authorization = `Bearer ${token}`

}

return config

},

(error) => {

return Promise.reject(error)

}

)

// 响应拦截器(统一异常处理)

service.interceptors.response.use(

(response) => {

return response.data

},

(error) => {

// 处理401未授权、500服务器错误等

if (error.response?.status === 401) {

localStorage.removeItem('token')

window.location.href = '/login'

}

return Promise.reject(error)

}

)

export default service

3. 用户列表组件(views/UserList.vue)

<template>

class="container">

用户管理">>

按钮 -->

-button type="primary" @click="openAddDialog">新增用户

data="userList" border style="width: 100%; margin-top: 20px;">

="id" label="ID" align="center">-table-column>

<el-table-column prop="username" label="用户名" align="center">>

-column prop="email" label="邮箱" align="center"> ="status" label="状态" align="center">

scope">

-tag :type="scope.row.status === 1 ? 'success' : 'danger'">

{{ scope.row.status === 1 ? '启用' : '禁用' }}

>

>

-column label="操作" align="center">

<template #default="scope">

="text" @click="openEditDialog(scope.row)">编辑

="text" @click="deleteUser(scope.row.id)" style="color: #f56c6c;">删除

编辑对话框 -->

<el-dialog v-model="dialogVisible" :title="dialogTitle">

-form :model="form" :rules="rules" ref="formRef" label-width="120px">

label="用户名" prop="username">

-input v-model="form.username"> ="密码" :required="isAdd">

<el-input v-model="form.password" type="password"></el-input>

</el-form-item>

邮箱" prop="email">

-model="form.email">-input>

-form-item>

<el-form-item label="状态" prop="status">

="form.status" placeholder="请选择">

<el-option label="启用" value="1">>

label="禁用" value="0">

>

@click="dialogVisible = false">取消>

type="primary" @click="submitForm">确定</el-button>

</template>

-dialog>

>

<script setup lang="ts">

import { ref, onMounted, reactive } from 'vue'

import { ElTable, ElButton, ElDialog, ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElTag, ElPageHeader, ElMessage } from 'element-plus'

import axios from '@/utils/axios'

// 定义变量

const userList = refconst dialogVisible = ref(false)

const dialogTitle = ref('新增用户')

const isAdd = ref(true)

const formRef = ref<InstanceType ElForm>>()

const form = reactive({

id: '',

username: '',

password: '',

email: '',

status: '1'

})

const rules = reactive({

username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],

email: [{ required: true, message: '请输入邮箱', trigger: 'blur' }, { type: 'email', message: '邮箱格式不正确', trigger: 'blur' }],

status: [{ required: true, message: '请选择状态', trigger: 'change' }]

})

// 页面加载时查询用户列表

onMounted(() => {

getUsers()

})

// 查询所有用户

const getUsers = async () => {

try {

const res = await axios.get('/api/user')

userList.value = res

} catch (error) {

ElMessage.error('查询用户失败')

console.error(error)

}

}

// 打开新增对话框

const openAddDialog = () => {

dialogTitle.value = '新增用户'

isAdd.value = true

// 重置表单

formRef.value?.resetFields()

form.id = ''

form.password = ''

dialogVisible.value = true

}

// 打开编辑对话框

const openEditDialog = (row: any) => {

dialogTitle.value = '编辑用户'

isAdd.value = false

// 回显数据

form.id = row.id

form.username = row.username

form.email = row.email

form.status = row.status + '' // 转为字符串,适配下拉框

dialogVisible.value = true

}

// 提交表单(新增/编辑)

const submitForm = async () => {

try {

// 表单校验

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐