diff --git "a/source/_posts/\345\246\202\344\275\225\344\275\277\347\224\250nextjs\345\256\236\347\216\260\344\270\212\344\274\240pdf.md" "b/source/_posts/\345\246\202\344\275\225\344\275\277\347\224\250nextjs\345\256\236\347\216\260\344\270\212\344\274\240pdf.md" new file mode 100644 index 0000000..708e632 --- /dev/null +++ "b/source/_posts/\345\246\202\344\275\225\344\275\277\347\224\250nextjs\345\256\236\347\216\260\344\270\212\344\274\240pdf.md" @@ -0,0 +1,107 @@ +--- +title: 如何使用nextjs实现上传pdf +date: 2023-12-09 14:21:53 +tags: +--- +之前研究gpt4-pdf-chatbot-langchain这个项目,觉得挺有意思,竟然可以让chatgpt去读一本书,还是js写的。不过这项目有一点不方便,他需要用nodejs解析pdf,然后用openai提供的api把api向量化,接着在浏览器里才能基于这本书的内容向chatgpt提问。 + +然后我就想能不能把所有的操作入口都放在浏览器上,在浏览器上上传一个pdf,然后后端拿到上传的pdf,解析pdf,向量化,后面的操作和原项目一样。基于这样的想法,我写了以下的代码。 + +```javascript +//pages/index.tsx +function App(){ + const onFileUplload = e=>{ + const file = event.target.files?.[0] + const formData = new FormData() + formData.append('file', file) + return fetch('/api/upload',{ + method:'post', + body: formData, + }).then((resp)=> { + if(resp.status !== 200) return Promise.reject(resp.statusText) + return resp.json() + }) + } + return ( +
+ +
+ ) +} +``` +```javascript +//pegae/api/upload.ts +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, + +){ + const buffer = Buffer.from(req.body) + console.log(buffer); + // 处理buffer,解析pdf +} + +export const config = { + api:{ + bodyParser: { + sizeLimit: '10mb' + } + } +} +``` +以上代码后续在解析pdf的过程中报错了,说我的pdf是加密的,很明显不是这个原因。经过一番查找发现,发现用以上代码拿到的buffer与直接用nodejs读pdf拿到的buffer长度不一样。 + +buffer from browser upload +![839ccc20-58c2-4830-94fb-f1ed9af5d960-buffer_from_upload.png](https://likaiqiang-blog.oss-cn-beijing.aliyuncs.com/images/839ccc20-58c2-4830-94fb-f1ed9af5d960-buffer_from_upload.png) + +buffer from nodejs +![5a5738e6-2085-4381-953b-173466716bd9-image.png](https://likaiqiang-blog.oss-cn-beijing.aliyuncs.com/images/5a5738e6-2085-4381-953b-173466716bd9-image.png) + +显然,[buffer from browser upload]有问题,那这一步的buffer是从哪里来的,const buffer = Buffer.from(req.body),把这里的req.body打印出来看看,发现是个乱码的字符串,很明显不对,后续再把这个错误的字符串转换成buffer,后面的一系列步骤都错了。 + +这个错误到底是怎么来的,怎么会拿到一个乱码的字符串呢?那就从源头查起, 我们知道浏览器上的http请求,不管请求体中是什么数据,最终都会转化为二进制,就是所谓的数字信号,然后数电转模电,单纯的数字信号是不能发射的,只能转换成模拟信号,就是我们说的波,发射出去。目标服务器在接收到这个波以后,需要把模拟信号转换成数字信号,就是二进制,然后才能处理这条http请求。数电模电之间的转换是不可能出错的,按照以上理论,我们在服务端拿到的这条请求应该是个二进制流才对,为什么会是乱码的字符串呢,所以,最大的可能就是nextjs处理了这个二进制流,但是没有处理好。所以我们需要拿到原始的二进制流。 +```javascript +import { getBoundary, parse } from 'parse-multipart-data' +export default async function handler( + req: NextApiRequest, + res: NextApiResponse, + +){ + const chunks: Uint8Array[] = [] + let size = 0 + const sizeLimit = 10 * 1024 * 1024 + + req.on('data',(chunk)=>{ + size += chunk.length + if(size > sizeLimit){ + res.status(413).send('payload too large') + req.connection.destroy() + return + } + chunks.push(chunk) + }) + req.on('end',async ()=>{ + const boundary = getBoundary(req.rawHeaders.join(';')) + const completeBuffer = Buffer.concat(chunks) + const parts = parse(completeBuffer, boundary) + const [file] = parts + console.log('file.data',file.data); + }) + req.on('error',e=>{ + console.log(e) + res.status(500).send('an error occurred') + }) +} + +export const config = { + api:{ + bodyParser: false + } +} +``` +代码一目了然,先禁掉nextjs内置的bodyParser,直接监听req的data与end事件,收集chunk,然后通过getBoundary与parse从formdata中解析出file,这里log出的file.data就是正确的数据,长度是1086938。 + +拿到正确的buffer以后就可以调用pdf-parser解析了。 + + + diff --git "a/source/_posts/\346\225\264\347\220\206react\351\207\214\351\235\242\351\202\243\344\272\233\345\256\271\346\230\223\350\256\251\344\272\272\346\267\267\346\267\206\347\232\204\346\246\202\345\277\265-u-1F602.md" "b/source/_posts/\346\225\264\347\220\206react\351\207\214\351\235\242\351\202\243\344\272\233\345\256\271\346\230\223\350\256\251\344\272\272\346\267\267\346\267\206\347\232\204\346\246\202\345\277\265-u-1F602.md" index 9bcedc3..a8df60c 100644 --- "a/source/_posts/\346\225\264\347\220\206react\351\207\214\351\235\242\351\202\243\344\272\233\345\256\271\346\230\223\350\256\251\344\272\272\346\267\267\346\267\206\347\232\204\346\246\202\345\277\265-u-1F602.md" +++ "b/source/_posts/\346\225\264\347\220\206react\351\207\214\351\235\242\351\202\243\344\272\233\345\256\271\346\230\223\350\256\251\344\272\272\346\267\267\346\267\206\347\232\204\346\246\202\345\277\265-u-1F602.md" @@ -263,9 +263,9 @@ useState、useMemo、useCallback、useRef、useContext、useReducer、useEffect ### commit阶段的hooks useEffect与useLayoutEffect的回调函数。 -先说useLayoutEffect。这个hook会在dom更新完毕,但是浏览器还没有来得及绘制之前同步执行。这句话是什么意思呢,我们知道javascript只是用来操作dom,像setState之类操作或者我们手动操作dom只是改变了内存里一颗dom树上的某些节点,真正把dom树绘制成屏幕上形形色色的画面是浏览器完成的,假设我们浏览器一秒绘制60帧,那绘制一帧的时间就是16ms,而javascript是单线程语言,执行js代码与ui绘制是互斥的,所以如果我们在16ms内用10ms的时间来更新dom树,那么还剩最多6ms用来执行useLayoutEffect。 +先说useLayoutEffect。这个hook会在dom更新完毕,但是浏览器还没有来得及绘制之前同步执行。这句话是什么意思呢,我们知道javascript只是用来操作dom,像setState之类操作或者我们手动操作dom只是改变了内存里一颗dom树上的某些节点,真正把dom树绘制成屏幕上形形色色的画面是浏览器完成的,假设我们浏览器一秒绘制60帧,那绘制一帧的时间就是16ms,而javascript是单线程语言,执行js代码与ui绘制是互斥的,所以如果我们在16ms内用10ms的时间来更新dom树,那么还剩6ms用来执行useLayoutEffect与浏览器绘制界面。 -所以这个hook内部适合执行一些短小精悍的代码,假如执行时间过长,那这一次更新就没有被绘制,人就会感觉到卡。 +所以这个hook内部适合执行一些短小精悍的代码,假如执行时间过长,浏览器没有足够的时间在一帧内完成界面绘制,就会造成卡顿。 对比介绍类组件时也有个在commit阶段执行的函数,getSnapshotBeforeUpdate,useLayoutEffect与getSnapshotBeforeUpdate的执行时机却有细微的差别,前者是在真实dom更新完毕浏览器还未绘制之前触发,这时拿到的是更新后的dom,后者是在真实dom还未更新即将更新时触发,这时拿到的是更新前的dom。