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

Swift调用Frappe ERPNext API持续触发PermissionError求助

Swift调用Frappe ERPNext API遇PermissionError的解决

我在Swift中调用Frappe ERPNext数据库API时,无论怎么构造请求都返回PermissionError,多次重构调用逻辑都无法解决,但相同的请求在Postman中可以正常运行。

错误信息

{"exception":"frappe.exceptions.PermissionError","exc_type":"PermissionError","exc":"["Traceback (most recent call last):\n File "apps/frappe/frappe/app.py", line 114, in application\n response = frappe.api.handle(request)\n File "apps/frappe/frappe/api/init.py", line 49, in handle\n data = endpoint(**arguments)\n File "apps/frappe/frappe/api/v1.py", line 27, in document_list\n return frappe.call(frappe.client.get_list, doctype, **frappe.form_dict)\n File "apps/frappe/frappe/init.py", line 1781, in call\n return fn(*args, **newargs)\n File "apps/frappe/frappe/utils/typing_validations.py", line 31, in wrapper\n return func(*args, **kwargs)\n File "apps/frappe/frappe/client.py", line 67, in get_list\n return frappe.get_list(**args)\n File "apps/frappe/frappe/init.py", line 2045, in get_list\n return frappe.model.db_query.DatabaseQuery(doctype).execute(*args, **kwargs)\n File "apps/frappe/frappe/model/db_query.py", line 114, in execute\n self.check_read_permission(self.doctype, parent_doctype=parent_doctype)\n File "apps/frappe/frappe/model/db_query.py", line 511, in check_read_permission\n self._set_permission_map(doctype, parent_doctype)\n File "apps/frappe/frappe/model/db_query.py", line 517, in _set_permission_map\n frappe.has_permission(\n File "apps/frappe/frappe/init.py", line 1103, in has_permission\n raise frappe.PermissionError\nfrappe.exceptions.PermissionError\n"]","_server_messages":"["{"message": "User Guest does not have doctype access via role permission for document Item", "title": "Message"}"]","_error_message":"No permission for Item"}

初始实现代码

import Foundation
import SwiftUI

struct ItemResponse: Decodable {
    let data: [Item]
}

struct Item: Decodable, Identifiable {
    let name: String
    var id: String { name }
}

var itemDataResponse: String = ""

struct ShopAPIView: View {
    @ObservedObject var viewModel = itemapiresponse.itemGlobal
    
    var body: some View {
        VStack {
            if viewModel.itemList.isEmpty {
                Text(viewModel.itemDataResponse)
            } else {
                List(viewModel.itemList) { item in
                    Text(item.name)
                }
            }
        }
        .onAppear {
            viewModel.callItemAPI()
        }
    }
}


final class itemapiresponse: ObservableObject {
    static let itemGlobal = itemapiresponse()
    
    @Published var itemDataResponse: String = "loading"
    @Published var itemList: [Item] = []
    
    func callItemAPI() {
        let itemapiURL = "https://myurl"
        
        guard let url = URL(string: itemapiURL) else {
            DispatchQueue.main.async {
                self.itemDataResponse = "Invalid URL"
            }
            return
        }
        
        var request = URLRequest(url: url)
            request.httpMethod = "GET"
            request.addValue("my token", forHTTPHeaderField: "Authorization")
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                DispatchQueue.main.async {
                    self.itemDataResponse = "Error fetching data: \(error.localizedDescription)"
                }
                return
            }
            
            guard let data = data else {
                DispatchQueue.main.async {
                    self.itemDataResponse = "No data returned"
                }
                return
            }
            
            print(String(data: data, encoding: .utf8) ?? "No readable data")

            
            do {
                let itemInfo = try JSONDecoder().decode(ItemResponse.self, from: data)
                DispatchQueue.main.async {
                    self.itemList = itemInfo.data
                    self.itemDataResponse = "Success"
                }
            } catch {
                DispatchQueue.main.async {
                    self.itemDataResponse = "Error parsing JSON: \(error.localizedDescription)"
                }
            }
        }.resume()
    }
}

#Preview {
    ShopAPIView()
}

Postman导出的可正常运行代码

var request = URLRequest(url: URL(string: "\(MyURL)")!,timeoutInterval: Double.infinity)
request.addValue("token \(MyToken)", forHTTPHeaderField: "Authorization")
request.addValue("full_name=Guest; sid=Guest; system_user=no; user_id=Guest; user_image=", forHTTPHeaderField: "Cookie")

request.httpMethod = "GET"

let task = URLSession.shared.dataTask(with: request) { data, response, error in 
  guard let data = data else {
    print(String(describing: error))
    return
  }
  print(String(data: data, encoding: .utf8)!)
}

task.resume()

第二次尝试实现代码

struct ContentView: View {
    @State private var responseData: String = "Loading..."
    
    var body: some View {
        ScrollView {
            Text(responseData)
                .padding()
        }
        .onAppear(perform: fetchData)
    }

    func fetchData() {
        var request = URLRequest(url: URL(string: "\(MyURL)")!, timeoutInterval: Double.infinity)
        request.addValue("token \(MyToken)", forHTTPHeaderField: "Authorization")
        request.addValue("full_name=Guest; sid=Guest; system_user=no; user_id=Guest; user_image=", forHTTPHeaderField: "Cookie")
        request.httpMethod = "GET"

        let task = URLSession.shared.dataTask(with: request) { data, response, error in
          guard let data = data else {
            print(String(describing: error))
            return
          }
          print(String(data: data, encoding: .utf8)!)
        } 
        task.resume()
    }
}

问题分析与解决

从错误信息里的User Guest does not have doctype access可以看出,服务器识别你的请求为匿名Guest用户发起,核心问题有两个:

  1. 请求头未被发送:初始代码中构造了带认证头的request对象,但调用URLSession.dataTask时错误传入了原始url而非request,导致所有设置的请求头都未生效。
  2. Authorization头格式错误:Frappe的Token认证要求格式为"token 你的令牌字符串",初始代码里直接写"my token"不符合规范。

修正后的核心代码

func callItemAPI() {
    let itemapiURL = "https://你的实际API地址"
    
    guard let url = URL(string: itemapiURL) else {
        DispatchQueue.main.async {
            self.itemDataResponse = "Invalid URL"
        }
        return
    }
    
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    // 正确的Authorization格式
    request.addValue("token 你的实际令牌", forHTTPHeaderField: "Authorization")
    // 按需添加Postman中的Cookie头
    request.addValue("full_name=Guest; sid=Guest; system_user=no; user_id=Guest; user_image=", forHTTPHeaderField: "Cookie")
    
    // 必须传入构造好的request,而非原始url
    URLSession.shared.dataTask(with: request) { data, response, error in
        if let error = error {
            DispatchQueue.main.async {
                self.itemDataResponse = "Error fetching data: \(error.localizedDescription)"
            }
            return
        }
        
        guard let data = data else {
            DispatchQueue.main.async {
                self.itemDataResponse = "No data returned"
            }
            return
        }
        
        print(String(data: data, encoding: .utf8) ?? "No readable data")
        
        do {
            let itemInfo = try JSONDecoder().decode(ItemResponse.self, from: data)
            DispatchQueue.main.async {
                self.itemList = itemInfo.data
                self.itemDataResponse = "Success"
            }
        } catch {
            DispatchQueue.main.async {
                self.itemDataResponse = "Error parsing JSON: \(error.localizedDescription)"
            }
        }
    }.resume()
}

另外,第二次尝试代码已经修正了请求对象的传入,但要确保MyURL和MyToken是实际有效的值,并且在更新UI时切换到主线程(比如把responseData的赋值放到DispatchQueue.main.async块中)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:20:55