赞
踩
首先声明,本项目静态资源和前端部分样式,后端部分接口代码由黑马程序员提供,我根据黑马程序员的基础更改了一部分功能,本项目学习视频:https://www.bilibili.com/video/BV14z4y1N7pg/?spm_id_from=333.337.search-card.all.click&vd_source=3c7831e560a6c8660ad9e4ed74fc8980
该项目是经典的后台管理系统
前后端分别使用vue和springboot
首先我先进行演示一下
注册模块:用户通过输入用户名,邮箱,邮箱验证码,密码进行注册,后端接收到用户传入的数据,首先对数据进行校验,验证用户输入的邮箱验证码是否与数据库中的匹配,如果匹配就通过验证,将用户的信息存储到数据库中,其中密码通过md5加密算法进行算法加密,前端返回用户登录成功。
登录模块:用户输入邮箱和密码后进行登录,后端开始对数据进行验证,首先验证邮箱输入是否正确,如果错误返回给用户提示信息,否则登录成功,当用户登录成功后,后端会生成相应的token,并储存在redis中一份,用户在访问其他页面时,必须携带token,否则拦截器进行拦截。
忘记密码模块:用户通过输入邮箱,获取验证码后进行校验比对,如果用户输入的验证码正确,就通过下一步,用户输入新密码,确认新密码后,密码重置成功。
文章管理模块:用户登录成功后,会跳转后文章管理的页面,如果用户在数据库中有数据,则前端向后端发送请求获取文章数据,同时获取用户数据的数量,对其进行分页,用户可以进行条件查询,后端使用动态sql语句进行查询并返回结果,用户点击添加文章,前端通过监听点击事件,弹出抽屉,用户可以输入文章标题,文章分类通过调用后台获取用户的分类数量并显示,用户输入文章封面时,获取图片的url地址,图片会上传到阿里云服务器,数据库存储的仅仅只是数据库的路径,当用户点击编辑按钮时,也会通过监听事件弹出抽屉,并且获取点击按钮的数据并展示,当用户修改了数据,会调用后台的接口进行修改,当用户删除时会进行提示,当用户点击确定后,后台会将该文章从该数据库中删除。
文章分类模块:用户点击分类模块时,前端监听到事件后会调用后台接口进行数据查询,并且返回给前端,前端进行渲染,用户点击添加分类时会弹出表单框,用户输入数据后,前端会把数据返回到后端,后端进行添加,用户点击编辑分类时,通过会弹出表单框,表单框中存有数据,用户可以修改数据然后提交,后端接收到用户修改的数据进行修改,当用户点击删除时,前端监听到事件后会提示用户是否删除,如果用户点击确定,前端调用后端接口进行删除。
基本资料模块:用户点击基本资料,调用后台接口查询数据库中的用户信息,渲染在前端页面上,当用户点击操作按钮时,会弹出一个表单框,用户数据渲染在表单上,用户可以修改用户信息,但是用户名和邮箱是不可修改的,用户无权限修改。
上传头像模块:用户点击选择图片,选择本地图片,然后调用后端接口显示在页面上,此时图片储存到了阿里云服务器上,数据库仅仅存放图片在阿里云服务器url地址,当用户点击上传,后端接口接收到指令后,向阿里云服务器发送请求,获取图片,并且将图片返给前端,前端将图片渲染在页面上。
重置密码模块:用户在表单中输入旧密码,新密码和确认新密码后,点击提交,前端将用户数据发送给后端,后端接收到请求后首先判断用户输入的旧密码是否正确,如果错误,返回给前端错误信息,前端将错误信息渲染后返给用户,如果正确,则验证输入的新密码和确认后的新密码是否一致,如果一致,则调用接口修改数据库中的密码,并将md5加密后的密码存储在数据库中。
由于项目过大,本次我仅仅演示 登录注册模块的前后端交互模块
在后端接口中,我们创建业务的模块分为四部分,controller层,mapper层,service层和pojo层
controller层
- @PostMapping("/register")
- public Result register(@RequestBody UserRegistrationDTO userRegistrationDTO) {
- //获取前端传过来的参数
- String username = userRegistrationDTO.getUsername();
-
- String password = userRegistrationDTO.getPassword();
-
- String email = userRegistrationDTO.getEmail();
-
- String captcha = userRegistrationDTO.getCaptcha();
-
-
- if (username == null && password == null && email == null && captcha == null){
- return Result.error("请求参数不能为空!");
- }
-
- // 查找用户名是否已被占用
- User user = userService.findByUserName(username);
- if (user != null) {
- return Result.error("用户名已被占用!");
- }
- //查找邮箱是否被占用
- User find_email = userService.findByEmail(email);
- if (find_email != null){
- captchaMapper.deleteByEmail(email);
- return Result.error("邮箱已经被占用!");
- }
-
- // 验证邮箱和验证码是否匹配
- try {
- capthcaService.vaildCaptcha(email, captcha);
- } catch (IllegalArgumentException e) {
- return Result.error("验证码错误!");
- }
-
- // 注册用户
- try {
- userService.register(username, password, email, captcha);
- return Result.success();
- } catch (Exception e) {
- return Result.error("注册失败:" + e.getMessage());
- }
- }
- @PostMapping("/login")
- public Result<String> login(@RequestBody User user){
- String email = user.getEmail();
- String password = user.getPassword();
- // System.out.println(email);
- // System.out.println(password);
- //查找是否存在用户邮箱
- User find_email = userService.findByEmail(email);
- if (find_email == null){
- return Result.error("邮箱不存在!");
- }
- //加密后的密码
- String passwordByEmail = userService.findPasswordByEmail(email);
- // System.out.println("数据库中加密后的密码:"+passwordByEmail);
-
- String inputUserPassword = Md5Util.getMD5String(password);
- // System.out.println("用户输入的加密后的密码:"+inputUserPassword);
- if (passwordByEmail.equals(inputUserPassword)){
- //登录成功
- userService.login(email,password);
- //获取数据库中的用户名和id
- String usernameByEmail = userService.findUsernameByEmail(email);
- Integer idByEmail = userService.findIdByEmail(email);
- //jwt令牌验证
- Map<String,Object> claims = new HashMap<>();
- claims.put("id",idByEmail);
- claims.put("username",usernameByEmail);
- String token = JwtUtil.genToken(claims);
- // System.out.println(token);
- //把token存储到redis中
- ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
- //将token设置为键值对,token即为键,又为值,并且设置redis中token过期时间为12天
- operations.set(token,token,12, TimeUnit.DAYS);
- return Result.success(token);
- }else {
- return Result.error("密码错误!");
- }
-
- }
mapper层
- @Mapper
- public interface UserMapper {
- //根据用户名查询
- @Select("select * from user where username = #{username}")
- User findByUserName(String username);
-
- @Insert("insert into user(username,password,email,create_time,update_time) " +
- "values(#{username},#{encryptedPassword},#{email},now(),now())")
- void register(User user);
- //查询数据库中的邮箱
- @Select("select * from user where email = #{email}")
- User findByEmail(String email);
- //查询数据库中加密后的密码
-
- @Select("SELECT password FROM user WHERE email =#{email}")
- String findPasswordByEmail(String email);
- //根据邮箱查询数据库中的用户名
- @Select("SELECT username FROM user WHERE email =#{email}")
- String findUsernameByEmail(String email);
- //根据邮箱查询数据库中的id
- @Select("SELECT id FROM user WHERE email =#{email}")
- Integer findIdByEmail(String email);
- }
service层
- @Override
- public void register(String username, String password, String email, String captcha) {
- // 创建用户对象
- User user = new User();
- user.setUsername(username);
- String encryptedPassword = Md5Util.getMD5String(password);
- user.setEncryptedPassword(encryptedPassword);
- user.setEmail(email);
- // 将用户信息添加到数据库
- userMapper.register(user);
- }
-
- @Override
- public void login(String email, String password) {
- // 根据用户名查询用户信息
- User email_login = userMapper.findByEmail(email);
- if (email_login == null) {
- throw new IllegalArgumentException("邮箱不存在!");
- }
- // 验证密码是否匹配
- String encryptedPassword = Md5Util.getMD5String(password);
- if (!email_login.getPassword().equals(encryptedPassword)) {
- throw new IllegalArgumentException("密码已经错误!");
- }
-
- }
pojo层
- //lombok 在编译阶段自动生成getter setter toString等方法
- @Data
- public class User {
- private Integer id;//主键ID
- private String username;//用户名
- // @JsonIgnore //让springmvc把当前对象转换成json格式的时候忽略
- private String password;//密码
- private String encryptedPassword;//加密后密码
- private String nickname;//昵称
- private String email;//邮箱
- private String userPic;//用户头像地址
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime createTime;//创建时间
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime updateTime;//更新时间
- private String age; //年龄
- private String phoneNumber; //手机号
- private String gender; //性别
- private String work; //职业
- private String hobby; //兴趣爱好
- private String area; //地区
- private String personSignature; //个性签名
- }
登录注册在一个vue文件中,两个表单通过数据模型的变化而变化
- <script setup>
- import {User, Lock, CreditCard, Right} from '@element-plus/icons-vue'
- import {ref} from 'vue'
- // 导入 axios
- import axios from 'axios';
- import {getCaptcha,startCountdown} from './api/register.js'
- import {ElMessage} from "element-plus";
-
- //控制注册与登录表单得显示, 默认显示注册
- const isRegister = ref(false)
- //定义用于注册的数据模型
- const registerData = ref({
- username:'',
- email:'',
- captcha:'',
- password:'',
- repassword:''
- });
- const loginData = ref({
- email:'',
- password:''
- })
- //自定义确认密码的校验函数
- const rePasswordValid = (rule, value, callback) => {
- if (value == null || value === '') {
- return callback(new Error('请再次确认密码'))
- }else if(value !== registerData.value.password) {
- return callback(new Error('两次输入密码不一致'))
- }else {
- return callback()
- }
- }
- //定义表单校验规则
- const rules = {
- username:[
- {required:true,message:"请输入用户名",trigger:"blur"},
- {max:16,min:5,message: "长度为5到16位非空字符",trigger: "blur"}
- ],
- email:[
- {required:true,message:"请输入邮箱",trigger:"blur"}
- ],
- captcha:[
- {required:true,message:"请输入验证码",trigger:"blur"}
- ],
- password:[
- {required:true,message:"请输入密码",trigger:"blur"},
- {max:16,min:5,message: "长度为5到16位非空字符",trigger: "blur"}
- ],
- repassword:[
- {validator:rePasswordValid,trigger:"blur"}
- ]
- }
- const email = ref('');
- const disabled = ref(false);
- const countdown = ref(60);
- const buttonText = ref('获取验证码');
-
- function handleGetVerificationCode() {
-
- // 检查邮箱是否为空
- if (!registerData.value.email) {
- console.error("邮箱不能为空!");
- return;
- }
- // 发送请求给后端获取验证码
- getCaptcha(registerData.value.email, axios)
- .then(response => {
- ElMessage.success("验证码发送成功!")
- // 验证码发送成功,开始倒计时
- disabled.value = true;
- startCountdown({ disabled, countdown, buttonText });
- })
- .catch(error => {
- console.error(error);
- // 处理错误情况
- });
- }
-
-
- // 发送注册请求给后端
- async function register(){
- // 从 registerData 中获取用户输入的数据
- const { username, email, captcha, password } = registerData.value;
-
- // 发送注册请求给后端
- try {
- const response = await axios.post('/api/user/register', {
- username,
- password,
- email,
- captcha
- }, {
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- // 根据后端返回的响应进行处理
- if (response.data.code === 0) {
- ElMessage.success("注册成功!")
- } else {
- console.error('注册失败:', response.data.message);
- // 在界面上显示错误提示信息或者执行其他必要的操作
- ElMessage.error(response.data.message?response.data.message:"注册失败!")
- }
- } catch (error) {
- // 处理注册失败的情况
- console.error('验证注册失败:', error.message);
- }
- }
- import {useTokenStore} from "@/stores/token.js";
- import {useRouter} from 'vue-router'
- const router = useRouter()
- const tokenStore = useTokenStore()
- //发送登录请求给后端
- async function login(){
- // 从 loginData 中获取用户输入的数据
- const {email,password} =loginData.value;
-
- //给后端发送登录请求
- try {
- const response = await axios.post('/api/user/login', {
- email,
- password
- }, {
- headers: {
- 'Content-Type': 'application/json',
- }
- });
- if (response.data.code === 0){
-
- console.log('登录成功:', response.data);
- ElMessage.success("登录成功!")
- //将得到的token放入pinia中
- tokenStore.setToken(response.data.data,12 * 24 * 60 * 60)//设置12天有效期
- //登录成功后跳转到主页面
- router.push('/layoutMain')
- }else {
- // 在界面上显示错误提示信息或者执行其他必要的操作
- ElMessage.error(response.data.message?response.data.message:"登录失败!")
- }
- }catch (error){
- console.log("登录失败:"+error.message)
- }
- }
- //忘记密码跳转页面
- const forgetPwd = ()=>{
- router.push('/forgetPwd')
- }
- </script>
-
- <template>
- <el-row class="login-page">
- <el-col :span="12" class="bg"></el-col>
- <el-col :span="6" :offset="3" class="form">
- <!-- 注册表单 -->
- <template v-if="isRegister">
- <el-form ref="form" size="large" autocomplete="off" :rules="rules" :model="registerData">
- <el-form-item>
- <h1>注册</h1>
- </el-form-item>
- <el-form-item prop="username">
- <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username"></el-input>
- </el-form-item>
- <el-form-item prop="email">
- <el-input :prefix-icon="CreditCard" placeholder="请输入邮箱" v-model="registerData.email"></el-input>
- <el-button type="primary" :disabled="disabled" @click="handleGetVerificationCode">{{buttonText}}</el-button>
- </el-form-item>
- <el-form-item prop="captcha">
- <el-input :prefix-icon="Right" placeholder="请输入验证码" v-model="registerData.captcha"></el-input>
- </el-form-item>
- <el-form-item prop="password">
- <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password"></el-input>
- </el-form-item>
- <el-form-item prop="repassword">
- <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.repassword"></el-input>
- </el-form-item>
- <!-- 注册按钮 -->
- <el-form-item>
- <el-button class="button" type="primary" auto-insert-space @click="register">
- 注册
- </el-button>
- </el-form-item>
- <el-form-item class="flex">
- <el-link type="info" :underline="false" @click="isRegister = false">
- ← 返回
- </el-link>
- </el-form-item>
- </el-form>
- </template>
- <!-- 登录表单 -->
- <template v-else>
- <el-form ref="form" size="large" autocomplete="off" :rules="rules" :model="loginData">
- <el-form-item>
- <h1>登录</h1>
- </el-form-item>
- <el-form-item prop="email">
- <el-input :prefix-icon="User" placeholder="请输入邮箱" v-model="loginData.email"></el-input>
- </el-form-item>
- <el-form-item prop="password">
- <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="loginData.password"></el-input>
- </el-form-item>
- <el-form-item class="flex">
- <div class="flex">
- <el-checkbox>记住我</el-checkbox>
- <el-link type="primary" :underline="false" @click="forgetPwd">忘记密码?</el-link>
- </div>
- </el-form-item>
- <!-- 登录按钮 -->
- <el-form-item>
- <el-button class="button" type="primary" auto-insert-space @click="login">登录</el-button>
- </el-form-item>
- <el-form-item class="flex">
- <el-link type="info" :underline="false" @click="isRegister = true">
- 注册 →
- </el-link>
- </el-form-item>
- </el-form>
- </template>
- </el-col>
- </el-row>
- </template>
-
- <style lang="scss" scoped>
- /* 样式 */
- .login-page {
- height: 100vh;
- background-color: #fff;
-
- .bg {
- background: url('@/assets/logo.png') no-repeat 60% center / 240px auto,
- url('@/assets/login_bg.jpg') no-repeat center / cover;
- border-radius: 0 20px 20px 0;
- }
-
- .form {
- display: flex;
- flex-direction: column;
- justify-content: center;
- user-select: none;
-
- .title {
- margin: 0 auto;
- }
-
- .button {
- width: 100%;
- }
-
- .flex {
- width: 100%;
- display: flex;
- justify-content: space-between;
- }
- /* 将输入框和按钮的宽度设为自适应 */
- .el-input {
- flex: 2; /* 较长 */
- }
-
- .el-button {
- flex: 1; /* 较短 */
- }
-
- /* 调整输入框和按钮之间的间距 */
- .el-input + .el-button {
- margin-left: 10px;
- }
- }
- }
- </style>
在前后端分离的项目中还存在浏览器跨域问题,我们通过配置代理的方法,让前端向后端发送请求,不直接通过浏览器发送
- import { fileURLToPath, URL } from 'node:url'
-
- import { defineConfig } from 'vite'
- import vue from '@vitejs/plugin-vue'
-
- // https://vitejs.dev/config/
- export default defineConfig({
- plugins: [
- vue(),
- ],
- resolve: {
- alias: {
- '@': fileURLToPath(new URL('./src', import.meta.url))
- }
- },
- //为后端服务器配置代理
- server:{
- proxy:{
- '/api':{
- target:'http://localhost:8080', //后端服务器地址
- changeOrigin:true,
- rewrite:(path)=>path.replace(/^\/api/,'') //api替换为控制符串
- }
- }
- }
-
- })
本项目的源码后续更新......尽请期待.......
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。