input 失焦 (blur 事件) → 發送 AJAX 請求後端 → 後端查詢數據庫 → 返回結果 → 前端顯示提示
<input type="text" id="username" placeholder="請輸入用戶名">
<span id="msg" style="color: red; font-size: 12px;"></span>
<script>
document.getElementById('username').addEventListener('blur', function() {
const username = this.value.trim();
const msgEl = document.getElementById('msg');
if (!username) {
msgEl.textContent = '用戶名不能為空';
return;
}
// 防抖:避免頻繁請求(可選)
clearTimeout(this.debounceTimer);
this.debounceTimer = setTimeout(() => {
fetch('/api/check-username', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username })
})
.then(res => res.json())
.then(data => {
if (data.exists) {
msgEl.textContent = '用戶名已存在';
msgEl.style.color = 'red';
} else {
msgEl.textContent = '用戶名可用';
msgEl.style.color = 'green';
}
})
.catch(err => {
msgEl.textContent = '檢測失敗,請重試';
msgEl.style.color = 'red';
console.error(err);
});
}, 300); // 300ms 防抖
});
</script>
$('#username').on('blur', function() {
const $this = $(this);
const username = $this.val().trim();
const $msg = $('#msg');
if (!username) return $msg.text('用戶名不能為空').css('color', 'red');
clearTimeout($this.data('timer'));
$this.data('timer', setTimeout(() => {
$.post('/api/check-username', { username }, function(data) {
if (data.exists) {
$msg.text('用戶名已存在').css('color', 'red');
} else {
$msg.text('用戶名可用').css('color', 'green');
}
}, 'json').fail(() => {
$msg.text('檢測失敗').css('color', 'red');
});
}, 300));
});
@RestController
@RequestMapping("/api")
public class UserController {
@Autowired
private UserRepository userRepository; // JPA Repository
@PostMapping("/check-username")
public Map<String, Object> checkUsername(@RequestBody Map<String, String> param) {
String username = param.get("username");
boolean exists = userRepository.existsByUsername(username);
return Map.of("exists", exists);
}
}
@Mapper
public interface UserMapper {
@Select("SELECT COUNT(1) FROM users WHERE username = #{username}")
int countByUsername(String username);
}
// Service
public boolean existsByUsername(String username) {
return userMapper.countByUsername(username) > 0;
}
| 優化點 | 方案 |
|---|---|
| 防抖 | setTimeout/clearTimeout 300-500ms |
| 節流 | 限制單位時間請求次數 |
| 緩存 | 前端 Map 緩存已查結果,避免重複請求 |
| 併發控制 | 後端 Redis 分布式鎖防止重複註冊 |
| 安全 | 限流 (IP/用戶)、驗證碼、HTTPS |
| 用戶體驗 | 輸入時實時檢查 (input 事件 + 防抖) + 失焦複查 |