自学内容网 自学内容网

java抽奖系统登录上(三)

 6.用户登录模块

        这里的登录方式有两种,第一种是密码加账号登录;2、第二种是手机号加验证码来登录;

6.1 获取验证码

        下面时序图中短信的转发使用的是阿里云短信服务工具;

6.2 阿里云短信服务

        在阿里云短信服务上搭建项目的服务;

将该服务集成到项目上:

将其maven依赖添加到项目中;

在项目的工具类中实现一个工具类SMSUtil

首先配置id和secret,在配置文件里面;

sms.access-key-id=111111111111111111
sms.access-key-secret=1111111111111111
sms.sign-name=111111111

其次完善SMSUtil类:

@Component
public class SMSUtil {
    private static final Logger logger = LoggerFactory.getLogger(SMSUtil.class);

    @Value(value = "${sms.sign-name}")
    private String signName;
    @Value(value = "${sms.access-key-id}")
    private String accessKeyId;
    @Value(value = "${sms.access-key-secret}")
    private String accessKeySecret;

    /**
     * 发送短信
     *
     * @param templateCode 模板号
     * @param phoneNumbers 手机号
     * @param templateParam 模板参数 {"key":"value"}
     */
    public void sendMessage(String templateCode, String phoneNumbers, String templateParam) {
        try {
            Client client = createClient();
            SendSmsRequest sendSmsRequest = new SendSmsRequest()
                    .setSignName(signName)
                    .setTemplateCode(templateCode)
                    .setPhoneNumbers(phoneNumbers)
                    .setTemplateParam(templateParam);
            RuntimeOptions runtime = new RuntimeOptions();
            SendSmsResponse response = client.sendSmsWithOptions(sendSmsRequest, runtime);
            if (null != response.getBody()
                    && null != response.getBody().getMessage()
                    && "OK".equals(response.getBody().getMessage())) {
                logger.info("向{}发送信息成功,templateCode={}", phoneNumbers, templateCode);
                return;
            }
            logger.error("向{}发送信息失败,templateCode={},失败原因:{}",
                    phoneNumbers, templateCode, response.getBody().getMessage());
        } catch (TeaException error) {
            logger.error("向{}发送信息失败,templateCode={}", phoneNumbers, templateCode, error);
        } catch (Exception _error) {
            TeaException error = new TeaException(_error.getMessage(), _error);
            logger.error("向{}发送信息失败,templateCode={}", phoneNumbers, templateCode, error);
        }
    }

    /**
     * 使用AK&SK初始化账号Client
     * @return Client
     */
    private Client createClient() throws Exception {
        // 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
        // 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。
        Config config = new Config()
                .setAccessKeyId(accessKeyId)
                .setAccessKeySecret(accessKeySecret);
        // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
        config.endpoint = "dysmsapi.aliyuncs.com";
        return new Client(config);
    }
}

 使用测试类进行测试:

@SpringBootTest
public class SMSTest {

    @Autowired
    private SMSUtil smsUtil;

    @Test
    void smsTest() {
        smsUtil.sendMessage(
                "SMS_475795287",
                "15809211624",
                "{\"code\":\"7945\"}");
        // {"code":"7945"}
        //使用json字符串的格式,对于符号要进行转意
    }

}

测试结果如下:验证码成功的发向手机;

6.3 redis的使用

        redis本次使用主要是用来存储验证码,这里缓存的验证码用来和客户端输入的验证码进行正误比较;相对于从磁盘和数据库中访问数据,从内存中进行访问速度会很快;redis可以部署在集群环境中,是将数据存放在内存中;

redis的优点:

        1、redis将数据放在内存中,可以提高数据访问的速度;

        2、redis是由c语言实现的,距离操作系统比较近;

        3、redis使用的是单线程,可以避免锁竞争;

        将其依赖放入到项目文件中,配置端口转发,使用端口转发的方式,直接吧服务器的redis端口映射到本地,这样本地主机就能访问redis;

        配置redis的相关项:

## redis  spring boot 3.x ##
spring.data.redis.host=localhost
spring.data.redis.port=8888
# 连接空闲超过N(s秒、ms毫秒)后关闭,0为禁⽤,这⾥配置值和tcp-keepalive值⼀致
spring.data.redis.timeout=60s
# 默认使⽤ lettuce 连接池
# 允许最⼤连接数,默认8(负值表⽰没有限制)
spring.data.redis.lettuce.pool.max-active=8
# 最⼤空闲连接数,默认8
spring.data.redis.lettuce.pool.max-idle=8
# 最⼩空闲连接数,默认0
spring.data.redis.lettuce.pool.min-idle=0
# 连接⽤完时,新的请求等待时间(s秒、ms毫秒),超过该时间抛出异常JedisConnectionException,(默认-1,负值表⽰没有限制)
spring.data.redis.lettuce.pool.max-wait=5s

        在进行连接之前首先在服务器上启动redis;service redis-server start

        其次连接之后显示连接成功,如下所示

接下来对于redis中关于stringredistemplate进行工具类封装,后续对于redis的操作通过redisutil工具类来完成:下面代码是对于redis中数据的增加,删除,查找的实现:

@Configuration
public class RedisUtil {
    private static final Logger logger = LoggerFactory.getLogger(RedisUtil.class);
    /**
     * RedisTemplate 序列化规则:先将被存储的数据转换为字节数组(不可读),再存储到redis中,
     * 读取的时候按照字节数组读取
     * StringRedisTemplate序列化规则 : 直接存放的就是 string (可读),写的什么读到的就是什么
     * 项目背景:String,String(k,v)类型
     */
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    // --------------- String ----------------------
//    本次考虑的存储仅限于string类型的
    /**
     * 设置值
     *
     * @param key
     * @param value
     * @return
     */
    public boolean set(String key, String value) {
        try {
            stringRedisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            logger.error("RedisUtil error, set({}, {})", key, value, e);
            return false;
        }
    }

    /**
     * 设置值(设置过期时间)
     *
     * @param key
     * @param value
     * @param time   单位:秒
     * @return
     */
    public boolean set(String key, String value, Long time) {
        try {
            stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            logger.error("RedisUtil error, set({}, {}, {})", key, value, time, e);
            //e不需要占位符
            return false;
        }
    }

    /**
     * 获取值
     *
     * @param key
     * @return
     */
    public String get(String key) {
        try {
            return StringUtils.hasText(key)
                    ? stringRedisTemplate.opsForValue().get(key)
                    : null;
        } catch (Exception e) {
            logger.error("RedisUtil error, get({})", key, e);
            return null;
        }
    }

    /**
     * 删除值
     *
     * @param key
     * @return
     */
    public boolean del(String... key) {
//        String...可以传入多种k
        try {
            if (null != key && key.length > 0) {
                if (key.length == 1) {
                    stringRedisTemplate.delete(key[0]);
                } else {
                    stringRedisTemplate.delete(
                            (Collection<String>) CollectionUtils.arrayToList(key)
                    );
                }
            }
            return true;
        } catch (Exception e) {
            logger.error("RedisUtil error, del({})", key, e);
            return false;
        }
    }

    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        try {
            return StringUtils.hasText(key) ? stringRedisTemplate.hasKey(key) : false;
        } catch (Exception e) {
            logger.error("RedisUtil error, hasKey({})", key, e);
            return false;
        }
    }
}

        接下来完成根据手机号从redis'中获取验证码的操作:

service层短信验证业务接口:

public interface VerificationCodeService {
    //验证码服务接口
    //发送验证码
    void sendVerificationCode(String phoneNumber);
    //获取验证码 从缓存中
    String getVerificationCode(String phoneNumber);
}

对于该层接口的实现方法 

@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {

    // 对于redis里面的key需要标准化:为了区分业务,应该给key定义前缀:
    //向活动相关的获取信息,人员相关的活动信息,下面虽然手机号一样,但是对于活动业务就区分开了
    //防治一个key参加多种业务存储的value被替代
    // VerificationCode_13111111111:1233、User_13111111111:userInfo

    private static final String VERIFICATION_CODE_PREFIX = "VERIFICATION_CODE_";
    private static final Long VERIFICATION_CODE_TIMEOUT = 120L;
    private static final String VERIFICATION_CODE_TEMPLATE_CODE = "SMS_475795287";

    @Autowired
    private SMSUtil smsUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Override
    public void sendVerificationCode(String phoneNumber) {
        //校验手机号
        if(!RegexUtil.checkMobile(phoneNumber)){
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        //生成随机验证码 hutool里面的一个工具类完成验证码=随机生成
        String code = CaptchaUtil.getCaptcha(4);
        //发送验证码
        // {"code":"xxxx"},参数要求的是json格式的字符串,所以提前使用map进行包装
        Map<String, String> map = new HashMap<>();
        map.put("code", code);
        smsUtil.sendMessage(
                VERIFICATION_CODE_TEMPLATE_CODE,
                phoneNumber,
                JacksonUtil.writeValueAsString(map));//将string的map转为json格式的字符串
        //缓存验证码
        // 131xxxxxxxx: code 一个手机号对应一个验证码
        redisUtil.set(VERIFICATION_CODE_PREFIX + phoneNumber, code, VERIFICATION_CODE_TIMEOUT);
    }

    @Override
    public String getVerificationCode(String phoneNumber) {
        //校验手机号
        if(!RegexUtil.checkMobile(phoneNumber)){
            throw new ServiceException(ServiceErrorCodeConstants.PHONE_NUMBER_ERROR);
        }
        String result= redisUtil.get(VERIFICATION_CODE_PREFIX + phoneNumber);
        return result;
    }
}

 在生成短信四位随机数的时候使用了hutool包里面的方法,故此对随机生成验证码的相关代码进行工具类的封装:

public class CaptchaUtil {
    /**
     * 生成随机验证码
     *
     * @param length  几位
     * @return
     */
    public static String getCaptcha(int length) {
        // 自定义纯数字的验证码(随机4位数字,可重复)
        RandomGenerator randomGenerator = new RandomGenerator("0123456789", length);
        LineCaptcha lineCaptcha = cn.hutool.captcha.CaptchaUtil.createLineCaptcha(200, 100);
        lineCaptcha.setGenerator(randomGenerator);
        // 重新生成code
        lineCaptcha.createCode();
        return lineCaptcha.getCode();
    }
}

测试验证码发送:

手机可以收到短信验证码,下图是redis缓存测试结果:

controller层代码实现:

   @RequestMapping("/verification-code/send")
    public CommonResult<Boolean> sendVerificationCode(String phoneNumber) {
        logger.info("sendVerificationCode phoneNumber:{}", phoneNumber);
        verificationCodeService.sendVerificationCode(phoneNumber);
        return CommonResult.success(Boolean.TRUE);
    }

ps:委婉待续,谢谢观看!


原文地址:https://blog.csdn.net/2202_76101487/article/details/144396619

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!