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

Swift项目中REST API用户注册出现KeyNotFound错误求助

问题排查与修复方案

错误原因分析

你遇到的keyNotFound("name")解码错误,本质是前端请求发送错误+后端响应格式不匹配导致的:

  1. 前端发送的POST请求没有携带注册数据,且实际是GET请求(ApiService代码逻辑错误)
  2. 后端返回的是纯文本字符串,而非符合RegisterModel结构的JSON对象
  3. 前端尝试将纯文本解码为RegisterModel,自然找不到name字段

分步修复

1. 修复ApiService:支持POST请求与请求体发送

原ApiService的fetchData方法完全没处理POST请求,既没设置请求方法,也没发送请求体。修改后:

enum HTTPMethod: String {
    case get = "GET"
    case post = "POST"
}

struct ApiService {
    static let shared = ApiService()

    // 改用Result类型统一处理成功/失败场景
    func fetchData<T: Codable>(url: String, method: HTTPMethod, body: Data? = nil, completion: @escaping (Result<T, Error>) -> ()) {
        guard let url = URL(string: url) else {
            completion(.failure(NSError(domain: "Invalid URL", code: -1, userInfo: nil)))
            return
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = method.rawValue
        
        // 处理POST请求体,设置Content-Type为JSON
        if let body = body {
            request.httpBody = body
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        }
        
        URLSession.shared.dataTask(with: request) { data, response, error in
            if let error = error {
                completion(.failure(error))
                return
            }

            guard let data = data else {
                completion(.failure(NSError(domain: "No data received", code: -2, userInfo: nil)))
                return
            }
            
            // 打印响应内容,方便调试后端返回结果
            if let responseStr = String(data: data, encoding: .utf8) {
                print("Backend Response:", responseStr)
            }
            
            do {
                let decodedData = try JSONDecoder().decode(T.self, from: data)
                DispatchQueue.main.async {
                    completion(.success(decodedData))
                }
            } catch let jsonErr {
                completion(.failure(jsonErr))
            }
        }.resume()
    }
}

2. 修复ViewController:传递用户输入的注册数据

原代码没有把用户输入的name/email/password传给ApiService,修改后:

// 新增响应模型,匹配后端返回的JSON格式
struct RegisterResponse: Codable {
    let message: String
}

extension RegisterViewController {
    func userRegister() {
        // 先校验输入字段非空
        guard let name = objects.nameTextField.text,
              let email = objects.emailTextField.text,
              let password = objects.passwordTextField.text,
              !name.isEmpty, !email.isEmpty, !password.isEmpty else {
            print("请填写所有字段")
            return
        }
        
        // 构造请求体并编码为JSON
        let registerModel = RegisterModel(name: name, email: email, password: password)
        guard let requestBody = try? JSONEncoder().encode(registerModel) else {
            print("编码请求体失败")
            return
        }
        
        let url = "http://localhost:5050/auth/register"
        ApiService.shared.fetchData(url: url, method: .post, body: requestBody) { (result: Result<RegisterResponse, Error>) in
            switch result {
            case .success(let response):
                DispatchQueue.main.async {
                    print("注册成功:", response.message)
                    // 这里添加成功后的业务逻辑,比如跳转登录页
                }
            case .failure(let error):
                print("注册失败:", error)
                DispatchQueue.main.async {
                    // 显示错误提示给用户
                }
            }
        }
    }
}

3. 修复后端Node.js:返回JSON响应+修复SQL注入漏洞

原后端返回纯文本,且存在严重的SQL注入风险,修改后:

const Joi = require("@hapi/joi");
const bcrypt = require("bcryptjs");
const config = require('./../dbconfig');
const mysql = require('mysql');
const connection = mysql.createConnection(config.dbOptions);

// Register User Controller 
const registerController = async (req, res) => {
    const rc = req.body;
    const email = rc.email;
    const password = rc.password;
    const nowDate = new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '');
    
    // 提前做Joi参数校验
    const registerSchema = Joi.object().keys({
        name: Joi.string().required().min(3).max(255),
        surname: Joi.string().required().min(3).max(255),
        email: Joi.string().required().email().min(3).max(255),
        password: Joi.string().required().min(6).max(255),
        profile_image: Joi.string().allow(''),
        role: Joi.string().allow(''),
        account_type: Joi.string().allow('')
    });
    
    const validationResult = registerSchema.validate(rc);
    if (validationResult.error) {
        return res.status(400).json({ 
            message: "参数校验失败", 
            errors: validationResult.error.details 
        });
    }

    // 参数化查询检查邮箱是否存在,避免SQL注入
    const checkEmailQuery = `SELECT COUNT (*) AS cnt FROM users WHERE email = ?`;
    connection.query(checkEmailQuery, [email], function(error, data) {
        if (error) {
            console.log(error);
            return res.status(500).json({ message: "数据库查询错误" });
        } 

        if (data[0].cnt > 0) {
            return res.status(409).json({ message: "邮箱已存在!" });
        } 

        // 生成盐并加密密码
        bcrypt.genSalt(10, (saltErr, salt) => {
            if (saltErr) {
                console.log(saltErr);
                return res.status(500).json({ message: "密码加密失败" });
            }
            
            bcrypt.hash(password, salt, (hashErr, encryptedPassword) => {
                if (hashErr) {
                    console.log(hashErr);
                    return res.status(500).json({ message: "密码加密失败" });
                }

                // 参数化INSERT查询,彻底避免SQL注入
                const insertQuery = `INSERT INTO users (name, surname, email, password, profile_image, role, 
                    account_type, created_date, updated_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
                
                const values = [
                    rc.name, rc.surname, rc.email, encryptedPassword, 
                    rc.profile_image || '', rc.role || '', rc.account_type || '', 
                    nowDate, nowDate
                ];

                connection.query(insertQuery, values, function(err, data) {
                    if (err) {
                        console.log(err);
                        return res.status(500).json({ message: "保存用户数据失败" });
                    } else {
                        // 返回JSON格式的成功响应
                        return res.status(201).json({ 
                            message: "数据已成功保存到数据库",
                            // 可选:返回用户ID,比如 userId: data.insertId
                        });
                    }
                });
            });
        });
    });
};

关键注意点

  • 请求方法匹配:POST请求必须构造URLRequest并设置httpMethod,同时发送JSON格式的请求体
  • 响应格式统一:前后端必须约定用JSON格式交互,不能返回纯文本
  • SQL注入防护:永远不要直接拼接SQL语句,必须使用参数化查询
  • 错误处理:用Result类型统一处理成功/失败场景,便于调试和用户提示

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:37:07