From b6c2f7407bb91b12f85921cab0b667a02e4a501c Mon Sep 17 00:00:00 2001 From: wangli Date: Sun, 25 Jan 2026 23:41:14 +0800 Subject: [PATCH] init --- .../core/entity/GoofishAccountEntity.java | 2 +- xianyu-goofish/pom.xml | 8 ++ .../xianyu/goofish/qrcode/QrLoginService.java | 10 +- .../goofish/service/BrowserService.java | 2 +- .../goofish/service/GoofishApiService.java | 50 +++++++++ .../goofish/service/api/GetAccountApi.java | 100 ++++++++++++++++++ .../service/api/GoofishAbstractApi.java | 66 ++++++++++++ .../goofish/service/api/GoofishApi.java | 18 ++++ .../src/main/resources/application.yml | 5 + 9 files changed, 255 insertions(+), 6 deletions(-) create mode 100644 xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/GoofishApiService.java create mode 100644 xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GetAccountApi.java create mode 100644 xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishAbstractApi.java create mode 100644 xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishApi.java diff --git a/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/GoofishAccountEntity.java b/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/GoofishAccountEntity.java index 60abc91..3dd4940 100644 --- a/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/GoofishAccountEntity.java +++ b/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/GoofishAccountEntity.java @@ -52,7 +52,7 @@ public class GoofishAccountEntity { private String username; @ColumnDefault("''") - private String nickName; + private String nickname; @ColumnDefault("''") private String password; diff --git a/xianyu-goofish/pom.xml b/xianyu-goofish/pom.xml index 02b5d64..685427a 100644 --- a/xianyu-goofish/pom.xml +++ b/xianyu-goofish/pom.xml @@ -37,9 +37,17 @@ com.google.zxing javase + + com.google.guava + guava + org.projectlombok lombok + + cn.hutool + hutool-all + \ No newline at end of file diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/qrcode/QrLoginService.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/qrcode/QrLoginService.java index c8c9184..d3d53ff 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/qrcode/QrLoginService.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/qrcode/QrLoginService.java @@ -22,6 +22,7 @@ import top.biwin.xianyu.core.entity.GoofishAccountEntity; import top.biwin.xianyu.core.repository.GoofishAccountRepository; import top.biwin.xianyu.goofish.dto.QrLoginSessionDto; import top.biwin.xianyu.goofish.service.BrowserService; +import top.biwin.xianyu.goofish.service.GoofishApiService; import top.biwin.xinayu.common.dto.response.GoofishAccountSimpleInfo; import top.biwin.xinayu.common.dto.response.GoofishQrStatusResponse; import top.biwin.xinayu.common.dto.response.QrLoginResponse; @@ -49,6 +50,8 @@ public class QrLoginService { @Autowired private BrowserService browserService; @Autowired + private GoofishApiService goofishApiService; + @Autowired private OkHttpClient okHttpClient; @Autowired private ObjectMapper objectMapper; @@ -91,7 +94,6 @@ public class QrLoginService { log.info("【QR Login】Got login params for session: {}", sessionId); // 3. Generate QR Code Data - // Construct URL: https://passport.goofish.com/newlogin/qrcode/generate.do HttpUrl.Builder urlBuilder = HttpUrl.parse("https://passport.goofish.com/newlogin/qrcode/generate.do").newBuilder(); for (Map.Entry entry : loginParams.entrySet()) { urlBuilder.addQueryParameter(entry.getKey(), entry.getValue()); @@ -702,7 +704,7 @@ public class QrLoginService { accountId ); - if (verifiedCookies != null && !verifiedCookies.isEmpty()) { + if (!verifiedCookies.isEmpty()) { log.info("【QR Login】浏览器验证成功,获取到真实Cookie,数量: {}", verifiedCookies.size()); // 将Cookie转换为字符串 @@ -724,8 +726,8 @@ public class QrLoginService { goofishAccount.setCookie(finalCookieStr); if (isNewAccount) { - goofishAccount.setNickName(verifiedCookies.getOrDefault("nickName", "")); - goofishAccount.setUsername("TB_" + unb); + goofishAccount.setNickname(verifiedCookies.getOrDefault("nickName", "")); + goofishAccount.setUsername(goofishApiService.getAccount(accountId, finalCookieStr)); goofishAccount.setPassword("QR_LOGIN"); goofishAccount.setUserId(userId); } diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/BrowserService.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/BrowserService.java index 16c6f2d..218d40d 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/BrowserService.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/BrowserService.java @@ -170,7 +170,7 @@ public class BrowserService { log.error("【QR Login】Error creating browser context for verification", e); } - return null; // Failed + return new HashMap<>(); // Failed } } diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/GoofishApiService.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/GoofishApiService.java new file mode 100644 index 0000000..f756e32 --- /dev/null +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/GoofishApiService.java @@ -0,0 +1,50 @@ +package top.biwin.xianyu.goofish.service; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import top.biwin.xianyu.core.repository.GoofishAccountRepository; +import top.biwin.xianyu.goofish.service.api.GoofishApi; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Objects; + +/** + * TODO + * + * @author wangli + * @since 2026-01-25 15:59 + */ +@Component +@Data +public class GoofishApiService { + @Autowired + private List> apis; + @Autowired + private GoofishAccountRepository goofishAccountRepository; + + /** + * 获取账号 + * + * @return + */ + public String getAccount(String goofishId, @Nullable String cookieStr) { + GoofishApi goofishApi = getApi("getAccount"); + if (!StringUtils.hasText(cookieStr)) { + cookieStr = goofishAccountRepository.getReferenceById(goofishId).getCookie(); + } + return (String) goofishApi.call(goofishId, cookieStr, "{}"); + } + + private GoofishApi getApi(String apiName) { + if (CollectionUtils.isEmpty(apis)) { + throw new IllegalStateException("未初始化闲鱼 API"); + } + return apis.stream().filter(i -> Objects.equals(i.getName(), apiName)).findFirst().orElseThrow(() -> new IllegalStateException("未找到指定 API")); + } + + +} diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GetAccountApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GetAccountApi.java new file mode 100644 index 0000000..4d6f7ad --- /dev/null +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GetAccountApi.java @@ -0,0 +1,100 @@ +package top.biwin.xianyu.goofish.service.api; + +import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONArray; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +/** + * 获取闲鱼的账号 + * + * @author wangli + * @since 2026-01-25 17:20 + */ +@Component +@Slf4j +public class GetAccountApi extends GoofishAbstractApi { + @Override + public String getName() { + return "getAccount"; + } + + @Override + public String getApi() { + return "mtop.idle.web.user.page.account"; + } + + @Override + public String getVersion() { + return "1.0"; + } + + @Override + public String call(String goofishId, String cookieStr, String dataStr) { + String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr)); + + try (HttpResponse response = HttpRequest.post(apiUrl) + .header("Cookie", cookieStr) + .body("data=" + URLEncoder.encode(dataStr, StandardCharsets.UTF_8)) + .execute()) { + String body = response.body(); + log.info("【{}】Auto confirm response: {}", goofishId, body); + + JSONObject json = JSONUtil.parseObj(body); + if (json.containsKey("ret")) { + JSONArray ret = json.getJSONArray("ret"); + if (ret != null && !ret.isEmpty() && ret.getStr(0).startsWith("SUCCESS")) { + return json.getJSONObject("data").getJSONObject("module").getJSONObject("base").getStr("displayNick"); + } + + } + } catch (Exception e) { + log.error("获取闲鱼账号异常:{}", e.getMessage(), e); + } + return null; + } + + + public static void main(String[] args) { + String jsonStr = "{\n" + + " \"api\": \"mtop.idle.web.user.page.account\",\n" + + " \"data\": {\n" + + " \"module\": {\n" + + " \"certify\": {\n" + + " \"realPersonCertified\": true,\n" + + " \"alipayCertified\": true,\n" + + " \"xianyuCertified\": true\n" + + " },\n" + + " \"base\": {\n" + + " \"displayNick\": \"半夏落地\"\n" + + " }\n" + + " },\n" + + " \"needDecryptKeys\": [\n" + + " \"baseInfo.encryptedUserId\"\n" + + " ]\n" + + " },\n" + + " \"ret\": [\n" + + " \"SUCCESS::调用成功\"\n" + + " ],\n" + + " \"traceId\": \"2150444f17693348617105274e29cb\",\n" + + " \"v\": \"1.0\"\n" + + "}"; + + + JSONObject json = JSONUtil.parseObj(jsonStr); + if (json.containsKey("ret")) { + JSONArray ret = json.getJSONArray("ret"); + if (ret != null && !ret.isEmpty() && ret.getStr(0).startsWith("SUCCESS")) { + json.getJSONObject("data").getJSONObject("module").getJSONObject("base").getStr("displayNick"); + } + + } + } +} diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishAbstractApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishAbstractApi.java new file mode 100644 index 0000000..324c28d --- /dev/null +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishAbstractApi.java @@ -0,0 +1,66 @@ +package top.biwin.xianyu.goofish.service.api; + +import cn.hutool.crypto.SecureUtil; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.StringUtils; + +import java.util.HashMap; +import java.util.Map; + +/** + * TODO + * + * @author wangli + * @since 2026-01-25 17:16 + */ +public abstract class GoofishAbstractApi implements GoofishApi { + @Value("${goofish.api.appKey:34839810}") + private String appKey; + @Value("${goofish.api.hostUrl:https://h5api.m.goofish.com/h5/}") + private String apiHostUrl; + + protected final String buildApiUrl() { + return apiHostUrl + getApi() + "/" + getVersion() + "/"; + } + + protected Map buildQueryParams(String cookieStr, String dataStr) { + Map api_query_params = new HashMap<>(); + api_query_params.put("jsv", "2.7.2"); + api_query_params.put("appKey", appKey); + String t = String.valueOf(System.currentTimeMillis()); + api_query_params.put("t", t); + api_query_params.put("sign", generateSign(t, getApiToken(cookieStr), dataStr)); + api_query_params.put("v", "1.0"); + api_query_params.put("type", "originaljson"); + api_query_params.put("accountSite", "xianyu"); + api_query_params.put("dataType", "json"); + api_query_params.put("timeout", "20000"); + api_query_params.put("api", ""); + api_query_params.put("sessionOption", "AutoLoginOnly"); + api_query_params.put("spm_cnt", "a21ybx.account.0.0"); + return api_query_params; + } + + private String getApiToken(String cookieStr) { + if (!StringUtils.hasText(cookieStr)) { + throw new IllegalArgumentException("缺少 Cookie 数据"); + } + String token = ""; + String[] parts = cookieStr.split(";"); + for (String part : parts) { + String[] kv = part.trim().split("="); + if (kv.length > 0 && "_m_h5_tk".equals(kv[0])) { + if (kv.length > 1) { + token = kv[1].split("_")[0]; + } + break; + } + } + return token; + } + + private String generateSign(String millisTimeStampStr, String token, String data) { + String src = token + "&" + millisTimeStampStr + "&" + appKey + "&" + data; + return SecureUtil.md5(src); + } +} diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishApi.java new file mode 100644 index 0000000..3efa139 --- /dev/null +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/service/api/GoofishApi.java @@ -0,0 +1,18 @@ +package top.biwin.xianyu.goofish.service.api; + +/** + * TODO + * + * @author wangli + * @since 2026-01-25 17:16 + */ +public interface GoofishApi { + String getName(); + + String getApi(); + + String getVersion(); + + T call(String goofishId, String cookie, String dataStr); + +} diff --git a/xianyu-server/src/main/resources/application.yml b/xianyu-server/src/main/resources/application.yml index e70d938..d626f0c 100644 --- a/xianyu-server/src/main/resources/application.yml +++ b/xianyu-server/src/main/resources/application.yml @@ -62,5 +62,10 @@ jwt: # 7 天 = 604,800,000 毫秒 refresh-token-expiration: 604800000 +goofish: + api: + hostUrl: https://h5api.m.goofish.com/h5/ + appKey: 34839810 + browser: ua: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36 Edg/143.0.0.0'