springboot + vue项目:大事件文章管理系统(黑马程序员)

 一、项目介绍

首先声明,本项目静态资源和前端部分样式,后端部分接口代码由黑马程序员提供,我根据黑马程序员的基础更改了一部分功能,本项目学习视频: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替换为控制符串
      }
    }
  }

})

本项目的源码后续更新......尽请期待.......

  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
物流管理系统是一个集成了物流信息的处理、调度、跟踪和管理功能的应用系统。SpringBoot是一个快速开发框架,提供了便捷的配置和快速构建应用程序的能力。Vue是一个流行的前端框架,用于构建用户界面。 为了实现物流管理系统,我们可以使用SpringBoot构建后端,使用Vue构建前端。在后端方面,我们可以使用SpringBoot提供的特性来管理用户账户、管理物流订单、管理配送人员等等。可以使用Spring Security进行身份验证和授权控制,保证系统的安全性。使用Spring Data JPA可以方便地与数据库交互,存储和查询物流信息。同时可以使用Spring Boot的集成测试功能进行测试,保证系统的稳定性。 在前端方面,使用Vue来构建用户界面,可以借助Vue Router实现路由管理,实现用户之间的页面跳转。同时使用Vue的组件化开发方式,可以将不同的功能模块拆分成独立的组件进行开发,提高代码的复用性和可维护性。同时,可以使用Vue的状态管理工具Vuex,实现不同组件之间的状态共享和管理。在用户界面设计方面,可以使用Vue的UI框架Element UI,提供美观、易用的UI组件。 总的来说,使用SpringBootVue来开发物流管理系统可以提高开发效率和系统性能。SpringBoot提供了稳定可靠的后端框架,Vue提供了灵活方便的前端框架,两者结合可以实现一个高效、功能完善的物流管理系统
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值