/** * 获取在售商品信息 * @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} - 返回在售商品信息数组 */ 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} - 返回登录结果,包含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} 返回新的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('; '); };