文章大纲
加载中...

待办清单项目12:完善注册和登录功能

5/8/2025 280 阅读
待办清单项目12:完善注册和登录功能

封装WebUtils工具类

1.打开idea后端项目,在utils包内创建一个WebUtils类文件

粘贴如下的代码:

package com.youngshu.todolist.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.youngshu.todolist.common.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.text.SimpleDateFormat;

public class WebUtils {
    private static ObjectMapper objectMapper;
    // 初始化ObjectMapper
    static{
        objectMapper=new ObjectMapper();
        // 设置JSON和Object转换时的时间日期格式
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    }
    // 从请求中获取JSON串并转换为Object
    public static <T> T readJson(HttpServletRequest request, Class<T> clazz) {
        T t = null;
        BufferedReader reader = null;
        try {
            reader = request.getReader();
            StringBuffer buffer = new StringBuffer();
            String line = null;
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            t = objectMapper.readValue(buffer.toString(), clazz);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return t;
    }

    // 将Result对象转换成JSON串并放入响应对象
    public static void writeJson(HttpServletResponse response, Result result) {
        response.setContentType("application/json;charset=UTF-8");
        try {
            String json = objectMapper.writeValueAsString(result);
            response.getWriter().write(json);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.接下来用刚封装的类来简化代码,双击SysUserController文件,把验证用户名是否存在方面里面的代码改成下面这样

3.启动服务器,访问注册页面查看检验用户名是否可用的功能是否还可用使用

完成注册页的功能

1.用vscode打开前端项目,在命令行中运行 npm install axios指令来按住axios

2.在src文件夹中新建utils文件夹,然后在utils文件中新建一个request.js文件

request.js文件的代码如下:

import axios from 'axios'

// 创建instance实例
const instance = axios.create({
    baseURL: 'http://localhost:8080/'
})

// 添加请求拦截
instance.interceptors.request.use(
    // 设置请求头配置信息
    config => {
        // 处理指定的请求头
        return config
    },
    // 设置请求错误处理函数
    error => {
        return Promise.reject(error)
    }
)
// 添加响应拦截器
instance.interceptors.response.use(
    // 设置响应正确时的处理函数
    response => {
        return response
    },
    // 设置响应异常时的处理函数
    error => {
        return Promise.reject(error)
    }
)
// 默认导出
export default instance

3.修改注册组件的代码,把Regist.vue文件中的代码改成下面这样

<script setup>
import { ref, reactive } from 'vue'
import request from '../utils/request'

let registUser = reactive({
    username: "",
    userpassword: ""
})

let usernameMsg = ref('')
let userPwdMsg = ref('')
let reUserPwdMsg = ref('')
let reUserPwd = ref('')

async function checkUsername() {
    let usernameReg =/^[A-Za-z](?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{7,}$/;//用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位
    if (!usernameReg.test(registUser.username)) {
        usernameMsg.value = "格式有误,用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位"
        return false
    }
    // 继续校验用户名是否被占用
    let { data } = await request.post(`TodoList/user/checkUsername?username=${registUser.username}`)
    if (data.code!== 200) {
        usernameMsg.value = "用户名已被占用"
        return false
    }
    usernameMsg.value = ""
    return true
}

async function checkPassword(){
    let userPwdReg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
    if(!userPwdReg.test(registUser.userpassword)){
        userPwdMsg.value="密码至少包含字母、数字、特殊字符中的两种,长度至少6位"
        return false
    }
    userPwdMsg.value=""
    return true
}

async function checkReUserPwd() {
    let userPwdReg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
    if (!userPwdReg.test(reUserPwd.value)) {
        reUserPwdMsg.value = "密码至少包含字母、数字、特殊字符中的两种,长度至少6位";
        return false;
    }
    if (registUser.userpassword!== reUserPwd.value) {
        reUserPwdMsg.value = "两次密码不一致";
        return false;
    }
    reUserPwdMsg.value = "";
    return true;
}
async function regist() {
      // 校验所有的输入框是否合法
    let flag1 = await checkUsername()
    let flag2 = await checkPassword()
    let flag3 = await checkReUserPwd()
    console.log(flag1)
    console.log(flag2)
    console.log(flag3)
    if (flag1 && flag2 && flag3) {
        request.post("TodoList/user/regist", registUser)
    } else {
        alert("校验不通过,请求再次检查数据")
    }
}

</script>

<template>
  <div class="register-wrapper">
    <div class="register-card">
      <div class="register-header">
        <!-- 使用不同的 SVG 图标 -->
        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-plus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
        <h2>创建账户</h2>
        <p>加入我们,开始管理您的待办事项!</p>
      </div>
      <form @submit.prevent="handleRegister" class="register-form">
        <!-- 用户名输入组 -->
        <div class="input-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="registUser.username"
            placeholder="用户名"
            required
            @blur="checkUsername()"
          />
          <p v-if="usernameMsg" id="username-error" class="error-message">{{ usernameMsg }}</p>
        </div>
        <!-- 密码输入组 -->
        <div class="input-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="registUser.userpassword"
            placeholder="请输入密码"
            required
            @blur="checkPassword()"
          />
          <p v-if="userPwdMsg" id="password-error" class="error-message">{{ userPwdMsg }}</p>
        </div>
        <!-- 确认密码输入组 -->
        <div class="input-group">
          <label for="confirmPassword">确认密码</label>
          <input
            type="password"
            id="confirmPassword"
            v-model="reUserPwd"
            placeholder="请再次输入密码"
            required
            @blur="checkReUserPwd()"
          />
          <p v-if="reUserPwdMsg" id="confirm-password-error" class="error-message">{{ reUserPwdMsg }}</p>
        </div>
        <!-- 注册按钮 -->
        <button type="submit" class="btn-register" @click="regist()">注 册</button>
      </form>
      <!-- 页脚链接 -->
      <div class="register-footer">
        <router-link to="/login">已有账号?返回登录</router-link>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 整体布局和卡片样式 (与 Login.vue 类似) */
.register-wrapper {
  /* min-height: 100vh; */ /* 移除 min-height */
  flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
  display: flex;
  justify-content: center;
  align-items: center;
  /* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
  padding: 2rem; /* 保留内边距 */
}

.register-card {
  background: white;
  padding: 2.5rem 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 420px;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.register-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}

/* 头部样式 */
.register-header {
  margin-bottom: 2rem;
}

.register-header svg {
  color: #4361ee; /* 主题色 */
  margin-bottom: 0.5rem;
}

.register-header h2 {
  font-size: 1.8rem;
  color: #333;
  margin-bottom: 0.5rem;
}

.register-header p {
  color: #777;
  font-size: 0.95rem;
}

/* 表单样式 */
.register-form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem; /* 调整输入框组之间的基础间距 */
}

/* 输入框组样式 */
.input-group {
  text-align: left;
  margin-bottom: 0.7rem; /* 为错误信息预留空间 */
  position: relative; /* 使得错误信息可以定位 */
}

.input-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: #555;
  font-weight: 500;
  font-size: 0.9rem;
}

.input-group input {
  width: 100%;
  padding: 0.8rem 1rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box;
}

.input-group input:focus {
  outline: none;
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}

/* 错误状态的输入框 */
.input-group input.input-error {
  border-color: #ef476f; /* 红色边框 */
}

.input-group input.input-error:focus {
  border-color: #ef476f;
  box-shadow: 0 0 0 3px rgba(239, 71, 111, 0.1); /* 红色光晕 */
}

/* 错误信息样式 */
.error-message {
  color: #ef476f;
  font-size: 0.8rem;
  margin-top: 0.3rem;
  min-height: 1.1rem; /* 占据固定高度防止跳动 */
  /* position: absolute; */ /* 可以取消绝对定位,让其自然流动 */
  /* bottom: -1.2rem; */
  /* left: 0; */
  width: 100%; /* 确保宽度 */
  text-align: left;
}

/* 注册按钮样式 (与 Login.vue 类似,但类名不同) */
.btn-register {
  padding: 0.9rem;
  background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%);
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 1.1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 1rem; /* 与上方元素的间距 */
  letter-spacing: 1px;
}

.btn-register:hover {
  background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
  box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
  transform: translateY(-2px);
}

/* 页脚链接样式 (与 Login.vue 类似) */
.register-footer {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.register-footer a {
  color: #4361ee;
  text-decoration: none;
  transition: color 0.3s ease;
}

.register-footer a:hover {
  color: #3451db;
  text-decoration: underline;
}
</style>

4.在浏览器中访问前端页面,若输入有误,会有响应的提示,若输入正确,点了注册按钮后,在网络中可用看到我们在往服务器端发送注册请求,在负载里面携带了json格式的用户名和密码

5.打开IDEA后端项目,找到SysUserController中的regist方法,把代码改成下面这样:

// 1 接收客户端提交的json参数,并转换为User对象,获取信息
SysUser registUser = WebUtils.readJson(req, SysUser.class);
// 2 调用服务层方法,完成注册功能
//将参数放入一个SysUser对象中,在调用regist方法时传入
int rows = sysUserService.regist(registUser);
// 3 根据注册结果(成功 失败) 做页面跳转
Result result = Result.ok(null);
if (rows < 1) {
    result = Result.build(null, ResultCodeEnum.USERNAME_USED);
}
WebUtils.writeJson(resp, result);

6.重新部署一下Tomcat服务器

7.回到前端项目中,再次找到注册组件Regist.vue,把代码改成下面这样

<script setup>
import { ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
import request from '../utils/request'

let router = useRouter()

let registUser = reactive({
    username: "",
    user_pwd: ""
})

let usernameMsg = ref('')
let userPwdMsg = ref('')
let reUserPwdMsg = ref('')
let reUserPwd = ref('')

async function checkUsername() {
    let usernameReg =/^[A-Za-z](?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{7,}$/;//用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位
    if (!usernameReg.test(registUser.username)) {
        usernameMsg.value = "格式有误,用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位"
        return false
    }
    // 继续校验用户名是否被占用
    let { data } = await request.post(`TodoList/user/checkUsername?username=${registUser.username}`)
    if (data.code!== 200) {
        usernameMsg.value = "用户名已被占用"
        return false
    }
    usernameMsg.value = ""
    return true
}

async function checkPassword(){
    let userPwdReg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
    if(!userPwdReg.test(registUser.user_pwd)){
        userPwdMsg.value="密码至少包含字母、数字、特殊字符中的两种,长度至少6位"
        return false
    }
    userPwdMsg.value=""
    return true
}

async function checkReUserPwd() {
    let userPwdReg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
    if (!userPwdReg.test(reUserPwd.value)) {
        reUserPwdMsg.value = "密码至少包含字母、数字、特殊字符中的两种,长度至少6位";
        return false;
    }
    if (registUser.user_pwd!== reUserPwd.value) {
        reUserPwdMsg.value = "两次密码不一致";
        return false;
    }
    reUserPwdMsg.value = "";
    return true;
}
async function regist() {
  // 校验所有的输入框是否合法
  let flag1 = await checkUsername()
  let flag2 = await checkPassword()
  let flag3 = await checkReUserPwd()
  if (flag1 && flag2 && flag3) {
      let {data} = await request.post("TodoList/user/regist", registUser)
      if (data.code == 200) {
          // 注册成功跳转 登录页
          alert("注册成功,快去登录吧")
          router.push("/login")
      } else {
          alert("抱歉,用户名被抢注了")
      }
  } else {
      alert("校验不通过,请求再次检查数据")
  }
}

</script>

<template>
  <div class="register-wrapper">
    <div class="register-card">
      <div class="register-header">
        <!-- 使用不同的 SVG 图标 -->
        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-user-plus"><path d="M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="8.5" cy="7" r="4"></circle><line x1="20" y1="8" x2="20" y2="14"></line><line x1="23" y1="11" x2="17" y2="11"></line></svg>
        <h2>创建账户</h2>
        <p>加入我们,开始管理您的待办事项!</p>
      </div>
      <form @submit.prevent="handleRegister" class="register-form">
        <!-- 用户名输入组 -->
        <div class="input-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="registUser.username"
            placeholder="用户名"
            required
            @blur="checkUsername()"
          />
          <p v-if="usernameMsg" id="username-error" class="error-message">{{ usernameMsg }}</p>
        </div>
        <!-- 密码输入组 -->
        <div class="input-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="registUser.user_pwd"
            placeholder="请输入密码"
            required
            @blur="checkPassword()"
          />
          <p v-if="userPwdMsg" id="password-error" class="error-message">{{ userPwdMsg }}</p>
        </div>
        <!-- 确认密码输入组 -->
        <div class="input-group">
          <label for="confirmPassword">确认密码</label>
          <input
            type="password"
            id="confirmPassword"
            v-model="reUserPwd"
            placeholder="请再次输入密码"
            required
            @blur="checkReUserPwd()"
          />
          <p v-if="reUserPwdMsg" id="confirm-password-error" class="error-message">{{ reUserPwdMsg }}</p>
        </div>
        <!-- 注册按钮 -->
        <button type="submit" class="btn-register" @click="regist()">注 册</button>
      </form>
      <!-- 页脚链接 -->
      <div class="register-footer">
        <router-link to="/login">已有账号?返回登录</router-link>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* 整体布局和卡片样式 (与 Login.vue 类似) */
.register-wrapper {
  /* min-height: 100vh; */ /* 移除 min-height */
  flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
  display: flex;
  justify-content: center;
  align-items: center;
  /* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
  padding: 2rem; /* 保留内边距 */
}

.register-card {
  background: white;
  padding: 2.5rem 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 420px;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.register-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}

/* 头部样式 */
.register-header {
  margin-bottom: 2rem;
}

.register-header svg {
  color: #4361ee; /* 主题色 */
  margin-bottom: 0.5rem;
}

.register-header h2 {
  font-size: 1.8rem;
  color: #333;
  margin-bottom: 0.5rem;
}

.register-header p {
  color: #777;
  font-size: 0.95rem;
}

/* 表单样式 */
.register-form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem; /* 调整输入框组之间的基础间距 */
}

/* 输入框组样式 */
.input-group {
  text-align: left;
  margin-bottom: 0.7rem; /* 为错误信息预留空间 */
  position: relative; /* 使得错误信息可以定位 */
}

.input-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: #555;
  font-weight: 500;
  font-size: 0.9rem;
}

.input-group input {
  width: 100%;
  padding: 0.8rem 1rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box;
}

.input-group input:focus {
  outline: none;
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1);
}

/* 错误状态的输入框 */
.input-group input.input-error {
  border-color: #ef476f; /* 红色边框 */
}

.input-group input.input-error:focus {
  border-color: #ef476f;
  box-shadow: 0 0 0 3px rgba(239, 71, 111, 0.1); /* 红色光晕 */
}

/* 错误信息样式 */
.error-message {
  color: #ef476f;
  font-size: 0.8rem;
  margin-top: 0.3rem;
  min-height: 1.1rem; /* 占据固定高度防止跳动 */
  /* position: absolute; */ /* 可以取消绝对定位,让其自然流动 */
  /* bottom: -1.2rem; */
  /* left: 0; */
  width: 100%; /* 确保宽度 */
  text-align: left;
}

/* 注册按钮样式 (与 Login.vue 类似,但类名不同) */
.btn-register {
  padding: 0.9rem;
  background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%);
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 1.1rem;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 1rem; /* 与上方元素的间距 */
  letter-spacing: 1px;
}

.btn-register:hover {
  background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
  box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
  transform: translateY(-2px);
}

/* 页脚链接样式 (与 Login.vue 类似) */
.register-footer {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.register-footer a {
  color: #4361ee;
  text-decoration: none;
  transition: color 0.3s ease;
}

.register-footer a:hover {
  color: #3451db;
  text-decoration: underline;
}
</style>

8.在浏览器中尝试注册新用户,看是否能成功

完善登录页的功能

1.修改Login.vue文件

把代码改成下面这样:

<script setup>

import { ref, reactive } from 'vue'
import request from '../utils/request'
import { useRouter } from 'vue-router'
const router = useRouter()

let loginUser = reactive({
    username: "",
    user_pwd: ""
})

let usernameMsg = ref("")
let userPwdMsg = ref("")


function checkUsername(){
  // 用正则表达式验证用户名
  // 用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位
  const reg = /^[A-Za-z](?=.*[A-Za-z])(?=.*\d)(?=.*[^A-Za-z0-9]).{7,}$/;
  if (reg.test(loginUser.username)) {
    usernameMsg.value = '';
    return true;
  } else {
    usernameMsg.value = '用户名必须以英文字母开头,必须包含英文字母、数字和特殊字符三种字符,长度不少于8位';
    return false;
  }
};
function checkPassword(){
  // 用正则表达式验证密码
  const reg = /^(?:(?=.*[A-Za-z])(?=.*\d)|(?=.*[A-Za-z])(?=.*[^A-Za-z0-9])|(?=.*\d)(?=.*[^A-Za-z0-9])).{6,}$/; // 密码至少包含字母、数字、特殊字符中的两种,长度至少6位
  if (reg.test(loginUser.user_pwd)) {
    userPwdMsg.value = '';
    return true;
  } else {
    userPwdMsg.value = '// 密码至少包含字母、数字、特殊字符中的两种,长度至少6位';
    return false;
  }
};
async function login() {
  var flag = checkUsername() && checkPassword();
  if (!flag) {
    return;
  }
  let {data} = await request.post('TodoList/user/login',loginUser);
  if (data.code === 200) {
    alert("登录成功");
    router.push('/todolist');
  } else if (data.code === 503) {
    alert("密码错误");
  } else if (data.code === 502) {
    alert("用户名不存在");  
  }
  else {
    alert("未知错误,登录失败");
  }
};
  

</script>

<template>
  <div class="login-wrapper">
    <div class="login-card">
      <div class="login-header">
        <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-check-square"><polyline points="9 11 12 14 22 4"></polyline><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path></svg>
        <h2>用户登录</h2>
        <p>欢迎回来!请登录您的账户。</p>
      </div>
      <form @submit.prevent="handleLogin" class="login-form">
        <div class="input-group">
          <label for="username">用户名</label>
          <input
            type="text"
            id="username"
            v-model="loginUser.username"
            @blur="checkUsername()"
            placeholder="请输入用户名"
            required
          />
          <div v-if="usernameMsg" class="error-msg">{{ usernameMsg }} </div>
        </div>
        <div class="input-group">
          <label for="password">密码</label>
          <input
            type="password"
            id="password"
            v-model="loginUser.user_pwd"
            @blur = "checkPassword()"
            placeholder="请输入密码"
            required
          />
          <div v-if="userPwdMsg" class="error-msg">{{ userPwdMsg }} </div>
        </div>
        <button type="submit" class="btn-login" @click="login()">登 录</button>
      </form>
      <div class="login-footer">
        <router-link to="/regist">还没有账号?立即注册</router-link>
      </div>
    </div>
  </div>
</template>

<style scoped>
.error-msg {
  color: red;
  font-size: 0.8rem;
  margin-top: 0.2rem;
  }
.login-wrapper {
  /* min-height: 100vh; */ /* 移除 min-height */
  flex-grow: 1; /* 让 wrapper 在 main-content 内填充可用空间 */
  display: flex;
  justify-content: center;
  align-items: center;
  /* background-color: #fff; */ /* 背景色由 main-content 或 body 控制,或者按需保留 */
  padding: 2rem; /* 保留内边距 */
}

.login-card {
  background: white;
  padding: 2.5rem 3rem;
  border-radius: 12px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
  width: 100%;
  max-width: 420px;
  text-align: center;
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}

.login-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}


.login-header {
  margin-bottom: 2rem;
}

.login-header svg {
  color: #4361ee;
  margin-bottom: 0.5rem;
}

.login-header h2 {
  font-size: 1.8rem;
  color: #333;
  margin-bottom: 0.5rem;
}

.login-header p {
  color: #777;
  font-size: 0.95rem;
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: 1.5rem; /* 增加输入框间距 */
}

.input-group {
  text-align: left;
}

.input-group label {
  display: block;
  margin-bottom: 0.5rem;
  color: #555;
  font-weight: 500;
  font-size: 0.9rem;
}

.input-group input {
  width: 100%;
  padding: 0.8rem 1rem; /* 增加内边距 */
  border: 1px solid #ddd;
  border-radius: 6px; /* 更圆润的边角 */
  font-size: 1rem;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
  box-sizing: border-box; /* 确保 padding 不会撑大元素 */
}

.input-group input:focus {
  outline: none;
  border-color: #4361ee;
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.1); /* 添加聚焦光晕效果 */
}

.btn-login {
  padding: 0.9rem; /* 增加按钮内边距 */
  background: linear-gradient(90deg, #4361ee 0%, #3a53c0 100%); /* 渐变背景 */
  color: white;
  border: none;
  border-radius: 6px;
  font-size: 1.1rem; /* 增大字体 */
  font-weight: 600; /* 加粗字体 */
  cursor: pointer;
  transition: all 0.3s ease;
  margin-top: 1rem; /* 与上方输入框的间距 */
  letter-spacing: 1px; /* 增加字间距 */
}

.btn-login:hover {
  background: linear-gradient(90deg, #3a53c0 0%, #4361ee 100%);
  box-shadow: 0 4px 15px rgba(67, 97, 238, 0.3);
  transform: translateY(-2px);
}

.login-footer {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.login-footer a {
  color: #4361ee;
  text-decoration: none;
  transition: color 0.3s ease;
}

.login-footer a:hover {
  color: #3451db;
  text-decoration: underline;
}
</style>

2.来到IDEA,打开SysUserController

把代码改成下面这样:

package com.youngshu.todolist.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.youngshu.todolist.common.Result;
import com.youngshu.todolist.common.ResultCodeEnum;
import com.youngshu.todolist.pojo.SysUser;
import com.youngshu.todolist.service.SysUserService;
import com.youngshu.todolist.service.impl.SysUserServiceImpl;
import com.youngshu.todolist.utils.WebUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import com.youngshu.todolist.utils.MD5Utils;

// 该类处理用户相关的请求
@WebServlet("/user/*")
public class SysUserController extends BaseController{
    private SysUserService sysUserService = new SysUserServiceImpl();
    // 注册用户
    protected void regist(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1 接收客户端提交的json参数,并转换为User对象,获取信息
        SysUser registUser = WebUtils.readJson(req, SysUser.class);
        // 2 调用服务层方法,完成注册功能
        //将参数放入一个SysUser对象中,在调用regist方法时传入
        int rows = sysUserService.regist(registUser);
        // 3 根据注册结果(成功 失败) 做页面跳转
        Result result = Result.ok(null);
        if (rows < 1) {
            result = Result.build(null, ResultCodeEnum.USERNAME_USED);
        }
        WebUtils.writeJson(resp, result);
    }
    // 登录用户
    protected void login(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //1 接收用户名和密码
        SysUser sysUser = WebUtils.readJson(req, SysUser.class);
        //2 调用服务层方法,根据用户名查询用户信息
        SysUser loginUser = sysUserService.findByUsername(sysUser.getUsername());
        Result result = null;
        if (null == loginUser) {
            result = Result.build(null, ResultCodeEnum.USERNAEM_ERROR);
        } else if (!MD5Utils.md5encode(sysUser.getUser_pwd()).equals(loginUser.getUser_pwd())) {
            result = Result.build(null, ResultCodeEnum.PASSWORD_ERROR);
        } else {
            result = Result.ok(null);
        }
        // 3将登录结果响应给客户端
        WebUtils.writeJson(resp, result);
    }
    // 验证用户名是否存在
    protected void checkUsername(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 获取请求参数
        String username = req.getParameter("username");
        // 2. 调用服务层方法,根据用户名查询用户信息
        SysUser sysUser = sysUserService.findByUsername(username);
        // 3.设置响应信息
        Result result =Result.ok( null);
        if(null != sysUser){
            result= Result.build( null, ResultCodeEnum.USERNAME_USED);
        }
        // 将result对象转换为JSON串响应给客户端
        WebUtils.writeJson(resp,result);
    }
}

3.打开CROSFilter文件

把代码改成下面这样:

package com.youngshu.todolist.filter;

import com.youngshu.todolist.pojo.SysUser;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;

import java.io.IOException;


@WebFilter(urlPatterns = {"/*"})
public class CROSFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        httpResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpResponse.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        httpResponse.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
        httpResponse.setHeader("Access-Control-Max-Age", "3600");

        // 处理预检请求,直接返回
        if ("OPTIONS".equalsIgnoreCase(httpRequest.getMethod())) {
            httpResponse.setStatus(HttpServletResponse.SC_OK);
            return;
        }
        chain.doFilter(request, response);
    }

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
            // 初始化方法
        }

        @Override
        public void destroy() {
            // 销毁方法
        }
}

4.重启Tomcat服务器,在前端项目的登录页面中输入正确的用户名和密码,验证是否可以登录成功并跳转到待办事项页面


评论 (0)

暂无评论,来发表第一条评论吧!