
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' }
],