阳光沙滩-课程笔记 阳光沙滩-课程笔记
首页 (opens new window)
VIP (opens new window)
  • 课程笔记

    • 《Android项目喜马拉雅FM》
    • 《Android项目领券联盟》
    • 《AndroidStudio技巧》
    • 《Android自定义控件》
    • 《Android开发基础》
    • 《Android约束布局》
    • 《AOSP安卓开源项目》
    • 《RecyclerView》
  • 《领券联盟Nuxt.js》
  • 《博客系统后台管理系统vue.js》
  • 《博客系统门户Nuxt.js》
  • 《博客系统前后端分离后台》
  • 《博客系统部署》
  • 《摸鱼君后台》
  • 《OTA升级管理系统》
  • 阳光沙滩API (opens new window)
  • 领券联盟API (opens new window)
  • 博客系统API (opens new window)
首页 (opens new window)
VIP (opens new window)
  • 课程笔记

    • 《Android项目喜马拉雅FM》
    • 《Android项目领券联盟》
    • 《AndroidStudio技巧》
    • 《Android自定义控件》
    • 《Android开发基础》
    • 《Android约束布局》
    • 《AOSP安卓开源项目》
    • 《RecyclerView》
  • 《领券联盟Nuxt.js》
  • 《博客系统后台管理系统vue.js》
  • 《博客系统门户Nuxt.js》
  • 《博客系统前后端分离后台》
  • 《博客系统部署》
  • 《摸鱼君后台》
  • 《OTA升级管理系统》
  • 阳光沙滩API (opens new window)
  • 领券联盟API (opens new window)
  • 博客系统API (opens new window)
  • 单体应用博客系统

  • 博客系统部署

  • 摸鱼君微服务项目实战

    • 摸鱼君介绍
    • 数据库创建&表字段设计
    • Mybatis-plus
    • 项目创建
    • 编写u-center模块功能
      • 通过代码生成器生成各种类
      • 编写统一返回结果R
      • 接口分类
      • 接口重构
      • 用户注册流程
      • redis配置
      • 验证码
      • 创建用户中心的前端项目
      • 集成验证码功能
      • 前端页面
      • 前端引入ElementUI
      • 编写注册页面
      • 注册功能编写
      • 邮箱发送功能
      • 前端网络模块引入
      • 注册功能实现
      • 数据自动填充
      • 登录功能
      • 解析Token
      • 重置密码
      • 定义用户管理的接口
        • 实现用户列表的获取
        • 实现用户禁止
        • 实现用户密码重置
      • RBAC (role-base access control)
        • 涉及到的数据表
        • 表的字段
      • 登录以后token需要包含用户角色
    • 管理中心项目准备
    • 编写mo-yu-jun模块功能
    • 微服务结构
    • 服务注册中心
    • 网关
    • API文档
  • OTA升级管理系统

  • 后台
  • 摸鱼君微服务项目实战
TrillGates
2022-01-03
目录

编写u-center模块功能

# 编写u-center模块功能

  • 通过代码生成器生成各种类

# 通过代码生成器生成各种类

相关的依赖

<!--数据库相关-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

<!-- RESTful APIs swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

代码生成器的代码

public class GeneratorCodes {

    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir("D:\\code\\MoYuJun\\u-center\\src\\main\\java");
        gc.setAuthor("sob");
        gc.setOpen(false);//生成以后是否打开文件夹
        gc.setSwagger2(true);//实体属性 Swagger2 注解
        gc.setFileOverride(true);//true表示覆盖原来的
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://192.168.220.128:3306/mo_yu_ucenter?characterEncoding=utf-8&useSSL=false&useUnicode=true");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setModuleName("uc");
        pc.setParent("net.sunofbeach");
        pc.setController("api");
        pc.setMapper("mapper");
        pc.setService("service");
        pc.setEntity("pojo");
        mpg.setPackageInfo(pc);


        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        //多个表的时候,写多个就可以
        strategy.setInclude("uc_app","uc_black_list","uc_fans","uc_images","uc_login_record","uc_register_info","uc_settings","uc_statistics","uc_token","uc_user","uc_user_info");
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix(pc.getModuleName() + "_");
        mpg.setStrategy(strategy);
        mpg.execute();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

同学们要修改上面的配置,如果不放心,可以先测试一个数据表,而不是全部。

# 编写统一返回结果R

在common模块下,我们创建以下类

package net.sunofbeach.common.response;

import lombok.Data;

/**
 * 返回的类
 * 数据有:
 * - 是否成功:success [true/false] 类型 boolean
 * - 状态码:code [20000/40000] 类型 int
 * - 消息:msg 对code的说明,比如说:操作成功;操作失败;登录成功... 类型 字符串类型
 * - 返回的数据:data 类型object
 */
@Data
public class R {

    public static final int CODE_SUCCESS = 20000;
    public static final int CODE_FAILED = 40000;

    //是否成功
    private boolean success;
    //状态码
    private int code;
    //描述
    private String msg;
    //数据
    private Object data;

    //提供一些静态的方法,可以快速地创建返回对象
    public static R SUCCESS(String msg) {
        R r = new R();
        r.code = CODE_SUCCESS;
        r.msg = msg;
        r.success = true;
        return r;
    }

    public static R SUCCESS(String msg, Object data) {
        R success = SUCCESS(msg);
        success.data = data;
        return success;
    }

    public static R FAILED(String msg) {
        R r = new R();
        r.code = CODE_FAILED;
        r.msg = msg;
        r.success = false;
        return r;
    }

    public static R FAILED(String msg, Object data) {
        R success = FAILED(msg);
        success.data = data;
        return success;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

我们其他模块都依赖于此模块,都使用到返回结果,所以我们写到公共模块里。

测试:

/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author sob
 * @since 2021-10-20
 */
@RestController
@RequestMapping("/uc/user")
public class UserController {

    @GetMapping("/test")
    public R testResponse() {
        return R.SUCCESS("请求成功.");
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

请求地址:

localhost:40100/uc/user/test
1

返回结果

{
    "success": true,
    "code": 20000,
    "msg": "请求成功.",
    "data": null
}
1
2
3
4
5
6

# 接口分类

我们的接口主要是分两部分,一部分是给门户使用的,一部分是管理中心使用的。

所以我们可以对我们的API进行分包管理一下,比说如:

portal和admin

image-20211030223320684

# 接口重构

我们目前api里的接口都是根据数据表生成的,不一定完全符合我们的需求。所以我们有必要对此进行一个修改,根据我们面的功能进行修改。

  • portal
    • UserController,user,用户的基本功能,比如说登录,注册
      • 登录 sing_in(post)
      • 退出登录 sign_out (get)
      • 注册 sig_up(post)
    • CheckController,check,帮助用户类实现基本功能的工具接口,比如说验证码,邮箱是否有注册检查,用户名是否有注册检查之类的
      • 获取验证码,verify_code(get)
      • 获取邮箱验证码,email_code (get)
      • 检查邮箱是否有注册 email(get)
      • 检查手机号是否有注册 phone (get)
      • 检查用户名是否有注册 user_name (get)
      • 检查token是否有效 token (get)
    • UserInfoController,info 用户信息接口
      • 获取用户信息 (get)
      • 更新用户信息(put)
    • ImageController ,image 图片上传接口
      • 头像上传,avatar(post)
      • app的Logo上传 app_logo(post)
      • 用户封面上传 user_cover(post)
    • AppController,app,第三方开发者使用统一用户中心时,需要创建应用,不支持删除
      • 创建应用(post)
      • 修改应用信息(只允许修改logo和名称)(put)
      • 获取应用列表 list (get)
      • 获取某个应用的信息(get)
    • FansController,fans,粉丝接口
      • 关注 follow (post)
      • 取消关注 unfollow (put)
      • 获取关注列表 follow_list(get)
      • 获取粉丝列表 fans_list(get)
      • 加入黑名单 black (post)
      • 取消黑名单 white (put)
      • 获取黑名单列表 black_list(get)
  • admin
    • UserController,user 用户控制类,获取用户列表,重置用户密码,禁止用户等
      • 获取用户列表 list(get)
      • 重置用户密码 reset_pwd(put)
      • 删除用户 (delete)
      • 禁止用户 disable(put)
      • 取消禁止 enable(put)
    • InfoController,info 统一用户中心的信息接口,比如说当前的在线人数,用户总数量,应用总数
      • 当前在线人数 online_count(get)
      • 用户中数 user_count(get)
      • app总数 app_count(get)
      • =====================
      • 今天注册人数 today_reg_count(get)
      • 本周注册人数 toweek_reg_count(get)
      • 本月注册人数 tomonth_reg_count(get)
    • StatisticsController,statistics 统计接口
      • //TODO:
    • SettingsController,settings 设置相关的接口
      • 注册方式 reg_type (put)
      • 获取注册方式 reg_type(get)
      • 邮件服务器 email (put)
      • 获取邮件服务器信息 email(get)
      • 敏感词 taboo (put)
      • 获取敏感词列表 taboo(get)
      • 管理员账号 account (put)

# 用户注册流程

image-20211031110650043

  • 获取图灵验证 captcha (get)
  • 检查当前手机号码是否有被注册 phone_num (get)
  • 获取手机验证码 phone_verify_code (get)
  • 检查当前邮箱是否有被注册 email (get)
  • 获取邮箱验证码 email_verify_code (get)
  • 检查昵称是否有被使用 nickname (get)
  • 提交注册信息 sign_up (post)

# redis配置

首先是redis的安装,如果没有学习博客系统同学,可以参考一下这篇文章,或者去看视频。

  1. redis的安装文章:
  • 本地版本: 安装redis-6.2.5(linux版) (opens new window)
  • 安装redis-6.2.5(单机版) (opens new window)
  • docker版本:阳光沙滩博客系统-Redis (opens new window)
  • docker搭建redis以及项目配置 (opens new window)
  1. 添加依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    1
    2
    3
    4
  2. 配置

    spring:
      redis:
        database: 1 #我的0号库使用在其他项目上,我用1号库
        host: 192.168.220.128
        port: 6379
        password: 123456
    
    1
    2
    3
    4
    5
    6
  3. 工具类

    package net.sunofbeach.common.utils;
    
    
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import javax.annotation.Resource;
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    @Component
    public class RedisUtil {
    
        @Resource
        private RedisTemplate redisTemplate;
    
        public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        /**
         * 指定缓存失效时间
         *
         * @param key  键
         * @param time 时间(秒)
         * @return
         */
        private boolean expire(String key, long time) {
            try {
                if (time > 0) {
                    redisTemplate.expire(key, time, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 根据key 获取过期时间
         *
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
    
        /**
         * 判断key是否存在
         *
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(String key) {
            try {
                return redisTemplate.hasKey(key);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 删除缓存
         *
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        public void del(String... key) {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        }
    
        /**
         * 普通缓存获取
         *
         * @param key 键
         * @return 值
         */
        public Object get(String key) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
    
        /**
         * 普通缓存放入
         *
         * @param key   键
         * @param value 值
         * @return true成功 false失败
         */
        public boolean set(String key, Object value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
        }
    
        /**
         * 普通缓存放入并设置时间
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 递增
         *
         * @param key   键
         * @param delta 要增加几(大于0)
         * @return
         */
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
    
        /**
         * 递减
         *
         * @param key   键
         * @param delta 要减少几(小于0)
         * @return
         */
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
    
        /**
         * HashGet
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return 值
         */
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
    
        /**
         * 获取hashKey对应的所有键值
         *
         * @param key 键
         * @return 对应的多个键值
         */
        public Map<Object, Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * HashSet
         *
         * @param key 键
         * @param map 对应多个键值
         * @return true 成功 false 失败
         */
        public boolean hmset(String key, Map<String, Object> map) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * HashSet 并设置时间
         *
         * @param key  键
         * @param map  对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        private boolean hmset(String key, Map<String, Object> map, long time) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         *
         * @param key   键
         * @param item  项
         * @param value 值
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         *
         * @param key   键
         * @param item  项
         * @param value 值
         * @param time  时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value, long time) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 删除hash表中的值
         *
         * @param key  键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
    
        /**
         * 判断hash表中是否有该项的值
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
    
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         *
         * @param key  键
         * @param item 项
         * @param by   要增加几(大于0)
         * @return
         */
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
    
        /**
         * hash递减
         *
         * @param key  键
         * @param item 项
         * @param by   要减少记(小于0)
         * @return
         */
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
    
        /**
         * 根据key获取Set中的所有值
         *
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 根据value从一个set中查询,是否存在
         *
         * @param key   键
         * @param value 值
         * @return true 存在 false不存在
         */
        public boolean sHasKey(String key, Object value) {
            try {
                return redisTemplate.opsForSet().isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将数据放入set缓存
         *
         * @param key    键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object... values) {
            try {
                return redisTemplate.opsForSet().add(key, values);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 将set数据放入缓存
         *
         * @param key    键
         * @param time   时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(String key, long time, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().add(key, values);
                if (time > 0) expire(key, time);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 获取set缓存的长度
         *
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 移除值为value的
         *
         * @param key    键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 获取list缓存的内容
         *
         * @param key   键
         * @param start 开始
         * @param end   结束  0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            try {
                return redisTemplate.opsForList().range(key, start, end);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 获取list缓存的长度
         *
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            try {
                return redisTemplate.opsForList().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 通过索引 获取list中的值
         *
         * @param key   键
         * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            try {
                return redisTemplate.opsForList().index(key, index);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                if (time > 0) expire(key, time);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, List<Object> value) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        public boolean lSet(String key, List<Object> value, long time) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                if (time > 0) expire(key, time);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 根据索引修改list中的某条数据
         *
         * @param key   键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            try {
                redisTemplate.opsForList().set(key, index, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 移除N个值为value
         *
         * @param key   键
         * @param count 移除多少个
         * @param value 值
         * @return 移除的个数
         */
        public long lRemove(String key, long count, Object value) {
            try {
                Long remove = redisTemplate.opsForList().remove(key, count, value);
                return remove;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    316
    317
    318
    319
    320
    321
    322
    323
    324
    325
    326
    327
    328
    329
    330
    331
    332
    333
    334
    335
    336
    337
    338
    339
    340
    341
    342
    343
    344
    345
    346
    347
    348
    349
    350
    351
    352
    353
    354
    355
    356
    357
    358
    359
    360
    361
    362
    363
    364
    365
    366
    367
    368
    369
    370
    371
    372
    373
    374
    375
    376
    377
    378
    379
    380
    381
    382
    383
    384
    385
    386
    387
    388
    389
    390
    391
    392
    393
    394
    395
    396
    397
    398
    399
    400
    401
    402
    403
    404
    405
    406
    407
    408
    409
    410
    411
    412
    413
    414
    415
    416
    417
    418
    419
    420
    421
    422
    423
    424
    425
    426
    427
    428
    429
    430
    431
    432
    433
    434
    435
    436
    437
    438
    439
    440
    441
    442
    443
    444
    445
    446
    447
    448
    449
    450
    451
    452
    453
    454
    455
    456
    457
    458
    459
    460
    461
    462
    463
    464
    465
    466
    467
    468
    469
    470
    471
    472
    473
    474
    475
    476
    477
    478
    479
    480
    481
    482
    483
    484
    485
    486
    487
    488
    489
    490
    491
    492
    493
    494
    495
    496
    497
    498
    499
    500
    501
    502
    503
    504
    505
    506
    507
    508
    509
    510
    511
    512
    513
    514
    515
    516
    517
    518
    519
    520
    521
    522
    523
    524
    525
    526
    527
    528
    529
    530
    531
    532
    533
    534
    535
    536
    537
    538
    539
    540
    541
    542
    543
    544
    545
    546
    547
    548
    549
    550
    551
    552
    553
    554
    555
    556
    557
    558
    559
  4. 配置

    @Configuration
    public class RedisConfiguration {
    
        @Autowired
        private RedisConnectionFactory redisConnectionFactory;
    
        @Bean
        public RedisTemplate<String, Object> redisTemplate() {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new StringRedisSerializer());
            redisTemplate.setConnectionFactory(redisConnectionFactory);
            return redisTemplate;
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  5. 测试

    随便往redis里保存一些东西,读写一下就可以知道配置成功没了

# 验证码

推荐使用的开源框架:

https://github.com/anji-plus/captcha
1

# 创建用户中心的前端项目

我们前端使用vue.js来写

https://cn.vuejs.org/v2/guide/
1

用户中心的前端主要包括:

  • 注册
  • 登录
  • 找回密码

# 集成验证码功能

  • 后端,添加依赖
<!--验证码-->
<dependency>
    <groupId>com.anji-plus</groupId>
    <artifactId>spring-boot-starter-captcha</artifactId>
    <version>1.3.0</version>
</dependency>
1
2
3
4
5
6

添加完以后,import一下,然后就可以了,相关的接口在jar包的controller里有的。

  • 前端,前端复制组件,还有assert目录下的资源文件。

然后引入组件,注册组件

import Verify from './components/verifition/Verify.vue'

export default {
    name: 'App',
    components: {
        Verify
    },
    methods: {
        login(params) {
            console.log(params);
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

使用组件

<Verify
ref="verify"
:captcha-type="'blockPuzzle'"
:img-size="{width:'400px',height:'200px'}"
@success="login"/>
1
2
3
4
5

解决访问后台的跨域问题

@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {

    @Override
    public  void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws ServletException, IOException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET,PUT, OPTIONS, DELETE");//http请求方式
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, token");
        filterChain.doFilter(request, response);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

到这里我们就集成完了,接下来,我们稍微写一下前端的页面

# 前端页面

  • 注册
  • 登录
  • 找回密码

安装vue-router

https://router.vuejs.org/installation.html
npm install vue-router
import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)
1
2
3
4
5
6

编写和注册路由

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false;

import VueRouter from 'vue-router'

Vue.use(VueRouter);

const index = () => import('@/page/home/index');
const login = () => import('@/page/login/index');
const forgot = () => import('@/page/forgot/index');
const register = () => import('@/page/register/index');


const routes = [
    {
        path: '/index',
        name: 'index',
        component: index
    },
    {
        path: '/login',
        name: 'login',
        component: login
    },
    {
        path: '/forgot',
        name: 'forgot',
        component: forgot
    },
    {
        path: '/register',
        name: 'register',
        component: register
    },
    {
        path: '',
        redirect: '/index'
    }
];

const router = new VueRouter({
    routes // short for `routes: routes`
});

new Vue({
    router,
    render: h => h(App),
}).$mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 前端引入ElementUI

https://element.eleme.cn/#/zh-CN/component/installation
npm i element-ui -S
npm install element-ui --save
1
2
3

# 编写注册页面

<template>
    <div class="register-page">
        <div class="title">注册</div>
        <div class="register-form-part">
            <el-form label-width="80px" size="medium">
                <el-form-item label="邮箱" :required="true">
                    <el-input v-model="userVo.email"></el-input>
                </el-form-item>
                <el-form-item label="昵称" :required="true">
                    <el-input v-model="userVo.name"></el-input>
                </el-form-item>
                <el-form-item label="密码" :required="true">
                    <el-input v-model="userVo.password"></el-input>
                </el-form-item>
                <el-form-item>
                    <el-button @click="register" type="primary">注 册</el-button>
                    <span style="margin-left: 20px; color: #7e8c8d">
                        已注册? 直接登录
                    </span>
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                userVo: {
                    email: '',
                    name: '',
                    password: ''
                }
            }
        },
        methods: {
            register() {

            }
        }
    }
</script>
<style>
    .register-form-part .el-input {
        width: 400px;
    }

    .register-form-part {
        margin-top: 20px;
    }

</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

image-20220102213757702

# 注册功能编写

  • 动作:

    1. 用户输入邮箱,获取验证码--->显示图灵验证码(手动校验,不会删除验证)

    2. 提交数据:邮箱,图灵验证码内容

    3. 填写邮箱验证码和用户名还有密码(MD5)

    4. 进行注册,提交邮箱,邮箱验证码,用户名,密码,图灵验证码结果(可以使用官方的API校验,验证成功会删除验证码)

手动校验图灵验证码的代码

//手动判断验证码是否正确
String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
CaptchaCacheService local = CaptchaServiceFactory.getCache("local");
boolean exists = local.exists(codeKey);
if (exists) {
    //存在
    //就可发验证
} else {
    //不存在
}
1
2
3
4
5
6
7
8
9
10

# 邮箱发送功能

邮箱验证码发送,跟业务类型有关系。

  • 注册时获取邮箱验证码(邮箱地址必须未注册的)
  • 找回密码的时候发送邮箱验证码(邮箱地址是必须已经注册的)
  • 更邮箱地址的时候,新的邮箱地址必须是未注册的

邮箱地址校验正则表达式

String regEx = "^([a-z0-9A-Z]+[-|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$"; 
Pattern p = Pattern.compile(regEx);
Matcher m = p.matcher(邮箱地址);
1
2
3

在博客系统里我们已经做过这样的功能

https://www.sunofbeach.net/a/1273944367953903616
1

异步发送邮件

https://www.sunofbeach.net/a/1274207577059655680
1

邮箱SMTP的配置

image-20220115134106775

image-20220115134244371

找到SMTP的设置

image-20220115134344810

异步发送邮件

https://www.sunofbeach.net/a/875112677616123904
1

# 前端网络模块引入

axios

https://cn.vuejs.org/v2/cookbook/using-axios-to-consume-apis.html
1

安装axios

https://github.com/axios/axios#installing
1
 npm install axios --save
1

配置

import axios from 'axios'

axios.defaults.withCredentials = true;
axios.defaults.timeout = 1000000;
axios.defaults.headers.post['Content-Type'] = 'application/x-www=form-urlencoded'

export default {
    // get请求
    requestGet(url, params = {}) {
        return new Promise((resolve, reject) => {
            axios.get(url, params).then(res => {
                console.log(res);
                resolve(res.data)
            }).catch(error => {
                reject(error)
            });
        })
    },
    // post请求
    requestPost(url, params = {}) {
        return new Promise((resolve, reject) => {
            axios.post(url, params).then(res => {
                resolve(res.data)
            }).catch(error => {
                reject(error)
            })
        })
    },
    // delete请求
    requestDelete(url, params = {}) {
        return new Promise((resolve, reject) => {
            axios.delete(url, params).then(res => {
                resolve(res.data)
            }).catch(error => {
                reject(error)
            })
        })
    },
    // put请求
    requestPut(url, params = {}) {
        return new Promise((resolve, reject) => {
            axios.put(url, params).then(res => {
                resolve(res.data)
            }).catch(error => {
                reject(error)
            })
        })
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

api.js

import http from './http';

const base_url = 'http://localhost:40100';

export const SUCCESS_CODE = 20000;

export const sendMailCode = (mailAddress) => {
    return http.requestGet(base_url + "/uc/ex/re/mail-code?mail=" + mailAddress)
};

export const registerAccount = (mailCode, userVo) => {
    return http.requestPost(base_url + "/uc/user?mailCode=" + mailCode, userVo);
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# 注册功能实现

  • 数据校验

    • 邮箱验证码是否正确
    • 邮箱地址是否有注册
    • 用户名是否已经注册
  • 默认头像

    https://cdn.sunofbeaches.com/images/default_avatar.png
    
    1
  • 密码加密

  • 数据入库

  • 返回注册结果

密码加密,添加SpringSecurity的依赖

<!--security-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
1
2
3
4
5

放行配置

@Configuration
@EnableWebSecurity
public class WebSpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有都放行
        http.authorizeRequests()
                .antMatchers("/**").permitAll()
                .and().csrf().disable();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13

密码处理的相关代码

@Autowired
protected BCryptPasswordEncoder bCryptPasswordEncoder;
//加密
bCryptPasswordEncoder.encode(sobUser.getPassword())
//校验,输入的原密码和数据库的密文进行对比
bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
1
2
3
4
5
6

# 数据自动填充

@Component
public class MBMetaObjectHandler implements MetaObjectHandler {

    @Override
    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());
        this.strictInsertFill(metaObject, "deleted", String.class, "0");
        System.out.println("insertFill...");
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
        System.out.println("updateFill...");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

逻辑删除配置项

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 输出Sql语句执行日志
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
1
2
3
4
5
6
7
8

# 登录功能

  • 来源判断
    • 作用,比如说统计,比如说小尾巴
    • 通常的做法携带一个AppID,不同的端,用不同的AppID
  • 图灵验证码校验
  • 数据校验(用户名、密码)
  • 创建token
  • 设置cookie

cookie设置的工具类

@Slf4j
public class CookieUtils {

    //1年
    public static final int default_age = 60 * 60 * 24 * 365;

    public static final String domain = "sunofbeaches.com";

    /**
     * 设置cookie值
     *
     * @param response
     * @param key
     * @param value
     */
    public static void setUpCookie(HttpServletResponse response, String key, String value) {
        setUpCookie(response, key, value, default_age);
    }


    public static void setUpCookie(HttpServletResponse response, String key, String value, int age) {
        Cookie cookie = new Cookie(key, value);
        cookie.setPath("/");
        /**
         * 域名:如果是单点登录,就设置顶级域名
         * sunofbeach.net
         * https://www.sunofbeach.net/
         * https://mp.sunofbeach.net/
         */
        //cookie.setDomain(domain);
        cookie.setMaxAge(age);
        response.addCookie(cookie);
    }

    /**
     * 删除cookie
     *
     * @param response
     * @param key
     */
    public static void deleteCookie(HttpServletResponse response, String key) {
        setUpCookie(response, key, null, 0);
    }

    /**
     * 获取cookie
     *
     * @param request
     * @param key
     * @return
     */
    public static String getCookie(HttpServletRequest request, String key) {
        Cookie[] cookies = request.getCookies();
        if (cookies == null) {
            log.info("cookies is nuLL...");
            return null;
        }
        for (Cookie cookie : cookies) {
            if (key.equals(cookie.getName())) {
                return cookie.getValue();
            }
        }
        return null;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

JWT

依赖包:

<!--JJWT-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.6.0</version>
</dependency>
1
2
3
4
5
6

JwtUtil.java

public class JwtUtil {

    //盐值,使用用户特定的盐值
    private static String key = "";

    //单位是毫秒
    private static long ttl = Constants.TimeValueInMillions.HOUR_2;//2个小时

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public long getTtl() {
        return ttl;
    }

    public void setTtl(long ttl) {
        this.ttl = ttl;
    }

    /**
     * @param claims 载荷内容
     * @param ttl    有效时长
     * @return
     */
    public static String createToken(Map<String, Object> claims, long ttl) {
        JwtUtil.ttl = ttl;
        return createToken(claims);
    }


    public static String createRefreshToken(String userId, long ttl) {
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder().setId(userId)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key);
        if (ttl > 0) {
            builder.setExpiration(new Date(nowMillis + ttl));
        }
        return builder.compact();
    }

    /**
     * @param claims 载荷
     * @return token
     */
    public static String createToken(Map<String, Object> claims) {

        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        JwtBuilder builder = Jwts.builder()
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, key);

        if (claims != null) {
            builder.setClaims(claims);
        }

        if (ttl > 0) {
            builder.setExpiration(new Date(nowMillis + ttl));
        }
        return builder.compact();
    }

    public static Claims parseJWT(String jwtStr) {
        return Jwts.parser()
                .setSigningKey(key)
                .parseClaimsJws(jwtStr)
                .getBody();
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

# 解析Token

/**
     * 解析Token
     *
     * @return
     */
@Override
public R checkToken() {
    //先是拿到tokenKey
    String tokenKey = CookieUtils.getCookie(getRequest(), Constants.User.KEY_SOB_TOKEN);
    if (TextUtils.isEmpty(tokenKey)) {
        return R.NOT_LOGIN();
    }
    //先去redis去拿token,有可能没有,有超过2个小时没有活跃了,所以没有了
    String token = (String) redisUtil.get(Constants.User.KEY_TOKEN + tokenKey);
    String salt = (String) redisUtil.get(Constants.User.KEY_SALT + tokenKey);
    if (TextUtils.isEmpty(salt)) {
        return R.NOT_LOGIN();
    }
    if (!TextUtils.isEmpty(token)) {
        //有就解析token
        try {
            Claims claims = JwtUtil.parseJWT(token, salt);
            UserVo userVo = ClaimsUtil.claim2User(claims);
            return R.SUCCESS("已登录.").setData(userVo);
        } catch (Exception e) {
            e.printStackTrace();
            //走检查refreshToken的路
            return checkRefreshToken(tokenKey, salt);
        }
    } else {
        //走检查refreshToken的路
        return checkRefreshToken(tokenKey, salt);
    }
}


/**
     * 从数据库上找到refreshToken,如果没有,就真的没有登录了
     * 如果有,判断是否有过期
     *
     * @param tokenKey
     * @return
     */
private R checkRefreshToken(String tokenKey, String salt) {
    QueryWrapper<RefreshToken> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("token_key", tokenKey);
    queryWrapper.select("refresh_token", "user_id");
    RefreshToken refreshToken = tokenService.getOne(queryWrapper);
    if (refreshToken != null) {
        try {
            JwtUtil.parseJWT(refreshToken.getRefreshToken(), salt);
            //通过用户ID查到用户的内容,再创建token
            String userId = refreshToken.getUserId();
            //先删除原来的数据
            redisUtil.del(Constants.User.KEY_TOKEN + tokenKey);
            QueryWrapper<RefreshToken> refreshTokenQueryWrapper = new QueryWrapper<>();
            queryWrapper.eq("user_id", userId);
            tokenService.remove(refreshTokenQueryWrapper);
            //创建新的token
            User user = getById(userId);
            UserVo userVo = new UserVo();
            userVo.setId(userId);
            userVo.setUserName(user.getUserName());
            userVo.setAvatar(userVo.getAvatar());
            userVo.setSex(userVo.getSex());
            userVo.setStatus(userVo.getStatus());
            userVo.setSalt(userVo.getSalt());
            createToken(userVo);
            return R.SUCCESS("已登录").setData(userVo);
        } catch (Exception e) {
            e.printStackTrace();
            //过期了就是走未登录
        }
    }
    return R.NOT_LOGIN();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76

# 重置密码

通过邮箱找回密码,首先填写邮箱,发送邮箱验证码,填写邮箱码,验证通过以后,再设置新的密码。

# 定义用户管理的接口

  • 用户列表(有条件,搜索)
  • 用户禁止
  • 重置密码
@RestController
public class UserAdminController {

    @GetMapping("/uc/admin/user/list")
    public R listUser(@RequestParam(value = "phone", required = false) String phone,
                      @RequestParam(value = "email", required = false) String email,
                      @RequestParam(value = "name", required = false) String userName,
                      @RequestParam(value = "id", required = false) String userId,
                      @RequestParam(value = "state", required = false) String state) {
        return null;
    }

    @PutMapping("/uc/admin/user/disable/{userId}")
    public R disableUser(@PathVariable("userId") String userId) {
        return null;
    }

    @PutMapping("/uc/admin/user/reset/{userId}")
    public R resetPassword(@PathVariable("userId") String userId,
                           @RequestBody RegisterVo registerVo) {
        return null;
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 实现用户列表的获取

/**
     * 获取用户列表
     *
     * @param page     页码 ,从1开始
     * @param phone    手机号
     * @param email    邮箱地址
     * @param userName 用户名
     * @param userId   用户ID
     * @param status   状态
     * @return
     */
@Override
public R listUser(int page, String phone, String email,
                  String userName, String userId,
                  String status) {
    //检查页码,不过小
    page = checkPage(page);
    int size = Constants.DEFAULT_SIZE;
    int offset = (page - 1) * size;
    //查询数据
    List<UserAdminVo> userAdminVos = this.baseMapper.listUser(size, offset, page, phone, email, userName, userId, status);
    //有总数量
    PageVo<UserAdminVo> pageVo = new PageVo<>();
    pageVo.setList(userAdminVos);
    pageVo.setCurrentPage(page);
    pageVo.setListSize(size);
    //不是第一页,就是有上一页
    pageVo.setHasPre(page != 1);
    //总数,总的页数,是否下一页
    //总数要查询出来
    //总页数 = 总数/size
    // 比如说我65个记录,size是30,应该有3页
    // 60个记录的时候,那么就是2页
    //如果是55个记录,也是2页
    //65/30 = 2.x ==> 3,如果2.0===>2。55/30 = 1.x
    //是否有下一页:如果当前页不是最后一页 ,那么就表示有下一页
    long totalCount = this.baseMapper.listUserTotalCount(phone, email, userName, userId, status);
    pageVo.setTotalCount(totalCount);
    //计算总的页数
    float tempTotalPage = (totalCount * 1.0f / size) + 0.5f;
    log.info("tempTotalPage ==> " + tempTotalPage);
    int totalPage = Math.round(tempTotalPage);
    pageVo.setTotalPage(totalPage);
    pageVo.setHasNext(page != totalPage);
    return R.SUCCESS("查询用户列表成功.").setData(pageVo);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

mapper.xml代码

<select id="listUser" resultType="net.sunofbeach.uc.vo.UserAdminVo">
    SELECT
    uu.`id`,uu.`user_name`,uu.`phone_num`,uu.`email`,uu.`lev`,uu.`sex`,uu.`avatar`,uu.`status`,uu.`create_time`,uui.`compony`,uui.`birthday`,uui.`position`,uui.`good_at`,uui.`location`
    FROM `uc_user` uu
    LEFT JOIN `uc_user_info` uui ON uu.`id` = uui.`user_id`
    <where>
        1=1
        <if test="email!=null and email!=''">
            and uu.`email` = #{email}
        </if>
        <if test="phone!=null and phone!=''">
            and uu.`phone_num` = #{phone}
        </if>
        <if test="userId!=null and userId!=''">
            and uu.`id` = #{userId}
        </if>
        <if test="userId!=null and userId!=''">
            and uu.`id` = #{userId}
        </if>
        <if test="status!=null and status!=''">
            and uu.`status` = #{status}
        </if>
    </where>
    ORDER BY uu.`create_time` DESC
    LIMIT #{offset},#{size}
</select>
<select id="listUserTotalCount" resultType="java.lang.Long">
    SELECT count(uu.`id`)
    FROM `uc_user` uu
    <where>
        1=1
        <if test="email!=null and email!=''">
            and uu.`email` = #{email}
        </if>
        <if test="phone!=null and phone!=''">
            and uu.`phone_num` = #{phone}
        </if>
        <if test="userId!=null and userId!=''">
            and uu.`id` = #{userId}
        </if>
        <if test="userId!=null and userId!=''">
            and uu.`id` = #{userId}
        </if>
        <if test="status!=null and status!=''">
            and uu.`status` = #{status}
        </if>
    </where>
</select>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 实现用户禁止

/**
     * 禁止用户
     * - 用户是否存在
     * - 修改用户状态值
     * - 让用户退出登录
     * - 登录的代码要检查状态
     *
     * @param userId
     * @return
     */
@Override
public R disableUser(String userId) {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("id", userId);
    User user = this.baseMapper.selectOne(queryWrapper);
    if (user == null) {
        return R.FAILED("用户不存在.");
    }
    user.setStatus(Constants.User.STATUS_DISABLE);
    this.baseMapper.updateById(user);
    //让用户退出登录,不能直接调用logout,这样子会让自己退出登录.
    //可以通过userId去refreshToken的表里查询相关数据,然后退出登录
    QueryWrapper<RefreshToken> tokenQueryWrapper = new QueryWrapper<>();
    tokenQueryWrapper.eq("user_id", user.getId());
    RefreshToken refreshToken = tokenService.getOne(tokenQueryWrapper);
    //各种删除
    // token
    String tokenKey = refreshToken.getTokenKey();
    redisUtil.del(Constants.User.KEY_TOKEN + tokenKey);
    //删除refreshToken
    //salt
    redisUtil.del(Constants.User.KEY_SALT + tokenKey);
    tokenService.remove(tokenQueryWrapper);
    return R.SUCCESS("已禁用该用户.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 实现用户密码重置

/**
     * 管理员重置密码
     * - 查找用户是否存在
     * - 对密码判断和处理
     * - 修改密码
     * - 让用户退出登录
     *
     * @param userId
     * @param registerVo
     * @return
     */
@Override
public R resetPasswordByUid(String userId, RegisterVo registerVo) {
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("id", userId);
    User user = this.baseMapper.selectOne(queryWrapper);
    if (user == null) {
        return R.FAILED("用户不存在.");
    }
    if (TextUtils.isEmpty(registerVo.getPassword()) || registerVo.getPassword().length() != 32) {
        return R.FAILED("密码格式不对.");
    }
    String encodePwd = bCryptPasswordEncoder.encode(registerVo.getPassword());
    user.setPassword(encodePwd);
    this.baseMapper.updateById(user);
    //让对应的用户退出登录
    doLogoutByUid(userId);
    return R.SUCCESS("用户密码重置成功.");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# RBAC (role-base access control)

  • 角色
  • 权限
  • 用户

用户因为是某个角色而有权限,比如你是校长,所以你有删除学生资料的权限。而不是因为你,所有有权限删除学生。

用户-角色,角色-权限

回到我们的案例里:你-管理员,管理员-获取用户列表,管理员-禁止用户,管理员-重置密码

# 涉及到的数据表

  • 用户表,已经有了
  • 角色表
  • 权限表
  • 模块表(微服务多应用)
  • =======关系表======
  • 用户-角色表
  • 角色-权限表

# 表的字段

  • 角色表
    • id:ID
    • name:角色名称,ADMIN,SU_ADMIN
    • label:标签,比如说管理员,超级管理员
    • crate_time:创建时间
    • update_time:更新时间
  • 权限表
    • id:ID
    • name:权限名称,controller名:方法名:请求方法
    • label:权限标签:禁止用户,重置密码
    • api:请求路径
    • create_time:创建时间
    • update_time:更新时间
  • 用户角色表
    • id:ID
    • user_id:用户ID
    • role_id:角色ID
    • create_time:创建时间
    • update_time:更新时间
  • 角色-权限表
    • id:ID
    • role_id:角色ID
    • permission_id:权限ID
    • create_time:创建时间
    • update_time:更新时间
  • 角色表
CREATE TABLE `mo_yu_ucenter`.`uc_role`(  
  `id` VARCHAR(20) NOT NULL COMMENT 'ID',
  `name` VARCHAR(32) COMMENT '角色名称',
  `label` VARCHAR(32) COMMENT '标签',
  `create_time` DATETIME COMMENT '创建时间',
  `update_time` DATETIME COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
1
2
3
4
5
6
7
8

权限表

CREATE TABLE `mo_yu_ucenter`.`uc_permission`(  
  `id` VARCHAR(20) NOT NULL COMMENT 'ID',
  `name` VARCHAR(64) COMMENT '权限名称',
  `label` VARCHAR(32) COMMENT '标签',
  `api` VARCHAR(128) COMMENT '请求路径',
  `model_id` VARCHAR(20) COMMENT '模块Id',
  `create_time` DATETIME COMMENT '创建时间',
  `update_time` DATETIME COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
1
2
3
4
5
6
7
8
9
10

模块表

CREATE TABLE `mo_yu_ucenter`.`uc_app_model`(  
  `id` VARCHAR(20) NOT NULL COMMENT 'ID',
  `app_name` VARCHAR(32) COMMENT '模块名称',
  `label` VARCHAR(32) COMMENT '标签',
  `create_time` DATETIME COMMENT '创建时间',
  `update_time` DATETIME COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
1
2
3
4
5
6
7
8

用户-角色表

CREATE TABLE `mo_yu_ucenter`.`uc_user_role`(  
  `id` VARCHAR(20) NOT NULL COMMENT 'ID',
  `user_id` VARCHAR(20) COMMENT '用户ID',
  `role_id` VARCHAR(20) COMMENT '角色ID',
  `create_time` DATETIME COMMENT '创建时间',
  `update_time` DATETIME COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
1
2
3
4
5
6
7
8

角色-权限表

CREATE TABLE `mo_yu_ucenter`.`uc_role_permission`(  
  `id` VARCHAR(20) NOT NULL COMMENT 'ID',
  `role_id` VARCHAR(20) COMMENT '角色ID',
  `permission_id` VARCHAR(20) COMMENT '权限ID',
  `create_time` DATETIME COMMENT '创建时间',
  `update_time` DATETIME COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=INNODB CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
1
2
3
4
5
6
7
8

# 登录以后token需要包含用户角色

查询语句

SELECT GROUP_CONCAT(ur.`name`) AS roles,uu.`id`,uu.`sex`,uu.`status`,uu.`avatar`,uu.`user_name` FROM `uc_user` uu
LEFT JOIN `uc_user_role` uur ON uu.`id` = uur.`user_id`
LEFT JOIN `uc_role` ur ON uur.`role_id` = ur.`id`
WHERE uu.`id` = '1500421581694861313'
1
2
3
4

如果报错,加以下配置

[mysqld]
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
1
2
编辑 (opens new window)
项目创建
管理中心项目准备

← 项目创建 管理中心项目准备→

Theme by Vdoing | Copyright © 2022-2022 sunofbeach.net
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式