diff --git a/litemall-core/pom.xml b/litemall-core/pom.xml index aadfb5dcc..9855ec359 100644 --- a/litemall-core/pom.xml +++ b/litemall-core/pom.xml @@ -42,9 +42,13 @@ javax.mail + + com.github.qcloudsms + qcloudsms + 1.0.5 + - diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/ExecutorConfig.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/ExecutorConfig.java new file mode 100644 index 000000000..421a6b1ca --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/ExecutorConfig.java @@ -0,0 +1,40 @@ +package org.linlinjava.litemall.core.notify; + + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 异步线程池,用于异步发送通知 + */ +@Configuration +@EnableScheduling +@EnableAsync +class ExecutorConfig { + + @Value("${spring.notify.corePoolSize}") + private int corePoolSize; + @Value("${spring.notify.maxPoolSize}") + private int maxPoolSize; + @Value("${spring.notify.queueCapacity}") + private int queueCapacity; + + @Bean(name = "nofityAsync") + public Executor nofityAsync() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(corePoolSize); + executor.setMaxPoolSize(maxPoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + executor.setThreadNamePrefix("NotifyExecutor-"); + executor.initialize(); + return executor; + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/LitemallNotifyService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/LitemallNotifyService.java new file mode 100644 index 000000000..52de1cef4 --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/LitemallNotifyService.java @@ -0,0 +1,69 @@ +package org.linlinjava.litemall.core.notify; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Service; + +/** + * Litemall商城通知服务类 + */ +@PropertySource(value = "classpath:notify.properties") +@Service("litemallNotifyService") +public class LitemallNotifyService { + @Autowired + MailSendService mailSendService; + @Autowired + SMSSendService smsSendService; + @Autowired + Environment environment; + + @Value("${sprint.mail.enable}") + private boolean sendMailEnable; + @Value("${spring.sms.enable}") + private boolean sendSMSEnable; + + public void notifySMSMessage(String phoneNumber,String message) { + if (!sendSMSEnable) + return; + + smsSendService.sendSMS(phoneNumber, message); + } + + /** + * 短信模版通知 + * @param phoneNumber 接收通知的电话号码 + * @param params 通知模版内容里的参数,类似"您的验证码为{1}"中{1}的值 + * @param notifyType 通知类别,通过该枚举值在配置文件中获取相应的模版ID + */ + public void notifySMSTemplate(String phoneNumber, String[] params, NotifyUtils.NotifyType notifyType) { + if (!sendSMSEnable) + return; + + int templateId = -1; + switch (notifyType) { + case PAY_COMPLATED: + templateId = Integer.parseInt(environment.getProperty("spring.sms.template.pay.complated")); + break; + case VERIFICATIONCODE: + templateId = Integer.parseInt(environment.getProperty("spring.sms.template.verificationcode")); + break; + } + + if (templateId != -1) + smsSendService.sendSMSWithTemplate(phoneNumber, templateId, params); + } + + /** + * 发送邮件通知,接收者在spring.mail.sendto中指定 + * @param setSubject 邮件标题 + * @param setText 邮件内容 + */ + public void notifyMailMessage(String setSubject, String setText) { + if(!sendMailEnable) + return; + + mailSendService.sendEmail(setSubject, setText); + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/MailSendService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/MailSendService.java new file mode 100644 index 000000000..3c8edac7f --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/MailSendService.java @@ -0,0 +1,46 @@ +package org.linlinjava.litemall.core.notify; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import javax.annotation.Resource; +import javax.mail.internet.MimeMessage; + +@PropertySource(value = "classpath:notify.properties") +@Service("mailSendService") +class MailSendService { + @Resource + private JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String from; + + @Value("${spring.mail.sendto}") + private String sendto; + + /** + * 异步发送邮件通知 + * @param setSubject 邮件标题 + * @param setText 邮件内容 + */ + @Async("nofityAsync") + public void sendEmail(String setSubject, String setText) { + try { + final MimeMessage mimeMessage = mailSender.createMimeMessage(); + final MimeMessageHelper message = new MimeMessageHelper(mimeMessage); + + message.setFrom(from); + message.setTo(sendto); + message.setSubject(setSubject); + message.setText(setText); + mailSender.send(mimeMessage); + + } catch (Exception ex) { + + } + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyUtils.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyUtils.java new file mode 100644 index 000000000..571d28e93 --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/NotifyUtils.java @@ -0,0 +1,10 @@ +package org.linlinjava.litemall.core.notify; + +public class NotifyUtils { + /** + * 该枚举定义了所有的需要通知的事件,调用通知时作为参数 + */ + public enum NotifyType { + PAY_COMPLATED, REGISTER, VERIFICATIONCODE, + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SMSSendService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SMSSendService.java new file mode 100644 index 000000000..95f79803d --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/SMSSendService.java @@ -0,0 +1,64 @@ +package org.linlinjava.litemall.core.notify; + +import com.github.qcloudsms.SmsSingleSender; +import com.github.qcloudsms.SmsSingleSenderResult; +import com.github.qcloudsms.httpclient.HTTPException; +import org.json.JSONException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.PropertySource; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@PropertySource(value = "classpath:notify.properties") +@Service("smsSendService") +class SMSSendService { + @Value("${spring.sms.appid}") + private int appid; + + @Value("${spring.sms.appkey}") + private String appkey; + + @Value("${spring.sms.sign}") + private String smsSign; + + @Async("nofityAsync") + public void sendSMS(String phoneNumber, String content) { + try { + SmsSingleSender ssender = new SmsSingleSender(appid, appkey); + SmsSingleSenderResult result = ssender.send(0, "86", phoneNumber, + content, "", ""); + + System.out.println(result); + } catch (HTTPException e) { + // HTTP响应码错误 + e.printStackTrace(); + } catch (JSONException e) { + // json解析错误 + e.printStackTrace(); + } catch (IOException e) { + // 网络IO错误 + e.printStackTrace(); + } + } + + @Async("nofityAsync") + public void sendSMSWithTemplate(String phoneNumber, int templateId, String[] params) { + try { + SmsSingleSender ssender = new SmsSingleSender(appid, appkey); + SmsSingleSenderResult result = ssender.sendWithParam("86", phoneNumber, + templateId, params, smsSign, "", ""); // 签名参数未提供或者为空时,会使用默认签名发送短信 + System.out.println(result); + } catch (HTTPException e) { + // HTTP响应码错误 + e.printStackTrace(); + } catch (JSONException e) { + // json解析错误 + e.printStackTrace(); + } catch (IOException e) { + // 网络IO错误 + e.printStackTrace(); + } + } +} diff --git a/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/WXTemplateMsgSendService.java b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/WXTemplateMsgSendService.java new file mode 100644 index 000000000..cdaca41f1 --- /dev/null +++ b/litemall-core/src/main/java/org/linlinjava/litemall/core/notify/WXTemplateMsgSendService.java @@ -0,0 +1,132 @@ +package org.linlinjava.litemall.core.notify; + +import org.json.JSONObject; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Service; + +import javax.net.ssl.*; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.ConnectException; +import java.net.URL; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * 微信模版消息通知,未完成 + */ +@PropertySource(value = "classpath:notify.properties") +@Service("wxTemplateMsgSendService") +public class WXTemplateMsgSendService { + /** + * 发送微信消息(模板消息) + * + * @param touser 用户 OpenID + * @param templatId 模板消息ID + * @param formId payId或者表单ID + * @param clickurl URL置空,则在发送后,点击模板消息会进入一个空白页面(ios),或无法点击(android)。 + * @param topcolor 标题颜色 + * @param data 详细内容 + * @return + */ + public String sendWechatMsgToUser(String token, String touser, String templatId, String formId, String clickurl, String topcolor, JSONObject data) { + String tmpurl = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=" + token; + JSONObject json = new JSONObject(); + json.put("touser", touser); + json.put("template_id", templatId); + json.put("form_id", formId); + json.put("url", clickurl); + json.put("topcolor", topcolor); + json.put("data", data); + try { + JSONObject result = httpsRequest(tmpurl, "POST", json.toString()); +// log.info("发送微信消息返回信息:" + resultJson.get("errcode")); + String errmsg = (String) result.get("errmsg"); + if (!"ok".equals(errmsg)) { //如果为errmsg为ok,则代表发送成功,公众号推送信息给用户了。 + return "error"; + } + } catch (Exception e) { + e.printStackTrace(); + return "error"; + } + return "success"; + } + + /** + * 发送https请求 + * + * @param requestUrl 请求地址 + * @param requestMethod 请求方式(GET、POST) + * @param outputStr 提交的数据 + * @return JSONObject(通过JSONObject.get ( key)的方式获取json对象的属性值) + */ + private JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) { + JSONObject jsonObject = null; + try { + // 创建SSLContext对象,并使用我们指定的信任管理器初始化 + TrustManager[] tm = {new MyX509TrustManager()}; + SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); + sslContext.init(null, tm, new SecureRandom()); + // 从上述SSLContext对象中得到SSLSocketFactory对象 + SSLSocketFactory ssf = sslContext.getSocketFactory(); + URL url = new URL(requestUrl); + HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); + conn.setSSLSocketFactory(ssf); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setUseCaches(false); + // 设置请求方式(GET/POST) + conn.setRequestMethod(requestMethod); + // 当outputStr不为null时向输出流写数据 + if (null != outputStr) { + OutputStream outputStream = conn.getOutputStream(); + // 注意编码格式 + outputStream.write(outputStr.getBytes("UTF-8")); + outputStream.close(); + } + // 从输入流读取返回内容 + InputStream inputStream = conn.getInputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + String str = null; + StringBuffer buffer = new StringBuffer(); + while ((str = bufferedReader.readLine()) != null) { + buffer.append(str); + } + // 释放资源 + bufferedReader.close(); + inputStreamReader.close(); + inputStream.close(); + inputStream = null; + conn.disconnect(); + jsonObject = new JSONObject(buffer.toString()); + } catch (ConnectException ce) { +// log.error("连接超时:{}", ce); + } catch (Exception e) { +// log.error("https请求异常:{}", e); + } + return jsonObject; + } + + /** + * 微信请求 - 信任管理器 + */ + private class MyX509TrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + // return new X509Certificate[0]; + return null; + } + } +} diff --git a/litemall-core/src/main/resources/application.properties b/litemall-core/src/main/resources/application.properties index 257b30648..dd877f46b 100644 --- a/litemall-core/src/main/resources/application.properties +++ b/litemall-core/src/main/resources/application.properties @@ -1 +1,2 @@ -spring.profiles.active=dev \ No newline at end of file +spring.profiles.active=dev +spring.message.encoding = UTF-8 \ No newline at end of file diff --git a/litemall-core/src/main/resources/notify.properties b/litemall-core/src/main/resources/notify.properties new file mode 100644 index 000000000..9579ab7a2 --- /dev/null +++ b/litemall-core/src/main/resources/notify.properties @@ -0,0 +1,22 @@ + +#\u90AE\u4EF6\u53D1\u9001\u914D\u7F6E +sprint.mail.enable=false +spring.mail.host=smtp.exmail.qq.com +spring.mail.username=ex@ex.com.cn +spring.mail.password= +spring.mail.sendto=ex@qq.com + +#\u77ED\u4FE1\u53D1\u9001\u914D\u7F6E +spring.sms.enable=false +spring.sms.appid= +spring.sms.appkey= +spring.sms.sign= + +#\u77ED\u4FE1\u6A21\u7248\u6D88\u606F\u914D\u7F6E\uFF0C\u8BF7\u5728\u817E\u8BAF\u77ED\u4FE1\u5E73\u53F0\u914D\u7F6E\u597D\u5404\u4E2A\u901A\u77E5\u6D88\u606F\u7684\u6A21\u7248\uFF0C\u7136\u540E\u5C06\u6A21\u7248ID\u4E00\u4E00\u8D4B\u503C,LitemallNotifyService,NotifyType\u679A\u4E3E\u4E2D\u4E0E\u8FD9\u91CC\u4E00\u4E00\u5BF9\u5E94 +spring.sms.template.pay.complated=156349 +spring.sms.template.verificationcode=156433 + +#\u53D1\u9001\u7EBF\u7A0B\u6C60\u914D\u7F6E +spring.notify.corePoolSize=5 +spring.notify.maxPoolSize=100 +spring.notify.queueCapacity=50 \ No newline at end of file diff --git a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java index ab5382752..ee1cd33e6 100644 --- a/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java +++ b/litemall-wx-api/src/main/java/org/linlinjava/litemall/wx/web/WxOrderController.java @@ -9,8 +9,9 @@ import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.linlinjava.litemall.core.notify.LitemallNotifyService; +import org.linlinjava.litemall.core.notify.NotifyUtils; import org.linlinjava.litemall.core.util.JacksonUtil; -import org.linlinjava.litemall.core.util.MailUtils; import org.linlinjava.litemall.core.util.ResponseUtil; import org.linlinjava.litemall.db.domain.*; import org.linlinjava.litemall.db.service.*; @@ -80,6 +81,9 @@ public class WxOrderController { @Autowired private WxPayService wxPayService; + @Autowired + private LitemallNotifyService litemallNotifyService; + public WxOrderController() { } @@ -485,7 +489,7 @@ public Object prepay(@LoginUser Integer userId, @RequestBody String body, HttpSe orderRequest.setOutTradeNo(order.getOrderSn()); orderRequest.setOpenid(openid); // TODO 更有意义的显示名称 - orderRequest.setBody("litemall小商场-订单测试支付"); + orderRequest.setBody("订单:" + order.getOrderSn()); // 元转成分 Integer fee = 0; // 这里演示仅支付1分 @@ -552,8 +556,9 @@ public Object payNotify(HttpServletRequest request, HttpServletResponse response order.setOrderStatus(OrderUtil.STATUS_PAY); orderService.updateById(order); - //TODO 发送邮件通知,这里最好才用异步发送 - MailUtils.getMailUtils().sendEmail("订单通知", order.toString()); + //TODO 发送邮件和短信通知,这里采用异步发送 + litemallNotifyService.notifyMailMessage("订单通知", order.toString()); + litemallNotifyService.notifySMSTemplate(order.getMobile(), new String[]{""}, NotifyUtils.NotifyType.PAY_COMPLATED); return WxPayNotifyResponse.success("处理成功!"); } catch (Exception e) { @@ -715,5 +720,4 @@ public Object comment(@LoginUser Integer userId, Integer orderId, Integer goodsI LitemallOrderGoods orderGoods = orderGoodsList.get(0); return ResponseUtil.ok(orderGoods); } - } \ No newline at end of file