feat(doc): 拓展文档功能并优化主题配置
- 新增 SecureMedia 组件以支持安全媒体播放 - 更新导航栏和侧边栏结构,将"processed"重命名为"enhance" - 在主题配置中添加 PDF.js 相关设置 - 更新 .gitignore 文件以排除 pdfjs 相关目录
This commit is contained in:
parent
cb12614fc3
commit
7fa137e24e
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ gateway/static/*
|
||||
gateway/main
|
||||
gateway/.env
|
||||
gateway/config/urls.json
|
||||
doc/src/.vuepress/public/pdfjs/
|
||||
@ -1,9 +1,11 @@
|
||||
import { defineClientConfig } from "@vuepress/client";
|
||||
import UserInfo from "./components/UserInfo.vue";
|
||||
import SecureMedia from "./components/SecureMedia.vue";
|
||||
|
||||
export default defineClientConfig({
|
||||
enhance({ app, router }) {
|
||||
app.component("UserInfo", UserInfo);
|
||||
app.component("SecureMedia", SecureMedia);
|
||||
|
||||
// 仅在客户端执行
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
139
doc/src/.vuepress/components/SecureMedia.vue
Normal file
139
doc/src/.vuepress/components/SecureMedia.vue
Normal file
@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div class="secure-media-container">
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="loading-state">
|
||||
加载中...
|
||||
</div>
|
||||
|
||||
<!-- 错误状态 -->
|
||||
<div v-else-if="error" class="error-state">
|
||||
{{ error.message }}
|
||||
</div>
|
||||
|
||||
<!-- 内容展示 -->
|
||||
<template v-else-if="secureUrl">
|
||||
<!-- PDF 文件 -->
|
||||
<PDF v-if="isPDF" :url="secureUrl" />
|
||||
|
||||
<!-- 视频文件 -->
|
||||
<video v-else-if="isVideo" :src="secureUrl" controls class="secure-video" preload="metadata" />
|
||||
|
||||
<!-- 图片文件 -->
|
||||
<img v-else-if="isImage" :src="secureUrl" :alt="alt" class="secure-image" />
|
||||
|
||||
<!-- 音频文件 -->
|
||||
<audio v-else-if="isAudio" :src="secureUrl" controls class="secure-audio" />
|
||||
|
||||
<!-- 不支持的文件类型 -->
|
||||
<div v-else class="unsupported-media">
|
||||
不支持的文件类型
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
alt: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const secureUrl = ref('')
|
||||
const error = ref(null)
|
||||
const loading = ref(true)
|
||||
|
||||
// 判断文件类型
|
||||
const isPDF = computed(() => props.src.toLowerCase().endsWith('.pdf'))
|
||||
const isVideo = computed(() => /\.(mp4|webm|ogg|mov)$/i.test(props.src))
|
||||
const isImage = computed(() => /\.(jpg|jpeg|png|gif|webp)$/i.test(props.src))
|
||||
const isAudio = computed(() => /\.(mp3|wav|ogg)$/i.test(props.src))
|
||||
|
||||
// 获取安全链接
|
||||
const getSecureUrl = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
const response = await fetch('/api/secure-url', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: props.src
|
||||
})
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('获取安全链接失败')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
secureUrl.value = data.secureUrl
|
||||
} catch (err) {
|
||||
error.value = err
|
||||
console.error('获取安全链接失败:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getSecureUrl()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.secure-media-container {
|
||||
width: 100%;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.secure-image {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.secure-audio {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.secure-video {
|
||||
width: 100%;
|
||||
max-height: 80vh;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.unsupported-media {
|
||||
padding: 1rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: #dc3545;
|
||||
background: #fff3f3;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ffcdd2;
|
||||
}
|
||||
</style>
|
||||
@ -2,5 +2,5 @@ import { navbar } from "vuepress-theme-hope";
|
||||
|
||||
export default navbar([
|
||||
"/",
|
||||
"/processed/",
|
||||
"/enhance/",
|
||||
]);
|
||||
|
||||
@ -4,10 +4,10 @@ export default sidebar({
|
||||
"/": [
|
||||
"",
|
||||
{
|
||||
text: "译文",
|
||||
text: "拓展",
|
||||
icon: "laptop-code",
|
||||
prefix: "processed/",
|
||||
link: "processed/",
|
||||
prefix: "enhance/",
|
||||
link: "enhance/",
|
||||
children: "structure",
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { hopeTheme } from "vuepress-theme-hope";
|
||||
import UserInfo from "./components/UserInfo.vue";
|
||||
|
||||
import navbar from "./navbar.js";
|
||||
import sidebar from "./sidebar.js";
|
||||
@ -135,6 +134,11 @@ export default hopeTheme({
|
||||
|
||||
components: {
|
||||
components: ["Badge", "VPCard", "PDF"],
|
||||
componentOptions: {
|
||||
pdf: {
|
||||
pdfjs: '/pdfjs/', // 假设 PDF.js 放在 public/pdfjs/web/
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
icon: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: 译文
|
||||
title: 拓展
|
||||
index: false
|
||||
icon: laptop-code
|
||||
---
|
||||
@ -2,6 +2,8 @@
|
||||
title: 人物关系
|
||||
---
|
||||
|
||||
<SecureMedia src="https://files.jdysya.top/beishan/beishan-relation-graph.pdf" />
|
||||
|
||||
## 十一世
|
||||
|
||||
- **可观**:有一子,光友。生卒不详。
|
||||
13
doc/src/enhance/show.md
Normal file
13
doc/src/enhance/show.md
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
title: 媒体展示
|
||||
---
|
||||
|
||||
善琪公之墓碑
|
||||
|
||||
<SecureMedia src="https://files.jdysya.top/beishan/xiang/IMG_1868.jpeg" />
|
||||
|
||||
善琪公与德艳公墓地环绕
|
||||
|
||||
<SecureMedia src="https://files.jdysya.top/beishan/xiang/IMG_1870.MOV" />
|
||||
|
||||
|
||||
79
gateway/handlers/media.go
Normal file
79
gateway/handlers/media.go
Normal file
@ -0,0 +1,79 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type SecureUrlRequest struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// urlEncode 对URL路径进行编码,保持斜杠不变
|
||||
func urlEncode(s string) string {
|
||||
return strings.ReplaceAll(url.QueryEscape(s), "%2F", "/")
|
||||
}
|
||||
|
||||
// toHex16 将时间戳转换为16进制小写形式
|
||||
func toHex16(t int64) string {
|
||||
return fmt.Sprintf("%x", t)
|
||||
}
|
||||
|
||||
func GenerateSecureURL(c *gin.Context) {
|
||||
var req SecureUrlRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求"})
|
||||
return
|
||||
}
|
||||
|
||||
// 从环境变量获取防盗链密钥
|
||||
secretKey := os.Getenv("QINIU_ANTILEECH_KEY")
|
||||
if secretKey == "" {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "未配置防盗链密钥"})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置链接有效期为5分钟
|
||||
deadline := time.Now().Add(5 * time.Minute).Unix()
|
||||
deadlineHex := toHex16(deadline)
|
||||
|
||||
// 解析URL
|
||||
parsedURL, err := url.Parse(req.URL)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的URL"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取并编码路径部分
|
||||
urlPath := parsedURL.Path
|
||||
encodedPath := urlEncode(urlPath)
|
||||
|
||||
// 构建签名字符串
|
||||
signStr := secretKey + encodedPath + deadlineHex
|
||||
|
||||
// 计算MD5签名
|
||||
hash := md5.New()
|
||||
hash.Write([]byte(signStr))
|
||||
sign := hex.EncodeToString(hash.Sum(nil))
|
||||
|
||||
// 构建查询参数
|
||||
query := parsedURL.Query()
|
||||
query.Set("sign", sign)
|
||||
query.Set("t", deadlineHex)
|
||||
|
||||
// 重建URL
|
||||
parsedURL.RawQuery = query.Encode()
|
||||
secureUrl := parsedURL.String()
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"secureUrl": secureUrl,
|
||||
})
|
||||
}
|
||||
@ -75,6 +75,7 @@ func main() {
|
||||
|
||||
// 添加路由认证中间件
|
||||
r.Use(middleware.RouteAuthMiddleware())
|
||||
r.POST("/api/secure-url", handlers.GenerateSecureURL)
|
||||
|
||||
// 在权限校验中间件后添加退出路由
|
||||
r.GET("/logout", handlers.Logout)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user