浏览代码

阿里云短信发送

xlongwei 6 年之前
父节点
当前提交
157bdacebc

+ 1 - 1
src/main/java/com/ifast/common/service/ConfigService.java

@@ -17,7 +17,7 @@ import java.util.Map;
 public interface ConfigService extends CoreService<ConfigDO> {
     ConfigDO getByKey(String k);
 
-    String getValuByKey(String k);
+    String getValueByKey(String k);
     
     void updateKV(Map<String, String> kv);
     

+ 1 - 1
src/main/java/com/ifast/common/service/impl/ConfigServiceImpl.java

@@ -26,7 +26,7 @@ public class ConfigServiceImpl extends CoreServiceImpl<ConfigDao, ConfigDO> impl
     }
     
     @Override
-    public String getValuByKey(String k) {
+    public String getValueByKey(String k) {
         ConfigDO bean = this.getByKey(k);
         return bean == null ? "" : bean.getV();
     }

+ 16 - 0
src/main/java/com/ifast/sms/config/AliyunProperties.java

@@ -0,0 +1,16 @@
+package com.ifast.sms.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+
+@Component
+@ConfigurationProperties(prefix = "ifast.sms.aliyun")
+@Data
+public class AliyunProperties {
+    private String accessKeyId;
+    private String accessKeySecret;
+    private Map<String, String> scenes;
+}

+ 13 - 0
src/main/java/com/ifast/sms/config/SmsConfiguration.java

@@ -1,11 +1,13 @@
 package com.ifast.sms.config;
 
 import com.ifast.common.config.CacheConfiguration;
+import com.ifast.sms.sender.AliyunSender;
 import com.ifast.sms.sender.ZhuTongSender;
 import com.ifast.sms.support.SmsManager;
 import com.ifast.sms.support.SmsSender;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.cache.Cache;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -28,6 +30,7 @@ public class SmsConfiguration {
     }
 
     @Bean
+    @ConditionalOnProperty(prefix="ifast.sms.zt", name="passwd", matchIfMissing=false)
     SmsSender zhuTongSender(ZhuTongProperties properties){
         if(log.isDebugEnabled()){
             log.debug("启用上海助通短信服务");
@@ -35,5 +38,15 @@ public class SmsConfiguration {
         SmsSender sender = new ZhuTongSender(properties);
         return sender;
     }
+    
+    @Bean
+    @ConditionalOnProperty(prefix="ifast.sms.aliyun", name="accessKeySecret", matchIfMissing=false)
+    SmsSender aliyunSender(AliyunProperties properties){
+    	if(log.isDebugEnabled()){
+    		log.debug("启用阿里云短信服务");
+    	}
+    	SmsSender sender = new AliyunSender(properties);
+    	return sender;
+    }
 
 }

+ 180 - 0
src/main/java/com/ifast/sms/sender/AliyunSender.java

@@ -0,0 +1,180 @@
+package com.ifast.sms.sender;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Triple;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import com.aliyuncs.DefaultAcsClient;
+import com.aliyuncs.IAcsClient;
+import com.aliyuncs.dysmsapi.model.v20170525.QueryInterSmsIsoInfoRequest;
+import com.aliyuncs.dysmsapi.model.v20170525.QueryInterSmsIsoInfoResponse;
+import com.aliyuncs.dysmsapi.model.v20170525.QueryInterSmsIsoInfoResponse.IsoSupportDTO;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;
+import com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;
+import com.aliyuncs.http.MethodType;
+import com.aliyuncs.profile.DefaultProfile;
+import com.aliyuncs.profile.IClientProfile;
+import com.ifast.api.exception.IFastApiException;
+import com.ifast.common.service.ConfigService;
+import com.ifast.common.type.EnumErrorCode;
+import com.ifast.common.utils.JSONUtils;
+import com.ifast.sms.config.AliyunProperties;
+import com.ifast.sms.support.SmsSender;
+
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 
+ * @author xlongwei 2018年9月12日
+ * @see <a href="https://help.aliyun.com/document_detail/55284.html?spm=5176.10629532.106.1.5f5b1cbeZg62kR">短信发送文档</a>
+ */
+@Slf4j
+public class AliyunSender implements SmsSender {
+	@Autowired
+	private ConfigService configService;
+	@Setter
+	private AliyunProperties properties;
+	
+	private static final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改)
+	private static final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改)
+	private static String accessKeyId;
+	private static String accessKeySecret;
+	
+	public AliyunSender(AliyunProperties properties) {
+		this.properties = properties;
+		accessKeyId = properties.getAccessKeyId();
+		accessKeySecret = properties.getAccessKeySecret();
+	}
+
+	@Override
+	public void send(String mobile, String code, String scene) throws IFastApiException {
+		String k = properties.getScenes().get(scene);
+		if (StringUtils.isBlank(k)) {
+			log.warn("【SMS】发送短信失败:请配置ifast.sms.aliyun.scenes.{}", scene);
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+		String v = configService.getValueByKey(k);
+		if (StringUtils.isBlank(v)) {
+			log.warn("【SMS】发送短信失败:请添加系统配置sys_config.{}={SignName, TemplateCode}", k);
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+
+		Map<String, Object> config = JSONUtils.jsonToMap(v);
+		if (config == null || !config.containsKey("SignName") || !config.containsKey("TemplateCode")) {
+			log.warn("【SMS】发送短信失败:请修改系统配置sys_config.{}={SignName, TemplateCode}", k);
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+		
+		String signName = config.get("SignName").toString(), TemplateCode = config.get("TemplateCode").toString();
+		String templateParam = "{\"code\":\"" + code + "\"}";
+		boolean send = send(mobile, signName, TemplateCode, templateParam);
+		log.info("【SMS】发送短信结果:{},mobile:{},code:{},scene:{}", send, mobile, code, scene);
+	}
+
+	/**
+	 * @param phoneNumbers 待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时,接收号码格式为00+国际区号+号码,如“0085200000000”
+	 * @param signName 短信签名-可在短信控制台中找到
+	 * @param templateCode 短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
+	 * @param templateParam 模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为"{\"name\":\"Tom\", \"code\":\"123\"}"
+	 * @return true or false
+	 * @throws IFastApiException
+	 */
+	public static boolean send(String phoneNumbers, String signName, String templateCode, String templateParam) {
+		if(StringUtils.isBlank(accessKeyId) || StringUtils.isBlank(accessKeySecret) || StringUtils.isBlank(phoneNumbers) || StringUtils.isBlank(signName) || StringUtils.isBlank(templateCode) || StringUtils.isBlank(templateParam)) {
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+		
+		try {
+			// 设置超时时间-可自行调整
+			System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
+			System.setProperty("sun.net.client.defaultReadTimeout", "10000");
+			// 初始化ascClient需要的几个参数
+//			final String product = "Dysmsapi";// 短信API产品名称(短信产品名固定,无需修改)
+//			final String domain = "dysmsapi.aliyuncs.com";// 短信API产品域名(接口地址固定,无需修改)
+			// 替换成你的AK
+//			final String accessKeyId = "yourAccessKeyId";// 你的accessKeyId,参考本文档步骤2
+//			final String accessKeySecret = "yourAccessKeySecret";// 你的accessKeySecret,参考本文档步骤2
+			// 初始化ascClient,暂时不支持多region(请勿修改)
+			IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
+			DefaultProfile.addEndpoint("cn-hangzhou", "cn-hangzhou", product, domain);
+			IAcsClient acsClient = new DefaultAcsClient(profile);
+			// 组装请求对象
+			SendSmsRequest request = new SendSmsRequest();
+			// 使用post提交
+			request.setMethod(MethodType.POST);
+			// 必填:待发送手机号。支持以逗号分隔的形式进行批量调用,批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式;发送国际/港澳台消息时,接收号码格式为00+国际区号+号码,如“0085200000000”
+			request.setPhoneNumbers(phoneNumbers);
+			// 必填:短信签名-可在短信控制台中找到
+			request.setSignName(signName);
+			// 必填:短信模板-可在短信控制台中找到,发送国际/港澳台消息时,请使用国际/港澳台短信模版
+			request.setTemplateCode(templateCode);
+			// 可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
+			// 友情提示:如果JSON中需要带换行符,请参照标准的JSON协议对换行符的要求,比如短信内容中包含\r\n的情况在JSON中需要表示成\\r\\n,否则会导致JSON在服务端解析失败
+			request.setTemplateParam(templateParam);
+			// 可选-上行短信扩展码(扩展码字段控制在7位或以下,无特殊需求用户请忽略此字段)
+			// request.setSmsUpExtendCode("90997");
+			// 可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
+			// request.setOutId("yourOutId");
+			// 请求失败这里会抛ClientException异常
+			SendSmsResponse sendSmsResponse = acsClient.getAcsResponse(request);
+			if (sendSmsResponse.getCode() != null && sendSmsResponse.getCode().equals("OK")) {
+				log.info("【SMS】阿里云发送短信成功,code:{},message:{},requestId:{},bizId:{}", sendSmsResponse.getCode(), sendSmsResponse.getMessage(), sendSmsResponse.getRequestId(), sendSmsResponse.getBizId());
+				return true;
+			}else {
+				log.warn("【SMS】阿里云发送短信失败,code:{},message:{},requestId:{},bizId:{}", sendSmsResponse.getCode(), sendSmsResponse.getMessage(), sendSmsResponse.getRequestId(), sendSmsResponse.getBizId());
+				return false;
+			}
+		}catch (Exception e) {
+			log.warn("【SMS】阿里云发送短信失败:{},mobile:{},signName:{},templateCode:{},templateParam:{}", e.getMessage(), phoneNumbers, signName, templateCode, templateParam);
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+	}
+	
+	/**
+	 * 国家区号可以考虑存入字典表
+	 * @return [(963,SY,叙利亚), (886,TW,中国台湾), (992,TJ,塔吉克斯坦), ...]
+	 */
+	public static List<Triple<String, String, String>> countryCode(){
+		if(StringUtils.isBlank(accessKeyId) || StringUtils.isBlank(accessKeySecret)) {
+			throw new IFastApiException(EnumErrorCode.apiSmsSendFailed.getCodeStr());
+		}
+		
+		try {
+			List<Triple<String, String, String>> list = new ArrayList<>();
+			// 可自助调整超时时间
+			System.setProperty("sun.net.client.defaultConnectTimeout", "10000");
+			System.setProperty("sun.net.client.defaultReadTimeout", "10000");
+			// 初始化acsClient,暂不支持region化
+			IClientProfile profile = DefaultProfile.getProfile("cn-hangzhou", accessKeyId, accessKeySecret);
+			DefaultProfile.addEndpoint("ak-openapi-pop-cn-hangzhou", "cn-hangzhou", product, domain);
+			IAcsClient acsClient = new DefaultAcsClient(profile);
+			// 组装请求对象
+			QueryInterSmsIsoInfoRequest request = new QueryInterSmsIsoInfoRequest();
+			// request.setCountryName("中国");
+			// hint 此处可能会抛出异常,注意catch
+			QueryInterSmsIsoInfoResponse queryInterSmsIsoInfoResponse = acsClient.getAcsResponse(request);
+			if (queryInterSmsIsoInfoResponse.getCode() != null && "OK".equals(queryInterSmsIsoInfoResponse.getCode())) {
+				List<IsoSupportDTO> isoSupportDTOs = queryInterSmsIsoInfoResponse.getIsoSupportDTOs();
+				for (IsoSupportDTO isoSupportDTO : isoSupportDTOs) {
+					list.add(Triple.of(isoSupportDTO.getIsoCode(), isoSupportDTO.getCountryCode(), isoSupportDTO.getCountryName()));
+				}
+			}
+			return list;
+		}catch (Exception e) {
+			return null;
+		}
+	}
+	
+	public static void main(String[] args) {
+		accessKeyId = "";
+		accessKeySecret = "";
+		String phoneNumbers = "", signName = "", templateCode = "", templateParam = "{\"code\":\"3572\"}";
+		log.info("countryCode: {}", countryCode());
+		log.info("send sms: {}", send(phoneNumbers, signName, templateCode, templateParam));
+	}
+}

+ 7 - 0
src/main/resources/application-dev.yml

@@ -31,6 +31,13 @@ ifast:
       productId: PRODUCT_ID
       scenes:
         validCode: 验证码{code},您正在进行身份验证,打死也不要告诉别人哦!
+    # 使用aliyun发送短信时请注释zt.passwd,配置accessKeyId和accessKeySecret,添加配置表项sys_config.sms_aliyun_validCode
+    aliyun:
+      accessKeyId: 
+      #accessKeySecret: 
+      scenes:
+        # 配置格式为包含SignName和TemplateCode的json字符串:sys_config.sms_aliyun_validCode = {SignName, TemplateCode}
+        validCode: sms_aliyun_validCode
 logging:
   level:
     com.ifast: debug

+ 72 - 65
src/main/resources/application-prod.yml

@@ -1,65 +1,72 @@
-ifast:
-  projectName: ifast
-  projectRootURL: http://127.0.0.1:8088/
-  demoMode: false
-  jwt:
-    userPrimaryKey: userId
-    expireTokenKeyPrefix: ifast:expireToken
-    expireTime: 7200000
-    refreshTokenExpire: 86400000
-  swagger:
-    title: ifast接口文档
-    description: 开发人员太懒,没有写描述
-    contactName: Aron
-    contactEmail: izenglong@163.com
-    contactUrl: http://ifast.site/swagger-ui.html
-  shiro:
-    jsessionidKey: SESSION
-    sessionKeyPrefix: ifast:session
-  oss:
-    local:
-      localPath: /Users/Aron/dev_projects/idea/ifast/src/main/resources/static/upload
-      rootURL: http://localhost:8088/upload
-  sms:
-    cacheKey: ifast:cache
-    cacheKeyPrefix: sms
-    codeExpireTime: 900000
-    zt:
-      url: http://www.ztsms.cn/sendNSms.do
-      uname: UNAME
-      passwd: PASSWD
-      productId: PRODUCT_ID
-      scenes:
-        validCode: 验证码{code},您正在进行身份验证,打死也不要告诉别人哦!
-logging:
-  level:
-    com.ifast: info
-spring:
-  datasource:
-    type: com.alibaba.druid.pool.DruidDataSource
-    driverClassName: com.mysql.jdbc.Driver
-    url: jdbc:mysql://127.0.0.1:3306/ifast?useUnicode=true&characterEncoding=utf8
-    username: root
-    password: 1024key@root
-    initialSize: 1
-    minIdle: 3
-    maxActive: 20
-    # 配置获取连接等待超时的时间
-    maxWait: 60000
-    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-    timeBetweenEvictionRunsMillis: 60000
-    # 配置一个连接在池中最小生存的时间,单位是毫秒
-    minEvictableIdleTimeMillis: 30000
-    validationQuery: select 'x'
-    testWhileIdle: true
-    testOnBorrow: false
-    testOnReturn: false
-    # 打开PSCache,并且指定每个连接上PSCache的大小
-    poolPreparedStatements: true
-    maxPoolPreparedStatementPerConnectionSize: 20
-    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
-    filters: stat,wall,slf4j
-    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
-    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
-    # 合并多个DruidDataSource的监控数据
-    #useGlobalDataSourceStat: true
+ifast:
+  projectName: ifast
+  projectRootURL: http://127.0.0.1:8088/
+  demoMode: false
+  jwt:
+    userPrimaryKey: userId
+    expireTokenKeyPrefix: ifast:expireToken
+    expireTime: 7200000
+    refreshTokenExpire: 86400000
+  swagger:
+    title: ifast接口文档
+    description: 开发人员太懒,没有写描述
+    contactName: Aron
+    contactEmail: izenglong@163.com
+    contactUrl: http://ifast.site/swagger-ui.html
+  shiro:
+    jsessionidKey: SESSION
+    sessionKeyPrefix: ifast:session
+  oss:
+    local:
+      localPath: /Users/Aron/dev_projects/idea/ifast/src/main/resources/static/upload
+      rootURL: http://localhost:8088/upload
+  sms:
+    cacheKey: ifast:cache
+    cacheKeyPrefix: sms
+    codeExpireTime: 900000
+    zt:
+      url: http://www.ztsms.cn/sendNSms.do
+      uname: UNAME
+      passwd: PASSWD
+      productId: PRODUCT_ID
+      scenes:
+        validCode: 验证码{code},您正在进行身份验证,打死也不要告诉别人哦!
+    # 使用aliyun发送短信时请注释zt.passwd,配置accessKeyId和accessKeySecret,添加配置表项sys_config.sms_aliyun_validCode
+    aliyun:
+      accessKeyId: 
+      #accessKeySecret: 
+      scenes:
+        # 配置格式为包含SignName和TemplateCode的json字符串:sys_config.sms_aliyun_validCode = {SignName, TemplateCode}
+        validCode: sms_aliyun_validCode        
+logging:
+  level:
+    com.ifast: info
+spring:
+  datasource:
+    type: com.alibaba.druid.pool.DruidDataSource
+    driverClassName: com.mysql.jdbc.Driver
+    url: jdbc:mysql://127.0.0.1:3306/ifast?useUnicode=true&characterEncoding=utf8
+    username: root
+    password: 1024key@root
+    initialSize: 1
+    minIdle: 3
+    maxActive: 20
+    # 配置获取连接等待超时的时间
+    maxWait: 60000
+    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+    timeBetweenEvictionRunsMillis: 60000
+    # 配置一个连接在池中最小生存的时间,单位是毫秒
+    minEvictableIdleTimeMillis: 30000
+    validationQuery: select 'x'
+    testWhileIdle: true
+    testOnBorrow: false
+    testOnReturn: false
+    # 打开PSCache,并且指定每个连接上PSCache的大小
+    poolPreparedStatements: true
+    maxPoolPreparedStatementPerConnectionSize: 20
+    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
+    filters: stat,wall,slf4j
+    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
+    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
+    # 合并多个DruidDataSource的监控数据
+    #useGlobalDataSourceStat: true

+ 6 - 12
src/main/resources/templates/common/generator/Controller.java.vm

@@ -17,6 +17,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
 import com.baomidou.mybatisplus.mapper.EntityWrapper;
 import com.baomidou.mybatisplus.mapper.Wrapper;
 import com.baomidou.mybatisplus.plugins.Page;
+import com.ifast.common.annotation.Log;
 import com.ifast.common.base.AdminBaseController;
 import ${package}.domain.${className}DO;
 import ${package}.service.${className}Service;
@@ -64,9 +65,7 @@ public class ${className}Controller extends AdminBaseController {
 	    return "${pathName}/${classname}/edit";
 	}
 	
-	/**
-	 * 保存
-	 */
+	@Log("添加${comments}")
 	@ResponseBody
 	@PostMapping("/save")
 	@RequiresPermissions("${pathName}:${classname}:add")
@@ -74,9 +73,8 @@ public class ${className}Controller extends AdminBaseController {
 		${classname}Service.insert(${classname});
         return Result.ok();
 	}
-	/**
-	 * 修改
-	 */
+	
+	@Log("修改${comments}")
 	@ResponseBody
 	@RequestMapping("/update")
 	@RequiresPermissions("${pathName}:${classname}:edit")
@@ -85,9 +83,7 @@ public class ${className}Controller extends AdminBaseController {
 		return update ? Result.ok() : Result.fail();
 	}
 	
-	/**
-	 * 删除
-	 */
+	@Log("删除${comments}")
 	@PostMapping( "/remove")
 	@ResponseBody
 	@RequiresPermissions("${pathName}:${classname}:remove")
@@ -96,9 +92,7 @@ public class ${className}Controller extends AdminBaseController {
         return Result.ok();
 	}
 	
-	/**
-	 * 删除
-	 */
+	@Log("批量删除${comments}")
 	@PostMapping( "/batchRemove")
 	@ResponseBody
 	@RequiresPermissions("${pathName}:${classname}:batchRemove")