SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:项目搭建(一)

目录

一、前言

二、后端搭建

1.实现JwtUtil类

1.什么是jwt?

2.具体实现

2.配置跨域类

1.什么是跨域?为什么需要跨域?

2.具体实现

3.登录接口实现

1.LoginController.java

2.UserService.java

3.UserServiceImpl.java

4.mapper.java

5.mapper.xml

6.校准application.yml

7.后端架构示例

三、前端搭建

1.初始化vue

[1.安装 Vue CLI](#1.安装 Vue CLI)

[2.安装 Element UI](#2.安装 Element UI)

2.配置

1.配置端口、跨域

2.配置挂载、请求拦截器

3.页面实现

1.改造项目入口页面App.vue

2.初始化登录、首页、用户页面

3.配置路由信息

四、附:源码

五、结语

一、前言

突然想搭建一个前后端的框架,以后在次基础上扩展、学习其它技术也方便点,也给自己一个学习、查漏补缺的过程吧。

此项目是在我上一个文章:Spring Boot整合MyBatis+MySQL教程(附详细代码)的基础上开发的,有需要的同学可以关注查看一下。

(注:源码我会在文章结尾提供gitee连接,需要的同学可以去自行下载)

二、后端搭建

1.实现JwtUtil类

1.什么是jwt?

JWT(JSON Web Token)是一种开放标准(RFC 7519),用于在网络应用之间安全地传递声明(claims)。它以一种紧凑且自包含的方式,将用户信息、权限或其他数据通过数字签名或加密进行传输。

核心作用:

  1. 身份验证:用户登录后,服务器返回一个 JWT,客户端在后续请求中携带该 token。
  2. 信息交换:安全地在各方之间传递结构化数据(如用户信息、权限等)。
  3. 无状态认证:服务端无需保存 session,所有认证信息都包含在 token 中
2.具体实现

暂时只实现了生成Jwt令牌从Jwt令牌中解析出用户名两个方法。

java 复制代码
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;

import java.security.Key;
import java.util.Date;
/**
 * JwtUtil 是一个工具类,用于生成和解析 JWT(JSON Web Token)。
 * 主要功能包括:
 * - 生成带有用户名和过期时间的 JWT 令牌
 * - 从 JWT 令牌中解析出用户名
 *
 * 注意事项:
 * - 密钥(SECRET_KEY)应通过配置文件管理,避免硬编码
 * - 过期时间(EXPIRATION)可按业务需求调整
 */
public class JwtUtil {
    /**
     * JWT 签名所使用的密钥。
     * 在生产环境中建议使用更安全的方式存储,如配置中心或环境变量。
     */
    public static final Key SIGNING_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS512);    /**
     * JWT 令牌的有效期,单位为毫秒。
     * 当前设置为 24 小时(86,400,000 毫秒)。
     */
    private static final long EXPIRATION = 86400000; // 24小时

    /**
     * 生成 JWT 令牌。
     *
     * @param username 用户名,作为 JWT 的 subject 字段
     * @return 返回生成的 JWT 字符串
     */
    public static String generateToken(String username) {
        return Jwts.builder()
                // 设置 JWT 的主题(通常为用户标识)
                .setSubject(username)
                // 设置 JWT 的过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
                // 使用 HS512 算法签名,并指定密钥
                .signWith(SIGNING_KEY)
                // 构建并返回紧凑格式的 JWT 字符串
                .compact();
    }

    /**
     * 从 JWT 令牌中解析出用户名。
     *
     * @param token 需要解析的 JWT 字符串
     * @return 解析出的用户名(subject)
     * @throws JwtException 如果 token 无效或签名不匹配会抛出异常
     */
    public static String parseUsername(String token) {
        return Jwts.parser()
                // 设置签名验证所使用的密钥
                .setSigningKey(SIGNING_KEY)
                // 解析并验证 JWT 令牌
                .parseClaimsJws(token)
                // 获取 JWT 中的负载(claims),并提取 subject(用户名)
                .getBody()
                .getSubject();
    }

}

2.配置跨域类

1.什么是跨域?为什么需要跨域?

跨域(Cross-Origin)是浏览器的一种安全机制,称为 同源策略 (Same-Origin Policy)。它限制了不同源之间的资源访问,防止恶意网站通过脚本访问其他网站的敏感数据。两个 URL 的协议、域名、端口三个部分完全相同,才被认为是同源。

2.具体实现
java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
 * 跨域配置类
 */
@Configuration
@EnableWebMvc
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**") // 匹配所有以 /api 开头的接口
                .allowedOrigins("http://localhost:9527") // 前端地址
                .allowedMethods("GET", "POST", "PUT", "DELETE")
                .allowedHeaders("*")//允许前端发送哪些请求头
                .allowCredentials(true);//是否允许发送 Cookie 或认证信息(如 session、token)。
    }
}

3.登录接口实现

1.LoginController.java
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.wal.userdemo.DTO.req.LoginReq;
import org.wal.userdemo.service.UserService;
import org.wal.userdemo.utils.JwtUtil;
import org.wal.userdemo.utils.Result;

@RestController
@RequestMapping("/api/auth")
public class LoginController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public Result<?> login(@RequestBody LoginReq request) {

        // 验证账号密码
        Integer result =userService.checkUser(request.getUsername(), request.getPassword());
        if (result == 1) {
            String token = JwtUtil.generateToken(request.getUsername());
            return Result.success(token);
        } else {
            return Result.error("用户名或密码错误");
        }
    }
}
2.UserService.java
java 复制代码
import org.wal.userdemo.entity.UserEntity;

import java.util.List;

public interface UserService {

    /**
     * 登录校验
     * @param username
     * @param password
     * @return
     */
    Integer checkUser(String username, String password);

  //省略其他接口信息。。。。。。
}
3.UserServiceImpl.java
java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.wal.userdemo.entity.UserEntity;
import org.wal.userdemo.mapper.UserMapper;
import org.wal.userdemo.service.UserService;

import java.util.Collections;
import java.util.List;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public Integer checkUser(String username, String password) {
        return userMapper.checkUser(username, password);
    }
//省略其他接口实现。。。。。。
}
4.mapper.java
java 复制代码
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.wal.userdemo.entity.UserEntity;

import java.util.List;

@Mapper
public interface UserMapper {
    /**
     * 登录校验
     * @param username
     * @param password
     * @return
     */
    Integer checkUser(@Param("username") String username, @Param("password") String password);
//省略其他mapper接口。。。。。。
}
5.mapper.xml
XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.wal.userdemo.mapper.UserMapper">

    <resultMap id="BaseResultMap" type="org.wal.userdemo.entity.UserEntity">
        <id column="id" property="id" />
        <result column="name" property="name" />
        <result column="age" property="age" />
    </resultMap>
    <select id="checkUser" resultType="java.lang.Integer" parameterType="String">
        select count(*) from user where name = #{username} and password = #{password} and del_flag = 0;
    </select>
//省略其他mapper.xml的代码。。。。。。
</mapper>
6.校准application.yml
java 复制代码
server:
  # 端口
  port: 8080
spring:
  datasource:
    # 数据库连接地址
    url: jdbc:mysql://127.0.0.1:3306/wal?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
# mybatis 配置
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.wal.userdemo.entity
# 日志
logging:
  level:
    org.wal.userdemo.mapper: debug
7.后端架构示例
html 复制代码
UserDemo/
├── src/
│   └── main/
│       ├── java/
│       │   └── org.wal.userdemo/                # 主包
│       │       ├── UserDemoApplication.java     # 启动类
│       │       │
│       │       ├── config/                      # 配置类
│       │       │   └── CorsConfig.java          # 跨域配置
│       │       │
│       │       ├── controller/                  # 控制器层(接收 HTTP 请求)
│       │       │   ├── LoginController.java
│       │       │   └── UserController.java      # 用户相关接口
│       │       │
│       │       ├── service/                     # 业务逻辑层
│       │       │   ├── UserService.java         # 接口
│       │       │   └── impl/
│       │       │       └── UserServiceImpl.java
│       │       │
│       │       ├── mapper/                      # 数据访问层(MyBatis Mapper)
│       │       │   └── UserMapper.java
│       │       │
│       │       ├── entity/                      # 实体类(与数据库表对应)
│       │       │   └── User.java
│       │       │
│       │       ├── dto/
│       │       │   └── req/                     # 请求参数 DTO 目录
│       │       │       └── LoginReq.java        # 登录请求参数对象
│       │       │
│       │       └── utils/                       # 工具类和通用组件
│       │           ├── JwtUtil.java             # JWT 工具类
│       │           └── Result.java              # 统一返回结果封装类
│       │
│       └── resources/
│           ├── application.yml                  # 配置文件
│           ├── mapper/
│           │   └── UserMapper.xml               # MyBatis XML 映射文件
│           └── logback-spring.xml               # 日志配置(可选)
│
└── pom.xml                                      # Maven 项目配置

到这里后端就告一段落了,暂时就这些,后续有需要会再添加和配置。

三、前端搭建

1.初始化vue

1.安装 Vue CLI

1.打开终端,切换到userdemo项目目录下执行以下命令:

html 复制代码
npm install -g @vue/cli

2.创建新项目,执行以下命令:

html 复制代码
vue create userdemo-vue

选择默认配置即可,或者手动选择 Babel、Router 等功能。

(稍等片刻等待加载依赖、目录)

这是 Vue CLI 提供的标准命令,用于快速搭建基于 Webpack、Babel、Vue Router 等的现代前端开发环境。使用这个命令:

  1. 会自动为你配置好开发服务器、构建工具、ESLint、TypeScript 支持等;
  2. 可以选择默认配置或手动选择功能(如 Babel、Router、Vuex、CSS 预处理器等);
  3. 保证项目结构标准化,方便后续维护和部署。
2.安装 Element UI

1.安装依赖

html 复制代码
npm install element-ui --save

2.在 main.js 中引入 Element UI:

javascript 复制代码
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

3.安装 Axios(用于调用后端 API),执行以下命令:

javascript 复制代码
npm install axios --save

(注:安装过程中的npm版本问题,可以更换npm的版本后再执行命令)

2.配置

1.配置端口、跨域

1.打开vue.config.js,配置端口、跨域代理信息

javascript 复制代码
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    port: 9527,
    proxy: { // 配置代理,以api为前缀的请求会转发到 target 属性配置的代理服务器上
      '/api': {
        target: 'http://localhost:8080', // 后端服务地址和端口
        changeOrigin: true, // 是否跨域
      }
    }
  }
})
2.配置挂载、请求拦截器

1.打开main.js,配置挂载全局的示例、路由、拦截器信息等

javascript 复制代码
import Vue from 'vue'
import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import router from './router'
import axios from 'axios'

Vue.use(ElementUI);
Vue.config.productionTip = false

new Vue({
  router, // 必须挂载 router 实例
  render: h => h(App),
}).$mount('#app')
// 创建一个 axios 实例
const apiClient = axios.create({
  baseURL: '/api',
})
// 请求拦截器
apiClient.interceptors.request.use(config => {
  const token = localStorage.getItem('token')
  if (token) {//token为登录接口返回,如果token不存在说明此次请求为登录请求或者外部的非法请求
    config.headers['Authorization'] = 'Bearer ' + token
  }
  return config
})

export default apiClient

3.页面实现

1.改造项目入口页面App.vue

App.vue是vue初始化生成页面,在main.js中已经引入并且挂载初始化。

1.稍微改造入口页面(和初始化区别不大,有需要就自行修改)

html 复制代码
<template>
  <div id="app">
    <router-view />
  </div>
</template>

<script>
export default {
  name: 'App',
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
body {
  margin: 0 !important;
}
</style>
2.初始化登录、首页、用户页面

1.在src下创建view目录用来存放页面(结构可自由选择)

2.在view目录下创建login.vue

html 复制代码
<template>
  <el-container style="height: 100vh;">
    <el-main class="login-main">
      <el-row type="flex" justify="center" align="middle" style="height: 100%;">
        <el-col :xs="20" :sm="12" :md="8" :lg="6" :xl="4">
          <el-card class="login-card">
            <div slot="header" class="login-header">
              <h2>用户管理平台</h2>
            </div>
            <el-form ref="form" :model="formData" label-width="80px" :rules="rules">
              <el-form-item label="用户名" prop="username">
                <el-input v-model="formData.username" placeholder="请输入用户名"></el-input>
              </el-form-item>
              <el-form-item label="密码" prop="password">
                <el-input v-model="formData.password" show-password placeholder="请输入密码"></el-input>
              </el-form-item>
              <el-form-item>
                <el-button type="primary" @click="login" style="width: 100%;">登录</el-button>
              </el-form-item>
            </el-form>
          </el-card>
        </el-col>
      </el-row>
    </el-main>
  </el-container>
</template>

<script>
import axios from 'axios'

export default {
  name: 'UserLogin',
  data() {
    return {
      formData: {
        username: '',
        password: ''
      },
      rules: {
        username: [
          { required: true, message: '用户名不能为空', trigger: 'blur' }
        ],
        password: [
          { required: true, message: '密码不能为空', trigger: 'blur' }
        ],

      }
    };
  },
  methods: {
    async login() {
      this.$refs.form.validate(async valid => {
        if (valid) {
          try {
            const res = await axios.post('/api/auth/login', this.formData)
            console.table(res.data);
            if (res.data.code === 200) {
              const token = res.data.data
              localStorage.setItem('token', token)
              this.$router.push('/')
              this.$message.success('登录成功!');
            } else {
              this.$message.error(res.data.message);
            }
          } catch (error) {
            this.$message.error('请求失败,请检查网络或服务端状态')
          }
        } else {
          return false;
        }
      });
    }
  }
};
</script>

<style scoped>
.login-main {
  background: linear-gradient(to right, #e0f7fa, #fffde7);
  /* 柔和渐变背景 */
}

.login-card {
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  border-radius: 10px;
}

.login-header {
  text-align: center;
  margin-bottom: 20px;
}
</style>

3.在view下创建index.vue(首页兼主要前端布局)

html 复制代码
<template>
  <el-container class="home-container">
    <!-- 左侧区域 -->
    <el-aside class="left-section" :width="'12%'">
      <!-- 左上部分:logo + 标题 -->
      <div class="top-left">
        <div class="logo-container">
          <img src="../assets/logo.png" alt="logo">
        </div>
        <h1>我的管理系统</h1>
      </div>

      <!-- 左下部分:菜单 -->
      <el-menu
        default-active="1"
        class="sidebar-menu"
        :collapse="isCollapse"
        :collapse-transition="false"
        @open="handleOpen"
        @close="handleClose"
        background-color="#304156"
        text-color="#fff"
        active-text-color="#ffd04b"
      >
        <el-submenu index="1">
          <template #title>
            <i class="el-icon-document"></i>
            <span>内容管理</span>
          </template>
          <el-menu-item index="1-1">文章列表</el-menu-item>
          <el-menu-item index="1-2">新增文章</el-menu-item>
        </el-submenu>

        <el-submenu index="2">
          <template #title>
            <i class="el-icon-user"></i>
            <span>用户管理</span>
          </template>
          <el-menu-item index="2-1">用户列表</el-menu-item>
        </el-submenu>

        <el-menu-item index="3" disabled>
          <i class="el-icon-document"></i>
          <span slot="title">导航三</span>
        </el-menu-item>

        <el-menu-item index="4">
          <i class="el-icon-setting"></i>
          <span slot="title">导航四</span>
        </el-menu-item>
      </el-menu>
    </el-aside>

    <!-- 右侧区域 -->
    <el-container class="right-section">
      <!-- 右上部分:顶部导航 -->
      <el-header class="top-right-header">
        <div class="header-right">
          <span>欢迎,Admin</span>
          <el-button type="text" @click="logout">退出</el-button>
        </div>
      </el-header>

      <!-- 右下部分:主内容区域 -->
      <el-main class="main-content">
        <!-- <router-view /> -->
         <user/>
      </el-main>
    </el-container>
  </el-container>
</template>

<script>
import user from './user.vue'

export default {
  name: 'userIndex',
  components: { user },
  data() {
    return {
      isCollapse: false, // 默认展开
    };
  },
  methods: {
    logout() {
      localStorage.removeItem('token') // 清除 token
      this.$router.push('/login');
    },
    handleOpen(key, keyPath) {
      console.log(key, keyPath);
    },
    handleClose(key, keyPath) {
      console.log(key, keyPath);
    },
  },
};
</script>

<style scoped>
.home-container {
  height: 100vh;
}

/* 左侧整体样式 */
.left-section {
  display: flex;
  flex-direction: column;
  background-color: #304156;
  color: white;
  padding: 10px;
  width: 50px;
}

/* 左上角 logo 和标题 */
.top-left {
  display: flex;
  align-items: center;
  margin-bottom: 20px;
}

.logo-container {
  margin-right: 10px;
}

.logo-container img {
  height: 36px;
  width: auto;
  object-fit: contain;
}

.top-left h1 {
  font-size: 18px;
  margin: 0;
  color: white;
}

/* 菜单样式 */
.sidebar-menu {
  flex: 1;
  border-right: none;
}

/* 右侧整体样式 */
.right-section {
  display: flex;
  flex-direction: column;
}

/* 右上角导航栏 */
.top-right-header {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  background-color: #ffffff;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  padding: 0 20px;
}

.header-right {
  display: flex;
  align-items: center;
}

/* 主内容区域 */
.main-content {
  padding: 20px;
}
</style>

4.在view目录下创建user.vue

html 复制代码
<template>
   <div>user页面开发中。。。。。。</div>
</template>
<script>
export default {
  name: 'userView', 
}
</script>
<style scoped>

</style>

(注:用户页面后续文章会具体讲解,需要的同学可以关注我

3.配置路由信息

1.打开src下router下的index.js(已经在main.js中挂载)

2.配置路由、路由守卫信息等(根据需要自行配置)

javascript 复制代码
import Vue from 'vue'
import Router from 'vue-router'
import Login from '../view/login.vue' // 替换为你自己的登录页组件
import Index from '../view/index.vue'
import user from '../view/user.vue'
Vue.use(Router)

const router = new Router({
    mode: 'history',
    routes: [
      {
        path: '/',
        name: 'Index',
        component: Index,
        // 如果你想让首页重定向到其他路径,可以加 redirect
      },
      {
        path: '/index',
        redirect: '/'
      },
      {
        path: '/login',
        name: 'Login',
        component: Login
      },
      {
        path: '/user',
        name: 'user',
        component: user,
        meta: { requiresAuth: true } // 表示该页面需要登录才能访问
      }
    ]
  })
  // 全局前置守卫
router.beforeEach((to, from, next) => {
  const isAuthenticated = !!localStorage.getItem('token') // 假设你用 token 判断登录状态
  console.log("to.name:" + to.name);
  console.log("isAuthenticated:" + isAuthenticated)
  console.log("to.path" + to.path);

  if (to.path === '/'|| to.path === '/index' || to.name === 'Index' 
    || to.matched.some(record => record.meta.requiresAuth)) {
    // 如果目标路由是 Index 或者需要登录才能访问
    if (isAuthenticated) {
      next() // 已登录,允许访问
    } else {
      next({ name: 'Login' }) // 未登录,跳转到登录页
    }
  } else {
    console.log("不需要验证的页面")
    next() // 不需要登录验证的页面直接放行
  }
})
  export default router//导出,让项目中其他地方可以使用

4.前端架构示例

html 复制代码
userdemo-vue/
├── public/                     # 静态资源(不会被 webpack 处理)
│   └── favicon.ico
├── src/                        # 源码目录
│   ├── assets/                 # 图片、字体等静态资源(会被 webpack 处理)
│   │   └── logo.png
│   ├── components/             # 可复用的组件(如公共头部、按钮等)
│   │   └── ...
│   ├── view/                   # 页面视图组件
│   │   ├── login.vue           # 登录页
│   │   ├── index.vue           # 首页
│   │   └── user.vue            # 用户页
│   ├── router/                 # 路由配置
│   │   └── index.js            # 路由表及导航守卫
│   ├── App.vue                 # 根组件
│   └── main.js                 # 入口文件(初始化 Vue 实例)
├── vue.config.js               # Vue CLI 配置(代理、端口等)
├── package.json                # 项目描述和依赖
└── README.md                   # 项目说明文档

四、附:源码

1.源码下载地址

https://gitee.com/wangaolin/user-demo.git

同学们有需要可以自行下载查看,此文章是dev-vue分支。

五、结语

通过搭建这个前后端框架,不仅巩固了Spring Boot、MyBatis和MySQL的技术栈,也为后续扩展和学习新技术提供了基础。项目源码已上传至Gitee,有需要的同学可以自行下载参考。

希望这个框架能帮助大家更快上手开发,也欢迎一起交流改进。

持续学习,不断进步,共勉!

相关推荐
-SGlow-5 小时前
MySQL相关概念和易错知识点(2)(表结构的操作、数据类型、约束)
linux·运维·服务器·数据库·mysql
新world6 小时前
mybatis-plus从入门到入土(三):持久层接口之IService
mybatis
双力臂4047 小时前
Spring Boot 单元测试进阶:JUnit5 + Mock测试与切片测试实战及覆盖率报告生成
java·spring boot·后端·单元测试
水瓶_bxt8 小时前
Centos安装HAProxy搭建Mysql高可用集群负载均衡
mysql·centos·负载均衡
♡喜欢做梦8 小时前
【MySQL】深入浅出事务:保证数据一致性的核心武器
数据库·mysql
遇见你的雩风8 小时前
MySQL的认识与基本操作
数据库·mysql
dblens 数据库管理和开发工具8 小时前
MySQL新增字段DDL:锁表全解析、避坑指南与实战案例
数据库·mysql·dblens·dblens mysql·数据库连接管理
weixin_419658318 小时前
MySQL的基础操作
数据库·mysql
itLaity9 小时前
基于Kafka实现简单的延时队列
spring boot·分布式·kafka
midsummer_woo9 小时前
基于spring boot的医院挂号就诊系统(源码+论文)
java·spring boot·后端