Swift项目中REST API用户注册出现KeyNotFound错误求助
问题排查与修复方案
错误原因分析
你遇到的keyNotFound("name")解码错误,本质是前端请求发送错误+后端响应格式不匹配导致的:
- 前端发送的POST请求没有携带注册数据,且实际是GET请求(ApiService代码逻辑错误)
- 后端返回的是纯文本字符串,而非符合
RegisterModel结构的JSON对象 - 前端尝试将纯文本解码为
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
相关产品推荐
相关产品推荐

