SmsRepository.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. <?php
  2. namespace App\Repositories\Utils;
  3. use App\Constants\PatternConstant;
  4. use Carbon\Carbon;
  5. use Psr\SimpleCache\CacheInterface;
  6. use Hyperf\Guzzle\ClientFactory;
  7. use App\Repositories\ConfigRepoistory;
  8. class SmsRepository
  9. {
  10. /**
  11. * 缓存仓库
  12. * @var null|CacheInterface
  13. */
  14. private $cacheRepo = null;
  15. /**
  16. * http客户端仓库
  17. * @var null|\GuzzleHttp\Client
  18. */
  19. private $httpClient = null;
  20. private $configRepo = null;
  21. /**
  22. * 缓存前缀
  23. * @var string
  24. */
  25. public static $cachePrefix = 'sms:';
  26. /**
  27. * 实效性.
  28. * @var int
  29. */
  30. public static $cacheTimeoutMinutes = 5;
  31. /**
  32. * 发送校验短信
  33. * @param string $mobile 手机号
  34. * @param number $code 短信code
  35. * @return array
  36. * @throws \GuzzleHttp\Exception\GuzzleException|array
  37. * @throws \Psr\SimpleCache\InvalidArgumentException
  38. */
  39. public function sendVerify($mobile, $code)
  40. {
  41. $type = 'verify_text';
  42. $result = ['code' => 200, 'message' => '', 'data' => []];
  43. $lastDateKey = self::$cachePrefix . $type . ':' . $mobile . ':last_date';
  44. $lastDate = $this->getCacheRepo()->get($lastDateKey);
  45. $lastDateObj = Carbon::parse($lastDate);
  46. $nowDateObj = Carbon::now();
  47. //比较发送频率
  48. if (!empty($lastDate) && abs($nowDateObj->diffInSeconds($lastDateObj)) <= 30) {
  49. $result['code'] = '601011';
  50. $result['message'] = '请间隔30秒发送短信';
  51. return $result;
  52. }
  53. $sentLogKey = self::$cachePrefix . $type . ':'. $mobile. ':log';
  54. //去掉1小时前的发送记录
  55. $sendLogs = $this->getCacheRepo()->get($sentLogKey, []);
  56. if (!empty($sendLogs)) {
  57. foreach ($sendLogs as $k => $logDate) {
  58. if (abs($nowDateObj->diffInMinutes(Carbon::parse($logDate))) >= 60) {
  59. // 剔除超过1小时的记录.
  60. unset($sendLogs[$k]);
  61. }
  62. }
  63. }
  64. // 1小时3次限制
  65. if (count($sendLogs) >= 3) {
  66. $result['code'] = '601012';
  67. $result['message'] = '一小时之内验证码的发送次数不得超过3次';
  68. return $result;
  69. }
  70. if (!self::isChinaMobile($mobile)) {
  71. $result['code'] = '600012';
  72. $result['message'] = '仅限发送中国大陆手机号';
  73. return $result;
  74. }
  75. $code = $this->createCode($mobile, $type,$code);
  76. // 记录会话时间
  77. $this->getCacheRepo()->set($lastDateKey, Carbon::now()->toDateTimeString(), 2 * 60 * 60);
  78. //发送短信 + 1
  79. if ($result['code'] == 200) {
  80. $arrConfig = $this->getConfigRepo()->gets(['tg_api_token', 'tg_msg_server'], 'sys', []);
  81. $token = data_get($arrConfig, 'tg_api_token', '');
  82. $sendLogs[] = Carbon::now()->toDateTimeString();
  83. $this->getCacheRepo()->set($sentLogKey, $sendLogs, 24 * 60 * 60);
  84. $uri = '/api/sms/send/verify';
  85. $options = [
  86. 'query' => [
  87. 'AUTH-TOKEN' => $token,
  88. ],
  89. 'json' => [
  90. 'mobile' => $mobile,
  91. 'code' => $code,
  92. ],
  93. ];
  94. $ret = $this->getHttpClient()->post($uri, $options);
  95. $httpStatus = $ret->getStatusCode();
  96. if ($httpStatus == 200) {
  97. $arrResp = json_decode($ret->getBody()->getContents(), true);
  98. $result['code'] = $arrResp['code'];
  99. $result['message'] = $arrResp['message'];
  100. $result['data'] = $arrResp['data'];
  101. }
  102. }
  103. return $result;
  104. }
  105. /**
  106. * 创建CODE
  107. * @param int|string $mobile 手机号
  108. * @param string $type 类型
  109. * @param number $code 短信验验证码
  110. * @return mixed
  111. */
  112. public function createCode($mobile, $type, $code)
  113. {
  114. $cacheKey = self::$cachePrefix . $type . ':' . $mobile;
  115. $this->getCacheRepo()->set($cacheKey, $code, self::$cacheTimeoutMinutes * 60);
  116. return $code;
  117. }
  118. /**
  119. * 获取Code
  120. * @param int|string $mobile 手机号
  121. * @param string $type 类型
  122. * @return mixed
  123. */
  124. public function getCode($mobile, $type)
  125. {
  126. $cacheKey = self::$cachePrefix . $type . ':' . $mobile;
  127. return $this->getCacheRepo()->get($cacheKey, null);
  128. }
  129. /**
  130. * 检查Code
  131. * @param int|string $code 校验码
  132. * @param int|string $mobile 手机号
  133. * @param string $type 类型
  134. * @return bool
  135. */
  136. public function checkCode($code, $mobile, $type)
  137. {
  138. $cacheCode = $this->getCode($mobile, $type);
  139. if ($cacheCode) {
  140. $cacheCode = (string)$cacheCode;
  141. if ($cacheCode === (string)($code)) {
  142. // 认证了就马上删除.
  143. $this->deleteCode($mobile, $type);
  144. return true;
  145. }
  146. }
  147. return false;
  148. }
  149. /**
  150. * 检查Code但不删除code
  151. * @param int|string $code 校验码
  152. * @param int|string $mobile 手机号
  153. * @param string $type 类型
  154. * @return bool
  155. */
  156. public function checkCodeNotDelete($code, $mobile, $type)
  157. {
  158. $cache_code = $this->getCode($mobile, $type);
  159. if ($cache_code) {
  160. $cache_code = (string)$cache_code;
  161. if ($cache_code === (string)($code)) {
  162. return true;
  163. }
  164. }
  165. return false;
  166. }
  167. /**
  168. * 删除Code
  169. * @param int|string $mobile 手机号
  170. * @param string $type 类型
  171. */
  172. public function deleteCode($mobile, $type)
  173. {
  174. $cacheKey = self::$cachePrefix . $type . ':' . $mobile;
  175. $this->getCacheRepo()->forget($cacheKey);
  176. }
  177. /**
  178. * 是否是中国手机号.
  179. * @param string $number
  180. * @return bool
  181. */
  182. public function isChinaMobile($number) :bool
  183. {
  184. return boolval(preg_match(PatternConstant::CHINA_MOBILE, $number));
  185. }
  186. /**
  187. * 获取缓存仓库.
  188. * @return mixed|CacheInterface
  189. */
  190. private function getCacheRepo()
  191. {
  192. if (!$this->cacheRepo instanceof CacheInterface) {
  193. $this->cacheRepo = make(CacheInterface::class);
  194. }
  195. return $this->cacheRepo;
  196. }
  197. /**
  198. * @return \GuzzleHttp\Client|ClientFactory|null
  199. */
  200. public function getHttpClient()
  201. {
  202. if (!$this->httpClient instanceof ClientFactory) {
  203. $arrConfig = $this->getConfigRepo()->gets(['tg_api_token', 'tg_msg_server'], 'sys', []);
  204. $token = data_get($arrConfig, 'tg_api_token', '');
  205. $baseUrl = rtrim(data_get($arrConfig, 'tg_msg_server', ''), '/');
  206. $options = array(
  207. 'base_uri' => $baseUrl,
  208. 'timeout' => 15, // 超时,单位秒.
  209. 'verify' => false, // 是否校验SSL证书.
  210. 'http_errors' => false, // 如果跟随http异常头部是否中断.
  211. 'debug' => false, // 是否启用调试模式.
  212. 'allow_redirects' => false, // 不跟随301/302跳转.
  213. 'headers' => [
  214. 'AUTH-TOKEN' => $token,
  215. ],
  216. );
  217. $this->httpClient = make(ClientFactory::class)->create($options);
  218. }
  219. return $this->httpClient;
  220. }
  221. /**
  222. * @return ConfigRepoistory|mixed
  223. */
  224. private function getConfigRepo()
  225. {
  226. if (!$this->configRepo instanceof ConfigRepoistory) {
  227. $this->configRepo = make(ConfigRepoistory::class);
  228. }
  229. return $this->configRepo;
  230. }
  231. }