ttttt.html 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>Java后台手机验证码处理</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  13. }
  14. body {
  15. background: linear-gradient(135deg, #1e3c72, #2a5298);
  16. color: #333;
  17. line-height: 1.6;
  18. padding: 20px;
  19. min-height: 100vh;
  20. }
  21. .container {
  22. max-width: 1200px;
  23. margin: 0 auto;
  24. }
  25. header {
  26. text-align: center;
  27. margin-bottom: 40px;
  28. color: white;
  29. padding: 30px;
  30. background: rgba(255, 255, 255, 0.1);
  31. border-radius: 15px;
  32. box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
  33. backdrop-filter: blur(10px);
  34. }
  35. h1 {
  36. font-size: 2.8rem;
  37. margin-bottom: 15px;
  38. text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
  39. }
  40. .subtitle {
  41. font-size: 1.2rem;
  42. opacity: 0.9;
  43. max-width: 800px;
  44. margin: 0 auto;
  45. }
  46. .content {
  47. display: grid;
  48. grid-template-columns: 1fr 1fr;
  49. gap: 30px;
  50. margin-bottom: 40px;
  51. }
  52. @media (max-width: 768px) {
  53. .content {
  54. grid-template-columns: 1fr;
  55. }
  56. }
  57. .card {
  58. background: white;
  59. border-radius: 12px;
  60. padding: 25px;
  61. box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
  62. transition: transform 0.3s, box-shadow 0.3s;
  63. }
  64. .card:hover {
  65. transform: translateY(-5px);
  66. box-shadow: 0 15px 30px rgba(0, 0, 0, 0.2);
  67. }
  68. .card h2 {
  69. color: #1e3c72;
  70. margin-bottom: 20px;
  71. padding-bottom: 10px;
  72. border-bottom: 2px solid #4a90e2;
  73. display: flex;
  74. align-items: center;
  75. }
  76. .card h2 i {
  77. margin-right: 10px;
  78. font-size: 1.5rem;
  79. }
  80. .input-group {
  81. margin-bottom: 20px;
  82. }
  83. label {
  84. display: block;
  85. margin-bottom: 8px;
  86. font-weight: 600;
  87. color: #444;
  88. }
  89. input, textarea, select {
  90. width: 100%;
  91. padding: 12px;
  92. border: 1px solid #ddd;
  93. border-radius: 6px;
  94. font-size: 16px;
  95. transition: all 0.3s;
  96. }
  97. textarea {
  98. min-height: 120px;
  99. resize: vertical;
  100. font-family: 'Courier New', monospace;
  101. }
  102. input:focus, textarea:focus, select:focus {
  103. border-color: #4a90e2;
  104. outline: none;
  105. box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
  106. }
  107. .btn-group {
  108. display: flex;
  109. gap: 10px;
  110. margin-top: 20px;
  111. }
  112. button {
  113. padding: 12px 20px;
  114. border: none;
  115. border-radius: 6px;
  116. font-size: 16px;
  117. font-weight: 600;
  118. cursor: pointer;
  119. transition: all 0.3s;
  120. flex: 1;
  121. display: flex;
  122. align-items: center;
  123. justify-content: center;
  124. }
  125. button i {
  126. margin-right: 8px;
  127. }
  128. .send-btn {
  129. background: #4a90e2;
  130. color: white;
  131. }
  132. .send-btn:hover {
  133. background: #3a7bc8;
  134. transform: translateY(-2px);
  135. }
  136. .verify-btn {
  137. background: #2ecc71;
  138. color: white;
  139. }
  140. .verify-btn:hover {
  141. background: #27ae60;
  142. transform: translateY(-2px);
  143. }
  144. .reset-btn {
  145. background: #95a5a6;
  146. color: white;
  147. }
  148. .reset-btn:hover {
  149. background: #7f8c8d;
  150. transform: translateY(-2px);
  151. }
  152. .code-block {
  153. background: #2d3748;
  154. color: #e2e8f0;
  155. padding: 25px;
  156. border-radius: 10px;
  157. font-family: 'Consolas', 'Courier New', monospace;
  158. overflow-x: auto;
  159. margin-top: 20px;
  160. box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3);
  161. line-height: 1.5;
  162. }
  163. .code-block h3 {
  164. color: #4a90e2;
  165. margin-bottom: 15px;
  166. font-size: 1.3rem;
  167. }
  168. .code-comment {
  169. color: #a0aec0;
  170. font-style: italic;
  171. }
  172. .code-keyword {
  173. color: #63b3ed;
  174. }
  175. .code-string {
  176. color: #68d391;
  177. }
  178. .code-method {
  179. color: #fbb6ce;
  180. }
  181. .code-class {
  182. color: #f6ad55;
  183. }
  184. .code-number {
  185. color: #f6e05e;
  186. }
  187. .explanation {
  188. margin-top: 40px;
  189. background: white;
  190. border-radius: 12px;
  191. padding: 30px;
  192. box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
  193. }
  194. .explanation h2 {
  195. color: #1e3c72;
  196. margin-bottom: 20px;
  197. padding-bottom: 10px;
  198. border-bottom: 2px solid #4a90e2;
  199. }
  200. .info-box {
  201. background: #e8f4fd;
  202. border-left: 4px solid #4a90e2;
  203. padding: 20px;
  204. margin: 20px 0;
  205. border-radius: 0 10px 10px 0;
  206. }
  207. .warning-box {
  208. background: #fff5f5;
  209. border-left: 4px solid #e74c3c;
  210. padding: 20px;
  211. margin: 20px 0;
  212. border-radius: 0 10px 10px 0;
  213. }
  214. footer {
  215. text-align: center;
  216. margin-top: 40px;
  217. color: white;
  218. padding: 20px;
  219. opacity: 0.9;
  220. }
  221. .result {
  222. margin-top: 20px;
  223. padding: 15px;
  224. border-radius: 8px;
  225. display: none;
  226. }
  227. .success {
  228. border-left: 4px solid #2ecc71;
  229. background: #f0fff4;
  230. }
  231. .error {
  232. border-left: 4px solid #e74c3c;
  233. background: #fff5f5;
  234. }
  235. .response-area {
  236. margin-top: 20px;
  237. }
  238. .response-area h3 {
  239. margin-bottom: 10px;
  240. color: #444;
  241. }
  242. .response-content {
  243. background: #f8f9fa;
  244. border: 1px solid #e9ecef;
  245. border-radius: 6px;
  246. padding: 15px;
  247. min-height: 150px;
  248. font-family: 'Courier New', monospace;
  249. white-space: pre-wrap;
  250. overflow-x: auto;
  251. }
  252. .verification-form {
  253. display: flex;
  254. flex-direction: column;
  255. gap: 15px;
  256. }
  257. .code-input-group {
  258. display: flex;
  259. gap: 10px;
  260. align-items: center;
  261. }
  262. .code-input {
  263. flex: 1;
  264. }
  265. .countdown {
  266. color: #e74c3c;
  267. font-weight: bold;
  268. min-width: 100px;
  269. text-align: center;
  270. }
  271. .step {
  272. display: flex;
  273. margin-bottom: 30px;
  274. background: white;
  275. border-radius: 10px;
  276. overflow: hidden;
  277. box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
  278. }
  279. .step-number {
  280. background: #4a90e2;
  281. color: white;
  282. font-size: 2rem;
  283. font-weight: bold;
  284. padding: 20px;
  285. display: flex;
  286. align-items: center;
  287. justify-content: center;
  288. min-width: 80px;
  289. }
  290. .step-content {
  291. padding: 20px;
  292. flex: 1;
  293. }
  294. .flow-diagram {
  295. display: flex;
  296. flex-direction: column;
  297. align-items: center;
  298. margin: 30px 0;
  299. }
  300. .flow-step {
  301. background: white;
  302. border-radius: 8px;
  303. padding: 15px;
  304. margin: 10px 0;
  305. width: 80%;
  306. text-align: center;
  307. box-shadow: 0 3px 10px rgba(0, 0, 0, 0.1);
  308. position: relative;
  309. }
  310. .flow-step:not(:last-child):after {
  311. content: "↓";
  312. position: absolute;
  313. bottom: -25px;
  314. left: 50%;
  315. transform: translateX(-50%);
  316. font-size: 20px;
  317. color: #4a90e2;
  318. }
  319. .method-comparison {
  320. display: grid;
  321. grid-template-columns: 1fr 1fr;
  322. gap: 20px;
  323. margin-top: 20px;
  324. }
  325. @media (max-width: 768px) {
  326. .method-comparison {
  327. grid-template-columns: 1fr;
  328. }
  329. }
  330. .method-card {
  331. background: #f8f9fa;
  332. border-radius: 8px;
  333. padding: 20px;
  334. border: 1px solid #e9ecef;
  335. }
  336. .method-card h4 {
  337. color: #4a90e2;
  338. margin-bottom: 10px;
  339. }
  340. </style>
  341. <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
  342. </head>
  343. <body>
  344. <div class="container">
  345. <header>
  346. <h1>Java后台手机验证码处理</h1>
  347. <p class="subtitle">演示Java后台处理手机验证码的完整流程,包括生成、发送、验证和安全性考虑</p>
  348. </header>
  349. <div class="content">
  350. <div class="card">
  351. <h2><i class="fas fa-mobile-alt"></i> 验证码测试</h2>
  352. <div class="verification-form">
  353. <div class="input-group">
  354. <label for="phone">手机号码:</label>
  355. <input type="tel" id="phone" placeholder="请输入11位手机号码" maxlength="11" value="13800138000">
  356. </div>
  357. <div class="input-group">
  358. <label for="code">验证码:</label>
  359. <div class="code-input-group">
  360. <input type="text" id="code" class="code-input" placeholder="请输入6位验证码" maxlength="6">
  361. <button class="send-btn" id="send-btn">发送验证码</button>
  362. </div>
  363. <div class="countdown" id="countdown"></div>
  364. </div>
  365. <div class="btn-group">
  366. <button class="verify-btn" onclick="verifyCode()"><i class="fas fa-check-circle"></i> 验证验证码</button>
  367. <button class="reset-btn" onclick="resetForm()"><i class="fas fa-redo"></i> 重置</button>
  368. </div>
  369. </div>
  370. <div class="response-area">
  371. <h3>验证结果:</h3>
  372. <div class="response-content" id="verify-result">
  373. 验证结果将显示在这里...
  374. </div>
  375. </div>
  376. <div id="verify-status" class="result"></div>
  377. </div>
  378. <div class="card">
  379. <h2><i class="fas fa-code"></i> Java 核心代码</h2>
  380. <div class="code-block">
  381. <h3>验证码服务类</h3>
  382. <pre><code><span class="code-keyword">import</span> org.springframework.stereotype.Service;
  383. <span class="code-keyword">import</span> java.util.Random;
  384. <span class="code-keyword">import</span> java.util.concurrent.ConcurrentHashMap;
  385. <span class="code-keyword">import</span> java.util.concurrent.TimeUnit;
  386. <span class="code-comment">/**
  387. * 手机验证码服务
  388. */</span>
  389. @Service
  390. <span class="code-keyword">public class</span> <span class="code-class">SmsCodeService</span> {
  391. <span class="code-comment">// 存储验证码,key: 手机号, value: 验证码和过期时间</span>
  392. <span class="code-keyword">private final</span> ConcurrentHashMap&lt;String, CodeInfo&gt; codeCache =
  393. <span class="code-keyword">new</span> ConcurrentHashMap&lt;&gt;();
  394. <span class="code-comment">// 验证码有效期(分钟)</span>
  395. <span class="code-keyword">private static final int</span> CODE_EXPIRE_MINUTES = <span class="code-number">5</span>;
  396. <span class="code-comment">// 验证码长度</span>
  397. <span class="code-keyword">private static final int</span> CODE_LENGTH = <span class="code-number">6</span>;
  398. <span class="code-comment">// 发送间隔(秒)</span>
  399. <span class="code-keyword">private static final int</span> SEND_INTERVAL_SECONDS = <span
  400. class="code-number">60</span>;
  401. <span class="code-comment">/**
  402. * 生成验证码
  403. */</span>
  404. <span class="code-keyword">public</span> String <span class="code-method">generateCode</span>() {
  405. Random random = <span class="code-keyword">new</span> Random();
  406. StringBuilder sb = <span class="code-keyword">new</span> StringBuilder();
  407. <span class="code-keyword">for</span> (<span class="code-keyword">int</span> i = <span
  408. class="code-number">0</span>; i < CODE_LENGTH; i++) {
  409. sb.append(random.nextInt(<span class="code-number">10</span>));
  410. }
  411. <span class="code-keyword">return</span> sb.toString();
  412. }
  413. <span class="code-comment">/**
  414. * 发送验证码
  415. */</span>
  416. <span class="code-keyword">public</span> boolean <span class="code-method">sendCode</span>(String phone) {
  417. <span class="code-comment">// 检查发送频率</span>
  418. <span class="code-keyword">if</span> (isTooFrequent(phone)) {
  419. <span class="code-keyword">return false</span>;
  420. }
  421. <span class="code-comment">// 生成验证码</span>
  422. String code = generateCode();
  423. <span class="code-comment">// 存储验证码信息</span>
  424. CodeInfo codeInfo = <span class="code-keyword">new</span> CodeInfo(code,
  425. System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(CODE_EXPIRE_MINUTES));
  426. codeCache.put(phone, codeInfo);
  427. <span class="code-comment">// 记录发送时间</span>
  428. recordSendTime(phone);
  429. <span class="code-comment">// 调用短信服务发送验证码(这里模拟发送)</span>
  430. <span class="code-keyword">return</span> sendSms(phone, code);
  431. }
  432. <span class="code-comment">/**
  433. * 验证验证码
  434. */</span>
  435. <span class="code-keyword">public</span> boolean <span class="code-method">verifyCode</span>(String phone, String inputCode) {
  436. CodeInfo codeInfo = codeCache.get(phone);
  437. <span class="code-keyword">if</span> (codeInfo == <span class="code-keyword">null</span>) {
  438. <span class="code-keyword">return false</span>; <span class="code-comment">// 验证码不存在</span>
  439. }
  440. <span class="code-comment">// 检查是否过期</span>
  441. <span class="code-keyword">if</span> (System.currentTimeMillis() > codeInfo.getExpireTime()) {
  442. codeCache.remove(phone); <span class="code-comment">// 移除过期验证码</span>
  443. <span class="code-keyword">return false</span>;
  444. }
  445. <span class="code-comment">// 验证码是否正确</span>
  446. <span class="code-keyword">if</span> (codeInfo.getCode().equals(inputCode)) {
  447. codeCache.remove(phone); <span class="code-comment">// 验证成功后移除验证码</span>
  448. <span class="code-keyword">return true</span>;
  449. }
  450. <span class="code-keyword">return false</span>;
  451. }
  452. <span class="code-comment">/**
  453. * 检查发送频率
  454. */</span>
  455. <span class="code-keyword">private</span> boolean <span class="code-method">isTooFrequent</span>(String phone) {
  456. <span class="code-comment">// 实现发送频率检查逻辑</span>
  457. <span class="code-comment">// 这里简化实现,实际应该存储发送时间并检查</span>
  458. <span class="code-keyword">return false</span>;
  459. }
  460. <span class="code-comment">/**
  461. * 记录发送时间
  462. */</span>
  463. <span class="code-keyword">private void</span> <span class="code-method">recordSendTime</span>(String phone) {
  464. <span class="code-comment">// 记录发送时间,用于频率控制</span>
  465. }
  466. <span class="code-comment">/**
  467. * 发送短信(模拟实现)
  468. */</span>
  469. <span class="code-keyword">private</span> boolean <span class="code-method">sendSms</span>(String phone, String code) {
  470. <span class="code-comment">// 这里模拟发送短信</span>
  471. System.out.println(<span class="code-string">"发送短信到 "</span> + phone + <span
  472. class="code-string">": 验证码是 "</span> + code);
  473. <span class="code-comment">// 实际项目中这里会调用短信服务商的API</span>
  474. <span class="code-comment">// 例如阿里云短信服务、腾讯云短信服务等</span>
  475. <span class="code-keyword">return true</span>;
  476. }
  477. <span class="code-comment">/**
  478. * 验证码信息类
  479. */</span>
  480. <span class="code-keyword">private static class</span> <span class="code-class">CodeInfo</span> {
  481. <span class="code-keyword">private final</span> String code;
  482. <span class="code-keyword">private final long</span> expireTime;
  483. <span class="code-keyword">public</span> <span class="code-class">CodeInfo</span>(String code, long expireTime) {
  484. <span class="code-keyword">this</span>.code = code;
  485. <span class="code-keyword">this</span>.expireTime = expireTime;
  486. }
  487. <span class="code-keyword">public</span> String <span class="code-method">getCode</span>() {
  488. <span class="code-keyword">return</span> code;
  489. }
  490. <span class="code-keyword">public long</span> <span class="code-method">getExpireTime</span>() {
  491. <span class="code-keyword">return</span> expireTime;
  492. }
  493. }
  494. }</code></pre>
  495. </div>
  496. </div>
  497. </div>
  498. <div class="step">
  499. <div class="step-number">1</div>
  500. <div class="step-content">
  501. <h3>验证码生成与发送</h3>
  502. <p>Java后台生成6位数字验证码,并通过短信服务发送给用户。</p>
  503. <div class="code-block">
  504. <pre><code><span class="code-comment">// 生成6位数字验证码</span>
  505. <span class="code-keyword">public</span> String <span class="code-method">generateCode</span>() {
  506. Random random = <span class="code-keyword">new</span> Random();
  507. StringBuilder sb = <span class="code-keyword">new</span> StringBuilder();
  508. <span class="code-keyword">for</span> (<span class="code-keyword">int</span> i = <span class="code-number">0</span>; i < <span
  509. class="code-number">6</span>; i++) {
  510. sb.append(random.nextInt(<span class="code-number">10</span>));
  511. }
  512. <span class="code-keyword">return</span> sb.toString();
  513. }
  514. <span class="code-comment">// 发送验证码</span>
  515. <span class="code-keyword">public</span> boolean <span class="code-method">sendCode</span>(String phone) {
  516. String code = generateCode();
  517. <span class="code-comment">// 存储验证码(设置5分钟过期)</span>
  518. codeCache.put(phone, <span class="code-keyword">new</span> CodeInfo(code,
  519. System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(<span class="code-number">5</span>)));
  520. <span class="code-comment">// 调用短信服务发送</span>
  521. <span class="code-keyword">return</span> sendSms(phone, code);
  522. }</code></pre>
  523. </div>
  524. </div>
  525. </div>
  526. <div class="step">
  527. <div class="step-number">2</div>
  528. <div class="step-content">
  529. <h3>验证码验证</h3>
  530. <p>用户提交验证码后,后台进行验证,包括检查是否存在、是否过期、是否正确。</p>
  531. <div class="code-block">
  532. <pre><code><span class="code-comment">// 验证验证码</span>
  533. <span class="code-keyword">public</span> boolean <span class="code-method">verifyCode</span>(String phone, String inputCode) {
  534. CodeInfo codeInfo = codeCache.get(phone);
  535. <span class="code-comment">// 检查验证码是否存在</span>
  536. <span class="code-keyword">if</span> (codeInfo == <span class="code-keyword">null</span>) {
  537. <span class="code-keyword">return false</span>;
  538. }
  539. <span class="code-comment">// 检查是否过期</span>
  540. <span class="code-keyword">if</span> (System.currentTimeMillis() > codeInfo.getExpireTime()) {
  541. codeCache.remove(phone);
  542. <span class="code-keyword">return false</span>;
  543. }
  544. <span class="code-comment">// 检查验证码是否正确</span>
  545. <span class="code-keyword">if</span> (codeInfo.getCode().equals(inputCode)) {
  546. codeCache.remove(phone); <span class="code-comment">// 验证成功后移除</span>
  547. <span class="code-keyword">return true</span>;
  548. }
  549. <span class="code-keyword">return false</span>;
  550. }</code></pre>
  551. </div>
  552. </div>
  553. </div>
  554. <div class="flow-diagram">
  555. <div class="flow-step">用户请求发送验证码</div>
  556. <div class="flow-step">后端生成验证码并存储</div>
  557. <div class="flow-step">调用短信服务发送验证码</div>
  558. <div class="flow-step">用户输入验证码提交</div>
  559. <div class="flow-step">后端验证验证码</div>
  560. <div class="flow-step">返回验证结果</div>
  561. </div>
  562. <div class="explanation">
  563. <h2><i class="fas fa-info-circle"></i> Java验证码处理详解</h2>
  564. <div class="method-comparison">
  565. <div class="method-card">
  566. <h4>存储方案选择</h4>
  567. <ul>
  568. <li><strong>内存存储</strong>:简单快速,适合单机应用</li>
  569. <li><strong>Redis</strong>:分布式支持,自动过期,性能优秀</li>
  570. <li><strong>数据库</strong>:持久化存储,适合重要业务场景</li>
  571. <li><strong>Ehcache</strong>:本地缓存,性能较好</li>
  572. </ul>
  573. </div>
  574. <div class="method-card">
  575. <h4>安全性考虑</h4>
  576. <ul>
  577. <li><strong>频率限制</strong>:防止恶意发送验证码</li>
  578. <li><strong>IP限制</strong>:限制同一IP的发送次数</li>
  579. <li><strong>验证码复杂度</strong>:适当长度的数字或字母数字组合</li>
  580. <li><strong>时效性</strong>:设置合理的过期时间</li>
  581. </ul>
  582. </div>
  583. </div>
  584. <div class="info-box">
  585. <h3><i class="fas fa-lightbulb"></i> 最佳实践</h3>
  586. <ul>
  587. <li>使用线程安全的ConcurrentHashMap或Redis存储验证码</li>
  588. <li>设置合理的验证码有效期(通常5-10分钟)</li>
  589. <li>实现发送频率限制,防止短信轰炸</li>
  590. <li>验证成功后立即删除验证码,防止重复使用</li>
  591. <li>记录发送日志,便于排查问题</li>
  592. <li>使用专业的短信服务商,确保送达率</li>
  593. </ul>
  594. </div>
  595. <div class="warning-box">
  596. <h3><i class="fas fa-exclamation-triangle"></i> 注意事项</h3>
  597. <ul>
  598. <li>不要在前端生成验证码,必须在后端生成</li>
  599. <li>验证码不能过于简单,避免被暴力破解</li>
  600. <li>生产环境不要使用System.out打印验证码</li>
  601. <li>考虑短信服务商调用失败的重试机制</li>
  602. <li>注意验证码存储的内存泄漏问题</li>
  603. </ul>
  604. </div>
  605. <h3><i class="fas fa-cogs"></i> 完整Controller示例</h3>
  606. <div class="code-block">
  607. <pre><code><span class="code-keyword">import</span> org.springframework.beans.factory.annotation.Autowired;
  608. <span class="code-keyword">import</span> org.springframework.web.bind.annotation.*;
  609. @RestController
  610. @RequestMapping(<span class="code-string">"/api/sms"</span>)
  611. <span class="code-keyword">public class</span> <span class="code-class">SmsController</span> {
  612. @Autowired
  613. <span class="code-keyword">private</span> SmsCodeService smsCodeService;
  614. <span class="code-comment">/**
  615. * 发送验证码
  616. */</span>
  617. @PostMapping(<span class="code-string">"/send"</span>)
  618. <span class="code-keyword">public</span> ApiResult&lt;String&gt; <span class="code-method">sendCode</span>(@RequestBody SendCodeRequest request) {
  619. <span class="code-comment">// 验证手机号格式</span>
  620. <span class="code-keyword">if</span> (!isValidPhone(request.getPhone())) {
  621. <span class="code-keyword">return</span> ApiResult.error(<span class="code-string">"手机号格式不正确"</span>);
  622. }
  623. <span class="code-comment">// 发送验证码</span>
  624. boolean success = smsCodeService.sendCode(request.getPhone());
  625. <span class="code-keyword">if</span> (success) {
  626. <span class="code-keyword">return</span> ApiResult.success(<span class="code-string">"验证码发送成功"</span>);
  627. } <span class="code-keyword">else</span> {
  628. <span class="code-keyword">return</span> ApiResult.error(<span class="code-string">"验证码发送失败,请稍后重试"</span>);
  629. }
  630. }
  631. <span class="code-comment">/**
  632. * 验证验证码
  633. */</span>
  634. @PostMapping(<span class="code-string">"/verify"</span>)
  635. <span class="code-keyword">public</span> ApiResult&lt;String&gt; <span class="code-method">verifyCode</span>(@RequestBody VerifyCodeRequest request) {
  636. boolean isValid = smsCodeService.verifyCode(request.getPhone(), request.getCode());
  637. <span class="code-keyword">if</span> (isValid) {
  638. <span class="code-keyword">return</span> ApiResult.success(<span class="code-string">"验证码正确"</span>);
  639. } <span class="code-keyword">else</span> {
  640. <span class="code-keyword">return</span> ApiResult.error(<span class="code-string">"验证码错误或已过期"</span>);
  641. }
  642. }
  643. <span class="code-comment">/**
  644. * 验证手机号格式
  645. */</span>
  646. <span class="code-keyword">private</span> boolean <span class="code-method">isValidPhone</span>(String phone) {
  647. <span class="code-comment">// 简单的手机号格式验证</span>
  648. <span class="code-keyword">return</span> phone != <span class="code-keyword">null</span> && phone.matches(<span
  649. class="code-string">"^1[3-9]\\d{9}$"</span>);
  650. }
  651. <span class="code-comment">/**
  652. * 发送验证码请求参数
  653. */</span>
  654. <span class="code-keyword">public static class</span> <span class="code-class">SendCodeRequest</span> {
  655. <span class="code-keyword">private</span> String phone;
  656. <span class="code-comment">// getter和setter</span>
  657. <span class="code-keyword">public</span> String <span class="code-method">getPhone</span>() { <span
  658. class="code-keyword">return</span> phone; }
  659. <span class="code-keyword">public void</span> <span class="code-method">setPhone</span>(String phone) { <span
  660. class="code-keyword">this</span>.phone = phone; }
  661. }
  662. <span class="code-comment">/**
  663. * 验证验证码请求参数
  664. */</span>
  665. <span class="code-keyword">public static class</span> <span class="code-class">VerifyCodeRequest</span> {
  666. <span class="code-keyword">private</span> String phone;
  667. <span class="code-keyword">private</span> String code;
  668. <span class="code-comment">// getter和setter</span>
  669. <span class="code-keyword">public</span> String <span class="code-method">getPhone</span>() { <span
  670. class="code-keyword">return</span> phone; }
  671. <span class="code-keyword">public void</span> <span class="code-method">setPhone</span>(String phone) { <span
  672. class="code-keyword">this</span>.phone = phone; }
  673. <span class="code-keyword">public</span> String <span class="code-method">getCode</span>() { <span
  674. class="code-keyword">return</span> code; }
  675. <span class="code-keyword">public void</span> <span class="code-method">setCode</span>(String code) { <span
  676. class="code-keyword">this</span>.code = code; }
  677. }
  678. }</code></pre>
  679. </div>
  680. </div>
  681. <footer>
  682. <p>© 2023 Java后台手机验证码处理 | 注意:生产环境需要考虑安全性和性能优化</p>
  683. </footer>
  684. </div>
  685. <script>
  686. // 验证码相关变量
  687. let countdown = 0;
  688. let countdownInterval;
  689. let generatedCode = '';
  690. // 发送验证码
  691. document.getElementById('send-btn').addEventListener('click', function () {
  692. const phone = document.getElementById('phone').value.trim();
  693. const statusDiv = document.getElementById('verify-status');
  694. if (!phone) {
  695. showResult(statusDiv, '请输入手机号码', 'error');
  696. return;
  697. }
  698. if (!/^1[3-9]\d{9}$/.test(phone)) {
  699. showResult(statusDiv, '手机号格式不正确', 'error');
  700. return;
  701. }
  702. // 生成验证码
  703. generatedCode = generateCode();
  704. // 开始倒计时
  705. startCountdown();
  706. // 模拟发送验证码
  707. setTimeout(() => {
  708. showResult(statusDiv, `验证码已发送到 ${phone},验证码:${generatedCode}(控制台查看)`, 'success');
  709. console.log(`模拟发送验证码到 ${phone}: ${generatedCode}`);
  710. }, 1000);
  711. });
  712. // 验证验证码
  713. function verifyCode() {
  714. const phone = document.getElementById('phone').value.trim();
  715. const inputCode = document.getElementById('code').value.trim();
  716. const resultDiv = document.getElementById('verify-result');
  717. const statusDiv = document.getElementById('verify-status');
  718. if (!phone || !inputCode) {
  719. showResult(statusDiv, '请输入手机号和验证码', 'error');
  720. return;
  721. }
  722. if (inputCode === generatedCode) {
  723. resultDiv.textContent = '验证成功!验证码正确。';
  724. resultDiv.style.color = '#2ecc71';
  725. showResult(statusDiv, '验证码正确', 'success');
  726. // 验证成功后清除验证码
  727. generatedCode = '';
  728. } else {
  729. resultDiv.textContent = '验证失败!验证码错误。';
  730. resultDiv.style.color = '#e74c3c';
  731. showResult(statusDiv, '验证码错误', 'error');
  732. }
  733. }
  734. // 生成6位数字验证码
  735. function generateCode() {
  736. let code = '';
  737. for (let i = 0; i < 6; i++) {
  738. code += Math.floor(Math.random() * 10);
  739. }
  740. return code;
  741. }
  742. // 开始倒计时
  743. function startCountdown() {
  744. const sendBtn = document.getElementById('send-btn');
  745. const countdownDiv = document.getElementById('countdown');
  746. countdown = 60;
  747. sendBtn.disabled = true;
  748. sendBtn.textContent = '发送中...';
  749. countdownInterval = setInterval(() => {
  750. countdown--;
  751. countdownDiv.textContent = `${countdown}s`;
  752. if (countdown <= 0) {
  753. clearInterval(countdownInterval);
  754. sendBtn.disabled = false;
  755. sendBtn.textContent = '发送验证码';
  756. countdownDiv.textContent = '';
  757. }
  758. }, 1000);
  759. }
  760. // 显示结果消息
  761. function showResult(element, message, type) {
  762. element.textContent = message;
  763. element.className = 'result ' + type;
  764. element.style.display = 'block';
  765. // 3秒后自动隐藏消息
  766. setTimeout(() => {
  767. element.style.display = 'none';
  768. }, 3000);
  769. }
  770. // 重置表单
  771. function resetForm() {
  772. document.getElementById('phone').value = '13800138000';
  773. document.getElementById('code').value = '';
  774. document.getElementById('verify-result').textContent = '验证结果将显示在这里...';
  775. document.getElementById('verify-result').style.color = '#333';
  776. document.getElementById('verify-status').style.display = 'none';
  777. // 清除倒计时
  778. if (countdownInterval) {
  779. clearInterval(countdownInterval);
  780. }
  781. const sendBtn = document.getElementById('send-btn');
  782. sendBtn.disabled = false;
  783. sendBtn.textContent = '发送验证码';
  784. document.getElementById('countdown').textContent = '';
  785. generatedCode = '';
  786. }
  787. </script>
  788. </body>
  789. </html>