yikart/AiToEarn · warning · AppHttpException
ErrHttpBack.err_user_code_had
ErrHttpBack.err_user_code_had
Error message
err_user_code_had
What it means
AppHttpException with ErrHttpBack.err_user_code_had (errCode 40019, message '验证码未过期'). Thrown by LoginService.postPhoneRegisterCode (login.service.ts:34) when a verification code for this phone is still cached in Redis under key `Password:<phone>`, i.e. the previous code has not expired (5-minute TTL). Rate-limits SMS code requests.
Source
Thrown at project/aitoearn-electron/server/src/user/login.service.ts:34
Password = 'phone_register_code',
Code = 'phone_login_code',
PhoneAuth = 'phone_login_auth_token', // 手机号一键登录
}
@Injectable()
export class LoginService {
constructor(
private readonly redisService: RedisService,
private readonly alicloudSmsService: AlicloudSmsService,
) {}
/**
* 发送手机号注册的验证码
* @param phone
*/
async postPhoneRegisterCode(phone: string) {
const cacheKey = `${LoginTypeCacheKey.Password}:${phone}`;
let code = await this.redisService.get(cacheKey);
if (code) throw new AppHttpException(ErrHttpBack.err_user_code_had);
code = getRandomString(6, true);
const res = await this.alicloudSmsService.sendLoginSms(phone, code);
if (process.env.NODE_ENV === 'production') {
if (!res) throw new AppHttpException(ErrHttpBack.err_user_code_send_fail);
}
this.redisService.setKey(cacheKey, code, 60 * 5);
console.log('发送短信成功', code);
return process.env.NODE_ENV === 'production' ? res : code;
}
/**
* 发送手机号登录的验证码
* @param phone
*/View on GitHub (pinned to d3aa8bea5b)
Solutions
- Wait until the existing code expires (60*5s TTL from issue time) before requesting a new one
- Use the still-valid cached code instead of requesting a new one
- In tests, delete the Redis key `Password:<phone>` (or flush the test Redis) between runs
- Disable the resend button client-side for the 5-minute cooldown window
Example fix
// before
await api.postPhoneRegisterCode(phone) // throws 40019 if code cached
// after
try {
await api.postPhoneRegisterCode(phone)
} catch (e) {
if (e.errCode === '40019') { /* reuse existing code or wait for TTL */ }
} Defensive patterns
Strategy: retry
Validate before calling
const cooldownKey = `sms-cooldown:${phone}`
if (await cache.get(cooldownKey)) throw new Error('Please wait for the previous code to expire')
await api.postPhoneRegisterCode(phone)
await cache.set(cooldownKey, '1', 300) Type guard
null
Try / catch
try {
await api.postPhoneRegisterCode(phone)
} catch (e) {
if (e?.errCode === '40019') {
// code still valid: prompt user to check SMS or wait out the 5-minute TTL
} else throw e
} Prevention
- Enforce a client-side 5-minute resend cooldown matching the server TTL
- Clear the Redis key `Password:<phone>` between test runs
- Reuse the still-valid code instead of requesting a new one
When it happens
Trigger: POST phone register code for a phone number whose Redis key `${LoginTypeCacheKey.Password}:${phone}` still holds an unexpired code — requesting a second code within the 5-minute window.
Common situations: User clicks 'send code' twice; automated test reuses the same phone number without clearing Redis; Redis TTL not respected by a client that retries on UI error; shared test phone across environments with a common Redis.
Related errors
- ErrHttpBack.err_user_code_nohad
- ErrHttpBack.err_user_code_nohad
- TOO_MANY_REQUESTS
- ResponseCode.ChannelAuthSessionInvalid
- ChannelPlatformApiFailed
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/8be7bd19113ed848.
Report an issue: GitHub.