手机号校验+客户余额校验+雪花算法生成短信id

main
heqijun 4 months ago
parent 58c5cd258c
commit 5b81d000e2

@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
/** /**
* @author heqijun * @author heqijun
@ -16,6 +17,7 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication @SpringBootApplication
@EnableFeignClients @EnableFeignClients
@EnableDiscoveryClient @EnableDiscoveryClient
@ComponentScan(basePackages = {"com.mashibing.api", "com.mashibing.common"})
public class ApiApplicaiton { public class ApiApplicaiton {
public static void main(String[] args) { public static void main(String[] args) {

@ -7,6 +7,7 @@ import com.mashibing.api.service.sendCheck.SendCheckContext;
import com.mashibing.common.pojo.JsonResult; import com.mashibing.common.pojo.JsonResult;
import com.mashibing.common.pojo.StandardSubmit; import com.mashibing.common.pojo.StandardSubmit;
import com.mashibing.common.utils.JsonResultUtil; import com.mashibing.common.utils.JsonResultUtil;
import com.mashibing.common.utils.SnowFlakeUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -31,6 +32,9 @@ import javax.validation.Valid;
@RefreshScope @RefreshScope
public class SMSController { public class SMSController {
@Autowired
SnowFlakeUtil snowFlakeUtil;
@Autowired @Autowired
SendCheckContext sendCheckContext; SendCheckContext sendCheckContext;
@ -49,8 +53,12 @@ public class SMSController {
standardSubmit.setRealIp(realIp); standardSubmit.setRealIp(realIp);
BeanUtils.copyProperties(request, standardSubmit); BeanUtils.copyProperties(request, standardSubmit);
//请求参数校验 //责任链校验
sendCheckContext.check(standardSubmit); sendCheckContext.check(standardSubmit);
//雪花算法生成唯一id
standardSubmit.setSequenceId(snowFlakeUtil.nextId());
//TODO 发送到MQ //TODO 发送到MQ
SingleSendResponse singleSendResponse = new SingleSendResponse(); SingleSendResponse singleSendResponse = new SingleSendResponse();

@ -31,7 +31,7 @@ public class ApikeySendCheckServiceImpl implements SendCheckService {
@Override @Override
public void check(StandardSubmit submit) { public void check(StandardSubmit submit) {
log.info("【接口模块】发送短信前apikey校验。。。"); log.info("【接口模块】apikey校验================================");
Map<String, String> clientBusinessMap = (Map<String, String>) cacheClient.hget(CacheConstant.CLIENT_BUSINESS + submit.getApikey()); Map<String, String> clientBusinessMap = (Map<String, String>) cacheClient.hget(CacheConstant.CLIENT_BUSINESS + submit.getApikey());
log.info("【接口模块】缓存中查询到客户信息:{}", clientBusinessMap); log.info("【接口模块】缓存中查询到客户信息:{}", clientBusinessMap);
if (MapUtils.isEmpty(clientBusinessMap)) { if (MapUtils.isEmpty(clientBusinessMap)) {

@ -3,6 +3,10 @@ package com.mashibing.api.service.sendCheck.impl;
import com.mashibing.api.feignClient.CacheClient; import com.mashibing.api.feignClient.CacheClient;
import com.mashibing.api.pojo.SingleSendRequest; import com.mashibing.api.pojo.SingleSendRequest;
import com.mashibing.api.service.sendCheck.SendCheckService; import com.mashibing.api.service.sendCheck.SendCheckService;
import com.mashibing.common.constant.CacheConstant;
import com.mashibing.common.constant.SMSConstant;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.exception.ApiException;
import com.mashibing.common.pojo.StandardSubmit; import com.mashibing.common.pojo.StandardSubmit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -20,10 +24,46 @@ import org.springframework.stereotype.Service;
public class FeeSendCheckServiceImpl implements SendCheckService { public class FeeSendCheckServiceImpl implements SendCheckService {
@Autowired @Autowired
CacheClient cacheClient; private CacheClient cacheClient;
/**
* 70
*/
private final int MAX_LENGTH = 70;
/**
* 7067/
*/
private final int LOOP_LENGTH = 67;
private final String BALANCE = "balance";
@Override @Override
public void check(StandardSubmit standardSubmit) { public void check(StandardSubmit standardSubmit) {
log.info("Check Fee Send Check"); log.info("【接口模块】客户余额校验================================");
//1、从submit中获取到短信内容
int length = standardSubmit.getText().length();
//2、判断短信内容的长度如果小于等于70算作一条如果大于70字按照67字/条,算出来当前短信的费用
if (length <= MAX_LENGTH) {
// 当前短信内容是一条
standardSubmit.setFee(SMSConstant.SINGLE_FEE);
} else {
int strip = length % LOOP_LENGTH == 0 ? length / LOOP_LENGTH : length / LOOP_LENGTH + 1;
standardSubmit.setFee(SMSConstant.SINGLE_FEE * strip);
}
//3、从Redis中查询出客户剩余的金额
Long balance = ((Integer) cacheClient.hget(CacheConstant.CLIENT_BALANCE + standardSubmit.getClientId(), BALANCE)).longValue();
//4、判断金额是否满足当前短信费用\
if (balance >= standardSubmit.getFee()) {
log.info("【接口模块-校验客户余额】 用户金额充足!!");
return;
}
//5、不满足就抛出异常
log.info("【接口模块-校验客户余额】 客户余额不足");
throw new ApiException(ExceptionEnums.BALANCE_NOT_ENOUGH);
} }
} }

@ -29,7 +29,7 @@ public class IpSendCheckServiceImpl implements SendCheckService {
@Override @Override
public void check(StandardSubmit submit) { public void check(StandardSubmit submit) {
log.info("【接口模块-校验ip】校验ing…………"); log.info("【接口模块】ip白名单校验================================");
//1. 根据CacheClient根据客户的apikey以及ipAddress去查询客户的IP白名单 //1. 根据CacheClient根据客户的apikey以及ipAddress去查询客户的IP白名单
String ip = cacheClient.hgetString(CacheConstant.CLIENT_BUSINESS + submit.getApikey(), IP_ADDRESS); String ip = cacheClient.hgetString(CacheConstant.CLIENT_BUSINESS + submit.getApikey(), IP_ADDRESS);
submit.setIpAddress(ip); submit.setIpAddress(ip);

@ -1,7 +1,11 @@
package com.mashibing.api.service.sendCheck.impl; package com.mashibing.api.service.sendCheck.impl;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.mashibing.api.pojo.SingleSendRequest; import com.mashibing.api.pojo.SingleSendRequest;
import com.mashibing.api.service.sendCheck.SendCheckService; import com.mashibing.api.service.sendCheck.SendCheckService;
import com.mashibing.api.util.PhoneFormatCheckUtil;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.exception.ApiException;
import com.mashibing.common.pojo.StandardSubmit; import com.mashibing.common.pojo.StandardSubmit;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -18,6 +22,14 @@ import org.springframework.stereotype.Service;
public class MobileSendCheckServiceImpl implements SendCheckService { public class MobileSendCheckServiceImpl implements SendCheckService {
@Override @Override
public void check(StandardSubmit standardSubmit) { public void check(StandardSubmit standardSubmit) {
log.info("Check mobile send check"); log.info("【接口模块】手机号校验================================");
String mobile = standardSubmit.getMobile();
if (!StringUtils.isEmpty(mobile) && PhoneFormatCheckUtil.isChinaPhone(mobile)) {
// 如果校验进来,代表手机号么得问题
log.info("【接口模块-校验手机号】 手机号格式合法 mobile = {}", mobile);
return;
}
log.info("【接口模块-校验手机号】 手机号格式不正确 mobile = {}", mobile);
throw new ApiException(ExceptionEnums.ERROR_MOBILE);
} }
} }

@ -31,7 +31,7 @@ public class SignSendCheckServiceImpl implements SendCheckService {
@Override @Override
public void check(StandardSubmit standardSubmit) { public void check(StandardSubmit standardSubmit) {
log.info("【接口模块】短信客户签名校验。。。"); log.info("【接口模块】客户签名校验================================");
String text = standardSubmit.getText(); String text = standardSubmit.getText();
//短信正确格式:【签名】短信内容。。。 //短信正确格式:【签名】短信内容。。。
//短信内容为空,显然不可能包含签名 //短信内容为空,显然不可能包含签名

@ -35,7 +35,7 @@ public class TemplateSendCheckServiceImpl implements SendCheckService {
@Override @Override
public void check(StandardSubmit standardSubmit) { public void check(StandardSubmit standardSubmit) {
log.info("【接口模块-校验模板】 校验ing…………"); log.info("【接口模块】短信模板校验================================");
// 1、从submit中获取到短信内容签名信息签名id // 1、从submit中获取到短信内容签名信息签名id
String text = standardSubmit.getText(); String text = standardSubmit.getText();
String sign = standardSubmit.getSign(); String sign = standardSubmit.getSign();

@ -0,0 +1,30 @@
package com.mashibing.api.util;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author heqijun
* @ClassName: PhoneFormatCheckUtil
* @Description: TODO()
* @date 2025/6/7 14:31
*/
public class PhoneFormatCheckUtil {
/**
*
*/
private final static Pattern CHINA_PATTERN = Pattern.compile("^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$");
/**
*
*
* @param number
* @return
*/
public static boolean isChinaPhone(String number) {
Matcher matcher = CHINA_PATTERN.matcher(number);
return matcher.matches();
}
}

@ -17,4 +17,6 @@ public class SMSConstant {
public static final String SIGN_SUFFIX = "】"; public static final String SIGN_SUFFIX = "】";
public static final Long SINGLE_FEE = 50L;
} }

@ -16,7 +16,9 @@ public enum ExceptionEnums {
ERROR_SIGN(-3, "无可用签名"), ERROR_SIGN(-3, "无可用签名"),
ERROR_TEMPLATE(-4, "无可用模板"), ERROR_TEMPLATE(-4, "无可用模板"),
ERROR_MOBILE(-5, "手机号格式不正确"), ERROR_MOBILE(-5, "手机号格式不正确"),
BALANCE_NOT_ENOUGH(-6, "手客户余额不足"), BALANCE_NOT_ENOUGH(-6, "客户余额不足"),
SNOWFLAKE_OUT_OF_RANGE(-10, "机器id超过最大范围!"),
SNOWFLAKE_TIME_BACK(-11, "雪花算法出现时间回拨!!!")
; ;
private int code; private int code;

@ -0,0 +1,158 @@
package com.mashibing.common.utils;
import com.mashibing.common.enums.ExceptionEnums;
import com.mashibing.common.exception.ApiException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
/**
* @author heqijun
* @ClassName: SnowFlakeUtil
* @Description: id
* @date 2025/6/7 16:40
*/
@Component
public class SnowFlakeUtil {
/**
* ID
* 64bitlong
* 1bit0.
* 41bit
* 5bitid
* 5bitid
* 12bit
*/
/**
* 41bit069.7
* 使19702039
* 2022-11-1141bit使2092~~
*/
private final long timeStart = 1668096000000L;
/**
* id
*/
@Value("${snowflake.machineId:0}")
private long machineId;
/**
* id
*/
@Value("${snowflake.serviceId:0}")
private long serviceId;
/**
*
*/
private long sequence;
/**
* idbit
*/
private final long machineIdBits = 5L;
/**
* idbit
*/
private final long serviceIdBits = 5L;
/**
* bit
*/
private final long sequenceBits = 12L;
/**
* id
*/
private long maxMachineId = ~(-1 << machineIdBits);
/**
* id
*/
private long maxServiceId = ~(-1 << serviceIdBits);
@PostConstruct
public void init() {
if (machineId > maxMachineId || serviceId > maxServiceId) {
System.out.println("机器ID或服务ID超过最大范围值");
throw new ApiException(ExceptionEnums.SNOWFLAKE_OUT_OF_RANGE);
}
}
/**
* id
*/
private long serviceIdShift = sequenceBits;
/**
* id
*/
private long machineIdShift = sequenceBits + serviceIdBits;
/**
*
*/
private long timestampShift = sequenceBits + serviceIdBits + machineIdBits;
/**
*
*/
private long maxSequenceId = ~(-1 << sequenceBits);
/**
* id
*/
private long lastTimestamp = -1;
/**
*
*
* @return
*/
private long timeGen() {
return System.currentTimeMillis();
}
public synchronized long nextId() {
//1、 拿到当前系统时间的毫秒值
long timestamp = timeGen();
// 避免时间回拨造成出现重复的id
if (timestamp < lastTimestamp) {
// 说明出现了时间回拨
System.out.println("当前服务出现时间回拨!!!");
throw new ApiException(ExceptionEnums.SNOWFLAKE_TIME_BACK);
}
//2、 判断当前生成id的时间和上一次生成的时间
if (timestamp == lastTimestamp) {
// 同一毫秒值生成id
sequence = (sequence + 1) & maxSequenceId;
// 0000 10100000 :sequence
// 1111 11111111 :maxSequenceId
if (sequence == 0) {
// 进到这个if说明已经超出了sequence序列的最大取值范围
// 需要等到下一个毫秒再做回来生成具体的值
timestamp = timeGen();
while (timestamp <= lastTimestamp) {
// 时间还没动。
timestamp = timeGen();
}
}
} else {
// 另一个时间点生成id
sequence = 0;
}
//3、重新给lastTimestamp复制
lastTimestamp = timestamp;
//4、计算id将几位值拼接起来。 41bit位的时间5位的机器5位的服务 12位的序列
return ((timestamp - timeStart) << timestampShift) |
(machineId << machineIdShift) |
(serviceId << serviceIdShift) |
sequence &
Long.MAX_VALUE;
}
}

@ -1,5 +1,6 @@
package com.mashibing.test.mapper; package com.mashibing.test.mapper;
import com.mashibing.test.entity.ClientBalance;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Select;
@ -12,7 +13,7 @@ import org.apache.ibatis.annotations.Select;
public interface ClientBalanceMapper { public interface ClientBalanceMapper {
@Select("select balance from client_balance where client_id = #{clientId}") @Select("select * from client_balance where client_id = #{clientId}")
Long getBalanceByClientId(@Param("clientId") Long clientId); ClientBalance getBalanceByClientId(@Param("clientId") Long clientId);
} }

@ -1,5 +1,8 @@
package com.mashibing.test.mapper; package com.mashibing.test.mapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mashibing.test.entity.ClientBalance;
import com.mashibing.test.feignClient.BeaconCacheClient; import com.mashibing.test.feignClient.BeaconCacheClient;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -7,6 +10,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest @SpringBootTest
@ -20,10 +25,11 @@ class ClientBalanceMapperTest {
ClientBalanceMapper clientBalanceMapper; ClientBalanceMapper clientBalanceMapper;
@Test @Test
void getBalanceByClientId() { void getBalanceByClientId() throws JsonProcessingException {
Long balance = clientBalanceMapper.getBalanceByClientId(1L); ClientBalance balance = clientBalanceMapper.getBalanceByClientId(1L);
System.out.println("balance = " + balance); System.out.println("balance = " + balance);
ObjectMapper objectMapper = new ObjectMapper();
beaconCacheClient.set("client_balance:1", balance.toString()); Map map = objectMapper.readValue(objectMapper.writeValueAsString(balance), Map.class);
beaconCacheClient.hset("client_balance:1", map);
} }
} }
Loading…
Cancel
Save