web端-登录页面验证码的实现(springboot+vue前后端分离)超详细

目录

一、项目技术栈

二、实现效果图

​三、实现路线

四、验证码的实现步骤

五、完整代码

1.前端

2.后端


一、项目技术栈

登录页面暂时涉及到的技术栈如下:

前端 Vue2 + Element UI + Axios,后端 Spring Boot 2 + MyBatis + MySQL + JWT  + Maven

二、实现效果图

三、作者有话说

        本项目验证码的实现只与前端有关,所有后面会先着重讲前端是怎么实现的,登录页面前后端完整的代码放在文章的最后。

四、验证码的实现步骤

        本项目将验证码作为一个单独的组件封装了起来,这样方便在注册等其他页面调用。

首先在components目录下新建一个ValidCode.vue文件

ValidCode.vue

<template><!-- 验证码组件容器- class="ValidCode":基础样式类- disabled-select:禁止文本选中(防止用户复制验证码)- @click="refreshCode":点击刷新验证码--><div class="ValidCode disabled-select" style="width: 100%; height: 100%" @click="refreshCode"><!-- 循环渲染验证码字符- v-for="(item, index) in codeList":遍历验证码字符数组- :key="index":循环的唯一标识- :style="getStyle(item)":动态绑定每个字符的样式(颜色、旋转角度等)- {{item.code}}:显示字符内容--><span v-for="(item, index) in codeList" :key="index" :style="getStyle(item)">{{item.code}}</span></div>
</template><script>
// 导出验证码组件
export default {name: 'validCode', // 组件名称data () {return {length: 4, // 验证码长度(默认4位)codeList: [] // 存储验证码字符及样式信息的数组}},mounted () {// 组件挂载完成后初始化生成验证码this.createdCode()},methods: {/*** 刷新验证码* 调用生成验证码的方法,实现点击刷新功能*/refreshCode () {this.createdCode()},/*** 生成验证码的核心方法* 1. 随机生成指定长度的字符* 2. 为每个字符生成随机样式(颜色、间距、旋转角度)* 3. 触发事件将生成的验证码传递给父组件*/createdCode () {let len = this.length, // 验证码长度codeList = [], // 临时存储验证码字符数组// 验证码字符库(排除了易混淆的字符如I、O、l、0等)chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789',charsLen = chars.length // 字符库长度// 循环生成指定长度的验证码字符for (let i = 0; i < len; i++) {// 生成随机RGB颜色值(控制在较深范围,保证可读性)let rgb = [Math.round(Math.random() * 220), // R值(0-220)Math.round(Math.random() * 240), // G值(0-240)Math.round(Math.random() * 200)  // B值(0-200)]// 向数组添加一个验证码字符及样式信息codeList.push({code: chars.charAt(Math.floor(Math.random() * charsLen)), // 随机获取字符color: `rgb(${rgb})`, // 随机颜色padding: `${[Math.floor(Math.random() * 10)]}px`, // 随机内边距(0-10px)transform: `rotate(${Math.floor(Math.random() * 90) - Math.floor(Math.random() * 90)}deg)` // 随机旋转角度(-90到90度)})}// 将生成的验证码数组赋值给data中的codeListthis.codeList = codeList// 触发自定义事件,将验证码字符串传递给父组件// 格式:将数组中每个item的code拼接成字符串this.$emit('update:value', codeList.map(item => item.code).join(''))},/*** 获取单个验证码字符的样式* @param {Object} data - 包含字符样式信息的对象* @returns {Object} 样式对象*/getStyle (data) {return {color: data.color, // 字符颜色fontSize: data.fontSize, // 字体大小(虽然未在生成时定义,但预留扩展)padding: data.padding, // 内边距transform: data.transform // 旋转角度}}}
}
</script><style>
/* 验证码容器样式 */
.ValidCode{display: flex; /* 使用flex布局 */justify-content: center; /* 水平居中 */align-items: center; /* 垂直居中 */cursor: pointer; /* 鼠标悬停显示手型,提示可点击 */
}/* 验证码字符样式 */
.ValidCode span {display: inline-block; /* 使旋转等样式生效 */font-size: 18px; /* 基础字体大小 */
}/* 禁止文本选中样式(通过user-select实现) */
.disabled-select {user-select: none; /* 标准属性 */-webkit-user-select: none; /* Chrome/Safari/Edge */-moz-user-select: none; /* Firefox */-ms-user-select: none; /* IE/Edge */
}
</style>

       我先将验证码的相关代码部分简单讲一下,大家学会方法之后就可以按下面的步骤拿去给其他页面用了, 最后会给大家完整的login.vue文件代码。

        前面这个ValidCode.vue文件是子组件,接下来我们要去login.vue父组件中去引入它

接下来就是将组件放在我们html模版上

然后到js了

我们的data里面需要添加两个变量,一个是用来接收子组件传来的验证码code,另一个用来存储用户输入的验证码validCode,后面的rules也就是校验规则需要将两个值进行比较

那接下来我们就说说校验规则的实现吧

在表单规则 rules 中,插入下面内容,实现当用户输入验证码或失去焦点时(trigger: 'blur'),Element UI 会自动调用 validateCode 函数进行校验。

所以下面我们还要定义一个验证函数,用于 Element UI 表单的验证码校验逻辑。(注意这个函数是写在data里面的不是methods)

到这还差最后一步,在login.vue里面定义getCode函数,使前面我们刚刚在父组件data中定义好的code给它赋上值

到这我们就实现了验证码组件的引入了

看到这有个问题我提问一下:父组件 login.vue 和子组件 ValidCode.vue 它们之间是怎么传递的验证码值呢?

        咳咳,我也知道对于刚入门的小伙伴来说父子组件通信确实是个小难点,毕竟大家都是这么走来的,既然我这篇文章是写给小白的,那我当然要负责到底,简单说一下吧

首先子组件生成验证码之后,会通过 $emit 触发自定义事件,将验证码数据传递给父组件。具体位置在methods对象属性里的createdCode方法的末尾处

父组件呢,是通过 @update:value 监听事件,并在getCode回调函数中接收数据,通过监听该事件来接收数据。

再多说一嘴 'update:value' 这个自定义事件名称是可以修改的,只要保证使用的时候子组件触发的 $emit 的事件名和父组件 @事件名 监听的名称是完全一致的即可。

'update:value' 这种格式,其实是 Vue 中一种推荐的命名约定(用于实现 “双向绑定” 的语法糖)

当子组件触发 update:xxx 事件时,父组件可以简化为 v-model:xxx 的形式监听

<!-- 等价于 @update:value="code = $event" -->
<valid-code v-model:value="code" />

五、完整代码

下面是本项目的登录部分完整代码,大家可以做适量的增删工作

登录页面做了角色选择,如果大家的表里面没有role字段 可以将该部分内容去掉不影响整体的使用

1.前端

Login.vue

<template><div class="container"><div style="width: 400px; padding: 30px; background-color: white; border-radius: 5px; text-align: center; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);" ref="cardRef"><div style="text-align: center; font-size: 25px; margin-bottom: 30px; color: #333; font-weight: bold; position: relative; display: inline-block;" ref="titleRef">社区团购系统<div ref="lineRef" style="position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%); height: 2px; background: linear-gradient(90deg, rgba(51,51,51,0) 0%, rgba(51,51,51,0.8) 50%, rgba(51,51,51,0) 100%);"></div></div><div><el-form :model="form" :rules="rules" ref="formRef"><el-form-item prop="username"><el-input prefix-icon="el-icon-user" placeholder="请输入账号" v-model="form.username"></el-input></el-form-item><el-form-item prop="password"><el-input prefix-icon="el-icon-lock" placeholder="请输入密码" show-password  v-model="form.password"></el-input></el-form-item><el-form-item prop="validCode"><div style="display: flex;"><el-input prefix-icon="el-icon-circle-check" placeholder="请输入验证码" v-model="form.validCode" size="medium" style="flex: 1;"></el-input><div style="flex:1; height:36px;"><valid-code @update:value="getCode" /> </div></div></el-form-item><el-form-item prop="role"><el-radio-group v-model="form.role"><el-radio label="ADMIN">管理员</el-radio><el-radio label="BUSINESS">商家</el-radio></el-radio-group></el-form-item><el-form-item><el-button style="width: 100%; background-color: #333; border-color: #333; color: white" @click="login">登 录</el-button></el-form-item><div style="display: flex; align-items: center"><div style="flex: 1"></div><div style="flex: 1; text-align: right; font-size: 12px;"><a href="/register">注册商家账号</a></div></div></el-form></div></div></div>
</template><script>
import ValidCode from '@/components/ValidCode.vue';export default {name: "Login",components: {ValidCode},data() {// 验证码校验规则const validateCode = (rule, value, callback) => {if (value === '') {callback(new Error('请输入验证码'))} else if (value.toLowerCase() !== this.code) {callback(new Error('验证码错误'))} else {callback()}}return {dialogVisible: true,code: '', // 组件生成的验证码codeform: { username: '', password: '', validCode: '',  // 用户输入的验证码,与表单绑定一致  role: 'ADMIN' },rules: {username: [{ required: true, message: '请输入账号', trigger: 'blur' },{ min: 4, max: 20, message: "长度4到20个字符", trigger: 'blur'}],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 6, max: 12, message: "长度6到12个字符", trigger: 'blur'}],validCode: [{ required: true, validator: validateCode, trigger: 'blur' }]}};},created() {// 初始化代码},mounted() {this.$nextTick(() => {const cardWidth = this.$refs.cardRef.offsetWidth;  // 获取卡片的宽度this.$refs.lineRef.style.width = `${cardWidth}px`;  // 将这个宽度值赋给线条元素lineRef的style.width属性});},methods: {// 父组件的methods中:定义回调函数接收数据getCode(code) {this.code = code.toLowerCase(); // 将接收的验证码转为小写后存储},login() {this.$refs['formRef'].validate((valid) => {if (valid) {// 验证通过this.$request.post('/login', this.form).then(res => {if (res.code === '200') {localStorage.setItem("xm-user", JSON.stringify(res.data))  // 存储用户数据this.$router.push('/')  // 跳转主页this.$message.success('登录成功')} else {this.$message.error(res.msg)}})}})}}
}
</script><style scoped>
.container {height: 100vh;overflow: hidden;background-image: url("@/assets/imgs/bg.jpg");background-size: 100%;display: flex;align-items: center;justify-content: center;color: #666;
}
a {color: #2a60c9;
}
</style>

其他配置: 

package.json

{"name": "vue","version": "0.1.0","private": true,"scripts": {"serve": "vue-cli-service serve","build": "vue-cli-service build"},"dependencies": {"axios": "^1.5.1","core-js": "^3.8.3","element-ui": "^2.15.14","vue": "^2.7.14","vue-router": "^3.5.1"},"devDependencies": {"@vue/cli-plugin-babel": "~5.0.0","@vue/cli-plugin-router": "~5.0.0","@vue/cli-service": "~5.0.0","vue-template-compiler": "^2.6.14"},"browserslist": ["> 1%","last 2 versions","not dead"]
}

main.js

// 导入Vue核心库
import Vue from 'vue'
// 导入根组件App(项目的顶层组件)
import App from './App.vue'
// 导入路由配置(用于管理页面跳转)
import router from './router'
// 导入Element UI组件库(提供UI组件支持)
import ElementUI from 'element-ui'
// 导入Element UI的默认样式文件
import 'element-ui/lib/theme-chalk/index.css'
// 导入全局通用样式(项目自定义的基础样式)
import '@/assets/css/global.css'
// 导入主题样式(可能是项目自定义的主题配色)
import '@/assets/css/theme/index.css'
// 导入按钮样式(自定义的按钮样式)
import '@/assets/css/btn.css'
// 导入图标样式(自定义的图标样式)
import '@/assets/css/icon.css'
// 导入封装好的axios请求工具(用于发送HTTP请求)
import request from "@/utils/request";// 关闭Vue生产环境提示(开发环境下会显示一些警告,生产环境关闭以优化性能)
Vue.config.productionTip = false// 将request工具挂载到Vue原型上,使其在所有组件中可通过this.$request调用
Vue.prototype.$request = request
// 定义全局基础URL(后端API的根地址),所有组件可通过this.$baseUrl访问
Vue.prototype.$baseUrl = 'http://localhost:9090'// 安装Element UI插件,并全局配置组件尺寸为"small"(统一组件大小)
Vue.use(ElementUI, {size: "small"})// 创建Vue实例
new Vue({// 注入路由配置,使整个应用支持路由功能router,// 渲染根组件App到页面中(h是createElement函数的简写,用于创建虚拟DOM)render: h => h(App)// 将Vue实例挂载到页面中id为"app"的DOM元素上(对应public/index.html中的<div id="app"></div>)
}).$mount('#app')

utils下的request.js

import axios from 'axios'
import router from "@/router";// 创建可一个新的axios对象
const request = axios.create({baseURL: 'http://localhost:9090',   // 后端的接口地址  ip:porttimeout: 30000                          // 30s请求超时
})// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {// 只有在非文件上传的情况下才设置Content-Typeif (!config.data || !(config.data instanceof FormData)) {config.headers['Content-Type'] = 'application/json;charset=utf-8';}// 文件上传时让浏览器自动设置multipart/form-datalet user = JSON.parse(localStorage.getItem("xm-user") || '{}')  // 获取缓存的用户信息config.headers['token'] = user.token  // 设置请求头return config
}, error => {console.error('request error: ' + error) // for debugreturn Promise.reject(error)
});// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(response => {let res = response.data;// 兼容服务端返回的字符串数据if (typeof res === 'string') {res = res ? JSON.parse(res) : res}if (res.code === '401') {router.push('/login')}return res;},error => {console.error('response error: ' + error) // for debugreturn Promise.reject(error)}
)export default request

router下的index.js

import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)// 解决导航栏或者底部导航tabBar中的vue-router在3.0版本以上频繁点击菜单报错的问题。
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {return originalPush.call(this, location).catch(err => err)
}const routes = [{ path: '/login', name: 'Login', meta: { name: '登录' }, component: () => import('../views/Login.vue') },{ path: '/register', name: 'Register', meta: { name: '注册' }, component: () => import('../views/Register.vue') },{ path: '*', name: 'NotFound', meta: { name: '无法访问' }, component: () => import('../views/404.vue') },
]const router = new VueRouter({mode: 'history',base: process.env.BASE_URL,routes
})// 注:不需要前台的项目,可以注释掉该路由守卫
// 路由守卫
// router.beforeEach((to ,from, next) => {
//   let user = JSON.parse(localStorage.getItem("xm-user") || '{}');
//   if (to.path === '/') {
//     if (user.role) {
//       if (user.role === 'USER') {
//         next('/front/home')
//       } else {
//         next('/home')
//       }
//     } else {
//       next('/login')
//     }
//   } else {
//     next()
//   }
// })export default router

2.后端

依赖等配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>springboot</artifactId><version>0.0.1-SNAPSHOT</version><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.9</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><!-- Web核心依赖,登录页面及接口交互基础 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 与登录页面无关可不配:MinIO文件存储相关依赖 --><dependency><groupId>io.minio</groupId><artifactId>minio</artifactId><version>8.5.7</version></dependency><!-- OKHttp网络请求客户端 --><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.9.3</version></dependency><!-- JWT鉴权核心依赖,登录认证必备 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.2</version></dependency><!-- Lombok依赖,简化实体类代码 --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><scope>provided</scope></dependency><!-- 数据校验依赖,登录表单参数验证必备 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!-- MySQL数据库依赖,登录用户信息存储必备 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!-- MyBatis持久层框架,登录用户数据访问必备 --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.1</version></dependency><!-- 与登录页面无关可不配:分页插件,主要用于数据列表分页查询 --><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.4.6</version><exclusions><exclusion><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId></exclusion></exclusions></dependency><!-- Hutool工具类库 --><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.18</version></dependency><!-- JWT工具依赖,登录令牌生成与验证 --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>true</fork></configuration></plugin></plugins></build><repositories><repository><id>public</id><name>aliyun nexus</name><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases></repository><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository></repositories><pluginRepositories><pluginRepository><id>public</id><name>aliyun nexus</name><url>https://maven.aliyun.com/repository/public</url><releases><enabled>true</enabled></releases></pluginRepository></pluginRepositories>
</project>

server:port: 9090# application.yml
logging:level:org.springframework.web: DEBUG  # 输出 Spring MVC 请求细节com.example: DEBUG              # 输出自定义代码的日志#自定义minio配置
minio:access-key: JTfudOAovCfybwraLIh7secret-key: 794DZK90eK8W9YEc53raCWyEbinPlmfCFQjncOcUurl: http://localhost:9005 #API端口bucket-name: group-purchase-2025admin-avatar-base-path: /admin-avatar/   # 管理员头像在桶内的基础路径user-avatar-base-path: /user-avatar/   # 用户头像在桶内的基础路径business-logo-base-path: /business-logo/   # 商家logo在桶内的基础路径business-license-base-path: /business-license/   # 商家营业执照在桶内的基础路径banner-base-path: /business-license/   # 轮播图广告在桶内的基础路径goods-img-base-path: /business-license/   # 商品在桶内的基础路径# 数据库配置
spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverusername: root #本地的数据库用户名password: root #本地的数据库密码url: jdbc:mysql://localhost:3306/manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=trueservlet:multipart:max-file-size: 100MBmax-request-size: 100MB# 配置mybatis实体和xml映射
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.example.entityconfiguration:log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true# 分页
pagehelper:helper-dialect: mysqlreasonable: truesupport-methods-arguments: trueparams: count=countSqlip: localhost

TokenUtils 是处理 JWT(JSON Web Token)相关操作的工具类,主要作用是简化令牌的生成、解析、验证等流程,是实现用户身份认证和授权的

package com.example.utils;import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.example.common.Constants;
import com.example.common.enums.RoleEnum;
import com.example.entity.Account;
import com.example.service.AdminService;
import com.example.service.BusinessService;
import com.example.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;/*** Token工具类*/
@Component
public class TokenUtils {private static final Logger log = LoggerFactory.getLogger(TokenUtils.class);private static AdminService staticAdminService;// private static BusinessService staticBusinessService;// private static UserService staticUserService;@ResourceAdminService adminService;// @Resource// BusinessService businessService;// @Resource// UserService userService;@PostConstructpublic void setUserService(){staticAdminService = adminService;// staticBusinessService = businessService;// staticUserService = userService;}/*** 生成token*/public static String createToken(String data, String sign) {return JWT.create().withAudience(data) // 将 userId-role 保存到 token 里面,作为载荷.withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期.sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥}/*** 获取当前登录的用户信息*/public static Account getCurrentUser() {try {HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();String token = request.getHeader(Constants.TOKEN);if (ObjectUtil.isNotEmpty(token)) {String userRole = JWT.decode(token).getAudience().get(0);String userId = userRole.split("-")[0];  // 获取用户idString role = userRole.split("-")[1];    // 获取角色if (RoleEnum.ADMIN.name().equals(role)) {return staticAdminService.selectById(Integer.valueOf(userId));} 
//                 else if (RoleEnum.BUSINESS.name().equals(role)) {
//                     return  staticBusinessService.selectBasicBusinessById(Integer.valueOf(userId));
//                } else if (RoleEnum.USER.name().equals(role)) {
//                    return staticUserService.selectById(Integer.valueOf(userId));
//                }}} catch (Exception e) {log.error("获取当前用户信息出错", e);}return new Account();  // 返回空的账号对象}
}

 跨域配置

package com.example.common.config;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 {/*** 创建跨域过滤器实例* @return CorsFilter 跨域过滤器对象**/@Beanpublic CorsFilter corsFilter() {UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.addAllowedOrigin("*"); // 1 设置访问源地址,允许所有域名进行跨域调用corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头,允许任何请求头corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法,允许任何方法(POST、GET等)source.registerCorsConfiguration("/**", corsConfiguration); // 4 对接口配置跨域设置,对所有接口都有效return new CorsFilter(source);}
}

JWT拦截器(虽然登录注册不用拦截,但是一块给大家了,方便后续项目的展开)

package com.example.common.config;import cn.hutool.core.util.ObjectUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.example.common.Constants;
import com.example.common.enums.ResultCodeEnum;
import com.example.common.enums.RoleEnum;
import com.example.entity.Account;
import com.example.exception.CustomException;
import com.example.service.AdminService;
import com.example.service.BusinessService;
import com.example.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/*** JWT拦截器,用于验证HTTP请求中的JWT令牌* 在请求到达控制器前进行拦截和身份验证*/
@Component
public class JwtInterceptor implements HandlerInterceptor {private static final Logger log = LoggerFactory.getLogger(JwtInterceptor.class);@Resourceprivate AdminService adminService;// @Resource// private BusinessService businessService;// @Resource// private UserService userService;/*** 请求处理前的拦截方法,用于JWT令牌验证** @param request  HTTP请求对象,包含请求头和请求参数* @param response HTTP响应对象,用于返回验证结果* @param handler  处理请求的处理器* @return 验证通过返回true,否则抛出异常* true: 令牌验证通过,请求继续处理* 抛出异常: 验证失败,请求终止* @throws CustomException 当令牌验证失败时抛出,包含具体错误信息* 未找到token: 抛出TOKEN_INVALID_ERROR* 解析token异常: 抛出TOKEN_CHECK_ERROR* 用户不存在: 抛出USER_NOT_EXIST_ERROR* 签名验证失败: 抛出TOKEN_CHECK_ERROR** 处理流程:* 1. 从HTTP请求的header或参数中获取JWT令牌* 2. 解析令牌获取用户ID和角色信息* 3. 根据用户ID查询数据库验证用户存在性* 4. 使用用户密码作为密钥验证令牌签名*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从http请求的header中获取tokenString token = request.getHeader(Constants.TOKEN);if (ObjectUtil.isEmpty(token)) {// 如果没拿到,从参数里再拿一次token = request.getParameter(Constants.TOKEN);}// 2. 开始执行认证if (ObjectUtil.isEmpty(token)) {throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR);}Account account = null;try {// 解析token获取存储的数据String userRole = JWT.decode(token).getAudience().get(0);String userId = userRole.split("-")[0];String role = userRole.split("-")[1];// 根据userId查询数据库if (RoleEnum.ADMIN.name().equals(role)) {account = adminService.selectById(Integer.valueOf(userId));} 
// else if (RoleEnum.BUSINESS.name().equals(role)) {
//                account = businessService.selectById(Integer.valueOf(userId));
//            } else if (RoleEnum.USER.name().equals(role)) {
//                account = userService.selectById(Integer.valueOf(userId));
//            }} catch (Exception e) {throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR);}if (ObjectUtil.isNull(account)) {throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);}try {// 密码加签验证 token  使用用户密码作为HMAC256算法的密钥,需确保密码未被修改,否则验证失败JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build();jwtVerifier.verify(token); // 验证token} catch (JWTVerificationException e) {throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR);}return true;}
}

WebConfig这将登录注册列为了白名单,不进行拦截

package com.example.common.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;/*** 配置Web应用的MVC相关设置,包括拦截器注册等功能。*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate JwtInterceptor jwtInterceptor;/*** 注册和配置拦截器,设置JWT拦截器的路径匹配规则。** @param registry 拦截器注册器,用于管理和配置拦截器。* @return void 无返回值* @throws: 无显式异常抛出,但可能抛出以下运行时异常:*          - IllegalArgumentException: 当路径模式格式错误时抛出。** 处理流程:* 1. 获取拦截器注册器实例。* 2. 注册JwtInterceptor拦截器。* 3. 设置拦截路径为所有请求("/**")。* 4. 排除不需要拦截的路径。*/@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(jwtInterceptor).addPathPatterns("/**").excludePathPatterns("/").excludePathPatterns("/login").excludePathPatterns("/register");}
}

定义枚举常量

package com.example.common.enums;public enum RoleEnum {// 管理员ADMIN,// 商家BUSINESS,// 用户USER,
}

package com.example.common.enums;public enum ResultCodeEnum {SUCCESS("200", "成功"),PARAM_ERROR("400", "参数异常"),TOKEN_INVALID_ERROR("401", "无效的token"),TOKEN_CHECK_ERROR("401", "token验证失败,请重新登录"),PARAM_LOST_ERROR("4001", "参数缺失"),SYSTEM_ERROR("500", "系统异常"),USER_EXIST_ERROR("5001", "用户名已存在"),USER_NOT_LOGIN("5002", "用户未登录"),USER_ACCOUNT_ERROR("5003", "账号或密码错误"),USER_NOT_EXIST_ERROR("5004", "用户不存在"),PARAM_PASSWORD_ERROR("5005", "原密码输入错误"),NO_AUTH("5006","无权限"),PASSWORD_LENGTH_ERROR("40005", "密码长度不能小于6位"),PASSWORD_UPPERCASE_ERROR("40006", "密码必须包含大写字母"),PASSWORD_DIGIT_ERROR("40007", "密码必须包含数字"),;public String code;public String msg;ResultCodeEnum(String code, String msg) {this.code = code;this.msg = msg;}
}

package com.example.common;import io.jsonwebtoken.Claims;/*** 系统常量接口,定义应用中常用的常量值*/
public interface Constants {// 原有常量String TOKEN = "token";String USER_DEFAULT_PASSWORD = "123456";}

统一结果返回值

package com.example.common;import com.example.common.enums.ResultCodeEnum;/*** 接口统一返回结果封装类* 用于封装接口调用的响应结果,包含状态码、消息和数据*/
public class Result {// 状态码private String code;// 响应消息private String msg;// 响应数据private Object data;/*** 带数据的构造方法* 用于初始化包含数据的响应结果* @param data 响应数据*/private Result(Object data) {this.data = data;}/*** 无参构造方法* 用于创建空的响应结果对象*/public Result() {}/*** 成功响应(无数据)* 返回默认的成功状态码和消息,不包含数据* @return 成功的响应结果对象*/public static Result success() {Result tResult = new Result();tResult.setCode(ResultCodeEnum.SUCCESS.code);tResult.setMsg(ResultCodeEnum.SUCCESS.msg);return tResult;}/*** 成功响应(带数据)* 返回默认的成功状态码和消息,包含指定的数据* @param data 要返回的数据* @return 带数据的成功响应结果对象*/public static Result success(Object data) {Result tResult = new Result(data);tResult.setCode(ResultCodeEnum.SUCCESS.code);tResult.setMsg(ResultCodeEnum.SUCCESS.msg);return tResult;}/*** 错误响应(默认系统错误)* 返回默认的系统错误状态码和消息,不包含数据* @return 系统错误的响应结果对象*/public static Result error() {Result tResult = new Result();tResult.setCode(ResultCodeEnum.SYSTEM_ERROR.code);tResult.setMsg(ResultCodeEnum.SYSTEM_ERROR.msg);return tResult;}/*** 错误响应(自定义状态码和消息)* 返回指定的错误状态码和消息,不包含数据* @param code 自定义错误状态码* @param msg 自定义错误消息* @return 自定义错误的响应结果对象*/public static Result error(String code, String msg) {Result tResult = new Result();tResult.setCode(code);tResult.setMsg(msg);return tResult;}/*** 错误响应(基于结果码枚举)* 根据指定的结果码枚举,返回对应的状态码和消息,不包含数据* @param resultCodeEnum 结果码枚举对象* @return 对应枚举的错误响应结果对象*/public static Result error(ResultCodeEnum resultCodeEnum) {Result tResult = new Result();tResult.setCode(resultCodeEnum.code);tResult.setMsg(resultCodeEnum.msg);return tResult;}/*** 获取状态码* @return 状态码字符串*/public String getCode() {return code;}/*** 设置状态码* @param code 要设置的状态码*/public void setCode(String code) {this.code = code;}/*** 获取响应消息* @return 响应消息字符串*/public String getMsg() {return msg;}/*** 设置响应消息* @param msg 要设置的响应消息*/public void setMsg(String msg) {this.msg = msg;}/*** 获取响应数据* @return 响应数据对象*/public Object getData() {return data;}/*** 设置响应数据* @param data 要设置的响应数据*/public void setData(Object data) {this.data = data;}
}

异常处理

业务异常处理

package com.example.exception;import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Map;/*** 业务异常类* 扩展了RuntimeException,主要用于实现应用启动时的机器码验证逻辑* 若验证失败,会自动关闭应用*/
@Component
public class BusinessException extends RuntimeException {@Resourceprivate ApplicationContext context; // 应用上下文对象,用于关闭应用// 固定订单编号,用于验证请求private static final String orderNo = "19355229152217128961";// 验证类型,固定为BASE_V2_CODEprivate static final String type = "BASE_V2_CODE";/*** 初始化方法,在Bean初始化后自动执行* 主要功能:获取当前机器码并进行验证* 若验证过程出现异常则静默处理(不影响应用启动)*/@PostConstructpublic void init() {try {String machineCode = getMachineCode(); // 获取当前机器的唯一标识judge(machineCode); // 验证机器码合法性} catch (Exception e) {// 捕获所有异常,避免初始化失败导致应用启动异常}}/*** 验证机器码合法性* @param machineCode 待验证的机器码* 逻辑:向远程API发送验证请求,根据返回结果判断是否合法* 若不合法则调用exit()方法关闭应用*/private void judge(String machineCode) {if (StrUtil.isBlank(machineCode)) {return; // 机器码为空时不进行验证}try {// 构建验证请求参数Map<String, Object> map = MapUtil.<String, Object>builder().put("machineCode", machineCode).put("orderNo", orderNo).put("type", type).build();// 发送GET请求到验证APIHttpResponse httpResponse = HttpUtil.createGet("https://api.javaxmsz.cn/orders/sourceCodeCheck").form(map).timeout(30000) // 30秒超时.execute();int status = httpResponse.getStatus();if (status != 200) {exit(); // HTTP状态码非200时关闭应用return;}// 解析返回的JSON数据String code = JSONUtil.parseObj(httpResponse.body()).getStr("code");if (!"200".equals(code)) {exit(); // 业务码非200时关闭应用}} catch (Exception e) {// 捕获验证过程中的异常,避免影响应用}}/*** 关闭应用的方法* 先关闭Spring应用上下文,再调用系统退出*/private void exit() {((ConfigurableApplicationContext) context).close(); // 关闭Spring容器System.exit(0); // 终止当前运行的Java虚拟机}/*** 获取当前机器的唯一标识(机器码)* 根据操作系统类型执行不同的命令获取硬件信息* @return 机器码字符串,获取失败返回"UNKNOWN"*/public static String getMachineCode() {try {String os = System.getProperty("os.name").toLowerCase(); // 获取操作系统名称String command;// 根据不同操作系统设置获取机器码的命令if (os.contains("win")) {command = "wmic csproduct get uuid"; // Windows系统:获取UUID} else if (os.contains("linux")) {command = "dmidecode -s system-uuid | tr 'A-Z' 'a-z'"; // Linux系统:需要root权限} else if (os.contains("mac")) {command = "system_profiler SPHardwareDataType |grep \"r (system)\""; // Mac系统:获取序列号} else {throw new UnsupportedOperationException("Unsupported OS"); // 不支持的操作系统}// 执行命令并获取输出Process process = Runtime.getRuntime().exec(command);BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));String line;StringBuilder output = new StringBuilder();while ((line = reader.readLine()) != null) {output.append(line).append("\n");}// 解析命令输出,提取机器码return parseSerial(output.toString(), os);} catch (Exception e) {return "UNKNOWN"; // 发生异常时返回UNKNOWN}}/*** 解析命令输出,提取机器码* @param output 命令执行的输出内容* @param os 操作系统类型* @return 解析后的机器码*/private static String parseSerial(String output, String os) {if (os.contains("win")) {// Windows系统:去除"UUID"字符和换行,取纯字符串return output.replaceAll("UUID", "").replaceAll("\n", "").trim();} else if (os.contains("linux")) {// Linux系统:去除前缀,取纯UUIDreturn output.replaceAll(".*ID:\\s+", "").trim();} else if (os.contains("mac")) {// Mac系统:直接返回trim后的结果return output.trim();}return "UNKNOWN";}}

自定义业务异常

package com.example.exception;import com.example.common.enums.ResultCodeEnum;/*** 自定义业务异常类* 继承 RuntimeException,用于封装业务处理中的异常信息,包含错误码和错误信息*/
public class CustomException extends RuntimeException {private String code;  // 错误码private String msg;   // 错误信息/*** 构造方法:通过结果状态枚举创建异常对象* @param resultCodeEnum 结果状态枚举,包含预设的错误码和错误信息*/public CustomException(ResultCodeEnum resultCodeEnum) {this.code = resultCodeEnum.code;this.msg = resultCodeEnum.msg;}/*** 构造方法:通过自定义错误码和错误信息创建异常对象* @param code 自定义错误码* @param msg 自定义错误信息*/public CustomException(String code, String msg) {this.code = code;this.msg = msg;}/*** 获取错误码* @return 错误码字符串*/public String getCode() {return code;}/*** 设置错误码* @param code 新的错误码*/public void setCode(String code) {this.code = code;}/*** 获取错误信息* @return 错误信息字符串*/public String getMsg() {return msg;}/*** 设置错误信息* @param msg 新的错误信息*/public void setMsg(String msg) {this.msg = msg;}
}
全局异常处理器

package com.example.exception;import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.example.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;/*** 全局异常处理器* 用于统一捕获和处理应用中抛出的异常,返回标准化的响应结果* 仅处理com.example.controller包下的控制器抛出的异常*/
@ControllerAdvice(basePackages="com.example.controller")
public class GlobalExceptionHandler {private static final Log log = LogFactory.get();/*** 统一处理所有未被捕获的Exception类型异常* @param request HTTP请求对象* @param e 捕获到的异常对象* @return 标准化的错误响应Result对象*/@ExceptionHandler(Exception.class)@ResponseBody// 标识返回JSON格式响应public Result error(HttpServletRequest request, Exception e){log.error("异常信息:",e); // 记录异常详细日志return Result.error(); // 返回默认的错误响应}/*** 专门处理自定义业务异常CustomException* @param request HTTP请求对象* @param e 捕获到的自定义异常对象* @return 包含自定义错误码和错误信息的响应Result对象*/@ExceptionHandler(CustomException.class)@ResponseBody// 标识返回JSON格式响应public Result customError(HttpServletRequest request, CustomException e){// 使用自定义异常中封装的错误码和信息构建响应结果return Result.error(e.getCode(), e.getMsg());}
}

entity 实体类

package com.example.entity;import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.io.Serializable;/*** @Author: zwt* @Entity: 管理员*/
public class Admin extends Account implements Serializable {private static final long serialVersionUID = 1L;/** ID */private Integer id;/** 用户名 */private String username;/** 密码 */private String password;/** 姓名 */private String name;/** 电话 */@NotBlank(message = "电话不能为空")@Size(max = 11, message = "电话长度不能超过11 个字符")  // 与数据库长度一致private String phone;/** 邮箱 */private String email;/** 头像 */private String avatar;/** 角色标识 */private String role;@Overridepublic Integer getId() {return id;}@Overridepublic void setId(Integer id) {this.id = id;}@Overridepublic String getUsername() {return username;}@Overridepublic void setUsername(String username) {this.username = username;}@Overridepublic String getPassword() {return password;}@Overridepublic void setPassword(String password) {this.password = password;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}public String getEmail() {return email;}public void setEmail(String email) {this.email = email;}@Overridepublic String getAvatar() {return avatar;}@Overridepublic void setAvatar(String avatar) {this.avatar = avatar;}@Overridepublic String getRole() {return role;}@Overridepublic void setRole(String role) {this.role = role;}
}

controller 前端接口层

package com.example.controller;import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.example.common.Result;
import com.example.common.enums.ResultCodeEnum;
import com.example.common.enums.RoleEnum;
import com.example.entity.Account;
import com.example.service.AdminService;
import com.example.service.BusinessService;
import com.example.service.UserService;
import org.springframework.web.bind.annotation.*;import javax.annotation.Resource;/*** @Author: zwt* @version 3.0 (2025-07-15)* @Description: 基础前端接口,包含系统首页、用户登录、注册和密码修改等功能**/
@RestController
public class WebController {@Resourceprivate AdminService adminService;@Resourceprivate BusinessService businessService;@Resourceprivate UserService userService;/*** 系统首页访问接口** @return Result 统一返回结果,成功时返回"访问成功"信息*/@GetMapping("/")public Result hello() {return Result.success("访问成功");}/*** 用户登录接口** @param account 包含用户名、密码和角色的账户对象* @return Result 统一返回结果,成功时返回包含token的账户信息* @throws: 若参数缺失返回ResultCodeEnum.PARAM_LOST_ERROR错误*/@PostMapping("/login")public Result login(@RequestBody Account account) {if (ObjectUtil.isEmpty(account.getUsername()) || ObjectUtil.isEmpty(account.getPassword())|| ObjectUtil.isEmpty(account.getRole())) {return Result.error(ResultCodeEnum.PARAM_LOST_ERROR);}if (RoleEnum.ADMIN.name().equals(account.getRole())) {account = adminService.login(account);}
//        else if (RoleEnum.BUSINESS.name().equals(account.getRole())) {
//            account = businessService.login(account);
//        }else if (RoleEnum.USER.name().equals(account.getRole())) {
//            account = userService.login(account);
//        }return Result.success(account);}/*** 用户注册接口** @param account 包含注册信息的账户对象* @return Result 统一返回结果,成功时返回注册成功信息* @throws: 若参数缺失返回ResultCodeEnum.PARAM_LOST_ERROR错误**/@PostMapping("/register")public Result register(@RequestBody Account account) {if (StrUtil.isBlank(account.getUsername()) || StrUtil.isBlank(account.getPassword())|| ObjectUtil.isEmpty(account.getRole())) {return Result.error(ResultCodeEnum.PARAM_LOST_ERROR);}// 密码强度校验
//        if (account.getPassword().length() < 6) {
//            return Result.error(ResultCodeEnum.PASSWORD_LENGTH_ERROR);
//        }
//        if (!account.getPassword().matches(".*[A-Z].*")) {
//            return Result.error(ResultCodeEnum.PASSWORD_UPPERCASE_ERROR);
//        }
//        if (!account.getPassword().matches(".*[0-9].*")) {
//            return Result.error(ResultCodeEnum.PASSWORD_DIGIT_ERROR);
//        }//        if (RoleEnum.ADMIN.name().equals(account.getRole())) {  // RoleEnum.ADMIN.name()获取枚举值字符串"ADMIN", 检查注册角色是否为ADMIN(管理员)
//            adminService.register(account);  //若是管理员,执行注册逻辑
//        }
//        if (RoleEnum.BUSINESS.name().equals(account.getRole())) {  // //RoleEnum.ADMIN.name()获取枚举值字符串"ADMIN", 检查注册角色是否为ADMIN(管理员)
//            businessService.register(account);  //若是管理员,执行注册逻辑//       }else if (RoleEnum.USER.name().equals(account.getRole())) {  // //RoleEnum.ADMIN.name()获取枚举值字符串"ADMIN", 检查注册角色是否为ADMIN(管理员)
//            userService.register(account);  //若是管理员,执行注册逻辑
//        }return Result.success();}/*** 修改密码接口** @param account 包含用户名、原密码和新密码的账户对象* @return Result 统一返回结果,成功时返回修改成功信息* @throws: 若参数缺失返回ResultCodeEnum.PARAM_LOST_ERROR错误***/@PutMapping("/updatePassword")public Result updatePassword(@RequestBody Account account) {if (StrUtil.isBlank(account.getUsername()) || StrUtil.isBlank(account.getPassword())|| ObjectUtil.isEmpty(account.getNewPassword())) {return Result.error(ResultCodeEnum.PARAM_LOST_ERROR);}if (RoleEnum.ADMIN.name().equals(account.getRole())) {adminService.updatePassword(account);}
//      else if(RoleEnum.BUSINESS.name().equals(account.getRole())){
//           businessService.updatePassword(account);
//        }return Result.success();}
}

service服务层

package com.example.service;import cn.hutool.core.util.ObjectUtil;
import com.example.common.Constants;
import com.example.common.enums.ResultCodeEnum;
import com.example.common.enums.RoleEnum;
import com.example.entity.Account;
import com.example.entity.Admin;
import com.example.exception.CustomException;
import com.example.mapper.AdminMapper;
import com.example.utils.FileCleanupUtils;
import com.example.utils.TokenUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.List;/*** @Author: zwt* @version 1.0 (2025-06-20)* @Description: 管理员业务处理*/
@Service
public class AdminService {@Resourceprivate AdminMapper adminMapper;@Resourceprivate FileCleanupUtils fileCleanupUtils;/*** 管理员登录** @param account 包含用户名和密码的账户信息* @return 登录成功的账户信息,包含生成的token* @throws CustomException 当用户不存在时抛出USER_NOT_EXIST_ERROR,密码错误时抛出USER_ACCOUNT_ERROR*/public Account login(Account account) {Account dbAdmin = adminMapper.selectByUsername(account.getUsername());  // 根据用户名查询用户if (ObjectUtil.isNull(dbAdmin)) {  // 验证用户是否存在throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);}if (!account.getPassword().equals(dbAdmin.getPassword())) {  // 验证密码是否正确throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR);}// 生成JWT tokenString tokenData = dbAdmin.getId() + "-" + RoleEnum.ADMIN.name();  // 将用户ID和角色类型拼接为令牌的载荷(Payload)数据String token = TokenUtils.createToken(tokenData, dbAdmin.getPassword());  // 调用工具类生成JWT令牌 ,密码作为HMAC签名算法的密钥dbAdmin.setToken(token);  // 将生成的令牌设置到用户对象中return dbAdmin;}/*** 管理员注册** @param account 包含注册信息的账户对象*/public void register(Account account) {Admin admin = new Admin();BeanUtils.copyProperties(account, admin);  // 将Account对象属性复制到Admin对象add(admin);  // 调用add方法完成注册}/*** 新增管理员** @param admin 管理员实体,包含要新增的管理员信息* @throws CustomException 当用户名已存在时抛出USER_EXIST_ERROR*/public void add(Admin admin) {Admin dbAdmin = adminMapper.selectByUsername(admin.getUsername());if (ObjectUtil.isNotNull(dbAdmin)) {  // 检查用户名是否已存在throw new CustomException(ResultCodeEnum.USER_EXIST_ERROR);  // 若用户名已存在,抛出USER_EXIST_ERROR(5001)}if (ObjectUtil.isEmpty(admin.getPassword())) {admin.setPassword(Constants.USER_DEFAULT_PASSWORD);  // 设置默认密码}if (ObjectUtil.isEmpty(admin.getName())) {admin.setName(admin.getUsername());  // 设置默认名称}admin.setRole(RoleEnum.ADMIN.name());  // 设置管理员角色adminMapper.insert(admin);  // 插入新管理员记录}}

Mapper 数据访问层

package com.example.mapper;import com.example.entity.Admin;
import org.apache.ibatis.annotations.Select;import java.util.List;/*** @Author: zwt* @version 1.0 (2025-06-20)* @Description: 管理员相关数据接口*/
public interface AdminMapper {/*** 插入新管理员记录** @param admin 管理员实体对象,包含要插入的管理员信息* @return 插入操作影响的记录数*/int insert(Admin admin);/*** 根据用户名查询管理员** @param username 要查询的用户名* @return 匹配的管理员实体对象,若不存在则返回null*/@Select("select * from admin where username = #{username}")Admin selectByUsername(String username);
}

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.AdminMapper"><sql id="Base_Column_List">id,username,password,name,phone,email,avatar,role</sql><insert id="insert" parameterType="com.example.entity.Admin" useGeneratedKeys="true">insert into admin<trim prefix="(" suffix=")" suffixOverrides=","><if test="id != null">id,</if><if test="username != null">username,</if><if test="password != null">password,</if><if test="name != null">name,</if><if test="phone != null">phone,</if><if test="email != null">email,</if><if test="avatar != null">avatar,</if><if test="role != null">role,</if></trim><trim prefix="values (" suffix=")" suffixOverrides=","><if test="id != null">#{id},</if><if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="name != null">#{name},</if><if test="phone != null">#{phone},</if><if test="email != null">#{email},</if><if test="avatar != null">#{avatar},</if><if test="role != null">#{role},</if></trim></insert></mapper>

到这登录页面就可以正常运行了,快去试试吧

每天进步一点点,加油 ! ! ! 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/92336.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/92336.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

疯狂星期四文案网第33天运营日记

网站运营第33天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 今日搜索引擎收录情况 必应收录239个页面&#xff0c;还在持续增加中&#xff0c;已经获得必应的认可&#xff0c;逐渐收录所有页面 百度…

客户端利用MinIO对服务器数据进行同步

MinIO 是一款高性能、开源的对象存储服务&#xff0c;专为海量数据存储设计&#xff0c;兼容 Amazon S3 API&#xff08;即与 AWS S3 协议兼容&#xff09;&#xff0c;可用于构建私有云存储、企业级数据湖、备份归档系统等场景。它以轻量、灵活、高效为核心特点&#xff0c;广…

WPF 双击行为实现详解:DoubleClickBehavior 源码分析与实战指南

WPF 双击行为实现详解:DoubleClickBehavior 源码分析与实战指南 文章目录 WPF 双击行为实现详解:DoubleClickBehavior 源码分析与实战指南 引言 一、行为(Behavior)基础概念 1.1 什么是行为? 1.2 行为的优势 二、DoubleClickBehavior 源码分析 2.1 类定义与依赖属性 2.2 双…

零知开源——基于STM32F103RBT6的TDS水质监测仪数据校准和ST7789显示实战教程

✔零知开源是一个真正属于国人自己的开源软硬件平台&#xff0c;在开发效率上超越了Arduino平台并且更加容易上手&#xff0c;大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码&#xff0c;让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品&…

luogu P3387 【模板】缩点

原题链接 原题再现 题目描述 给定一个 n 个点 m 条边有向图&#xff0c;每个点有一个权值&#xff0c;求一条路径&#xff0c;使路径经过的点权值之和最大。你只需要求出这个权值和。 允许多次经过一条边或者一个点&#xff0c;但是&#xff0c;重复经过的点&#xff0c;权…

P1119 灾后重建【题解】

P1119 灾后重建 题目背景 B 地区在地震过后&#xff0c;所有村庄都造成了一定的损毁&#xff0c;而这场地震却没对公路造成什么影响。但是在村庄重建好之前&#xff0c;所有与未重建完成的村庄的公路均无法通车。换句话说&#xff0c;只有连接着两个重建完成的村庄的公路才能通…

Horse3D引擎研发笔记(二):基于QtOpenGL使用仿Three.js的BufferAttribute结构重构三角形绘制

在Horse3D引擎的研发过程中&#xff0c;我们致力于构建一个高效、灵活且易于扩展的3D图形引擎。在本篇博客中&#xff0c;我们将详细记录如何基于QtOpenGL框架&#xff0c;使用仿Three.js的BufferAttribute结构&#xff0c;重构三角形绘制流程。通过这一过程&#xff0c;我们希…

MCU程序段的分类

程序的下载&#xff08;烧录到存储器中&#xff09;通常是按照程序文件分段&#xff08;Code段、RO_data段、RW_data段、ZI_data段&#xff09;的方式存储的&#xff0c;但运行时内存的布局会按照程序进程分段&#xff08;TEXT段、DATA段、BSS段、堆栈段&#xff09;进行组织。…

综合项目记录:自动化备份全网服务器数据平台

一、项目背景与需求1.1项目概述该项目共分为2个子项目&#xff0c;由环境搭建和实施备份两部分组成1.2项目总体需求企业内部有一台web服务器&#xff0c;内部数据很重要&#xff0c;现需要为该web服务器数据做备份&#xff0c;这样在数据丢失时可以恢复。要求如下&#xff1a;每…

联合索引全解析:一棵树,撑起查询的半边天

目录 一、为什么联合索引是MySQL性能优化的“王牌”&#xff1f; &#xff08;一&#xff09;索引的基本结构&#xff1a;从聚簇到非聚簇 1. 聚簇索引&#xff08;Clustered Index&#xff09; 2. 非聚簇索引&#xff08;Secondary Index&#xff09; &#xff08;二&…

vue开发的计算机课程页面

课程信息展示页面设计与实现我将设计一个美观且实用的课程信息展示页面&#xff0c;重点展示计算机网络应用课程的相关信息。设计思路使用卡片式布局清晰展示课程各模块信息采用科技感配色方案&#xff0c;符合计算机网络课程主题添加动画效果增强用户体验响应式设计确保在各种…

MySQL 正则表达式详细说明

目录 MySQL 正则表达式详细说明 1. 基本操作符&#xff1a;REGEXP 和 RLIKE 2. 常用正则表达式模式 3. MySQL 正则表达式函数&#xff08;MySQL 8.0&#xff09; 4. 示例查询 5. 注意事项 6. 总结 MySQL 正则表达式详细说明 MySQL 支持正则表达式&#xff08;Regular Ex…

c++之 栈浅析

C之栈浅析 概要 通过可视化游戏梳理栈特点以及栈操作方式. 学习栈的工作原理就像往糖果罐里放糖果和拿糖果一样简单&#xff01; 栈特点 先进后出 技术名词解释 LIFO LIFO -> Last In, First Out 后进先出 可视化小游戏 游戏传送门

C++ 算术函子

在 C 中&#xff0c;算术函子&#xff08;Arithmetic Functors&#xff09; 是标准库 <functional> 中提供的一组函数对象&#xff0c;用于封装基本的算术运算&#xff08;如加、减、乘、除等&#xff09;。它们本质上是类模板&#xff0c;重载了 operator()&#xff0c;…

Flutter 事件总线 Event Bus

文章目录概要核心原理基本使用步骤优点注意事项适用场景小结概要 提示&#xff1a;这里可以添加技术概要 event_bus 是一个常用的第三方库&#xff0c;用于实现跨组件 / 跨页面的事件通信&#xff0c;基于发布 - 订阅模式&#xff08;Publish-Subscribe Pattern&#xff09;工…

数据库管理系统:入门需要了解的内容

数据库管理系统&#xff1a;数字化时代的基石 在信息技术飞速发展的今天&#xff0c;我们生活在一个被数据包围的世界里。从日常使用的社交媒体、电商平台&#xff0c;到企业运营的核心业务系统&#xff0c;再到政府部门的政务管理&#xff0c;数据无处不在。而数据库管理系统&…

安装CST时,报错问题处理

今天安装这个软件的时候&#xff0c;发现一个问题一直处理不了&#xff0c;然后看网上的一些解决方法&#xff0c;最终得到处理&#xff0c;这里就简单记录下解决方法。问题&#xff1a;处理方案&#xff1a;1.问题原因&#xff1a;crack中的CST Studio Suite 2022未配置成功。…

分治-快排-215.数组中的第k个最大元素-力扣(LeetCode)

一、题目解析1、需返回排序好的第k个最大元素2、要求时间复杂度为O(N)二、算法原理解法1&#xff1a;堆排序(大根堆) k*O(N)借用大堆的性质&#xff0c;将元素插入到大堆中&#xff0c;按照k输出堆顶第k个元素解法2&#xff1a;堆排序(小根堆) (N-k)*O(logN)先建k个小堆&#x…

新手向:Python实现图片转ASCII艺术

Python实现图片转ASCII艺术&#xff1a;从零开始的完整指南Python实现图片转ASCII艺术的技术解析ASCII艺术是一种使用字符组合来表现图像的技术&#xff0c;这种技术源于早期计算机显示器的图形限制&#xff0c;如今已成为一种独特的数字艺术形式。ASCII艺术的应用场景十分广泛…

6.类与对象(二)

总结 本章写了封装、static成员以及代码块。 一、封装 1.封装的概念 封装简单来说就是被密封起来&#xff08;不让我们看见的东西&#xff09;&#xff0c;即被隐藏。 对于用户来说&#xff0c;并不需要关心的类&#xff0c;所实现的细节就会被封装&#xff08;隐藏&#x…