SpingBoot的项目实战--模拟电商【2.登录】

news2024/11/28 19:02:43

🥳🥳Welcome Huihui's Code World ! !🥳🥳

接下来看看由辉辉所写的关于SpringBoot电商项目的相关操作吧 

目录

🥳🥳Welcome Huihui's Code World ! !🥳🥳

一.功能需求

二.代码编写

1.登录功能的完成

2.全局异常的处理

3.登录密码的两次加密

(1)为什么要加密两次

(2)整体的加密流程

(3)具体流程的代码实现

①前端加密

②加密之后传到后端

③后端拿取用户信息

⑤登录测试


一.功能需求

①完成用户登录功能

②用户的各种错误操作都需要给出相应的错误提示,而不是抛出异常【全局异常处理】

③用户登录的密码的两次加密

        表单数据➡后端【加密】

        后端数据➡数据库【加密】

④用户登录成功之后,需要在首页显示出登录的用户的昵称【Redis+Cookie】

⑤用户输入表单时,前端【表单验证】和后端【JSR303】都需要有相对应的验证

二.代码编写

1.登录功能的完成

package com.wh.easyshop.service.impl;

import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.wh.easyshop.model.User;
import com.wh.easyshop.mapper.UserMapper;
import com.wh.easyshop.resp.JsonResponseBody;
import com.wh.easyshop.resp.JsonResponseStatus;
import com.wh.easyshop.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wh.easyshop.vo.UserVo;
import org.springframework.stereotype.Service;

import javax.swing.*;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author wh
 * @since 2023-12-27
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    /**
     * 登录
     * @param userVo
     * @return
     */
    public JsonResponseBody<?> login (UserVo userVo){
        //如果传过来的电话号码(登录名)为空,那么提示用户相应的信息
        if(userVo.getMobile()==null){
            return JsonResponseBody.other(JsonResponseStatus.LOGIN_MOBILE_INFO);
        }
        //如果传过来的密码为空,那么提示用户相应的信息
        if(userVo.getPassword()==null){
            return JsonResponseBody.other(JsonResponseStatus.LOGIN_PASSWORD_INFO);
        }
        User user = getOne(new QueryWrapper<User>().lambda()
                //判断用户名以及密码是否一致
                .eq(User::getId, userVo.getMobile())
                .eq(User::getPassword, userVo.getPassword()));
        //如果内容匹配不成功,那么提示用户相应的信息
        if(user==null){
            return JsonResponseBody.other(JsonResponseStatus.LOGIN_NO_EQUALS);
        }
        //如果带来的信息都一致,则提示成功
        return  JsonResponseBody.success();
    }
}

其中用到的响应类

package com.wh.easyshop.resp;

import lombok.Data;

@Data
public class JsonResponseBody<T> {

    private Integer code;
    private String msg;
    private T data;
    private Long total;

    private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data) {
        this.code = jsonResponseStatus.getCode();
        this.msg = jsonResponseStatus.getMsg();
        this.data = data;
    }

    private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data, Long total) {
        this.code = jsonResponseStatus.getCode();
        this.msg = jsonResponseStatus.getMsg();
        this.data = data;
        this.total = total;
    }

    public static <T> JsonResponseBody<T> success() {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, null);
    }

    public static <T> JsonResponseBody<T> success(T data) {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, data);
    }

    public static <T> JsonResponseBody<T> success(T data, Long total) {
        return new JsonResponseBody<T>(JsonResponseStatus.OK, data, total);
    }

    public static <T> JsonResponseBody<T> unknown() {
        return new JsonResponseBody<T>(JsonResponseStatus.UN_KNOWN, null);
    }

    public static <T> JsonResponseBody<T> other(JsonResponseStatus jsonResponseStatus) {
        return new JsonResponseBody<T>(jsonResponseStatus, null);
    }

}
package com.wh.easyshop.resp;

import lombok.Getter;

@Getter
public enum JsonResponseStatus {

    OK(200, "OK"),
    UN_KNOWN(500, "未知错误"),
    LOGIN_MOBILE_INFO(5001, "未携带手机号或手机号格式有误"),
    LOGIN_PASSWORD_INFO(5002, "未携带密码或不满足格式"),
    LOGIN_NO_EQUALS(5003, "登录信息不一致"),
    LOGIN_MOBILE_NOT_FOUND(5004, "登录手机号未找到"),
    ;

    private final Integer code;
    private final String msg;

    JsonResponseStatus(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getName(){
        return this.name();
    }

}

我这里为了规范,还建了一个vo类,而且也考虑到后面需要做验证,为了不污染实体类与数据库的连接,还是需要建一个vo类的

VO类:

  1. 数据传输:VO类可以用于封装客户端和服务器之间的数据传输,例如RESTful API的请求和响应对象。

  2. 数据库实体映射:VO类可以用于将数据库表的记录映射为Java对象,并进行数据的读取和存储操作。

  3. 领域模型中的值对象:在领域驱动设计(DDD)中,VO类可以用于表示领域模型中的值对象,如金额、日期范围等。

总之,VO类主要用于封装和传递数据,以提高代码的可读性、可维护性和可测试性。它们通常是不可变的,并且只包含属性和访问方法

package com.wh.easyshop.vo;

import lombok.Data;


@Data
public class UserVo {
    private String mobile;
    private String password;

}

但是上面的这个登录功能的代码只是很粗浅的,我们还需要将它进行升级

2.全局异常的处理

一个用户在输入自己的信息进行登录的时候,很可能会有一些非常规操作,一般这个时候,它会有一些错误以及异常抛出,我们可以使用全局异常进行处理

全局异常:

  1. 方便错误排查和日志记录:全局异常处理可以捕捉并记录异常信息,方便开发人员进行错误排查和系统故障分析。

  2. 提供友好的用户体验:通过合理的异常处理,可以向用户提供友好的错误提示,帮助他们理解发生的问题,并提供相应的解决方案

我先把原先的代码进行了修改,将前面有错误提示信息的地方,都换成异常【自定义异常】给它抛出

自定义异常

package com.wh.easyshop.exception;

import com.wh.easyshop.resp.JsonResponseStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class BusinessException extends RuntimeException {

    private JsonResponseStatus jsonResponseStatus;

}

但是这个异常抛出了,我们需要一个类来处理它--全局异常处理类,编写全局异常处理类,需要 用到一个注解@RestControllerAdvice

@RestControllerAdvice:

 @RestControllerAdvice 是 Spring 框架中的一个注解,用于定义全局异常处理器(Global Exception Handler)。

在 Spring MVC 中,当应用程序抛出异常时,可以使用 @ExceptionHandler 注解来处理该异常。但是,如果在多个控制器中都有相同的异常处理逻辑,那么需要在每个控制器中都编写相同的代码,这样会导致代码冗余和可维护性差。

        @RestControllerAdvice 的作用就是解决这个问题,它结合了                         @ControllerAdvice@ResponseBody 两个注解的功能,用于全局处理控制器抛出的异常,并返回相应的错误信息。

        使用 @RestControllerAdvice 注解的类可以包含多个被 @ExceptionHandler 注解修饰的方法,每个方法用于处理不同类型的异常。当应用程序抛出异常时,Spring 框架会根据异常的类型自动调用对应的异常处理方法。

        @RestControllerAdvice 类中的异常处理方法可以包含自定义的逻辑,比如记录日志、返回特定的错误信息等。通常,异常处理方法会返回一个包含错误信息的响应实体,供客户端进行处理。

        总之,@RestControllerAdvice 注解用于定义全局异常处理器,通过集中处理控制器抛出的异常,提高代码的可维护性和复用性。它可以在一个类中定义多个异常处理方法,根据异常类型自动调用相应的方法,并返回相应的错误信息。【其实简而言之,就是当controller抛出异常的时候,不会往外面抛了,这个注解相当于是@Controller的增强类,把@Controller给包裹起来了】

全局异常的编写

package com.wh.easyshop.exception;

import com.wh.easyshop.resp.JsonResponseBody;
import com.wh.easyshop.resp.JsonResponseStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Objects;

@RestControllerAdvice // 声明这是一个全局异常处理器类
@Slf4j // 使用log4j进行日志记录
public class GlobalExceptionHandler {

    // 处理业务异常
    @ExceptionHandler(BusinessException.class)
    public JsonResponseBody<?> exceptionBusinessException(BusinessException e) {
        JsonResponseStatus status = e.getJsonResponseStatus(); // 获取异常的状态信息
        log.info(status.getMsg()); // 记录日志
        return JsonResponseBody.other(status); // 返回状态信息
    }

    // 处理其他类型的异常
    @ExceptionHandler(Throwable.class)
    public JsonResponseBody<?> exceptionThrowable(Throwable e) {
        log.info(e.getMessage()); // 记录日志
        return JsonResponseBody.other(JsonResponseStatus.UN_KNOWN); // 返回未知状态信息
    }


}

3.登录密码的两次加密

(1)为什么要加密两次

        第一次加密防止前端传递数据时被截取

       

 第二次加密防止数据库泄露

(2)整体的加密流程

        MD5(MD5(pass明文+固定salt)+随机salt)
        第一次固定salt写死在前端
        第二次加密采用随机的salt 并将每次生成的salt保存在数据库中

(3)具体流程的代码实现

①前端加密

对用户输入的密码进行md5加密(固定的salt)

引入md5的js

<script src="http://www.gongjuji.net/Content/files/jquery.md5.js" type="text/javascript"></script>

使用MD5加密

<script>
		$("#login").click(()=>{
		let mobile=$("#mobile").val()
		let password=$("#password").val()
			password=$.md5(password)
			$.post(' ${springMacroRequestContext.contextPath}/user/login',{
				mobile,password
			},resp=>{

			},"json")
		})
	</script>

但是我们知道MD5它的加密方式是不可逆的,也很容易被解析,所以我们可以自己加盐进去,在前后都加上字符

②加密之后传到后端

将加密后的密码传递到后端

package com.wh.easyshop.controller;

import com.sun.corba.se.spi.orb.ParserImplBase;
import com.wh.easyshop.resp.JsonResponseBody;
import com.wh.easyshop.service.IUserService;
import com.wh.easyshop.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 * 用户信息表 前端控制器
 * </p>
 *
 * @author wh
 * @since 2023-12-27
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;
    @RequestMapping("/login")
    public JsonResponseBody<?> login(UserVo userVo){
        return userService.login(userVo);
    }

}
③后端拿取用户信息

使用用户id取出用户信息

 ④后端加密

后端对前端传过来的加密后的密码在进行md5加密(取出盐),然后与数据库中存储的密码进行对比

package com.wh.easyshop.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.wh.easyshop.exception.BusinessException;
import com.wh.easyshop.model.User;
import com.wh.easyshop.mapper.UserMapper;
import com.wh.easyshop.resp.JsonResponseBody;
import com.wh.easyshop.resp.JsonResponseStatus;
import com.wh.easyshop.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wh.easyshop.util.MD5Utils;
import com.wh.easyshop.vo.UserVo;
import org.springframework.stereotype.Service;

import javax.swing.*;

/**
 * <p>
 * 用户信息表 服务实现类
 * </p>
 *
 * @author wh
 * @since 2023-12-27
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    /**
     * 登录
     * @param userVo
     * @return
     */
    @Override
    public JsonResponseBody<?> login (UserVo userVo){
        //通过用户名拿到用户的信息
        User user = getOne(new QueryWrapper<User>().lambda().eq(User::getId, userVo.getMobile()), false);
        //如果内容匹配不成功,那么提示用户相应的信息
        if(user==null){
            throw  new BusinessException(JsonResponseStatus.LOGIN_MOBILE_NOT_FOUND);
        }
        //把数据库的盐值与前端的密码都拿出来,再加密一次【随机盐值】
        String secret = MD5Utils.formPassToDbPass(userVo.getPassword(), user.getSalt());
        //将数据库里面的密码与上面二次加密之后的密码进行比较,如果不一致就提示相应信息
        if(!user.getPassword().equals(secret)){
            throw  new BusinessException(JsonResponseStatus.LOGIN_NO_EQUALS);
        }
        //如果带来的信息都一致,则提示成功
        return  JsonResponseBody.success();
    }

}
⑤登录测试

🔺登录与注册之间的逻辑大概也是差不多的,再这里小编没有做注册了,但是没有做注册,我们的数据库中就没有数据,所以我们要使用debug将数据手动的加到数据库中,不然这个登录就永远都是失败的

把盐值拿到也放到数据库中,这里我用的是固定的盐值,大家也可以用时间戳等一些随机的不会重复的数字

package com.wh.easyshop.util;

import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

@Component
public class MD5Utils {

    //加密盐,与前端一致
    private static final String salt = "f1g2h3j4";

    public static String md5(String src) {
        return DigestUtils.md5DigestAsHex(src.getBytes(StandardCharsets.UTF_8));
    }

    public static String createSalt() {
        return UUID.randomUUID().toString().replace("-", "");
    }

    /**
     * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码,混淆固定盐salt,安全性更可靠
     */
    public static String inputPassToFormPass(String inputPass) {
        String str = salt.charAt(1) + String.valueOf(salt.charAt(5)) + inputPass + salt.charAt(0) + salt.charAt(3);
        return md5(str);
    }

    /**
     * 将后端密文密码+随机salt生成数据库的密码,混淆固定盐salt,安全性更可靠
     */
    public static String formPassToDbPass(String formPass, String salt) {
        String str = salt.charAt(7) + String.valueOf(salt.charAt(9)) + formPass + salt.charAt(1) + salt.charAt(5);
        return md5(str);
    }

    public static void main(String[] args) {
        String formPass = inputPassToFormPass("123456");
        System.out.println("前端加密密码:" + formPass);
        String salt = createSalt();
        System.out.println("后端加密随机盐:" + salt);
        String dbPass = formPassToDbPass(formPass, salt);
        System.out.println("后端加密密码:" + dbPass);
    }

}

好啦,今天的分享就到这了,希望能够帮到你呢!😊😊 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1341361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D展2D数学原理

今年早些时候&#xff0c;我为 MAKE 杂志写了一篇教程&#xff0c;介绍如何制作视频游戏角色的毛绒动物。 该技术采用给定的角色 3D 模型及其纹理&#xff0c;并以编程方式生成缝纫图案。 虽然我已经编写了一般摘要并将源代码上传到 GitHub&#xff0c;但我在这里编写了对使这一…

新版ONENET的物联网环境调节系统(esp32+onenet+微信小程序)

新版ONENET的物联网环境调节系统&#xff08;esp32onenet微信小程序&#xff09; 好久没用onenet突然发现它大更新了&#xff0c;现在都是使用新版的物联网开放平台&#xff0c;只有老用户还有老版的多协议接入&#xff0c;新用户是没有的&#xff0c;所以我顺便更新一下新的开…

百度CTO王海峰:文心一言用户规模破1亿

“文心一言用户规模突破1亿。”12月28日&#xff0c;百度首席技术官、深度学习技术及应用国家工程研究中心主任王海峰在第十届WAVE SUMMIT深度学习开发者大会上宣布。会上&#xff0c;王海峰以《文心加飞桨&#xff0c;翩然赴星河》为题作了主旨演讲&#xff0c;分享了飞桨和文…

微软为 Android 用户推出了人工智能助手 Copilot 应用程序

微软为 Android 用户推出了人工智能助手 Copilot 应用程序 - 与 ChatGPT 类似&#xff0c;它包括聊天机器人功能和 DALL-E 3 图像生成 - 该应用程序包括免费访问 OpenAI 的 GPT-4 模型&#xff0c;这是 ChatGPT 中的付费功能 - 发布微软将 Bing Chat 更名为 Copilot 您是否尝试…

Linux 线程概念

文章目录 前言线程的概念线程的操作操作的原理补充与说明 前言 ① 函数的具体说明被放在补充与说明部分 ② 只说些基础概念和函数使用 线程的概念 网络回答&#xff1a;Linux 线程是指在 Linux 操作系统中创建和管理的轻量级执行单元。线程是进程的一部分&#xff0c;与进程…

易趋产品升级(EasyTrack 11_V1.3) | 集成飞书、WPS、个性化设置,增强团队协作和用户体验

企业在项目管理过程中&#xff0c;经常会遇到项目信息同步不及时、沟通障碍以及管理软件使用不便捷等难题&#xff0c;导致团队协作效率低下。这种情况下&#xff0c;如果使用了多个办公软件&#xff08;如&#xff1a;钉钉、企业微信、项目管理软件等&#xff09;&#xff0c;…

【DC-DC】这是一款半亮 高亮 瀑闪的LED降压恒流刹车灯线路图

1产品描述 AP2402 是一款 PWM 工作模式,高效率、外围简单、内置功率管&#xff0c;适用于 5-100V输入的高精度降压 LED 恒流驱动芯片。输出功率可达 15W&#xff0c;电流 1.5A。AP2402 可实现三段功能切换&#xff0c;通过MODE1/2/3 切换三种功能模式&#xff1a;全亮&#xf…

STM32 学习(一)新建工程

本课程使用的stm32型号 引脚定义&#xff0c;有FT能接5v&#xff0c;没有FT能接3.3v 启动配置 第二种启动模式中&#xff0c;系统存储器中存放了一部分Bootloader程序&#xff0c;该程序可以接收串口的数据&#xff0c;然后刷新到主闪存中&#xff0c;这样就可以使用串口下载程…

【经验分享】日常开发中的故障排查经验分享(一)

目录 简介CPU飙高问题1、使用JVM命令排查CPU飙升100%问题2、使用Arthas的方式定位CPU飙升问题3、Java项目导致CPU飙升的原因有哪些&#xff1f;如何解决&#xff1f; OOM问题&#xff08;内存溢出&#xff09;1、如何定位OOM问题&#xff1f;2、OOM问题产生原因 死锁问题的定位…

uni-app引入vant表单(附源码)

新建项目 下载安装vant npm i vant main.js引入 import { Form } from vant; import { Field } from vant;Vue.use(Form); Vue.use(Field);代码引入 <van-form submit"onSubmit"><van-fieldclass"rePwd"v-model"username"name"请…

【51单片机系列】DS18B20温度传感器扩展实验之设计一个智能温控系统

本文是关于DS18B20温度传感器的一个扩展实验。 文章目录 一、相关元件介绍二、实验分析三、proteus原理图设计四、软件设计 本扩展实验实现的功能&#xff1a;利用DS18B20设计一个智能温度控制系统&#xff0c;具有温度上下限值设定。当温度高于上限值时&#xff0c;电机开启&a…

flask之文件管理系统-项目 JRP上线啦!!! ----Bug版

既然单人应用那么就不需要注册、登录了&#xff0c;太麻烦&#xff0c;直接上功能项&#xff0c;而初版太不好看了&#xff0c;略微修改归纳了一下&#xff0c;出了第一版 很有精神&#xff0c;为了纪念&#xff0c;这个网页项目我命名为JRP 主要就是&#xff1a; 1、定义一个类…

Kubernetes之Ingress详解

目录 IngressIngress配置配置写法转发到单个后端服务不同的URL路径被转发到不同的服务上不同的域名转发到不同的服务上不使用域名的转发规则 Ingress Ingress 是 Kubernetes 中的一种 API 对象&#xff0c;用于管理和配置集群中的 HTTP 和 HTTPS 服务路由。 Ingress 可以在 K…

目标检测-One Stage-YOLOv1

文章目录 前言一、YOLOv1的网络结构和流程二、YOLOv1的损失函数三、YOLOv1的创新点总结 前言 前文目标检测-Two Stage-Mask RCNN提到了Two Stage算法的局限性&#xff1a; 速度上并不能满足实时的要求 因此出现了新的One Stage算法簇&#xff0c;YOLOv1是目标检测中One Stag…

如何批量提取pdf文件名到excel?

如何批量提取pdf文件名到excel&#xff1f;在大家整理PDF文档的时候会不会遇到下面这些问题&#xff0c;首先PDF过多&#xff0c;每个PDF文件都有自己的名字&#xff0c;我们想要分类排放的话非常麻烦&#xff0c;不仅耗费时间而且带来的收益非常低&#xff0c;然后即使我们整理…

docker +gitee+ jenkins +maven项目 (一)

jenkins环境和插件配置 文章目录 jenkins环境和插件配置前言一、环境版本二、jenkins插件三、环境安装总结 前言 现在基本都是走自动化运维&#xff0c;想到用docker 来部署jenkins &#xff0c;然后jenkins来部署java代码&#xff0c;做到了开箱即用&#xff0c;自动发布代码…

eBay自养号测评:提升销量与排名的安全可控之道

近年来&#xff0c;eBay平台吸引了大量商家入驻&#xff0c;许多原本在其他平台的卖家也纷纷转型至eBay。然而&#xff0c;许多商家在运营一段时间后发现&#xff0c;新账号的流量扶持期结束后&#xff0c;店铺流量开始下滑。面对这种情况&#xff0c;卖家应该采取哪些措施呢&a…

elasticsearch系列五:集群的备份与恢复

概述 前几篇咱们讲了es的语法、存储的优化、常规运维等等&#xff0c;今天咱们看下如何备份数据和恢复数据。 在传统的关系型数据库中我们有多种备份方式&#xff0c;常见有热备、冷备、全量定时增量备份、通过开发程序备份等等&#xff0c;其实在es中是一样的。 官方建议采用s…

YOLOv5改进 | 2023主干篇 | 华为最新VanillaNet主干替换Backbone实现大幅度长点

一、本文介绍 本文给大家来的改进机制是华为最新VanillaNet网络&#xff0c;其是今年最新推出的主干网络&#xff0c;VanillaNet是一种注重极简主义和效率的神经网络架构。它的设计简单&#xff0c;层数较少&#xff0c;避免了像深度架构和自注意力这样的复杂操作(需要注意的是…

用html,js和layui写一个简单的点击打怪小游戏

介绍&#xff1a; 一个简单的打怪小游戏&#xff0c;点击开始游戏后&#xff0c;出现攻击按钮&#xff0c;击败怪物后可以选择继续下一关和结束游戏。 继续下一个怪兽的血量会增加5点&#xff0c;攻击按钮会随机变色。 效果图&#xff1a; html代码&#xff1a; <!DOCTYPE…