Skip to content

Commit

Permalink
增加代理池
Browse files Browse the repository at this point in the history
  • Loading branch information
jooooock committed Sep 2, 2024
1 parent c12df46 commit a69e954
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 64 deletions.
2 changes: 1 addition & 1 deletion components/Header.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</a>
</div>
<div v-if="loginAccount" class="flex flex-col sm:flex-row items-center sm:space-x-2 ml-10">
<img v-if="loginAccount.head_img" :src="loginAccount.head_img" alt="" class="rounded-full size-10">
<img v-if="loginAccount.head_img" :src="proxyImage(loginAccount.head_img)" alt="" class="rounded-full size-10">
<span v-if="loginAccount.nick_name">{{loginAccount.nick_name}}</span>
</div>
</header>
Expand Down
13 changes: 12 additions & 1 deletion config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* 文章列表每页大小
* 文章列表每页大小,20为最大有效值
*/
export const ARTICLE_LIST_PAGE_SIZE = 20;

Expand All @@ -17,6 +17,17 @@ export const ACCOUNT_TYPE: Record<number, string> = {
2: '服务号'
}

/**
* 代理池
*/
export const AVAILABLE_PROXY_LIST: string[] = [
'https://vproxy-wechat-article.deno.dev/api/proxy',
'https://vproxy-01.deno.dev/',
'https://vproxy-02.deno.dev/',
'https://vproxy-03.deno.dev/',
'https://vproxy-04.deno.dev/',
]

/**
* 扫码状态
*/
Expand Down
17 changes: 1 addition & 16 deletions server/api/download.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,10 @@ interface DownloadQuery {
export default defineEventHandler(async (event) => {
const query = getQuery<DownloadQuery>(event)

let html: string = await proxyMpRequest({
return await proxyMpRequest({
event: event,
endpoint: query.url,
method: 'GET',
withCredentials: false,
}).then(resp => resp.text())

// 处理 img data-src
html = html.replaceAll(/(<img[^>]+?)data-src="((https?|\/\/)[^"]+)"/gs, (match, p1, p2) => {
return `${p1}src="${proxyImage(p2)}"`
});
// 处理 background url
html = html.replaceAll(/((?:background|background-image): url\((?:&quot;)?)((?:https?|\/\/)[^)]+?)((?:&quot;)?\))/gs, (match, p1, p2, p3) => {
return `${p1}${proxyImage(p2)}${p3}`
})

return html
})

function proxyImage(url: string) {
return `https://vproxy-wechat-article.deno.dev/api/proxy?url=${encodeURIComponent(url)}`
}
159 changes: 159 additions & 0 deletions utils/download.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import JSZip from "jszip";
import mime from "mime";
import {AVAILABLE_PROXY_LIST} from "~/config";


let vproxy: string[] = JSON.parse(JSON.stringify(AVAILABLE_PROXY_LIST));

async function downloadImage(img: HTMLImageElement, proxy: string, zip: JSZip) {
try {
const imgData = await $fetch<Blob>(`${proxy}?url=${encodeURIComponent(img.src)}`)
const uuid = new Date().getTime() + Math.random().toString()
const ext = mime.getExtension(imgData.type)
zip.file(`assets/${uuid}.${ext}`, imgData)

// 改写html中的引用路径,指向本地图片文件
img.src = `./assets/${uuid}.${ext}`
} catch (e) {
console.info('图片下载失败: ', img.src)
console.info('proxy: ', proxy)
throw e
}
}

async function downloadBackgroundImage(url: string, proxy: string, zip: JSZip, url2pathMap: Map<string, string>) {
try {
const imgData = await $fetch<Blob>(`${proxy}?url=${encodeURIComponent(url)}`)
const uuid = new Date().getTime() + Math.random().toString()
const ext = mime.getExtension(imgData.type)

zip.file(`assets/${uuid}.${ext}`, imgData)
url2pathMap.set(url, `assets/${uuid}.${ext}`)
} catch (e) {
console.info('背景图片下载失败: ', url)
console.error(e)
}
}

let _resolve: (value: string) => void

// 获取代理资源
function acquireProxyResource(): Promise<string> {
return new Promise((resolve) => {
if (vproxy.length > 0) {
resolve(vproxy.shift()!)
} else {
_resolve = resolve
}
})
}
// 释放代理资源
function releaseProxyResource(proxy: string) {
if (_resolve) {
_resolve(proxy)
} else {
vproxy.push(proxy)
}
}
function reset() {
vproxy = JSON.parse(JSON.stringify(AVAILABLE_PROXY_LIST))
}

// 并发下载图片
export async function downloadImages(imgs: HTMLImageElement[], zip: JSZip): Promise<boolean> {
const errors = new WeakMap<HTMLImageElement, number>()
const total = imgs.length

reset()

return new Promise(async (resolve, reject) => {
let count = 0
let stop = false

for (const img of imgs) {
if (stop) {
break
}
img.src = img.src || img.dataset.src!

if (!img.src) {
console.warn('img元素的src为空')
continue
}
errors.set(img, errors.get(img) || 0)

const proxy = await acquireProxyResource()
downloadImage(img, proxy, zip).then(() => {
// 归还 resource
count++
releaseProxyResource(proxy)
}).catch(() => {
console.warn(img.src)
errors.set(img, errors.get(img)! + 1)

if (errors.get(img)! >= 3) {
// 该图片已经失败了3次,则结束整个过程
stop = true
reject(new Error('图片下载失败'))
return
}

imgs.push(img)
// 失败,延迟2s再归还该资源
setTimeout(() => {
releaseProxyResource(proxy)
}, 2000)
}).finally(() => {
if (count === total) {
resolve(true)
}
})
}
})
}

export async function downloadBgImages(bgImgUrls: string[], zip: JSZip): Promise<Map<string, string>> {
const url2pathMap = new Map<string, string>()
const errors = new Map<string, number>()
const total = bgImgUrls.length

reset()

return new Promise(async (resolve, reject) => {
let count = 0
let stop = false

for (const url of bgImgUrls) {
if (stop) {
break
}
errors.set(url, errors.get(url) || 0)

const proxy = await acquireProxyResource()
downloadBackgroundImage(url, proxy, zip, url2pathMap).then(() => {
// 归还 resource
count++
releaseProxyResource(proxy)
}).catch(() => {
errors.set(url, errors.get(url)! + 1)

if (errors.get(url)! >= 3) {
// 该图片已经失败了3次,则结束整个过程
stop = true
reject(new Error('背景图片下载失败'))
return
}

bgImgUrls.push(url)
// 失败,延迟2s再归还该资源
setTimeout(() => {
releaseProxyResource(proxy)
}, 2000)
}).finally(() => {
if (count === total) {
resolve(url2pathMap)
}
})
}
})
}
63 changes: 17 additions & 46 deletions utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {updateArticleCache} from "~/store/article";
import {ARTICLE_LIST_PAGE_SIZE, ACCOUNT_LIST_PAGE_SIZE} from "~/config";
import {getAssetCache, updateAssetCache} from "~/store/assetes";
import {updateAPICache} from "~/store/api";
import {downloadBgImages, downloadImages} from "~/utils/download";


export function proxyImage(url: string) {
Expand Down Expand Up @@ -72,65 +73,35 @@ export async function packHTMLAssets(html: string, zip?: JSZip) {

zip.folder('assets')


// 下载所有的图片 (图片地址已经在下载html接口中替换过了)
const imgs = $pageContent.querySelectorAll<HTMLImageElement>('img[src]')
for (const img of imgs) {
if (!img.src) {
console.warn('img元素的src为空')
continue
}

try {
const imgData = await $fetch<Blob>(img.src)
const uuid = new Date().getTime() + Math.random().toString()
const ext = mime.getExtension(imgData.type)
zip.file(`assets/${uuid}.${ext}`, imgData)

// 改写html中的引用路径,指向本地图片文件
img.src = `./assets/${uuid}.${ext}`
} catch (e) {
console.info('图片下载失败: ', img.src)
console.error(e)
}
// 下载所有的图片
const imgs = $pageContent.querySelectorAll<HTMLImageElement>('img')
if (imgs.length > 0) {
await downloadImages([...imgs], zip)
}


// 下载背景图片
// 背景图片无法用选择器选中并修改,因此用正则进行匹配替换
// 下载背景图片 背景图片无法用选择器选中并修改,因此用正则进行匹配替换
let pageContentHTML = $pageContent.outerHTML
const url2pathMap = new Map<string, string>()

// 收集所有的背景图片地址
const bgImageURLs = new Set<string>()
pageContentHTML.replaceAll(/((?:background|background-image): url\((?:&quot;)?)((?:https?|\/\/)[^)]+?)((?:&quot;)?\))/gs, (match, p1, url, p3) => {
bgImageURLs.add(url)
return `${p1}${url}${p3}`
})
for (const url of bgImageURLs) {
try {
const imgData = await $fetch<Blob>(url)
const uuid = new Date().getTime() + Math.random().toString()
const ext = mime.getExtension(imgData.type)

zip.file(`assets/${uuid}.${ext}`, imgData)
url2pathMap.set(url, `assets/${uuid}.${ext}`)
} catch (e) {
console.info('背景图片下载失败: ', url)
console.error(e)
}
if (bgImageURLs.size > 0) {
const url2pathMap = await downloadBgImages([...bgImageURLs], zip)
pageContentHTML = pageContentHTML.replaceAll(/((?:background|background-image): url\((?:&quot;)?)((?:https?|\/\/)[^)]+?)((?:&quot;)?\))/gs, (match, p1, url, p3) => {
if (url2pathMap.has(url)) {
const path = url2pathMap.get(url)!
return `${p1}./${path}${p3}`
} else {
console.warn('背景图片丢失: ', url)
return `${p1}${url}${p3}`
}
})
}

pageContentHTML = pageContentHTML.replaceAll(/((?:background|background-image): url\((?:&quot;)?)((?:https?|\/\/)[^)]+?)((?:&quot;)?\))/gs, (match, p1, url, p3) => {
if (url2pathMap.has(url)) {
const path = url2pathMap.get(url)!
return `${p1}./${path}${p3}`
} else {
console.warn('背景图片丢失: ', url)
return `${p1}${url}${p3}`
}
})

// 下载样式表
let localLinks: string = ''
const links = document.querySelectorAll<HTMLLinkElement>('head link[rel="stylesheet"]')
Expand Down

0 comments on commit a69e954

Please sign in to comment.