初始化

This commit is contained in:
zk
2026-05-09 14:55:30 +08:00
parent a64ef63433
commit 1430b38b20
16 changed files with 6967 additions and 83 deletions
+10
View File
@@ -0,0 +1,10 @@
import { config } from "~config"
import { createHttp } from "./request"
/** Python AI 后端接口 */
const http = createHttp(config.aiBaseApi)
/** 健康检查 */
export function healthCheck() {
return http.get('/health/')
}
+15
View File
@@ -0,0 +1,15 @@
import { config } from "~config"
import { createHttp } from "./request"
/** Java 后端接口 */
const http = createHttp(config.dataBaseApi)
/** 根据岗位来源地址查询岗位信息 */
export function findJobBySourceUrl(sourceUrl: string) {
return http.get('/job/findByUrl', { params: { sourceUrl } })
}
/** 校验登录状态 */
export function checkLogin() {
return http.get('/public/checkLogin')
}
+73
View File
@@ -0,0 +1,73 @@
import { getCookieValue } from "~utils/cookie"
interface ApiResult<T = unknown> {
code: string | number
msg: string
data: T
timestamp: string
uuid: string
}
interface RequestOptions {
/** 路径参数,替换 url 中的 :key 占位符 */
pathParams?: Record<string, string | number>
/** query 参数,拼接到 url 后面 */
params?: Record<string, string>
/** 请求体 */
body?: unknown
/** 自定义请求头 */
headers?: Record<string, string>
}
/** 替换路径参数,如 /user/:id → /user/123 */
function resolvePath(url: string, pathParams?: Record<string, string | number>): string {
if (!pathParams) return url
return Object.entries(pathParams).reduce(
(path, [key, value]) => path.replace(`:${key}`, String(value)),
url
)
}
async function request<T>(baseUrl: string, url: string, method: string, options: RequestOptions = {}): Promise<T> {
const token = await getCookieValue('Token')
const headers: HeadersInit = {
'Content-Type': 'application/json',
...(token ? { Token: token } : {}),
...options.headers,
}
const resolvedPath = resolvePath(url, options.pathParams)
const fullUrl = options.params
? `${baseUrl}${resolvedPath}?${new URLSearchParams(options.params)}`
: `${baseUrl}${resolvedPath}`
const response = await fetch(fullUrl, {
method,
headers,
body: options.body ? JSON.stringify(options.body) : undefined,
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const result: ApiResult<T> = await response.json()
// eslint-disable-next-line eqeqeq
if (result.code != 0) throw new Error(result.msg || '请求失败')
return result.data
}
function createHttp(baseUrl: string) {
return {
get: <T>(url: string, options?: RequestOptions) =>
request<T>(baseUrl, url, 'GET', options),
post: <T>(url: string, options?: RequestOptions) =>
request<T>(baseUrl, url, 'POST', options),
put: <T>(url: string, options?: RequestOptions) =>
request<T>(baseUrl, url, 'PUT', options),
delete: <T>(url: string, options?: RequestOptions) =>
request<T>(baseUrl, url, 'DELETE', options),
}
}
export { createHttp }
+34 -11
View File
@@ -1,19 +1,42 @@
/**
* Background Service Worker
* 插件的后台服务,负责:
* 1. 监听插件图标点击事件,通知 Content Script 切换侧边栏
* 2. 后续可扩展:定时任务调度、跨页面状态管理、与后端的长连接等
*/
import { config } from "~config"
import { MSG_TYPES } from "~constants"
export {}
/**
* 监听插件图标点击事件
* 由于 manifest 中 action 没有设置 default_popup
* 点击图标会触发此事件,向当前活动标签页发送切换侧边栏的消息
*/
/** 监听插件图标点击,切换侧边栏 */
chrome.action.onClicked.addListener(async (tab) => {
if (tab.id) {
chrome.tabs.sendMessage(tab.id, { type: "TOGGLE_SIDEBAR" })
}
})
/** 监听消息:处理 Cookie 读取请求 */
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
// 获取所有 Cookie
if (message.type === MSG_TYPES.GET_COOKIES) {
const targetUrl = message.url || config.cookieSourceUrl
const { hostname } = new URL(targetUrl)
chrome.cookies.getAll({ domain: hostname, url: targetUrl }).then((cookies) => {
sendResponse({ cookies })
})
return true
}
// 获取单个 Cookie 值
if (message.type === MSG_TYPES.GET_TOKEN) {
const targetUrl = message.url || config.cookieSourceUrl
chrome.cookies.get({ url: targetUrl, name: message.name }).then((cookie) => {
sendResponse({ value: cookie?.value || null })
})
return true
}
})
/** 插件安装事件 */
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "install") {
console.log("[OfferPie] 插件安装成功")
} else if (details.reason === "update") {
console.log("[OfferPie] 插件已更新")
}
})
+31
View File
@@ -0,0 +1,31 @@
/**
* 环境配置
*/
/** 当前环境,手动切换 */
const ENV = 'dev'
/** 各环境配置 */
const envConfigs: Record<string, {
dataBaseApi: string
aiBaseApi: string
cookieSourceUrl: string
}> = {
dev: {
dataBaseApi: 'http://localhost:8080/api',
aiBaseApi: 'http://localhost:8000',
cookieSourceUrl: 'http://192.168.31.135:5173',
},
prod: {
dataBaseApi: 'https://your-domain.com/api',
aiBaseApi: 'https://your-domain.com/ai',
cookieSourceUrl: 'https://your-domain.com',
},
}
/** 根据当前环境返回配置 */
export function getConfig() {
return envConfigs[ENV] || envConfigs.dev
}
export const config = getConfig()
+18
View File
@@ -0,0 +1,18 @@
/** 存储 key 定义 */
export const STORAGE_KEYS = {
/** 登录状态 */
LOGIN_STATUS: 'loginStatus',
/** 上次登录检查时间戳 */
LAST_LOGIN_CHECK: 'lastLoginCheck',
}
/** 登录状态缓存有效期(毫秒) */
export const LOGIN_CHECK_INTERVAL = 5 * 60 * 1000
/** 消息事件类型 */
export const MSG_TYPES = {
/** 获取 Token */
GET_TOKEN: 'GET_TOKEN',
/** 获取 Cookie */
GET_COOKIES: 'GET_COOKIES',
}
-70
View File
@@ -1,70 +0,0 @@
/**
* 后端 API 通信模块
* 封装了与 Java 后端和 Python AI 后端的 REST API 请求方法
* 注意:项目有两个后端,新增接口时务必确认是哪个后端的
*/
/** Java 后端基础地址 */
const BASE_URL = "http://localhost:8080/api"
/** Python AI 后端基础地址 */
const AI_BASE_URL = "http://localhost:5000/api"
/** 请求配置选项 */
interface ApiOptions {
/** 请求方法,默认 GET */
method?: string
/** 请求体数据 */
body?: unknown
/** 自定义请求头 */
headers?: Record<string, string>
}
/**
* 通用请求方法
* @param baseUrl - 后端基础地址
* @param path - 接口路径,如 /user/info
* @param options - 请求配置
* @returns 解析后的 JSON 响应数据
* @throws 当响应状态码非 2xx 时抛出错误
*/
async function request<T>(baseUrl: string, path: string, options: ApiOptions = {}): Promise<T> {
const { method = "GET", body, headers = {} } = options
const res = await fetch(`${baseUrl}${path}`, {
method,
headers: {
"Content-Type": "application/json",
...headers
},
body: body ? JSON.stringify(body) : undefined
})
if (!res.ok) {
throw new Error(`API Error: ${res.status} ${res.statusText}`)
}
return res.json()
}
/**
* Java 后端接口
* 用于用户管理、简历数据、投递记录等业务接口
*/
export const javaApi = {
/** 发送 GET 请求到 Java 后端 */
get: <T>(path: string) => request<T>(BASE_URL, path),
/** 发送 POST 请求到 Java 后端 */
post: <T>(path: string, body: unknown) => request<T>(BASE_URL, path, { method: "POST", body })
}
/**
* Python AI 后端接口
* 用于页面结构分析、智能填表、简历优化等 AI 功能接口
*/
export const aiApi = {
/** 发送 GET 请求到 Python AI 后端 */
get: <T>(path: string) => request<T>(AI_BASE_URL, path),
/** 发送 POST 请求到 Python AI 后端 */
post: <T>(path: string, body: unknown) => request<T>(AI_BASE_URL, path, { method: "POST", body })
}
+25
View File
@@ -0,0 +1,25 @@
import { get, set } from "~utils/storage"
import { checkLogin } from "~api/dataApi"
import { STORAGE_KEYS, LOGIN_CHECK_INTERVAL } from "~constants"
/**
* 检查登录状态(带缓存)
* 成功后缓存结果,有效期内不重复请求
* 失败则清除缓存,下次必定重新检查
*/
export async function ensureLogin(): Promise<boolean> {
const lastCheck = await get<number>(STORAGE_KEYS.LAST_LOGIN_CHECK)
if (lastCheck && Date.now() - lastCheck < LOGIN_CHECK_INTERVAL) {
return true
}
try {
await checkLogin()
await set(STORAGE_KEYS.LAST_LOGIN_CHECK, Date.now())
return true
} catch {
await set(STORAGE_KEYS.LAST_LOGIN_CHECK, 0)
return false
}
}
+13
View File
@@ -0,0 +1,13 @@
import { MSG_TYPES } from "~constants"
/** 获取指定地址下的所有 Cookie,默认取 cookieSourceUrl */
export async function getCookies(url?: string) {
const res = await chrome.runtime.sendMessage({ type: MSG_TYPES.GET_COOKIES, url })
return res?.cookies || []
}
/** 获取指定地址下某个 Cookie 的值,默认取 cookieSourceUrl */
export async function getCookieValue(name: string, url?: string) {
const res = await chrome.runtime.sendMessage({ type: MSG_TYPES.GET_TOKEN, name, url })
return res?.value || null
}
+20
View File
@@ -0,0 +1,20 @@
/**
* Chrome Storage 封装
* 统一使用 chrome.storage.local 进行数据持久化
*/
/** 读取存储值 */
export async function get<T = unknown>(key: string): Promise<T | null> {
const result = await chrome.storage.local.get(key)
return (result[key] as T) ?? null
}
/** 写入存储值 */
export async function set(key: string, value: unknown): Promise<void> {
await chrome.storage.local.set({ [key]: value })
}
/** 删除存储值 */
export async function remove(key: string): Promise<void> {
await chrome.storage.local.remove(key)
}