用户管理系统---第三天(登录功能的实现)

登录功能(第三天)

接口设计

接受参数:用户账户、密码

请求类型:POST

请求体:JSON 格式的数据

请求参数很长时不建议用 get

返回值:用户信息( 脱敏 )信息脱敏是指对某些敏感信息通过脱敏规则进行数据的变形,实现敏感隐私数据的可靠保护。 数据脱敏可以分为两种:静态数据脱敏和动态数据脱敏。

登录逻辑
  1. 校验用户账户和密码是否合法

    1. 非空
    2. 账户长度 不小于 4 位
    3. 密码就 不小于 8 位吧
    4. 账户不包含特殊字符
  2. 校验密码是否输入正确,要和数据库中的密文密码去对比

  3. 用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露(利用user对象封装该返回的信息,代码体现)可以封装一个dto,返回想要返回的信息比如uservo包装类

  4. 我们要记录用户的登录态(session),将其存到服务器上(用后端 SpringBoot 框架封装的服务器 tomcat 去记录)

    cookie 后端读取cookie跟session做匹配从而得知用户是否登录

  5. 返回脱敏后的用户信息(返回dto类)

    dto类:(封装一个除了实体类以外的包装类,返回要求的数据,因为有时不需要返回所有的属性故而自己封装一个)

如何知道是哪个用户登录了?

​ (这⽅⾯的知识是在学习javaweb会接触到)

  1. 连接服务器端后,得到⼀个 session 状态(匿名会话),返回给前端

  2. 登录成功后,得到了登录成功的 session,并且给该 session 设置⼀些值(⽐如⽤户信息),返回给前端⼀个设置 cookie 的 “命令”

    session => cookie

  3. 前端接收到后端的命令后,设置 cookie,保存到浏览器内

  4. 前端再次请求后端的时候(相同的域名),在请求头中带上cookie去请求

  5. 后端拿到前端传来的 cookie,找到对应的 session

  6. 后端从 session 中可以取出基于该 session 存储的变量(⽤户的登录信息、登录名,token等等)

实现

控制器注解:

@RestController 适用于编写 restful 风格的 api,返回值默认为 json 类型

校验写在哪里?

  • controller 层倾向于对请求参数本身的校验,不涉及业务逻辑本身(越少越好)
  • service 层是对业务逻辑的校验(有可能被 controller 之外的类调用)

service层:

 	/**
     * @param userAccount  用户账号
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    User doLogin(String userAccount, String userPassword, HttpServletRequest request);
@Slf4j    log记录日志注解
serviceimpl层:(return null 后边会返回自定义异常类)
//用户登录态键
public static final String USER_LOGIN_STATE="userLoginState";
 /**
     * @param userAccount  用户账号
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    @Override
    public User doLogin(String userAccount, String userPassword, HttpServletRequest request) {
        //1.校验
        if(StringUtils.isAnyBlank(userAccount,userPassword)){
            return null;
        }
        if(userAccount.length()<4){
            return null;
        }
        if(userPassword.length()<8){
            return null;
        }
        //账户不能包含特殊字符
        String regEx =  ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
        Pattern p = Pattern.compile(regEx);
        Matcher m = p.matcher(userAccount);
        boolean matches = m.matches();
        if(matches){
            return null;
        }
        //2.加密
        String encryPassword=DigestUtils.md5DigestAsHex((SALT+userPassword).getBytes());
        //查询用户是否存在(假如用户状态已经被删除了是否还会查出来,
        //MyBatis-Plus框架它有⼀个逻辑删除,默认会帮助我们查询出来没有被删的⽤户
        QueryWrapper<User>queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("userAccount",userAccount);
        queryWrapper.eq("userPassword",encryPassword);//这里用的加密后的密码
        //用selectOne方法查询一条数据即可
        User user = userMapper.selectOne(queryWrapper);
        //用户不存在
        if (user==null){
            //记录日志
            log.info("user login failed,userAccount cannot match userPasswird");
            return null;
        }
        //3. 用户脱敏(密码不用返回) 不脱敏前端可以看到所有后端返回的信息
        //可以自己封装一个vo类返回需要返回的信息
        User safeUser=new User();
        safeUser.setId(user.getId());
        safeUser.setUsername(user.getUsername());
        safeUser.setAvatarUrl(user.getAvatarUrl());
        safeUser.setUserAccount(user.getUserAccount());
        safeUser.setGender(user.getGender());
        safeUser.setPhone(user.getPhone());
        safeUser.setEmail(user.getEmail());
        safeUser.setUserStatus(user.getUserStatus());
        safeUser.setCreateTime(user.getCreateTime());
        //4.记录用户的登录态
        request.getSession().setAttribute(USER_LOGIN_STATE,safeUser);
        //5.返回脱敏后的信息
        return safeUser;
    }

安装该插件(可以自动填充java参数)alt+enter 快捷键自动填充

先封装⼀个对象,专⻔⽤来接收请求参数

implements Serializable 序列化
一个对象序列化的接口 。 一个类只有实现了Serializable接口 它的对象才是可序列化的
因此如果要序列化某些类的对象 这些类必须实现Serializable接口。并且 Serializable是一个空接口 没有什么具体内容 他的目的只是简单的标识一个类的对象可以被序列化。

什么情况下需要序列化?
当你想把的内存中的对象写入到硬盘的时候
当你想用套接字在网络上传送对象的时候
当你想通过RMI传输对象的时候

/**
 * 用户注册请求体
 */
@Data
public class UserRegisterRequest implements Serializable  {
    private static final long serialVersionUID = 1905122041950251207L;
    private  String userAccount;
    private  String userPassword;
    private  String checkPassword;
}
/**
 * 用户登录请求体
 */
@Data
public class UserLoginRequest {
    private  String userAccount;
    private  String userPassword;
}
登录注册接口的实现

Controller层:

@RestController 适⽤于编写 restful ⻛格的 api,返回值默认为 json 类型

@RequestBody 用来告诉springmvc把前端传来的json参数与对象做关联

/**
 * 用户接口
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource
    private UserService userService;
     /**
     * 用户注册
     * @param userRegisterRequest(用户注册请求体,上边引入了)
     * @return
     */
    @PostMapping("/register")
    public Long userRegister(@RequestBody UserRegisterRequest userRegisterRequest){
        if (userRegisterRequest==null){
            return null;
        }
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        if (StringUtils.isAnyBlank(userAccount,userPassword,checkPassword)){
            return null;
        }
        return userService.userRegister(userAccount, userPassword, checkPassword);
    }
    
    /**
     * 用户登录
     * @param userLoginRequest(用户登录请求体)
     * @param request
     * @return
     */
    @PostMapping("/login")
    public User userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){
        if (userLoginRequest==null){
            return null;
        }
        String userAccount = userLoginRequest.getUserAccount();
        String userPassword = userLoginRequest.getUserPassword();

        if (StringUtils.isAnyBlank(userAccount,userPassword)){
            return null;
        }
        return userService.doLogin(userAccount, userPassword,request);
    }
}

实现层已经进行非空判断了,Controller层可以不校验,但Controller层检验作用是controller层倾向于对请求参数本身的校验,不涉及业务逻辑本身(越少越好)

service层是对业务逻辑的校验(有可能被Controller之外的类调用)

mybatisplus逻辑删除

步骤1 application.yml(logic-delete-field如果指定了可以忽略步骤二,或者不指定在相应字段加注解,指定的话比较麻烦建议不指定,然后通过注解去指定每个实体类字段)

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

步骤2 实体类字段上加上@TableLogic注解

@TableLogic
private Integer deleted;
用户管理接口(必须鉴权)

加个role字段判断用户身份

然后再实体类中添加身份字段

/**
 * 用户身份 0普通用户 1管理员
 */
private Integer role;

改到service层

访问静态方法要用类名.方法

//仅管理员可查询
request.getSession().getAttribute(UserService.USER_LOGIN_STATE);

Controller层

 /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    @GetMapping("/search")
    public List<User> searchUsers(String username,HttpServletRequest request){
        //仅管理员可查询
        Object userObj = request.getSession().getAttribute(UserService.USER_LOGIN_STATE);
        //强制类型转换
        User user=(User)userObj;
        //要判空操作
        if(user==null ||user.getRole()!=1){
            return new ArrayList<>();

        }
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        //StringUtils.isNotBlank()不为空不为空格 StringUtils.isAnyBlank区别
        if(StringUtils.isNotBlank(username)){
            queryWrapper.like("username",username);
        }
        return userService.list(queryWrapper);
    }
 	/**
     * 根据用户id删除用户(没有鉴权)
     * @param id
     * @return
     */
    //TOOD @Delete的实现
    @PostMapping("/delete")
    public boolean deleteUser(@RequestBody long id){
        if(id<=0){
            return false;
        }
        return userService.removeById(id);
    }

改成这样后(定义一个常量,这样更符合编码规范)

/**
 * 用户常量
 */
public interface UserConstant {
    //用户登录态键
    String USER_LOGIN_STATE="userLoginState";
    /**
     * --------权限----------
     */
    //默认权限
    int OEFAULT_ROLE=0;
    //管理员权限
    int ADMIN_ROLE=1;
}

Controller层

/**
 * 根据用户名查询用户信息
 * @param username
 * @return
 */
@GetMapping("/search")
public List<User> searchUsers(String username,HttpServletRequest request){
    //仅管理员可查询
    Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
    //强制类型转换
    User user=(User)userObj;
    //要判空操作
    if(user==null ||user.getRole()!=ADMIN_ROLE){
        return new ArrayList<>();

    }
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    //StringUtils.isNotBlank()不为空不为空格 StringUtils.isAnyBlank区别
    if(StringUtils.isNotBlank(username)){
        queryWrapper.like("username",username);
    }
    return userService.list(queryWrapper);
}
 /**
     * 根据用户id删除用户
     * @param id
     * @return
     */
    //TOOD @Delete的实现
    @PostMapping("/delete")
    public boolean deleteUser(@RequestBody long id,HttpServletRequest request){
        //仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        //强制类型转换
        User user=(User)userObj;
        //要判空操作
        if(user==null ||user.getRole()!=ADMIN_ROLE){
            return false;

        }
        //强制类型转换
        if(id<=0){
            return false;
        }
        return userService.removeById(id);
    }

然后发现这两个方法有相同的地方所以提出来写成一个公共方法

/**
 * 根据用户名查询用户信息
 * @param username
 * @return
 */
@GetMapping("/search")
public List<User> searchUsers(String username,HttpServletRequest request){
   if(!isAdmin(request)){
       return new ArrayList<>();
   }
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    //StringUtils.isNotBlank()不为空不为空格 StringUtils.isAnyBlank区别
    if(StringUtils.isNotBlank(username)){
        queryWrapper.like("username",username);
    }
    return userService.list(queryWrapper);
}

/**
 * 根据用户id删除用户
 * @param id
 * @return
 */
//TOOD @Delete的实现
@PostMapping("/delete")
public boolean deleteUser(@RequestBody long id,HttpServletRequest request){
    if(!isAdmin(request)){
        return false;
    }
    //强制类型转换
    if(id<=0){
        return false;
    }
    return userService.removeById(id);
}
/**
 * 是否为管理员
 * @param request
 * @return
 */
private boolean isAdmin(HttpServletRequest request){
    //仅管理员可查询
    Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
    //强制类型转换
    User user=(User)userObj;
    //要判空操作
    if(user==null ||user.getRole()!=ADMIN_ROLE){
        return false;
    }
    return true;
}

role是mysql的保留关键字所以要在xml映射里面写成role形式,或者修改表字段,这里推荐修改表字段为user_role

spring:
  application:
    name: user-center
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/yupi
    #session失效时间
  session:
    timeout: 86400

给session添加失效时间,此处失效时间为一天

userServiceImpl 的改变 (用户脱敏(改为调用公共方法)的地方)

/**
 * @param userAccount  用户账号
 * @param userPassword 用户密码
 * @param request
 * @return 脱敏后的用户信息
 */
@Override
public User doLogin(String userAccount, String userPassword, HttpServletRequest request) {
    //1.校验
    if(StringUtils.isAnyBlank(userAccount,userPassword)){
        return null;
    }
    if(userAccount.length()<4){
        return null;
    }
    if(userPassword.length()<8){
        return null;
    }
    //账户不能包含特殊字符
    String regEx =  ".*[\\s`~!@#$%^&*()+=|{}':;',\\[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?\\\\]+.*";
    Pattern p = Pattern.compile(regEx);
    Matcher m = p.matcher(userAccount);
    boolean matches = m.matches();
    if(matches){
        return null;
    }
    //2.加密
    String encryPassword=DigestUtils.md5DigestAsHex((SALT+userPassword).getBytes());
    //查询用户是否存在(假如用户状态已经被删除了是否还会查出来,
    // MyBatis-Plus框架它有⼀个逻辑删除,默认会帮助我们查询出来没有被删的⽤户
    QueryWrapper<User>queryWrapper=new QueryWrapper<>();
    queryWrapper.eq("userAccount",userAccount);
    queryWrapper.eq("userPassword",encryPassword);//这里用的加密后的密码
    //用selectOne方法查询一条数据即可
    User user = userMapper.selectOne(queryWrapper);
    //用户不存在
    if (user==null){
        //记录日志
        log.info("user login failed,userAccount cannot match userPasswird");
        return null;
    }
    //3 用户脱敏(改为调用公共方法)
    User safeUser=getSafetyUser(user);
    //4.记录用户的登录态
    request.getSession().setAttribute(USER_LOGIN_STATE,safeUser);
    //5.返回脱敏后的信息
    return safeUser;
}
	/**
     * 用户脱敏
     * @param orignUser
     * @return
     */
@Override
public User getSafetyUser(User orignUser){
    //3. 用户脱敏(密码不用返回) 不脱敏前端可以看到所有后端返回的信息
    //可以自己封装一个vo类返回需要返回的信息
    User safeUser=new User();
    safeUser.setId(orignUser.getId());
    safeUser.setUsername(orignUser.getUsername());
    safeUser.setAvatarUrl(orignUser.getAvatarUrl());
    safeUser.setUserAccount(orignUser.getUserAccount());
    safeUser.setGender(orignUser.getGender());
    safeUser.setPhone(orignUser.getPhone());
    safeUser.setEmail(orignUser.getEmail());
    safeUser.setUserRole(orignUser.getUserRole());
    safeUser.setUserStatus(orignUser.getUserStatus());
    safeUser.setCreateTime(orignUser.getCreateTime());
    return safeUser;
}

userservice

/**
 * 用户脱敏
 * @param orignUser
 * @return
 */
User getSafetyUser(User orignUser);

userController层修改方法:

/**
 * 根据用户名查询用户信息
 * @param username
 * @return
 */
@GetMapping("/search")
public List<User> searchUsers(String username,HttpServletRequest request){
   if(!isAdmin(request)){
       return new ArrayList<>();
   }
    QueryWrapper<User> queryWrapper=new QueryWrapper<>();
    //StringUtils.isNotBlank()不为空不为空格 StringUtils.isAnyBlank区别
    if(StringUtils.isNotBlank(username)){
        queryWrapper.like("username",username);
    }

    List<User> userList=userService.list(queryWrapper);
    return userList.stream().map(user -> {
        user.setUserPassword(null);
        return userService.getSafetyUser(user);
    }).collect(Collectors.toList());
}

我认为不如建一个dto包去返回想要的数据

前端修改

react的学习:https://blog.csdn.net/qq_31281245/article/details/127243871

在这里插入图片描述

export const SYSTEM_LOGO="https://img1.baidu.com/it/u=279206479,2064644760&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1678813200&t=ad7a9bb914d24cd46686e6913ae45522";
/**
 * 文档地址
 */
export const PLAMET_LINK="www.baidu.com";

export const 常量名 :对应组件下新建文件constants常量文件来定义所有的dispatch的常量

删掉其他登录方式:我想做成点击后效果为暂未开发

忘记密码改为邮箱验证码找回也没做呢

1.修改底部版权信息

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

按要求改成自己的就行

2.修改logo

定义⼀个全局的常量,在src包下新建constant包

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-302cRig9-1679040956133)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20230316215051621.png)]

把 “/logo.svg” 改为 {SYSTEM_LOGO},这⾥要导⼊⼀下,根据提示或使⽤快捷键

导⼊的内容如下:

在Footer->index.tsx也可以直接使⽤ PLANET_LINK

还可以修改标题和副标题

3. 删除多余代码

按需删除即可

4.对接(请求)后台的接⼝:

找到handleSubmit,点击⼀下,vscode要按ctrl+单击

选中LoginParams ctrl+鼠标点击进入

将username,password的值改为后台接口定义的值

点击username按f2重构为userAccunt,点击password按f2重构为userPassword(webstrom快捷键shift+f6重构)

typings.d.ts就是定义设置返回参数的类型文件

回到handleSubmit

点击login进入api定义接口的文件

根据后台修改request的请求地址,去Ant Design Pro的官⽅⽂档https://pro.ant.design/zh-CN/docs/overview,搜索请求

提示我们要去app.ts去配置,顺便把官⽹提示的配置复制

mport { RequestConfig } from 'umi';

export const request: RequestConfig = {
  timeout: 1000,
  errorConfig: {},
  middlewares: [],
  requestInterceptors: [],
  responseInterceptors: [],
  errorHandler,
};


先测试⼀下,写下配置

请求地址修改为/user/login

去登录测试⼀下,请求地址正确(右侧的框是前端开发者的调试⼯具,按f12或者⿏标右键点检查)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1KsuZYRh-1679040956138)(C:\Users\dell\AppData\Roaming\Typora\typora-user-images\image-20230316220427603.png)]

现在有个问题。有啥问题,我们遇到了这个跨域错误,为什么会跨域?因为我们当前前端的浏览器⾥的地址,它是8000端⼝, 但是我们的后端,它是8080端⼝,端⼝不⼀样,就是跨域的。跨域的话,有很多种⽅式解决,要么搭⼀个代理,要么在你的后端去⽀持跨域,但后端⽀持跨域很不安全。Ant Design Pro它提供了⼀个配置代理的⽅式,我们直接⽤它的代理即可。

或者后台配置跨域:

可以用过滤器做,这里没改呢还

api.ts加上api前缀在请求路径上

后台则在application.yml 指定接口全局路径前缀

server:
  port: 8080
  servlet:
    context-path: /api

在index.tsx把username替换成userAccount

password替换成userPassword(区别⼤⼩写匹配)

给密码多加⼀个⻓度校验

这⾥改成user,如果user存在的话,就显示登录成功,并且设置⽤户的登录状态为user

测试⼀下

⻓度的校验成功了

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值