• 微服务项目:尚融宝(19)(后端搭建:阿里云短信服务整合)


    认清现实,放弃幻想,准备斗争

     

    阿里云短信服务

    一、使用RAM子用户

    1、进入子用户管理页面

    2、添加用户 

    3、获取子用户key

    AccessKeyId, AccessKeySecret

    这个玩意是找不回来的,创建的时候就应该马上保存下来,并且这玩意一般越久越不安全

    4、设置用户权限

    授权:AliyunDysmsFullAccess

    二、开通短信服务

    1、开通

    2、添加签名 

     3、添加模板

    一般个人来说,很难申请到的,可以用他自带的玩意先弄,自带也有模板什么的 

    4、套餐

    free.aliyun.com

    5、快速学习

    三、测试短信发送

    1、查找使用示例 

     2、测试短信发送

    3、查看发送结果

    短信服务->业务统计->发送记录查询

    创建短信微服务

    需求

    一、创建项目

    1、创建模块

    service-sms

    2、配置 pom.xml

    1. <dependencies>
    2. <dependency>
    3. <groupId>com.atguigugroupId>
    4. <artifactId>service-baseartifactId>
    5. <version>0.0.1-SNAPSHOTversion>
    6. dependency>
    7. <dependency>
    8. <groupId>org.springframework.bootgroupId>
    9. <artifactId>spring-boot-starter-webartifactId>
    10. dependency>
    11. <dependency>
    12. <groupId>org.projectlombokgroupId>
    13. <artifactId>lombokartifactId>
    14. dependency>
    15. <dependency>
    16. <groupId>com.aliyungroupId>
    17. <artifactId>aliyun-java-sdk-coreartifactId>
    18. dependency>
    19. dependencies>

    3、application.yml

    resources目录下创建文件

    1. spring:
    2. profiles:
    3. active: dev # 环境设置
    4. application:
    5. name: service-sms # 服务名
    6. #spring:
    7. redis:
    8. host: 192.168.100.100
    9. port: 6379
    10. database: 0
    11. password: 123456 #默认为空
    12. timeout: 3000ms #最大等待时间,超时则抛出异常,否则请求一直等待
    13. lettuce:
    14. pool:
    15. max-active: 20 #最大连接数,负值表示没有限制,默认8
    16. max-wait: -1 #最大阻塞等待时间,负值表示没限制,默认-1
    17. max-idle: 8 #最大空闲连接,默认8
    18. min-idle: 0 #最小空闲连接,默认0
    19. #阿里云短信
    20. aliyun:
    21. sms:
    22. region-id: cn-hangzhou
    23. key-id: 你的keyid
    24. key-secret: 你的keysecret
    25. template-code: 你的短信模板code
    26. sign-name: 你的短信模板签名

     4、logback-spring.xml

    1. "1.0" encoding="UTF-8"?>
    2. <configuration>
    3. <contextName>atguiguSrbcontextName>
    4. <property name="log.path" value="D:/project/test/srb_log/sms" />
    5. <property name="CONSOLE_LOG_PATTERN"
    6. value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) %highlight([%-5level]) %green(%logger) %msg%n"/>
    7. <property name="FILE_LOG_PATTERN"
    8. value="%date{yyyy-MM-dd HH:mm:ss} [%-5level] %thread %file:%line %logger %msg%n" />
    9. <property name="ENCODING"
    10. value="UTF-8" />
    11. <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    12. <encoder>
    13. <pattern>${CONSOLE_LOG_PATTERN}pattern>
    14. <charset>${ENCODING}charset>
    15. encoder>
    16. appender>
    17. <appender name="FILE" class="ch.qos.logback.core.FileAppender">
    18. <file>${log.path}/log.logfile>
    19. <append>trueappend>
    20. <encoder>
    21. <pattern>${FILE_LOG_PATTERN}pattern>
    22. <charset>${ENCODING}charset>
    23. encoder>
    24. appender>
    25. <appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    26. <file>${log.path}/log-rolling.logfile>
    27. <encoder>
    28. <pattern>${FILE_LOG_PATTERN}pattern>
    29. <charset>${ENCODING}charset>
    30. encoder>
    31. <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    32. <fileNamePattern>${log.path}/info/log-rolling-%d{yyyy-MM-dd}.%i.logfileNamePattern>
    33. <maxHistory>15maxHistory>
    34. <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
    35. <maxFileSize>1000KBmaxFileSize>
    36. timeBasedFileNamingAndTriggeringPolicy>
    37. rollingPolicy>
    38. appender>
    39. <springProfile name="dev,test">
    40. <logger name="com.atguigu" level="INFO">
    41. <appender-ref ref="CONSOLE" />
    42. logger>
    43. springProfile>
    44. <springProfile name="prod">
    45. <logger name="com.atguigu" level="ERROR">
    46. <appender-ref ref="CONSOLE" />
    47. <appender-ref ref="ROLLING_FILE" />
    48. logger>
    49. springProfile>
    50. configuration>

    5、创建SpringBoot启动类

    1. @SpringBootApplication
    2. @ComponentScan({"com.atguigu.srb", "com.atguigu.common"})
    3. public class ServiceSmsApplication {
    4. public static void main(String[] args) {
    5. SpringApplication.run(ServiceSmsApplication.class, args);
    6. }
    7. }

    二、自定义配置

    1、从配置文件读取常量

    创建常量读取工具类:SmsProperties.java+

    1. @Setter
    2. @Getter //idea2020.2.3版配置文件自动提示需要这个
    3. @Component
    4. //注意prefix要写到最后一个 "." 符号之前
    5. //调用setter为成员赋值
    6. @ConfigurationProperties(prefix = "aliyun.sms")
    7. public class SmsProperties implements InitializingBean {
    8. private String regionId;
    9. private String keyId;
    10. private String keySecret;
    11. private String templateCode;
    12. private String signName;
    13. public static String REGION_Id;
    14. public static String KEY_ID;
    15. public static String KEY_SECRET;
    16. public static String TEMPLATE_CODE;
    17. public static String SIGN_NAME;
    18. //当私有成员被赋值后,此方法自动被调用,从而初始化常量
    19. @Override
    20. public void afterPropertiesSet() throws Exception {
    21. REGION_Id = regionId;
    22. KEY_ID = keyId;
    23. KEY_SECRET = keySecret;
    24. TEMPLATE_CODE = templateCode;
    25. SIGN_NAME = signName;
    26. }
    27. }

    Idea报告如下错误信息(不影响程序的编译和运行):

    解决方案参考文档:

    解决步骤:

    step1:添加
    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-configuration-processorartifactId>
    4. <optional>trueoptional>
    5. dependency>

    step2:启动processor

    • step3:重新编译模块
    • step4:这样就可以在yml文件中有自定义配置的提示信息啦

       2、测试工具类

    创建测试类 UtilsTests,测试配置信息是否能正常获取

    1. @SpringBootTest
    2. @RunWith(SpringRunner.class)
    3. public class UtilsTests {
    4. @Test
    5. public void testProperties(){
    6. System.out.println(SmsProperties.KEY_ID);
    7. System.out.println(SmsProperties.KEY_SECRET);
    8. System.out.println(SmsProperties.REGION_Id);
    9. }
    10. }

     

    三、发送短信

    1、短信发送业务

    接口:创建 SmsService

    1. public interface SmsService {
    2. void send(String mobile, String templateCode, Map param);
    3. }

    实现:创建 SmsServiceImpl 

    1. @Service
    2. @Slf4j
    3. public class SmsServiceImpl implements SmsService {
    4. @Override
    5. public void send(String mobile, String templateCode, Map param) {
    6. //创建远程连接客户端对象
    7. DefaultProfile profile = DefaultProfile.getProfile(
    8. SmsProperties.REGION_Id,
    9. SmsProperties.KEY_ID,
    10. SmsProperties.KEY_SECRET);
    11. IAcsClient client = new DefaultAcsClient(profile);
    12. //创建远程连接的请求参数
    13. CommonRequest request = new CommonRequest();
    14. request.setSysMethod(MethodType.POST);
    15. request.setSysDomain("dysmsapi.aliyuncs.com");
    16. request.setSysVersion("2017-05-25");
    17. request.setSysAction("SendSms");
    18. request.putQueryParameter("RegionId", SmsProperties.REGION_Id);
    19. request.putQueryParameter("PhoneNumbers", mobile);
    20. request.putQueryParameter("SignName", SmsProperties.SIGN_NAME);
    21. request.putQueryParameter("TemplateCode", templateCode);
    22. Gson gson = new Gson();
    23. String json = gson.toJson(param);
    24. request.putQueryParameter("TemplateParam", json);
    25. try {
    26. //使用客户端对象携带请求对象发送请求并得到响应结果
    27. CommonResponse response = client.getCommonResponse(request);
    28. boolean success = response.getHttpResponse().isSuccess();
    29. //ALIYUN_RESPONSE_FAIL(-501, "阿里云响应失败"),
    30. Assert.isTrue(success, ResponseEnum.ALIYUN_RESPONSE_FAIL);
    31. String data = response.getData();
    32. HashMap resultMap = gson.fromJson(data, HashMap.class);
    33. String code = resultMap.get("Code");
    34. String message = resultMap.get("Message");
    35. log.info("阿里云短信发送响应结果:");
    36. log.info("code:" + code);
    37. log.info("message:" + message);
    38. //ALIYUN_SMS_LIMIT_CONTROL_ERROR(-502, "短信发送过于频繁"),//业务限流
    39. Assert.notEquals("isv.BUSINESS_LIMIT_CONTROL", code, ResponseEnum.ALIYUN_SMS_LIMIT_CONTROL_ERROR);
    40. //ALIYUN_SMS_ERROR(-503, "短信发送失败"),//其他失败
    41. Assert.equals("OK", code, ResponseEnum.ALIYUN_SMS_ERROR);
    42. } catch (ServerException e) {
    43. log.error("阿里云短信发送SDK调用失败:");
    44. log.error("ErrorCode=" + e.getErrCode());
    45. log.error("ErrorMessage=" + e.getErrMsg());
    46. throw new BusinessException(ResponseEnum.ALIYUN_SMS_ERROR , e);
    47. } catch (ClientException e) {
    48. log.error("阿里云短信发送SDK调用失败:");
    49. log.error("ErrorCode=" + e.getErrCode());
    50. log.error("ErrorMessage=" + e.getErrMsg());
    51. throw new BusinessException(ResponseEnum.ALIYUN_SMS_ERROR , e);
    52. }
    53. }
    54. }

    2、引入工具类

    RegexValidateUtils.java:常用正则表达式验证,这里提供了手机号码验证
    guigu-common中创建util包,引入工具类:
    RandomUtils.java:生成四位或六位的验证码

     3、创建controller

    创建controller.api包,创建类ApiSmsController
    1. @RestController
    2. @RequestMapping("/api/sms")
    3. @Api(tags = "短信管理")
    4. @CrossOrigin //跨域
    5. @Slf4j
    6. public class ApiSmsController {
    7. @Resource
    8. private SmsService smsService;
    9. @Resource
    10. private RedisTemplate redisTemplate;
    11. @ApiOperation("获取验证码")
    12. @GetMapping("/send/{mobile}")
    13. public R send(
    14. @ApiParam(value = "手机号", required = true)
    15. @PathVariable String mobile){
    16. //MOBILE_NULL_ERROR(-202, "手机号不能为空"),
    17. Assert.notEmpty(mobile, ResponseEnum.MOBILE_NULL_ERROR);
    18. //MOBILE_ERROR(-203, "手机号不正确"),
    19. Assert.isTrue(RegexValidateUtils.checkCellphone(mobile), ResponseEnum.MOBILE_ERROR);
    20. //生成验证码
    21. String code = RandomUtils.getFourBitRandom();
    22. //组装短信模板参数
    23. Map param = new HashMap<>();
    24. param.put("code", code);
    25. //发送短信
    26. smsService.send(mobile, SmsProperties.TEMPLATE_CODE, param);
    27. //将验证码存入redis
    28. redisTemplate.opsForValue().set("srb:sms:code:" + mobile, code, 5, TimeUnit.MINUTES);
    29. return R.ok().message("短信发送成功");
    30. }
    31. }

    四、配置Swagger

    1、添加配置

    service-base中添加网站端api的配置

    1. @Bean
    2. public Docket apiConfig(){
    3. return new Docket(DocumentationType.SWAGGER_2)
    4. .groupName("api")
    5. .apiInfo(apiInfo())
    6. .select()
    7. //只显示admin路径下的页面
    8. .paths(Predicates.and(PathSelectors.regex("/api/.*")))
    9. .build();
    10. }
    11. private ApiInfo apiInfo(){
    12. return new ApiInfoBuilder()
    13. .title("尚融宝-API文档")
    14. .description("本文档描述了尚融宝接口")
    15. .version("1.0")
    16. .contact(new Contact("Helen", "http://atguigu.com", "55317332@qq.com"))
    17. .build();
    18. }

    2、测试

    测试正确发送

    测试手机号码不正确

    测试业务限流

    今日bug

    2022-09-05 11:37:25 [ERROR] com.atguigu.common.exception.UnifiedExceptionHandler Unable to connect to Redis; nested exception is org.springframework.data.redis.connection.PoolException: Could not get a resource from the pool; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to 192.168.231.137:6379

    Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'redisTemplate' defined in class path resource [com/atguigu/srb/base/config/RedisConfig.class]: Unsatisfied dependency expressed through method 'redisTemplate' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {} 

     原因和解决

    redis版本冲突出现问题,在base模块中引新的的依赖解决

    1. <dependency>
    2. <groupId>org.springframework.bootgroupId>
    3. <artifactId>spring-boot-starter-data-redisartifactId>
    4. <version>2.1.3.RELEASEversion>
    5. dependency>
    6. <dependency>
    7. <groupId>redis.clientsgroupId>
    8. <artifactId>jedisartifactId>
    9. <version>2.9.1version>
    10. dependency>

  • 相关阅读:
    Spring循环依赖
    植物大战僵尸各种僵尸攻略
    常规的知识积累
    c++ 线程类
    C++项目:仿mudou库one thread one loop式并发服务器实现
    RabbitMQ-简述
    《树莓派项目实战》第六节 使用超声波模块测距
    Nginx rtmp&&Centos FFmpeg安装配置
    说说Flink双流join
    java之流程控制
  • 原文地址:https://blog.csdn.net/m0_62436868/article/details/126715130