【项目实践】SpringBoot+vue家教平台项目添加登录功能

一.概述

要为家教平台添加登录功能,建议先从后端开始,因为这样可以确保前端有一个明确的 API 进行交互,从而在开发前端时更容易进行调试和验证。

后端开发

  1. 创建登录接口

    • 在后端创建一个 RESTful API,用于处理用户登录请求。
    • 验证用户提交的登录号码和密码是否与数据库中的记录匹配。
  2. 后端验证流程

    • 接收请求:通过 POST 请求接收用户提交的登录号码和密码。
    • 查询数据库:在数据库中查找对应的用户记录(通常使用 MyBatis 或 JPA)。
    • 密码验证:比较提交的密码和数据库中存储的密码(注意:存储密码时应使用哈希加密,如 bcrypt)。
    • 生成令牌:如果验证成功,生成并返回一个 JWT(JSON Web Token)或其他形式的令牌,用于后续的身份验证。
    • 返回响应:返回登录结果(成功或失败)以及令牌(如果成功)。
  3. 后端代码

    • UserService 负责用户查询和密码验证逻辑。
    • JWTTokenProvider 负责生成 JWT。

前端开发

  1. 创建登录页面

    • 创建一个简单的登录页面,用户可以输入他们的登录号码和密码。
    • 在用户点击“登录”按钮后,前端会向后端的登录接口发送请求。
  2. 前端验证流程

    • 表单验证:在提交之前,前端可以先进行简单的表单验证(例如,检查号码格式是否正确,密码是否为空等)。
    • 发送请求:使用 axiosfetch 向后端发送登录请求(POST 请求)。
    • 处理响应:根据后端返回的响应,决定登录是否成功。如果成功,将收到的令牌存储在 localStoragesessionStorage 中,并重定向到主页面。
    • 错误处理:如果登录失败,显示适当的错误信息给用户。
  3. 前端代码

验证

  • 后端验证:使用 Postman 或 cURL 来测试后端登录接口,确保它能够正确处理请求并返回预期的响应。
  • 前端验证:在浏览器中测试登录页面,输入有效和无效的登录号码及密码,检查前端如何响应。

综合建议

  1. 先写后端:确保后端登录逻辑和 API 接口正常工作,并返回正确的响应。
  2. 再写前端:开发登录页面并连接到后端 API,处理响应和错误显示。

 

二.后端编写:

User实体类:

package com.example.entity;

import lombok.Data;

@Data
public class User {
    private Integer userId;
    private String userPhone;
    private String userPassword;
}

UserMapper接口:

package com.example.mapper;

import com.example.entity.User;

public interface UserMapper {
    User selectByPhone1(String userPhone);
}

UserMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--对应接口-->
<mapper namespace="com.example.mapper.UserMapper">
    <select id="selectByPhone1" resultType="com.example.entity.User">
        select * from user_view where user_phone=#{userPhone}
    </select>
</mapper>

UserService接口:

package com.example.service;

import com.example.entity.User;

public interface UserService {

    User selectByPhone2(String userPhone);

    //比较用户输入的密码和数据库中的密码
    boolean checkPassword(String rawPassword, String storedPassword);
}

UserServiceImpl实现类:

package com.example.service.impl;

import com.example.entity.User;
import com.example.mapper.UserMapper;
import com.example.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User selectByPhone2(String userPhone) {
        User user = userMapper.selectByPhone1(userPhone);
        return user;
    }

    @Override
    public boolean checkPassword(String rawPassword, String storedPassword) {
        return rawPassword.equals(storedPassword);
    }
}

UserController实现类:

package com.example.controller;
//登录验证类

import com.example.config.ApiResponse;
import com.example.entity.User;
import com.example.service.UserService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
public class UserController {

    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public ResponseEntity<ApiResponse<?>> login(@RequestBody LoginRequest loginRequest) {
        User user = userService.selectByPhone2(loginRequest.getPhoneNumber());
        if (user != null && userService.checkPassword(loginRequest.getPassword(), user.getUserPassword())) {
            // 在实际应用中,你可能会生成并返回一个 JWT 或 session token
            return ResponseEntity.ok(ApiResponse.success("登录成功",user));
        } else {
            return ResponseEntity.status(401).body(ApiResponse.error("登录失败"));
        }
    }

    // 登录请求的内部类
    @Data
    public static class LoginRequest {
        private String phoneNumber;
        private String password;
    }


}

三.使用postman进行后端验证:

24b29c60d8624b32aaaf44bc8ae88648.png

四.前端代码编写:

Login.vue:

<template>
  <div class="login-container">
    <h2>登录界面</h2>
    <form @submit.prevent="login">
      <div class="form-group">
        <label for="phoneNumber">账号:</label>
        <input
            type="text"
            id="phoneNumber"
            v-model="phoneNumber"
            placeholder="请输入登录账号"
            required
        />
      </div>
      <div class="form-group">
        <label for="password">密码:</label>
        <input
            type="password"
            id="password"
            v-model="password"
            placeholder="请输入密码"
            required
        />
      </div>
      <button type="submit">登录</button>
    </form>
    <p v-if="errorMessage" class="error">{{ errorMessage }}</p>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      phoneNumber: '',
      password: '',
      errorMessage: null,
    };
  },
  methods: {
    async login() {
      try {
        // console.log('发送的手机号:', this.phoneNumber);
        // console.log('发送的密码:', this.password);
        const response = await axios.post('http://localhost:8081/api/auth/login', {
          phoneNumber: this.phoneNumber,
          password: this.password,
        });
        // console.log('后端返回的响应:', response.data);
        if (response.data.success) {
          // 登录成功,触发父组件的登录成功事件
          this.$emit('login-success');
          alert(response.data.message);
        } else {
          this.errorMessage = response.data.message;
        }
      } catch (error) {
        this.errorMessage = '登录失败,请检查账号和密码。';
      }
    },
  },
};
</script>

<style scoped>
.login-container {
  max-width: 400px;
  margin: 50px auto;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.form-group {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: bold;
}

input[type="text"],
input[type="password"] {
  width: 100%;
  padding: 8px;
  box-sizing: border-box;
  border: 1px solid #ccc;
  border-radius: 4px;
}

button {
  width: 100%;
  padding: 10px;
  background-color: #007bff;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 16px;
}

button:hover {
  background-color: #0056b3;
}

.error {
  color: red;
  margin-top: 15px;
  text-align: center;
}
</style>

App.vue:

 

<template>
 <div>
   <el-container v-if="isLoggedIn" style="height: 100%;">
     <el-header style="width: 100% ;padding: 0">
       <Header />
     </el-header>
     <div class="divider"></div> <!-- 分割线 -->
     <el-container style="width: 100%">
       <el-aside width="200px">
         <Sidebar @select="updateContent" />
       </el-aside>
       <el-main>
         <Content :selectedSection="selectedSection" />
       </el-main>
     </el-container>
   </el-container>

   <div v-else>
     <Login @login-success="handleLoginSuccess" />
   </div>
 </div>



</template>

<script>
import Header from './components/Header.vue';
import Sidebar from './components/Sidebar.vue';
import Content from './components/Content.vue';
import Login from './components/Login.vue';

export default {
  components: {
    Header,
    Sidebar,
    Content,
    Login,
  },
  data() {
    return {
      selectedSection: '1',
      isLoggedIn: false, // 登录状态
    };
  },
  methods: {
    updateContent(index) {
      this.selectedSection = index;
    },
    handleLoginSuccess() {
      this.isLoggedIn = true; // 登录成功后更新状态
    }
  }
};
</script>

<style>
body, html, #app {
  margin: 0;
  padding: 0;
  height: 100%;
}
.divider {
  height: 1px;
  background-color: #e0e0e0; /* 分割线颜色,可以根据需要调整 */
  margin: 0;
}

</style>

出现的问题及学到的方法:

1.一直显示登录失败,原因是前端url选择错误(应使用http://localhost:/api/auth/login')完整路径

2.可以在前后端输出数据进行错误检验:

86c79b31a5ca4c5c94b4e07301370a78.png

3548be5e8417484d96581d43ad718a36.png

3.侧边栏及主体大小不固定,添加CSS样式进行修改:

<style>
.sidebar {
  height: 100vh;
  border-right: none;
  display: flex;
  flex-direction: column;
}
.el-menu {
  flex: 1;
  border-right: none !important; /* 强制移除右侧边框 */
}
</style>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值