11,Springboot3+vue3个人中心,修改密码

src/views/Manager.vue

复制代码
@click="reset"


<template>
	<div>
		<!-- 头部区域开始 -->
		<div style="height: 60px;display: flex;">
			<div style="width: 240px;display: flex;align-items: center;padding-left: 20px;background-color: #3a456b;">
				<img style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo.png" alt="">
				<span style="font-size: 20px;font-weight: bold;color: #f1f1f1;margin-left: 5px;">隆迟民宿管理系统</span>
			</div>
			<div style="flex: 1;display: flex;align-items: center;padding-left: 20px;border-bottom: 1px solid #ddd;">
				<span style="margin-right: 5px; cursor: pointer;" @click="router.push('/manager/home')">首页</span>/<span style="margin-left: 5px;">{{ router.currentRoute.value.meta.title || '首页' }}</span>
			</div>
			<div style="width: fit-content;padding-right: 20px;display: flex;align-items: center;border-bottom: 1px solid #ddd;">
				<el-dropdown>
					<div style="display: flex;align-items: center;">
						<img v-if="data.user?.avatar" style="width: 40px;height: 40px;border-radius: 50%;" :src="data.user?.avatar" />
						<img v-else style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo1.png" alt="">
						<span style="margin-left: 10px;color: #333;">{{ data.user?.name }}</span>
					</div>
					<template #dropdown>
						<el-dropdown-menu>
							<el-dropdown-item @click="router.push('/manager/person')">个人信息</el-dropdown-item>
							<el-dropdown-item>修改密码</el-dropdown-item>
							<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
						</el-dropdown-menu>
					</template>
				</el-dropdown>
			</div>
		</div>
		<!-- 头部区域结束 -->
		<!--下方区域开始-->
		<div style="display: flex">
			<!-- 菜单区域开始 -->
			<div style="width: 240px;">
				<el-menu router :default-openeds="['1']" :default-active="router.currentRoute.value.path" style="min-height:calc(100vh)">
					<el-menu-item index="/manager/home">
						<el-icon><House /></el-icon>
						<span>首页</span>
					</el-menu-item>
					<el-sub-menu index="1">
						<template #title>
							<el-icon><Location /></el-icon>
							<span>用户管理</span>
						</template>
						<el-menu-item index="/manager/admin">管理员信息</el-menu-item>
						<el-menu-item index="/manager/user">普通用户信息</el-menu-item>
					</el-sub-menu>
				</el-menu>
			</div>
			<!-- 菜单区域结束 -->
			
			<!-- 数据渲染区域开始 -->
			<div style="flex: 1; width: 0; padding: 10px; background-color: #f2f4ff">
				<RouterView @updateUser = "updateUser" />
			</div>
			<!-- 数据渲染区域结束 -->
		</div>
		<!--下方区域结束-->
	</div>
</template>

<script setup>
	import router from '@/router/index.js'
    import { RouterView } from 'vue-router'
	import { reactive } from 'vue'

	const data = reactive({
		user: JSON.parse(localStorage.getItem('code_user') || '{}')
	})

	// 退出登录函数
	const logout = () => {
		localStorage.removeItem('code_user')
		location.href = '/login'
	}

	// 更新用户信息
	const updateUser = () => {
		data.user = JSON.parse(localStorage.getItem('code_user') || '{}')
	}


	// 这个判断有隐患 可以伪造去登录 直接去登录页面
	// if (!data.user?.id) {
	// 	location.href = '/login'
	// }

	// 判断是否登录,如果没有登录则跳转到登录页面
	// let userStr = localStorage.getItem('code_user')
	// if (userStr) {
	// 	let user = JSON.parse(userStr)
	// } else {
	// 	location.href = '/login'
	// }

</script>

<style scoped>
	.el-menu {
		background-color: #3a456b;
		border: none;
	}
	.el-sub-menu__title {
		background-color: #3a456b;
		color: #ddd;
	}
	.el-menu-item {
		height: 50px;
		color: #ddd;
	}
	.el-menu .is-active {
		background-color: #537bee;
		color: #fff;
	}
	.el-sub-menu__title:hover {
		background-color: #3a456b;
	}
	.el-menu-item:not(.is-active):hover {
		background-color: #7a9fff;
		color: #333;
	}
	.el-dropdown {
		cursor: pointer;
	}
	.el-tooltip__trigger {
		outline: none;
	}
	.el-menu--inline .el-menu-item {
		padding-left: 48px !important;
	}
</style> 

src/views/User.vue

复制代码
<template>
	<div>
		<div class="card" style="margin-bottom: 5px">
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.name" placeholder="请输入名称查询" :prefix-icon="Search"></el-input>
			<el-button type="primary" @click="load">查 询</el-button>
			<el-button @click="reset">重 置</el-button>
		</div>
		<div class="card" style="margin-bottom: 5px">
			<el-button type="primary" @click="handleAdd">新增</el-button>
			<el-button type="danger" @click="deleteBatch">批量删除</el-button>
			<el-button type="info" @click="exportData">批量导出</el-button>
			<el-upload 
				style="display: inline-block;margin-left: 10px;"
				action="http://localhost:9999/admin/import"
				:show-file-list="false"
				:on-success="handleImportSuccess"
			>
				<el-button type="success">批量导入</el-button>
			</el-upload>			
		</div>

		<div class="card" style="margin-bottom: 5px">
			<el-table :data="data.tableData" style="width: 100%" @selection-change="handleSelectionChange" :header-cell-style="{color: '#333', backgroundColor: 'eaf4ff'}">
				<el-table-column type="selection" width="55" />
				<el-table-column label="头像" width="100">
					<template #default="scope">
						<el-image v-if="scope.row.avatar" :src="scope.row.avatar" :preview-src-list="[scope.row.avatar]" :preview-teleported="true" style="width: 40px;height: 40px;border-radius: 50%;display: block;" />
					</template>
				</el-table-column>
				<el-table-column prop="username" label="账号" width="188" />
				<el-table-column prop="name" label="名称" width="188" />
				<el-table-column prop="phone" label="电话" width="188" />
				<el-table-column prop="email" label="邮箱" width="222" />
				<el-table-column label="操作" width="100">
					<template #default="scope">
						<el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
						<el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
					</template>
				</el-table-column>
			</el-table>
		</div>
		<div class="card">
			<el-pagination 
                v-model:current-page="data.pageNum" 
				v-model:page-size="data.pageSize" 
                layout="total, sizes, prev, pager, next, jumper" 
				:page-sizes="[5, 10, 20]"
                :total="data.total"
				@current-change="load"
				@size-change="load"
            />
		</div>
		<!--弹窗开始-->
		<el-dialog title="普通用户信息" v-model="data.FormVisible" width="40%" destroy-on-close>
			<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
				<el-form-item prop="username" label="账号">
					<el-input v-model="data.form.username" autocomplete="off" placeholder="请输入账号" />
				</el-form-item>
				<el-form-item prop="name" label="名称">
					<el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称" />
				</el-form-item>
				<el-form-item prop="phone" label="电话">
					<el-input v-model="data.form.phone" autocomplete="off" placeholder="请输入电话" />
				</el-form-item>
				<el-form-item prop="email" label="邮箱">
					<el-input v-model="data.form.email" autocomplete="off" placeholder="请输入邮箱" />
				</el-form-item>
				<el-form-item prop="avatar" label="头像">
					<el-upload 
						action="http://localhost:9999/files/upload"
						:headers="{token: data.user.token}"
						:on-success="handleFileSuccess"
						list-type="picture"
						>
						<el-button type="primary">上传头像</el-button>
					</el-upload>
				</el-form-item>
			</el-form>
			<template #footer>
				<div class="dialog-footer">
					<el-button @click="data.FormVisible = false">取消</el-button>
					<el-button type="primary" @click="save">保存</el-button>
				</div>
			</template>
		</el-dialog>
		<!--弹窗结束-->
	</div>
</template>
<script setup>
import {reactive,ref} from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import { ElMessageBox,ElMessage } from "element-plus";

// 定义数据对象 , 页面渲染的数据都在这里面定义
// 定义表单验证规则 触发表单验证事件 , 提交表单事件 , 表单验证规则等都在这里面定义
const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
	username: null,
	name: null,
	pageNum: 1,
	pageSize: 5,
	total: 6,
	tableData: [],
	FormVisible: false,
	form: {},
	rules: {
		username: [
			{required: true, message: '请输入账号', trigger: 'blur'},
			{min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur'}],
		name: [
			{required: true, message: '请输入名称', trigger: 'blur'},
			{min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur'}],
	
		phone: [
			{required: true, message: '请输入电话', trigger: 'blur'},
			{min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur'}],
	
		email: [
			{required: true, message: '请输入邮箱', trigger: 'blur'},
			{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']},
			{min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur'},
			{pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
		]
	},
	rows: [],
	ids: []
})

// 定义表单引用
const formRef = ref()

// 分页查询 拿到后端接口数据(后端给前端返回的数据, 赋值给前端页面展示的数据)
const load = () => {
	request.get('/user/selectPage',{
		params: {
			pageNum: data.pageNum,
			pageSize: data.pageSize,
			username: data.username,
			name: data.name
		}
	}).then(res => {
		if (res.code === '200') {
			data.tableData = res.data.list
			data.total = res.data.total
		} else {
			ElMessage.error(res.msg)
		}
	})
}

load()

// 重置查询条件
const reset = () => {
	data.username = null
	data.name = null
	load()
}

// 新增管理员信息弹窗展示
const handleAdd = () => {
	data.FormVisible = true
	data.form = {}
}

// 提交表单验证

// 新增管理员信息
const add = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.post('/user/add', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('新增成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			})
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

const handleEdit = (row) => {
	// data.form = row // 浅拷贝,修改原始数据
	data.form = JSON.parse(JSON.stringify(row)) // 深拷贝,防止修改原始数据
	data.FormVisible = true
}

// 修改管理员信息 前端调用后端接口 修改管理员信息 并刷新页面数据 刷新页面数据有两种方式 
// 一种是通过前端调用后端接口 直接修改数据库中的数据 然后重新查询一遍数据 
// 另一种是直接通过前端调用后端接口 直接修改数据库中的数据 但是不重新查询一遍数据 
// 直接在前端更新数据 这样效率更高 因为不需要重新请求服务器 
const update = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.put('/user/update', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('修改成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			}) 
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

// 保存按钮点击事件,根据表单中的id判断是新增还是修改操作 一个方法同时兼容两种方法
const save = () => {
	data.form.id ? update() : add()
}

// 删除管理员信息 前端调用后端接口 删除管理员信息 并刷新页面数据
const del = (id) => {
	ElMessageBox.confirm('删除后无法恢复,你确认要删除该行数据吗?','删除确认',{type: 'warning'}).then(res => {
		request.delete('/user/delete/' + id).then(res => {
			if (res.code === '200') {
				ElMessage.success('删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 批量删除管理员信息 前端调用后端接口 批量删除管理员信息 并刷新页面数据
const handleSelectionChange = (rows) => { // rows 就是实际选择的数组 选中项发生变化时触发
	// data.rows就等于传进来的rows 选中项发生变化时触发
	data.rows = rows
	// map可以把对象的数组 转换成 一个纯数字的数组 [1,2,3]
	data.ids = data.rows.map(v => v.id)
}

const deleteBatch = () => {
	if (data.rows.length === 0) {
		ElMessage.warning('请选择数据')
		return 
	}
	ElMessageBox.confirm('删除后无法恢复,您确认删除吗?',{type: 'warning'}).then(res => {
		request.delete('/user/deleteBatch', {data: data.rows}).then(res => {
			if (res.code === '200') {
				ElMessage.success('批量删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 可以全量导出数据也可以条件导出数据和选择导出数据
const exportData = () => {
	// 把数组 转换成一个字符串 逗号分隔的字符串 比如 [1,2,3] => '1,2,3'
	let idsStr = data.ids.join(',')
	// 动态拼接url地址 导出数据 导出数据有两种方式 一种是通过前端调用后端接口 
	// 直接导出数据 一种是通过前端调用后端接口 直接下载文件 
	// ES6 模板字符串拼接url地址 动态拼接url地址 语法:`${变量}` 动态拼接url地址
	let url = `http://localhost:9999/admin/export?username=${data.username === null ? '' : data.username}&name=${data.name === null ? '' : data.name}&ids=${idsStr}&token=${data.user.token}`	
	window.open(url)
}

// 加入判断 导入成功之后提示函数
const handleImportSuccess = (res) => {
	if (res.code === '200') {
		ElMessage.success('批量导入数据成功')
		load()
	} else {
		ElMessage.error(res.msg)
	}
}

// 头像上传成功之后 把返回的图片地址赋值给表单中的avatar字段 用于更新用户信息
const handleFileSuccess = (res) => {
	data.form.avatar = res.data	
}
</script>

src/views/Person.vue

复制代码
<template>
    <div class="card" style="width: 50%;">
        <div style="font-weight: 500;font-size: 20px;text-align: center;">个人中心</div>

        <el-form ref="formRef" :model="data.user" label-width="80px" style="padding: 20px 30px 10px 0;">
            <el-form-item prop="username" label="账号">
                <el-input size="large" v-model="data.user.username" autocomplete="off" placeholder="请输入账号" />
            </el-form-item>
            <el-form-item prop="name" label="名称">
                <el-input size="large" v-model="data.user.name" autocomplete="off" placeholder="请输入名称" />
            </el-form-item>
            <el-form-item prop="phone" label="电话">
                <el-input size="large" v-model="data.user.phone" autocomplete="off" placeholder="请输入电话" />
            </el-form-item>
            <el-form-item prop="email" label="邮箱">
                <el-input size="large" v-model="data.user.email" autocomplete="off" placeholder="请输入邮箱" />
            </el-form-item>
            <el-form-item prop="avatar" label="头像">
                <el-upload 
                    action="http://localhost:9999/files/upload"
                    :headers="{token: data.user.token}"
                    :on-success="handleFileSuccess"
                    list-type="picture"
                    >
                    <el-button type="primary">上传头像</el-button>
                </el-upload>
            </el-form-item>
        </el-form>
        <div style="text-align: center;">
            <el-button type="info"  style="padding: 20px 50px;" @click="data.FormVisible = false">取消</el-button>
            <el-button type="primary" style="padding: 20px 50px;" @click="update">保存</el-button>
        </div>
    </div>
</template>

<script setup>
import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
import { reactive} from 'vue'
const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
})

const handleFileSuccess = (res) => {
    data.user.avatar = res.data;
}

// const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['updateUser'])


const update = () => {
    let url
    if (data.user.role = 'ADMIN') {
        url = '/admin/update'
    }
    if (data.user.role = 'USER') {
        url = '/user/update'
    }
    request.put(url,data.user).then(res => {
        if (res.code === '200') {
            ElMessage.success('更新成功')
            localStorage.setItem('code_user', JSON.stringify(data.user))
            emit('updateUser')

            // window.location.reload()
        }
    })
    // localStorage.setItem('code_user', JSON.stringify(data.user));
}

</script>

<style scoped>

</style>
我们在 src/views/Person.vue 里面去定义
复制代码
const emit = defineEmits(['updateUser'])
然后我们去 src/views/Manager.vue 去添加
复制代码
<div style="flex: 1; width: 0; padding: 10px; background-color: #f2f4ff">
    <RouterView @updateUser = "updateUser" />
</div>
我们通过 emit 去通过 Person.vue 里面的数据,我再传送到 Manager.vue,然后让 Manager.vue 里面去做更新。

老师的

修改密码

复制代码
// 修改密码方法
const updatePassword = () => {
    formRef.value.validate((valid) => {
        if (valid) {
            request.post('/updatePassword', data.user).then(res => {
                if (res.code === '200') {
                    ElMessage.success('修改成功')
                    // 延迟500毫秒后跳转登录页面,防止太快导致还没退出就进入登录界面了
                    setInterval(() => {
                        localStorage.removeItem('code_user')
                        location.href = '/login'
                    },500)
                } else {
                    ElMessage.error(res.msg)
                }
            })
        }
    })    
}

前端

但凡是点击事件,点击事件你要么没有做,要么点击事件之后,这个事件里面的接口没有调用,要么点击的时候,报错了你不知道,此时你按F12去看控制台。
复制代码
<template>
	<div>
		<!-- 头部区域开始 -->
		<div style="height: 60px;display: flex;">
			<div style="width: 240px;display: flex;align-items: center;padding-left: 20px;background-color: #3a456b;">
				<img style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo.png" alt="">
				<span style="font-size: 20px;font-weight: bold;color: #f1f1f1;margin-left: 5px;">隆迟民宿管理系统</span>
			</div>
			<div style="flex: 1;display: flex;align-items: center;padding-left: 20px;border-bottom: 1px solid #ddd;">
				<span style="margin-right: 5px; cursor: pointer;" @click="router.push('/manager/home')">首页</span>/<span style="margin-left: 5px;">{{ router.currentRoute.value.meta.title || '首页' }}</span>
			</div>
			<div style="width: fit-content;padding-right: 20px;display: flex;align-items: center;border-bottom: 1px solid #ddd;">
				<el-dropdown>
					<div style="display: flex;align-items: center;">
						<img v-if="data.user?.avatar" style="width: 40px;height: 40px;border-radius: 50%;" :src="data.user?.avatar" />
						<img v-else style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo1.png" alt="">
						<span style="margin-left: 10px;color: #333;">{{ data.user?.name }}</span>
					</div>
					<template #dropdown>
						<el-dropdown-menu>
							<el-dropdown-item @click="router.push('/manager/person')">个人信息</el-dropdown-item>
							<el-dropdown-item @click="router.push('/manager/updatePassword')">修改密码</el-dropdown-item>
							<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
						</el-dropdown-menu>
					</template>
				</el-dropdown>
			</div>
		</div>
		<!-- 头部区域结束 -->
		<!--下方区域开始-->
		<div style="display: flex">
			<!-- 菜单区域开始 -->
			<div style="width: 240px;">
				<el-menu router :default-openeds="['1']" :default-active="router.currentRoute.value.path" style="min-height:calc(100vh)">
					<el-menu-item index="/manager/home">
						<el-icon><House /></el-icon>
						<span>首页</span>
					</el-menu-item>
					<el-sub-menu index="1">
						<template #title>
							<el-icon><Location /></el-icon>
							<span>用户管理</span>
						</template>
						<el-menu-item index="/manager/admin">管理员信息</el-menu-item>
						<el-menu-item index="/manager/user">普通用户信息</el-menu-item>
					</el-sub-menu>
				</el-menu>
			</div>
			<!-- 菜单区域结束 -->
			
			<!-- 数据渲染区域开始 -->
			<div style="flex: 1; width: 0; padding: 10px; background-color: #f2f4ff">
				<RouterView @updateUser = "updateUser" />
			</div>
			<!-- 数据渲染区域结束 -->
		</div>
		<!--下方区域结束-->
	</div>
</template>

<script setup>
	import router from '@/router/index.js'
    import { RouterView } from 'vue-router'
	import { reactive } from 'vue'

	const data = reactive({
		user: JSON.parse(localStorage.getItem('code_user') || '{}')
	})

	// 退出登录函数
	const logout = () => {
		localStorage.removeItem('code_user')
		location.href = '/login'
	}

	// 更新用户信息
	const updateUser = () => {
		data.user = JSON.parse(localStorage.getItem('code_user') || '{}')
	}


	// 这个判断有隐患 可以伪造去登录 直接去登录页面
	// if (!data.user?.id) {
	// 	location.href = '/login'
	// }

	// 判断是否登录,如果没有登录则跳转到登录页面
	// let userStr = localStorage.getItem('code_user')
	// if (userStr) {
	// 	let user = JSON.parse(userStr)
	// } else {
	// 	location.href = '/login'
	// }

</script>

<style scoped>
	.el-menu {
		background-color: #3a456b;
		border: none;
	}
	.el-sub-menu__title {
		background-color: #3a456b;
		color: #ddd;
	}
	.el-menu-item {
		height: 50px;
		color: #ddd;
	}
	.el-menu .is-active {
		background-color: #537bee;
		color: #fff;
	}
	.el-sub-menu__title:hover {
		background-color: #3a456b;
	}
	.el-menu-item:not(.is-active):hover {
		background-color: #7a9fff;
		color: #333;
	}
	.el-dropdown {
		cursor: pointer;
	}
	.el-tooltip__trigger {
		outline: none;
	}
	.el-menu--inline .el-menu-item {
		padding-left: 48px !important;
	}
</style> 

自己的

老师的

src/views/UpdatePassword.vue
复制代码
<template>
    <div class="card" style="width: 50%;">
        <div style="margin-bottom: 20px;text-align: center;font-weight: bold;font-size: 26px;">修 改 密 码</div>
        <el-form status-icon ref="formRef" :model="data.user" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
            <el-form-item prop="password" label="原密码">
                <el-input show-password size="large" v-model="data.user.password" autocomplete="off" placeholder="请输入原密码" prefix-icon="Lock" />
            </el-form-item>
            <el-form-item prop="newPassword" label="新密码">
                <el-input show-password size="large" v-model="data.user.newPassword" autocomplete="off" placeholder="请输入新密码" prefix-icon="Lock" />
            </el-form-item>
            <el-form-item prop="confirmPassword" label="确认密码">
                <el-input show-password size="large" v-model="data.user.confirmPassword" autocomplete="off" placeholder="请再次输入新密码" prefix-icon="Lock" />
            </el-form-item>
        </el-form>
        <div style="text-align: center;">
            <el-button type="info"  style="padding: 20px 50px;" @click="data.FormVisible = false">取消</el-button>
            <el-button type="primary" style="padding: 20px 50px;" @click="updatePassword">保存</el-button>
        </div>
    </div>
</template>

<script setup>
import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
import { reactive, ref} from 'vue'

const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
    rules: {
        password: [
            { required: true, message: '请输入原密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'}
        ],
        newPassword: [
            { required: true, message: '请输入新密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'}
        ],
        confirmPassword: [
            { required: true, message: '请再次输入密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'},
        ]

    },
    FormVisible: false,
})

const formRef = ref()

const updatePassword = () => {
    formRef.value.validate((valid) => {
        if (valid) {
            request.post('/updatePassword', data.user).then(res => {
                if (res.code === '200') {
                    ElMessage.success('修改成功')
                    // 延迟500毫秒后跳转登录页面,防止太快导致还没退出就进入登录界面了
                    setInterval(() => {
                        localStorage.removeItem('code_user')
                        location.href = '/login'
                    },500)
                } else {
                    ElMessage.error(res.msg)
                }
            })
        }
    })    
}

</script>

<style scoped>

</style>

老师的

System.out.println(""); 打端点 调试
复制代码
System.out.println(""); 打端点 调试

Error:java: 错误: 不支持发行版本 21


解决方案
解决IDEA运行Java代码提示"Error:java: 错误: 不支持发行版本21"的方法

此错误表示项目配置的Java版本(21)与当前环境不兼容。以下是逐步解决方案:
1. 检查JDK安装与环境变量

    确认本地已安装JDK 21(下载地址)

    检查环境变量JAVA_HOME是否指向JDK 21安装路径

    终端验证:执行java -version应显示21.x.x

2. IDEA项目配置修改(关键步骤)

步骤1:设置Project SDK
        
markdown

File > Project Structure > Project Settings > Project
  - Project SDK: 选择JDK 21
  - Project language level: 选择"21"

Project SDK设置示意图4

步骤2:调整模块语言级别       
markdown

Project Structure > Modules > Sources
  - Language level: 改为"21"

步骤3:更新编译器设置      
markdown

File > Settings > Build, Execution, Deployment > Compiler > Java Compiler
  - Target bytecode version: 设置为21
  - 每个模块的编译版本:同步改为21


3. Maven项目特殊配置

在pom.xml中添加:       
XML

<properties>
  <maven.compiler.source>21</maven.compiler.source>
  <maven.compiler.target>21</maven.compiler.target>
</properties>

<!-- 或使用插件配置 -->
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.11.0</version>
      <configuration>
        <source>21</source>
        <target>21</target>
      </configuration>
    </plugin>
  </plugins>
</build>

4. 验证IDEA版本兼容性

    Java 21需IntelliJ IDEA 2023.1+(查看兼容版本)

    旧版IDEA升级:Help > Check for Updates

5. 重建项目

完成配置后执行:
       
markdown
       

Build > Rebuild Project

备选方案:降级Java版本

若无法使用Java 21:

    修改pom.xml/项目配置中的版本号为已安装版本(如17)

    同步调整所有相关设置(SDK/Language level等)
将退出登录方法代码 去 管理员页(src/views/Manager.vue)复制UpdatePassword.vue
修改密码代码 去 登录页(src/views/Login.vue)复制

后端

1, 去 controller/WebController.java 文件添加如下代码
复制代码
/**
 * 修改密码接口
 * */
@PostMapping("/updatePassword")
public Result updatePassword(@RequestBody Account account) {
    // System.out.println(""); 打端点 调试
    // 判断用户角色
    if ("ADMIN".equals(account.getRole())) {
        adminService.updatePassword(account);
    }
    if ("USER".equals(account.getRole())) {
        userService.updatePassword(account);
    }
    return Result.success();
}
2, 去 entity/Account.java entity/Admin.java entity/User.java 添加如下代码
复制代码
private String newPassword;
private String confirmPassword;
    
@Override
public String getNewPassword() {
    return newPassword;
}

@Override
public void setNewPassword(String newPassword) {
    this.newPassword = newPassword;
}

@Override
public String getConfirmPassword() {
    return confirmPassword;
}

@Override
public void setConfirmPassword(String confirmPassword) {
    this.confirmPassword = confirmPassword;
}    
3, 去 controller/WebController.java 文件添加如下代码
复制代码
/**
 * 修改密码接口
 * */
@PostMapping("/updatePassword")
public Result updatePassword(@RequestBody Account account) {
    // System.out.println(""); 打端点 调试
    // 判断用户角色
    if ("ADMIN".equals(account.getRole())) {
        adminService.updatePassword(account);
    }
    if ("USER".equals(account.getRole())) {
        userService.updatePassword(account);
    }
    return Result.success();
}

老师的

4, 去 service/AdminService.java 文件 添加如下代码
复制代码
public void updatePassword(Account account) {
    // 判断用户输入的新密码和确认密码是否一致 这个写到 service 里面
    if (!account.getNewPassword().equals(account.getConfirmPassword())) {
        throw new CustomerException("500","你两次输入的密码不一致");
    }
    // 效验一下,原密码是否一致
    Account currentUser = TokenUtils.getCurrentUser();
    // 判断获取到的原密码是否等于当前用户的密码
    if (!account.getPassword().equals(currentUser.getPassword())) {
        throw new CustomerException("500","原密码输入错误");
    }
}

老师的

5, 去 service/UserService.java 文件 添加如下代码
复制代码
public void updatePassword(Account account) {
    // 判断用户输入的新密码和确认密码是否一致 这个写到 service 里面
    if (!account.getNewPassword().equals(account.getConfirmPassword())) {
        throw new CustomerException("500","你两次输入的密码不一致");
    }
    // 效验一下,原密码是否一致
    Account currentUser = TokenUtils.getCurrentUser();
    // 判断获取到的原密码是否等于当前用户的密码
    if (!account.getPassword().equals(currentUser.getPassword())) {
        throw new CustomerException("500","原密码输入错误");
    }
}

老师的

验证表单
复制代码
{ validator: (rule, value, callback) => {
    if (value !== data.user.newPassword) {
        callback(new Error('两次输入的密码不一致'))
    } else {
        callback()
    }
}, trigger: 'blur' }

查错

从网络预览可以看到,请求成功了,就是没有返回数据,问题出在哪里,在前端,错误应该在前端提示,解决如下图
备份 service/AdminService.java
复制代码
package com.longchi.service;

import cn.hutool.core.util.StrUtil;
import com.longchi.entity.Account;
import com.longchi.entity.Admin;
import com.longchi.exception.CustomerException;
import com.longchi.mapper.AdminMapper;
import com.longchi.utils.TokenUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import java.util.List;

@Service
public class AdminService {

    @Resource
    AdminMapper adminMapper;

    public void add(Admin admin) {
        // 根据新的账号查询数据库 是否存在同样账号的数据
        Admin dbAdmin = adminMapper.selectByUsername(admin.getUsername());
        if (dbAdmin != null) {
            throw new CustomerException("账号重复");
        }

        // 设置默认密码
        if (StrUtil.isBlank(admin.getPassword())) {
            admin.setPassword("admin");
        }
        // 设置默认名称
        if (StrUtil.isBlank(admin.getName())) {
            admin.setName(admin.getUsername());
        }
        admin.setRole("ADMIN");
        adminMapper.insert(admin);
    }

    public void update(Admin admin) {
        adminMapper.updateById(admin);
    }

    public void deleteById(Integer id) {
        adminMapper.deleteById(id);
    }

    // 批量删除就是循环之后调用单个删除
    public void deleteBatch(List<Admin> list) {
        for (Admin admin : list) {
            this.deleteById(admin.getId());
            //this.deleteById(admin.getId());
        }
    }

    public String admin(String name) {
        if ("admin".equals(name)) {
            return "admin";
        } else {
            throw new CustomerException("账号错误");
        }
    }

    public Admin selectById(String id) {
        return adminMapper.selectById(id);
    }

    public List<Admin> selectAll(Admin admin) {
        return adminMapper.selectAll(admin);
    }

    public PageInfo<Admin> selectPage(Integer pageNum, Integer pageSize, Admin admin) {
        // 获取当前登录用户信息 可以去做一些验证
        // Account currentUser = TokenUtils.getCurrentUser();
        // 开启分页查询
        PageHelper.startPage(pageNum,pageSize);
        List<Admin> list = adminMapper.selectAll(admin);
        return PageInfo.of(list);
    }

    public Admin login(Account account) {
        // 验证数据库有没有该账号
        Admin dbAdmin = adminMapper.selectByUsername(account.getUsername());
        if (dbAdmin == null) {
            throw new CustomerException("账号不存在");
        }
        // 验证密码是否正确 数据库的密码与我们获取到输入密码(参数)不一致
        if (!dbAdmin.getPassword().equals(account.getPassword())) {
            throw new CustomerException("账号或密码错误");
        }
        // 创建 token 并返回给前端
        String token = TokenUtils.createToken(dbAdmin.getId()+"-"+"ADMIN",dbAdmin.getPassword());
        //在 dbAdmin 里面设置 token,然后返回出去
        dbAdmin.setToken(token);
        // 返回给前端的数据
        return dbAdmin;
    }

    public void updatePassword(Account account) {
        // 判断用户输入的新密码和确认密码是否一致 这个写到 service 里面
        if (!account.getNewPassword().equals(account.getConfirmPassword())) {
            throw new CustomerException("500","你两次输入的密码不一致");
        }
        // 效验一下,原密码是否一致
        Account currentUser = TokenUtils.getCurrentUser();
        // 判断获取到的原密码是否等于当前用户的密码
        if (!account.getPassword().equals(currentUser.getPassword())) {
            throw new CustomerException("500","原密码输入错误");
        }
        // 开始更新密码
        Admin admin = adminMapper.selectById(currentUser.getId().toString());
    }
}
Springboot3+vue3个人中心,修改密码 实现代码如下
复制代码
1, src/views/Login.vue
<template>
    <div class="bg">
        <div style="width: 600px;height: 550px;background-color: #fff;opacity: 0.9;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);padding: 10px;">
            <el-form status-icon ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
                <div style="margin-bottom: 30px;text-align: center;font-weight: bold;font-size: 26px;">欢 迎 登 录</div>
				<el-form-item prop="username" label="账号">
					<el-input size="large" v-model="data.form.username" autocomplete="off" placeholder="请输入账号" prefix-icon="User" />
				</el-form-item>
                <el-form-item prop="password" label="密码">
					<el-input show-password size="large" v-model="data.form.password" autocomplete="off" placeholder="请输入密码" prefix-icon="Lock" />
				</el-form-item>
                <el-form-item prop="role" label="角色">
					<el-select size="large" style="width: 100%;" v-model="data.form.role" placeholder="请选择角色">
                        <el-option label="管理员" value="ADMIN"></el-option>
                        <el-option label="普通用户" value="USER"></el-option>
                    </el-select>
				</el-form-item>
                <div style="margin-bottom: 20px;">
                    <el-button type="primary" style="width: 100%;" size="large" @click="login">登 录</el-button>
                </div>
                <div style="text-align: right;">
                    还没有账号? 请 <a style="color: #3741fb;" href="/register">注册</a>
                </div>
			</el-form>
        </div>
    </div>
</template>

<script setup>
import { ElMessage } from 'element-plus'
import router from '@/router/index.js';
import request from "@/utils/request.js";
import { reactive, ref } from 'vue'

// 发起效验 表单提交事件
const formRef = ref()
const data = reactive({
    form: {
        username: '',
        password: '',
        role: 'USER'
    },
    rules: {
        username: [
            { required: true, message: '请输入账号', trigger: 'blur' },
            { min: 1, max: 15, message: '长度在 1 到 15 个字符', trigger: 'blur' }
        ],
        password: [
            { required: true, message: '请输入密码', trigger: 'blur' },
            { min: 3, max: 18, message: '长度在 3 到 18 个字符', trigger: 'blur' },
            { pattern: /^[a-zA-Z0-9_]+$/, message: '只能输入字母、数字和下划线', trigger: 'blur' }
        ],
        role: [
            { required: true, message: '请选择角色', trigger: 'change' }
        ]
    }
} )


// 登录方法
const login = () => {
    formRef.value.validate((valid) => {
        // 校验通过
        if (valid) {
            request.post('/login', data.form).then(res => {
                if (res.code ==='200') {
                    // 存储用户信息
                    localStorage.setItem('code_user', JSON.stringify(res.data || {}))
                    ElMessage.success('登录成功')
                    router.push('/')
                } else {
                    ElMessage.error(res.msg)
                }
            })
        }
    })    
}
</script>

<style scoped>
    .bg {
        width: 100%;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: hidden;
        background-image: url("@/assets/imgs/bg.jpg");
        background-size: cover;
    }
</style>



2, src/views/Register.vue
<template>
    <div class="bg">
        <div style="width: 600px;height: 550px;background-color: #fff;opacity: 0.9;border-radius: 10px;box-shadow: 0 0 10px rgba(0,0,0,0.1);padding: 10px;">
            <el-form status-icon ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
                <div style="margin-bottom: 30px;text-align: center;font-weight: bold;font-size: 26px;">欢 迎 注 册</div>
				<el-form-item prop="username" label="账号">
					<el-input size="large" v-model="data.form.username" autocomplete="off" placeholder="请输入账号" prefix-icon="User" />
				</el-form-item>
                <el-form-item prop="password" label="密码">
					<el-input show-password size="large" v-model="data.form.password" autocomplete="off" placeholder="请输入密码" prefix-icon="Lock" />
				</el-form-item>
                <el-form-item prop="confirmPassword" label="确认密码">
					<el-input show-password size="large" v-model="data.form.confirmPassword" autocomplete="off" placeholder="请再次确认密码" prefix-icon="Lock" />
				</el-form-item>
				<el-form-item prop="name" label="名称">
					<el-input size="large" v-model="data.form.name" autocomplete="off" placeholder="请输入名称" prefix-icon="User" />
				</el-form-item>
				<el-form-item prop="phone" label="电话">
					<el-input size="large" v-model="data.form.phone" autocomplete="off" placeholder="请输入电话" prefix-icon="User" />
				</el-form-item>
				<el-form-item prop="email" label="邮箱">
					<el-input size="large" v-model="data.form.email" autocomplete="off" placeholder="请输入邮箱" prefix-icon="User" />
				</el-form-item>
                <div style="margin-bottom: 20px;">
                    <el-button type="primary" style="width: 100%;background-color: #248243;border-color: #248243;" size="large" @click="register">注 册</el-button>
                </div>
                <div style="text-align: right;">
                    已有账号? 请 <a style="color: #248243;" href="/login">登录</a>
                </div>
			</el-form>
        </div>
    </div>
</template>

<script setup>
import { ElMessage } from 'element-plus'
import router from '@/router/index.js';
import request from "@/utils/request.js";
import { reactive, ref } from 'vue'

// 自定义效验两次密码是否一致
const validatePass = (rule, value, callback) => {
    // value 表示用户再次输入的密码,ruleForm.pass(data.form.password) 表示用户第一次输入的密码
    if (value !== data.form.password) {
        callback(new Error("两次输入的密码不匹配!"))
    } else {
        callback()
    }
}

// 发起效验 表单提交事件
const formRef = ref()
const data = reactive({
    form: {
        username: '',
        password: '',
        confirmPassword: '',
        name: '',
        phone: '',
        email: ''
    },
    rules: {
        username: [
            { required: true, message: '请输入账号', trigger: 'blur' },
            { min: 1, max: 15, message: '长度在 1 到 15 个字符', trigger: 'blur' }
        ],
        password: [
            { required: true, message: '请输入密码', trigger: 'blur' },
            { min: 3, max: 18, message: '长度在 3 到 18 个字符', trigger: 'blur' },
            { pattern: /^[a-zA-Z0-9_]+$/, message: '只能输入字母、数字和下划线', trigger: 'blur' }
        ],
        confirmPassword: [
            { required: true, message: '请再次确认密码', trigger: 'blur' },
            { validator: validatePass, trigger: 'blur' }
        ],
        name: [
            { required: true, message: '请输入名称', trigger: 'blur' },
            { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
        ],
        phone: [
            { required: true, message: '请输入电话', trigger: 'blur' },
            { min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur' }
        ],
        email: [
            { required: true, message: '请输入邮箱地址', trigger: 'blur' },
            { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] },
            { min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' },
            { pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, message: '请输入正确的邮箱地址', trigger: 'blur' },
            { pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, message: '请输入正确的邮箱地址', trigger: 'change' }
        ]
    }
} )

// 注册方法
const register = () => {
    formRef.value.validate((valid) => {
        if (valid) {
            request.post('/register', data.form).then(res => {
                if (res.code ==='200') {
                    ElMessage.success('注册成功,请登录!')
                    router.push('/login')
                } else {
                    ElMessage.error(res.msg)
                }
            })
        }
    })
}
</script>

<style scoped>
    .bg {
        width: 100%;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        overflow: hidden;
        background-image: url("@/assets/imgs/bg1.jpg");
        background-size: cover;
    }
</style>



3, src/views/Admin.vue
<template>
	<div>
		<div class="card" style="margin-bottom: 5px">
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.name" placeholder="请输入名称查询" :prefix-icon="Search"></el-input>
			<el-button type="primary" @click="load">查 询</el-button>
			<el-button @click="reset">重 置</el-button>
		</div>
		<div class="card" style="margin-bottom: 5px">
			<el-button type="primary" @click="handleAdd">新增</el-button>
			<el-button type="danger" @click="deleteBatch">批量删除</el-button>
			<el-button type="info" @click="exportData">批量导出</el-button>
			<el-upload 
				style="display: inline-block;margin-left: 10px;"
				action="http://localhost:9999/admin/import"
				:show-file-list="false"
				:on-success="handleImportSuccess"
			>
				<el-button type="success">批量导入</el-button>
			</el-upload>			
		</div>
		<div class="card" style="margin-bottom: 5px">
			<el-table :data="data.tableData" style="width: 100%" @selection-change="handleSelectionChange" :header-cell-style="{color: '#333', backgroundColor: 'eaf4ff'}">
				<el-table-column type="selection" width="55" />
				<el-table-column label="头像" width="100">
					<template #default="scope">
						<el-image v-if="scope.row.avatar" :src="scope.row.avatar" :preview-src-list="[scope.row.avatar]" :preview-teleported="true" style="width: 40px;height: 40px;border-radius: 50%;display: block;" />
					</template>
				</el-table-column>
				<el-table-column prop="username" label="账号" width="188" />
				<el-table-column prop="name" label="名称" width="188" />
				<el-table-column prop="phone" label="电话" width="188" />
				<el-table-column prop="email" label="邮箱" width="222" />
				<el-table-column label="操作" width="100">
					<template #default="scope">
						<el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
						<el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
					</template>
				</el-table-column>
			</el-table>
		</div>
		<div class="card">
			<el-pagination 
                v-model:current-page="data.pageNum" 
				v-model:page-size="data.pageSize" 
                layout="total, sizes, prev, pager, next, jumper" 
				:page-sizes="[5, 10, 20]"
                :total="data.total"
				@current-change="load"
				@size-change="load"
            />
		</div>
		<!--弹窗开始-->
		<el-dialog title="管理员信息" v-model="data.FormVisible" width="40%" destroy-on-close>
			<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
				<el-form-item prop="username" label="账号">
					<el-input v-model="data.form.username" autocomplete="off" placeholder="请输入账号" />
				</el-form-item>
				<el-form-item prop="name" label="名称">
					<el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称" />
				</el-form-item>
				<el-form-item prop="phone" label="电话">
					<el-input v-model="data.form.phone" autocomplete="off" placeholder="请输入电话" />
				</el-form-item>
				<el-form-item prop="email" label="邮箱">
					<el-input v-model="data.form.email" autocomplete="off" placeholder="请输入邮箱" />
				</el-form-item>
				<el-form-item prop="avatar" label="头像">
					<el-upload 
						action="http://localhost:9999/files/upload"
						:headers="{token: data.user.token}"
						:on-success="handleFileSuccess"
						list-type="picture"
						>
						<el-button type="primary">上传头像</el-button>
					</el-upload>
				</el-form-item>
			</el-form>
			<template #footer>
				<div class="dialog-footer">
					<el-button @click="data.FormVisible = false">取消</el-button>
					<el-button type="primary" @click="save">保存</el-button>
				</div>
			</template>
		</el-dialog>
		<!--弹窗结束-->
	</div>
</template>
<script setup>
import {reactive,ref} from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import { ElMessageBox,ElMessage } from "element-plus";

// 定义数据对象 , 页面渲染的数据都在这里面定义
// 定义表单验证规则 触发表单验证事件 , 提交表单事件 , 表单验证规则等都在这里面定义
const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
	username: null,
	name: null,
	pageNum: 1,
	pageSize: 5,
	total: 6,
	tableData: [],
	FormVisible: false,
	form: {},
	rules: {
		username: [
			{required: true, message: '请输入账号', trigger: 'blur'},
			{min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur'}],
		name: [
			{required: true, message: '请输入名称', trigger: 'blur'},
			{min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur'}],
	
		phone: [
			{required: true, message: '请输入电话', trigger: 'blur'},
			{min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur'}],
	
		email: [
			{required: true, message: '请输入邮箱', trigger: 'blur'},
			{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']},
			{min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur'},
			{pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
		]
	},
	rows: [],
	ids: []
})

// 定义表单引用
const formRef = ref()

// 分页查询 拿到后端接口数据(后端给前端返回的数据, 赋值给前端页面展示的数据)
const load = () => {
	request.get('/admin/selectPage',{
		params: {
			pageNum: data.pageNum,
			pageSize: data.pageSize,
			username: data.username,
			name: data.name
		}
	}).then(res => {
		if (res.code === '200') {
			data.tableData = res.data.list
			data.total = res.data.total
		} else {
			ElMessage.error(res.msg)
		}
	})
}

load()

// 重置查询条件
const reset = () => {
	data.username = null
	data.name = null
	load()
}

// 新增管理员信息弹窗展示
const handleAdd = () => {
	data.FormVisible = true
	data.form = {}
}

// 提交表单验证

// 新增管理员信息
const add = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.post('/admin/add', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('新增成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			})
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

const handleEdit = (row) => {
	// data.form = row // 浅拷贝,修改原始数据
	data.form = JSON.parse(JSON.stringify(row)) // 深拷贝,防止修改原始数据
	data.FormVisible = true
}

// 修改管理员信息 前端调用后端接口 修改管理员信息 并刷新页面数据 刷新页面数据有两种方式 
// 一种是通过前端调用后端接口 直接修改数据库中的数据 然后重新查询一遍数据 
// 另一种是直接通过前端调用后端接口 直接修改数据库中的数据 但是不重新查询一遍数据 
// 直接在前端更新数据 这样效率更高 因为不需要重新请求服务器 
const update = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.put('/admin/update', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('修改成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			}) 
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

// 保存按钮点击事件,根据表单中的id判断是新增还是修改操作 一个方法同时兼容两种方法
const save = () => {
	data.form.id ? update() : add()
}

// 删除管理员信息 前端调用后端接口 删除管理员信息 并刷新页面数据
const del = (id) => {
	ElMessageBox.confirm('删除后无法恢复,你确认要删除该行数据吗?','删除确认',{type: 'warning'}).then(res => {
		request.delete('/admin/delete/' + id).then(res => {
			if (res.code === '200') {
				ElMessage.success('删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 批量删除管理员信息 前端调用后端接口 批量删除管理员信息 并刷新页面数据
const handleSelectionChange = (rows) => { // rows 就是实际选择的数组 选中项发生变化时触发
	// data.rows就等于传进来的rows 选中项发生变化时触发
	data.rows = rows
	// map可以把对象的数组 转换成 一个纯数字的数组 [1,2,3]
	data.ids = data.rows.map(v => v.id)
}

const deleteBatch = () => {
	if (data.rows.length === 0) {
		ElMessage.warning('请选择数据')
		return 
	}
	ElMessageBox.confirm('删除后无法恢复,您确认删除吗?',{type: 'warning'}).then(res => {
		request.delete('/admin/deleteBatch', {data: data.rows}).then(res => {
			if (res.code === '200') {
				ElMessage.success('批量删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 可以全量导出数据也可以条件导出数据和选择导出数据
const exportData = () => {
	// 把数组 转换成一个字符串 逗号分隔的字符串 比如 [1,2,3] => '1,2,3'
	let idsStr = data.ids.join(',')
	// 动态拼接url地址 导出数据 导出数据有两种方式 一种是通过前端调用后端接口 
	// 直接导出数据 一种是通过前端调用后端接口 直接下载文件 
	// ES6 模板字符串拼接url地址 动态拼接url地址 语法:`${变量}` 动态拼接url地址
	let url = `http://localhost:9999/admin/export?username=${data.username === null ? '' : data.username}&name=${data.name === null ? '' : data.name}&ids=${idsStr}&token=${data.user.token}`	
	window.open(url)
}

// 加入判断 导入成功之后提示函数
const handleImportSuccess = (res) => {
	if (res.code === '200') {
		ElMessage.success('批量导入数据成功')
		load()
	} else {
		ElMessage.error(res.msg)
	}
}

// 头像上传成功之后 把返回的图片地址赋值给表单中的avatar字段 用于更新用户信息
const handleFileSuccess = (res) => {
	data.form.avatar = res.data	
}
</script>




4, src/views/User.vue
<template>
	<div>
		<div class="card" style="margin-bottom: 5px">
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.username" placeholder="请输入用户名查询" :prefix-icon="Search"></el-input>
			<el-input clearable @click="load" style="width: 260px;margin-right: 5px" v-model="data.name" placeholder="请输入名称查询" :prefix-icon="Search"></el-input>
			<el-button type="primary" @click="load">查 询</el-button>
			<el-button @click="reset">重 置</el-button>
		</div>
		<div class="card" style="margin-bottom: 5px">
			<el-button type="primary" @click="handleAdd">新增</el-button>
			<el-button type="danger" @click="deleteBatch">批量删除</el-button>
			<el-button type="info" @click="exportData">批量导出</el-button>
			<el-upload 
				style="display: inline-block;margin-left: 10px;"
				action="http://localhost:9999/admin/import"
				:show-file-list="false"
				:on-success="handleImportSuccess"
			>
				<el-button type="success">批量导入</el-button>
			</el-upload>			
		</div>

		<div class="card" style="margin-bottom: 5px">
			<el-table :data="data.tableData" style="width: 100%" @selection-change="handleSelectionChange" :header-cell-style="{color: '#333', backgroundColor: 'eaf4ff'}">
				<el-table-column type="selection" width="55" />
				<el-table-column label="头像" width="100">
					<template #default="scope">
						<el-image v-if="scope.row.avatar" :src="scope.row.avatar" :preview-src-list="[scope.row.avatar]" :preview-teleported="true" style="width: 40px;height: 40px;border-radius: 50%;display: block;" />
					</template>
				</el-table-column>
				<el-table-column prop="username" label="账号" width="188" />
				<el-table-column prop="name" label="名称" width="188" />
				<el-table-column prop="phone" label="电话" width="188" />
				<el-table-column prop="email" label="邮箱" width="222" />
				<el-table-column label="操作" width="100">
					<template #default="scope">
						<el-button type="primary" icon="Edit" circle @click="handleEdit(scope.row)"></el-button>
						<el-button type="danger" icon="Delete" circle @click="del(scope.row.id)"></el-button>
					</template>
				</el-table-column>
			</el-table>
		</div>
		<div class="card">
			<el-pagination 
                v-model:current-page="data.pageNum" 
				v-model:page-size="data.pageSize" 
                layout="total, sizes, prev, pager, next, jumper" 
				:page-sizes="[5, 10, 20]"
                :total="data.total"
				@current-change="load"
				@size-change="load"
            />
		</div>
		<!--弹窗开始-->
		<el-dialog title="普通用户信息" v-model="data.FormVisible" width="40%" destroy-on-close>
			<el-form ref="formRef" :model="data.form" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
				<el-form-item prop="username" label="账号">
					<el-input v-model="data.form.username" autocomplete="off" placeholder="请输入账号" />
				</el-form-item>
				<el-form-item prop="name" label="名称">
					<el-input v-model="data.form.name" autocomplete="off" placeholder="请输入名称" />
				</el-form-item>
				<el-form-item prop="phone" label="电话">
					<el-input v-model="data.form.phone" autocomplete="off" placeholder="请输入电话" />
				</el-form-item>
				<el-form-item prop="email" label="邮箱">
					<el-input v-model="data.form.email" autocomplete="off" placeholder="请输入邮箱" />
				</el-form-item>
				<el-form-item prop="avatar" label="头像">
					<el-upload 
						action="http://localhost:9999/files/upload"
						:headers="{token: data.user.token}"
						:on-success="handleFileSuccess"
						list-type="picture"
						>
						<el-button type="primary">上传头像</el-button>
					</el-upload>
				</el-form-item>
			</el-form>
			<template #footer>
				<div class="dialog-footer">
					<el-button @click="data.FormVisible = false">取消</el-button>
					<el-button type="primary" @click="save">保存</el-button>
				</div>
			</template>
		</el-dialog>
		<!--弹窗结束-->
	</div>
</template>
<script setup>
import {reactive,ref} from "vue";
import {Search} from "@element-plus/icons-vue";
import request from "@/utils/request.js";
import { ElMessageBox,ElMessage } from "element-plus";

// 定义数据对象 , 页面渲染的数据都在这里面定义
// 定义表单验证规则 触发表单验证事件 , 提交表单事件 , 表单验证规则等都在这里面定义
const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
	username: null,
	name: null,
	pageNum: 1,
	pageSize: 5,
	total: 6,
	tableData: [],
	FormVisible: false,
	form: {},
	rules: {
		username: [
			{required: true, message: '请输入账号', trigger: 'blur'},
			{min: 2, max: 15, message: '长度在 2 到 15 个字符', trigger: 'blur'}],
		name: [
			{required: true, message: '请输入名称', trigger: 'blur'},
			{min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur'}],
	
		phone: [
			{required: true, message: '请输入电话', trigger: 'blur'},
			{min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur'}],
	
		email: [
			{required: true, message: '请输入邮箱', trigger: 'blur'},
			{type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change']},
			{min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur'},
			{pattern: /^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/, message: '请输入正确的邮箱地址', trigger: ['blur', 'change']}
		]
	},
	rows: [],
	ids: []
})

// 定义表单引用
const formRef = ref()

// 分页查询 拿到后端接口数据(后端给前端返回的数据, 赋值给前端页面展示的数据)
const load = () => {
	request.get('/user/selectPage',{
		params: {
			pageNum: data.pageNum,
			pageSize: data.pageSize,
			username: data.username,
			name: data.name
		}
	}).then(res => {
		if (res.code === '200') {
			data.tableData = res.data.list
			data.total = res.data.total
		} else {
			ElMessage.error(res.msg)
		}
	})
}

load()

// 重置查询条件
const reset = () => {
	data.username = null
	data.name = null
	load()
}

// 新增管理员信息弹窗展示
const handleAdd = () => {
	data.FormVisible = true
	data.form = {}
}

// 提交表单验证

// 新增管理员信息
const add = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.post('/user/add', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('新增成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			})
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

const handleEdit = (row) => {
	// data.form = row // 浅拷贝,修改原始数据
	data.form = JSON.parse(JSON.stringify(row)) // 深拷贝,防止修改原始数据
	data.FormVisible = true
}

// 修改管理员信息 前端调用后端接口 修改管理员信息 并刷新页面数据 刷新页面数据有两种方式 
// 一种是通过前端调用后端接口 直接修改数据库中的数据 然后重新查询一遍数据 
// 另一种是直接通过前端调用后端接口 直接修改数据库中的数据 但是不重新查询一遍数据 
// 直接在前端更新数据 这样效率更高 因为不需要重新请求服务器 
const update = () => {
	// formRef 是表单的引用,通过它来触发表单验证方法 validate(),该方法会触发表单验证规则。
	formRef.value.validate((valid) => {
		if (valid) { // 表单验证通过时执行以下代码
			request.put('/user/update', data.form).then(res => {
				if (res.code === '200') {
					ElMessage.success('修改成功')
					data.FormVisible = false
					load()
				} else {
					ElMessage.error(res.msg)
				}
			}) 
		} else { // 表单验证不通过时执行以下代码
			return false; // 阻止表单提交,并显示错误信息
		}
	})	
}

// 保存按钮点击事件,根据表单中的id判断是新增还是修改操作 一个方法同时兼容两种方法
const save = () => {
	data.form.id ? update() : add()
}

// 删除管理员信息 前端调用后端接口 删除管理员信息 并刷新页面数据
const del = (id) => {
	ElMessageBox.confirm('删除后无法恢复,你确认要删除该行数据吗?','删除确认',{type: 'warning'}).then(res => {
		request.delete('/user/delete/' + id).then(res => {
			if (res.code === '200') {
				ElMessage.success('删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 批量删除管理员信息 前端调用后端接口 批量删除管理员信息 并刷新页面数据
const handleSelectionChange = (rows) => { // rows 就是实际选择的数组 选中项发生变化时触发
	// data.rows就等于传进来的rows 选中项发生变化时触发
	data.rows = rows
	// map可以把对象的数组 转换成 一个纯数字的数组 [1,2,3]
	data.ids = data.rows.map(v => v.id)
}

const deleteBatch = () => {
	if (data.rows.length === 0) {
		ElMessage.warning('请选择数据')
		return 
	}
	ElMessageBox.confirm('删除后无法恢复,您确认删除吗?',{type: 'warning'}).then(res => {
		request.delete('/user/deleteBatch', {data: data.rows}).then(res => {
			if (res.code === '200') {
				ElMessage.success('批量删除成功')
				load()
			} else {
				ElMessage.error(res.msg)
			}
		})
	}).catch(err => {
		ElMessage.info('已取消删除')
	})
}

// 可以全量导出数据也可以条件导出数据和选择导出数据
const exportData = () => {
	// 把数组 转换成一个字符串 逗号分隔的字符串 比如 [1,2,3] => '1,2,3'
	let idsStr = data.ids.join(',')
	// 动态拼接url地址 导出数据 导出数据有两种方式 一种是通过前端调用后端接口 
	// 直接导出数据 一种是通过前端调用后端接口 直接下载文件 
	// ES6 模板字符串拼接url地址 动态拼接url地址 语法:`${变量}` 动态拼接url地址
	let url = `http://localhost:9999/admin/export?username=${data.username === null ? '' : data.username}&name=${data.name === null ? '' : data.name}&ids=${idsStr}&token=${data.user.token}`	
	window.open(url)
	// let admin_url = `http://localhost:9999/admin/export?username=${data.username === null ? '' : data.username}&name=${data.name === null ? '' : data.name}&ids=${idsStr}&token=${data.user.token}`	
	// window.open(admin_url)
	// let user_url = `http://localhost:9999/user/export?username=${data.username === null ? '' : data.username}&name=${data.name === null ? '' : data.name}&ids=${idsStr}&token=${data.user.token}`	
	// window.open(user_url)
}

// 加入判断 导入成功之后提示函数
const handleImportSuccess = (res) => {
	if (res.code === '200') {
		ElMessage.success('批量导入数据成功')
		load()
	} else {
		ElMessage.error(res.msg)
	}
}

// 头像上传成功之后 把返回的图片地址赋值给表单中的avatar字段 用于更新用户信息
const handleFileSuccess = (res) => {
	data.form.avatar = res.data	
}
</script>




5, src/views/Person.vue
<template>
    <div class="card" style="width: 50%;">
        <div style="font-weight: 500;font-size: 20px;text-align: center;">个人信息</div>

        <el-form ref="formRef" :model="data.user" label-width="80px" style="padding: 20px 30px 10px 0;">
            <el-form-item prop="username" label="账号">
                <el-input size="large" v-model="data.user.username" autocomplete="off" placeholder="请输入账号" />
            </el-form-item>
            <el-form-item prop="name" label="名称">
                <el-input size="large" v-model="data.user.name" autocomplete="off" placeholder="请输入名称" />
            </el-form-item>
            <el-form-item prop="phone" label="电话">
                <el-input size="large" v-model="data.user.phone" autocomplete="off" placeholder="请输入电话" />
            </el-form-item>
            <el-form-item prop="email" label="邮箱">
                <el-input size="large" v-model="data.user.email" autocomplete="off" placeholder="请输入邮箱" />
            </el-form-item>
            <el-form-item prop="avatar" label="头像">
                <el-upload 
                    action="http://localhost:9999/files/upload"
                    :headers="{token: data.user.token}"
                    :on-success="handleFileSuccess"
                    list-type="picture"
                    >
                    <el-button type="primary">上传头像</el-button>
                </el-upload>
            </el-form-item>
        </el-form>
        <div style="text-align: center;">
            <el-button type="info"  style="padding: 20px 50px;" @click="data.FormVisible = false">取消</el-button>
            <el-button type="primary" style="padding: 20px 50px;" @click="update">保存</el-button>
        </div>
    </div>
</template>

<script setup>
import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
import { reactive} from 'vue'



const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
    FormVisible: false
})

const handleFileSuccess = (res) => {
    data.user.avatar = res.data;
}

// const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['updateUser'])


const update = () => {
    let url
    if (data.user.role = 'ADMIN') {
        url = '/admin/update'
    }
    if (data.user.role = 'USER') {
        url = '/user/update'
    }
    request.put(url,data.user).then(res => {
        if (res.code === '200') {
            ElMessage.success('更新成功')
            localStorage.setItem('code_user', JSON.stringify(data.user))
            emit('updateUser')
        }
    })
}

</script>

<style scoped>

</style>



6, src/views/UpdatePassword.vue
<template>
    <div class="card" style="width: 50%;">
        <div style="margin-bottom: 20px;text-align: center;font-weight: bold;font-size: 26px;">修 改 密 码</div>
        <el-form status-icon ref="formRef" :model="data.user" :rules="data.rules" label-width="80px" style="padding: 20px 30px 10px 0;">
            <el-form-item prop="password" label="原密码">
                <el-input show-password size="large" v-model="data.user.password" autocomplete="off" placeholder="请输入原密码" prefix-icon="Lock" />
            </el-form-item>
            <el-form-item prop="newPassword" label="新密码">
                <el-input show-password size="large" v-model="data.user.newPassword" autocomplete="off" placeholder="请输入新密码" prefix-icon="Lock" />
            </el-form-item>
            <el-form-item prop="confirmPassword" label="确认密码">
                <el-input show-password size="large" v-model="data.user.confirmPassword" autocomplete="off" placeholder="请再次输入新密码" prefix-icon="Lock" />
            </el-form-item>
        </el-form>
        <div style="text-align: center;">
            <el-button type="info"  style="padding: 20px 50px;" @click="data.FormVisible = false">取消</el-button>
            <el-button type="primary" style="padding: 20px 50px;" @click="updatePassword">保存</el-button>
        </div>
    </div>
</template>

<script setup>
import request from "@/utils/request.js";
import { ElMessage } from "element-plus";
import { reactive, ref} from 'vue'

const data = reactive({
	user: JSON.parse(localStorage.getItem('code_user') || '{}'),
    rules: {
        password: [
            { required: true, message: '请输入原密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'}
        ],
        newPassword: [
            { required: true, message: '请输入新密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'}
        ],
        confirmPassword: [
            { required: true, message: '请再次输入密码', trigger: 'blur' },
            { min: 3, max: 15, message: '长度在 3 到 15 个字符', trigger: 'blur'},
        ]

    },
    FormVisible: false,
})

const formRef = ref()

// 修改密码方法
const updatePassword = () => {
    formRef.value.validate((valid) => {
        if (valid) {
            request.post('/updatePassword', data.user).then(res => {
                if (res.code === '200') {
                    ElMessage.success('修改成功')
                    // 延迟500毫秒后跳转登录页面,防止太快导致还没退出就进入登录界面了
                    setInterval(() => {
                        localStorage.removeItem('code_user')
                        location.href = '/login'
                    },500)
                } else {
                    ElMessage.error(res.msg)
                }
            })
        }
    })    
}

</script>

<style scoped>

</style>



7, src/views/Manager.vue
<template>
	<div>
		<!-- 头部区域开始 -->
		<div style="height: 60px;display: flex;">
			<div style="width: 240px;display: flex;align-items: center;padding-left: 20px;background-color: #3a456b;">
				<img style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo.png" alt="">
				<span style="font-size: 20px;font-weight: bold;color: #f1f1f1;margin-left: 5px;">隆迟民宿管理系统</span>
			</div>
			<div style="flex: 1;display: flex;align-items: center;padding-left: 20px;border-bottom: 1px solid #ddd;">
				<span style="margin-right: 5px; cursor: pointer;" @click="router.push('/manager/home')">首页</span>/<span style="margin-left: 5px;">{{ router.currentRoute.value.meta.title || '首页' }}</span>
			</div>
			<div style="width: fit-content;padding-right: 20px;display: flex;align-items: center;border-bottom: 1px solid #ddd;">
				<el-dropdown>
					<div style="display: flex;align-items: center;">
						<img v-if="data.user?.avatar" style="width: 40px;height: 40px;border-radius: 50%;" :src="data.user?.avatar" />
						<img v-else style="width: 40px;height: 40px;border-radius: 50%;" src="@/assets/imgs/logo1.png" alt="">
						<span style="margin-left: 10px;color: #333;">{{ data.user?.name }}</span>
					</div>
					<template #dropdown>
						<el-dropdown-menu>
							<el-dropdown-item @click="router.push('/manager/person')">个人信息</el-dropdown-item>
							<el-dropdown-item @click="router.push('/manager/updatePassword')">修改密码</el-dropdown-item>
							<el-dropdown-item @click="logout">退出登录</el-dropdown-item>
						</el-dropdown-menu>
					</template>
				</el-dropdown>
			</div>
		</div>
		<!-- 头部区域结束 -->
		<!--下方区域开始-->
		<div style="display: flex">
			<!-- 菜单区域开始 -->
			<div style="width: 240px;">
				<el-menu router :default-openeds="['1']" :default-active="router.currentRoute.value.path" style="min-height:calc(100vh)">
					<el-menu-item index="/manager/home">
						<el-icon><House /></el-icon>
						<span>首页</span>
					</el-menu-item>
					<el-sub-menu index="1">
						<template #title>
							<el-icon><Location /></el-icon>
							<span>用户管理</span>
						</template>
						<el-menu-item index="/manager/admin">管理员信息</el-menu-item>
						<el-menu-item index="/manager/user">普通用户信息</el-menu-item>
					</el-sub-menu>
				</el-menu>
			</div>
			<!-- 菜单区域结束 -->
			
			<!-- 数据渲染区域开始 -->
			<div style="flex: 1; width: 0; padding: 10px; background-color: #f2f4ff">
				<RouterView @updateUser = "updateUser" />
			</div>
			<!-- 数据渲染区域结束 -->
		</div>
		<!--下方区域结束-->
	</div>
</template>

<script setup>
	import router from '@/router/index.js'
    import { RouterView } from 'vue-router'
	import { reactive } from 'vue'

	const data = reactive({
		user: JSON.parse(localStorage.getItem('code_user') || '{}')
	})

	// 退出登录函数
	const logout = () => {
		localStorage.removeItem('code_user')
		location.href = '/login'
	}

	// 更新用户信息
	const updateUser = () => {
		data.user = JSON.parse(localStorage.getItem('code_user') || '{}')
	}


	// 这个判断有隐患 可以伪造去登录 直接去登录页面
	// if (!data.user?.id) {
	// 	location.href = '/login'
	// }

	// 判断是否登录,如果没有登录则跳转到登录页面
	// let userStr = localStorage.getItem('code_user')
	// if (userStr) {
	// 	let user = JSON.parse(userStr)
	// } else {
	// 	location.href = '/login'
	// }

</script>

<style scoped>
	.el-menu {
		background-color: #3a456b;
		border: none;
	}
	.el-sub-menu__title {
		background-color: #3a456b;
		color: #ddd;
	}
	.el-menu-item {
		height: 50px;
		color: #ddd;
	}
	.el-menu .is-active {
		background-color: #537bee;
		color: #fff;
	}
	.el-sub-menu__title:hover {
		background-color: #3a456b;
	}
	.el-menu-item:not(.is-active):hover {
		background-color: #7a9fff;
		color: #333;
	}
	.el-dropdown {
		cursor: pointer;
	}
	.el-tooltip__trigger {
		outline: none;
	}
	.el-menu--inline .el-menu-item {
		padding-left: 48px !important;
	}
</style> 





8, src/utils/request.js
import axios from "axios";
import {ElMessage} from "element-plus";
import router from "@/router/index.js"

const request = axios.create({
    baseURL: 'http://localhost:9999',
    timeout: 30000	//后台接口超时时间
})

// request 拦截器
// 可以自请求发送前对请求做一些处理
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    let user = JSON.parse(localStorage.getItem('code_user') || '{}');
    config.headers['token'] = user.token // 设置请求头
    return config
}, error => {
    return Promise.reject(error)
});

// response 拦截器
// 可以在接口响应后统一处理结果
request.interceptors.response.use(
    response => {
        let res = response.data;
        // 兼容服务器返回的字符串数据
        if (typeof res === 'string') {
            // 返回的数据不是标准的json格式,尝试转为json格式
            res = res ? JSON.parse(res) : res
        }
        if (res.code === 401) {
            ElMessage.error(res.msg)
            router.push('/login')
            // localStorage.removeItem('code_user') // 清除token
            // window.location.href = '/login' // 跳转到登录页面
        } else {
            return res;
        }       
    },
    error => {
        if (error.response.status === 404) {
            ElMessage.error('未找到请求接口')
        } else if (error.response.status === 500) {
            ElMessage.error('系统异常,请查看后端控制台报错')
        } else {
            console.error(error.message)
        }
        return Promise.reject(error)
    }
)

export default request




9, src/router/index.js
import {createRouter, createWebHistory} from 'vue-router'

const router = createRouter({
    history: createWebHistory(import.meta.env.BASE_URL),
    routes: [
        {path: '/', redirect: '/manager/home'},
        {
            path: '/manager', component: () => import('@/views/Manager.vue'),
            children: [
                {path: 'home', meta: {title: '主页'}, component: () => import('@/views/Home.vue'),},
                {path: 'admin', meta: {title: '管理员信息'}, component: () => import('@/views/Admin.vue'),},
                {path: 'user', meta: {title: '普通用户信息'}, component: () => import('@/views/User.vue'),},
                {path: 'person', meta: {title: '个人信息'}, component: () => import('@/views/Person.vue'),},
                {path: 'updatePassword', meta: {title: '修改密码'}, component: () => import('@/views/UpdatePassword.vue'),},
            ]
        },
        {path: '/login', component: () => import('@/views/Login.vue'),},
        {path: '/register', component: () => import('@/views/Register.vue'),},
        {path: '/notFound', component: () => import('@/views/404.vue'),},
        {path: '/:pathMatch(.*)*', redirect: '/notFound'},
    ],
})

export default router






10, controller/AdminController.java
package com.longchi.controller;


import com.github.pagehelper.PageInfo;
import com.longchi.common.Result;
import com.longchi.entity.Admin;
import com.longchi.service.AdminService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;

import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import java.util.List;

/**
 * @Description com.longchi.controller
 * @Author zengguoqing
 * @Date 2026-03-18 21:27
 **/

// 暴露查询接口
@RestController
@RequestMapping("/admin") // 添加统一前缀
public class AdminController {

    @Resource
    AdminService adminService;


    @PostMapping("/add")
    public Result add(@RequestBody Admin admin) { // @RequestBody 接收前端传来的 json 参数
        adminService.add(admin);
        return Result.success();
    }

    @PutMapping("/update")
    public Result update(@RequestBody Admin admin) { // @RequestBody 接收前端传来的 json 参数
        adminService.update(admin);
        return Result.success();
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Integer id) { // @PathVariable 接收前端传来的路径参数
        adminService.deleteById(id);
        return  Result.success();
    }

    @DeleteMapping("/deleteBatch")
    public Result deleteBatch(@RequestBody List<Admin> list) { // @RequestBody 接收前端传来的 json 数组
        adminService.deleteBatch(list);
        return  Result.success();
    }

    @GetMapping("/selectAll") // 完整的请求路径 http://ip:port/admin/selectAll
    public Result selectAll(Admin admin) {
        List<Admin> adminList = adminService.selectAll(admin);
        return Result.success(adminList);
    }

    /**
     * 分页查询
     * pageNum: 当前的页码
     * pageSize: 每页的个数
     * */
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, Admin admin) {
        PageInfo<Admin> pageInfo = adminService.selectPage(pageNum, pageSize, admin);
        return Result.success(pageInfo);
        // 返回的分页的对象
    }

    /**
     * 实现数据全量导出
     * ids: 1,2,3  前端是不能直接传数字,我们需要将前端的id拼接一下然后传递
     * 默认的,未添加alias的属性也会写出,实现数据全量导出,如果想只写出加了别名的字段,可以调用此方法 writer.setOnlyAlias(true);排除之
     * writer.setOnlyAlias(true);
     * */
    @GetMapping("/export")
    public void exportData(Admin admin, HttpServletResponse response) throws IOException {
        // 后端拿到 ids
        String ids = admin.getIds();
        if (StrUtil.isNotBlank(ids)) {
            String[] idsArr = ids.split(",");
            admin.setIdsArr(idsArr);
        }
        // 1, 拿到所有的数据
        List<Admin> list = adminService.selectAll(admin);
        // 2, 构建 Writer 对象
        ExcelWriter writer = ExcelUtil.getWriter(true);
        // 3, 设置中文的表头
        // 先写属性 比如:'username', 再写表头 比如:'账号', 代码如下
        writer.addHeaderAlias("username","账号");
        writer.addHeaderAlias("name","名称");
        writer.addHeaderAlias("phone","电话");
        writer.addHeaderAlias("email","邮箱");
        writer.addHeaderAlias("role","角色");
        // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);
        // 4, 写出数据到 writer
        writer.write(list);
        // 5, 设置输出文件的名称以及输出流的头信息
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("管理员信息", StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
        // 6, 写出到输出流,并关闭 writer
        ServletOutputStream os = response.getOutputStream();
        // 通过writer将os输出到os里面去
        writer.flush(os);
        // 关闭
        writer.close();
        os.close();
    }


    /**
     * 批量导入
     * */
    @PostMapping("/import")
    public Result importData(MultipartFile file) throws IOException {
        // 1, 获取到输入流,构建 reader
        InputStream inputStream = file.getInputStream();
        // file.getInputStream();
        // 构建 reader
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        // 2, 通过 reader 读取 excel 里面的数据
        // 先写表头 比如:'账号',再写属性 比如:'username' 代码如下
        reader.addHeaderAlias("账号","username");
        reader.addHeaderAlias("名称","name");
        reader.addHeaderAlias("电话","phone");
        reader.addHeaderAlias("邮箱","email");
        reader.addHeaderAlias("角色","role");
        // 拿到的数据转化为 Admin 集合
        List<Admin> list = reader.readAll(Admin.class);
        // 使用iter快捷键写循环 再将 Admin 集合数据写入到数据库中
        for (Admin admin : list) {
            adminService.add(admin);
        }
        return Result.success();
    }
}





11, controller/UserController.java
package com.longchi.controller;

import com.github.pagehelper.PageInfo;
import com.longchi.common.Result;
import com.longchi.entity.User;
import com.longchi.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.*;

import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;

import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import java.util.List;

/**
 * @Description com.longchi.controller
 * @Author zengguoqing
 * @Date 2026-03-18 21:27
 **/

// 暴露查询接口
@RestController
@RequestMapping("/user") // 添加统一前缀
public class UserController {

    @Resource
    UserService userService;


    @PostMapping("/add")
    public Result add(@RequestBody User user) { // @RequestBody 接收前端传来的 json 参数
        userService.add(user);
        return Result.success();
    }

    @PutMapping("/update")
    public Result update(@RequestBody User user) { // @RequestBody 接收前端传来的 json 参数
        userService.update(user);
        return Result.success();
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable Integer id) { // @PathVariable 接收前端传来的路径参数
        userService.deleteById(id);
        return  Result.success();
    }

    @DeleteMapping("/deleteBatch")
    public Result deleteBatch(@RequestBody List<User> list) { // @RequestBody 接收前端传来的 json 数组
        userService.deleteBatch(list);
        return  Result.success();
    }

    @GetMapping("/selectAll") // 完整的请求路径 http://ip:port/admin/selectAll
    public Result selectAll(User user) {
        List<User> userList = userService.selectAll(user);
        return Result.success(userList);
    }

    /**
     * 分页查询
     * pageNum: 当前的页码
     * pageSize: 每页的个数
     * */
    @GetMapping("/selectPage")
    public Result selectPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, User user) {
        PageInfo<User> pageInfo = userService.selectPage(pageNum, pageSize, user);
        return Result.success(pageInfo);
        // 返回的分页的对象
    }

    /**
     * 实现数据全量导出
     * ids: 1,2,3  前端是不能直接传数字,我们需要将前端的id拼接一下然后传递
     * 默认的,未添加alias的属性也会写出,实现数据全量导出,如果想只写出加了别名的字段,可以调用此方法 writer.setOnlyAlias(true);排除之
     * writer.setOnlyAlias(true);
     * */
    @GetMapping("/export")
    public void exportData(User user, HttpServletResponse response) throws IOException {
        // 后端拿到 ids
        String ids = user.getIds();
        if (StrUtil.isNotBlank(ids)) {
            String[] idsArr = ids.split(",");
            user.setIdsArr(idsArr);
        }
        // 1, 拿到所有的数据
        List<User> list = userService.selectAll(user);
        // 2, 构建 Writer 对象
        ExcelWriter writer = ExcelUtil.getWriter(true);
        // 3, 设置中文的表头
        // 先写属性 比如:'username', 再写表头 比如:'账号', 代码如下
        writer.addHeaderAlias("username","账号");
        writer.addHeaderAlias("name","名称");
        writer.addHeaderAlias("phone","电话");
        writer.addHeaderAlias("email","邮箱");
        writer.addHeaderAlias("role","角色");
        // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);
        // 4, 写出数据到 writer
        writer.write(list);
        // 5, 设置输出文件的名称以及输出流的头信息
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("普通用户信息", StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
        // 6, 写出到输出流,并关闭 writer
        ServletOutputStream os = response.getOutputStream();
        // 通过writer将os输出到os里面去
        writer.flush(os);
        // 关闭
        writer.close();
        os.close();
    }




    /**
     * 批量导入
     * */
    @PostMapping("/import")
    public Result importData(MultipartFile file) throws IOException {
        // 1, 获取到输入流,构建 reader
        InputStream inputStream = file.getInputStream();
        // file.getInputStream();
        // 构建 reader
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        // 2, 通过 reader 读取 excel 里面的数据
        // 先写表头 比如:'账号',再写属性 比如:'username' 代码如下
        reader.addHeaderAlias("账号","username");
        reader.addHeaderAlias("名称","name");
        reader.addHeaderAlias("电话","phone");
        reader.addHeaderAlias("邮箱","email");
        reader.addHeaderAlias("角色","role");
        // 拿到的数据转化为 User 集合
        List<User> list = reader.readAll(User.class);
        // 使用iter快捷键写循环 再将 User 集合数据写入到数据库中
        for (User user : list) {
            userService.add(user);
        }
        return Result.success();
    }
}







12, controller/WebController.java
package com.longchi.controller;

import com.longchi.common.Result;
import com.longchi.entity.Account;
import com.longchi.entity.User;
import com.longchi.exception.CustomerException;
import com.longchi.service.AdminService;
import com.longchi.service.UserService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;


@RestController
public class WebController {
    // 将 @Resource 注入 Service
    @Resource
    AdminService adminService;

    @Resource
    UserService userService;



    // 表示这是一个get请求的接口
    @GetMapping("/hello")    // 接口的路径 路由名和路径是全局唯一的
    public Result hello() {
        return Result.success("hello王哥哥");
        // HashMap<Object, Object> map = new HashMap<>();
        // map.put("name", "青哥哥");
        // return Result.success(map);
    }

    // 表示这是一个get请求的接口
    @GetMapping("/admin")
    public Result admin(String name) {
        String admin = adminService.admin(name);
        return Result.success(admin);
    }

    /**
     * 实现管理员与普通用户共同登录接口
     * */
    @PostMapping("/login")
    public Result login(@RequestBody Account account) {
        Account dbAccount = null;
        if ("ADMIN".equals(account.getRole())) {
            dbAccount = adminService.login(account);
        } else if ("USER".equals(account.getRole())) {
            dbAccount = userService.login(account);
        } else {
            throw new CustomerException("非法请求");
        }
        return Result.success(dbAccount);
    }

    /**
     * 注册接口
     * */
    @PostMapping("/register")
    public Result register(@RequestBody User user) {
        userService.register(user);
        return Result.success();
    }

    /**
     * 修改密码接口
     * */
    @PostMapping("/updatePassword")
    public Result updatePassword(@RequestBody Account account) {
        // System.out.println(""); 打端点 调试
        // 判断用户角色
        if ("ADMIN".equals(account.getRole())) {
            adminService.updatePassword(account);
        }
        if ("USER".equals(account.getRole())) {
            userService.updatePassword(account);
        }
        return Result.success();
    }

    /**
     * 实现管理员与普通用户共同批量导出接口
     * */

    /**
    @GetMapping("/export")
    public void exportData(Account account, HttpServletResponse response) throws IOException {
        // 后端拿到 ids
        String ids = account.getIds();
        if (StrUtil.isNotBlank(ids)) {
            String[] idsArr = ids.split(",");
            account.setIdsArr(idsArr);
        }
        // 1, 拿到所有的数据
        List<Admin> list = adminService.selectAll(account);
        // 2, 构建 Writer 对象
        ExcelWriter writer = ExcelUtil.getWriter(true);
        // 3, 设置中文的表头
        // 先写属性 比如:'username', 再写表头 比如:'账号', 代码如下
        writer.addHeaderAlias("username","账号");
        writer.addHeaderAlias("name","名称");
        writer.addHeaderAlias("phone","电话");
        writer.addHeaderAlias("email","邮箱");
        writer.addHeaderAlias("role","角色");
        // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之
        writer.setOnlyAlias(true);
        // 4, 写出数据到 writer
        writer.write(list);
        // 5, 设置输出文件的名称以及输出流的头信息
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
        String fileName = URLEncoder.encode("管理员信息", StandardCharsets.UTF_8);
        response.setHeader("Content-Disposition","attachment;filename="+fileName+".xlsx");
        // 6, 写出到输出流,并关闭 writer
        ServletOutputStream os = response.getOutputStream();
        // 通过writer将os输出到os里面去
        writer.flush(os);
        // 关闭
        writer.close();
        os.close();
    }
     */


    /**
     * 实现管理员与普通用户共同批量导入接口
     * */

    /**
    @PostMapping("/import")
    public Result importData(MultipartFile file) throws IOException {
        // 1, 获取到输入流,构建 reader
        InputStream inputStream = file.getInputStream();
        // file.getInputStream();
        // 构建 reader
        ExcelReader reader = ExcelUtil.getReader(inputStream);
        // 2, 通过 reader 读取 excel 里面的数据
        // 先写表头 比如:'账号',再写属性 比如:'username' 代码如下
        reader.addHeaderAlias("账号","username");
        reader.addHeaderAlias("名称","name");
        reader.addHeaderAlias("电话","phone");
        reader.addHeaderAlias("邮箱","email");
        reader.addHeaderAlias("角色","role");
        // 拿到的数据转化为 Admin 集合
        List<Account> list = reader.readAll(Account.class);
        // 使用iter快捷键写循环 再将 Admin 集合数据写入到数据库中
        for (Account account : list) {
            AdminService.add((Admin) account);
        }
        return Result.success();
    }
     */

    /**
    @PostMapping("/login")
    public Result login(@RequestBody Admin admin) {
        Admin dbAdmin = adminService.login(admin);
        // 返回给前端的数据 dbAdmin
        return Result.success(dbAdmin);
    }
     */
}





12, controller/FileController.java
package com.longchi.controller;

/**
 * @author Administrator
 */
import cn.hutool.core.io.FileUtil;
import com.longchi.common.Result;
import com.longchi.exception.CustomerException;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletOutputStream;
import org.springframework.web.multipart.MultipartFile;

/**
 * 处理文件上传下载相关的接口
 * 为什么使用 void ,因为我们使用流的形式下载的,不需要返回 Result
 * download() 方法里面是一个路径参数
 * 编程的思路: 当你拥有一个详细的思路,你就知道该如何去做了
 * @author Administrator
 * */
@RestController
@RequestMapping("/files")
public class FileController {
    /**
     * 文件上传
     * */
    @PostMapping("/upload")
    public Result upload(@RequestParam("file") MultipartFile file) throws IOException {
        // 要明确文件上传的位置
        String filePath = System.getProperty("user.dir") + "/files/";
        // 判断
        if (!FileUtil.isDirectory(filePath)) {
            // 创建文件夹
            FileUtil.mkdir(filePath);
        }
        // 拿到 bytes 数组
        byte[] bytes = file.getBytes();
        // 拿到文件的原始名称且防止文件相互覆盖,做文件名唯一性处理 当前的唯一值
        // 使用currentTimeMillis()方法也可以达到随机数的效果
        String fileName = System.currentTimeMillis() + "-" + file.getOriginalFilename();
        // 编码规范 对原始文件进行编码
        // String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
        // 直接写入文件
        // FileUtil.writeBytes(bytes,filePath+encodeFileName);
        FileUtil.writeBytes(bytes,filePath+fileName);
        String url = "http://localhost:9999/files/download/"+fileName;
        return Result.success(url);
    }

    /**
     * 文件下载接口
     * 下载一定是get请求,去浏览器输入网址回车就可以实现下载了
     * 下载路径: "http://localhost:9999/files/download/404.jpg","http://localhost:9999/files/download/game.png"
     */
    @GetMapping("/download/{fileName}")
    public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {

        // 找到文件的位置,我们如何去获取files文件夹的位置
        // java 里面有一个获取系统变量的方法 System.getProperty()
        // 获取当前项目的根路径 比如老师的 获取到 code2025 的绝对路径 ,自己的 获取到 longchi 的绝对路径 D:\items\longchi
        // 拿到项目里面 files 的完整路径
        String filePath = System.getProperty("user.dir") + "/files/";
        // 在 windown 里面 '/' 与 '\' 是不区分的,都可以使用 反斜杠 '\' 必须写两个 '\\'
        // 我们去下载 404.jpg 文件,是通过文件的具体路径或者是磁盘的路径去下载
        // 找到文件的位置,我们需要将文件拿出来 最终路径 D:\items\longchi\files\404.jpg
        String realPath = filePath + fileName;
        // 判断文件是否存在
        boolean exist = FileUtil.exist(realPath);
        if (!exist) {
            throw new CustomerException("文件不存在");
        }
        // 读取文件的字节流
        byte[] bytes = FileUtil.readBytes(realPath);
        // response.addHeader();
        ServletOutputStream os = response.getOutputStream();
        // 以附件的形式下载
        // response.addHeader("Content-Disposition","attachment;filename="=URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        // response.setContentType("application/octet.stream");
        // 输出流对象,把文件写出到客户端(浏览器)
        os.write(bytes);
        os.flush();
        os.close();
    }
}



12, controller/FileController.java
package com.longchi.controller;

/**
 * @author Administrator
 */
import cn.hutool.core.io.FileUtil;
import com.longchi.common.Result;
import com.longchi.exception.CustomerException;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;

import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.ServletOutputStream;
import org.springframework.web.multipart.MultipartFile;

/**
 * 处理文件上传下载相关的接口
 * 为什么使用 void ,因为我们使用流的形式下载的,不需要返回 Result
 * download() 方法里面是一个路径参数
 * 编程的思路: 当你拥有一个详细的思路,你就知道该如何去做了
 * @author Administrator
 * */
@RestController
@RequestMapping("/files")
public class FileController {
    /**
     * 文件上传
     * */
    @PostMapping("/upload")
    public Result upload(@RequestParam("file") MultipartFile file) throws IOException {
        // 要明确文件上传的位置
        String filePath = System.getProperty("user.dir") + "/files/";
        // 判断
        if (!FileUtil.isDirectory(filePath)) {
            // 创建文件夹
            FileUtil.mkdir(filePath);
        }
        // 拿到 bytes 数组
        byte[] bytes = file.getBytes();
        // 拿到文件的原始名称且防止文件相互覆盖,做文件名唯一性处理 当前的唯一值
        // 使用currentTimeMillis()方法也可以达到随机数的效果
        String fileName = System.currentTimeMillis() + "-" + file.getOriginalFilename();
        // 编码规范 对原始文件进行编码
        // String encodeFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
        // 直接写入文件
        // FileUtil.writeBytes(bytes,filePath+encodeFileName);
        FileUtil.writeBytes(bytes,filePath+fileName);
        String url = "http://localhost:9999/files/download/"+fileName;
        return Result.success(url);
    }

    /**
     * 文件下载接口
     * 下载一定是get请求,去浏览器输入网址回车就可以实现下载了
     * 下载路径: "http://localhost:9999/files/download/404.jpg","http://localhost:9999/files/download/game.png"
     */
    @GetMapping("/download/{fileName}")
    public void download(@PathVariable String fileName, HttpServletResponse response) throws IOException {

        // 找到文件的位置,我们如何去获取files文件夹的位置
        // java 里面有一个获取系统变量的方法 System.getProperty()
        // 获取当前项目的根路径 比如老师的 获取到 code2025 的绝对路径 ,自己的 获取到 longchi 的绝对路径 D:\items\longchi
        // 拿到项目里面 files 的完整路径
        String filePath = System.getProperty("user.dir") + "/files/";
        // 在 windown 里面 '/' 与 '\' 是不区分的,都可以使用 反斜杠 '\' 必须写两个 '\\'
        // 我们去下载 404.jpg 文件,是通过文件的具体路径或者是磁盘的路径去下载
        // 找到文件的位置,我们需要将文件拿出来 最终路径 D:\items\longchi\files\404.jpg
        String realPath = filePath + fileName;
        // 判断文件是否存在
        boolean exist = FileUtil.exist(realPath);
        if (!exist) {
            throw new CustomerException("文件不存在");
        }
        // 读取文件的字节流
        byte[] bytes = FileUtil.readBytes(realPath);
        // response.addHeader();
        ServletOutputStream os = response.getOutputStream();
        // 以附件的形式下载
        // response.addHeader("Content-Disposition","attachment;filename="=URLEncoder.encode(fileName, StandardCharsets.UTF_8));
        // response.setContentType("application/octet.stream");
        // 输出流对象,把文件写出到客户端(浏览器)
        os.write(bytes);
        os.flush();
        os.close();
    }
}







13, entity/Account.java
package com.longchi.entity;

/**
 * 父类
 * @author Administrator
 */
public class Account {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private String phone;
    private String email;
    private String role;
    private String token;
    private String avatar;
    private String newPassword;
    private String confirmPassword;

    private String ids;
    private String[] idsArr;

    public String getIds() {
        return ids;
    }

    public void setIds(String ids) {
        this.ids = ids;
    }

    public String[] getIdsArr() {
        return idsArr;
    }

    public void setIdsArr(String[] idsArr) {
        this.idsArr = idsArr;
    }

    public String getNewPassword() {
        return newPassword;
    }

    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

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

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}






14, entity/Admin.java
package com.longchi.entity;

/**
 * 管理员信息
 * 子类
 * @author Administrator
 */
public class Admin extends Account {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private String phone;
    private String email;
    private String role;
    private String token;
    private String avatar;
    private String newPassword;
    private String confirmPassword;


    // 非数据库字段
    private String ids;
    private String[] idsArr;

    @Override
    public String getNewPassword() {
        return newPassword;
    }

    @Override
    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }

    @Override
    public String getConfirmPassword() {
        return confirmPassword;
    }

    @Override
    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    @Override
    public String getAvatar() {
        return avatar;
    }

    @Override
    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    @Override
    public String getToken() {
        return token;
    }

    @Override
    public void setToken(String token) {
        this.token = token;
    }

    @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;
    }

    @Override
    public String getPhone() {
        return phone;
    }

    @Override
    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String getEmail() {
        return email;
    }

    @Override
    public void setEmail(String email) {
        this.email = email;
    }

    @Override
    public String getRole() {
        return role;
    }

    @Override
    public void setRole(String role) {
        this.role = role;
    }

    @Override
    public String getIds() {
        return ids;
    }

    @Override
    public void setIds(String ids) {
        this.ids = ids;
    }

    @Override
    public String[] getIdsArr() {
        return idsArr;
    }

    @Override
    public void setIdsArr(String[] idsArr) {
        this.idsArr = idsArr;
    }
}





15, entity/User.java
package com.longchi.entity;

/**
 * 用户信息
 * 子类
 * @author Administrator
 */
public class User extends Account {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private String phone;
    private String email;
    private String role;
    private String token;
    private String avatar;
    private String newPassword;
    private String confirmPassword;

    // 非数据库字段
    private String ids;
    private String[] idsArr;

    @Override
    public String getNewPassword() {
        return newPassword;
    }

    @Override
    public void setNewPassword(String newPassword) {
        this.newPassword = newPassword;
    }

    @Override
    public String getConfirmPassword() {
        return confirmPassword;
    }

    @Override
    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }

    @Override
    public String getAvatar() {
        return avatar;
    }

    @Override
    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    @Override
    public String getToken() {
        return token;
    }

    @Override
    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public String getRole() {
        return role;
    }

    @Override
    public void setRole(String role) {
        this.role = role;
    }

    @Override
    public String[] getIdsArr() {
        return idsArr;
    }

    @Override
    public void setIdsArr(String[] idsArr) {
        this.idsArr = idsArr;
    }

    @Override
    public String getIds() {
        return ids;
    }

    @Override
    public void setIds(String ids) {
        this.ids = ids;
    }

    @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;
    }

    @Override
    public String getPhone() {
        return phone;
    }

    @Override
    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String getEmail() {
        return email;
    }

    @Override
    public void setEmail(String email) {
        this.email = email;
    }
}








16, mapper/AdminMapper.java
package com.longchi.mapper;

import com.longchi.entity.Admin;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;


import java.util.List;

/**
 * 定义 Mapper 接口的方法
 */

public interface AdminMapper {
    List<Admin> selectAll(Admin admin);

    void insert(Admin admin);

    // 账号是唯一的,查询的是一个对象Admin
    @Select("select * from `admin` where username = #{username}")
    Admin selectByUsername(String username);

    void updateById(Admin admin);

    @Delete("delete from `admin` where id = #{id}")
    void deleteById(Integer id);

    void login();

    @Select("select * from `admin` where id = #{id}")
    Admin selectById(String id);

}







17, mapper/UserMapper.java
package com.longchi.mapper;

import com.longchi.entity.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Select;


import java.util.List;

/**
 * 定义 Mapper 接口的方法
 */

public interface UserMapper {
    List<User> selectAll(User user);

    void insert(User user);

    // 账号是唯一的,查询的是一个对象Admin
    @Select("select * from `user` where username = #{username}")
    User selectByUsername(String username);

    void updateById(User user);

    @Delete("delete from `user` where id = #{id}")
    void deleteById(Integer id);

    void login();

    @Select("select * from `user` where id = #{id}")
    User selectById(String id);
}








18, service/AdminService.java
package com.longchi.service;

import cn.hutool.core.util.StrUtil;
import com.longchi.entity.Account;
import com.longchi.entity.Admin;
import com.longchi.exception.CustomerException;
import com.longchi.mapper.AdminMapper;
import com.longchi.utils.TokenUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import java.util.List;

@Service
public class AdminService {

    @Resource
    AdminMapper adminMapper;

    public void add(Admin admin) {
        // 根据新的账号查询数据库 是否存在同样账号的数据
        Admin dbAdmin = adminMapper.selectByUsername(admin.getUsername());
        if (dbAdmin != null) {
            throw new CustomerException("账号重复");
        }

        // 设置默认密码
        if (StrUtil.isBlank(admin.getPassword())) {
            admin.setPassword("admin");
        }
        // 设置默认名称
        if (StrUtil.isBlank(admin.getName())) {
            admin.setName(admin.getUsername());
        }
        admin.setRole("ADMIN");
        adminMapper.insert(admin);
    }

    public void update(Admin admin) {
        adminMapper.updateById(admin);
    }

    public void deleteById(Integer id) {
        adminMapper.deleteById(id);
    }

    // 批量删除就是循环之后调用单个删除
    public void deleteBatch(List<Admin> list) {
        for (Admin admin : list) {
            this.deleteById(admin.getId());
            //this.deleteById(admin.getId());
        }
    }

    public String admin(String name) {
        if ("admin".equals(name)) {
            return "admin";
        } else {
            throw new CustomerException("账号错误");
        }
    }

    public Admin selectById(String id) {
        return adminMapper.selectById(id);
    }

    public List<Admin> selectAll(Admin admin) {
        return adminMapper.selectAll(admin);
    }

    public PageInfo<Admin> selectPage(Integer pageNum, Integer pageSize, Admin admin) {
        // 获取当前登录用户信息 可以去做一些验证
        // Account currentUser = TokenUtils.getCurrentUser();
        // 开启分页查询
        PageHelper.startPage(pageNum,pageSize);
        List<Admin> list = adminMapper.selectAll(admin);
        return PageInfo.of(list);
    }

    public Admin login(Account account) {
        // 验证数据库有没有该账号
        Admin dbAdmin = adminMapper.selectByUsername(account.getUsername());
        if (dbAdmin == null) {
            throw new CustomerException("账号不存在");
        }
        // 验证密码是否正确 数据库的密码与我们获取到输入密码(参数)不一致
        if (!dbAdmin.getPassword().equals(account.getPassword())) {
            throw new CustomerException("账号或密码错误");
        }
        // 创建 token 并返回给前端
        String token = TokenUtils.createToken(dbAdmin.getId()+"-"+"ADMIN",dbAdmin.getPassword());
        //在 dbAdmin 里面设置 token,然后返回出去
        dbAdmin.setToken(token);
        // 返回给前端的数据
        return dbAdmin;
    }

    public void updatePassword(Account account) {
        // 判断用户输入的新密码和确认密码是否一致 这个写到 service 里面
        if (!account.getNewPassword().equals(account.getConfirmPassword())) {
            throw new CustomerException("500","你两次输入的密码不一致");
        }
        // 效验一下,原密码是否一致
        Account currentUser = TokenUtils.getCurrentUser();
        // 判断获取到的原密码是否等于当前用户的密码
        if (!account.getPassword().equals(currentUser.getPassword())) {
            throw new CustomerException("500","原密码输入错误");
        }
        // 开始更新密码
        Admin admin = adminMapper.selectById(currentUser.getId().toString());
        admin.setPassword(account.getNewPassword());
        adminMapper.updateById(admin);
    }
}






19, service/UserService.java
package com.longchi.service;

import cn.hutool.core.util.StrUtil;
import com.longchi.entity.Account;
import com.longchi.entity.User;
import com.longchi.exception.CustomerException;
import com.longchi.mapper.UserMapper;
import com.longchi.utils.TokenUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;

import java.util.List;

@Service
public class UserService {

    @Resource
    UserMapper userMapper;

    public void add(User user) {
        // 根据新的账号查询数据库 是否存在同样账号的数据
        User dbUser = userMapper.selectByUsername(user.getUsername());
        if (dbUser != null) {
            throw new CustomerException("账号重复");
        }

        // 设置默认密码
        if (StrUtil.isBlank(user.getPassword())) {
            user.setPassword("123456");
        }
        // 设置默认名称
        if (StrUtil.isBlank(user.getName())) {
            user.setName(user.getUsername());
        }
        user.setRole("USER");
        userMapper.insert(user);
    }

    public void update(User user) {
        userMapper.updateById(user);
    }

    public void deleteById(Integer id) {
        userMapper.deleteById(id);
    }

    // 批量删除就是循环之后调用单个删除
    public void deleteBatch(List<User> list) {
        for (User user : list) {
            this.deleteById(user.getId());
        }
    }

    public String user(String name) {
        if ("user".equals(name)) {
            return "user";
        } else {
            throw new CustomerException("账号错误");
        }
    }

    public User selectById(String id) {
        return userMapper.selectById(id);
    }

    public List<User> selectAll(User user) {
        return userMapper.selectAll(user);
    }

    public PageInfo<User> selectPage(Integer pageNum, Integer pageSize, User user) {
        // 开启分页查询
        PageHelper.startPage(pageNum,pageSize);
        List<User> list = userMapper.selectAll(user);
        return PageInfo.of(list);
    }

    public User login(Account account) {
        // 验证数据库有没有该账号
        User dbUser = userMapper.selectByUsername(account.getUsername());
        if (dbUser == null) {
            throw new CustomerException("账号不存在");
        }
        // 验证密码是否正确 数据库的密码与我们获取到输入密码(参数)不一致
        if (!dbUser.getPassword().equals(account.getPassword())) {
            throw new CustomerException("账号或密码错误");
        }
        // 创建 token 并返回给前端
        String token = TokenUtils.createToken(dbUser.getId()+"-"+"USER",dbUser.getPassword());
        //在 dbAdmin 里面设置 token,然后返回出去
        dbUser.setToken(token);
        // 返回给前端的数据
        return dbUser;
    }

    public void register(User user) {
        // 注册的逻辑就是新增
        this.add(user);
    }

    public void updatePassword(Account account) {
        // 判断用户输入的新密码和确认密码是否一致 这个写到 service 里面
        if (!account.getNewPassword().equals(account.getConfirmPassword())) {
            throw new CustomerException("500","你两次输入的密码不一致");
        }
        // 效验一下,原密码是否一致
        Account currentUser = TokenUtils.getCurrentUser();
        // 判断获取到的原密码是否等于当前用户的密码
        if (!account.getPassword().equals(currentUser.getPassword())) {
            throw new CustomerException("500","原密码输入错误");
        }
        // 开始更新密码
        User user = userMapper.selectById(currentUser.getId().toString());
        user.setPassword(account.getNewPassword());
        userMapper.updateById(user);
    }
}







20, resources/mapper/AdminMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.longchi.mapper.AdminMapper">
<insert id="insert">
    insert into `admin` (username,password,name,phone,email,role,avatar) values(#{username},#{password},#{name},#{phone},#{email},#{role},#{avatar})
</insert>
<update id="updateById">
    update `admin` set username=#{username},password=#{password},name=#{name},phone=#{phone},email=#{email},role=#{role},avatar=#{avatar} where id=#{id}
</update>
<update id="login"></update>
<select id="selectAll" resultType="com.longchi.entity.Admin">
    select * from `admin`
    <where>
        <if test="username != null and username != ''">username like concat('%',#{username},'%')</if>
        <if test="name != null and name != ''">and name like concat('%',#{name},'%')</if>
        <if test="ids != null and ids != ''">
            and id in
            <foreach collection="idsArr" open="(" close=")" separator="," item="id">
                #{id}
            </foreach>
        </if>
    </where>
    order by id desc
</select>
<!--    <delete id="deleteById">-->
<!--        delete from `admin` where id=#{id}-->
<!--    </delete>-->
<!--    <select id="selectById" resultType="com.longchi.entity.Admin">-->
<!--        select * from `admin` where id = #{id}-->
<!--    </select>-->
<!--    <select id="selectByUsername" resultType="com.longchi.entity.Admin">-->
<!--        select * from `admin` where username = #{username}-->
<!--    </select>-->
</mapper>







21, resources/mapper/UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.longchi.mapper.UserMapper">
    <insert id="insert">
        insert into `user` (username,password,name,phone,email,role,avatar) values(#{username},#{password},#{name},#{phone},#{email},#{role},#{avatar})
    </insert>
    <update id="updateById">
        update `user` set username=#{username},password=#{password},name=#{name},phone=#{phone},email=#{email},role=#{role},avatar=#{avatar} where id=#{id}
    </update>
    <update id="login"></update>
    <!--    <delete id="deleteById">-->
    <!--        delete from `user` where id=#{id}-->
    <!--    </delete>-->
    <select id="selectAll" resultType="com.longchi.entity.User">
        select * from `user`
        <where>
            <if test="username != null and username != ''">username like concat('%',#{username},'%')</if>
            <if test="name != null and name != ''">and name like concat('%',#{name},'%')</if>
            <if test="ids != null and ids != ''">
                and id in
                <foreach collection="idsArr" open="(" close=")" separator="," item="id">
                    #{id}
                </foreach>
            </if>
        </where>
        order by id desc
    </select>
    <!--    <select id="selectByUsername" resultType="com.longchi.entity.User">-->
    <!--        select * from `user` where username = #{username}-->
    <!--    </select>-->
</mapper>






22, exception/CustomerException.java
package com.longchi.exception;

/**
 * 自定义异常处理
 * 编译时不会报错,只有在运行时报错
 * 运行时异常
 */
public class CustomerException extends RuntimeException {
    private String code;
    private String msg;

    // 构造器 (有两个参数)
    public CustomerException(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    // 构造器 (有一个参数) 只保留一个参数 code为500
    public CustomerException(String msg) {
        this.code = "500";
        this.msg = msg;
    }

    // 无参构造器
    public CustomerException() {}

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}






23, exception/GlobalExceptionHandler.java
package com.longchi.exception;

/*
  全局的异常捕获器
  */

import com.longchi.common.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

// 要标注在类上,表示当前类是一个全局异常处理器的类
@ControllerAdvice("com.longchi.controller")
public class GlobalExceptionHandler {
    // 配置一个log
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(Exception.class)
    @ResponseBody    // 将result对象转换成json格式 返回json串
    public Result error(Exception e) {
        log.error("系统异常", e);
        return Result.errror("系统异常");
    }

    @ExceptionHandler(CustomerException.class)
    @ResponseBody    // 将result对象转换成json格式 返回json串
    public Result CustomerError(CustomerException e) {
        log.error("自定义错误", e);
        // 从 CustomerException.java里面将code和msg拿过来
        return Result.errror(e.getCode(), e.getMsg());
    }
}







24, common/Result.java
package com.longchi.common;

public class Result {
    private String code;
    private Object data;
    private String msg;

    // 不携带参数 成功时有可能不返回数据 定义一个success
    public static Result success() {
        Result result = new Result();
        result.setCode("200");
        result.setMsg("请求成功");
        return result;
    }

    // 携带参数 成功时返回数据 定义一个success
    public static Result success(Object data) {
        Result result = new Result();
        result.setCode("200");
        result.setData(data);
        result.setMsg("请求成功");
        return result;
    }

    // 定义统一的code(比如code为500),返回msg
    public static Result errror(String msg) {
        Result result = new Result();
        result.setCode("500");
        result.setMsg(msg);
        return result;
    }

    // 自定义code参数 传两个参数,比如code是400,或者401,500等等,返回msg
    public static Result errror(String code, String msg) {
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        return result;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}





25, common/CorsConfig.java
package com.longchi.common;

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 {
    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        // 1, 设置访问源地址
        corsConfiguration.addAllowedHeader("*");
        // 2, 设置访问源请求头
        corsConfiguration.addAllowedMethod("*");
        // 3, 设置访问源请求方法
        source.registerCorsConfiguration("/**", corsConfiguration);
        // 4, 对接口配置跨域设置
        return new CorsFilter(source);
    }
}





26, common/JWTInterceptor.java
package com.longchi.common;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.longchi.entity.Account;
import com.longchi.exception.CustomerException;
import com.longchi.service.AdminService;
import com.longchi.service.UserService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class JWTInterceptor implements HandlerInterceptor {

    @Resource
    AdminService adminService;

    @Resource
    UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1, 从请求头里面拿到Header 因为Header里面包含token
        String token = request.getHeader("token");
        // 2, 如果没有拿到,从参数里面再拿一次,防止导出接口的token在请求里面。
        if (ObjectUtil.isEmpty(token)) {
            token = request.getParameter("token");
        }
        // 3, 认证 token
        if (StrUtil.isBlank(token)) {
            throw new CustomerException("401","你无权限操作");
        }

        Account account = null;
        try {
            // 拿到 token 的载荷数据
            String audience = JWT.decode(token).getAudience().get(0);
            String[] split = audience.split("-");
            String userId = split[0];
            String role = split[1];
            // 根据 token 解析出来 userId 去对应表查询用户信息。
            if ("ADMIN".equals(role)) {
                account = adminService.selectById(userId);
            } else if ("USER".equals(role)) {
                account = userService.selectById(userId);
            }
        } catch (Exception e) {
            throw new CustomerException("401","你无权限操作");
        }
        if (account == null) {
            throw new CustomerException("401","你无权限操作");
        }
        // 验证签名的操作
        try {
            // 1,创建加签验证器
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build();
            // 2, 通过 verify() 这个方法去验证 token
            jwtVerifier.verify(token);
        } catch (Exception e) {
            throw new CustomerException("401","你无权限操作");
        }
        return true;
    }
}






27, common/WebConfig.java
package com.longchi.common;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author Administrator
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 配置拦截器
        registry.addInterceptor(jwtInterceptor())
                .addPathPatterns("/**")
                .excludePathPatterns("/login","/register","/files/download/**");
    }

    @Bean
    public JWTInterceptor jwtInterceptor() {
        return new JWTInterceptor();
    }
}






28, Utils/TokenUtils.java
package com.longchi.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.longchi.entity.Account;
import com.longchi.service.AdminService;
import com.longchi.service.UserService;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.Date;

/**
 * @Component注入 Bean 容器里面,不注入我们是拿不到 @Resource 的
 * 才能拿到 adminService与 userService
 * 在 springboot 工程启动时给他赋值,
 * 赋值结束后才可以通过静态变量去查询数据
 * */
@Component
public class TokenUtils {

    @Resource
    UserService userService;

    @Resource
    AdminService adminService;

    /**
     * 注意: 静态方法是拿不到容器里面的 Bean,所以我们做如下转换
     * */
    static AdminService staticAdminService;
    static UserService staticUserService;

    /**
     * springboot 工程启动后会加载下面这段代码
     * 给 adminService与 userService 分别赋值
     * 把 springboot 容器的 Bean 赋值给静态变量 staticAdminService 与 staticUserService
     * 通过静态变量 staticAdminService 与 staticUserService 再去查询我们需要的数据
     * */
    @PostConstruct
    public void init() {
        staticAdminService = adminService;
        staticUserService = userService;
    }

    /**
     * 生成 Token
     * */
    public static String createToken(String data, String sign) {
        return JWT.create().withAudience(data)
                .withExpiresAt(DateUtil.offsetDay(new Date(), 1))
                .sign(Algorithm.HMAC256(sign));
    }

    /**
     * 通过 token 去获取当前登录用户的信息
     * */
    public static Account getCurrentUser() {
        // 通过这个方法拿到 request 请求数据
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 再通过 request 获取到当前登录用户的 token
        String token = request.getHeader("token");
        // 如果请求头里面token不存在,可以去导出的参数里面去获取token,防止导出接口的token在请求里面。
        if (StrUtil.isBlank(token)) {
            token = request.getParameter("token");
        }
        // 拿到 token 的载荷数据
        String audience = JWT.decode(token).getAudience().get(0);
        String[] split = audience.split("-");
        String userId = split[0];
        String role = split[1];
        // 根据 token 解析出来 userId 去对应表查询用户信息。
        if ("ADMIN".equals(role)) {
            return staticAdminService.selectById(userId);
        } else if ("USER".equals(role)) {
            return staticUserService.selectById(userId);
        }
        return null;
    }
}
备份 将登录页面的名称,电话,邮箱删除,方便登录
复制代码
<el-form-item prop="name" label="名称">
    <el-input size="large" v-model="data.form.name" autocomplete="off" placeholder="请输入名称" prefix-icon="User" />
</el-form-item>
<el-form-item prop="phone" label="电话">
    <el-input size="large" v-model="data.form.phone" autocomplete="off" placeholder="请输入电话" prefix-icon="User" />
</el-form-item>
<el-form-item prop="email" label="邮箱">
    <el-input size="large" v-model="data.form.email" autocomplete="off" placeholder="请输入邮箱" prefix-icon="User" />
</el-form-item>

form: {
	name: '',
	phone: '',
	email: ''
}


name: [
    { required: true, message: '请输入名称', trigger: 'blur' },
    { min: 2, max: 10, message: '长度在 2 到 10 个字符', trigger: 'blur' }
],
phone: [
    { required: true, message: '请输入电话', trigger: 'blur' },
    { min: 11, max: 11, message: '长度为 11 个字符', trigger: 'blur' }
],
email: [
    { required: true, message: '请输入邮箱地址', trigger: 'blur' },
    { type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] },
    { min: 5, max: 20, message: '长度在 5 到 20 个字符', trigger: 'blur' },
    { pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, message: '请输入正确的邮箱地址', trigger: 'blur' },
    { pattern: /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/, message: '请输入正确的邮箱地址', trigger: 'change' }
],
相关推荐
小则又沐风a1 小时前
C++模板进阶
java·服务器·前端·c++
段ヤシ.1 小时前
回顾Java知识点,面试题汇总Day3(持续更新)
java·开发语言·windows
ooseabiscuit1 小时前
Laravel 7.x 十大新特性解析
数据库
不会写DN1 小时前
PyScript-GitHubRepo:构建高性能GitHub仓库批量下载工具的技术实践
开发语言·前端·python
Setsuna_F_Seiei1 小时前
AI 提效之 MCP - Agent 与执行工具的链接协议
前端·javascript·ai编程
woai33642 小时前
项目-轻客管家1-环境准备
java
ZC跨境爬虫2 小时前
跟着 MDN 学 HTML day_37:(深入掌握 CustomEvent 自定义事件接口)
前端·javascript·ui·html·音视频
A-刘晨阳8 小时前
AI原生时序数据库选型指南:从数据存储到智能决策的范式跃迁
数据库·时序数据库·ai-native
whinc9 小时前
JavaScript技术周刊 2026年第18周
javascript