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

发布于:2025-07-04 ⋅ 阅读:(23) ⋅ 点赞:(0)

目录

一、前言

二、后端搭建

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

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令牌中解析出用户名两个方法。

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.具体实现

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

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
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

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
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 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
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.后端架构示例
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项目目录下执行以下命令:

npm install -g @vue/cli

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

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.安装依赖

npm install element-ui --save

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

import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

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

npm install axios --save

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

2.配置

1.配置端口、跨域

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

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,配置挂载全局的示例、路由、拦截器信息等

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.稍微改造入口页面(和初始化区别不大,有需要就自行修改)

<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

<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(首页兼主要前端布局)

<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

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

</style>

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

3.配置路由信息

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

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

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.前端架构示例

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,有需要的同学可以自行下载参考。

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

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


网站公告

今日签到

点亮在社区的每一天
去签到