Browse Source

添加注册短信验证和重置密码短信验证

Menethil 7 years ago
parent
commit
bd22847aab

+ 17 - 1
litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java

@@ -1,6 +1,5 @@
 package org.linlinjava.litemall.core.notify;
 
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.mail.MailSender;
 import org.springframework.mail.SimpleMailMessage;
 import org.springframework.scheduling.annotation.Async;
@@ -55,6 +54,23 @@ public class NotifyService {
     }
 
     /**
+     * 以同步的方式发送短信模版消息通知
+     *
+     * @param phoneNumber 接收通知的电话号码
+     * @param notifyType  通知类别,通过该枚举值在配置文件中获取相应的模版ID
+     * @param params      通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值
+     * @return
+     */
+    public SmsResult notifySmsTemplateSync(String phoneNumber, NotifyType notifyType, String[] params) {
+        if (smsSender == null)
+            return null;
+
+        int templateId = Integer.parseInt(getTemplateId(notifyType, smsTemplate));
+
+        return smsSender.sendWithTemplate(phoneNumber, templateId, params);
+    }
+
+    /**
      * 微信模版消息通知
      *
      * @param token      通过wxMAService获取token或者通过url请求token

+ 30 - 0
litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java

@@ -0,0 +1,30 @@
+package org.linlinjava.litemall.core.notify;
+
+/**
+ * 发送短信的返回结果
+ */
+public class SmsResult {
+    private boolean successful;
+    private Object result;
+
+    /**
+     * 短信是否发送成功
+     *
+     * @return
+     */
+    public boolean isSuccessful() {
+        return successful;
+    }
+
+    public void setSuccessful(boolean successful) {
+        this.successful = successful;
+    }
+
+    public Object getResult() {
+        return result;
+    }
+
+    public void setResult(Object result) {
+        this.result = result;
+    }
+}

+ 2 - 2
litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java

@@ -8,7 +8,7 @@ public interface SmsSender {
      * @param phone   接收通知的电话号码
      * @param content 短消息内容
      */
-    void send(String phone, String content);
+    SmsResult send(String phone, String content);
 
 
     /**
@@ -18,5 +18,5 @@ public interface SmsSender {
      * @param templateId 通知模板ID
      * @param params     通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值
      */
-    void sendWithTemplate(String phone, int templateId, String[] params);
+    SmsResult sendWithTemplate(String phone, int templateId, String[] params);
 }

+ 19 - 2
litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java

@@ -5,11 +5,14 @@ import com.github.qcloudsms.SmsSingleSenderResult;
 import com.github.qcloudsms.httpclient.HTTPException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.springframework.stereotype.Service;
+
 import java.io.IOException;
 
 /*
  * 腾讯云短信服务
  */
+@Service
 public class TencentSmsSender implements SmsSender {
     private final Log logger = LogFactory.getLog(TencentSmsSender.class);
 
@@ -24,22 +27,36 @@ public class TencentSmsSender implements SmsSender {
     }
 
     @Override
-    public void send(String phone, String content) {
+    public SmsResult send(String phone, String content) {
         try {
             SmsSingleSenderResult result = sender.send(0, "86", phone, content, "", "");
             logger.debug(result);
+
+            SmsResult smsResult = new SmsResult();
+            smsResult.setSuccessful(true);
+            smsResult.setResult(result);
+            return smsResult;
         } catch (HTTPException | IOException e) {
             e.printStackTrace();
         }
+
+        return null;
     }
 
     @Override
-    public void sendWithTemplate(String phone, int templateId, String[] params) {
+    public SmsResult sendWithTemplate(String phone, int templateId, String[] params) {
         try {
             SmsSingleSenderResult result = sender.sendWithParam("86", phone, templateId, params, "", "", "");
             logger.debug(result);
+
+            SmsResult smsResult = new SmsResult();
+            smsResult.setSuccessful(true);
+            smsResult.setResult(result);
+            return smsResult;
         } catch (HTTPException | IOException e) {
             e.printStackTrace();
         }
+
+        return null;
     }
 }

+ 33 - 0
litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/dao/CaptchaItem.java

@@ -0,0 +1,33 @@
+package org.linlinjava.litemall.wx.dao;
+
+import java.time.LocalDateTime;
+
+public class CaptchaItem {
+    private String phoneNumber;
+    private String code;
+    private LocalDateTime expireTime;
+
+    public String getPhoneNumber() {
+        return phoneNumber;
+    }
+
+    public void setPhoneNumber(String phoneNumber) {
+        this.phoneNumber = phoneNumber;
+    }
+
+    public String getCode() {
+        return code;
+    }
+
+    public void setCode(String code) {
+        this.code = code;
+    }
+
+    public LocalDateTime getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(LocalDateTime expireTime) {
+        this.expireTime = expireTime;
+    }
+}

+ 63 - 0
litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/CaptchaCodeManager.java

@@ -0,0 +1,63 @@
+package org.linlinjava.litemall.wx.service;
+
+import org.linlinjava.litemall.wx.dao.CaptchaItem;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 缓存系统中的验证码
+ */
+public class CaptchaCodeManager {
+    private static Map<String, CaptchaItem> captchaCodeCache = new HashMap<>();
+
+    /**
+     * 添加到缓存
+     *
+     * @param phoneNumber 电话号码
+     * @param code        验证码
+     */
+    public static boolean addToCache(String phoneNumber, String code) {
+
+
+        //已经发过验证码且验证码还未过期
+        if (captchaCodeCache.get(phoneNumber) != null) {
+            if (captchaCodeCache.get(phoneNumber).getExpireTime().isAfter(LocalDateTime.now())) {
+                return false;
+            } else {
+                //存在但是已过期,删掉
+                captchaCodeCache.remove(phoneNumber);
+            }
+        }
+
+        CaptchaItem captchaItem = new CaptchaItem();
+        captchaItem.setPhoneNumber(phoneNumber);
+        captchaItem.setCode(code);
+        // 有效期为1分钟
+        captchaItem.setExpireTime(LocalDateTime.now().plusMinutes(1));
+
+        captchaCodeCache.put(phoneNumber, captchaItem);
+
+        return true;
+    }
+
+    /**
+     * 获取缓存的验证码
+     *
+     * @param phoneNumber 关联的电话号码
+     * @return 验证码
+     */
+    public static String getCachedCaptcha(String phoneNumber) {
+        //没有这个电话记录
+        if (captchaCodeCache.get(phoneNumber) == null)
+            return null;
+
+        //有电话记录但是已经过期
+        if (captchaCodeCache.get(phoneNumber).getExpireTime().isBefore(LocalDateTime.now())) {
+            return null;
+        }
+
+        return captchaCodeCache.get(phoneNumber).getCode();
+    }
+}

+ 119 - 87
litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxAuthController.java

@@ -5,17 +5,22 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.linlinjava.litemall.core.notify.NotifyService;
+import org.linlinjava.litemall.core.notify.NotifyType;
+import org.linlinjava.litemall.core.notify.SmsResult;
+import org.linlinjava.litemall.core.util.CharUtil;
+import org.linlinjava.litemall.core.util.JacksonUtil;
 import org.linlinjava.litemall.core.util.RegexUtil;
+import org.linlinjava.litemall.core.util.ResponseUtil;
+import org.linlinjava.litemall.core.util.bcrypt.BCryptPasswordEncoder;
 import org.linlinjava.litemall.db.domain.LitemallUser;
 import org.linlinjava.litemall.db.service.LitemallUserService;
-import org.linlinjava.litemall.core.util.JacksonUtil;
-import org.linlinjava.litemall.core.util.ResponseUtil;
-import org.linlinjava.litemall.wx.dao.WxLoginInfo;
 import org.linlinjava.litemall.wx.dao.UserInfo;
 import org.linlinjava.litemall.wx.dao.UserToken;
+import org.linlinjava.litemall.wx.dao.WxLoginInfo;
+import org.linlinjava.litemall.wx.service.CaptchaCodeManager;
 import org.linlinjava.litemall.wx.service.UserTokenManager;
 import org.linlinjava.litemall.wx.util.IpUtil;
-import org.linlinjava.litemall.core.util.bcrypt.BCryptPasswordEncoder;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -39,47 +44,48 @@ public class WxAuthController {
     @Autowired
     private WxMaService wxService;
 
+    @Autowired
+    private NotifyService notifyService;
+
     /**
      * 账号登录
      *
-     * @param body 请求内容,{ username: xxx, password: xxx }
+     * @param body    请求内容,{ username: xxx, password: xxx }
      * @param request 请求对象
      * @return 登录结果
-     *   成功则
-     *  {
-     *      errno: 0,
-     *      errmsg: '成功',
-     *      data:
-     *          {
-     *              token: xxx,
-     *              tokenExpire: xxx,
-     *              userInfo: xxx
-     *          }
-     *  }
-     *   失败则 { errno: XXX, errmsg: XXX }
+     * 成功则
+     * {
+     * errno: 0,
+     * errmsg: '成功',
+     * data:
+     * {
+     * token: xxx,
+     * tokenExpire: xxx,
+     * userInfo: xxx
+     * }
+     * }
+     * 失败则 { errno: XXX, errmsg: XXX }
      */
     @RequestMapping("login")
     public Object login(@RequestBody String body, HttpServletRequest request) {
         String username = JacksonUtil.parseString(body, "username");
         String password = JacksonUtil.parseString(body, "password");
-        if(username == null || password == null){
+        if (username == null || password == null) {
             return ResponseUtil.badArgument();
         }
 
-        List<LitemallUser> userList =userService.queryByUsername(username);
+        List<LitemallUser> userList = userService.queryByUsername(username);
         LitemallUser user = null;
-        if(userList.size() > 1){
+        if (userList.size() > 1) {
             return ResponseUtil.serious();
-        }
-        else if(userList.size() == 0){
+        } else if (userList.size() == 0) {
             return ResponseUtil.badArgumentValue();
-        }
-        else {
+        } else {
             user = userList.get(0);
         }
 
         BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
-        if(!encoder.matches(password, user.getPassword())){
+        if (!encoder.matches(password, user.getPassword())) {
             return ResponseUtil.fail(403, "账号密码不对");
         }
 
@@ -102,26 +108,26 @@ public class WxAuthController {
      * 微信登录
      *
      * @param wxLoginInfo 请求内容,{ code: xxx, userInfo: xxx }
-     * @param request 请求对象
+     * @param request     请求对象
      * @return 登录结果
-     *   成功则
-     *  {
-     *      errno: 0,
-     *      errmsg: '成功',
-     *      data:
-     *          {
-     *              token: xxx,
-     *              tokenExpire: xxx,
-     *              userInfo: xxx
-     *          }
-     *  }
-     *   失败则 { errno: XXX, errmsg: XXX }
+     * 成功则
+     * {
+     * errno: 0,
+     * errmsg: '成功',
+     * data:
+     * {
+     * token: xxx,
+     * tokenExpire: xxx,
+     * userInfo: xxx
+     * }
+     * }
+     * 失败则 { errno: XXX, errmsg: XXX }
      */
     @RequestMapping("login_by_weixin")
     public Object loginByWeixin(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) {
         String code = wxLoginInfo.getCode();
         UserInfo userInfo = wxLoginInfo.getUserInfo();
-        if(code == null || userInfo == null){
+        if (code == null || userInfo == null) {
             return ResponseUtil.badArgument();
         }
 
@@ -135,12 +141,12 @@ public class WxAuthController {
             e.printStackTrace();
         }
 
-        if(sessionKey == null || openId == null){
+        if (sessionKey == null || openId == null) {
             return ResponseUtil.fail();
         }
 
         LitemallUser user = userService.queryByOid(openId);
-        if(user == null){
+        if (user == null) {
             user = new LitemallUser();
             user.setUsername(userInfo.getNickName());  // 其实没有用,因为用户没有真正注册
             user.setPassword(openId);                  // 其实没有用,因为用户没有真正注册
@@ -148,15 +154,14 @@ public class WxAuthController {
             user.setAvatar(userInfo.getAvatarUrl());
             user.setNickname(userInfo.getNickName());
             user.setGender(userInfo.getGender());
-            user.setUserLevel((byte)0);
-            user.setStatus((byte)0);
+            user.setUserLevel((byte) 0);
+            user.setStatus((byte) 0);
             user.setLastLoginTime(LocalDateTime.now());
             user.setLastLoginIp(IpUtil.client(request));
             user.setAddTime(LocalDateTime.now());
 
             userService.add(user);
-        }
-        else{
+        } else {
             user.setLastLoginTime(LocalDateTime.now());
             user.setLastLoginIp(IpUtil.client(request));
             userService.update(user);
@@ -172,31 +177,49 @@ public class WxAuthController {
         return ResponseUtil.ok(result);
     }
 
+
+    /**
+     * 请求验证码
+     *
+     * @param body 手机号码{mobile}
+     * @return
+     */
+    @PostMapping("regCaptcha")
+    public Object registerCaptcha(@RequestBody String body) {
+        String phoneNumber = JacksonUtil.parseString(body, "mobile");
+        String code = CharUtil.getRandomNum(6);
+
+        notifyService.notifySmsTemplateSync(phoneNumber, NotifyType.CAPTCHA, new String[]{code});
+
+        boolean successful = CaptchaCodeManager.addToCache(phoneNumber, code);
+        return successful ? ResponseUtil.ok() : ResponseUtil.badArgument();
+    }
+
     /**
      * 账号注册
      *
-     * @param body 请求内容
-     *  {
-     *      username: xxx,
-     *      password: xxx,
-     *      mobile: xxx
-     *      code: xxx
-     *  }
-     *  其中code是手机验证码,目前还不支持手机短信验证码
+     * @param body    请求内容
+     *                {
+     *                username: xxx,
+     *                password: xxx,
+     *                mobile: xxx
+     *                code: xxx
+     *                }
+     *                其中code是手机验证码,目前还不支持手机短信验证码
      * @param request 请求对象
      * @return 登录结果
-     *   成功则
-     *  {
-     *      errno: 0,
-     *      errmsg: '成功',
-     *      data:
-     *          {
-     *              token: xxx,
-     *              tokenExpire: xxx,
-     *              userInfo: xxx
-     *          }
-     *  }
-     *   失败则 { errno: XXX, errmsg: XXX }
+     * 成功则
+     * {
+     * errno: 0,
+     * errmsg: '成功',
+     * data:
+     * {
+     * token: xxx,
+     * tokenExpire: xxx,
+     * userInfo: xxx
+     * }
+     * }
+     * 失败则 { errno: XXX, errmsg: XXX }
      */
     @PostMapping("register")
     public Object register(@RequestBody String body, HttpServletRequest request) {
@@ -205,22 +228,27 @@ public class WxAuthController {
         String mobile = JacksonUtil.parseString(body, "mobile");
         String code = JacksonUtil.parseString(body, "code");
 
-        if(username == null || password == null || mobile == null || code == null){
+        if (username == null || password == null || mobile == null || code == null) {
             return ResponseUtil.badArgument();
         }
 
         List<LitemallUser> userList = userService.queryByUsername(username);
-        if(userList.size() > 0){
+        if (userList.size() > 0) {
             return ResponseUtil.fail(403, "用户名已注册");
         }
 
         userList = userService.queryByMobile(mobile);
-        if(userList.size() > 0){
+        if (userList.size() > 0) {
             return ResponseUtil.fail(403, "手机号已注册");
         }
-        if(!RegexUtil.isMobileExact(mobile)){
+        if (!RegexUtil.isMobileExact(mobile)) {
             return ResponseUtil.fail(403, "手机号格式不正确");
         }
+        //判断验证码是否正确
+        String cacheCode = CaptchaCodeManager.getCachedCaptcha(mobile);
+        if (cacheCode == null || cacheCode.isEmpty() || !cacheCode.equals(code))
+            return ResponseUtil.fail(403, "验证码错误");
+
         LitemallUser user = new LitemallUser();
 
         BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
@@ -234,9 +262,9 @@ public class WxAuthController {
         user.setWeixinOpenid("");
         user.setAvatar("https://yanxuan.nosdn.127.net/80841d741d7fa3073e0ae27bf487339f.jpg?imageView&quality=90&thumbnail=64x64");
         user.setNickname(username);
-        user.setGender((byte)0);
-        user.setUserLevel((byte)0);
-        user.setStatus((byte)0);
+        user.setGender((byte) 0);
+        user.setUserLevel((byte) 0);
+        user.setStatus((byte) 0);
         user.setLastLoginTime(LocalDateTime.now());
         user.setLastLoginIp(IpUtil.client(request));
         user.setAddTime(LocalDateTime.now());
@@ -261,17 +289,17 @@ public class WxAuthController {
     /**
      * 账号密码重置
      *
-     * @param body 请求内容
-     *  {
-     *      password: xxx,
-     *      mobile: xxx
-     *      code: xxx
-     *  }
-     *  其中code是手机验证码,目前还不支持手机短信验证码
+     * @param body    请求内容
+     *                {
+     *                password: xxx,
+     *                mobile: xxx
+     *                code: xxx
+     *                }
+     *                其中code是手机验证码,目前还不支持手机短信验证码
      * @param request 请求对象
      * @return 登录结果
-     *   成功则 { errno: 0, errmsg: '成功' }
-     *   失败则 { errno: XXX, errmsg: XXX }
+     * 成功则 { errno: 0, errmsg: '成功' }
+     * 失败则 { errno: XXX, errmsg: XXX }
      */
     @PostMapping("reset")
     public Object reset(@RequestBody String body, HttpServletRequest request) {
@@ -279,22 +307,26 @@ public class WxAuthController {
         String mobile = JacksonUtil.parseString(body, "mobile");
         String code = JacksonUtil.parseString(body, "code");
 
-        if(mobile == null || code == null || password == null){
+        if (mobile == null || code == null || password == null) {
             return ResponseUtil.badArgument();
         }
 
+        //判断验证码是否正确
+        String cacheCode = CaptchaCodeManager.getCachedCaptcha(mobile);
+        if (cacheCode == null || cacheCode.isEmpty() || !cacheCode.equals(code))
+            return ResponseUtil.fail(403, "验证码错误");
+
         List<LitemallUser> userList = userService.queryByMobile(mobile);
         LitemallUser user = null;
-        if(userList.size() > 1){
+        if (userList.size() > 1) {
             return ResponseUtil.serious();
-        }
-        else if(userList.size() == 0){
+        } else if (userList.size() == 0) {
             return ResponseUtil.fail(403, "手机号未注册");
-        }
-        else{
+        } else {
             user = userList.get(0);
         }
 
+        // TODO 重新生成的密码无法登陆
         BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
         String encodedPassword = encoder.encode(password);
         user.setPassword(encodedPassword);

+ 26 - 4
litemall-wx/pages/auth/register/register.js

@@ -31,10 +31,32 @@ Page({
 
   },
   sendCode: function () {
-    wx.showModal({
-      title: '注意',
-      content: '由于目前不支持手机短信发送,因此验证码任意值都可以',
-      showCancel: false
+    let that = this;
+    wx.request({
+      url: api.AuthRegisterCaptcha,
+      data: {
+        mobile: that.data.mobile
+      },
+      method: 'POST',
+      header: {
+        'content-type': 'application/json'
+      },
+      success: function (res) {
+        if (res.data.errno == 0) {
+          wx.showModal({
+            title: '发送成功',
+            content: '验证码已发送',
+            showCancel: false
+          });
+        }
+        else {
+          wx.showModal({
+            title: '错误信息',
+            content: res.data.errmsg,
+            showCancel: false
+          });
+        }
+      }
     });
   },
   startRegister: function () {

+ 27 - 4
litemall-wx/pages/auth/reset/reset.js

@@ -29,11 +29,34 @@ Page({
     // 页面关闭
 
   },
+
   sendCode: function () {
-    wx.showModal({
-      title: '注意',
-      content: '由于目前不支持手机短信发送,因此验证码任意值都可以',
-      showCancel: false
+    let that = this;
+    wx.request({
+      url: api.AuthRegisterCaptcha,
+      data: {
+        mobile: that.data.mobile
+      },
+      method: 'POST',
+      header: {
+        'content-type': 'application/json'
+      },
+      success: function (res) {
+        if (res.data.errno == 0) {
+          wx.showModal({
+            title: '发送成功',
+            content: '验证码已发送',
+            showCancel: false
+          });
+        }
+        else {
+          wx.showModal({
+            title: '错误信息',
+            content: res.data.errmsg,
+            showCancel: false
+          });
+        }
+      }
     });
   },
   startReset: function(){