You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Twilio点击提交后无法处理手机号、发送验证码问题

问题原因

一共6个核心错误,直接导致功能完全失效:

  • 前后端接口路径不匹配:前端提交手机号时请求的接口地址是/send-notification,但后端定义的发送验证码路由是/send-verification,请求直接返回404,前端判断response.ok为false,既不会触发验证码发送逻辑,也不会切换到验证码输入表单。
  • 后端未定义Verify服务SID变量:两个Twilio接口中都使用了未声明的verify变量,运行时会直接抛出引用错误,无法调用Twilio的短信服务。
  • 后端发送验证码接口逻辑错误:接口没有等待Twilio接口返回结果就提前发送200状态码,既无法感知Twilio侧的报错(比如账号配置错误、手机号格式非法、余额不足),也可能因为异步时序问题导致请求失败。
  • 前端验证码传参笔误:提交验证码时,传给后端的参数写的是otp: top,top是浏览器内置的全局窗口对象,不是用户输入的验证码值,后端拿不到正确验证码永远校验失败。
  • 静态资源路径配置问题:后端配置的静态资源目录是public文件夹,如果script.js放在views目录下,浏览器会加载不到JS文件,所有表单交互逻辑直接失效。
  • 前端错误处理缺失:仅捕获了网络层错误,没有打印HTTP 4xx/5xx的错误信息,出问题时控制台无明确报错,难以定位问题。
修复步骤

1. 修正后端代码

打开index.js,做如下修改:

  • 补充读取Twilio Verify服务SID的环境变量,确保.env文件中已经配置了TWILIO_VERIFY_SERVICE_SID(值为Twilio控制台创建的Verify服务SID,VA开头)
  • 修正发送验证码接口的异步逻辑,等待Twilio返回结果后再给前端响应
  • 保持后端路由路径不变,后续修改前端请求路径对齐即可

修正后的核心后端代码:

const express = require('express');
const path = require('path');
require('dotenv').config();
const client = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
// 补上Verify服务SID读取
const verify = process.env.TWILIO_VERIFY_SERVICE_SID;
const app = express();
const port = process.env.PORT || 3000;

app.use(express.static(__dirname + '/public'));
app.use(express.urlencoded({extended: true}));
app.use(express.json()); 

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, '/views/index.html'));
});

// 修正发送验证码接口逻辑
app.post('/send-verification', async (req, res) => {
  try {
    const verification = await client.verify.services(verify)
      .verifications
      .create({to: `+${req.body.phoneNumber}`, channel: 'sms'});
    console.log('验证码发送状态:', verification.status);
    res.sendStatus(200);
  } catch (e) {
    console.log('发送验证码报错:', e);
    res.status(500).send(e.message);
  }
});

app.post('/verify-otp', async (req, res) => {
  try {
    const check = await client.verify.services(verify)
      .verificationChecks
      .create({to: `+${req.body.phoneNumber}`, code: req.body.otp});
    res.status(200).send(check);
  } catch (e) {
    console.log('校验验证码报错:', e);
    res.status(500).send(e.message);
  }
});

app.listen(port, () => {
  console.log('Server started at http://localhost:' + port);
});

2. 修正前端JS代码

打开script.js,做如下修改:

  • 将提交手机号的请求路径从/send-notification改为/send-verification,和后端路由对齐
  • 修正验证码提交时的参数笔误,把otp: top改为otp: otp
  • 补充基础错误提示,方便排查问题
  • 将script.js移动到项目根目录的public文件夹下,确保浏览器能正常加载静态资源

修正后的script.js代码:

const phoneForm = document.getElementById('phone-form');
const verifyForm = document.getElementById('verify-form');
const responseText = document.getElementById('response-text');

let phoneNumber;

phoneForm.addEventListener('submit', async e => {
  e.preventDefault();
  phoneNumber = document.getElementById('phone-number-input').value.trim();
  if (!phoneNumber) {
    alert('请输入手机号');
    return;
  }

  try {
    const response = await fetch('/send-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({phoneNumber : phoneNumber })
    });

    if (!response.ok) {
      const errMsg = await response.text();
      alert(`发送验证码失败:${errMsg}`);
      return;
    }

    phoneForm.style.display = 'none';
    verifyForm.style.display = 'block';
  } catch (e) {
    console.log('请求错误:', e);
    alert('网络请求失败,请检查服务是否启动');
  }
});

verifyForm.addEventListener('submit', async e => {
  e.preventDefault();
  const otp = document.getElementById('otp-input').value.trim();
  if (!otp) {
    alert('请输入验证码');
    return;
  }

  const data = { 
    phoneNumber: phoneNumber, 
    otp: otp // 修正笔误,原来的top是错误值
  };

  try {
    const response = await fetch('/verify-otp', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
      body: JSON.stringify(data)
    });
    const check = await response.json();

    if (!response.ok) {
      alert(`校验失败:${response.statusText}`);
      return;
    }

    responseText.innerHTML = check.status === 'approved' ? '验证成功' : '验证失败';
    verifyForm.style.display = 'none';
    responseText.style.display = 'block';  
  } catch (e) {
    console.log('校验请求错误:', e);
    alert('校验请求失败');
  }
});

3. 配置检查

修改完代码后,确认以下配置项正确:

  • .env文件中三个参数TWILIO_ACCOUNT_SID、TWILIO_AUTH_TOKEN、TWILIO_VERIFY_SERVICE_SID均为Twilio控制台复制的正确值,没有多余空格或拼写错误
  • 如果使用Twilio测试账号,需要将接收短信的手机号添加到测试账号的可信手机号列表中,否则无法收到验证码
  • 输入手机号时需要带正确的国家码,比如中国大陆手机号是86开头,格式为8613xxxxxxxxx,不需要手动加+,后端已经做了拼接

内容的提问来源于stack exchange,提问作者amoztar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 09:36:19