• SpringBoot轻轻松松搞定用户邮箱登录注册


    前言

    ok,我又来水博文了,今天的内容很简单,就是咱们的这个用户登录注册,加上邮箱验证,非常简单,为我们接下来的Auto2.0做好前置工作。因为这个没做好,那个也做不好。本文的内容可能比较多,但是都很简单。

    效果演示

    注册效果

    ok,多说无益,我们先来看看完成后的效果咋样。
    注册首页:
    在这里插入图片描述

    在这里插入图片描述
    这块的话,咱们这边发送邮箱验证码之后,前端这边的验证码还会重新刷新一次,反正是在10分钟内完成操作。
    在这里插入图片描述

    登录

    在这里插入图片描述

    环境准备

    邮箱准备

    首先我们需要使用到邮箱服务,所以的话我们需要去申请到这个邮箱发送的权限,这个也简单,我们以QQ邮箱为例子,打开这个QQ邮箱的管理页面,然后开启下面的服务就好了,之后的话,会显示密钥,这个请记下来。
    在这里插入图片描述

    相关工具类

    为了提高开发效率,我这里准备了几个工具类。用于方便后续的操作。
    在这里插入图片描述

    验证码工具类

    这个工具类就是单纯用来产生验证码的。

    
    public class CodeUtils {
        public static void main(String[] args) {
            String s = creatCode(4);
            System.out.println("随机验证码为:" + s);
    
        }
    
        //定义一个方法返回一个随机验证码
        public static String creatCode(int n) {
    
            String code = "";
            Random r = new Random();
            //2.在方法内部使用for循环生成指定位数的随机字符,并连接起来
            for (int i = 0; i <= n; i++) {
                //生成一个随机字符:大写 ,小写 ,数字(0  1  2)
                int type = r.nextInt(3);
                switch (type) {
                    case 0:
                        char ch = (char) (r.nextInt(26) + 65);
                        code += ch;
                        break;
                    case 1:
                        char ch1 = (char) (r.nextInt(26) + 97);
                        code += ch1;
                        break;
                    case 2:
                        code +=  r.nextInt(10);
                        break;
                }
    
            }
            return code;
        }
    }
    
    • 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

    日期工具类

    这个主要是对日期进行处理的,可以很方便得到YY-MM-DD HH:MM:SS 的日期。

    public class DateUtils {
    
        /**
         * 获得当前日期 yyyy-MM-dd HH:mm:ss
         *
         */
        public static String getCurrentTime() {
            // 小写的hh取得12小时,大写的HH取的是24小时
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = new Date();
            return df.format(date);
        }
    
        /**
         * 获取系统当前时间戳
         *
         */
        public static String getSystemTime() {
            String current = String.valueOf(System.currentTimeMillis());
            return current;
        }
    
    
        /**
         * 获取当前日期 yy-MM-dd
         */
        public static String getDateByString() {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            return sdf.format(date);
        }
    
        /**
         * 得到两个时间差  格式yyyy-MM-dd HH:mm:ss
         
         */
        public static long dateSubtraction(String start, String end) {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date date1 = df.parse(start);
                Date date2 = df.parse(end);
                return date2.getTime() - date1.getTime();
            } catch (ParseException e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 得到两个时间差
         *
         * @param start 开始时间
         * @param end 结束时间
         * @return
         */
        public static long dateTogether(Date start, Date end) {
            return end.getTime() - start.getTime();
        }
    
        /**
         * 转化long值的日期为yyyy-MM-dd  HH:mm:ss.SSS格式的日期
         *
         * @param millSec 日期long值  5270400000
         * @return 日期,以yyyy-MM-dd  HH:mm:ss.SSS格式输出 1970-03-03  08:00:00.000
         */
        public static String transferLongToDate(String millSec) {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd  HH:mm:ss.SSS");
            Date date = new Date(Long.parseLong(millSec));
            return sdf.format(date);
        }
    
        /**
         * 获得当前日期 yyyy-MM-dd HH:mm:ss
         *
         * @return
         */
        public static String getOkDate(String date) {
            try {
                if (StringUtils.isEmpty(date)) {
                    return null;
                }
                Date date1 = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy", Locale.ENGLISH).parse(date);
                //格式化
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                return sdf.format(date1);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        /**
         * 获取当前日期是一个星期的第几天
        
         */
        public static int getDayOfWeek() {
            Calendar cal = Calendar.getInstance();
            cal.setTime(new Date());
            return cal.get(Calendar.DAY_OF_WEEK) - 1;
        }
    
    
        /**
         * 判断当前时间是否在[startTime, endTime]区间,注意时间格式要一致
         * @param nowTime     当前时间
         * @param dateSection 时间区间   yy-mm-dd,yy-mm-dd
         */
        public static boolean isEffectiveDate(Date nowTime, String dateSection) {
            try {
                String[] times = dateSection.split(",");
                String format = "yyyy-MM-dd";
                Date startTime = new SimpleDateFormat(format).parse(times[0]);
                Date endTime = new SimpleDateFormat(format).parse(times[1]);
                if (nowTime.getTime() == startTime.getTime()
                        || nowTime.getTime() == endTime.getTime()) {
                    return true;
                }
                Calendar date = Calendar.getInstance();
                date.setTime(nowTime);
    
                Calendar begin = Calendar.getInstance();
                begin.setTime(startTime);
    
                Calendar end = Calendar.getInstance();
                end.setTime(endTime);
    
                if (isSameDay(date, begin) || isSameDay(date, end)) {
                    return true;
                }
                if (date.after(begin) && date.before(end)) {
                    return true;
                } else {
                    return false;
                }
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        public static boolean isSameDay(Calendar cal1, Calendar cal2) {
            if (cal1 != null && cal2 != null) {
                return cal1.get(0) == cal2.get(0) && cal1.get(1) == cal2.get(1) && cal1.get(6) == cal2.get(6);
            } else {
                throw new IllegalArgumentException("The date must not be null");
            }
        }
    
        public static long getTimeByDate(String time) {
            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date date = format.parse(time);
                //日期转时间戳(毫秒)
                return date.getTime();
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 获取当前小时 :2020-10-3 17
        
         */
        public static String getCurrentHour() {
            GregorianCalendar calendar = new GregorianCalendar();
            int hour = calendar.get(Calendar.HOUR_OF_DAY);
            if (hour < 10) {
                return DateUtils.getCurrentTime() + " 0" + hour;
            }
            return DateUtils.getDateByString() + " " + hour;
        }
    
        /**
         * 获取当前时间一个小时前
         */
        public static String getCurrentHourBefore() {
            GregorianCalendar calendar = new GregorianCalendar();
            int hour = calendar.get(Calendar.HOUR_OF_DAY);
            if (hour > 0) {
                hour = calendar.get(Calendar.HOUR_OF_DAY) - 1;
                if (hour < 10) {
                    return DateUtils.getDateByString() + " 0" + hour;
                }
                return DateUtils.getDateByString() + " " + hour;
            }
            //获取当前日期前一天
            return DateUtils.getBeforeDay() + " " + 23;
        }
    
        /**
         * 获取当前日期前一天
    
         */
        public static String getBeforeDay() {
    
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Date date = new Date();
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            calendar.add(Calendar.DAY_OF_MONTH, -1);
            date = calendar.getTime();
            return sdf.format(date);
        }
    
    
        /**
         * 获取最近七天
    
         */
        public static String getServen() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
            Calendar c = Calendar.getInstance();
    
            c.add(Calendar.DATE, -7);
    
            Date monday = c.getTime();
    
            String preMonday = sdf.format(monday);
    
            return preMonday;
        }
    
        /**
         * 获取最近一个月
    
         */
        public static String getOneMonth() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
            Calendar c = Calendar.getInstance();
    
            c.add(Calendar.MONTH, -1);
    
            Date monday = c.getTime();
    
            String preMonday = sdf.format(monday);
    
            return preMonday;
        }
    
        /**
         * 获取最近三个月
    
         */
        public static String getThreeMonth() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    
            Calendar c = Calendar.getInstance();
    
            c.add(Calendar.MONTH, -3);
    
            Date monday = c.getTime();
    
            String preMonday = sdf.format(monday);
    
            return preMonday;
        }
    
        /**
         * 获取最近一年
    
         */
        public static String getOneYear() {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            Calendar c = Calendar.getInstance();
            c.add(Calendar.YEAR, -1);
            Date start = c.getTime();
            String startDay = sdf.format(start);
            return startDay;
        }
    
    
        private static int month = Calendar.getInstance().get(Calendar.MONTH) + 1;
        /**
         * 获取今年月份数据
         * 说明 有的需求前端需要根据月份查询每月数据,此时后台给前端返回今年共有多少月份
         *
         * @return [1, 2, 3, 4, 5, 6, 7, 8]
         */
        public static List getMonthList(){
            List list = new ArrayList();
            for (int i = 1; i <= month; i++) {
                list.add(i);
            }
            return list;
        }
    
        /**
         * 返回当前年度季度list
         * 本年度截止目前共三个季度,然后根据1,2,3分别查询相关起止时间
         * @return [1, 2, 3]
         */
        public static List getQuartList(){
            int quart = month / 3 + 1;
            List list = new ArrayList();
            for (int i = 1; i <= quart; i++) {
                list.add(i);
            }
            return list;
        }
    
        public static void main(String[] args) {
            System.out.println(DateUtils.getQuartList());
        }
    }
    
    
    • 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

    Redis 工具类

    public class RedisUtils {
    
        @Autowired
        private RedisTemplate<String,Object> redisTemplate;
    
        /**
         *  指定缓存失效时间
         * @param key 键
         * @param time 时间(秒)
         * @return
         */
        public boolean expire(String key, long time) {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
                return true;
            } else {
                throw new RuntimeException("超时时间小于0");
            }
        }
    
    
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
    
        /**
         * 根据key 获取过期时间
         * @param key 键 不能为null
         * @param tiemtype 时间类型
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key,TimeUnit tiemtype) {
    
            return redisTemplate.getExpire(key, tiemtype);
        }
    
        /**
         *  判断key是否存在
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(String key) {
            return Boolean.TRUE.equals(redisTemplate.hasKey(key));
        }
    
        /**
         *  删除缓存
         * @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));
                }
            }
        }
    
    // ============================String=============================
        /**
         * 普通缓存获取
         * @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) {
            redisTemplate.opsForValue().set(key, value);
            return true;
        }
    
    
        /**
         *  普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time) {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                this.set(key, value);
            }
            return true;
        }
    
        /**
         *  普通缓存放入并设置时间
         * @param key 键
         * @param value 值
         * @param time time 时间类型自定义设定
         * @return true成功 false 失败
         */
    
        public boolean set(String key, Object value, long time,TimeUnit tiemtype) {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, tiemtype);
            } else {
                this.set(key, value);
            }
            return true;
        }
    
    
    
    
        /**
         *  递增
         * @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);
        }
    
    // ================================Map=================================
    
        /**
         *  HashGet
         * @param key 键
         * @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) {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        }
        /**
         * HashSet 并设置时间
         * @param key 键
         * @param map 对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        public boolean hmset(String key, Map<String, Object> map, long time) {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value) {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        }
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         * @param key 键
         * @param item 项
         * @param value 值
         * @param time 时间(秒)  注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value, long time) {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 删除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);
        }
        // ============================set=============================
        /**
         * 根据key获取Set中的所有值
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key) {
            return redisTemplate.opsForSet().members(key);
        }
        /**
         * 根据value从一个set中查询,是否存在
         * @param key 键
         * @param value 值
         * @return true 存在 false不存在
         */
        public boolean sHasKey(String key, Object value) {
            return redisTemplate.opsForSet().isMember(key, value);
        }
        /**
         * 将数据放入set缓存
         * @param key 键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object... values) {
            return redisTemplate.opsForSet().add(key, values);
        }
        /**
         * 将set数据放入缓存
         * @param key 键
         * @param time 时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(String key, long time, Object... values) {
            final Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        }
        /**
         * 获取set缓存的长度
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key) {
            return redisTemplate.opsForSet().size(key);
        }
        /**
         * 移除值为value的
         * @param key 键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            final Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        }
        // ===============================list=================================
        /**
         * 获取list缓存的内容
         * @param key 键
         * @param start 开始
         * @param end 结束  0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            return redisTemplate.opsForList().range(key, start, end);
        }
        /**
         * 获取list缓存的长度
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            return redisTemplate.opsForList().size(key);
        }
        /**
         * 通过索引 获取list中的值
         * @param key 键
         * @param index 索引  index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            return redisTemplate.opsForList().index(key, index);
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @param time 时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSetList(String key, List<Object> value) {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        }
        /**
         * 将list放入缓存
         * @param key 键
         * @param value 值
         * @return
         */
        public boolean lSetList(String key, List<Object> value, long time) {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        }
        /**
         * 根据索引修改list中的某条数据
         * @param key 键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        }
    
    }
    
    • 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

    专门用来管理key的类

    这个主要是对key做一个分组,一是美化,而是方便管理。

    public class RedisTransKey {
    
        public static final String RedisNameSpace="user";
        public static final String RedisTokenName="token";
        public static final String RedisLoginName="login";
        public static final String RedisEmailCodeName="emailCode";
    
        public static String setEmailKey(String key){
            return RedisNameSpace+":"+RedisEmailCodeName+":"+key;
        }
        public static String setRootKey(String key){
            return RedisNameSpace+":"+key+":";
        }
        public static String setTokenKey(String key){
            return RedisNameSpace+':'+RedisTokenName+":"+key;
        }
        public static String setLoginKey(String key){
            return RedisNameSpace+':'+RedisLoginName+":"+key;
        }
    
        public static String getEmailKey(String key){return setEmailKey(key);}
        public static String getRootKey(String key){return setRootKey(key);}
        public static String getTokenKey(String key){return setTokenKey(key);}
        public static String getLoginKey(String key){return setLoginKey(key);}
    
    }
    
    
    • 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

    JWT的封装

    这个主要是为了校验,产生token用的,值得一提的是,我们的token并不是放在Mysql里面的,而是放在了redis里面,主要是为了提高查询速度,减轻数据库压力,毕竟token这玩意是有时间限制的,而且对于校验修改的速度要求还挺高。

    public class JwtTokenUtil {
    
        private static String secret;
        private static Long expiration;
        private static Map<String, Object> header;
        static {
            secret="你的SRET";
            expiration = 7*24*60*60*1000L;
            header=new HashMap<>();
            header.put("typ", "jwt");
    
    
        }
    
    
        /**
         * 生成token令牌
         * @return 令token牌
         */
        public static String generateToken(UserEntity user) {
            Map<String, Object> claims = new HashMap<>();
            claims.put("username", user.getUsername());
            claims.put("userid",user.getUserid());
            claims.put("created", new Date());
            return generateToken(claims);
        }
    
        /**
         * @param token 令牌
         * @return 用户名
         */
        public static String GetUserNameFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = (String) claims.get("username");
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
        public static String GetUserIDFromToken(String token) {
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username = (String) claims.get("userid");
            } catch (Exception e) {
                username = null;
            }
            return username;
        }
        /**
         * 判断令牌是否过期
         * @param token 令牌
         * @return 是否过期
         */
        public static Boolean isTokenExpired(String token) {
            try {
                Claims claims = getClaimsFromToken(token);
                Date expiration = claims.getExpiration();
                return expiration.before(new Date());
            } catch (Exception e) {
                return false;
            }
        }
    
        /**
         * 刷新令牌
         *
         * @param token 原令牌
         * @return 新令牌
         */
        public String refreshToken(String token) {
            String refreshedToken;
            try {
                Claims claims = getClaimsFromToken(token);
                claims.put("created", new Date());
                refreshedToken = generateToken(claims);
            } catch (Exception e) {
                refreshedToken = null;
            }
            return refreshedToken;
        }
    
        /**
         * 验证令牌
         * @return 是否有效
         */
        public static Boolean validateToken(String token, UserEntity user) {
            String username = GetUserNameFromToken(token);
            return (username.equals(user.getUsername()) && !isTokenExpired(token));
        }
    
    
        /**
         * 从claims生成令牌,如果看不懂就看谁调用它
         *
         * @param claims 数据声明
         * @return 令牌
         */
        private static String generateToken(Map<String, Object> claims) {
            Date expirationDate = new Date(System.currentTimeMillis() + expiration);
            return Jwts.builder()
                    .setHeader(header)
                    .setClaims(claims)
                    .setExpiration(expirationDate)
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }
    
        /**
         * 从令牌中获取数据声明,如果看不懂就看谁调用它
         *
         * @param token 令牌
         * @return 数据声明
         */
        public static Claims getClaimsFromToken(String token) {
            Claims claims;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                claims = null;
            }
            return claims;
        }
    
    }
    
    • 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

    密码加密比对

    这个主要是用来加密和对比密码的,这里密码是解不了密的,因为用的md5压根不是“加密”算法,而是“特征”算法,计算你的密码的特征,既然只是拿到了“特征”那么显然不可能通过“特征”完整地还原一个东西,因为信息存在损失。

    我们这块使用的是Spring提供的方案。

    public class SecurityUtils {
        /**
         * 生成BCryptPasswordEncoder密码
         * @param password 密码
         * @return 加密字符串
         */
       public static BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        public static String encodePassword(String password)
        {
            return passwordEncoder.encode(password);
        }
    
        /**
         * 判断密码是否相同
         * @param rawPassword 真实密码
         * @param encodedPassword 加密后字符
         * @return 结果
         */
        public static boolean matchesPassword(String rawPassword, String encodedPassword)
        {
            return passwordEncoder.matches(rawPassword, encodedPassword);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    接口与实体

    接口

    之后是我们的接口和实体类,这个很重要,我们目前的这个玩意其实就三个接口。

    register
    login
    emailCode
    
    • 1
    • 2
    • 3

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

    实体

    之后是我们前后端分类的实体类了。专门再提取出单独的Entity而不是直接使用数据库的原因很简单,首先提交的一些信息和那些实体类有些对不上,其次,我的实体类是按照数据库的字段来的,如果直接这样搞的话,很容易猜到我数据库的字段。所以我单独搞了一个专门和前端交互的Entity,同时使用JSR303校验美滋滋。
    在这里插入图片描述
    这些实体我是按照功能来划分的。

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class LoginEntity {
    
        private String username;
        @NotEmpty(message = "用户密码不能为空")
        @Length(min = 6,max = 18,message="密码必须是6-18位")
        private String password;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    这个EmailCode主要是后面对Email的验证码进行处理的,这个是存在Redis里面的将来。

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class EmailCodeEntity {
        private String emailCode;
        private String username;
        private String email;
        private int times;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class GetEmailCodeEntity {
    
        @NotNull(message = "用户邮箱不能为空")
        @Email(message = "邮箱格式错位")
        private String email;
        @NotNull(message = "用户账号不能为空")
        private String username;
        @NotNull(message = "用户密码不能为空")
        @Size(min=6, max=15,message="密码长度必须在 6 ~ 15 字符之间!")
        @Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!")
        private String password;
        @NotNull(message = "用户昵称不能为空")
        private String nickname;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class RegisterEntity {
    
    //    @Pattern(regexp = "^[1]([3-9])[0-9]{9}$",message = "手机号格式不正确")
        private String phone;
        @NotEmpty(message = "用户昵称不能为空")
        private String nickname;
        @NotEmpty(message = "用户账号不能为空")
        private String username;
        @NotEmpty(message = "用户密码不能为空")
        @Length(min = 6,max = 18,message="密码必须是6-18位")
        private String password;
        @NotEmpty(message = "用户邮箱不能为空")
        @Email(message = "邮箱格式错位")
        private String email;
        @NotEmpty(message = "邮箱验证码不能为空")
        private String emailCode;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    信息枚举类

    同时的话,为了统一方便管理,这里专门做了一个枚举类。

    public enum BizCodeEnum {
        UNKNOW_EXCEPTION(10000,"系统未知异常"),
        VAILD_EXCEPTION(10001,"参数格式校验失败"),
        HAS_USERNAME(10002,"已存在该用户"),
        OVER_REQUESTS(10003,"访问频次过多"),
        OVER_TIME(10004,"操作超时"),
        BAD_DOING(10005,"疑似恶意操作"),
        BAD_EMAILCODE_VERIFY(10007,"邮箱验证码错误"),
        REPARATION_GO(10008,"请重新操作"),
        NO_SUCHUSER(10009,"该用户不存在"),
        BAD_PUTDATA(10010,"信息提交错误,请重新检查"),
        SUCCESSFUL(200,"successful");
    
        private int code;
        private String msg;
        BizCodeEnum(int code,String msg){
            this.code = code;
            this.msg = msg;
        }
    
        public int getCode() {
            return code;
        }
    
        public String getMsg() {
            return msg;
        }
    }
    
    
    • 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

    这个是用来定义一些异常操作的。
    当然还有我们还有R返回类。

    public class R extends HashMap<String, Object> {
    	private static final long serialVersionUID = 1L;
    	
    	public R() {
    		put("code", 0);
    		put("msg", "success");
    	}
    	
    	public static R error() {
    		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
    	}
    	
    	public static R error(String msg) {
    		return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
    	}
    	
    	public static R error(int code, String msg) {
    		R r = new R();
    		r.put("code", code);
    		r.put("msg", msg);
    		return r;
    	}
    
    	public static R ok(String msg) {
    		R r = new R();
    		r.put("msg", msg);
    		return r;
    	}
    	
    	public static R ok(Map<String, Object> map) {
    		R r = new R();
    		r.putAll(map);
    		return r;
    	}
    	
    	public static R ok() {
    		return new R();
    	}
    
    	public R put(String key, Object value) {
    		super.put(key, value);
    		return this;
    	}
    }
    
    
    • 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

    统一异常处理

    之后是我们的异常处理了,这个直接交给切片去做。
    这里的话复杂管理整个的Controller的异常

    
    @Slf4j
    @RestControllerAdvice(basePackages = "com.huterox.whitehole.whiteholeuser.controller")
    public class UserExceptionControllerAdvice {
    
        @ExceptionHandler(value= MethodArgumentNotValidException.class)
        public R handleVaildException(MethodArgumentNotValidException e){
            log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
            BindingResult bindingResult = e.getBindingResult();
    
            Map<String,String> errorMap = new HashMap<>();
            bindingResult.getFieldErrors().forEach((fieldError)->{
                errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
            });
            return R.error(BizCodeEnum.VAILD_EXCEPTION.getCode(),BizCodeEnum.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
        }
    
        @ExceptionHandler(value = Throwable.class)
        public R handleException(Throwable throwable){
    
            log.error("错误:",throwable);
            return R.error(BizCodeEnum.UNKNOW_EXCEPTION.getCode(),BizCodeEnum.UNKNOW_EXCEPTION.getMsg());
        }
    
    }
    
    
    • 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

    登录流程

    我们先从最简单的开始讲起吧。因为这个流程是最好搞的。
    在这里插入图片描述
    这个就是最简单的流程。

    前端

    这个前端没啥,主要就是使用axios发生请求。

       this.axios({
              url: "/user/user/login",
              method: 'post',
              data:{
                "username":this.formLogin.username,
                "password":this.formLogin.password
              }
            }).then((res)=>{
                res = res.data
              if (res.code===10001){
                alert("请将对应信息填写完整!")
              }else if(res.code===0){
                alert("登录成功")
    
                sessionStorage.setItem("loginToken",res.loginToken)
                this.$router.push("/userinfo")
              }else {
                this.$message.error(res.msg);
              }
            })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    后端

    首先,会先通过我们的校验,通过之后触发我们的流程。
    我们这一块有几个点要做

    密码比对

    我们无法解密,所以我们只能比对,这里就用到了先前封装好的工具。

    SecurityUtils.matchesPassword(password,User.getPassword())
    
    • 1

    防刷

    由于我们每次在进行用户登录的时候都是需要查询数据库的,并且每个人访问的时候,请求的数据都不一样,所以很难存到缓存里面,因为可能几天就一次,除非是永久存储,但是这个内存消耗就太大了。所以只能直接查数据库,所以这里的话就可能存在恶意刷接口,导致mysql瘫痪的情况。所以需要做防刷,限制请求频次,最好的方案就是在redis里面记录一下。

    只有访问接口,我们就这样

    redisUtils.set(RedisTransKey.setLoginKey(username),1,20);
    
    • 1

    开始的时候在判断一下:

    if(redisUtils.hasKey(RedisTransKey.getLoginKey(username))){
                return R.error(BizCodeEnum.OVER_REQUESTS.getCode(),BizCodeEnum.OVER_REQUESTS.getMsg());
            }
    
    • 1
    • 2
    • 3

    20s的话,可能太久了可以适当减少一点,但是如果是密码输错了的话可能很快就修改好了。当然这样做还是有漏洞的,我们只是根据这个username来的,实际上脚本换一个username就好了,只要随机生成username我们就一样不行,那么这个时候的话就要锁IP了,这个也有个问题,那就是有些地方是公共IP,也就是很多人共用一个IP,那就尴尬了,而且还有就是这个要做的话应该在网关去做,这样会更好一点,或者是拦截器去做。所以这里我就不做了,原理是一样的。

    完整代码

     public R Login(LoginEntity entity) {
    
            String username = entity.getUsername();
            String password = entity.getPassword();
            password=password.replaceAll(" ","");
            if(redisUtils.hasKey(RedisTransKey.getLoginKey(username))){
                return R.error(BizCodeEnum.OVER_REQUESTS.getCode(),BizCodeEnum.OVER_REQUESTS.getMsg());
            }
            redisUtils.set(RedisTransKey.setLoginKey(username),1,20);
            UserEntity User = userService.getOne(
                    new QueryWrapper<UserEntity>().eq("username", username)
            );
            if(User!=null){
                if(SecurityUtils.matchesPassword(password,User.getPassword())){
                    //登录成功,签发token
                    String token = JwtTokenUtil.generateToken(User);
                    redisUtils.set(RedisTransKey.setTokenKey(username),token,7, TimeUnit.DAYS);
                    return R.ok(BizCodeEnum.SUCCESSFUL.getMsg()).put("loginToken",token);
                }else {
                    return R.error(BizCodeEnum.BAD_PUTDATA.getCode(),BizCodeEnum.BAD_PUTDATA.getMsg());
                }
            }else {
                return R.error(BizCodeEnum.NO_SUCHUSER.getCode(),BizCodeEnum.NO_SUCHUSER.getMsg());
            }
        }
    
    • 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

    注册流程

    前端

    咱们这个前端也没啥,就是两个。

      getEmailCode () {
          const that = this
          if (this.formRegister.email === '') {
            this.$message.error('请先输入邮箱再点击获取验证码')
          } else {
            let flag=true;
            let regEmail = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/
            if (!regEmail.test(this.formRegister.email)) {
              this.$message({showClose: true, message: '请输入格式正确有效的邮箱号!', type: 'error'})
              flag=false;
            } else if(!this.formRegister.username){
              this.$message.error('请填写账号');
              this.refreshCode();
              flag=false;
            } else if(!this.formRegister.password){
              this.$message.error('请填写密码');
              this.refreshCode();
              flag=false;
            }else if(!this.formRegister.nickname){
              this.$message.error('请填写用户昵称');
              this.refreshCode();
              flag=false;
            }
    
            if(flag){
              //  这部分是发送邮箱验证码的玩意
              this.axios({
                url: "/user/user/emailcode",
                method: 'post',
                data:{
                  "email":this.formRegister.email,
                  "username":this.formRegister.username,
                  "password":this.formRegister.password,
                  "nickname":this.formRegister.nickname,
                }
              }).then((res)=>{
                res = res.data;
                if (res.code===10001){
                  alert("请将对应信息填写完整!")
                }else if(res.code===0){
                  alert("邮箱验证码发送成功,请及时查看,10分钟有效")
                }else {
                  this.$message.error(res.msg);
                }
    
              });
              //倒计时
              if (!this.timer) {
                this.show = false
                this.timer = setInterval(() => {
                  if (this.count > 0 && this.count <= this.TIME_COUNT) {
                    this.count--
                  } else {
                    this.show = true
                    clearInterval(this.timer)
                    this.timer = null
                  }
                }, 1000)
              }
    
    
            }
    
          }
        },
    
    • 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

    还有这个:

       submitForm(){
          let flag = true;
          if (this.formRegister.code.toLowerCase() !== this.identifyCode.toLowerCase()) {
            this.$message.error('请填写正确验证码');
            this.refreshCode();
            flag=false;
          }
          else if(!this.formRegister.emailCode){
            this.$message.error('请填写邮箱验证码');
            this.refreshCode();
            flag=false;
          }
          else if(!this.formRegister.email){
            this.$message.error('已填写邮箱请勿删除或修改邮箱,恶意操作将在120分钟内禁止注册!');
            this.refreshCode();
            flag=false;
          }
    
          if(flag){
            //这边后面做一个提交,提交对于消息
            this.axios({
              url: "/user/user/register",
              method: 'post',
              data:{
                "nickname": this.formRegister.nickname,
                "phone": this.formRegister.phone,
                "username": this.formRegister.username,
                "password": this.formRegister.password,
                "email":this.formRegister.email,
                "emailCode":this.formRegister.emailCode
              }
            }).then((res)=>{
              res = res.data;
              if (res.code===10001){
                alert("请将对应信息填写完整!")
              }else if(res.code===0){
                alert("注册成功")
                this.goLogin();
              }else {
                this.$message.error(res.msg);
              }
            });
          }
        },
    
    • 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

    后端

    之后是我们的注册,注册也是分为两个部分的。
    我们的流程如下:
    在这里插入图片描述

    邮箱服务

    那么这个时候咱们就需要使用到咱们的邮箱服务了。首先是导入相关依赖。

            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-mailartifactId>
            dependency>
    
    • 1
    • 2
    • 3
    • 4

    然后填写你的配置

    spring:
      #邮箱基本配置
      mail:
    #    配置在limit_time内,用户可以发送limit次验证码
        limit: 2 这个是我额外的配置,结合邮箱服务用的
        limitTime: 10 这个是我额外的配置
        #配置smtp服务主机地址
        # qq邮箱为smtp.qq.com          端口号465587
        # sina    smtp.sina.cn
        # aliyun  smtp.aliyun.com
        # 163     smtp.163.com       端口号465994
        host: smtp.qq.com
        #发送者邮箱
        username: xxxxx@foxmail.com
        #配置密码,注意不是真正的密码,而是刚刚申请到的授权码
        password: vmtwmkq6564651asd
        #端口号465587
        port: 587
        #默认的邮件编码为UTF-8
        default-encoding: UTF-8
        #其他参数
        properties:
          mail:
            #配置SSL 加密工厂
            smtp:
              ssl:
                #本地测试,先放开ssl
                enable: false
                required: false
              #开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误
            debug: true
            socketFactory:
              class: javax.net.ssl.SSLSocketFactory
    
    
    • 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

    之后就是咱们的服务了

    public class MaliServiceImpl implements MailService {
        /**
         * 注入邮件工具类
         */
        @Autowired
        private JavaMailSenderImpl javaMailSender;
    
        @Value("${spring.mail.username}")
        private String sendMailer;
    
        /**
         * 检测邮件信息类
         * @param to
         * @param subject
         * @param text
         */
        private void checkMail(String to,String subject,String text){
            if(StringUtils.isEmpty(to)){
                throw new RuntimeException("邮件收信人不能为空");
            }
            if(StringUtils.isEmpty(subject)){
                throw new RuntimeException("邮件主题不能为空");
            }
            if(StringUtils.isEmpty(text)){
                throw new RuntimeException("邮件内容不能为空");
            }
        }
    
        /**
         * 发送纯文本邮件
         * @param to
         * @param subject
         * @param text
         */
        @Override
        public void sendTextMailMessage(String to,String subject,String text){
    
            try {
                //true 代表支持复杂的类型
                MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
                //邮件发信人
                mimeMessageHelper.setFrom(sendMailer);
                //邮件收信人  1或多个
                mimeMessageHelper.setTo(to.split(","));
                //邮件主题
                mimeMessageHelper.setSubject(subject);
                //邮件内容
                mimeMessageHelper.setText(text);
                //邮件发送时间
                mimeMessageHelper.setSentDate(new Date());
    
                //发送邮件
                javaMailSender.send(mimeMessageHelper.getMimeMessage());
                System.out.println("发送邮件成功:"+sendMailer+"->"+to);
    
            } catch (MessagingException e) {
                e.printStackTrace();
                System.out.println("发送邮件失败:"+e.getMessage());
            }
        }
    
    
        /**
         * 发送html邮件
         * @param to
         * @param subject
         * @param content
         */
        @Override
        public void sendHtmlMailMessage(String to,String subject,String content){
    
            content="\n" +
                    "\n" +
                    "\n" +
                    "\n" +
                    "邮件\n" +
                    "\n" +
                    "\n" +
                    "\t

    这是一封HTML邮件!

    \n"
    + "\n" + ""; try { //true 代表支持复杂的类型 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true); //邮件发信人 mimeMessageHelper.setFrom(sendMailer); //邮件收信人 1或多个 mimeMessageHelper.setTo(to.split(",")); //邮件主题 mimeMessageHelper.setSubject(subject); //邮件内容 true 代表支持html mimeMessageHelper.setText(content,true); //邮件发送时间 mimeMessageHelper.setSentDate(new Date()); //发送邮件 javaMailSender.send(mimeMessageHelper.getMimeMessage()); System.out.println("发送邮件成功:"+sendMailer+"->"+to); } catch (MessagingException e) { e.printStackTrace(); System.out.println("发送邮件失败:"+e.getMessage()); } } /** * 发送带附件的邮件 * @param to 邮件收信人 * @param subject 邮件主题 * @param content 邮件内容 * @param filePath 附件路径 */ @Override public void sendAttachmentMailMessage(String to,String subject,String content,String filePath){ try { //true 代表支持复杂的类型 MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true); //邮件发信人 mimeMessageHelper.setFrom(sendMailer); //邮件收信人 1或多个 mimeMessageHelper.setTo(to.split(",")); //邮件主题 mimeMessageHelper.setSubject(subject); //邮件内容 true 代表支持html mimeMessageHelper.setText(content,true); //邮件发送时间 mimeMessageHelper.setSentDate(new Date()); //添加邮件附件 FileSystemResource file = new FileSystemResource(new File(filePath)); String fileName = file.getFilename(); mimeMessageHelper.addAttachment(fileName, file); //发送邮件 javaMailSender.send(mimeMessageHelper.getMimeMessage()); System.out.println("发送邮件成功:"+sendMailer+"->"+to); } catch (MessagingException e) { e.printStackTrace(); System.out.println("发送邮件失败:"+e.getMessage()); } } /** * 发送邮箱验证码 * @param to * @param code */ @Override public void sendCodeMailMessage(String to, String code) { String subject = "WhiteHole邮箱验证码"; String text = "验证码10分钟内有效:"+code; sendTextMailMessage(to,subject,text); } }
    • 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

    支持发送多种格式的邮箱,不过咱们的验证码只需要文本的就够了,但是保不齐后面还有别的。比如我们可以搞一个更加复杂的一点的邮箱链接验证,这个时候可能需要加点东西了。

    邮箱验证码

    那么这个时候就是咱们的邮箱验证码服务了。

    防刷

    同样的我们最怕的就是防刷,前端我们是有60s倒数计时的,我们的逻辑是这样的,前端60s后才能去再次点击发送邮箱,一个邮箱的验证码的有效期是10分钟,如果用户填写错了邮箱,那么60s倒计时后,可以在前端再次点击发送邮箱,但是在10分钟内我们只允许发送2次。前端的只是用来糊弄不太懂的用户的,后端是为了校验各种恶心的脚本的。啥都不怕就怕脚本乱搞。所以的话,咱们这边就是这样设计的。

    原理一样:
    判断一下

    if (redisUtils.hasKey(RedisTransKey.getEmailKey(username)))
    
    • 1

    成功后
    在这里插入图片描述

    这里多了点东西,主要是还需要计数。

    验证码

    这个我得说一下的就是,那个验证码的话,我是在前端做的,每次还是来骗骗不太“懂”的用户的。这里不是后端做,主要是因为,第一有了邮箱验证不需要再鉴别一次,我们在接口层就做了硬性指标只能访问多少次,不太再需要验证码防脚本了,之后也是降低服务端请求次数。

    完整代码
       public R emailCode(GetEmailCodeEntity entity) {
            String email = entity.getEmail();
            String username = entity.getUsername();
            //判断用户是不是恶意刷邮箱,在规定时间内进行的
            if (redisUtils.hasKey(RedisTransKey.getEmailKey(username))) {
                Object o = redisUtils.get(RedisTransKey.getEmailKey(username));
                EmailCodeEntity emailCodeEntity = JSON.parseObject(o.toString(), EmailCodeEntity.class);
                if (emailCodeEntity.getTimes() >= limit) {
                    return R.error(BizCodeEnum.OVER_REQUESTS.getCode(), BizCodeEnum.OVER_REQUESTS.getMsg());
                } else {
    //                这里就不去判断两次绑定的邮箱是不是一样的了,不排除第一次输入错了邮箱的情况
                    String emailCode = CodeUtils.creatCode(6);
                    emailCodeEntity.setEmailCode(emailCode);
                    emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1);
                    long overTime = redisUtils.getExpire(username, TimeUnit.MINUTES);
                    redisUtils.set(RedisTransKey.setEmailKey(username), emailCodeEntity,
                            overTime, TimeUnit.MINUTES
                    );
                    mailService.sendCodeMailMessage(email, emailCodeEntity.getEmailCode());
                }
            } else {
                UserEntity User = userService.getOne(
                        new QueryWrapper<UserEntity>().eq("username", username)
                );
                if (User != null) {
                    return R.error(BizCodeEnum.HAS_USERNAME.getCode(), BizCodeEnum.HAS_USERNAME.getMsg());
                } else {
                    String emailCode = CodeUtils.creatCode(6);
                    //            我们这里做一件事情,那就是最多允许用户在10分钟内发送2次的邮箱验证
                    //            60s倒计时后用户可以再发送验证码,但是间隔在10分钟内只能再发送1次
                    EmailCodeEntity emailCodeEntity = new EmailCodeEntity(
                            emailCode, username,email,1
                    );
                    redisUtils.set(RedisTransKey.setEmailKey(username), emailCodeEntity,
                            limitTime, TimeUnit.MINUTES
                    );
                    mailService.sendCodeMailMessage(email, emailCodeEntity.getEmailCode());
                }
            }
            return R.ok(BizCodeEnum.SUCCESSFUL.getMsg());
        }
    
    • 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

    注册

    这个也是类似的,我们拿到这个验证码后,去校验就好了。

    那么注册这里的话就不需要做防刷了,或者说已经做好了因为有邮箱验证码间接做好了。因为如果没有验证码,直接校验过不去,如果有验证码在Redis当中的对不到一样没法后序操作。其实这里的防刷都是消耗了服务器资源的,只是消耗了多少的问题,因为咱们这边都是拿Redis先顶住的。

        public R register(RegisterEntity entity) {
            String username = entity.getUsername();
            username = username.replaceAll(" ","");
            String emailCode = entity.getEmailCode();
    //        先检验一下验证码,对不对,邮箱有没有被更改
            if(redisUtils.hasKey(RedisTransKey.getEmailKey(username))){
                Object o = redisUtils.get(RedisTransKey.getEmailKey(username));
                EmailCodeEntity emailCodeEntity = JSON.parseObject(o.toString(), EmailCodeEntity.class);
                if(username.equals(emailCodeEntity.getUsername())){
                    if(emailCode.equals(emailCodeEntity.getEmailCode())){
                        //开始封装用户并进行存储
                        UserEntity userEntity = new UserEntity();
                        userEntity.setEmail(entity.getEmail());
                        userEntity.setNickname(entity.getNickname());
                        userEntity.setPassword(SecurityUtils.encodePassword(
                                entity.getPassword()).replaceAll(" ","")
                        );
    //                    用户状态,1-正常 2-警告 3-封禁
                        userEntity.setStatus(1);
                        userEntity.setCreatTime(DateUtils.getCurrentTime());
                        userEntity.setUsername(username);
                        userEntity.setPhone(entity.getPhone());
                        userService.save(userEntity);
                        redisUtils.del(RedisTransKey.getEmailKey(username));
                    }else {
                        return R.error(BizCodeEnum.BAD_EMAILCODE_VERIFY.getCode(),BizCodeEnum.BAD_EMAILCODE_VERIFY.getMsg());
                    }
                }else {
                    return R.error(BizCodeEnum.BAD_DOING.getCode(),BizCodeEnum.BAD_DOING.getMsg());
                }
            }else {
                return R.error(BizCodeEnum.OVER_TIME.getCode(),BizCodeEnum.OVER_TIME.getMsg());
            }
            return R.ok(BizCodeEnum.SUCCESSFUL.getMsg());
        }
    
    
    
    • 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

    总结

    到此的话,一个简单的用户登录注册就做好了,那么接下来的话就是咱们的Auto2.0了,这里咱们要实现的就是这两个功能
    在这里插入图片描述

  • 相关阅读:
    Python 升级之路( Lv12 ) Pygame游戏开发基础
    随机性检测模块支持GM/T 0005-2021标准的升级建议
    CSS 响应式设计:网格视图
    iOS消息转发流程
    拒绝水文!八大排序(三)【适合初学者】快速排序
    Webpack入门 | 青训营笔记
    Excel 快速分析
    实体对齐(二):BERT-INT
    java集合总结
    缓存的力量:提升API性能和可扩展性
  • 原文地址:https://blog.csdn.net/FUTEROX/article/details/127232757