From 65e1328fa5dad410defa9edf0ffdc3da596d58f7 Mon Sep 17 00:00:00 2001 From: weicanie <2042365244@qq.com> Date: Mon, 15 Sep 2025 21:33:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87?= =?UTF-8?q?=E5=A4=96=E9=83=A8API=E6=8F=90=E4=BE=9B=E7=AE=80=E5=8E=86?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 3 +- src/store/resumeRepositoryManager.ts | 156 +++++++++++++++++++++++++++ src/store/useResumeStore.ts | 84 +++------------ src/types/repository.ts | 25 +++++ 4 files changed, 198 insertions(+), 70 deletions(-) create mode 100644 src/store/resumeRepositoryManager.ts create mode 100644 src/types/repository.ts diff --git a/.env b/.env index ecbb77b..b51bcd4 100644 --- a/.env +++ b/.env @@ -1,2 +1,3 @@ -FONTCONFIG_PATH=/var/task/fonts \ No newline at end of file +FONTCONFIG_PATH=/var/task/fonts +NEXT_PUBLIC_RESUME_REPO_URL = 简历数据仓库接口URL \ No newline at end of file diff --git a/src/store/resumeRepositoryManager.ts b/src/store/resumeRepositoryManager.ts new file mode 100644 index 0000000..c58e407 --- /dev/null +++ b/src/store/resumeRepositoryManager.ts @@ -0,0 +1,156 @@ +import { ResumeRepositoryManager } from '../types/repository'; +import { ResumeData } from '../types/resume'; +import { getFileHandle, verifyPermission } from '../utils/fileSystem'; + +// 同步简历到文件系统 +const syncResumeToFile = async (resumeData: ResumeData, prevResume?: ResumeData) => { + try { + const handle = await getFileHandle('syncDirectory'); + if (!handle) { + console.warn('No directory handle found'); + return; + } + + const hasPermission = await verifyPermission(handle); + if (!hasPermission) { + console.warn('No permission to write to directory'); + return; + } + + const dirHandle = handle as FileSystemDirectoryHandle; + + // 避免简历标题发生变化时保留不再使用的文件 + if (prevResume && prevResume.id === resumeData.id && prevResume.title !== resumeData.title) { + try { + await dirHandle.removeEntry(`${prevResume.title}.json`); + } catch (error) { + console.warn('Error deleting old file:', error); + } + } + const fileName = `${resumeData.title}.json`; + const fileHandle = await dirHandle.getFileHandle(fileName, { + create: true + }); + const writable = await fileHandle.createWritable(); + // 如果简历文件已存在,则直接替换其内容 + await writable.write(JSON.stringify(resumeData, null, 2)); + await writable.close(); + } catch (error) { + console.error('Error syncing resume to file:', error); + } +}; +// 从文件系统中删除简历 +// 不在具体业务逻辑中暴露文件句柄 +const deleteResumeFromFile = async (resume: ResumeData) => { + try { + const handle = await getFileHandle('syncDirectory'); + if (!handle) return; + + const hasPermission = await verifyPermission(handle); + if (!hasPermission) return; + + const dirHandle = handle as FileSystemDirectoryHandle; + try { + await dirHandle.removeEntry(`${resume.title}.json`); + } catch (error) {} + } catch (error) { + console.error('Error deleting resume file:', error); + } +}; +// 从文件系统中获取简历数据,并同步到store +const getResumesFromFiles = async (updateResumeFromFile: (resume: ResumeData) => void) => { + try { + const handle = await getFileHandle('syncDirectory'); + if (!handle) return; + + const hasPermission = await verifyPermission(handle); + if (!hasPermission) return; + + const dirHandle = handle as FileSystemDirectoryHandle; + + // @ts-ignore + for await (const entry of dirHandle.values()) { + if (entry.kind === 'file' && entry.name.endsWith('.json')) { + try { + const file = await entry.getFile(); + const content = await file.text(); + const resumeData = JSON.parse(content); + // 将简历数据同步到store + updateResumeFromFile(resumeData); + } catch (error) { + console.error('Error reading resume file:', error); + } + } + } + } catch (error) { + console.error('Error syncing resumes from files:', error); + } +}; + +const syncResuemToAPI = async (resumeData: ResumeData, prevResume?: ResumeData) => { + try { + await fetch(`${process.env.NEXT_PUBLIC_RESUME_REPO_URL}`, { + method: 'POST', + body: JSON.stringify({ actionType: 'sync', payload: { resumeData, prevResume } }), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}` + } + }); + } catch (error) { + console.error('Error syncing resume to database:', error); + } +}; + +const deleteResumeFromAPI = async (resumeData: ResumeData) => { + try { + await fetch(`${process.env.NEXT_PUBLIC_RESUME_REPO_URL}`, { + method: 'POST', + body: JSON.stringify({ actionType: 'delete', payload: { resumeData } }), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}` + } + }); + } catch (error) { + console.error('Error deleting resume from database:', error); + } +}; + +const getResumesFromAPI = async (updateResumeFromFile: (resume: ResumeData) => void) => { + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_RESUME_REPO_URL}`, { + method: 'POST', + body: JSON.stringify({ actionType: 'get' }), + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('token')}` + } + }); + const data = await response.json(); + // data.data: prisma-ai后端统一返回`SDF` + for (const resume of data.data) { + updateResumeFromFile(resume); + } + } catch (error) { + console.error('Error getting resumes from database:', error); + } +}; + +// 使用浏览器端的 File System Access API +const clientfileManager: ResumeRepositoryManager = { + syncResumeToRepository: syncResumeToFile, + deleteResumeFromRepository: deleteResumeFromFile, + getResumeFromRepository: getResumesFromFiles +}; + +// 使用外部API(服务器文件系统、数据库、OSS...) +const outerAPIManager: ResumeRepositoryManager = { + syncResumeToRepository: syncResuemToAPI, + deleteResumeFromRepository: deleteResumeFromAPI, + getResumeFromRepository: getResumesFromAPI +}; +const resumeRepoManager = clientfileManager; + +export { resumeRepoManager }; + diff --git a/src/store/useResumeStore.ts b/src/store/useResumeStore.ts index 5625777..e5a7ce0 100644 --- a/src/store/useResumeStore.ts +++ b/src/store/useResumeStore.ts @@ -3,7 +3,6 @@ import { initialResumeState, initialResumeStateEn, } from "@/config/initialResumeData"; -import { getFileHandle, verifyPermission } from "@/utils/fileSystem"; import { generateUUID } from "@/utils/uuid"; import { create } from "zustand"; import { @@ -16,6 +15,7 @@ import { Project, ResumeData, } from "../types/resume"; +import { resumeRepoManager } from "./resumeRepositoryManager"; interface ResumeStore { resumes: Record; activeResumeId: string | null; @@ -23,6 +23,7 @@ interface ResumeStore { createResume: (templateId: string | null) => string; deleteResume: (resume: ResumeData) => void; + getResumesFromRepository: () => void; duplicateResume: (resumeId: string) => string; updateResume: (resumeId: string, data: Partial) => void; setActiveResume: (resumeId: string) => void; @@ -61,50 +62,6 @@ interface ResumeStore { addResume: (resume: ResumeData) => string; } -// 同步简历到文件系统 -const syncResumeToFile = async ( - resumeData: ResumeData, - prevResume?: ResumeData -) => { - try { - const handle = await getFileHandle("syncDirectory"); - if (!handle) { - console.warn("No directory handle found"); - return; - } - - const hasPermission = await verifyPermission(handle); - if (!hasPermission) { - console.warn("No permission to write to directory"); - return; - } - - const dirHandle = handle as FileSystemDirectoryHandle; - - if ( - prevResume && - prevResume.id === resumeData.id && - prevResume.title !== resumeData.title - ) { - try { - await dirHandle.removeEntry(`${prevResume.title}.json`); - } catch (error) { - console.warn("Error deleting old file:", error); - } - } - - const fileName = `${resumeData.title}.json`; - const fileHandle = await dirHandle.getFileHandle(fileName, { - create: true, - }); - const writable = await fileHandle.createWritable(); - await writable.write(JSON.stringify(resumeData, null, 2)); - await writable.close(); - } catch (error) { - console.error("Error syncing resume to file:", error); - } -}; - export const useResumeStore = create( ( (set, get) => ({ @@ -150,7 +107,7 @@ export const useResumeStore = create( activeResume: newResume, })); - syncResumeToFile(newResume); + resumeRepoManager.syncResumeToRepository(newResume); return id; }, @@ -165,7 +122,7 @@ export const useResumeStore = create( ...data, }; - syncResumeToFile(updatedResume, resume); + resumeRepoManager.syncResumeToRepository(updatedResume, resume); return { resumes: { @@ -180,6 +137,10 @@ export const useResumeStore = create( }); }, + getResumesFromRepository: () => { + resumeRepoManager.getResumeFromRepository(get().updateResumeFromFile); + }, + // 从文件更新,直接更新resumes updateResumeFromFile: (resume) => { set((state) => ({ @@ -197,33 +158,18 @@ export const useResumeStore = create( } }, - deleteResume: (resume) => { + deleteResume: resume => { const resumeId = resume.id; - set((state) => { + set(state => { const { [resumeId]: _, activeResume, ...rest } = state.resumes; return { resumes: rest, activeResumeId: null, - activeResume: null, + activeResume: null }; }); - - (async () => { - try { - const handle = await getFileHandle("syncDirectory"); - if (!handle) return; - - const hasPermission = await verifyPermission(handle); - if (!hasPermission) return; - - const dirHandle = handle as FileSystemDirectoryHandle; - try { - await dirHandle.removeEntry(`${resume.title}.json`); - } catch (error) {} - } catch (error) { - console.error("Error deleting resume file:", error); - } - })(); + + resumeRepoManager.deleteResumeFromRepository(resume); }, duplicateResume: (resumeId) => { @@ -288,7 +234,7 @@ export const useResumeStore = create( activeResume: updatedResume, }; - syncResumeToFile(updatedResume, state.activeResume); + resumeRepoManager.syncResumeToRepository(updatedResume, state.activeResume); return newState; }); @@ -611,7 +557,7 @@ export const useResumeStore = create( activeResumeId: resume.id, })); - syncResumeToFile(resume); + resumeRepoManager.syncResumeToRepository(resume); return resume.id; }, }) diff --git a/src/types/repository.ts b/src/types/repository.ts new file mode 100644 index 0000000..6bc7242 --- /dev/null +++ b/src/types/repository.ts @@ -0,0 +1,25 @@ +import { + ResumeData +} from './resume'; + +/** + * 简历数据仓库管理器 + */ +interface ResumeRepositoryManager { + /** + * 进行简历数据的创建(C)和更新(U) + */ + syncResumeToRepository: (resumeData: ResumeData, prevResume?: ResumeData) => Promise; + /** + * 进行简历数据的删除(D) + */ + deleteResumeFromRepository: (resume: ResumeData) => Promise; + /** + * 进行简历数据的获取(R) + * @description 从文件系统或数据库中获取简历数据,并同步到store中 + */ + getResumeFromRepository: (updateResumeFromFile: (resume: ResumeData) => void) => Promise; +} + +export { type ResumeRepositoryManager }; +