Vue+Springboot 自制规划打卡系统【明日计划】(一)

目录

    • 简介
        • 预览
        • 初衷
        • 设计思路
    • 代码逻辑
        • 技术栈
        • 注册相关
            • 注册用户
            • 密码修改
            • 邮件激活
            • 随机昵称生成器
    • 尾声

简介

预览

自制的一套规划打卡系统,上效果图
Vue+Springboot 自制规划打卡系统【明日计划】(一)_第1张图片

初衷

首先讲讲我开发这套系统的初衷,想直接看代码的往下划
在日复一日的上班工作中,我发现生活之余所剩的时间少之又少,而且剩下的这些时间又被大量的闲杂琐事与自身的慵懒占据,下班躺床上玩手机、追剧、看小说,时间眨眼就过去,回首望去,自己的生活抛去工作,似乎没有什么有意义的事物存在。然而工作并不是生活的全部啊。空心病这个词逐渐在身边浮现,毫无在意的事,刻苦的努力一瞬间变得毫无意义。但我觉得生活不该是这样,一定存在着某种有趣的事物只是我们还没有发现,更何况,生而为人,来都来了,不去追求探索岂不可惜。在没有找到我的追求时,我觉得我需要让自己的身体保持一个良好的状态,于是我自己定制了一个健身计划,并不是健身房的那种增肌塑形,只是简简单单的夜跑,夜跑中,我发现类似于我的并不止一个。
很多人都拥有着他们自己的计划,当你迷茫不知方向时,看看其他人在做些什么,或者参与其中,坚持下去,时间久了,你会获得启发从而寻找到自己的追求的。
这就是我设计制作“明日计划”的初衷,它并不是一只闹钟,我希望它能够成为你深处黑夜时的一盏灯,哪怕只有星星点点的光芒。

设计思路

“明日计划”的重点在于打卡、协作以及自我审视,所以打卡页面需重点设计,我对此的初期构想为,该页面一定要具备操作感且需简洁明了,于是我参考探探app的卡片划动效果,来设计了这部分页面。用户查看自己的历史数据时,一般重点在于回顾自身计划的执行率(完成率),所以历史数据查询这部分需以图表来直观的反应出用户数据。用户注册模块需要最大程度上的简化,这会使用户更加快捷的加入其中。从之前制作网站来看,用户并不喜欢繁琐的账号注册,所以在“明日计划”中,只要用户填写好邮箱后,其他的一切资料,包括密码,均有系统后台自动生成。当然,密码是可以在用户登录后,通过邮箱验证的方式更改的。当然网站设计中还包含着其他方方面面的细节,这里只是简单的说几句,但一定要注意,设计的出发点是用户,而不是作为一个开发者怎么方便怎么来*。 比如:你眼中各种完善的条件查询,往往是用户觉得最最麻烦的地方。

代码逻辑

技术栈

这套系统由Vue+Springboot搭建而成,模式采取前后端分离,容器为tomcat
后端以若依系统为框架基础,前端则采用vant、element-ui两种UI框架
涉及到的技术栈有:

  • springboot框架
  • spring securit安全认证
  • mybatis持久层
  • redis缓存
  • hutool工具库
  • axios前后台数据交互
  • pagehelper分页
  • token票据验证
  • javax.mail邮件发送

注册相关

从账号注册开始:
我们能够想到的普通账户流程是这样的

  1. 填写注册信息
  2. 后台校验信息合法性
  3. 完成注册并自动登录

其中注册信息必填项包含账号和密码,有的还会有昵称、年龄、性别等等;
信息验证包括账号内容的合法性以及验证是否为本人操作注册;
比如手机号码为账号注册时会以短信验证、邮箱为账号注册时会以邮件链接验证;
我们这里使用邮箱验证(因为短信验证是需要RMB的)

考虑到用户便捷性,我将账号注册作以简化,用户仅需要提供邮箱信息即可。毕竟用户可不喜欢填写调查问卷。
后台接收到邮箱信息后,校验邮箱格式是否正确、数据库中该账号的唯一性,然后通过名称库自动拼接生成随机昵称,再从数据库的配置数据表中获取默认密码,整合好注册信息后,插入该数据完成账号的注册,最后向前台返回注册结果,前台根据注册结果执行下一步操作,若注册成功,将自动登录。这样,账号注册的流程就被极大的简化。
当然,这种方案下,由于默认密码是公开的,为保证账号安全性,需用户后续自主更改密码。
在首次更改密码流程中,必须通过邮箱邮件的验证,以校验是否为账户本人操作,后续密码更改则无需验证。

注册用户

controller::

 /**
  * 注册用户
  */
 @Log(title = "用户注册", businessType = BusinessType.INSERT)
 @PostMapping("/regist")
 public AjaxResult regist(@RequestBody SysUser user){
 	 //关于传参格式的校验我通常会放在controller中,service里主要是放业务逻辑
     if(StringUtils.isEmpty(user.getUserName().trim()) || !StringUtils.isEmail(user.getUserName()))
         return AjaxResult.error(2,"邮箱格式不正确");
     return capacityService.register(user.getUserName());
 }

service::

  /**
   * 注册用户
   */
  @Override
  public AjaxResult register(String email){
      //邮箱、账号查重
      SysUser info = userMapper.selectUserByUserName(email);
      if(StringUtils.isNotNull(info))
          return AjaxResult.error(3,"已存在该账号,无需重复注册");

      //设置账户名、邮箱值均为email
      SysUser sysUser = new SysUser();
      sysUser.setUserName(email);
      sysUser.setEmail(email);
      //生成随机昵称
      sysUser.setNickName(RandomLovelyNameUtils.generateName());
      //设置默认密码,直接从配置数据表中拿默认密码,这里是直接用id拿的
      SysConfig sysConfig = configService.selectConfigById((long) 2);
      //密码要加密,不要遗漏
      sysUser.setPassword(SecurityUtils.encryptPassword(sysConfig.getConfigValue()));      
      //设置账户状态为2-试用性状态,当首次更改密码后,状态会进行相应变更为正式用户的,这个设置很重要。
      sysUser.setStatus("2");
      // 插入用户信息
      int rows = userMapper.insertUser(sysUser);
      return rows>0?AjaxResult.success():AjaxResult.error();
  }

要注意注册时账号的状态 [试用性-2],后续的密码修改会根据这个状态来处理

密码修改
/**
 * 修改用户密码,试用性账号修改,需校验邮箱
 */
 @Override
 public AjaxResult editPwd(String oldPwd, String newPwd) throws Exception {
     //获取用户信息
     LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
     String userName = loginUser.getUser().getUserName();
     SysUser sysUser = userMapper.selectUserByUserName(userName);
     //校验oldPwd是否正确
     if(!SecurityUtils.matchesPassword(oldPwd,sysUser.getPassword()))
         return AjaxResult.error(-2,"原密码错误");
     //判断是否为试用性账号,如果为2,需邮件激活
     if(TEST_ACCOUNT.equals(sysUser.getStatus())){
         return postEmail(sysUser.getEmail(),newPwd);
     }else{
     	// 如果不是,那就进行常规的修改操作
         sysUser.setPassword(SecurityUtils.encryptPassword(newPwd));
         sysUser.setUpdateTime(new Date());
         int i = userMapper.updateUser(sysUser);
         return i>0?AjaxResult.success():AjaxResult.error();
     }
 }

首次修改密码时,向用户发送激活邮件,其中注意邮件内的链接

/**
 *  向邮箱中发送确认修改密码的链接,5分钟内有效
 */
public AjaxResult postEmail(String email,String pwd) throws Exception {
    String requestUrl = this.getEmailRequestURL(email,pwd);
    String subject = "缔曦平台密码修改校验";
    String content = "
请点击以下链接激活账号


  "+requestUrl+""; try { MailUtil.send(email, subject, content, true); }catch(MailException e){ e.printStackTrace(); System.out.println(e.getMessage()); return new AjaxResult(-3,"发送激活邮件失败"); } return new AjaxResult(1,"已向邮箱发送激活邮件,请于5分钟内完成激活"); }

链接中包含时间戳、账号、新密码、以及校验密匙。
其中肯定要对时间戳和密码加密,简单说一下这个加密的方法。
时间戳a、新密码加密生成b、时间戳拼接新密码形成的新字符串+自定义盐加密生成c,即三个参数 a、b、a+b+盐生成的c。
如果用户破解a与b并作以修改,传到后台时,后台会得到 修改后的a、修改后的b、和 c。此时我们再将a+b+自定义盐加密,生成的就不再是c,以此我们判断参数被修改过,不予继续操作。
其中密码加密用到了一个自定义盐SALT_1,密匙的生成用到了一个自定义盐SALT_2,上面的方法使SALT_1防守失效时还有一道SALT_2的保障,同时也能保证用户无法随意修改时间戳,更改激活链接的时效性。

/**
  * 获取邮箱指向的链接请求
  */
 private String getEmailRequestURL(String email,String pwd) throws Exception {
     //拼接获取邮箱中链接跳转的请求地址路径
     HttpServletRequest request = ServletUtils.getRequest();
     String servletPath = request.getScheme()+"://"+ request.getServerName() + ":" + request.getServerPort() + request.getContextPath();

     SysConfig sysConfig = configService.selectConfigById((long) 4);
     String configValue = sysConfig.getConfigValue();
     String requestUrl = servletPath + configValue;
     //获取时间戳
     long time = new Date().getTime();
     //拼接参数
     String secret = "";
     try {
         secret = HMACSHA256.generate(time + pwd, SALT_1);
     } catch (Exception e) {
         throw new Exception("HMACSHA256加密算法错误");
     }
     //pwd先加密,再utf-8编码
     String codePwd = AESUtils.AES_Encrypt(pwd, SALT_2, AESEncryptMode);
     String param = "?email="+email+"&pwd="+URLEncoder.encode(codePwd, "UTF-8")+"&time="+time+"&secret="+secret;
     String wholeUrl = requestUrl + param;
     return wholeUrl;
 }
邮件激活

用户点击邮件激活链接时,执行账号的升级及密码的首次修改。

/**
  * 通过邮件激活用户,更改status  2试用状态->0正常状态
  */
 @Override
 public AjaxResult accountLevelUp(String email, String pwd, long time, String secret) throws Exception {
     //5分钟校验
     long _time = new Date().getTime();
     if(_time-time > 1000*60*5)
         return AjaxResult.error(-1,"链接已超时");
     //pwd 不需要再手动utf-8解码了 , 直接解密就好
     String _pwd = AESUtils.AES_Decrypt(pwd, SALT_2, AESEncryptMode);
     String _secret = "";
     try {
         _secret = HMACSHA256.generate(time + _pwd, SALT_1);
     } catch (Exception e) {
         throw new Exception("HMACSHA256解密算法错误");
     }
     if(!_secret.equals(secret))
         return AjaxResult.error(-2,"加密链接不匹配");
     //校验账号是否停封或删除
     SysUser sysUser = userMapper.selectUserByUserName(email);
     if(UserStatus.DELETED.getCode().equals(sysUser.getDelFlag()))
         return AjaxResult.error(-3,"该账号已删除");
     if(UserStatus.DISABLE.getCode().equals(sysUser.getStatus()))
         return AjaxResult.error(-4,"该账号已停封");
     //执行levelup  status由2->0
     sysUser.setStatus("0");
     int i = userMapper.updateUser(sysUser);
     //判断是否修改成功
     if(i<=0)
         return AjaxResult.error(-5,"修改数据状态失败");
     //执行密码修改
     sysUser.setPassword(SecurityUtils.encryptPassword(_pwd));
     sysUser.setUpdateTime(new Date());
     int _i = userMapper.updateUser(sysUser);
     return _i>0?AjaxResult.success():AjaxResult.error("修改数据密码失败");
 }
随机昵称生成器
/**
  * 生成随机昵称
  *
  * @return
  */
 public static String generateName() {
     int adjLen= adjective.length;
     int nLen= noun.length;
     StringBuffer sb = new StringBuffer();
     Random random = new Random();
     sb.append(adjective[random.nextInt(adjLen)]);
     sb.append(noun[random.nextInt(nLen)]);
     return sb.toString();
 }

 private static String adjective[] = {
         "一样的", "喜欢的", "美丽的", "一定的", "原来的", "美好的", "开心的", "可能的", "可爱的",
         "明白的", "所有的", "后来的", "重要的", "经常的", "自然的", "真正的", "害怕的", "空中的",
         "红色的", "痛苦的", "干净的", "辛苦的", "精彩的", "欢乐的", "进步的", "影响的", "黄色的",
         "亲爱的", "根本的", "完美的", "金黄的", "聪明的", "清新的", "迷人的", "光明的", "共同的",
         "直接的", "真实的", "听说的", "用心的", "飞快的", "雪白的", "着急的", "乐观的", "主要的",
         "鲜艳的", "冰冷的", "细心的", "奇妙的", "水平的", "动人的", "大量的", "无知的", "礼貌的",
         "暖和的", "深情的", "正常的", "平淡的", "光亮的", "落后的", "大方的", "老大的", "刻苦的",
         "晴朗的", "专业的", "永久的", "大气的", "知己的", "刚好的", "相对的", "平和的", "友好的",
         "广大的", "秀丽的", "日常的", "高级的", "相同的", "笔直的", "安定的", "知足的", "结实的",
         "许久的", "听话的", "知名的", "闷热的", "众多的", "拥挤的", "天生的", "迷你的", "老实的",
         "友爱的", "原始的", "可笑的", "合格的", "公共的", "大红的", "得力的", "洁净的", "暗淡的",
         "鲜红的", "桃红的", "吓人的", "多余的", "秀美的", "繁忙的", "冰凉的", "热心的", "空旷的",
         "冷清的", "公开的", "冷淡的", "齐全的", "草绿的", "能干的", "发火的", "可心的", "业余的",
         "空心的", "凉快的", "长远的", "土黄的", "和好的", "合法的", "明净的", "过时的", "低下的",
         "不快的", "低级的", "中用的", "不定的", "公办的", "用功的", "少许的", "忙乱的", "日用的",
         "要紧的", "少见的", "非分的", "怕人的", "大忙的", "幸福的", "特别的", "未来的", "伟大的",
         "困难的", "伤心的", "实在的", "现实的", "丰富的", "同样的", "巨大的", "耐心的", "优秀的",
         "亲切的", "讨厌的", "严厉的", "积极的", "整齐的", "环保的"};

 private static String[] noun = {
         "子璇", "淼", "国栋", "夫子", "瑞堂", "甜", "敏", "尚", "国贤", "贺祥", "晨涛",
         "昊轩", "易轩", "益辰", "益帆", "益冉", "瑾春", "瑾昆", "春齐", "杨", "文昊",
         "东东", "雄霖", "浩晨", "熙涵", "溶溶", "冰枫", "欣欣", "宜豪", "欣慧", "建政",
         "美欣", "淑慧", "文轩", "文杰", "欣源", "忠林", "榕润", "欣汝", "慧嘉", "新建",
         "建林", "亦菲", "林", "冰洁", "佳欣", "涵涵", "禹辰", "淳美", "泽惠", "伟洋",
         "涵越", "润丽", "翔", "淑华", "晶莹", "凌晶", "苒溪", "雨涵", "嘉怡", "佳毅",
         "子辰", "佳琪", "紫轩", "瑞辰", "昕蕊", "萌", "明远", "欣宜", "泽远", "欣怡",
         "佳怡", "佳惠", "晨茜", "晨璐", "运昊", "汝鑫", "淑君", "晶滢", "润莎", "榕汕",
         "佳钰", "佳玉", "晓庆", "一鸣", "语晨", "添池", "添昊", "雨泽", "雅晗", "雅涵",
         "清妍", "诗悦", "嘉乐", "晨涵", "天赫", "玥傲", "佳昊", "天昊", "萌萌", "若萌"
 };

以上就是账号注册及首次修改密码时账号激活相关的代码
后续我会逐步分享“明日计划”中的一些技术应用以及代码细节。

尾声

作者不太会讲话,期待各位读者给个赞,同时欢迎加入计划者,微信扫一扫以下二维码,叨扰了。
Vue+Springboot 自制规划打卡系统【明日计划】(一)_第2张图片

你可能感兴趣的:(明日计划,java,vue,spring,boot)