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/main
|
||||||
gateway/.env
|
gateway/.env
|
||||||
gateway/config/urls.json
|
gateway/config/urls.json
|
||||||
|
doc/src/.vuepress/public/pdfjs/
|
||||||
@ -1,9 +1,11 @@
|
|||||||
import { defineClientConfig } from "@vuepress/client";
|
import { defineClientConfig } from "@vuepress/client";
|
||||||
import UserInfo from "./components/UserInfo.vue";
|
import UserInfo from "./components/UserInfo.vue";
|
||||||
|
import SecureMedia from "./components/SecureMedia.vue";
|
||||||
|
|
||||||
export default defineClientConfig({
|
export default defineClientConfig({
|
||||||
enhance({ app, router }) {
|
enhance({ app, router }) {
|
||||||
app.component("UserInfo", UserInfo);
|
app.component("UserInfo", UserInfo);
|
||||||
|
app.component("SecureMedia", SecureMedia);
|
||||||
|
|
||||||
// 仅在客户端执行
|
// 仅在客户端执行
|
||||||
if (typeof window !== 'undefined') {
|
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([
|
export default navbar([
|
||||||
"/",
|
"/",
|
||||||
"/processed/",
|
"/enhance/",
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -4,10 +4,10 @@ export default sidebar({
|
|||||||
"/": [
|
"/": [
|
||||||
"",
|
"",
|
||||||
{
|
{
|
||||||
text: "译文",
|
text: "拓展",
|
||||||
icon: "laptop-code",
|
icon: "laptop-code",
|
||||||
prefix: "processed/",
|
prefix: "enhance/",
|
||||||
link: "processed/",
|
link: "enhance/",
|
||||||
children: "structure",
|
children: "structure",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { hopeTheme } from "vuepress-theme-hope";
|
import { hopeTheme } from "vuepress-theme-hope";
|
||||||
import UserInfo from "./components/UserInfo.vue";
|
|
||||||
|
|
||||||
import navbar from "./navbar.js";
|
import navbar from "./navbar.js";
|
||||||
import sidebar from "./sidebar.js";
|
import sidebar from "./sidebar.js";
|
||||||
@ -24,7 +23,7 @@ export default hopeTheme({
|
|||||||
navbarLayout: {
|
navbarLayout: {
|
||||||
start: ["Brand"],
|
start: ["Brand"],
|
||||||
center: ["Links"],
|
center: ["Links"],
|
||||||
end: [ "Outlook", "UserInfo"],
|
end: ["Outlook", "UserInfo"],
|
||||||
},
|
},
|
||||||
|
|
||||||
// 侧边栏
|
// 侧边栏
|
||||||
@ -135,6 +134,11 @@ export default hopeTheme({
|
|||||||
|
|
||||||
components: {
|
components: {
|
||||||
components: ["Badge", "VPCard", "PDF"],
|
components: ["Badge", "VPCard", "PDF"],
|
||||||
|
componentOptions: {
|
||||||
|
pdf: {
|
||||||
|
pdfjs: '/pdfjs/', // 假设 PDF.js 放在 public/pdfjs/web/
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
icon: {
|
icon: {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: 译文
|
title: 拓展
|
||||||
index: false
|
index: false
|
||||||
icon: laptop-code
|
icon: laptop-code
|
||||||
---
|
---
|
||||||
@ -2,6 +2,8 @@
|
|||||||
title: 人物关系
|
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.Use(middleware.RouteAuthMiddleware())
|
||||||
|
r.POST("/api/secure-url", handlers.GenerateSecureURL)
|
||||||
|
|
||||||
// 在权限校验中间件后添加退出路由
|
// 在权限校验中间件后添加退出路由
|
||||||
r.GET("/logout", handlers.Logout)
|
r.GET("/logout", handlers.Logout)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user