SpringBoot+Vue login (including verification code), registration function

1. Initialization project

Refer to the following article, the initial vue project and the introduction of ElementUI components

Artifact for webpage creation—ElementUI (Xiaobai introduction super detailed)

And refer to Axios to complete the front-end and back-end separation project data interaction  to complete the encapsulation of the request request tool.

The final code of the entry file main.js of the Vue.js framework application is as follows:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import '@/assets/css/global.css'
import '@/assets/css/iconfont/iconfont.css'
import '@/assets/css/theme/index.css'
import request from "@/utils/request";
// 首先,导入了 Vue、App 组件、router、ElementUI 组件库以及相关CSS 文件。
// 其中,Vue 是 Vue.js 框架的核心库,App 是根组件,router 是路由配置文件,ElementUI 是基于 Vue.js 的 UI 组件库,包括一些常用的界面元素和样式。

//设置了 Vue.config.productionTip 的值为 false,这是对 Vue 配置项的修改,表示阻止在生产环境下产生提示信息。
Vue.config.productionTip = false
// 通过 Vue.use(ElementUI, { size: 'small' }) 使用 ElementUI 插件,并设置全局的默认组件尺寸为 small。
Vue.use(ElementUI, { size: 'small' });
// 通过Vue.prototype.request=request将自定义的request对象挂载到Vue实例的原型上,以便在组件中可以通过this.request 访问到该对象。
// 这个request对象可能是用于发送HTTP请求的工具类或者封装了接口请求的方法。
Vue.prototype.$request=request
// 创建了一个新的 Vue 实例,配置了 router 和 App 组件,并通过 $mount 方法将实例挂载到 id 为 app 的 HTML 元素上,实现应用程序的渲染和启动。
new Vue({
  router,
  render: h => h(App) // 行代码导入了名为 App 的根组件,并在 render 函数中将其渲染到页面上。h 是 createElement 的别名,用于创建虚拟 DOM 元素
}).$mount('#app')

        render: h => h(App)It is the way of writing the render function in Vue.js. It uses the syntax rules of arrow functions to represent an anonymous function that receives one parameter hand returns it h(App). In Vue.js, component templates can be templatedefined through attributes, or they can be rendered dynamically using rendering functions. The rendering function can programmatically create a virtual DOM and ultimately render it into actual DOM elements. Here, it h's actually createElementan abbreviation for , which is a function used to create virtual DOM elements. h(App)Indicates calling createElementthe function to create a virtual DOM object generated based on the App component.

        Therefore, render: h => h(App)it means to render the App component as a virtual DOM element and finally display it on the page. This writing method is common in single-file components and is used when rendering functions are used for dynamic rendering.

Of course, a verification code plug-in must be introduced: ValidCode.vue

<template>
  <div class="ValidCode disabled-select" style="width: 100%; height: 100%" @click="refreshCode">
    <span v-for="(item, index) in codeList" :key="index" :style="getStyle(item)">{
   
   {item.code}}</span>
  </div>
</template>

<script>
export default {
  name: 'validCode',
  data () {
    return {
      length: 4,
      codeList: []
    }
  },
  mounted () {
    this.createdCode()
  },
  methods: {
    refreshCode () {
      this.createdCode()
    },
    createdCode () {
      let len = this.length,
          codeList = [],
          chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz0123456789',
          charsLen = chars.length
      // 生成
      for (let i = 0; i < len; i++) {
        let rgb = [Math.round(Math.random() * 220), Math.round(Math.random() * 240), Math.round(Math.random() * 200)]
        codeList.push({
          code: chars.charAt(Math.floor(Math.random() * charsLen)),
          color: `rgb(${rgb})`,
          padding: `${[Math.floor(Math.random() * 10)]}px`,
          transform: `rotate(${Math.floor(Math.random() * 90) - Math.floor(Math.random() * 90)}deg)`
        })
      }
      // 指向
      this.codeList = codeList
      // 将当前数据派发出去
      this.$emit('update:value', codeList.map(item => item.code).join(''))
    },
    getStyle (data) {
      return `color: ${data.color}; font-size: ${data.fontSize}; padding: ${data.padding}; transform: ${data.transform}`
    }
  }
}
</script>

<style>
.ValidCode{
  display: flex;
  justify-content: center;
  align-items: center;
  cursor: pointer;
}
.ValidCode span {
  display: inline-block;
  font-size: 18px;
}
</style>

Reference in the login and registration interface: import ValidCode from "@/components/ValidCode";

2. Login interface:

<template>
  <div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #0f9876">
    <div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden">
      <div style="flex: 1">
        <img src="@/assets/login.png" alt="" style="width: 100%">
      </div>
      <div style="flex: 1; display: flex; align-items: center; justify-content: center">
        <el-form :model="user" style="width: 80%" :rules="rules" ref="loginRef">
          <div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">欢迎登录后台管理系统</div>
<!--在 Element UI 的表单组件中,prop 是用于指定表单项对应的数据对象中的属性名。它的作用是将该表单项与数据对象进行绑定,实现数据的双向绑定以及表单验证。
具体来说,当你在 <el-form-item> 中使用 prop="username" 时,它实际上告诉 Element UI 表单组件,该表单项对应的数据对象中的属性名是 username。
此外,prop 还有一个重要的作用是进行表单验证。你可以定义一些验证规则(通过 :rules 属性传递一个数组),然后在表单提交或表单项失去焦点时,Element UI 会自动根据这些验证规则验证表单项的值,并显示相应的错误信息。
综上所述,prop 用于指定表单项对应的数据对象的属性名,实现数据的双向绑定,同时也用于进行表单验证。-->
          <el-form-item prop="username">
<!--<el-form-item prop="username"> 定义了一个表单项,并将它与 user 数据对象中名为 username 的属性进行关联。这样,当用户输入文本框中的内容时,v-model="user.username" 将实现将输入的值同步到 user.username 属性中。-->
            <el-input prefix-icon="el-icon-user" size="medium" placeholder="请输入账号" v-model="user.username"></el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请输入密码" v-model="user.password"></el-input>
          </el-form-item>
          <el-form-item prop="code">
            <div style="display: flex">
              <el-input placeholder="请输入验证码" prefix-icon="el-icon-circle-check" size="medium" style="flex: 1" v-model="user.code"></el-input>
              <div style="flex: 1; height: 36px">
<!--是一个自定义组件ValidCode的使用方式,并且添加了一个事件监听器。这里的@update:value是监听了ValidCode组件中的value属性更新事件,并将更新后的值传递给名为getCode的方法。
当ValidCode组件中的value属性更新时,会触发getCode方法。getCode方法可在父组件中定义,用于处理接收到的验证码信息或进行相关操作。-->
                <valid-code @update:value="getCode" />
              </div>
            </div>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" style="width: 100%" @click="login">登 录</el-button>
          </el-form-item>
          <div style="display: flex">
            <div style="flex: 1">还没有账号?请 <span style="color: #0f9876; cursor: pointer" @click="$router.push('/register')">注册</span></div>
            <div style="flex: 1; text-align: right"><span style="color: #0f9876; cursor: pointer" @click="handleForgetPass">忘记密码</span></div>
          </div>
        </el-form>
      </div>
    </div>


    <el-dialog title="忘记密码" :visible.sync="forgetPassDialogVis" width="30%">
      <el-form :model="forgetUserForm" label-width="80px" style="padding-right: 20px">
        <el-form-item label="用户名">
          <el-input v-model="forgetUserForm.username" autocomplete="off" placeholder="请输入用户名"></el-input>
        </el-form-item>
        <el-form-item label="手机号">
          <el-input v-model="forgetUserForm.phone" autocomplete="off" placeholder="请输入手机号"></el-input>
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="forgetPassDialogVis = false">取 消</el-button>
        <el-button type="primary" @click="resetPassword">确 定</el-button>
      </div>
    </el-dialog>

  </div>
</template>

<script>
import ValidCode from "@/conponents/ValidCode";
// 在components属性中,将ValidCode作为一个组件注册进来。这样就可以在模板中使用<valid-code>标签。
export default {
  name: "Login",
  components: {
    ValidCode
  },
  data() {

    // 验证码校验
    const validateCode = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请输入验证码'))
      } else if (value.toLowerCase() !== this.code) {
        callback(new Error('验证码错误'))
      } else {
        callback()
      }
    }

    return {
      forgetUserForm: {},   // 忘记密码的表单数据
      forgetPassDialogVis: false,
      code: '',  // 验证码组件传递过来的code
      user: {
        code: '',   // 表单里用户输入的code 验证码
        username: '',
        password: ''
      },
      // trigger属性指定了触发校验的事件类型,这里是在字段失去焦点时触发校验(blur事件)。
      rules: {
        username: [
          { required: true, message: '请输入账号', trigger: 'blur' },
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
        ],
        code: [
          { validator: validateCode, trigger: 'blur' }
        ],
      }
    }
  },
  created() {

  },
  methods: {
    handleForgetPass() {   //  初始化表单的数据
      this.forgetUserForm = {}
      this.forgetPassDialogVis = true
    },
    resetPassword() {
      this.$request.put('/password', this.forgetUserForm).then(res => {
        if (res.code === '200') {
          this.$message.success('重置成功')
          this.forgetPassDialogVis = false
        } else {
          this.$message.error(res.msg)
        }
      })
    },
    getCode(code) {
      this.code = code.toLowerCase()
    },
    login() {
      this.$refs['loginRef'].validate((valid) => {
        if (valid) {
          // 验证通过
          this.$request.post('/login', this.user).then(res => {
            if (res.code === '200') {
              this.$router.push('/')
              this.$message.success('登录成功')
              localStorage.setItem("honey-user", JSON.stringify(res.data))  // 存储用户数据
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

        This is a login page component written in Vue.js, which mainly includes a form and verification code functions. Users can enter their account number, password and verification code to log in.

        In the template part, Flex layout is used to center the login box and set the background color. The login box is divided into two parts: the left side is the picture display, and the right side is the login form. The login form uses el-formthe and el-inputcomponents of the Element UI component library to enter the account number, password and verification code respectively. The verification code section also introduces custom components ValidCodeto generate verification codes. When the login button is clicked, loginthe method will be triggered to log in. There is also a link below to register and forget your password.

        In the script section, methods are defined validateCodeto verify whether the verification code entered by the user is correct, and datarelated data and verification rules are defined in . getCodeThe method is used to receive the verification code value passed by the verification code component. loginThe method calls the background interface to perform the login operation. After successful login, it jumps to the home page and stores the user data locally.

$router.push('/register')It is the routing jump method in Vue.js. In this code, when the registration link is clicked $router.push('/register'), the method will be called to jump the route of the current page /registerto the registration page.

$routerIt is a routing instance provided by Vue Router, which contains some methods for navigation, such as pushadding new records to the history stack and jumping to the specified address.

Therefore, $router.push('/register')the function is to jump to the registration page. When the user clicks "Don't have an account yet? Please register", this method will be triggered and jump to the registration page.

3. Registration interface

<template>
  <div style="height: 100vh; display: flex; align-items: center; justify-content: center; background-color: #669fef">
    <div style="display: flex; background-color: white; width: 50%; border-radius: 5px; overflow: hidden">
      <div style="flex: 1">
        <img src="@/assets/register.png" alt="" style="width: 100%">
      </div>
      <div style="flex: 1; display: flex; align-items: center; justify-content: center">
        <el-form :model="user" style="width: 80%" :rules="rules" ref="registerRef">
          <div style="font-size: 20px; font-weight: bold; text-align: center; margin-bottom: 20px">欢迎注册后台管理系统</div>
          <el-form-item prop="username">
            <el-input prefix-icon="el-icon-user" size="medium" placeholder="请输入账号" v-model="user.username"></el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请输入密码" v-model="user.password"></el-input>
          </el-form-item>
          <el-form-item prop="confirmPass">
            <el-input prefix-icon="el-icon-lock" size="medium" show-password placeholder="请确认密码" v-model="user.confirmPass"></el-input>
          </el-form-item>
          <el-form-item prop="role">
            <el-radio-group v-model="user.role">
              <el-radio label="用户"></el-radio>
              <el-radio label="商家"></el-radio>
            </el-radio-group>
          </el-form-item>
          <el-form-item>
            <el-button type="info" style="width: 100%" @click="register">注 册</el-button>
          </el-form-item>
          <div style="display: flex">
            <div style="flex: 1">已经有账号了?请 <span style="color: #6e77f2; cursor: pointer" @click="$router.push('/login')">登录</span></div>
          </div>
        </el-form>
      </div>
    </div>

  </div>
</template>

<script>
// export default 的作用是将整个组件对象作为模块的默认导出,方便其他文件引入和使用。
export default {
  name: "Register",
  data() {
    // 验证码校验
    const validatePassword = (rule, confirmPass, callback) => {
      if (confirmPass === '') {
        callback(new Error('请确认密码'))
      } else if (confirmPass !== this.user.password) {
        callback(new Error('两次输入的密码不一致'))
      } else {
        callback()
      }
    }
    return {
      user: {
        username: '',
        password: '',
        confirmPass: ''
      },
      rules: {
        username: [
          { required: true, message: '请输入账号', trigger: 'blur' },
        ],
        password: [
          { required: true, message: '请输入密码', trigger: 'blur' },
        ],
        confirmPass: [
          { validator: validatePassword, trigger: 'blur' }
        ],
        role: [
          { required: true, message: '请选择角色', trigger: 'blur' },
        ],
      }
    }
  },
  created() {

  },
  methods: {
    register() {
      this.$refs['registerRef'].validate((valid) => {
        if (valid) {
          // 验证通过
          this.$request.post('/register', this.user).then(res => {
            if (res.code === '200') {
              this.$router.push('/login')
              this.$message.success('注册成功')
            } else {
              this.$message.error(res.msg)
            }
          })
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

 4. Backend interface

@RestController
public class WebController {

    @Resource
    UserService userService;


    @PostMapping("/login")
    public Result login(@RequestBody User user) {
        if (StrUtil.isBlank(user.getUsername()) || StrUtil.isBlank(user.getPassword())) {
            return Result.error("数据输入不合法");
        }
        user = userService.login(user);
        return Result.success(user);
    }

    @AuthAccess
    @PostMapping("/register")
    public Result register(@RequestBody User user) {
        if (StrUtil.isBlank(user.getUsername()) || StrUtil.isBlank(user.getPassword()) || StrUtil.isBlank(user.getRole())) {
            return Result.error("数据输入不合法");
        }
        if (user.getUsername().length() > 10 || user.getPassword().length() > 20) {
            return Result.error("数据输入不合法");
        }
        user = userService.register(user);
        return Result.success(user);
    }

}

Of course, a domestic tool class dependency is also introduced here: (or use other toolkits)

<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.18</version>
</dependency>
@Service
public class UserService {

    @Autowired
    UserMapper userMapper;

    // 验证用户账户是否合法
    public User login(User user) {
        // 根据用户名查询数据库的用户信息
        User dbUser = userMapper.selectByUsername(user.getUsername());
        if (dbUser == null) {
            // 抛出一个自定义的异常
            throw new ServiceException("用户名或密码错误");
        }
        if (!user.getPassword().equals(dbUser.getPassword())) {
            throw new ServiceException("用户名或密码错误");
        }
        // 生成token
        String token = TokenUtils.createToken(dbUser.getId().toString(), dbUser.getPassword());
        dbUser.setToken(token);
        return dbUser;
    }

    public User register(User user) {
        User dbUser = userMapper.selectByUsername(user.getUsername());
        if (dbUser != null) {
            // 抛出一个自定义的异常
            throw new ServiceException("用户名已存在");
        }
        user.setName(user.getUsername());
        userMapper.insert(user);
        return user;
    }

}

 Global exceptions and custom exceptions:

package com.example.springboot.exception;

import com.example.springboot.common.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
public class GlobalException {

    @ExceptionHandler(ServiceException.class)
    @ResponseBody
    public Result serviceException(ServiceException e) {
        return Result.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result globalException(Exception e) {
        e.printStackTrace();
        return Result.error("500", "系统错误");
    }
}
package com.example.springboot.exception;

import lombok.Getter;

@Getter
public class ServiceException extends RuntimeException {

    private final String code;

    public ServiceException(String msg) {
        super(msg);
        this.code = "500";
    }

    public ServiceException(String code, String msg) {
        super(msg);
        this.code = code;
    }
}

Guess you like

Origin blog.csdn.net/weixin_49171365/article/details/132424614