diff --git a/API.md b/API.md index b14aaf3..e69de29 100644 --- a/API.md +++ b/API.md @@ -1,17 +0,0 @@ -# 获取会员名 -curl 'https://h5api.m.goofish.com/h5/mtop.idle.web.user.page.account/1.0/?jsv=2.7.2&appKey=34839810&t=1769270729440&sign=02c196fa1bf3d9797d63374dbd942843&v=1.0&type=originaljson&accountSite=xianyu&dataType=json&timeout=20000&api=mtop.idle.web.user.page.account&sessionOption=AutoLoginOnly&spm_cnt=a21ybx.account.0.0&spm_pre=a21ybx.personal.menu.6.1b866ac226jKae&log_id=1b866ac226jKae' \ - -H 'accept: application/json' \ - -H 'accept-language: zh-CN,zh;q=0.9' \ - -H 'content-type: application/x-www-form-urlencoded' \ - -b 'cna=9P3pIdIP/CsCASSqLXsBph0X; t=81cea1923c0f7c1c6acd493e8fb98440; cookie2=1865e191ee74187929f741a5b527cb43; mtop_partitioned_detect=1; _m_h5_tk=634d547c63abefec9bd902b069faa5bb_1769276924644; _m_h5_tk_enc=7b76e180c49784d8fc49f098b2ac3967; xlly_s=1; _samesite_flag_=true; _tb_token_=53eda6bad563a; tracknick=%E5%8D%8A%E5%A4%8F%E8%90%BD%E5%9C%B0; unb=2045669855; sdkSilent=1769354710900; sgcookie=E100MpVcyQgT5YDPe3ISBbg8t7xrPjLAJMVTGTLnZHR1TPlLwGYotQYxGlZrUE%2FzSToFWstgiPsiQtbqRyDuqfyd8I64zsZ%2BOIsTKDMKFEAeuiQ%3D; csg=019ee131; havana_lgc2_77=eyJoaWQiOjIwNDU2Njk4NTUsInNnIjoiNjg0OWZmY2E1OTQ5YjVlNjk1MjZmMDhlMWYwNDNhNWEiLCJzaXRlIjo3NywidG9rZW4iOiIxOXZjMkZtWFMtSFFRenJfRnJ1dVpUZyJ9; _hvn_lgc_=77; havana_lgc_exp=1771861120777; tfstk=gPEKdSYz0OppePS1MHbGZDty-r6GoN2UtWyXqbcHP5FTh-RnPkm3wUF3U705TDq867VzxXD3EY9UXSKkx67UF3noVsfcmi2eL0o5iFsIYYcEURG5I2TyIYBnVsfcjFvs86m7K32g7Rws_YHWR3NSffMx13T7NXg61xHqVbN7NCTsEvKBP4gQCOHr1biSV7i1BYcsN0i7NRTRgNhyRjx8-Opp4gDK63t7XA1rGJhiQArtp2hj5fK7IlHKJjwQc_51pYUbD2PfsecQkzVxnWj6vSeQ9rgbAC1t4R4YwxEChEh8TRZn5k6kuAozRriQ2sdQCqE_xDzOMncb78Z-5J5vuA47nk48t1-Stre_6qqMsnm_vWEIWmsrSoqYdROmMYYCBOLyz2MaZQZCf3oTiyktiODHzUuO7AhcB_8yz2iKBjXMRU8rWrC..' \ - -H 'origin: https://www.goofish.com' \ - -H 'priority: u=1, i' \ - -H 'referer: https://www.goofish.com/' \ - -H 'sec-ch-ua: "Not(A:Brand";v="8", "Chromium";v="144", "Google Chrome";v="144"' \ - -H 'sec-ch-ua-mobile: ?0' \ - -H 'sec-ch-ua-platform: "macOS"' \ - -H 'sec-fetch-dest: empty' \ - -H 'sec-fetch-mode: cors' \ - -H 'sec-fetch-site: same-site' \ - -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36' \ - --data-raw 'data=%7B%7D' \ No newline at end of file diff --git a/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/request/FetchRemoteAllProductRequest.java b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/request/FetchRemoteAllProductRequest.java new file mode 100644 index 0000000..75ac789 --- /dev/null +++ b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/request/FetchRemoteAllProductRequest.java @@ -0,0 +1,17 @@ +package top.biwin.xinayu.common.dto.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * TODO + * + * @author wangli + * @since 2026-02-03 23:19 + */ +@Data +public class FetchRemoteAllProductRequest { + + @JsonProperty("goofish_id") + private String goofishId; +} diff --git a/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/CheckDefaultPwdResponse.java b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/CheckDefaultPwdResponse.java index 0161472..f422141 100644 --- a/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/CheckDefaultPwdResponse.java +++ b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/CheckDefaultPwdResponse.java @@ -3,6 +3,7 @@ package top.biwin.xinayu.common.dto.response; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @@ -12,6 +13,7 @@ import lombok.experimental.SuperBuilder; * @author wangli * @since 2026-01-22 23:50 */ +@EqualsAndHashCode(callSuper = true) @Data @SuperBuilder public class CheckDefaultPwdResponse extends BaseResponse { diff --git a/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/FetchRemoteAllProductResponse.java b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/FetchRemoteAllProductResponse.java new file mode 100644 index 0000000..8f254c1 --- /dev/null +++ b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/FetchRemoteAllProductResponse.java @@ -0,0 +1,21 @@ +package top.biwin.xinayu.common.dto.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.SuperBuilder; + +/** + * TODO + * + * @author wangli + * @since 2026-02-03 23:21 + */ +@EqualsAndHashCode(callSuper = true) +@Data +@SuperBuilder +public class FetchRemoteAllProductResponse extends BaseResponse { + + private Integer totalCount; + private Integer totalPages; + private Integer savedCount; +} diff --git a/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/GetRemoteProductApiVo.java b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/GetRemoteProductApiVo.java new file mode 100644 index 0000000..302b796 --- /dev/null +++ b/xianyu-common/src/main/java/top/biwin/xinayu/common/dto/response/GetRemoteProductApiVo.java @@ -0,0 +1,22 @@ +package top.biwin.xinayu.common.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * TODO + * + * @author wangli + * @since 2026-02-03 23:34 + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class GetRemoteProductApiVo{ + private Integer totalCount; + private Integer totalPages; + private Integer totalSaved; +} diff --git a/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/ProductEntity.java b/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/ProductEntity.java new file mode 100644 index 0000000..e3c17fe --- /dev/null +++ b/xianyu-core/src/main/java/top/biwin/xianyu/core/entity/ProductEntity.java @@ -0,0 +1,77 @@ +package top.biwin.xianyu.core.entity; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import org.hibernate.annotations.ColumnDefault; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; + +@Data +@Entity +@Table(name = "products") +public class ProductEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "goofish_id", nullable = false) + @JsonProperty("goofish_id") + private String goofishId; + + @Column(name = "goofish_product_id", nullable = false) + @JsonProperty("goofish_product_id") + private String goofishProductId; + + @Column(name = "product_title") + @JsonProperty("product_title") + private String productTitle; + + @Column(name = "product_description", columnDefinition = "TEXT") + @JsonProperty("product_description") + private String productDescription; + + @Column(name = "product_category") + @JsonProperty("product_category") + private String productCategory; + + @Column(name = "product_price") + @JsonProperty("product_price") + private String productPrice; + + @Column(name = "product_detail", columnDefinition = "TEXT") + @JsonProperty("product_detail") + private String productDetail; + + @Column(name = "is_multi_spec") + @ColumnDefault("false") + @JsonProperty("is_multi_spec") + private Boolean isMultiSpec = false; + + @Column(name = "multi_quantity_delivery") + @ColumnDefault("false") + @JsonProperty("multi_quantity_delivery") + private Boolean multiQuantityDelivery = false; + + @Column(name = "user_id") + @JsonProperty("user_id") + private Long userId; + + @CreationTimestamp + @Column(name = "created_at", updatable = false) + @JsonProperty("created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + @JsonProperty("updated_at") + private LocalDateTime updatedAt; +} diff --git a/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/GoofishAccountRepository.java b/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/GoofishAccountRepository.java index d81fa9d..9a7a64c 100644 --- a/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/GoofishAccountRepository.java +++ b/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/GoofishAccountRepository.java @@ -4,12 +4,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import top.biwin.xianyu.core.entity.GoofishAccountEntity; +import java.util.List; import java.util.Optional; @Repository public interface GoofishAccountRepository extends JpaRepository { - Optional findByUserId(Long UserId); + List findByUserId(Long UserId); long countByEnabled(Boolean enabled); diff --git a/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/ProductRepository.java b/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/ProductRepository.java new file mode 100644 index 0000000..40efea8 --- /dev/null +++ b/xianyu-core/src/main/java/top/biwin/xianyu/core/repository/ProductRepository.java @@ -0,0 +1,14 @@ +package top.biwin.xianyu.core.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import top.biwin.xianyu.core.entity.ProductEntity; + +import java.util.List; + +@Repository +public interface ProductRepository extends JpaRepository { + List findByUserId(Long userId); + + List findByGoofishId(String goofishId); +} diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishAbstractApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishAbstractApi.java index 377151e..d900486 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishAbstractApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishAbstractApi.java @@ -2,6 +2,7 @@ package top.biwin.xianyu.goofish.api; import cn.hutool.crypto.SecureUtil; import org.springframework.beans.factory.annotation.Value; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.HashMap; @@ -20,10 +21,10 @@ public abstract class GoofishAbstractApi implements GoofishApi { private String apiHostUrl; protected final String buildApiUrl() { - return apiHostUrl + getApi() + "/" + getVersion() + "/?"; + return apiHostUrl + getApi() + "/" + getVersion() + "/?"; } - protected Map buildQueryParams(String cookieStr, String dataStr) { + protected Map buildQueryParams(String cookieStr, String dataStr, Map overrideMap) { Map api_query_params = new HashMap<>(); api_query_params.put("jsv", "2.7.2"); api_query_params.put("appKey", appKey); @@ -38,9 +39,17 @@ public abstract class GoofishAbstractApi implements GoofishApi { api_query_params.put("api", ""); api_query_params.put("sessionOption", "AutoLoginOnly"); api_query_params.put("spm_cnt", "a21ybx.account.0.0"); + + if (!CollectionUtils.isEmpty(overrideMap)) { + api_query_params.putAll(overrideMap); + } return api_query_params; } + protected Map buildQueryParams(String cookieStr, String dataStr) { + return buildQueryParams(cookieStr, dataStr, null); + } + private String getApiToken(String cookieStr) { if (!StringUtils.hasText(cookieStr)) { throw new IllegalArgumentException("缺少 Cookie 数据"); diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishApi.java index 3cea46a..d3d63f4 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/GoofishApi.java @@ -1,5 +1,7 @@ package top.biwin.xianyu.goofish.api; +import java.util.Map; + /** * TODO * @@ -13,6 +15,6 @@ public interface GoofishApi { String getVersion(); - T call(String goofishId, String cookieStr, String dataStr); + T call(String goofishId, String cookieStr, Map dataMap); } diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAccountApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAccountApi.java index 3e19be8..1a8aaca 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAccountApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAccountApi.java @@ -12,6 +12,7 @@ import top.biwin.xianyu.goofish.api.GoofishAbstractApi; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Map; /** * 获取闲鱼的账号 @@ -38,7 +39,8 @@ public class GetAccountApi extends GoofishAbstractApi { } @Override - public String call(String goofishId, String cookieStr, String dataStr) { + public String call(String goofishId, String cookieStr, Map data) { + String dataStr = JSONUtil.toJsonStr(data); String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr)); log.debug("【{}】获取账号名 ApiUrl: {}", goofishId, apiUrl); log.debug("【{}】获取账号名时使用的 Cookie 为: {}", goofishId, cookieStr); diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAllItemsApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAllItemsApi.java new file mode 100644 index 0000000..1d89a2c --- /dev/null +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetAllItemsApi.java @@ -0,0 +1,149 @@ +package top.biwin.xianyu.goofish.api.impl; + +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 top.biwin.xianyu.goofish.api.GoofishAbstractApi; +import top.biwin.xinayu.common.dto.response.GetRemoteProductApiVo; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * TODO + * + * @author wangli + * @since 2026-02-03 23:28 + */ +@Slf4j +@Component +public class GetAllItemsApi extends GoofishAbstractApi { + @Override + public String getName() { + return "getAllItems"; + } + + @Override + public String getApi() { + return "mtop.idle.web.xyh.item.list"; + } + + @Override + public String getVersion() { + return "1.0"; + } + + /** + * + * @param goofishId + * @param cookieStr + * @param data {"needGroupInfo":false,"pageNumber":1,"userId":"450737657","pageSize":20,"groupName":"在售","groupId":66679630,"defaultGroup":true} + * @return + */ + @Override + public GetRemoteProductApiVo call(String goofishId, String cookieStr, Map data) { + String dataStr = JSONUtil.toJsonStr(data); + String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr, selfMap())); + log.debug("【{}】获取闲鱼商品数据 ApiUrl: {}", goofishId, apiUrl); + log.debug("【{}】获取闲鱼商品数据时使用的 Cookie 为: {}", goofishId, cookieStr); + + GetRemoteProductApiVo.GetRemoteProductApiVoBuilder builder = GetRemoteProductApiVo.builder(); + try (HttpResponse response = HttpRequest.post(apiUrl) + .header("Cookie", cookieStr) + .header("content-type", "application/x-www-form-urlencoded") + .header("priority", "u=1, i") + .header("dnt", "1") + .header("user-agent", "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") + .body("data=" + URLEncoder.encode(dataStr, StandardCharsets.UTF_8)) + .execute()) { + String body = response.body(); + log.info("【{}】获取闲鱼商品数据时,服务端返回的完整响应为: {}", 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")) { + JSONObject itemsData = json.getJSONObject("data"); + JSONArray cardList = itemsData.getJSONArray("cardList"); + + // 解析商品信息 + java.util.List> itemsList = new java.util.ArrayList<>(); + if (cardList != null) { + for (int i = 0; i < cardList.size(); i++) { + JSONObject card = cardList.getJSONObject(i); + JSONObject cardData = card.getJSONObject("cardData"); + if (cardData != null) { + Map itemInfo = new HashMap<>(); + itemInfo.put("id", cardData.getStr("id")); + itemInfo.put("title", cardData.getStr("title")); + + JSONObject priceInfo = cardData.getJSONObject("priceInfo"); + if (priceInfo != null) { + itemInfo.put("price", priceInfo.getStr("price")); + String priceText = (priceInfo.getStr("preText") != null ? priceInfo.getStr("preText") : "") + + (priceInfo.getStr("price") != null ? priceInfo.getStr("price") : ""); + itemInfo.put("price_text", priceText); + } else { + itemInfo.put("price", ""); + itemInfo.put("price_text", ""); + } + + itemInfo.put("category_id", cardData.getStr("categoryId")); + itemInfo.put("auction_type", cardData.getStr("auctionType")); + itemInfo.put("item_status", cardData.getInt("itemStatus")); + itemInfo.put("detail_url", cardData.getStr("detailUrl")); + itemInfo.put("pic_info", cardData.getJSONObject("picInfo")); + itemInfo.put("detail_params", cardData.getJSONObject("detailParams")); + itemInfo.put("track_params", cardData.getJSONObject("trackParams")); + itemInfo.put("item_label_data", cardData.getJSONObject("itemLabelDataVO")); + itemInfo.put("card_type", card.getInt("cardType")); + + itemsList.add(itemInfo); + } + } + } + log.info("【{}】成功获取到 {} 个商品", goofishId, itemsList.size()); + // 打印商品详细信息到控制台 + System.out.println("\n" + "=".repeat(80)); + System.out.println(String.format("📦 账号 %s 的商品列表 (第%d页,%d 个商品)", goofishId, data.get("pageNumber"), itemsList.size())); + System.out.println("=".repeat(80)); + + for (int i = 0; i < itemsList.size(); i++) { + Map item = itemsList.get(i); + System.out.println(String.format("\n🔸 商品 %d:", i + 1)); + System.out.println(String.format(" 商品ID: %s", item.get("id"))); + System.out.println(String.format(" 商品标题: %s", item.get("title"))); + System.out.println(String.format(" 价格: %s", item.get("price_text"))); + System.out.println(String.format(" 分类ID: %s", item.get("category_id"))); + System.out.println(String.format(" 商品状态: %s", item.get("item_status"))); + System.out.println(String.format(" 拍卖类型: %s", item.get("auction_type"))); + System.out.println(String.format(" 详情链接: %s", item.get("detail_url"))); + } + + System.out.println("\n" + "=".repeat(80)); + System.out.println("✅ 商品列表获取完成"); + System.out.println("=".repeat(80)); + + // TODO 保存近数据库 + } + } + } catch (Exception e) { + log.error("获取闲鱼商品数据异常: {}", e.getMessage(), e); + } + + return builder.build(); + } + + private Map selfMap() { + Map map = new HashMap<>(); + map.put("spm_cnt", "a21ybx.personal.0.0"); + return map; + } +} diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetDisplayNameApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetDisplayNameApi.java index 1e6acfb..b5f4c9f 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetDisplayNameApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetDisplayNameApi.java @@ -12,6 +12,7 @@ import top.biwin.xianyu.goofish.api.GoofishAbstractApi; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Map; /** * TODO @@ -38,7 +39,8 @@ public class GetDisplayNameApi extends GoofishAbstractApi { } @Override - public String call(String goofishId, String cookieStr, String dataStr) { + public String call(String goofishId, String cookieStr, Map data) { + String dataStr = JSONUtil.toJsonStr(data); String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr)); log.debug("【{}】获取闲鱼昵称 ApiUrl: {}", goofishId, apiUrl); log.debug("【{}】获取闲鱼昵称时使用的 Cookie 为: {}", goofishId, cookieStr); diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetUserIdApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetUserIdApi.java index f964cfa..0f126b8 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetUserIdApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetUserIdApi.java @@ -12,6 +12,7 @@ import top.biwin.xianyu.goofish.api.GoofishAbstractApi; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Map; /** * TODO @@ -19,8 +20,8 @@ import java.nio.charset.StandardCharsets; * @author wangli * @since 2026-01-29 21:41 */ -@Component @Slf4j +@Component public class GetUserIdApi extends GoofishAbstractApi { @Override public String getName() { @@ -38,7 +39,8 @@ public class GetUserIdApi extends GoofishAbstractApi { } @Override - public Long call(String goofishId, String cookieStr, String dataStr) { + public Long call(String goofishId, String cookieStr, Map data) { + String dataStr = JSONUtil.toJsonStr(data); String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr)); log.debug("【{}】获取闲鱼用户 ID ApiUrl: {}", goofishId, apiUrl); log.debug("【{}】获取闲鱼用户 ID 时使用的 Cookie 为: {}", goofishId, cookieStr); diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetWsTokenApi.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetWsTokenApi.java index fce9eb6..a840fa8 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetWsTokenApi.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/api/impl/GetWsTokenApi.java @@ -12,6 +12,7 @@ import top.biwin.xianyu.goofish.api.GoofishAbstractApi; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Map; /** * TODO @@ -38,7 +39,8 @@ public class GetWsTokenApi extends GoofishAbstractApi { } @Override - public String call(String goofishId, String cookieStr, String dataStr) { + public String call(String goofishId, String cookieStr, Map data) { + String dataStr = JSONUtil.toJsonStr(data); String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr)); log.debug("【{}】获取闲鱼 WebSocket Token ApiUrl: {}", goofishId, apiUrl); log.debug("【{}】获取闲鱼 WebSocket Token 时使用的 Cookie 为: {}", goofishId, cookieStr); 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 index 41ce95c..9e4fd80 100644 --- 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 @@ -5,12 +5,16 @@ 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.entity.GoofishAccountEntity; import top.biwin.xianyu.core.repository.GoofishAccountRepository; import top.biwin.xianyu.goofish.api.GoofishApi; import top.biwin.xianyu.goofish.websocket.WebSocketConfiguration; +import top.biwin.xinayu.common.dto.response.GetRemoteProductApiVo; import javax.annotation.Nullable; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -37,11 +41,9 @@ public class GoofishApiService { public String getAccount(String goofishId, @Nullable String cookieStr) { GoofishApi goofishApi = getApi("getAccount"); if (!StringUtils.hasText(cookieStr)) { - cookieStr = goofishAccountRepository.findById(goofishId) - .orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户名,缺少 Cookie 信息")) - .getCookie(); + cookieStr = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼账号相关信息")).getCookie(); } - return (String) goofishApi.call(goofishId, cookieStr, "{}"); + return (String) goofishApi.call(goofishId, cookieStr, new HashMap<>()); } /** @@ -54,31 +56,47 @@ public class GoofishApiService { public Long getUserId(String goofishId, @Nullable String cookieStr) { GoofishApi goofishApi = getApi("getUserId"); if (!StringUtils.hasText(cookieStr)) { - cookieStr = goofishAccountRepository.findById(goofishId) - .orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户 ID,缺少 Cookie 信息")) - .getCookie(); + cookieStr = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户 ID,缺少 Cookie 信息")).getCookie(); } - return (Long) goofishApi.call(goofishId, cookieStr, "{}"); + return (Long) goofishApi.call(goofishId, cookieStr, new HashMap<>()); } public String getNickName(String goofishId, @Nullable String cookieStr) { GoofishApi goofishApi = getApi("getDisplayName"); if (!StringUtils.hasText(cookieStr)) { - cookieStr = goofishAccountRepository.findById(goofishId) - .orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户 ID,缺少 Cookie 信息")) - .getCookie(); + cookieStr = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户 ID,缺少 Cookie 信息")).getCookie(); } - return (String) goofishApi.call(goofishId, cookieStr, "{}"); + return (String) goofishApi.call(goofishId, cookieStr, new HashMap<>()); } public String getWsToken(String goofishId, @Nullable String cookieStr, String deviceId) { GoofishApi goofishApi = getApi("getWsToken"); if (!StringUtils.hasText(cookieStr)) { - cookieStr = goofishAccountRepository.findById(goofishId) - .orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼用户 ID,缺少 Cookie 信息")) - .getCookie(); + cookieStr = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼账号相关信息")).getCookie(); } - return (String) goofishApi.call(goofishId, cookieStr, "{\"appKey\":\"" + webSocketConfiguration.getAppKey() + "\", \"deviceId\":\"" + deviceId + "\"}"); + Map data = new HashMap<>(); + data.put("appKey", webSocketConfiguration.getAppKey()); + data.put("deviceId", deviceId); + return (String) goofishApi.call(goofishId, cookieStr, data); + } + + public GetRemoteProductApiVo getAllItems(String goofishId, @Nullable String cookieStr) { + GoofishApi goofishApi = getApi("getAllItems"); + + GoofishAccountEntity account = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalArgumentException("无法获取闲鱼账号相关信息")); + if (!StringUtils.hasText(cookieStr)) { + cookieStr = account.getCookie(); + } + Map data = new HashMap<>(); + data.put("needGroupInfo", false); + data.put("pageNumber", 1); + data.put("userId", account.getId()); + data.put("pageSize", 20); + data.put("groupName", "在售"); + data.put("groupId", 66679630); + data.put("defaultGroup", true); + + return (GetRemoteProductApiVo) goofishApi.call(goofishId, cookieStr, data); } private GoofishApi getApi(String apiName) { diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketConfiguration.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketConfiguration.java index 94871d3..4cf380c 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketConfiguration.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketConfiguration.java @@ -14,6 +14,7 @@ import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix = "goofish.websocket") public class WebSocketConfiguration { + private Boolean autoConnectOnStartup; private String appKey; private String wsUrl; private Integer tokenRefreshInterval; diff --git a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketStarter.java b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketStarter.java index dec6f8b..39cd462 100644 --- a/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketStarter.java +++ b/xianyu-goofish/src/main/java/top/biwin/xianyu/goofish/websocket/WebSocketStarter.java @@ -15,7 +15,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; /** @@ -40,8 +39,14 @@ public class WebSocketStarter { @Autowired @Qualifier("goofishAccountWebSocketExecutor") private ScheduledExecutorService goofishAccountWebSocketExecutor; + @PostConstruct public void init() { + Boolean autoConnectOnStartup = webSocketConfiguration.getAutoConnectOnStartup(); + if (!Objects.equals(Boolean.TRUE, autoConnectOnStartup)) { + log.info("ignore goofish account connect websocket..."); + return; + } log.info("Start all goofish account websocket ..."); List accounts = goofishAccountRepository.findAll(); accounts.stream() diff --git a/xianyu-server/src/main/java/top/biwin/xinayu/server/config/ThreadPoolConfig.java b/xianyu-server/src/main/java/top/biwin/xinayu/server/config/ThreadPoolConfig.java index 1f5fe00..78d60e8 100644 --- a/xianyu-server/src/main/java/top/biwin/xinayu/server/config/ThreadPoolConfig.java +++ b/xianyu-server/src/main/java/top/biwin/xinayu/server/config/ThreadPoolConfig.java @@ -20,7 +20,7 @@ public class ThreadPoolConfig { // 创建定时任务线程池 return Executors.newScheduledThreadPool(5, r -> { Thread t = new Thread(r); - t.setName("GoofishAccountWebSocket-" + t.getId()); + t.setName("GAWebSocket-" + t.getId()); t.setDaemon(true); return t; }); diff --git a/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/GoofishAccountController.java b/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/GoofishAccountController.java index 8161bd7..0d2de95 100644 --- a/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/GoofishAccountController.java +++ b/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/GoofishAccountController.java @@ -53,14 +53,13 @@ public class GoofishAccountController { * 获取所有Cookie的详细信息(包括值和状态) * 对应Python的 get_cookies_details 接口 */ - @GetMapping("/details") + @GetMapping("/accounts") public ResponseEntity> getAllGoofishAccountDetails() { List goofishAccountEntities = new ArrayList<>(); if (CurrentUserUtil.isSuperAdmin()) { goofishAccountEntities.addAll(goofishAccountRepository.findAll()); } else { - goofishAccountRepository.findByUserId(CurrentUserUtil.getCurrentUserId()) - .ifPresent(goofishAccountEntities::add); + goofishAccountEntities.addAll(goofishAccountRepository.findByUserId(CurrentUserUtil.getCurrentUserId())); } // 构建详细信息响应 @@ -111,7 +110,7 @@ public class GoofishAccountController { } - @PostMapping("/cookies") + @PostMapping("/login-by-cookie") public ResponseEntity loginByCookie(@RequestBody GoofishAddCookieRequest request) { Long goofishUserId = goofishApiService.getUserId(request.getId(), request.getValue()); log.info("尝试利用 cookie 获取闲鱼用户 ID:{}", goofishUserId > 0L ? goofishUserId : "Cookie 失效"); @@ -138,7 +137,7 @@ public class GoofishAccountController { .build()); } - @DeleteMapping("/cookies/{id}") + @DeleteMapping("/account/{id}") public void deleteAccount(@PathVariable("id") String goofishId) { GoofishAccountEntity entity; @@ -156,7 +155,7 @@ public class GoofishAccountController { goofishAccountRepository.delete(entity); } - @PutMapping("/cookies/{id}/remark") + @PutMapping("/account/{id}/remark") public ResponseEntity updateAccountRemark(@PathVariable("id") String goofishId, @RequestBody AccountUpdateRequest request) { GoofishAccountEntity account = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalStateException("账号不存在")); if (!CurrentUserUtil.isSuperAdmin()) { @@ -180,7 +179,7 @@ public class GoofishAccountController { } - @PutMapping("/cookies/{id}/status") + @PutMapping("/account/{id}/status") public ResponseEntity updateAccountStatus(@PathVariable("id") String goofishId, @RequestBody AccountUpdateRequest request) { GoofishAccountEntity account = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalStateException("账号不存在")); if (!CurrentUserUtil.isSuperAdmin()) { diff --git a/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/ProductController.java b/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/ProductController.java new file mode 100644 index 0000000..b652d95 --- /dev/null +++ b/xianyu-server/src/main/java/top/biwin/xinayu/server/controller/ProductController.java @@ -0,0 +1,64 @@ +package top.biwin.xinayu.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.biwin.xianyu.core.entity.ProductEntity; +import top.biwin.xianyu.core.repository.ProductRepository; +import top.biwin.xinayu.common.dto.request.FetchRemoteAllProductRequest; +import top.biwin.xinayu.common.dto.response.FetchRemoteAllProductResponse; +import top.biwin.xinayu.server.util.CurrentUserUtil; + +import java.util.ArrayList; +import java.util.List; + +/** + * TODO + * + * @author wangli + * @since 2026-02-03 22:42 + */ +@Slf4j +@RestController +@RequestMapping() +public class ProductController { + @Autowired + private ProductRepository productRepository; + + @GetMapping("/products") + public ResponseEntity> getAllProducts() { + List productEntities = new ArrayList<>(); + if (CurrentUserUtil.isSuperAdmin()) { + productEntities.addAll(productRepository.findAll()); + } else { + productEntities.addAll(productRepository.findByUserId(CurrentUserUtil.getCurrentUserId())); + } + return ResponseEntity.ok(productEntities); + } + + @GetMapping("/products/account/{goofishId}") + public ResponseEntity> getProductByGoofishId(@PathVariable String goofishId) { + return ResponseEntity.ok(productRepository.findByGoofishId(goofishId)); + } + + @PostMapping("/products/get-all-form-account") + public ResponseEntity getAllProductFormXianyu(@RequestBody FetchRemoteAllProductRequest request) { + FetchRemoteAllProductResponse.FetchRemoteAllProductResponseBuilder builder = FetchRemoteAllProductResponse.builder(); + if (!StringUtils.hasText(request.getGoofishId())) { + return ResponseEntity.ok(builder + .success(false) + .message("缺少 goofish_id 参数") + .build()); + } + log.info("触发商品同步任务,goofishId: {}", request.getGoofishId()); + + return ResponseEntity.ok(FetchRemoteAllProductResponse.builder().build()); + } +} diff --git a/xianyu-server/src/main/resources/application.yml b/xianyu-server/src/main/resources/application.yml index 8b2a6da..61b3998 100644 --- a/xianyu-server/src/main/resources/application.yml +++ b/xianyu-server/src/main/resources/application.yml @@ -10,7 +10,7 @@ server: spring: application: - name: xianyu-free + name: xianyu-freedom datasource: driver-class-name: org.sqlite.JDBC @@ -66,6 +66,7 @@ goofish: hostUrl: https://h5api.m.goofish.com/h5/ appKey: 34839810 websocket: + autoConnectOnStartup: false appKey: 444e9908a51d1cb236a27862abc769c9 wsUrl: wss://wss-goofish.dingtalk.com/ tokenRefreshInterval: 72000