目录
一、项目技术栈
登录页面暂时涉及到的技术栈如下:
前端 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中的codeList
this.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: '', // 组件生成的验证码code
form: {
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:port
timeout: 30000 // 30s请求超时
})
// request 拦截器
// 可以自请求发送前对请求做一些处理
// 比如统一加token,对请求参数统一加密
request.interceptors.request.use(config => {
// 只有在非文件上传的情况下才设置Content-Type
if (!config.data || !(config.data instanceof FormData)) {
config.headers['Content-Type'] = 'application/json;charset=utf-8';
}
// 文件上传时让浏览器自动设置multipart/form-data
let user = JSON.parse(localStorage.getItem("xm-user") || '{}') // 获取缓存的用户信息
config.headers['token'] = user.token // 设置请求头
return config
}, error => {
console.error('request error: ' + error) // for debug
return 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 debug
return 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: JTfudOAovCfybwraLIh7
secret-key: 794DZK90eK8W9YEc53raCWyEbinPlmfCFQjncOcU
url: http://localhost:9005 #API端口
bucket-name: group-purchase-2025
admin-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.Driver
username: root #本地的数据库用户名
password: root #本地的数据库密码
url: jdbc:mysql://localhost:3306/manager?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8&allowPublicKeyRetrieval=true
servlet:
multipart:
max-file-size: 100MB
max-request-size: 100MB
# 配置mybatis实体和xml映射
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.entity
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
# 分页
pagehelper:
helper-dialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql
ip: 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;
@Resource
AdminService adminService;
// @Resource
// BusinessService businessService;
// @Resource
// UserService userService;
@PostConstruct
public 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]; // 获取用户id
String 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 跨域过滤器对象
*
*/
@Bean
public 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);
@Resource
private 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. 使用用户密码作为密钥验证令牌签名
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 1. 从http请求的header中获取token
String 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 {
@Resource
private JwtInterceptor jwtInterceptor;
/**
* 注册和配置拦截器,设置JWT拦截器的路径匹配规则。
*
* @param registry 拦截器注册器,用于管理和配置拦截器。
* @return void 无返回值
* @throws: 无显式异常抛出,但可能抛出以下运行时异常:
* - IllegalArgumentException: 当路径模式格式错误时抛出。
*
* 处理流程:
* 1. 获取拦截器注册器实例。
* 2. 注册JwtInterceptor拦截器。
* 3. 设置拦截路径为所有请求("/**")。
* 4. 排除不需要拦截的路径。
*/
@Override
public 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 {
@Resource
private ApplicationContext context; // 应用上下文对象,用于关闭应用
// 固定订单编号,用于验证请求
private static final String orderNo = "19355229152217128961";
// 验证类型,固定为BASE_V2_CODE
private static final String type = "BASE_V2_CODE";
/**
* 初始化方法,在Bean初始化后自动执行
* 主要功能:获取当前机器码并进行验证
* 若验证过程出现异常则静默处理(不影响应用启动)
*/
@PostConstruct
public 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请求到验证API
HttpResponse 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系统:去除前缀,取纯UUID
return 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;
@Override
public Integer getId() {
return id;
}
@Override
public void setId(Integer id) {
this.id = id;
}
@Override
public String getUsername() {
return username;
}
@Override
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public String getName() {
return name;
}
@Override
public 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;
}
@Override
public String getAvatar() {
return avatar;
}
@Override
public void setAvatar(String avatar) {
this.avatar = avatar;
}
@Override
public String getRole() {
return role;
}
@Override
public 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 {
@Resource
private AdminService adminService;
@Resource
private BusinessService businessService;
@Resource
private 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 {
@Resource
private AdminMapper adminMapper;
@Resource
private 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 token
String 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 mapper
PUBLIC "-//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>
到这登录页面就可以正常运行了,快去试试吧
每天进步一点点,加油 ! ! !