项目开发流程(前端 · 后端)

这是我在实际工程中长期打磨并正在持续更新的一套开发流程。它以「可复现、可维护、可部署」为目标,覆盖从前端工程化初始化到后端安全认证的关键环节。内容偏向实战操作、命令与代码片段为主,适合已有基本前端/后端经验的开发者作为落地指南或快速上手手册。

本文开头直奔要点——我会把每一步的目的必要命令/配置、以及常见的陷阱与注意事项一并给出,便于你在新项目中快速复用或作为检查清单使用。读完你将能:

  • 用 Vite + Vue 快速搭建前端骨架并接入常用组件库(Element Plus、vue-router、axios 等);

  • 将常用的前端工具文件、路由、请求封装做到工程级别的可维护结构;

  • 用 Spring Boot 建立后端服务骨架,配合 MyBatis、Redis 与 Spring Security 完成持久层与安全认证;

  • 了解一套能在开发、调试、上线阶段减少重复工作的实践配置(例如:application.yml、统一响应体 R、登录处理器等)。

下方是我整理并保留的完整开发流程(按“前端 / 后端”分节),代码与配置均为可复制粘贴的实用示例。

—— 下面附上我的开发流程。

前端的:

一、创建 Vue 项目步骤

  1. 用 Vite 脚手架工具创建 Vue 项目。

    在需要创建项目目录的 Dos 窗口输入以下内容,通过 vite 获取最新版本的 Vue 项目结构:

    npm create vite@latest
  2. 键入该 Vue 项目的名称

    继续在原 Dos 窗口输入该项目名称,如:

    my-vue-project
  3. 选择 Vue 项目

    Vite 工具还可以创建除了 Vue 项目之外的其他项目,这里选择 Vue 项目。

  4. 选择项目语言

    TypeScriptJavaScript 等语言供选择,推荐选择 TypeScript

  5. 安装项目依赖

    此时,Vue 的项目已经创建完成。

    cd 进入刚刚创建的项目,在新的目录输入以下命令:

    npm install
  6. 启动项目

    在项目里输入以下命令以启动此 Vue 项目:

    npm run dev

    或者在 Idea 工具中打开此项目,点击项目根目录下的 "dev": "vite",,这一行左边绿色箭头以启动此 Vue 项目。

  7. 使用浏览器浏览项目

    浏览器访问以下默认网址,以浏览该项目:

    http://localhost:5173/

    至此,Vue 项目已经创建完成。下面几个步骤都是更加详细的步骤。

  8. 添加常见依赖

    继续在原 Dos 窗口输入以下命令以安装依赖:(注意,在这个 vue 项目的目录下,即该目录下一定有之前经过 npm install 指令生成的 node_modules 文件夹)

    • element-plus:(饿了么 plus 组件)

      npm install element-plus --save
    • @element-plus/icons-vue:(饿了么 plus icons 组件)

      npm install @element-plus/icons-vue --save
    • vue-router:(路由组件)

      npm install vue-router --save
    • axios:(axios 异步请求)

      npm install axios --save
    • xxx:

    最后:

    编辑 ~\src\main.js 文件中的所有内容,变成如下内容:

    // 从 vue 框架,导入 createApp 函数
    import { createApp } from 'vue'

    // 从 ./App.vue 页面导入 App 组件(一般文件名叫什么,组件名也叫什么)
    import App from './App.vue'
    let app = createApp(App)

    // 从 element-plus 中导入 ElementPlus 组件
    import ElementPlus from 'element-plus'
    // 导入 element-plus 的 CSS 样式,不需要 from 子句
    import 'element-plus/dist/index.css'
    app.use(ElementPlus)

    // 导入 icons
    import * as ElementPlusIconsVue from '@element-plus/icons-vue'
    for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
       app.component(key, component)
    }

    // 从 router.js 中导入 router 组件
    import router from "./router/router.js";
    app.use(router)

    // 利用上面所导入的 createApp 函数,创建一个 vue 应用,mount 是挂载到 #app 地方
    app.mount('#app')
  9. 调整项目文件

    • 可删除 ~\src\style.css,并删除其在 ~\src\main.js 中的引用

    • 可删除 ~\src\components 目录及里面的所有文件

    • 编辑 ~\index.html 文件,这里可以修改网站首页的 Tittle

    • 编辑 ~\src\App.vue 文件中的所有内容,变成如下内容:

      <template>
       <!--渲染路由地址所对应的页面组件-->
       <router-view/>
      </template>
    • 编辑 ~/vite.config.js 文件中的所有内容,变成如下内容:

      import {defineConfig} from 'vite'
      import vue from '@vitejs/plugin-vue'

      // https://vite.dev/config/
      export default defineConfig({
       plugins: [vue()],
       server: {
         // 设置访问的 ip 地址
         host: '0.0.0.0',
         // 设置前端 Vue 服务启动端口
         port: 8080,
         // 设置为 true 代表服务启动时自动打开浏览器
         open: true,
      }
      })
    • 创建 ~/src/view/IndexVue.vue

      这里的 view 文件夹需额外创建。

      文件内容为:

      <template>
       <h1>Hello,这里是系统的首页!</h1>
      </template>

      <script>
      import {defineComponent} from 'vue'
      export default defineComponent({
       name: "IndexVue",
      })
      </script>

      <style scoped>
      </style>
    • 创建 ~/src/router/router.js

      这里的 router 文件夹需额外创建。

      // 从 vue-router 中,导入 createRouter, createWebHistory 函数
      import {createRouter, createWebHistory} from "vue-router";

      // 定义变量
      let router = createRouter({
       // 路由历史
       history: createWebHistory(),

       // 配置路由,是一个数组,里面可以配置多个路由
       routes: [
        {
           // 路由路径,这里设置为"/",代表项目先进入以下vue页面
           path: "/",
           // 路由路径所对应的页面(此文件目录为:~/src/view/LoginVue.vue)
           component: () => import("../view/LoginVue.vue"),
        },
         // {
         //   // 路由路径
         //   path: "/dashboard",
         //   // 路由路径所对应的页面(此文件目录为:~/src/view/DashboardView.vue)
         //   component: () => import("../view/DashboardView.vue"),
         // },
         // 这里添加之后的路由,格式如上,如{},{},在大括号里写具体的路径对应
      ],
      })
      // 导出创建的路由对象
      export default router;

      仅可修改里面的routes数组里面的内容,每增加一个页面,就新增一个路由

    • 创建 ~/src/http/HttpRequest.js

      这里的 http 文件夹需额外创建。

      文件内容为:

      import axios from "axios";

      // 定义后端接口地址的前缀
      axios.defaults.baseURL = "http://localhost:8089";

      export function doGet(url, params) {
       return axios({
         method: 'get',
         url: url,
         // params 参数形式:{name: "张三", age: 22}
         params: params,
         dataType: "json"
      })
      }

      export function doPost(url, data) {
       return axios({
         method: 'post',
         url: url,
         data: data,
         dataType: "json"
      })
      }

      export function doPut(url, data) {
       return  axios({
         method: 'put',
         url: url,
         data: data,
         dataType: "json"
      })
      }

      export function doDelete(url, params) {
       return axios({
         method: 'delete',
         url: url,
         params: params,
         dataType: "json"
      })
      }

      仅可修改里面的定义后端接口地址的前缀中的后端端口号,其他不建议修改

    • 创建 ~/src/util/util.js

      这里的 util 文件夹需额外创建。

      文件内容为:

      import {ElMessage} from "element-plus";

      /**
      * 消息提示工具方法
      * @param msg 消息框的提示信息
      * @param type 消息框的类型,可选 "error"|"success"|"warning"|"primary"
      */
      export function messageTip(msg,type){
         // messageTip 函数
         ElMessage({
             // true 代表提示信息可被关闭
             showClose: true,
             // true 代表显示居中
             center: true,
             // 设置提示信息的消失时间(单位:ms)
             duration: 3000,
             // 示例 msg 的值:'登录成功,欢迎回来!',
             message: msg,
             // 示例 type 的值:可选 "error"|"success"|"warning"|"primary"
             type: type,
        })
      }

      这是个 js 工具类,需提前引入 element-plus。工具的作用是可以更加快捷得写出 弹出消息框 这个需求。使用代码如下:(在 vue 文件的 <script> 区域里中使用)

      import {messageTip} from "../util/util.js";

      // ...
      messageTip("登录成功,欢迎回来!", "success");
      messageTip("登录失败,账号或密码错误!", "error");
      // ...

二、一个登录的例子使用 element-plus

  1. 在官网找合适的表单组件

    我找的是:Form 表单 | Element Plus

  2. 找到想要的复制到自己的程序中

    复制所有可以用到的组件,组件都是在 el-xxx 中的。我复制的是这样的:

    <el-form :model="form" label-width="auto" style="max-width: 600px">
     <el-form-item label="Activity name">
       <el-input v-model="form.name" />
     </el-form-item>
     <el-form-item label="Activity type">
       <el-checkbox-group v-model="form.type">
         <el-checkbox value="Online activities" name="type">
          Online activities
         </el-checkbox>
         <el-checkbox value="Promotion activities" name="type">
          Promotion activities
         </el-checkbox>
         <el-checkbox value="Offline activities" name="type">
          Offline activities
     </el-checkbox>
         <el-checkbox value="Simple brand exposure" name="type">
    Simple brand exposure
         </el-checkbox>
       </el-checkbox-group>
     </el-form-item>
     <el-form-item>
       <el-button type="primary" @click="onSubmit">Create</el-button>
       <el-button>Cancel</el-button>
     </el-form-item>
    </el-form>
  3. 修改成自己所需要的

    <el-form :model="loginForm">
     <!--表单的标题-->
     <el-form-item>
       <h2>系统登录</h2>
     </el-form-item>

     <!--表单的账号-->
     <el-form-item label="账号">
       <el-input v-model="loginForm.loginAct" />
     </el-form-item>

     <!--表单的密码-->
     <el-form-item label="密码">
       <!--为该字段添加密码类型-->
       <el-input type="password" v-model="loginForm.loginPwd" />
     </el-form-item>

     <!--表单的注册按钮-->
     <el-form-item>
       <!--添加按钮点击函数:login-->
       <el-button type="primary" @click="login">登录</el-button>
     </el-form-item>

     <!--表单的记住我选项-->
     <el-form-item>
       <el-checkbox v-model="loginForm.rememberMe">
        记住我
       </el-checkbox>
     </el-form-item>
    </el-form>
  4. 注册 model

    在 js 中注册所有提到的 model

    <script>
    import {defineComponent} from 'vue'
    export default defineComponent({
     name: "LoginVue",
     // 所有的变量都需要注册在 data 里
     data(){
       return {
         // loginForm 是对象,所以注册为 {}
         loginForm: {},
         // loginAct 是字符串,所以注册为 ""
         loginAct: "",
         // loginPwd 是字符串,所以注册为 ""
         loginPwd: "",
         // rememberMe 是布尔类型,所以注册为 false
         rememberMe: false,
      }
    },
     // 所有的方法都需要注册在 methods 里
     methods: {
       // 登录函数
       login() {
     
      }
    }
    })
    </script>
  5. 添加表单的前端验证

    • Step1 为表单添加规则字段 :rules

    • Step2 为字段添加属性字段 prop

    • Step3 注册上述两步的字段

    • Step4 添加详细的验证规则

    <!--为需要添加验证的表单,添加 rules-->
    <el-form :model="loginForm" :rules="loginRules">
     <el-form-item class="form-title">
    <h2>系统登录</h2>
     </el-form-item>

     <!--为需要添加验证的账号字段,加上 prop 属性,值为这个字段的字段名-->
     <el-form-item label="账号" prop="loginAct">
    <el-input v-model="loginForm.loginAct" />
     </el-form-item>

     <!--为需要添加验证的账号字段,加上 prop 属性,值为这个字段的字段名-->
     <el-form-item label="密码" prop="loginPwd">
    <el-input type="password" v-model="loginForm.loginPwd" />
     </el-form-item>

     <el-form-item>
    <el-button type="primary" @click="login" class="form-button">登录</el-button>
     </el-form-item>

     <el-form-item>
    <el-checkbox v-model="loginForm.rememberMe">
    记住我
       </el-checkbox>
     </el-form-item>
    </el-form>
    <script>
    import {defineComponent} from 'vue'
    export default defineComponent({
     name: "LoginVue",
     data(){
       return {
         loginForm: {},
         loginAct: "",
         loginPwd: "",
         rememberMe: false,
         // loginRules 是对象,所以注册为 {}
         loginRules: {
           // 定义 loginAct 的规则,规则可以有多个,所以是数组,用 []
           loginAct: [
             // 添加账号不能为空的验证
            {required: true, message: '请输入账号!', trigger: 'blur'},
          ],
           // 定义 loginPwd 的规则,规则可以有多个,所以是数组,用 []
           loginPwd: [
             // 添加密码不能为空的验证
            {required: true, message: '请输入密码!', trigger: 'blur'},
             // 添加密码长度的验证
            {min: 6, max: 16, message: '密码的长度在 6-16 之间!', trigger: 'blur'},
          ],
        },
      }
    },
     // 所有的方法都需要注册在 methods 里
     methods: {
       // 登录函数
       login() {
     
      }
    }
    })
    </script>
  6. 添加表单的提交验证

    Step1 为需要添加登录验证的表单,添加 ref

    Step2 在提交方法中验证输入框的合法性(共用上一步写的验证)

    Step3 选择数据,发送请求

    Step4 根据请求的响应,做出不同的选择......

    Step5 若响应信息为做出登录请求,则进入系统主页,这里引入路由

    <template>
     <!--为需要添加登录验证的表单,添加 ref-->
     <el-form ref="loginRefForm" :model="loginForm" :rules="loginRules">
       <el-form-item class="form-title">
         <h2>系统登录</h2>
       </el-form-item>

       <el-form-item label="账号" prop="loginAct">
         <el-input v-model="loginForm.loginAct" />
       </el-form-item>

       <el-form-item label="密码" prop="loginPwd">
         <el-input type="password" v-model="loginForm.loginPwd" />
       </el-form-item>

       <el-form-item>
         <el-button type="primary" @click="login" class="form-button">登录</el-button>
       </el-form-item>

       <el-form-item>
           <el-checkbox v-model="loginForm.rememberMe">
            记住我
           </el-checkbox>
       </el-form-item>
     </el-form>
    </template>
    <script>
    import {defineComponent} from 'vue'
    // 自动导入依赖
    import {doPost} from "../http/HttpRequest.js";
    import {messageTip} from "../util/util.js";
    export default defineComponent({
     name: "LoginVue",
     data(){
       return {
         loginForm: {},
         loginAct: "",
         loginPwd: "",
         rememberMe: false,
         loginRules: {
           loginAct: [
            {required: true, message: '请输入账号!', trigger: 'blur'},
          ],
           loginPwd: [
            {required: true, message: '请输入密码!', trigger: 'blur'},
            {min: 6, max: 16, message: '密码的长度在 6-16 之间!', trigger: 'blur'},
          ],
        },
      }
    },
     methods: {
       login() {
         // 提交前验证输入框的合法性
         this.$refs.loginRefForm.validate((isValid) => {
           if (isValid) {
             // 运行到这里说明验证通过
             // 使用 formData 上传数据
             let formData = new FormData();
             // 以键值对的形式写入数据
             formData.append('loginAct', this.loginForm.loginAct);
             formData.append('loginPwd', this.loginForm.loginPwd);
             doPost("/api/login",formData).then((resp) =>{
               // 看看响应的形式是怎么样的
               // console.log(resp);
               if (resp.data.code === 200){
                 messageTip("登录成功,欢迎回来!", "success");
                 window.location.href = "/dashboard";
              }else {
                 messageTip("登录失败,账号或密码错误!", "error");
              }
            });
          }
        })
      },
    }
    })
    </script>
    import {createRouter, createWebHistory} from "vue-router";

    let router = createRouter({
     history: createWebHistory(),

     routes: [
      {
         path: "/",
         component: () => import("../view/LoginVue.vue"),
      },
       // 在这里添加要跳转的页面的路由,一个完整的路由就是由下面的 {},这样在一块的,里面是 path 和 component
      {
         // 路由路径
         path: "/dashboard",
         // 路由路径所对应的页面(此文件目录为:~/src/view/DashboardView.vue)
         component: () => import("../view/DashboardView.vue"),
      },
    ],
    })
    export default router;

三、持续更新中

后端的:

一、创建 SpringBoot 项目步骤

  1. 项目依赖(经验证过的SpringBoot版本:3.5.5)

    • Spring Boot DevTools

    • Lombok

    • Spring Configuration Processor

    • Spring Web

    • Spring Security

    • MyBatis Framework

    • MySQL Driver

    • Spring Data Redis (Access+Driver)

  2. 编写 application.yml

    server:
     # 设置后端 SpringBoot 端口
    port: 8089
    servlet:
       # 项目路径直接是斜杠
      context-path: /

    # 配置数据库连接相关信息
    spring:
    datasource:
      type: com.zaxxer.hikari.HikariDataSource
      url: jdbc:mysql://localhost:3306/xxxxxxxxx?useUnicode=true&characterEncoding=utf8&useSSL=false
      driver-class-name: com.mysql.cj.jdbc.Driver
      username: root
      password: xxxxxxxxxxxxxxx
      hikari:
        maximum-pool-size: 30
        minimum-idle: 5
        connection-timeout: 5000
        idle-timeout: 600000
        max-lifetime: 18000000
    data:
       # 配置 redis 的连接信息
      redis:
        host: 127.0.0.1
        port: 6379
        password:
        database:

    # 指定以下 mapper.xml 文件的位置
    mybatis:
    mapper-locations: classpath:mapper/*.xml
  3. 创建项目的包结构

    • config

      • handler

    • manager

    • mapper

    • model

    • query

    • result

    • service

      • impl

    • util

    • web

  4. 添加项目的工具类

    • 创建 ~/util/JsonUtils.java

      文件内容为:

      package com.sangui.util;


      import com.fasterxml.jackson.core.JsonProcessingException;
      import com.fasterxml.jackson.databind.ObjectMapper;

      /**
      * @Author: sangui
      * @CreateTime: 2025-08-30
      * @Description: json 工具类,进行 java 对象与 json 字符串之间的相互转化
      * @Version: 1.0
      */
      public class JsonUtils {
         private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

         /**
          * 把 java 对象转成 json 字符串
          *
          * @param object java 对象
          * @return json 字符串
          */
         public static String toJson(Object object) {
             try {
                 return OBJECT_MAPPER.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                 throw new RuntimeException(e);
            }
        }

         /**
          * 把 json 字符串转成 java 对象
          *
          * @param json json 字符串
          * @param clazz 想要转的 java 对象 的类型
          * @return java 对象
          * @param <T> java 对象的类型
          */
         public static <T> T toBean(String json, Class<T> clazz) {
             try {
                 return OBJECT_MAPPER.readValue(json, clazz);
            } catch (JsonProcessingException e) {
                 throw new RuntimeException(e);
            }
        }
      }
    • 创建 ~/util/ResponseUtils.java

      文件内容为:

      package com.sangui.util;


      import jakarta.servlet.http.HttpServletResponse;

      import java.io.IOException;
      import java.io.PrintWriter;

      /**
      * @Author: sangui
      * @CreateTime: 2025-08-30
      * @Description: Response 工具类
      * @Version: 1.0
      */
      public class ResponseUtils {

         /**
          * 使用 response,把结果写出到前端
          *
          * @param response 响应
          * @param result 结果
          */
         public static void write(HttpServletResponse response, String result) {
             response.setContentType("application/json;charset=UTF-8");
             PrintWriter writer = null;
             try {
                 writer = response.getWriter();
                 writer.write(result);
                 writer.flush();
            } catch (IOException e) {
                 throw new RuntimeException(e);
            } finally {
                 if (writer != null) {
                     writer.close();
                }
            }
        }
      }
    • 创建 ~/result/CodeEnum.java

      package com.sangui.result;


      import lombok.AllArgsConstructor;
      import lombok.Getter;

      /**
      * @Author: sangui
      * @CreateTime: 2025-08-30
      * @Description: R 状态的枚举类
      * @Version: 1.0
      */
      @Getter
      @AllArgsConstructor
      public enum CodeEnum {
         Ok(200,"成功"),
         Fail(500,"失败");

         // 结果码
         private final int code;

         // 结果信息
         private final String msg;
      }
    • 创建 ~/result/R.java

      package com.sangui.result;


      import lombok.AllArgsConstructor;
      import lombok.Builder;
      import lombok.Data;
      import lombok.NoArgsConstructor;

      /**
      * @Author: sangui
      * @CreateTime: 2025-08-30
      * @Description: 统一封装 web 层向前端页面返回的结果,即 VO(View Object)
      * @Version: 1.0
      */
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      @Data
      public class R {
         // 表示返回的结果码,比如 200 成功,500 失败
         private int code;

         // 表示返回的结果信息,比如 用户登录状态失效了,请求数格式有误......
         private String msg;

         // 表示返回的结果数据,数据可能是一个对象,也可以是一个 int 集合.....
         private Object data;

         public static R ok(Object data) {
             return R.builder()
                    .code(CodeEnum.Ok.getCode())
                    .msg(CodeEnum.Ok.getMsg())
                    .data(data)
                    .build();
        }

         public static R fail() {
             return R.builder()
                    .code(CodeEnum.Fail.getCode())
                    .msg(CodeEnum.Fail.getMsg())
                    .build();
        }

         public static R fail(String msg) {
             return R.builder()
                    .code(CodeEnum.Fail.getCode())
                    .msg(msg)
                    .build();
        }
      }

二、一个使用 SpringSecurity 登录作为例子

  1. 使用 Idea 插件 Free MyBatis Tool 自动生成代码

    使用插件生成以下内容:

    • model 实体类

      • 设置该类的包为:com.sangui.model

    • mapper 接口类

      • 设置接口的名字后缀为:Mapper

      • 设置该类的包为:com.sangui.mapper

    • mapper 映射文件

      • 设置该类的包为:mapper

    注意:

    • 生成代码之前,取消勾选 Rpository-Annotation(Repository注解),保留前面五个选项

    • 生成代码之后,修改数据库中 JdbcType=tinyint 对应类的属性的数据类型,从 Byte 改为 Boolean

    • model 类中的任何 Boolean 类型的变量,都不要加 is 前缀,从 isXxx 改为 xxx

    • 同时在含有 is_xxx 字段的表对应 mapper映射文件<resuletMap> 中设置从 is_xxx 到 xxx 的映射关系

    • 千万别忘了在主程序启动类上加上 mapper 包的扫描,如:自行更改可能不一样的包名)

      @MapperScan(basePackages = {"com.sangui.mapper"})
    • 在生成的 UserMapper 接口中新增按用户名查找用户的方法,示例如下:(自行更改可能不一样的变量名)

      User selectByLoginName(String loginName);
    • 同时在 mapper映射文件 中新增对应 id 的 SQL 语句,示例如下:(自行更改可能不一样的变量名)

      <select id="selectByLoginName" parameterType="java.lang.String" resultMap="BaseResultMap">
      select
         <include refid="Base_Column_List" />
        from t_user
        where login_act = #{loginName,jdbcType=BIGINT}
      </select>
    • 修改用于登录的 User 类

      • 类中加入两个属性,示例如下:

        // 角色 List
        private List<String> roleList;
        // 权限标识符 List
        private List<String> permissionList;
      • 实现 UserDetails 接口

      • 实现 UserDetails 接口的七个方法,示例如下:

        // 实现UserDetails的七个方法
        @JsonIgnore
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
           List<GrantedAuthority> list = new ArrayList<GrantedAuthority>();

           // 角色
           if (!ObjectUtils.isEmpty(this.getRoleList())){
               this.getRoleList().forEach(role -> {
                   list.add(new SimpleGrantedAuthority(role));
              });
          }

           // 权限标识符
           if (!ObjectUtils.isEmpty(this.getPermissionList())){
               this.getPermissionList().forEach(permission -> {
                   list.add(new SimpleGrantedAuthority(permission));
              });
          }
           return list;
        }

        @JsonIgnore
        @Override
        public String getPassword() {
           return this.getPasswordHash();
        }

        @JsonIgnore
        @Override
        public String getUsername() {
           return this.getLoginName();
        }

        @JsonIgnore
        @Override
        public boolean isAccountNonExpired() {
           return this.getAccountExpired();
        }

        @JsonIgnore
        @Override
        public boolean isAccountNonLocked() {
           return this.getAccountLocked();
        }

        @JsonIgnore
        @Override
        public boolean isCredentialsNonExpired() {
           return this.getCredentialsExpired();
        }

        @JsonIgnore
        @Override
        public boolean isEnabled() {
           return this.getEnabled();
        }
  2. 创建 ~/config/handler/MyAuthenticationFailureHandler.java

    文件内容为:

    package com.sangui.config.handler;


    import com.sangui.result.R;
    import com.sangui.util.JsonUtils;
    import com.sangui.util.ResponseUtils;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.stereotype.Component;

    import java.io.IOException;

    /**
    * @Author: sangui
    * @CreateTime: 2025-08-31
    * @Description: user 登录失败的处理器
    * @Version: 1.0
    */
    @Component
    public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {

       @Override
       public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
           // 登录失败,执行该方法,在该方法中返回 json 给前端,就行了
           // 登录失败的统一结果
           R result = R.fail(exception.getMessage());

           // 把 R 对象转成 json
           String resultJson = JsonUtils.toJson(result);

           // 把 R 以json返回给前端
           ResponseUtils.write(response, resultJson);
      }
    }
  3. 创建 ~/config/handler/MyAuthenticationSuccessHandler.java

    文件内容为:

    package com.sangui.config.handler;


    import com.sangui.model.TUser;
    import com.sangui.result.R;
    import com.sangui.util.JsonUtils;
    import com.sangui.util.ResponseUtils;
    import jakarta.servlet.ServletException;
    import jakarta.servlet.http.HttpServletRequest;
    import jakarta.servlet.http.HttpServletResponse;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.stereotype.Component;

    import java.io.IOException;

    /**
    * @Author: sangui
    * @CreateTime: 2025-08-31
    * @Description: user 登录成功的处理器
    * @Version: 1.0
    */
    @Component
    public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
       @Override
       public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
           // 登录成功,执行该方法,在该方法中返回 json 给前端,就行了
           TUser tUser = (TUser) authentication.getPrincipal();

           // 登录成功的统一结果
           R result = R.ok(tUser);

           // 把 R 对象转成 json
           String resultJson = JsonUtils.toJson(result);

           // 把 R 以 json 返回给前端
           ResponseUtils.write(response, resultJson);
      }
    }

    注意:

    • 方法中的 TUser 是之前自动创建的实体类,示例代码可能与实际不同,请根据自己的验证账号的实体类的名字决定。

  4. 创建 ~/config/SecurityConfig.java

    文件内容为:

    package com.sangui.config;


    import com.sangui.config.handler.MyAuthenticationFailureHandler;
    import com.sangui.config.handler.MyAuthenticationSuccessHandler;
    import jakarta.annotation.Resource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.CorsConfigurationSource;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

    import java.util.Arrays;

    /**
    * @Author: sangui
    * @CreateTime: 2025-08-31
    * @Description: SpringSecurity 配置文件
    * @Version: 1.0
    */
    @Configuration
    public class SecurityConfig {
       // 后端验证登录的 URL
       private final String LOGIN_URL = "/api/login";
       // 在 user 表中,登录账号的属性名
       private final String NAME_OF_USERNAME_IN_USER = "loginName";
       // 在 user 表中,密码的属性名
       private final String NAME_OF_PASSWORD_IN_USER = "passwordHash";


       @Resource
       private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

       @Resource
       private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

       @Bean
       public PasswordEncoder passwordEncoder(){
           return new BCryptPasswordEncoder();
      }

       @Bean
       public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, CorsConfigurationSource corsConfigurationSource) throws Exception {
           // 禁用跨站请求伪造
           return httpSecurity.formLogin((formLogin)->{
                       formLogin.loginProcessingUrl(LOGIN_URL)
                              .usernameParameter(NAME_OF_USERNAME_IN_USER)
                              .passwordParameter(NAME_OF_PASSWORD_IN_USER)
                              .successHandler(myAuthenticationSuccessHandler)
                              .failureHandler(myAuthenticationFailureHandler);
                  })
                  .authorizeHttpRequests((authorize)->{
                       // 任何请求都需要登录后才能访问,除了 "/api/login"
                       authorize.requestMatchers(LOGIN_URL).permitAll()
                              .anyRequest().authenticated();
                  })
                   // 方法引用 禁用跨站请求伪造
                  .csrf(AbstractHttpConfigurer::disable)
                   // 支持跨域请求
                  .cors((cors) ->{
                       cors.configurationSource(corsConfigurationSource);
                  })
                  .build();
      }

       @Bean
       public CorsConfigurationSource corsConfigurationSource(){
           CorsConfiguration corsConfiguration = new CorsConfiguration();
           // 允许任何来源,http://localhost:8080
           corsConfiguration.setAllowedOrigins(List.of("*"));
           // 运行任何方式,post, get, delete, put
           corsConfiguration.setAllowedMethods(List.of("*"));
           // 设置运行的请求头
           corsConfiguration.setAllowedHeaders(List.of("*"));

           UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
           source.registerCorsConfiguration("/**",corsConfiguration);
           return source;
      }
    }

    注意:

    • 修改该类中的这三个值以适配自己的 user 表

      // 后端验证登录的 URL
      private final String LOGIN_URL = "/api/login";
      // 在 user 表中,登录账号的属性名
      private final String NAME_OF_USERNAME_IN_USER = "loginName";
      // 在 user 表中,密码的属性名
      private final String NAME_OF_PASSWORD_IN_USER = "passwordHash";
  5. 创建 ~/service/UserService.java

    创建这个 UserService 接口,只需要继承 UserDetailsService 接口就好

    示例代码如下:

    package com.sangui.service;


    import org.springframework.security.core.userdetails.UserDetailsService;

    /**
    * @Author: sangui
    * @CreateTime: 2025-08-31
    * @Description: UserService
    * @Version: 1.0
    */
    public interface UserService extends UserDetailsService {
    }
  6. 创建 ~/service/impl/UserServiceImpl.java

    文件内容为:

    package com.sangui.service.impl;


    import com.sangui.mapper.TUserMapper;
    import com.sangui.model.TUser;
    import com.sangui.service.UserService;
    import jakarta.annotation.Resource;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;

    /**
    * @Author: sangui
    * @CreateTime: 2025-08-31
    * @Description: UserServiceImpl
    * @Version: 1.0
    */
    @Service
    public class UserServiceImpl implements UserService {
       @Resource
       TUserMapper userMapper;

       @Override
       public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
           TUser tUser = userMapper.selectByLoginName(username);
           if (tUser == null) {
               throw new UsernameNotFoundException("登录账号不存在!");
          }
           return tUser;
      }
    }

三、持续更新中

  • 微信
  • 赶快加我聊天吧
  • QQ
  • 赶快加我聊天吧
  • weinxin
三桂

发表评论 取消回复 您未登录,登录后才能评论,前往登录