Artifact e05ac2c9dd07b6e2f026fd71bfad00da1183e8632715371d4c8748ff92e324be:
- File
shared/ql361api/src/ql361api/client.gleam
— part of check-in
[5bb18ed4d4]
at
2026-10-23 03:34:10
on branch trunk
— 把来肯对接和本地库落到可切换的后端。
账号从环境读取,商品由人工增量同步,订单按五分钟轮询并缓存登录会话。开发期用 SQLite,同一套结构可切到 PostgreSQL。 (user: pluto size: 3516)
import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json import gleam/option.{type Option, None, Some} import gleam/result import gleam/string import ql361api/models.{type ApiError, type ApiSession} pub type Client { Client( base_url: String, session: Option(ApiSession), user_agent: String, ) } pub const default_base_url = "https://22beta.ql361.com/v_22beta/api.cc" pub const default_user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" pub fn new(base_url: String) -> Client { Client( base_url: base_url, session: None, user_agent: default_user_agent, ) } pub fn default_client() -> Client { new(default_base_url) } pub fn with_session(client: Client, session: ApiSession) -> Client { Client(..client, session: Some(session)) } pub fn with_user_agent(client: Client, user_agent: String) -> Client { Client(..client, user_agent: user_agent) } pub fn with_base_url(client: Client, base_url: String) -> Client { Client(..client, base_url: base_url) } pub fn post_rpc( client: Client, method: String, page_type: String, payload_json: String, ) -> Result(String, ApiError) { let token_query = case client.session { Some(sess) -> "&s=" <> sess.s <> "&tok=" <> sess.tok None -> "" } let full_url = client.base_url <> "?___method=" <> method <> "&pagetype=" <> page_type <> "&from=allinoneclient&platform=pc" <> token_query let cookie_header = case client.session { Some(sess) -> sess.cookie None -> "" } use req <- result.try( request.to(full_url) |> result.replace_error(models.UrlParseError), ) let req = req |> request.set_method(http.Post) |> request.set_header("content-type", "application/json") |> request.set_header("user-agent", client.user_agent) |> request.set_header("referer", "https://22beta.ql361.com/desktop.html?v=196647593") |> request.set_body(payload_json) let req = case cookie_header { "" -> req cookie -> request.set_header(req, "cookie", cookie) } case httpc.send(req) { Ok(resp) -> { case resp.status { 200 -> check_envelope(resp.body) status -> Error(models.HttpError(status: status, body: resp.body)) } } Error(err) -> Error(models.NetworkError(string.inspect(err))) } } fn check_envelope(body: String) -> Result(String, ApiError) { let envelope_decoder = { use response_type <- decode.field("type", decode.string) use msg <- decode.field("msg", decode.string) use product_version <- decode.optional_field("productVersion", "", decode.string) decode.success(models.RawEnvelope(response_type, msg, product_version)) } case json.parse(body, envelope_decoder) { Ok(envelope) -> { case envelope.response_type { "normal" -> Ok(body) "logoff" -> Error(models.AuthExpired(envelope.msg)) _ -> { case string.contains(envelope.msg, "已退出登录") { True -> Error(models.AuthExpired(envelope.msg)) False -> Error(models.ApiFailure(envelope.msg)) } } } } Error(err) -> { // In case QL361 returned non-standard envelope or raw data case string.contains(body, "已退出登录") { True -> Error(models.AuthExpired("Session expired")) False -> Error(models.JsonError(string.inspect(err))) } } } }