378 lines
11 KiB
JavaScript
378 lines
11 KiB
JavaScript
/**
|
||
* 获取在售商品信息
|
||
* @param {String} keyword - 搜索关键词(ISBN或书名)
|
||
* @param {String} sortType - 排序类型
|
||
* @param {String} conditionValue - 品相条件
|
||
* @param {String} cookies - 用户cookies
|
||
* @param {Object} options - 可选参数对象
|
||
* @param {String} options.publisher - 出版社(版权页比价时使用)
|
||
* @param {String} options.author - 作者(版权页比价时使用)
|
||
* @param {Boolean} options.autoSwitchAccount - 是否自动切换账号(默认为true)
|
||
* @param {Number} options.retryCount - 重试次数(内部使用)
|
||
* @returns {Promise<Array>} - 返回在售商品信息数组
|
||
*/
|
||
export const fetchOnSaleProducts = async (keyword, sortType, conditionValue, cookies, options = {}) => {
|
||
try {
|
||
// 设置默认值
|
||
const autoSwitchAccount = options.autoSwitchAccount !== false; // 默认为true
|
||
const retryCount = options.retryCount || 0;
|
||
const MAX_RETRY = 3; // 最大重试次数
|
||
|
||
// 首先检查cookies是否存在,不存在则尝试从本地获取最新的
|
||
if (!cookies) {
|
||
cookies = uni.getStorageSync('UserInfoCookies');
|
||
console.log('从本地存储获取cookies:', cookies);
|
||
}
|
||
|
||
// 如果还是没有cookies,提示用户登录
|
||
if (!cookies) {
|
||
uni.showToast({
|
||
title: '请先在设置页面登录孔网账号',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
return [];
|
||
}
|
||
|
||
// 构建直接调用孔夫子API的参数
|
||
const searchParams = {
|
||
searchType: 'category',
|
||
dataType: '0',
|
||
page: '1',
|
||
keyword: keyword,
|
||
sortType: sortType,
|
||
quality: conditionValue,
|
||
actionPath: 'sortType,quality',
|
||
quaSelect: '2',
|
||
userArea: '13003000000'
|
||
};
|
||
|
||
// 如果是版权页比价,添加出版社和作者参数
|
||
if (options.publisher) {
|
||
searchParams.press = options.publisher;
|
||
}
|
||
if (options.author) {
|
||
searchParams.author = options.author;
|
||
}
|
||
|
||
// 构建URL查询字符串
|
||
const queryString = Object.entries(searchParams)
|
||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
||
.join('&');
|
||
|
||
// 孔夫子API的URL
|
||
const apiUrl = `https://search.kongfz.com/pc-gw/search-web/client/pc/product/keyword/list?${queryString}`;
|
||
|
||
// 调用孔夫子API
|
||
const [err, res] = await uni.request({
|
||
url: apiUrl,
|
||
method: 'GET',
|
||
header: {
|
||
'Cookie': `PHPSESSID=${cookies}`
|
||
}
|
||
});
|
||
|
||
// 处理错误:请求失败时 err 不为 null
|
||
if (err) {
|
||
console.error('请求失败:', err);
|
||
|
||
// 如果允许自动切换账号且未超过最大重试次数
|
||
if (autoSwitchAccount && retryCount < MAX_RETRY) {
|
||
console.log(`尝试切换账号并重试(${retryCount + 1}/${MAX_RETRY})...`);
|
||
const newCookie = await tryNextAccount();
|
||
if (newCookie) {
|
||
// 使用新cookie重试
|
||
return fetchOnSaleProducts(keyword, sortType, conditionValue, newCookie, {
|
||
...options,
|
||
retryCount: retryCount + 1
|
||
});
|
||
}
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '网络请求失败',
|
||
icon: 'none'
|
||
});
|
||
return [];
|
||
}
|
||
|
||
// 处理接口返回的数据
|
||
console.log("res", res);
|
||
const responseData = res.data || {}; // 避免 res.data 为 undefined
|
||
console.log("在售商品原始数据", responseData);
|
||
|
||
// 检查孔夫子API返回的状态
|
||
if (!responseData || responseData.status !== 1) {
|
||
const errorMsg = responseData.message || '请求出错';
|
||
console.error('孔夫子API错误:', errorMsg);
|
||
|
||
// 检查是否是登录失效
|
||
const isLoginExpired = errorMsg.includes('登录') ||
|
||
errorMsg.includes('cookie') ||
|
||
(responseData.errCode && ["102", "1000", "1001", "1002", "1003", "1004", "1005", "1006", "1007", "1008", "1009"].includes(responseData.errCode))||
|
||
(responseData.errType && ["102", "1000", "1001", "1002", "1003", "1004", "1005", "1006", "1007", "1008", "1009"].includes(responseData.errType));
|
||
console.log("isLoginExpired", isLoginExpired);
|
||
if (isLoginExpired) {
|
||
// 如果允许自动切换账号且未超过最大重试次数
|
||
if (autoSwitchAccount && retryCount < MAX_RETRY) {
|
||
console.log(`登录已过期,尝试切换账号并重试(${retryCount + 1}/${MAX_RETRY})...`);
|
||
const newCookie = await tryNextAccount();
|
||
if (newCookie) {
|
||
// 使用新cookie重试
|
||
return fetchOnSaleProducts(keyword, sortType, conditionValue, newCookie, {
|
||
...options,
|
||
retryCount: retryCount + 1
|
||
});
|
||
}
|
||
}
|
||
|
||
uni.showToast({
|
||
title: '登录已过期,请重新登录',
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
} else {
|
||
uni.showToast({
|
||
title: errorMsg,
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
}
|
||
return [];
|
||
}
|
||
|
||
// 解析孔夫子API的响应数据
|
||
const products = [];
|
||
if (responseData.data && responseData.data.itemResponse && responseData.data.itemResponse.list) {
|
||
const list = responseData.data.itemResponse.list;
|
||
|
||
// 只取前12条记录
|
||
const limit = Math.min(12, list.length);
|
||
for (let i = 0; i < limit; i++) {
|
||
const item = list[i];
|
||
|
||
// 解析运费信息
|
||
let shippingFee = 0;
|
||
let shippingFeeText = '0';
|
||
if (item.postage && item.postage.shippingList && item.postage.shippingList.length > 0) {
|
||
const shipping = item.postage.shippingList[0];
|
||
shippingFee = parseFloat(shipping.shippingFee || 0);
|
||
shippingFeeText = shipping.shippingFeeText || '0';
|
||
}
|
||
|
||
// 处理价格,去除非数字字符
|
||
const priceText = item.priceText || '0';
|
||
const cleanPrice = parseFloat(priceText.replace(/[^\d.]/g, ''));
|
||
const totalPrice = Number((cleanPrice + shippingFee).toFixed(2));
|
||
|
||
products.push({
|
||
id: item.id || null,
|
||
bookName: item.title || '未知书名',
|
||
imageUrl: item.imgBigUrl ? item.imgBigUrl.trim() : null,
|
||
totalPrice: totalPrice,
|
||
bookPrice: cleanPrice,
|
||
shopName: item.shopName,
|
||
shippingFee: shippingFee,
|
||
shippingFeeText: shippingFeeText,
|
||
author: item.author || '未知作者',
|
||
publisher: item.press || '未知出版社',
|
||
qualityText: item.qualityText || '未知品相',
|
||
});
|
||
}
|
||
}
|
||
|
||
console.log('获取在售商品信息成功:', products);
|
||
return products;
|
||
} catch (error) {
|
||
console.error('获取在售商品信息失败:', error);
|
||
uni.showToast({
|
||
title: '获取在售商品信息失败',
|
||
icon: 'none'
|
||
});
|
||
return [];
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 刷新cookies
|
||
* @param {String} username - 孔网账号
|
||
* @param {String} password - 孔网密码
|
||
* @returns {Promise<Object>} - 返回登录结果,包含cookies
|
||
*/
|
||
const refreshCookies = async (username, password) => {
|
||
try {
|
||
// 第一步:先发送一个GET请求获取初始会话Cookie
|
||
const initResponse = await uniRequestPromise({
|
||
url: 'https://login.kongfz.com/Pc/Login/account',
|
||
method: 'GET'
|
||
});
|
||
|
||
// 提取初始响应中的Cookie
|
||
const initCookies = extractCookiesFromHeaders(initResponse.header);
|
||
|
||
// 第二步:发送登录请求,携带用户名和密码
|
||
const loginData = {
|
||
loginName: username,
|
||
loginPass: password
|
||
};
|
||
|
||
const loginResponse = await uniRequestPromise({
|
||
url: 'https://login.kongfz.com/Pc/Login/account',
|
||
method: 'POST',
|
||
data: loginData,
|
||
header: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
'Cookie': formatCookieHeader(initCookies)
|
||
}
|
||
});
|
||
|
||
// 提取登录响应中的Cookie
|
||
const loginCookies = extractCookiesFromHeaders(loginResponse.header);
|
||
|
||
// 合并所有Cookie
|
||
const allCookies = {
|
||
...initCookies,
|
||
...loginCookies
|
||
};
|
||
|
||
return {
|
||
success: true,
|
||
cookies: allCookies,
|
||
responseData: loginResponse.data
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('刷新cookies失败:', error);
|
||
return {
|
||
success: false,
|
||
error: error.message || '登录请求发生错误'
|
||
};
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 尝试切换到下一个可用账号并获取新cookie
|
||
* @returns {Promise<String|null>} 返回新的cookie或null(如果无法切换账号)
|
||
*/
|
||
const tryNextAccount = async () => {
|
||
try {
|
||
// 获取账号列表
|
||
const accountsStr = uni.getStorageSync('accounts');
|
||
if (!accountsStr) {
|
||
console.log('没有可用的备用账号');
|
||
return null;
|
||
}
|
||
|
||
const accounts = JSON.parse(accountsStr);
|
||
if (!accounts || accounts.length === 0) {
|
||
console.log('账号列表为空');
|
||
return null;
|
||
}
|
||
|
||
// 获取当前活跃账号索引
|
||
let currentIndex = uni.getStorageSync('currentAccountIndex');
|
||
currentIndex = currentIndex !== '' ? parseInt(currentIndex) : 0;
|
||
|
||
// 计算下一个账号索引
|
||
const nextIndex = (currentIndex + 1) % accounts.length;
|
||
|
||
// 如果循环回到了当前账号,说明只有一个账号或已经尝试了所有账号
|
||
if (nextIndex === currentIndex) {
|
||
console.log('已尝试所有账号,无法继续切换');
|
||
return null;
|
||
}
|
||
|
||
// 获取下一个账号信息
|
||
const nextAccount = accounts[nextIndex];
|
||
|
||
// 清除原有cookie
|
||
uni.removeStorageSync('cookies');
|
||
uni.removeStorageSync('UserInfoCookies');
|
||
|
||
// 使用下一个账号登录
|
||
const result = await refreshCookies(nextAccount.username, nextAccount.password);
|
||
|
||
// 检查登录结果
|
||
if (result.success && result.cookies && result.cookies.PHPSESSID) {
|
||
// 更新账号状态
|
||
accounts.forEach(acc => acc.isActive = false);
|
||
accounts[nextIndex].isActive = true;
|
||
|
||
// 保存cookie
|
||
const newCookie = result.cookies.PHPSESSID;
|
||
uni.setStorageSync('cookies', newCookie);
|
||
uni.setStorageSync('UserInfoCookies', newCookie);
|
||
uni.setStorageSync('KongfzUserName', nextAccount.username);
|
||
|
||
// 保存更新后的账号列表和当前索引
|
||
uni.setStorageSync('accounts', JSON.stringify(accounts));
|
||
uni.setStorageSync('currentAccountIndex', nextIndex);
|
||
|
||
// 发送自定义事件通知页面更新账号状态
|
||
uni.$emit('accountSwitched', {
|
||
index: nextIndex,
|
||
username: nextAccount.username
|
||
});
|
||
|
||
console.log(`成功切换到账号: ${nextAccount.username}`);
|
||
return newCookie;
|
||
} else {
|
||
console.log('切换账号失败,尝试下一个账号');
|
||
// 递归尝试下一个账号
|
||
accounts[nextIndex].isActive = false;
|
||
uni.setStorageSync('accounts', JSON.stringify(accounts));
|
||
uni.setStorageSync('currentAccountIndex', nextIndex);
|
||
return tryNextAccount();
|
||
}
|
||
} catch (error) {
|
||
console.error('切换账号出错:', error);
|
||
return null;
|
||
}
|
||
};
|
||
|
||
/**
|
||
* 将 uni.request 转换为 Promise 形式
|
||
*/
|
||
const uniRequestPromise = (options) => {
|
||
return new Promise((resolve, reject) => {
|
||
uni.request({
|
||
...options,
|
||
success: (res) => resolve(res),
|
||
fail: (err) => reject(err)
|
||
});
|
||
});
|
||
};
|
||
|
||
/**
|
||
* 从响应头中提取 Cookies
|
||
*/
|
||
const extractCookiesFromHeaders = (headers) => {
|
||
const cookies = {};
|
||
const cookieHeaders = headers['Set-Cookie'] || headers['set-cookie'];
|
||
|
||
if (!cookieHeaders) return cookies;
|
||
|
||
// 处理可能是数组或字符串的 Cookie 头
|
||
const cookieList = Array.isArray(cookieHeaders) ?
|
||
cookieHeaders : [cookieHeaders];
|
||
|
||
cookieList.forEach(cookieStr => {
|
||
// 提取 cookie 名值对(忽略路径、过期时间等属性)
|
||
const cookieParts = cookieStr.split(';')[0].split('=');
|
||
if (cookieParts.length >= 2) {
|
||
cookies[cookieParts[0].trim()] = cookieParts[1].trim();
|
||
}
|
||
});
|
||
|
||
return cookies;
|
||
};
|
||
|
||
/**
|
||
* 将 Cookie 对象格式化为请求头字符串
|
||
*/
|
||
const formatCookieHeader = (cookies) => {
|
||
return Object.entries(cookies)
|
||
.map(([key, value]) => `${key}=${value}`)
|
||
.join('; ');
|
||
};
|