链接服务 API
从 EdgeOne KV 存储中获取链接数据,支持各种配置和链接信息的快速检索。
🔗 接口信息
- 接口路径:
/link - 请求方法:
GET - 响应格式: JSON
- 数据源: EdgeOne KV Storage
🚀 快速开始
基础调用
bash
curl https://www.160621.xyz/api/linkJavaScript 调用
javascript
// 使用 fetch
fetch('https://www.160621.xyz/api/link')
.then(response => response.json())
.then(result => {
if (result.status === 'success') {
console.log('链接数据:', result.data)
// 遍历所有分类
Object.entries(result.data).forEach(([key, category]) => {
console.log(`${category.name}: ${category.items.length}个链接`)
})
} else {
console.error('获取失败:', result.message)
}
})
// 使用 async/await
async function getLinks() {
try {
const response = await fetch('https://www.160621.xyz/api/link')
const result = await response.json()
if (result.status === 'success') {
return result.data
} else {
throw new Error(result.message)
}
} catch (error) {
console.error('获取链接数据失败:', error)
throw error
}
}📋 响应字段
| 字段名 | 类型 | 说明 | 示例 |
|---|---|---|---|
code | number | 业务状态码 | 200 |
status | string | 状态标识 | "success" |
message | string | 提示信息 | "获取链接数据成功" |
data | object | 链接数据对象 | {} |
timestamp | number | 服务器响应时间戳 | 1736123456789 |
📝 响应示例
成功响应示例
json
{
"code": 200,
"status": "success",
"message": "获取链接数据成功",
"data": {
"tools": {
"name": "实用工具",
"items": [
{
"title": "在线图片压缩",
"url": "https://tinypng.com",
"description": "免费的在线图片压缩工具",
"icon": "🖼️",
"category": "tool"
},
{
"title": "代码格式化",
"url": "https://prettier.io",
"description": "代码格式化工具",
"icon": "💻",
"category": "tool"
}
]
},
"resources": {
"name": "学习资源",
"items": [
{
"title": "MDN Web Docs",
"url": "https://developer.mozilla.org",
"description": "Web 开发权威文档",
"icon": "📚",
"category": "resource"
}
]
}
},
"timestamp": 1736123456789
}空数据响应
json
{
"code": 200,
"status": "success",
"message": "获取链接数据成功",
"data": {},
"timestamp": 1736123456789
}错误响应 - KV存储不可用
json
{
"code": 500,
"status": "error",
"message": "KV存储不可用",
"data": {
"code": "KV_UNAVAILABLE"
},
"timestamp": 1736123456789
}错误响应 - 数据未找到
json
{
"code": 404,
"status": "error",
"message": "链接数据未找到",
"data": {
"code": "DATA_NOT_FOUND"
},
"timestamp": 1736123456789
}错误响应 - 服务器错误
json
{
"code": 500,
"status": "error",
"message": "服务器内部错误",
"data": {
"code": "INTERNAL_ERROR",
"details": "Unknown error occurred"
},
"timestamp": 1736123456789
}🎯 使用场景
1. 动态导航菜单
javascript
class DynamicNavMenu {
constructor(containerId) {
this.container = document.getElementById(containerId)
this.links = {}
}
async init() {
await this.loadLinks()
this.renderNavMenu()
}
async loadLinks() {
try {
const response = await fetch('https://www.160621.xyz/api/link')
const result = await response.json()
if (result.status === 'success') {
this.links = result.data
}
} catch (error) {
console.error('加载链接失败:', error)
}
}
renderNavMenu() {
if (!this.container) return
const nav = document.createElement('nav')
nav.className = 'dynamic-nav'
Object.entries(this.links).forEach(([key, category]) => {
const section = document.createElement('div')
section.className = 'nav-section'
const title = document.createElement('h3')
title.textContent = category.name
section.appendChild(title)
const list = document.createElement('ul')
category.items.forEach(item => {
const li = document.createElement('li')
li.innerHTML = `
<a href="${item.url}" target="_blank" rel="noopener noreferrer">
<span class="icon">${item.icon}</span>
<span class="title">${item.title}</span>
<span class="description">${item.description}</span>
</a>
`
list.appendChild(li)
})
section.appendChild(list)
nav.appendChild(section)
})
this.container.innerHTML = ''
this.container.appendChild(nav)
}
}
// 使用示例
const navMenu = new DynamicNavMenu('nav-container')
navMenu.init()2. 链接卡片组件
javascript
class LinkCard {
constructor(data) {
this.title = data.title
this.url = data.url
this.description = data.description
this.icon = data.icon
this.category = data.category
}
render() {
return `
<div class="link-card" data-category="${this.category}">
<div class="card-header">
<span class="icon">${this.icon}</span>
<h4 class="title">${this.title}</h4>
</div>
<p class="description">${this.description}</p>
<div class="card-footer">
<a href="${this.url}" target="_blank" class="btn primary">访问</a>
<button class="btn secondary copy-link" data-url="${this.url}">
复制链接
</button>
</div>
</div>
`
}
}
class LinkGallery {
constructor(containerId) {
this.container = document.getElementById(containerId)
this.links = []
this.filteredLinks = []
this.currentFilter = 'all'
}
async init() {
await this.loadLinks()
this.createFilters()
this.renderLinks()
this.bindEvents()
}
async loadLinks() {
try {
const response = await fetch('https://www.160621.xyz/api/link')
const result = await response.json()
if (result.status === 'success') {
// 将所有分类的链接合并到一个数组中
this.links = []
Object.values(result.data).forEach(category => {
category.items.forEach(item => {
this.links.push(item)
})
})
this.filteredLinks = [...this.links]
}
} catch (error) {
console.error('加载链接失败:', error)
}
}
createFilters() {
const categories = [...new Set(this.links.map(link => link.category))]
const filterContainer = document.createElement('div')
filterContainer.className = 'filter-container'
// 全部按钮
const allBtn = document.createElement('button')
allBtn.className = 'filter-btn active'
allBtn.textContent = '全部'
allBtn.dataset.filter = 'all'
filterContainer.appendChild(allBtn)
// 分类按钮
categories.forEach(category => {
const btn = document.createElement('button')
btn.className = 'filter-btn'
btn.textContent = this.getCategoryName(category)
btn.dataset.filter = category
filterContainer.appendChild(btn)
})
this.container.appendChild(filterContainer)
}
renderLinks() {
const gridContainer = document.createElement('div')
gridContainer.className = 'link-grid'
this.filteredLinks.forEach(linkData => {
const card = new LinkCard(linkData)
gridContainer.innerHTML += card.render()
})
// 移除旧的网格
const oldGrid = this.container.querySelector('.link-grid')
if (oldGrid) {
oldGrid.remove()
}
this.container.appendChild(gridContainer)
this.bindCardEvents()
}
bindEvents() {
// 筛选按钮事件
this.container.addEventListener('click', (e) => {
if (e.target.classList.contains('filter-btn')) {
this.filterLinks(e.target.dataset.filter)
this.updateActiveFilter(e.target)
}
})
}
bindCardEvents() {
// 复制链接按钮
this.container.querySelectorAll('.copy-link').forEach(btn => {
btn.addEventListener('click', (e) => {
const url = e.target.dataset.url
this.copyToClipboard(url)
this.showCopyFeedback(e.target)
})
})
}
filterLinks(category) {
if (category === 'all') {
this.filteredLinks = [...this.links]
} else {
this.filteredLinks = this.links.filter(link => link.category === category)
}
this.renderLinks()
}
updateActiveFilter(activeBtn) {
this.container.querySelectorAll('.filter-btn').forEach(btn => {
btn.classList.remove('active')
})
activeBtn.classList.add('active')
}
getCategoryName(category) {
const names = {
'tool': '工具',
'resource': '资源',
'website': '网站',
'service': '服务'
}
return names[category] || category
}
async copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text)
return true
} catch (error) {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
return true
}
}
showCopyFeedback(button) {
const originalText = button.textContent
button.textContent = '已复制!'
button.classList.add('copied')
setTimeout(() => {
button.textContent = originalText
button.classList.remove('copied')
}, 2000)
}
}
// 使用示例
const linkGallery = new LinkGallery('links-container')
linkGallery.init()3. React Hook 封装
jsx
import { useState, useEffect, useCallback } from 'react'
export function useLinks() {
const [links, setLinks] = useState({})
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
const fetchLinks = useCallback(async () => {
setLoading(true)
setError(null)
try {
const response = await fetch('https://www.160621.xyz/api/link')
const result = await response.json()
if (result.status === 'success') {
setLinks(result.data)
} else {
throw new Error(result.message || '获取链接失败')
}
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}, [])
useEffect(() => {
fetchLinks()
}, [fetchLinks])
return { links, loading, error, refetch: fetchLinks }
}
// 链接组件
function LinksDisplay() {
const { links, loading, error } = useLinks()
if (loading) return <div>正在加载链接...</div>
if (error) return <div>加载失败: {error}</div>
return (
<div className="links-container">
{Object.entries(links).map(([key, category]) => (
<div key={key} className="category-section">
<h3>{category.name}</h3>
<div className="links-grid">
{category.items.map((item, index) => (
<LinkCard key={index} {...item} />
))}
</div>
</div>
))}
</div>
)
}
function LinkCard({ title, url, description, icon, category }) {
const handleCopyLink = async () => {
try {
await navigator.clipboard.writeText(url)
alert('链接已复制到剪贴板')
} catch (error) {
console.error('复制失败:', error)
}
}
return (
<div className="link-card" data-category={category}>
<div className="card-icon">{icon}</div>
<h4 className="card-title">{title}</h4>
<p className="card-description">{description}</p>
<div className="card-actions">
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="btn primary"
>
访问
</a>
<button onClick={handleCopyLink} className="btn secondary">
复制
</button>
</div>
</div>
)
}4. 搜索功能
javascript
class LinkSearch {
constructor(containerId, linksData) {
this.container = document.getElementById(containerId)
this.allLinks = this.flattenLinks(linksData)
this.filteredLinks = [...this.allLinks]
this.searchIndex = this.buildSearchIndex()
}
flattenLinks(linksData) {
const flattened = []
Object.values(linksData).forEach(category => {
category.items.forEach(item => {
flattened.push({
...item,
categoryName: category.name
})
})
})
return flattened
}
buildSearchIndex() {
return this.allLinks.map(link => ({
...link,
searchText: [
link.title,
link.description,
link.category,
link.categoryName
].join(' ').toLowerCase()
}))
}
createSearchBox() {
const searchContainer = document.createElement('div')
searchContainer.className = 'search-container'
searchContainer.innerHTML = `
<input
type="text"
class="search-input"
placeholder="搜索链接..."
autocomplete="off"
/>
<div class="search-results" style="display: none;"></div>
`
this.container.appendChild(searchContainer)
this.bindSearchEvents()
}
bindSearchEvents() {
const input = this.container.querySelector('.search-input')
const resultsContainer = this.container.querySelector('.search-results')
input.addEventListener('input', (e) => {
const query = e.target.value.trim()
if (query.length < 2) {
resultsContainer.style.display = 'none'
return
}
const results = this.search(query)
this.displayResults(results, resultsContainer)
})
// 点击外部关闭搜索结果
document.addEventListener('click', (e) => {
if (!searchContainer.contains(e.target)) {
resultsContainer.style.display = 'none'
}
})
}
search(query) {
const lowerQuery = query.toLowerCase()
return this.searchIndex.filter(link =>
link.searchText.includes(lowerQuery)
).slice(0, 10) // 限制结果数量
}
displayResults(results, container) {
if (results.length === 0) {
container.innerHTML = '<div class="no-results">未找到相关链接</div>'
container.style.display = 'block'
return
}
container.innerHTML = results.map(link => `
<div class="search-result-item">
<div class="result-icon">${link.icon}</div>
<div class="result-content">
<div class="result-title">${link.title}</div>
<div class="result-description">${link.description}</div>
</div>
<a href="${link.url}" target="_blank" class="result-link">
访问
</a>
</div>
`).join('')
container.style.display = 'block'
}
}🛠️ 配置管理
KV 存储数据结构
链接服务的数据存储在 EdgeOne KV 中,建议的数据结构:
json
{
"tools": {
"name": "实用工具",
"description": "开发和学习相关的实用工具",
"items": [
{
"title": "工具名称",
"url": "https://example.com",
"description": "工具描述",
"icon": "🛠️",
"category": "tool",
"tags": ["开发", "效率"],
"lastUpdated": "2025-01-19T12:00:00Z"
}
]
}
}⚠️ 注意事项
1. 缓存策略
javascript
// 链接数据缓存(30分钟)
const linkCache = new Map()
async function getCachedLinks() {
const cacheKey = 'api_links'
const cached = linkCache.get(cacheKey)
if (cached && Date.now() - cached.timestamp < 1800000) { // 30分钟
return cached.data
}
const response = await fetch('https://www.160621.xyz/api/link')
const data = await response.json()
linkCache.set(cacheKey, {
data,
timestamp: Date.now()
})
return data
}2. 错误处理
javascript
async function safeGetLinks() {
try {
const response = await fetch('https://www.160621.xyz/api/link')
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
const result = await response.json()
if (result.status !== 'success') {
throw new Error(result.message || '获取链接失败')
}
return result
} catch (error) {
console.error('链接服务错误:', error)
// 返回默认数据
return {
code: 200,
status: 'success',
message: '获取链接数据成功',
data: {},
timestamp: Date.now()
}
}
}