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

相同代码本地正常运行但服务器端异常,求排查解决方法

问题:API本地运行正常,部署到服务器后无法工作

本地端响应:
本地端响应

服务器端响应:
服务器端响应

以下是相关业务逻辑代码:

async fullSignup(req, res, next) {
    const adminDetailSchema = Joi.object({
      // admin signUp valildation
      username: Joi.string().min(3).max(30).required().regex(/^\w+$/).messages({
        "string.base": "Username must be a string",
        "string.empty": "Username cannot be empty",
        "string.min": "Username must have at least {{#limit}} characters",
        "string.max": "Username must have at most {{#limit}} characters",
        "string.pattern.base": 
          "Username must only contain letters, numbers, or underscores",
        "any.required": `Username is a required field`,
      }),
      email: Joi.string().email().required().messages({
        "string.base": `Email should be a type of 'text'`,
        "string.empty": `Email cannot be empty`,
        "string.email": `Please enter a valid email address`,
        "any.required": `Email is a required field`,
      }),
      password: Joi.string()
        // .pattern(new RegExp(/^(?=.*[A-Z])(?=.*[!@#$&*])(?=.*[0-9]).{8,}$/))
        .min(5)
        .required()
        .messages({
          // "string.pattern.base": `Password must be between 8 characters and can only contain letters, numbers and 1-capital letter, 1-special character`,
          "string.min": "Password must have at least {{#limit}} characters",
          "string.empty": `Password cannot be empty`,
          "any.required": `Password is a required field`,
        }),
      // admin details valildation
      name: Joi.string().required().messages({
        "string.base": "name filed must be a string",
        "string.empty": "name cannot be empty",
        "any.required": `name is a required field`,
      }),
      company_name: Joi.string().required().messages({
        "string.base": "company_name filed must be a string",
        "string.empty": "company_name cannot be empty",
        "any.required": `company_name is a required field`,
      }),
      phone_number: Joi.string()
        .pattern(/^\+?[1-9]\d{1,9}$/)
        .min(10)
        .message("phone_number length must be at least 10 Digits")
        .required()
        .messages({
          "string.base": "phone_number filed must be a string",
          "string.empty": "phone_number cannot be empty",
          "string.pattern.base": "Please enter a valid phone number",
          "any.required": `phone_number is a required field`,
        }),
      address: Joi.string().required().messages({
        "string.base": "address filed must be a string",
        "string.empty": "address cannot be empty",
        "any.required": `address is a required field`,
      }),
      city: Joi.string().required().messages({
        "string.base": "city filed must be a string",
        "string.empty": "city cannot be empty",
        "any.required": `city is a required field`,
      }),
      pincode: Joi.number().optional().messages({
        "number.base": "pincode filed must be a number",
        // "number.empty": "pincode cannot be empty",
        // "any.required": `pincode is a required field`,
      }),
      image: Joi.string()
        .pattern(/\.(jpg|jpeg|png)$/i)
        // .required()
        .max(5242880) // 5MB in bytes
        .messages({
          "string.pattern.base": "Image must be a valid JPEG, JPG, or PNG file",
          "string.max": "Image size must be less than 5MB",
        }),
    }).options({ abortEarly: false });

    const { error } = adminDetailSchema.validate(req.body);

    if (error) {
      return next(error);
    }

    let {
      email,
      password,
      username,
      name,
      company_name,
      phone_number,
      address,
      city,
      pincode,
    } = req.body;

    // ! signup data

    try {
      // ? check valid email
      let doc = await emailValidator.validate(email);
      if (!doc.valid)
        return next(CustomeErrorHandler.validation("Enter valid email!"));

      // ? query of username validation
      let usernameQuery = await client.query(
        `SELECT username FROM accounts WHERE username = '${username}'`
      );
      if (usernameQuery.rows.length > 0) {
        return next(
          CustomeErrorHandler.alreadyExist("username is already exist")
        );
      }

      // ? query of email validation
      let emailQuery = await client.query(
        `SELECT email FROM accounts WHERE email = '${email}'`
      );
      if (emailQuery.rows.length > 0) {
        return next(CustomeErrorHandler.alreadyExist("email is already exist"));
      }
    } catch (error) {
      return next(error);
    }

    let adminSignup;
    try {
      // * password hash
      const hashedPassword = await bcrypt.hash(password, 10);

      // * role
      // * admin : 1
      // * partner : 0

      // ? query of insert user data
      let query = await client.query(
        `INSERT INTO accounts(username, email, password, role, created_on, updated_on) VALUES('${username}', '${email}', '${hashedPassword}', '${USER_ROLE.ADMIN}', current_timestamp, current_timestamp) RETURNING *`
      );
      adminSignup = query.rows[0];
      delete adminSignup.phone;
      const insertAdminQuery = await client.query(
        `INSERT INTO admins(account_id, name, company_name, phone_number, address, city, pincode, created_on, updated_on) VALUES('${adminSignup.account_id}', '${name}', '${company_name}', '${phone_number}', '${address}', '${city}', '${pincode}', current_timestamp, current_timestamp) RETURNING *`
      );

      document = insertAdminQuery.rows[0];
    } catch (error) {
      return next(error);
    }

    return res.status(200).json({
      apiCode: "WM036?",
      statusCode: 200,
      msg: commonMessages.createSuccfully,
      data: { ...adminSignup, originalPassword: password, ...document },
    });
  },

已重新安装所有依赖库并恢复服务器,但问题仍未解决。


解决方案

1. 修复SQL语法错误与注入风险

你当前直接将用户输入拼接到SQL语句中,一旦输入包含单引号等特殊字符,就会触发SQL语法错误,这大概率是服务器端失败的核心原因。同时这种写法存在严重的SQL注入漏洞,必须改用参数化查询:

// 验证用户名示例
const usernameQuery = await client.query(
  `SELECT username FROM accounts WHERE username = $1`,
  [username] // 参数数组,自动处理字符串转义
);

// 插入用户数据示例
const query = await client.query(
  `INSERT INTO accounts(username, email, password, role, created_on, updated_on) VALUES($1, $2, $3, $4, current_timestamp, current_timestamp) RETURNING *`,
  [username, email, hashedPassword, USER_ROLE.ADMIN]
);

所有涉及用户输入的SQL查询都要改成这种参数化方式,彻底避免语法错误和安全风险。

2. 排查服务器环境配置差异

  • 数据库连接: 确认服务器上的数据库连接地址、用户名、密码是否正确,数据库服务是否正常运行,服务器是否有访问数据库的权限(比如防火墙规则)。
  • 环境变量: 检查USER_ROLE、commonMessages等常量是否在服务器环境中正确配置,避免出现未定义的错误。
  • 依赖版本: 对比本地与服务器的依赖版本(查看package-lock.json),确保Joi、bcrypt、email-validator等核心依赖版本一致,部分版本差异可能导致运行逻辑不同。

3. 添加详细错误日志

服务器端看不到具体错误信息很难定位问题,在所有catch块中添加日志输出:

catch (error) {
  console.error("注册接口错误详情:", error); // 打印到服务器日志
  return next(error);
}

查看服务器的进程日志或系统错误日志,获取具体的错误类型(比如数据库连接失败、SQL语法错误、依赖缺失等)。

4. 验证Joi规则的兼容性

检查Joi的验证规则在服务器环境中是否存在触发条件:

  • 比如phone_number的正则/^\+?[1-9]\d{1,9}$/,如果服务器端接收的手机号长度超过10位(带国家码的长号),会触发验证失败,可调整正则或测试输入数据。
  • 确认image字段的验证逻辑,服务器端是否接收了符合要求的图片路径或base64字符串,避免长度或格式不符合规则。

5. 排查emailValidator的问题

服务器端的emailValidator可能因为网络或配置问题无法正常工作,可暂时注释该部分逻辑,测试是否能完成注册:

// 暂时注释邮箱验证,测试流程
// let doc = await emailValidator.validate(email);
// if (!doc.valid)
//   return next(CustomeErrorHandler.validation("Enter valid email!"));

如果注释后服务器端正常运行,说明emailValidator在服务器环境中存在问题,可更换其他邮箱验证方式或检查其配置。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 10:43:08