用户中心系统(后端)

近期做了一个最常用的系统——用户中心系统,简单实现了用户注册、登录、查询等基础功能。整个项目流程:需求分析 => 设计(概要设计、详细设计)=> 技术选型 => 初始化 / 引入需要的技术 => 写 Demo => 写代码(实现业务逻辑) => 测试(单元测试)=> 代码提交 / 代码评审 => 部署=> 发布。本篇文章主要记录这个项目后端的实现。成品图如下:

一、需求分析:

1.登录/注册

2.用户管理(仅管理员可见)对用户的查询或者修改

3.用户校验

二、技术选型

后端:

  • java
  • spring(依赖注入框架,帮助你管理 Java 对象,集成一些其他的内容)
  • springmvc(web 框架,提供接口访问、restful接口等能力)
  • mybatis(Java 操作数据库的框架,持久层框架,对 jdbc 的封装)
  • mybatis-plus(对 mybatis 的增强,不用写 sql 也能实现增删改查)
  • springboot(快速启动 / 快速集成项目。不用自己管理 spring 配置,不用自己整合各种框 架)
  • junit 单元测试库
  • mysql

部署:服务器/容器(平台)

三、后端初始化

1.mysql前期准备

(1)下载并安装

(2)验证Mysql是否安装成功

2.初始化springboot项目(直接在IDEA开发工具中生成)

3.引入mybatis-plus

通过查看官方文档,书写 MyBatis-Plus (baomidou.com) demo 完成引入

四、数据库设计

1.用户表设计

字段说明类型
id主键,唯一标识bigint
username昵称varchar
userAccount登录账号varchar
avatarUrl头像

varchar

gender性别tinyint
userPassword密码varchar
phone电话varchar
email邮箱varchar
userStatus用户状态;0-正常int
userRole用户权限;0-普通用户、1-管理员int
code编号varchar
createTime创建时间(数据插入时间)datetime
updateTime更新时间datetime
isDelete是否删除(逻辑删除)tinyint

2.创建用户表

(1)方式一:傻瓜式创建

(2)方式二:SQL语句

-- auto-generated definition
 create table user
 (
    id          bigint auto_increment comment 'id'  primary key,
    username     varchar(256)                 null comment '用户昵称',
    userAccount  varchar(256)                     null comment '账号',
    avatarUrl    varchar(1024) default 
'https://img1.baidu.com/it/u=1966616150,2146512490&fm=253&fmt=auto&app=138&f=JPE
 G?w=751&h=500' null comment '用户头像',
    gender       tinyint         null comment '性别',
    userPassword varchar(512)        not null comment '密码',
    phone        varchar(128)          null comment '电话',
    email        varchar(512)            null comment '邮箱',
    userStatus   int           default 0   not null comment '状态 0 - 正常',
    createTime   datetime      default CURRENT_TIMESTAMP  null comment '创建时间',
    updateTime   datetime      default CURRENT_TIMESTAMP   
                    null on update CURRENT_TIMESTAMP comment '更新时间',
    isDelete     tinyint       default 0       not null comment '是否删除(逻辑删除)',
    userRole     int       default 0    not null comment '用户权限 0 - 普通用户 1 - 管理员',
    code   varchar(512)       null comment '编号'
 );

五、登录功能

1.实现基本数据库操作(操作user表)

(1)模型user对象=>和数据库的字段关联,自动生成

 使用插件mybatisX:MyBatisX 插件,自动根据数据库生成:

  • domain 实体对象
  • mapper(操作数据库的对象)
  • mapper.xml(定义了 mapper对象和数据库的关联,可以在里面自己写 SQL)
  • service(包含常用的增删改查)
  • serviceImpl(具体实现 service)
步骤:

tips:在配置文件 application.yml 中添加如下配置:

# mybatis-plus的配置
mybatis-plus:
 configuration:
 # 解决 mybatis-plus 框架中 “ Unknown column 'user_account' in 'field list'”
 map-underscore-to-camel-case: false

(2)测试mybatisX生成的代码;

package com.example.backend.service;
import java.util.Date;

import com.example.backend.model.domain.User;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

/*
* 用户服务测试
* */
@SpringBootTest
class UserServiceTest {


    @Resource
    private UserService userService;


    @Test
    public void testAddUser(){
        User user=new User();
        user.setUsername("");
        user.setUserAccount("wangP");
        user.setAvatarUrl("https://636f-codenav-8grj8px727565176-1256524210.tcb.qcloud.la/img/logo.png");
        user.setGender(0);
        user.setUserPassword("12345678");
        user.setPhone("123");
        user.setEmail("456");
        user.setUserRole(1);
        boolean result = userService.save(user);
        System.out.println(user.getId());
        assertTrue(result);


    }
}

2.登录逻辑

(1)校验用户账户和密码是否合法

  • 非空
  • 账户长度 不小于 4 位
  • 密码就 不小于 8 位吧
  • 账户不包含特殊字符

(2)校验密码是否输入正确,要和数据库中的密文密码去对比

(3)用户信息脱敏,隐藏敏感信息,防止数据库中的字段泄露

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

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

3.代码实现:

  /**
     * 用户登录
     * @param userAccount  用户账号
     * @param userPassword 用户密码
     * @param request
     * @return
     */
    @Override
    public User userLogin(@RequestBody String userAccount, String userPassword, HttpServletRequest request) {
        //1.校验
        if(StringUtils.isAnyBlank(userAccount,userPassword)){
            return null;
        }
        //2.校验账户
        if(userAccount.length()<4){
            return null;
        }
        if(userPassword.length()<8 ){
            return null;
        }
        //账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return null;
        }

        //加密
        String encryptPassword = DigestUtils.md5DigestAsHex((salt + userPassword).getBytes());
        //查询用户是否存在
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("userAccount",userAccount);
        queryWrapper.eq("userPassword",encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        //用户不存在
        if(user==null){
            log.info("user login failed,userAccount cannot match userPassword");
            return null;
        }
        //用户脱敏

        User safetyUser=getSafetyUser(user);
        //记录用户登录态
        request.getSession().setAttribute(USER_LOGIN_STATE,safetyUser);

        return safetyUser;
    }

4.问题以及解决方案;

问题:

以上第二步检验密码是否正确时,查询数据库时,会把逻辑删除的用户数据也一并查出 来,所以需要对 mybatis-plus 框架进行设置

解决:

  • 在 application.yml 配置文件中配置,可避免这种情况出现
mybatis-plus:
 global-config:
 db-config:
 logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since3.3.0,配置后可以忽略不配置步骤2)
 logic-delete-value: 1 # 逻辑已删除值(默认为 1)
 logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  • 实体类字段上加上@TableLogic注解
@TableLogic
 private Integer isDelete;

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

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

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

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

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

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

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

6.控制层Controller封装请求

application.yml 指定接口全局 api

 servlet:
 context-path: /api

六、注册功能

 1.注册逻辑:

(1)校验用户的账户、密码、校验密码,是否符合要求

  • 非空
  • 账户长度 不小于 4 位
  • 密码就 不小于 8 位
  • 账户不能重复
  • 账户不包含特殊字符
  • 密码和校验密码相同

(2)对密码进行加密(密码千万不要直接以明文存储到数据库中)

(3)向数据库插入用户数据

2.代码实现:

   /**
     * 用户注册
     * @param userAccount 用户账号
     * @param userPassword 用户密码
     * @param checkPassword 检验密码
     * @param planetCode 星球编号
     * @return
     */
    @Override
    public long userRegister(@RequestBody String userAccount, String userPassword, String checkPassword,String planetCode) {
        //1.校验
        if(StringUtils.isAnyBlank(userAccount,userPassword,checkPassword,planetCode)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"参数为空");
        }
        //2.校验账户
        if(userAccount.length()<4){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户账号过短");
        }
        if(userPassword.length()<8 || checkPassword.length()<8){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"用户密码过短");
        }
        if(planetCode.length()>5){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,"星球编号过长");
        }
        //账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return -1;
        }
        //密码和检验码相同
        if(!userPassword.equals(checkPassword)){
            return -1;
        }
        //账户不能重复
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("userAccount",userAccount);
        long count=userMapper.selectCount(queryWrapper);
        if(count>0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
        }

        //星球编号不能重复
        queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("planetCode",planetCode);
        count=userMapper.selectCount(queryWrapper);
        if(count>0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号重复");
        }

        //加密
        String encryptPassword = DigestUtils.md5DigestAsHex((salt + userPassword).getBytes());
        //插入数据
        User user=new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        boolean saveResult = this.save(user);
        if(!saveResult){
            return -1;
        }
        return user.getId();
    }

七、用户管理(仅管理员可见)

  • 接口设计关键:必须鉴权(允许根据用户名查询、删除用户)
  • controller层:
/**
 * 管理员查询
* @param username 用户昵称
* @param request springboot内置请求对象,用于存储用户session
 * @return 查到得到的所有用户信息
*/
 @GetMapping("/search")
    public BaseResponse<List<User>> searchUsers(String username,HttpServletRequest request){
        if(!isAdmin(request)){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }

        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        if(StringUtils.isNoneBlank(username)){
            queryWrapper.like("username",username);
        }
        List<User> userList = userService.list(queryWrapper);
        List<User> list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
        return ResultUtils.success(list);
    }

 /**
 * 管理员删除
* @param id 用户id
 * @param request springboot内置请求对象,用于存储用户session
 * @return 是否删除用户,ture表示删除;false表示删除失败
*/
 @PostMapping("/delete")
    public BaseResponse<Boolean> deleteUser(@RequestBody long id,HttpServletRequest request){

        if(!isAdmin(request)){
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        if(id<=0){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean b = userService.removeById(id);
        return ResultUtils.success(b);
    }

八、后端添加获取当前用户信息的接口

  • controller 层
    /**
     * 获取当前用户
     * @param request
     * @return
     */

    @GetMapping("/current")
    public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
        Object useObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User currentUser = (User) useObj;
        if(currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        Long userId = currentUser.getId();
        // TODO 校验用户是否合法
        User user = userService.getById(userId);
        User safetyUser = userService.getSafetyUser(user);
        return ResultUtils.success(safetyUser);
    }

九、用户退出登录

  • controller 层
    /**
     * 用户注销
     * @param request
     * @return
     */

    @PostMapping("/logout")
    public BaseResponse<Integer> userLogout(HttpServletRequest request) {
        if(request==null){
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        int result = userService.userLogout(request);
        return ResultUtils.success(result);
    }
  • sevice 层
   /**
     *用户注销
     * @param request
     * @return
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        //用户注销
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }
}

十、部署和上线

1.多环境的简介以及作用:

  • 多环境::指同一套项目代码在不同的阶段需要根据实际情况来调整配置并且部署到不同的机器上。
  • 为什么需要:
  1.  每个环境互不影响
  2. 区分不同阶段:开发/测试/生存
  3. 对项目进行优化                  

2.多环境分类:

  • 本地环境(自己的电脑)localhost
  • 开发环境(远程开发)大家连同一台机器,为了大家开发方便
  • 测试环境(测试)开发 / 测试 / 产品,单元测试 / 性能测试 / 功能测试 / 系统集成测试,独立的数 据库、独立的服务器
  • 预发布环境(体验服):和正式环境一致,正式数据库,更严谨,查出更多问题
  • 正式环境(线上,公开对外访问的项目):尽量不要改动,保证上线前的代码是 “完美” 运行
  • 沙箱环境(实验环境):为了做实验

3.后端多环境实战:

  • SpringBoot 项目,通过 application.yml 添加不同的后缀来区分配置文件
    • 开发环境:application-prod.yml
    • 生产环境:application-dev.yml
  •  可以在启动项目时传入环境变量:

 java -jar .\user-center-backend-0.0.1-SNAPSHOT.jar -- spring.profiles.active=prod

  • 主要是改:
    • 依赖的环境地址
      • 数据库地址
      • 缓存地址
      • 消息队列地址
      • 项目端口号
  • 服务器配置 

4.部署:

  • java、maven
  • 安装

# 安装 java8

yum install -y java-1.8.0-openjdk*

# 安装 maven

curl -o apache-maven-3.8.5-bin.tar.gz https://dlcdn.apache.org/maven/maven 3/3.8.5/binaries/apache-maven-3.8.5-bin.tar.gz

# 下载源码 git clone xxx 下载代码

# 使用 maven 工具打包项目(跳过测试)

打包构建,跳过测试

mvn package -DskipTests

# 运行 jar 包 java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --server.port=8080 -- spring.profiles.active=prod

# 后台运行 jar 包

nohup java -jar ./user-center-backend-0.0.1-SNAPSHOT.jar --server.port=8080 -- spring.profiles.active=prod &

# 使用 jobs 查看用 nohup 运行的项目 jobs

5.绑定域名 、解决跨域问题

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EmilyRose

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值