This commit is contained in:
wangli 2026-02-04 00:30:24 +08:00
parent a6eedd9001
commit e7c1288ab2
22 changed files with 445 additions and 52 deletions

17
API.md
View File

@ -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'

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<GoofishAccountEntity, String> {
Optional<GoofishAccountEntity> findByUserId(Long UserId);
List<GoofishAccountEntity> findByUserId(Long UserId);
long countByEnabled(Boolean enabled);

View File

@ -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<ProductEntity, Long> {
List<ProductEntity> findByUserId(Long userId);
List<ProductEntity> findByGoofishId(String goofishId);
}

View File

@ -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<T> implements GoofishApi<T> {
private String apiHostUrl;
protected final String buildApiUrl() {
return apiHostUrl + getApi() + "/" + getVersion() + "/?";
return apiHostUrl + getApi() + "/" + getVersion() + "/?";
}
protected Map<String, String> buildQueryParams(String cookieStr, String dataStr) {
protected Map<String, String> buildQueryParams(String cookieStr, String dataStr, Map<String, String> overrideMap) {
Map<String, String> 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<T> implements GoofishApi<T> {
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<String, String> buildQueryParams(String cookieStr, String dataStr) {
return buildQueryParams(cookieStr, dataStr, null);
}
private String getApiToken(String cookieStr) {
if (!StringUtils.hasText(cookieStr)) {
throw new IllegalArgumentException("缺少 Cookie 数据");

View File

@ -1,5 +1,7 @@
package top.biwin.xianyu.goofish.api;
import java.util.Map;
/**
* TODO
*
@ -13,6 +15,6 @@ public interface GoofishApi<T> {
String getVersion();
T call(String goofishId, String cookieStr, String dataStr);
T call(String goofishId, String cookieStr, Map<String,Object> dataMap);
}

View File

@ -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<String> {
}
@Override
public String call(String goofishId, String cookieStr, String dataStr) {
public String call(String goofishId, String cookieStr, Map<String,Object> data) {
String dataStr = JSONUtil.toJsonStr(data);
String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr));
log.debug("【{}】获取账号名 ApiUrl: {}", goofishId, apiUrl);
log.debug("【{}】获取账号名时使用的 Cookie 为: {}", goofishId, cookieStr);

View File

@ -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<GetRemoteProductApiVo> {
@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<String, Object> 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<Map<String, Object>> 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<String, Object> 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<String, Object> 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<String, String> selfMap() {
Map<String, String> map = new HashMap<>();
map.put("spm_cnt", "a21ybx.personal.0.0");
return map;
}
}

View File

@ -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<String> {
}
@Override
public String call(String goofishId, String cookieStr, String dataStr) {
public String call(String goofishId, String cookieStr, Map<String,Object> data) {
String dataStr = JSONUtil.toJsonStr(data);
String apiUrl = buildApiUrl() + HttpUtil.toParams(buildQueryParams(cookieStr, dataStr));
log.debug("【{}】获取闲鱼昵称 ApiUrl: {}", goofishId, apiUrl);
log.debug("【{}】获取闲鱼昵称时使用的 Cookie 为: {}", goofishId, cookieStr);

View File

@ -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<Long> {
@Override
public String getName() {
@ -38,7 +39,8 @@ public class GetUserIdApi extends GoofishAbstractApi<Long> {
}
@Override
public Long call(String goofishId, String cookieStr, String dataStr) {
public Long call(String goofishId, String cookieStr, Map<String,Object> 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);

View File

@ -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<String> {
}
@Override
public String call(String goofishId, String cookieStr, String dataStr) {
public String call(String goofishId, String cookieStr, Map<String,Object> 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);

View File

@ -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<String, Object> 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<String, Object> 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) {

View File

@ -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;

View File

@ -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<GoofishAccountEntity> accounts = goofishAccountRepository.findAll();
accounts.stream()

View File

@ -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;
});

View File

@ -53,14 +53,13 @@ public class GoofishAccountController {
* 获取所有Cookie的详细信息包括值和状态
* 对应Python的 get_cookies_details 接口
*/
@GetMapping("/details")
@GetMapping("/accounts")
public ResponseEntity<List<GoofishAccountResponse>> getAllGoofishAccountDetails() {
List<GoofishAccountEntity> 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<BaseResponse> 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<GoofishAccountResponse> 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<GoofishAccountResponse> updateAccountStatus(@PathVariable("id") String goofishId, @RequestBody AccountUpdateRequest request) {
GoofishAccountEntity account = goofishAccountRepository.findById(goofishId).orElseThrow(() -> new IllegalStateException("账号不存在"));
if (!CurrentUserUtil.isSuperAdmin()) {

View File

@ -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<List<ProductEntity>> getAllProducts() {
List<ProductEntity> 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<List<ProductEntity>> getProductByGoofishId(@PathVariable String goofishId) {
return ResponseEntity.ok(productRepository.findByGoofishId(goofishId));
}
@PostMapping("/products/get-all-form-account")
public ResponseEntity<FetchRemoteAllProductResponse> 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());
}
}

View File

@ -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