Artifact 3789890a6feb02900fb9f3470da8c297ca706fee01569bb2374061f82fd453ce:
- File
shared/ql361api/src/ql361api/auth.gleam
— part of check-in
[5bb18ed4d4]
at
2026-10-23 03:34:10
on branch trunk
— 把来肯对接和本地库落到可切换的后端。
账号从环境读取,商品由人工增量同步,订单按五分钟轮询并缓存登录会话。开发期用 SQLite,同一套结构可切到 PostgreSQL。 (user: pluto size: 3400)
import gleam/dynamic/decode import gleam/http import gleam/http/request import gleam/httpc import gleam/json import gleam/result import gleam/string import gleam/uri import ql361api/client.{type Client} import ql361api/models.{ type ApiError, type LoginResult, ApiFailure, ApiSession, AuthExpired, HttpError, JsonError, LoginResult, NetworkError, UrlParseError, } pub const auth_endpoint = "https://www.ql361.com/account/Auth" pub const captcha_id = "77a29448570d458fb31f106383850cba" pub const get_login_status_method = "CC.ERP.BLL.API.MessageServer.GetLoginStatusForDesktop" pub fn login( username username: String, password password: String, ) -> Result(#(Client, LoginResult), ApiError) { let form_data = "username=" <> uri.percent_encode(username) <> "&password=" <> uri.percent_encode(password) <> "&type=2&captchaid=" <> captcha_id <> "&piccode=&vcode=" use req <- result.try( request.to(auth_endpoint) |> result.replace_error(UrlParseError), ) let req = req |> request.set_method(http.Post) |> request.set_header("content-type", "application/x-www-form-urlencoded; charset=UTF-8") |> request.set_header("x-requested-with", "XMLHttpRequest") |> request.set_header("user-agent", client.default_user_agent) |> request.set_header("referer", "https://www.ql361.com/account/logon") |> request.set_body(form_data) case httpc.send(req) { Ok(resp) -> { case resp.status { 200 -> parse_login_response(resp.body) status -> Error(HttpError(status: status, body: resp.body)) } } Error(err) -> Error(NetworkError(string.inspect(err))) } } fn parse_login_response(body: String) -> Result(#(Client, LoginResult), ApiError) { let company_decoder = { use company_name <- decode.optional_field("companyname", "", decode.string) use agent_real_name <- decode.optional_field("agentrealname", "", decode.string) decode.success(#(company_name, agent_real_name)) } let login_decoder = { use success <- decode.field("success", decode.bool) use token <- decode.optional_field("token", "", decode.string) use sign <- decode.optional_field("sign", "", decode.string) use msg <- decode.optional_field("msg", "", decode.string) use company <- decode.optional_field("companydata", #("", ""), company_decoder) decode.success(LoginResult( success: success, company_name: company.0, agent_real_name: company.1, token: token, sign: sign, msg: msg, )) } case json.parse(body, login_decoder) { Ok(info) -> { case info.success { True -> { let session = ApiSession( s: info.sign, tok: info.token, cookie: "s=" <> info.sign <> "; tok=" <> info.token, ) let authed_client = client.default_client() |> client.with_session(session) Ok(#(authed_client, info)) } False -> Error(ApiFailure(case info.msg { "" -> "登录失败,请检查账号密码" msg -> msg })) } } Error(err) -> Error(JsonError(string.inspect(err))) } } pub fn check_login_status(client: Client) -> Result(Bool, ApiError) { case client.post_rpc(client, get_login_status_method, "crmoa", "{}") { Ok(_) -> Ok(True) Error(AuthExpired(_)) -> Ok(False) Error(err) -> Error(err) } }