diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java index f78360c6..07410748 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyService.java +++ b/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; @@ -54,6 +53,23 @@ public class NotifyService { smsSender.sendWithTemplate(phoneNumber, templateId, params); } + /** + * 以同步的方式发送短信模版消息通知 + * + * @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); + } + /** * 微信模版消息通知 * diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsResult.java new file mode 100644 index 00000000..a3545994 --- /dev/null +++ b/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; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java index bda9e69e..156ecd6d 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SmsSender.java +++ b/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); } \ No newline at end of file diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java index daa0f6a7..26685223 100644 --- a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/TencentSmsSender.java +++ b/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; } } diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/dao/CaptchaItem.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/dao/CaptchaItem.java new file mode 100644 index 00000000..fe84eacd --- /dev/null +++ b/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; + } +} \ No newline at end of file diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/CaptchaCodeManager.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/service/CaptchaCodeManager.java new file mode 100644 index 00000000..4008c684 --- /dev/null +++ b/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 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(); + } +} diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxAuthController.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxAuthController.java index 84a72398..82ca635a 100644 --- a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxAuthController.java +++ b/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 userList =userService.queryByUsername(username); + List 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 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 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); diff --git a/litemall-wx/pages/auth/register/register.js b/litemall-wx/pages/auth/register/register.js index cb0681ab..7c62af28 100644 --- a/litemall-wx/pages/auth/register/register.js +++ b/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 () { diff --git a/litemall-wx/pages/auth/reset/reset.js b/litemall-wx/pages/auth/reset/reset.js index 0c3a6904..775c3a41 100644 --- a/litemall-wx/pages/auth/reset/reset.js +++ b/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(){