08. Springboot3+vue3实现登录注册功能
本节课效果图

素材


登录
页面 Login.vue
<template>
<div class="bg">
<div style="width: 350px; background-color: #fff; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); padding: 40px 20px">
<el-form ref="formRef" :model="data.form" :rules="data.rules">
<div style="margin-bottom: 40px; text-align: center; font-weight: bold; font-size: 24px">欢 迎 登 录</div>
<el-form-item prop="username">
<el-input size="large" v-model="data.form.username" autocomplete="off" prefix-icon="User" placeholder="请输入账号" />
</el-form-item>
<el-form-item prop="password">
<el-input size="large" show-password v-model="data.form.password" autocomplete="off" prefix-icon="Lock" placeholder="请输入密码" />
</el-form-item>
<el-form-item prop="role">
<el-select size="large" style="width: 100%" v-model="data.form.role">
<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 style="width: 100%" size="large" type="primary" @click="login">登 录</el-button>
</div>
<div style="text-align: right">
还没有账号?请 <a style="color: #274afa" href="/register">注册</a>
</div>
</el-form>
</div>
</div>
</template>
<script setup>
import { reactive, ref } from "vue";
import request from "@/utils/request.js";
import {ElMessage} from "element-plus";
import router from "@/router/index.js";
const formRef = ref()
const data = reactive({
form: { role: 'ADMIN' },
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 3, message: '账号最少3位', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
}
})
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 {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-image: url("@/assets/imgs/bg.jpg");
background-size: cover;
}
</style>
存储用户信息

登录接口
controller
@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);
}
service
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("账号或密码错误");
}
return dbAdmin;
}
模仿管理员开发用户管理
数据库表 user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`username` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '账号',
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '密码',
`name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
`phone` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '手机',
`email` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '邮箱',
`role` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '角色',
PRIMARY KEY (`id`),
UNIQUE KEY `username_index` (`username`) COMMENT '账号'
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='管理员信息';
User 相关的接口 和 管理页面
开发的过程:复制 Admin 相关的 Entity、Controller、Service、Mapper、Mapper,xml ,然后改名
Admin - > User 即可
注意 UserService 的 add 方法 和 AdminService 的 add 方法 的区别
user
public void add(User user) {
// 根据新的账号查询数据库 是否存在同样账号的数据
User dbUser = userMapper.selectByUsername(user.getUsername());
if (dbUser != null) {
throw new CustomerException("账号重复");
}
// 默认密码
if (StrUtil.isBlank(user.getPassword())) {
user.setPassword("123");
}
if (StrUtil.isBlank(user.getName())) {
user.setName(user.getUsername());
}
user.setRole("USER");
userMapper.insert(user);
}
admin
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");
}
admin.setRole("ADMIN");
adminMapper.insert(admin);
}
注意在 AdminMapper.xml 和 UserMapper.xml 里面补充 role

遇到 ”未找到接口“的问题
这是因为没有重启后台,重启即可
注册
页面 Register.vue
按钮颜色:#248243
<template>
<div class="bg">
<div style="width: 350px; background-color: #fff; border-radius: 5px; box-shadow: 0 0 10px rgba(0,0,0,0.1); padding: 40px 20px">
<el-form status-icon ref="formRef" :model="data.form" :rules="data.rules">
<div style="margin-bottom: 40px; text-align: center; font-weight: bold; font-size: 24px">欢 迎 注 册</div>
<el-form-item prop="username">
<el-input size="large" v-model="data.form.username" autocomplete="off" prefix-icon="User" placeholder="请输入账号" />
</el-form-item>
<el-form-item prop="password">
<el-input size="large" show-password v-model="data.form.password" autocomplete="off" prefix-icon="Lock" placeholder="请输入密码" />
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input size="large" show-password v-model="data.form.confirmPassword" autocomplete="off" prefix-icon="Lock" placeholder="请再次确认密码" />
</el-form-item>
<div style="margin-bottom: 20px">
<el-button style="width: 100%; background-color: #248243; border-color: #248243" size="large" type="primary" @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 { reactive, ref } from "vue";
import request from "@/utils/request.js";
import {ElMessage} from "element-plus";
import router from "@/router/index.js";
const validatePass = (rule, value, callback) => {
// value 表示用户输入的确认密码
if (value !== data.form.password) {
callback(new Error("两次输入的密码不匹配!"))
} else {
callback()
}
}
const formRef = ref()
const data = reactive({
form: {},
rules: {
username: [
{ required: true, message: '请输入账号', trigger: 'blur' },
{ min: 3, message: '账号最少3位', trigger: 'blur' },
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' }
],
confirmPassword: [
{ required: true, message: '请再次确认密码', trigger: 'blur' },
{ validator: validatePass, trigger: 'blur' }
]
}
})
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 {
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-image: url("@/assets/imgs/bg1.jpg");
background-size: cover;
}
</style>
注册接口
controller
@PostMapping("/register")
public Result register(@RequestBody User user) {
userService.register(user);
return Result.success();
}
service
public void register(User user) {
this.add(user);
}
自定义的错误
不是系统的错误,不是代码的 bug,这是我们给前端抛出的错误信息
当你看到页面一片空白怎么办?
打开页面的控制台,看看具体的报错信息,然后解决问题
本节课代码 样例
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">小白做毕设2025</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.name }}</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 style="width: 40px; height: 40px; border-radius: 50%" src="https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png" alt="">
<span style="margin-left: 5px">{{ data.user?.name }}</span>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人信息</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 - 60px)">
<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 />
</div>
<!-- 数据渲染区域结束 -->
</div>
<!-- 下方区域结束 -->
</div>
</template>
<script setup>
import router from "@/router/index.js";
import { reactive } from "vue";
const data = reactive({
user: JSON.parse(localStorage.getItem('code_user') || "{}")
})
const logout = () => {
localStorage.removeItem('code_user')
location.href = '/login'
}
if (!data.user?.id) {
location.href = '/login'
}
</script>
<style>
.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>