feat: 利用AI更新文档
This commit is contained in:
+3
-1
@@ -159,4 +159,6 @@ data/.env
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
|
||||||
AGENTS.md
|
AGENTS.md
|
||||||
|
|
||||||
|
dist/
|
||||||
@@ -35,7 +35,6 @@ DEFAULT_CONFIG = {
|
|||||||
"onedrive_password": "",
|
"onedrive_password": "",
|
||||||
"onedrive_root_path": "filebox_storage",
|
"onedrive_root_path": "filebox_storage",
|
||||||
"onedrive_proxy": 0,
|
"onedrive_proxy": 0,
|
||||||
"webdav_hostname": "",
|
|
||||||
"webdav_root_path": "filebox_storage",
|
"webdav_root_path": "filebox_storage",
|
||||||
"webdav_proxy": 0,
|
"webdav_proxy": 0,
|
||||||
"admin_token": "FileCodeBox2023",
|
"admin_token": "FileCodeBox2023",
|
||||||
|
|||||||
-275
@@ -1,275 +0,0 @@
|
|||||||
import {
|
|
||||||
useMediaQuery
|
|
||||||
} from "./chunk-KT7LHMJ2.js";
|
|
||||||
import {
|
|
||||||
computed,
|
|
||||||
ref,
|
|
||||||
shallowRef,
|
|
||||||
watch
|
|
||||||
} from "./chunk-CQOUZRMK.js";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/index.js
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/base.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/icons.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
|
|
||||||
import "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
|
|
||||||
import VPBadge from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
|
||||||
import Layout from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/Layout.vue";
|
|
||||||
import { default as default2 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
|
||||||
import { default as default3 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPButton.vue";
|
|
||||||
import { default as default4 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
|
|
||||||
import { default as default5 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPFeatures.vue";
|
|
||||||
import { default as default6 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeContent.vue";
|
|
||||||
import { default as default7 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
|
|
||||||
import { default as default8 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
|
|
||||||
import { default as default9 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
|
|
||||||
import { default as default10 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPImage.vue";
|
|
||||||
import { default as default11 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPLink.vue";
|
|
||||||
import { default as default12 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPNavBarSearch.vue";
|
|
||||||
import { default as default13 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPSocialLink.vue";
|
|
||||||
import { default as default14 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPSocialLinks.vue";
|
|
||||||
import { default as default15 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPSponsors.vue";
|
|
||||||
import { default as default16 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
|
|
||||||
import { default as default17 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
|
|
||||||
import { default as default18 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
|
|
||||||
import { default as default19 } from "/Users/lan/Project/Python/FileCodeBox/docs/node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
|
|
||||||
import { onContentUpdated } from "vitepress";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/outline.js
|
|
||||||
import { getScrollOffset } from "vitepress";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/support/utils.js
|
|
||||||
import { withBase } from "vitepress";
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/data.js
|
|
||||||
import { useData as useData$ } from "vitepress";
|
|
||||||
var useData = useData$;
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/support/utils.js
|
|
||||||
function ensureStartingSlash(path) {
|
|
||||||
return path.startsWith("/") ? path : `/${path}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/support/sidebar.js
|
|
||||||
function getSidebar(_sidebar, path) {
|
|
||||||
if (Array.isArray(_sidebar))
|
|
||||||
return addBase(_sidebar);
|
|
||||||
if (_sidebar == null)
|
|
||||||
return [];
|
|
||||||
path = ensureStartingSlash(path);
|
|
||||||
const dir = Object.keys(_sidebar).sort((a, b) => {
|
|
||||||
return b.split("/").length - a.split("/").length;
|
|
||||||
}).find((dir2) => {
|
|
||||||
return path.startsWith(ensureStartingSlash(dir2));
|
|
||||||
});
|
|
||||||
const sidebar = dir ? _sidebar[dir] : [];
|
|
||||||
return Array.isArray(sidebar) ? addBase(sidebar) : addBase(sidebar.items, sidebar.base);
|
|
||||||
}
|
|
||||||
function getSidebarGroups(sidebar) {
|
|
||||||
const groups = [];
|
|
||||||
let lastGroupIndex = 0;
|
|
||||||
for (const index in sidebar) {
|
|
||||||
const item = sidebar[index];
|
|
||||||
if (item.items) {
|
|
||||||
lastGroupIndex = groups.push(item);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (!groups[lastGroupIndex]) {
|
|
||||||
groups.push({ items: [] });
|
|
||||||
}
|
|
||||||
groups[lastGroupIndex].items.push(item);
|
|
||||||
}
|
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
function addBase(items, _base) {
|
|
||||||
return [...items].map((_item) => {
|
|
||||||
const item = { ..._item };
|
|
||||||
const base = item.base || _base;
|
|
||||||
if (base && item.link)
|
|
||||||
item.link = base + item.link;
|
|
||||||
if (item.items)
|
|
||||||
item.items = addBase(item.items, base);
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/sidebar.js
|
|
||||||
function useSidebar() {
|
|
||||||
const { frontmatter, page, theme: theme2 } = useData();
|
|
||||||
const is960 = useMediaQuery("(min-width: 960px)");
|
|
||||||
const isOpen = ref(false);
|
|
||||||
const _sidebar = computed(() => {
|
|
||||||
const sidebarConfig = theme2.value.sidebar;
|
|
||||||
const relativePath = page.value.relativePath;
|
|
||||||
return sidebarConfig ? getSidebar(sidebarConfig, relativePath) : [];
|
|
||||||
});
|
|
||||||
const sidebar = ref(_sidebar.value);
|
|
||||||
watch(_sidebar, (next, prev) => {
|
|
||||||
if (JSON.stringify(next) !== JSON.stringify(prev))
|
|
||||||
sidebar.value = _sidebar.value;
|
|
||||||
});
|
|
||||||
const hasSidebar = computed(() => {
|
|
||||||
return frontmatter.value.sidebar !== false && sidebar.value.length > 0 && frontmatter.value.layout !== "home";
|
|
||||||
});
|
|
||||||
const leftAside = computed(() => {
|
|
||||||
if (hasAside)
|
|
||||||
return frontmatter.value.aside == null ? theme2.value.aside === "left" : frontmatter.value.aside === "left";
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
const hasAside = computed(() => {
|
|
||||||
if (frontmatter.value.layout === "home")
|
|
||||||
return false;
|
|
||||||
if (frontmatter.value.aside != null)
|
|
||||||
return !!frontmatter.value.aside;
|
|
||||||
return theme2.value.aside !== false;
|
|
||||||
});
|
|
||||||
const isSidebarEnabled = computed(() => hasSidebar.value && is960.value);
|
|
||||||
const sidebarGroups = computed(() => {
|
|
||||||
return hasSidebar.value ? getSidebarGroups(sidebar.value) : [];
|
|
||||||
});
|
|
||||||
function open() {
|
|
||||||
isOpen.value = true;
|
|
||||||
}
|
|
||||||
function close() {
|
|
||||||
isOpen.value = false;
|
|
||||||
}
|
|
||||||
function toggle() {
|
|
||||||
isOpen.value ? close() : open();
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isOpen,
|
|
||||||
sidebar,
|
|
||||||
sidebarGroups,
|
|
||||||
hasSidebar,
|
|
||||||
hasAside,
|
|
||||||
leftAside,
|
|
||||||
isSidebarEnabled,
|
|
||||||
open,
|
|
||||||
close,
|
|
||||||
toggle
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/outline.js
|
|
||||||
var ignoreRE = /\b(?:VPBadge|header-anchor|footnote-ref|ignore-header)\b/;
|
|
||||||
var resolvedHeaders = [];
|
|
||||||
function getHeaders(range) {
|
|
||||||
const headers = [
|
|
||||||
...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")
|
|
||||||
].filter((el) => el.id && el.hasChildNodes()).map((el) => {
|
|
||||||
const level = Number(el.tagName[1]);
|
|
||||||
return {
|
|
||||||
element: el,
|
|
||||||
title: serializeHeader(el),
|
|
||||||
link: "#" + el.id,
|
|
||||||
level
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return resolveHeaders(headers, range);
|
|
||||||
}
|
|
||||||
function serializeHeader(h) {
|
|
||||||
let ret = "";
|
|
||||||
for (const node of h.childNodes) {
|
|
||||||
if (node.nodeType === 1) {
|
|
||||||
if (ignoreRE.test(node.className))
|
|
||||||
continue;
|
|
||||||
ret += node.textContent;
|
|
||||||
} else if (node.nodeType === 3) {
|
|
||||||
ret += node.textContent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret.trim();
|
|
||||||
}
|
|
||||||
function resolveHeaders(headers, range) {
|
|
||||||
if (range === false) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const levelsRange = (typeof range === "object" && !Array.isArray(range) ? range.level : range) || 2;
|
|
||||||
const [high, low] = typeof levelsRange === "number" ? [levelsRange, levelsRange] : levelsRange === "deep" ? [2, 6] : levelsRange;
|
|
||||||
return buildTree(headers, high, low);
|
|
||||||
}
|
|
||||||
function buildTree(data, min, max) {
|
|
||||||
resolvedHeaders.length = 0;
|
|
||||||
const result = [];
|
|
||||||
const stack = [];
|
|
||||||
data.forEach((item) => {
|
|
||||||
const node = { ...item, children: [] };
|
|
||||||
let parent = stack[stack.length - 1];
|
|
||||||
while (parent && parent.level >= node.level) {
|
|
||||||
stack.pop();
|
|
||||||
parent = stack[stack.length - 1];
|
|
||||||
}
|
|
||||||
if (node.element.classList.contains("ignore-header") || parent && "shouldIgnore" in parent) {
|
|
||||||
stack.push({ level: node.level, shouldIgnore: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (node.level > max || node.level < min)
|
|
||||||
return;
|
|
||||||
resolvedHeaders.push({ element: node.element, link: node.link });
|
|
||||||
if (parent)
|
|
||||||
parent.children.push(node);
|
|
||||||
else
|
|
||||||
result.push(node);
|
|
||||||
stack.push(node);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/composables/local-nav.js
|
|
||||||
function useLocalNav() {
|
|
||||||
const { theme: theme2, frontmatter } = useData();
|
|
||||||
const headers = shallowRef([]);
|
|
||||||
const hasLocalNav = computed(() => {
|
|
||||||
return headers.value.length > 0;
|
|
||||||
});
|
|
||||||
onContentUpdated(() => {
|
|
||||||
headers.value = getHeaders(frontmatter.value.outline ?? theme2.value.outline);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
headers,
|
|
||||||
hasLocalNav
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
|
||||||
var theme = {
|
|
||||||
Layout,
|
|
||||||
enhanceApp: ({ app }) => {
|
|
||||||
app.component("Badge", VPBadge);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var without_fonts_default = theme;
|
|
||||||
export {
|
|
||||||
default2 as VPBadge,
|
|
||||||
default3 as VPButton,
|
|
||||||
default4 as VPDocAsideSponsors,
|
|
||||||
default5 as VPFeatures,
|
|
||||||
default6 as VPHomeContent,
|
|
||||||
default7 as VPHomeFeatures,
|
|
||||||
default8 as VPHomeHero,
|
|
||||||
default9 as VPHomeSponsors,
|
|
||||||
default10 as VPImage,
|
|
||||||
default11 as VPLink,
|
|
||||||
default12 as VPNavBarSearch,
|
|
||||||
default13 as VPSocialLink,
|
|
||||||
default14 as VPSocialLinks,
|
|
||||||
default15 as VPSponsors,
|
|
||||||
default16 as VPTeamMembers,
|
|
||||||
default17 as VPTeamPage,
|
|
||||||
default18 as VPTeamPageSection,
|
|
||||||
default19 as VPTeamPageTitle,
|
|
||||||
without_fonts_default as default,
|
|
||||||
useLocalNav,
|
|
||||||
useSidebar
|
|
||||||
};
|
|
||||||
//# sourceMappingURL=@theme_index.js.map
|
|
||||||
File diff suppressed because one or more lines are too long
+9
-15
@@ -1,49 +1,43 @@
|
|||||||
{
|
{
|
||||||
"hash": "d07f6dee",
|
"hash": "8f855eaf",
|
||||||
"configHash": "dee2d3bc",
|
"configHash": "1b3ca22f",
|
||||||
"lockfileHash": "bd28b2c2",
|
"lockfileHash": "bd28b2c2",
|
||||||
"browserHash": "b96651ac",
|
"browserHash": "29e84937",
|
||||||
"optimized": {
|
"optimized": {
|
||||||
"vue": {
|
"vue": {
|
||||||
"src": "../../../node_modules/.pnpm/vue@3.5.13/node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
"src": "../../../node_modules/.pnpm/vue@3.5.13/node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||||
"file": "vue.js",
|
"file": "vue.js",
|
||||||
"fileHash": "bc5c7f34",
|
"fileHash": "3215885f",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > @vue/devtools-api": {
|
"vitepress > @vue/devtools-api": {
|
||||||
"src": "../../../node_modules/.pnpm/@vue+devtools-api@7.7.1/node_modules/@vue/devtools-api/dist/index.js",
|
"src": "../../../node_modules/.pnpm/@vue+devtools-api@7.7.1/node_modules/@vue/devtools-api/dist/index.js",
|
||||||
"file": "vitepress___@vue_devtools-api.js",
|
"file": "vitepress___@vue_devtools-api.js",
|
||||||
"fileHash": "00859881",
|
"fileHash": "5a5f95ef",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > @vueuse/core": {
|
"vitepress > @vueuse/core": {
|
||||||
"src": "../../../node_modules/.pnpm/@vueuse+core@12.5.0/node_modules/@vueuse/core/index.mjs",
|
"src": "../../../node_modules/.pnpm/@vueuse+core@12.5.0/node_modules/@vueuse/core/index.mjs",
|
||||||
"file": "vitepress___@vueuse_core.js",
|
"file": "vitepress___@vueuse_core.js",
|
||||||
"fileHash": "1c6da938",
|
"fileHash": "0fbf66f0",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > @vueuse/integrations/useFocusTrap": {
|
"vitepress > @vueuse/integrations/useFocusTrap": {
|
||||||
"src": "../../../node_modules/.pnpm/@vueuse+integrations@12.5.0_focus-trap@7.6.4/node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
"src": "../../../node_modules/.pnpm/@vueuse+integrations@12.5.0_focus-trap@7.6.4/node_modules/@vueuse/integrations/useFocusTrap.mjs",
|
||||||
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
"file": "vitepress___@vueuse_integrations_useFocusTrap.js",
|
||||||
"fileHash": "11c08fcf",
|
"fileHash": "91b03896",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > mark.js/src/vanilla.js": {
|
"vitepress > mark.js/src/vanilla.js": {
|
||||||
"src": "../../../node_modules/.pnpm/mark.js@8.11.1/node_modules/mark.js/src/vanilla.js",
|
"src": "../../../node_modules/.pnpm/mark.js@8.11.1/node_modules/mark.js/src/vanilla.js",
|
||||||
"file": "vitepress___mark__js_src_vanilla__js.js",
|
"file": "vitepress___mark__js_src_vanilla__js.js",
|
||||||
"fileHash": "b93dbfe7",
|
"fileHash": "99e4f81c",
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
},
|
},
|
||||||
"vitepress > minisearch": {
|
"vitepress > minisearch": {
|
||||||
"src": "../../../node_modules/.pnpm/minisearch@7.1.1/node_modules/minisearch/dist/es/index.js",
|
"src": "../../../node_modules/.pnpm/minisearch@7.1.1/node_modules/minisearch/dist/es/index.js",
|
||||||
"file": "vitepress___minisearch.js",
|
"file": "vitepress___minisearch.js",
|
||||||
"fileHash": "bfb6c3ac",
|
"fileHash": "cc176c9c",
|
||||||
"needsInterop": false
|
|
||||||
},
|
|
||||||
"@theme/index": {
|
|
||||||
"src": "../../../node_modules/.pnpm/vitepress@1.6.3_@algolia+client-search@5.20.2_postcss@8.5.2_search-insights@2.17.3/node_modules/vitepress/dist/client/theme-default/index.js",
|
|
||||||
"file": "@theme_index.js",
|
|
||||||
"fileHash": "07512287",
|
|
||||||
"needsInterop": false
|
"needsInterop": false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,473 @@
|
|||||||
|
|
||||||
|
:root {
|
||||||
|
/* --- 核心色板:清晨薄雾 (Teal -> Sky) --- */
|
||||||
|
--vp-c-brand-1: #14b8a6; /* Teal 500 */
|
||||||
|
--vp-c-brand-2: #0ea5e9; /* Sky 500 */
|
||||||
|
--vp-c-brand-3: #0f766e; /* Teal 700 */
|
||||||
|
--vp-c-brand-soft: rgba(20, 184, 166, 0.14);
|
||||||
|
|
||||||
|
/* --- 背景色:极简与通透 --- */
|
||||||
|
--vp-c-bg: #ffffff;
|
||||||
|
--vp-c-bg-alt: #f8fafc; /* Slate 50 */
|
||||||
|
--vp-c-bg-elv: #ffffff;
|
||||||
|
|
||||||
|
/* --- 文字颜色:柔和的深灰,而非纯黑 --- */
|
||||||
|
--vp-c-text-1: #334155; /* Slate 700 */
|
||||||
|
--vp-c-text-2: #64748b; /* Slate 500 */
|
||||||
|
--vp-c-text-3: #94a3b8; /* Slate 400 */
|
||||||
|
|
||||||
|
/* --- 边框与分隔 --- */
|
||||||
|
--vp-c-border: #e2e8f0;
|
||||||
|
--vp-c-divider: #f1f5f9;
|
||||||
|
|
||||||
|
/* --- 阴影:扩散柔光 --- */
|
||||||
|
--vp-shadow-1: 0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.03);
|
||||||
|
--vp-shadow-2: 0 10px 15px -3px rgba(0, 0, 0, 0.05), 0 4px 6px -2px rgba(0, 0, 0, 0.025);
|
||||||
|
--vp-shadow-brand: 0 4px 14px 0 rgba(20, 184, 166, 0.3);
|
||||||
|
|
||||||
|
/* --- 布局参数 --- */
|
||||||
|
--vp-nav-height: 64px;
|
||||||
|
--vp-sidebar-width: 280px;
|
||||||
|
--vp-radius-default: 12px;
|
||||||
|
--vp-radius-small: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* * ==========================================
|
||||||
|
* 暗黑模式重构 (Deep Ocean Theme)
|
||||||
|
* ==========================================
|
||||||
|
*/
|
||||||
|
.dark {
|
||||||
|
/* 背景:更深邃的蓝黑色,更有沉浸感 */
|
||||||
|
--vp-c-bg: #020617; /* Slate 950 */
|
||||||
|
--vp-c-bg-alt: #0f172a; /* Slate 900 */
|
||||||
|
--vp-c-bg-elv: #1e293b; /* Slate 800 */
|
||||||
|
|
||||||
|
/* 文字:降低纯白刺眼感,提高舒适度 */
|
||||||
|
--vp-c-text-1: #f8fafc; /* Slate 50 */
|
||||||
|
--vp-c-text-2: #94a3b8; /* Slate 400 */
|
||||||
|
--vp-c-text-3: #64748b; /* Slate 500 */
|
||||||
|
|
||||||
|
/* 边框:更细腻的深色线条 */
|
||||||
|
--vp-c-border: #1e293b;
|
||||||
|
--vp-c-divider: #0f172a;
|
||||||
|
|
||||||
|
/* 品牌色:调亮,产生荧光感 */
|
||||||
|
--vp-c-brand-1: #2dd4bf; /* Teal 400 */
|
||||||
|
--vp-c-brand-2: #38bdf8; /* Sky 400 */
|
||||||
|
--vp-c-brand-3: #14b8a6; /* Teal 500 */
|
||||||
|
--vp-c-brand-soft: rgba(45, 212, 191, 0.15); /* 淡淡的荧光底色 */
|
||||||
|
|
||||||
|
/* 阴影:暗色环境下的微光 */
|
||||||
|
--vp-shadow-1: 0 4px 6px -1px rgba(0, 0, 0, 0.5);
|
||||||
|
--vp-shadow-2: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
|
||||||
|
--vp-shadow-brand: 0 0 20px rgba(45, 212, 191, 0.3); /* 品牌色发光 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 全局基础设置 */
|
||||||
|
body {
|
||||||
|
font-family: 'Noto Sans SC', sans-serif;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
margin: 0;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
a { text-decoration: none; color: inherit; transition: color 0.2s; }
|
||||||
|
|
||||||
|
/* -------------------------------------------
|
||||||
|
布局结构 (模拟 VitePress 布局)
|
||||||
|
------------------------------------------- */
|
||||||
|
.app-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 1. 导航栏:毛玻璃 + 悬浮感 */
|
||||||
|
.VPNavBar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: var(--vp-nav-height);
|
||||||
|
background-color: rgba(255, 255, 255, 0.85); /* 稍微增加不透明度 */
|
||||||
|
backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
||||||
|
border-bottom: 1px solid rgba(226, 232, 240, 0.6);
|
||||||
|
z-index: 50;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 24px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.dark .VPNavBar {
|
||||||
|
background-color: rgba(2, 6, 23, 0.85); /* 深色背景半透 */
|
||||||
|
border-bottom: 1px solid rgba(30, 41, 59, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-logo {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-links {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link.active {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.nav-link.active::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -22px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: var(--vp-c-brand-1);
|
||||||
|
border-radius: 2px 2px 0 0;
|
||||||
|
box-shadow: 0 -2px 10px var(--vp-c-brand-1); /* 激活状态发光 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 2. 侧边栏:极简卡片式 */
|
||||||
|
.VPSidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: var(--vp-nav-height);
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: var(--vp-sidebar-width);
|
||||||
|
background-color: var(--vp-c-bg); /* 实色背景 */
|
||||||
|
border-right: 1px solid var(--vp-c-border);
|
||||||
|
padding: 32px 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
transform: translateX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
.dark .VPSidebar {
|
||||||
|
background-color: var(--vp-c-bg-alt); /* 侧边栏比内容区稍微亮一点点/或者暗一点 */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.VPSidebar { transform: translateX(-100%); }
|
||||||
|
.VPSidebar.open { transform: translateX(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-group-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
.sidebar-group-title:first-child { margin-top: 0; }
|
||||||
|
|
||||||
|
.sidebar-link {
|
||||||
|
display: block;
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
border-radius: var(--vp-radius-small);
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-link:hover {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
.dark .sidebar-link:hover {
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-link.active {
|
||||||
|
background-color: var(--vp-c-brand-soft);
|
||||||
|
color: var(--vp-c-brand-3);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.dark .sidebar-link.active {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
background-color: rgba(45, 212, 191, 0.1); /* 暗色模式下更通透 */
|
||||||
|
box-shadow: inset 2px 0 0 0 var(--vp-c-brand-1); /* 左侧高亮条 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 3. 主内容区:居中与留白 */
|
||||||
|
.VPContent {
|
||||||
|
margin-top: var(--vp-nav-height);
|
||||||
|
margin-left: var(--vp-sidebar-width);
|
||||||
|
padding: 48px 64px;
|
||||||
|
min-height: calc(100vh - var(--vp-nav-height));
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
transition: margin-left 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.VPContent { margin-left: 0; padding: 32px 24px; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 内容排版 (Typography) */
|
||||||
|
.vp-doc h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.2;
|
||||||
|
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
display: inline-block;
|
||||||
|
/* 暗色模式下加一点文字阴影增强可读性 */
|
||||||
|
filter: drop-shadow(0 0 2em rgba(20, 184, 166, 0.2));
|
||||||
|
}
|
||||||
|
.dark .vp-doc h1 {
|
||||||
|
filter: drop-shadow(0 0 25px rgba(45, 212, 191, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h2 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 1px solid var(--vp-c-border);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc h3 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc p {
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 代码块美化 */
|
||||||
|
.vp-doc pre {
|
||||||
|
background-color: #1e293b; /* Dark Slate */
|
||||||
|
border-radius: var(--vp-radius-default);
|
||||||
|
padding: 20px;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: #f8fafc;
|
||||||
|
margin: 24px 0;
|
||||||
|
border: 1px solid rgba(0,0,0,0.1);
|
||||||
|
box-shadow: var(--vp-shadow-1);
|
||||||
|
}
|
||||||
|
.dark .vp-doc pre {
|
||||||
|
background-color: #0f172a; /* 更深一层的背景 */
|
||||||
|
border: 1px solid #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-doc code {
|
||||||
|
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.vp-doc p code {
|
||||||
|
background-color: var(--vp-c-brand-soft);
|
||||||
|
color: var(--vp-c-brand-3);
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
.dark .vp-doc p code {
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
background-color: rgba(45, 212, 191, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 引用块 (Blockquote) */
|
||||||
|
.vp-doc blockquote {
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
padding: 16px 24px;
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
border-left: 4px solid var(--vp-c-brand-1);
|
||||||
|
border-radius: 0 var(--vp-radius-default) var(--vp-radius-default) 0;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
}
|
||||||
|
.dark .vp-doc blockquote {
|
||||||
|
background-color: rgba(30, 41, 59, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮与卡片 (Features) */
|
||||||
|
.vp-button {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 24px;
|
||||||
|
height: 48px;
|
||||||
|
line-height: 48px;
|
||||||
|
border-radius: 24px; /* Pill shape */
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1rem;
|
||||||
|
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-button.brand {
|
||||||
|
background: linear-gradient(135deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
|
||||||
|
color: white;
|
||||||
|
box-shadow: var(--vp-shadow-brand);
|
||||||
|
}
|
||||||
|
.dark .vp-button.brand {
|
||||||
|
color: #020617; /* 暗色模式下按钮文字用深色,因为背景亮 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-button.brand:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px -4px rgba(20, 184, 166, 0.5);
|
||||||
|
}
|
||||||
|
.dark .vp-button.brand:hover {
|
||||||
|
box-shadow: 0 0 30px -4px rgba(45, 212, 191, 0.6); /* 发光增强 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-button.alt {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
border: 1px solid var(--vp-c-border);
|
||||||
|
}
|
||||||
|
.dark .vp-button.alt {
|
||||||
|
background-color: transparent;
|
||||||
|
border-color: var(--vp-c-text-2);
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vp-button.alt:hover {
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工具类 */
|
||||||
|
.theme-toggle {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
.theme-toggle:hover {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
}
|
||||||
|
.dark .theme-toggle:hover {
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.mobile-menu-btn { display: block; }
|
||||||
|
.nav-links { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 首页 Hero 样式模拟 */
|
||||||
|
.hero-section {
|
||||||
|
text-align: center;
|
||||||
|
padding: 80px 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1.1;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.hero-title span {
|
||||||
|
background: linear-gradient(120deg, var(--vp-c-brand-1), var(--vp-c-brand-2));
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.hero-desc {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Features Grid */
|
||||||
|
.features-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 64px;
|
||||||
|
}
|
||||||
|
.feature-card {
|
||||||
|
background-color: var(--vp-c-bg-alt);
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: var(--vp-radius-default);
|
||||||
|
transition: transform 0.3s, box-shadow 0.3s;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
.dark .feature-card {
|
||||||
|
background-color: rgba(30, 41, 59, 0.3); /* 微微透明 */
|
||||||
|
border: 1px solid rgba(255,255,255,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-card:hover {
|
||||||
|
background-color: var(--vp-c-bg);
|
||||||
|
transform: translateY(-4px);
|
||||||
|
box-shadow: var(--vp-shadow-2);
|
||||||
|
border-color: var(--vp-c-brand-soft);
|
||||||
|
}
|
||||||
|
.dark .feature-card:hover {
|
||||||
|
background-color: var(--vp-c-bg-elv);
|
||||||
|
border-color: var(--vp-c-brand-1);
|
||||||
|
box-shadow: 0 0 15px rgba(45, 212, 191, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.feature-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: var(--vp-c-brand-1);
|
||||||
|
background-color: var(--vp-c-brand-soft);
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.feature-title {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
}
|
||||||
|
.feature-details {
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import DefaultTheme from 'vitepress/theme'
|
||||||
|
import './custom.css'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
...DefaultTheme,
|
||||||
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
# Configuration Guide
|
||||||
|
|
||||||
|
FileCodeBox provides rich configuration options that can be customized through the admin panel or by directly modifying the configuration. This document details all available configuration options.
|
||||||
|
|
||||||
|
## Configuration Methods
|
||||||
|
|
||||||
|
FileCodeBox supports two configuration methods:
|
||||||
|
|
||||||
|
1. **Admin Panel Configuration** (Recommended): Access `/admin` to enter the admin panel and modify settings on the settings page
|
||||||
|
2. **Database Configuration**: Configuration is stored in the `data/filecodebox.db` database
|
||||||
|
|
||||||
|
::: tip Note
|
||||||
|
On first startup, the system uses default configuration from `core/settings.py`. Modified configurations are saved to the database.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Basic Settings
|
||||||
|
|
||||||
|
### Site Information
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `name` | string | `文件快递柜 - FileCodeBox` | Site name, displayed in page title and navigation bar |
|
||||||
|
| `description` | string | `开箱即用的文件快传系统` | Site description, used for SEO |
|
||||||
|
| `keywords` | string | `FileCodeBox, 文件快递柜...` | Site keywords, used for SEO |
|
||||||
|
| `port` | int | `12345` | Service listening port |
|
||||||
|
|
||||||
|
### Notification Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `notify_title` | string | `系统通知` | Notification title |
|
||||||
|
| `notify_content` | string | Welcome message | Notification content, supports HTML |
|
||||||
|
| `page_explain` | string | Legal disclaimer | Footer explanation text |
|
||||||
|
| `robotsText` | string | `User-agent: *\nDisallow: /` | robots.txt content |
|
||||||
|
|
||||||
|
## Upload Settings
|
||||||
|
|
||||||
|
### File Upload Limits
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `openUpload` | int | `1` | Enable upload functionality (1=enabled, 0=disabled) |
|
||||||
|
| `uploadSize` | int | `10485760` | Maximum single file upload size (bytes), default 10MB |
|
||||||
|
| `enableChunk` | int | `0` | Enable chunked upload (1=enabled, 0=disabled) |
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
`uploadSize` is in bytes. 10MB = 10 * 1024 * 1024 = 10485760 bytes
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Upload Rate Limiting
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `uploadMinute` | int | `1` | Upload limit time window (minutes) |
|
||||||
|
| `uploadCount` | int | `10` | Maximum uploads allowed within the time window |
|
||||||
|
|
||||||
|
Example: Default configuration allows up to 10 uploads per minute.
|
||||||
|
|
||||||
|
|
||||||
|
### File Expiration Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `expireStyle` | list | `["day","hour","minute","forever","count"]` | Available expiration methods |
|
||||||
|
| `max_save_seconds` | int | `0` | Maximum file retention time (seconds), 0 means no limit |
|
||||||
|
|
||||||
|
Expiration methods explained:
|
||||||
|
- `day` - Expire by days
|
||||||
|
- `hour` - Expire by hours
|
||||||
|
- `minute` - Expire by minutes
|
||||||
|
- `forever` - Never expire
|
||||||
|
- `count` - Expire by download count
|
||||||
|
|
||||||
|
## Theme Settings
|
||||||
|
|
||||||
|
### Theme Selection
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `themesSelect` | string | `themes/2024` | Currently active theme |
|
||||||
|
| `themesChoices` | list | See below | Available themes list |
|
||||||
|
|
||||||
|
Default available themes:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "2023",
|
||||||
|
"key": "themes/2023",
|
||||||
|
"author": "Lan",
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2024",
|
||||||
|
"key": "themes/2024",
|
||||||
|
"author": "Lan",
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Interface Style
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `opacity` | float | `0.9` | Interface opacity (0-1) |
|
||||||
|
| `background` | string | `""` | Custom background image URL, empty uses default background |
|
||||||
|
|
||||||
|
## Admin Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `admin_token` | string | `FileCodeBox2023` | Admin login password |
|
||||||
|
| `showAdminAddr` | int | `0` | Show admin panel entry on homepage (1=show, 0=hide) |
|
||||||
|
|
||||||
|
::: danger Security Warning
|
||||||
|
Always change the default `admin_token` in production environments! Using the default password poses serious security risks.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Security Settings
|
||||||
|
|
||||||
|
### Error Rate Limiting
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `errorMinute` | int | `1` | Error limit time window (minutes) |
|
||||||
|
| `errorCount` | int | `1` | Maximum errors allowed within the time window |
|
||||||
|
|
||||||
|
This setting prevents brute-force attacks on extraction codes.
|
||||||
|
|
||||||
|
## Storage Settings
|
||||||
|
|
||||||
|
### Storage Type
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `file_storage` | string | `local` | Storage backend type |
|
||||||
|
| `storage_path` | string | `""` | Custom storage path |
|
||||||
|
|
||||||
|
Supported storage types:
|
||||||
|
- `local` - Local storage
|
||||||
|
- `s3` - S3-compatible storage (AWS S3, Aliyun OSS, MinIO, etc.)
|
||||||
|
- `onedrive` - OneDrive storage
|
||||||
|
- `webdav` - WebDAV storage
|
||||||
|
- `opendal` - OpenDAL storage
|
||||||
|
|
||||||
|
For detailed storage configuration, see [Storage Configuration](/en/guide/storage).
|
||||||
|
|
||||||
|
## Configuration Examples
|
||||||
|
|
||||||
|
### Example 1: Small Personal Use
|
||||||
|
|
||||||
|
Suitable for personal or small team use with relaxed limits:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "My File Share",
|
||||||
|
"uploadSize": 52428800, # 50MB
|
||||||
|
"uploadMinute": 5, # 5 minutes
|
||||||
|
"uploadCount": 20, # Max 20 uploads
|
||||||
|
"expireStyle": ["day", "hour", "forever"],
|
||||||
|
"admin_token": "your-secure-password",
|
||||||
|
"showAdminAddr": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Public Service
|
||||||
|
|
||||||
|
Suitable for public services requiring stricter limits:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "Public File Box",
|
||||||
|
"uploadSize": 10485760, # 10MB
|
||||||
|
"uploadMinute": 1, # 1 minute
|
||||||
|
"uploadCount": 5, # Max 5 uploads
|
||||||
|
"errorMinute": 5, # 5 minutes
|
||||||
|
"errorCount": 3, # Max 3 errors
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400, # Max retention 1 day
|
||||||
|
"admin_token": "very-secure-password-123",
|
||||||
|
"showAdminAddr": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Enterprise Internal Use
|
||||||
|
|
||||||
|
Suitable for enterprise internal use with large file and chunked upload support:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "Enterprise File Transfer",
|
||||||
|
"uploadSize": 1073741824, # 1GB
|
||||||
|
"enableChunk": 1, # Enable chunked upload
|
||||||
|
"uploadMinute": 10, # 10 minutes
|
||||||
|
"uploadCount": 100, # Max 100 uploads
|
||||||
|
"expireStyle": ["day", "forever"],
|
||||||
|
"file_storage": "s3", # Use S3 storage
|
||||||
|
"admin_token": "enterprise-secure-token",
|
||||||
|
"showAdminAddr": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Storage Configuration](/en/guide/storage) - Learn how to configure different storage backends
|
||||||
|
- [Security Settings](/en/guide/security) - Learn how to enhance system security
|
||||||
|
- [File Sharing](/en/guide/share) - Learn about file sharing features
|
||||||
|
|||||||
@@ -0,0 +1,413 @@
|
|||||||
|
# Admin Panel
|
||||||
|
|
||||||
|
FileCodeBox provides a fully-featured admin panel that allows administrators to conveniently manage files, view system status, and modify configurations. This document introduces the various features and usage of the admin panel.
|
||||||
|
|
||||||
|
## Accessing the Admin Panel
|
||||||
|
|
||||||
|
### Login Method
|
||||||
|
|
||||||
|
The admin panel is located at the `/admin` path. Access method:
|
||||||
|
|
||||||
|
1. Visit `http://your-domain.com/admin` in your browser
|
||||||
|
2. Enter the admin password (the value of the `admin_token` configuration)
|
||||||
|
3. Click the login button
|
||||||
|
|
||||||
|
::: tip Tip
|
||||||
|
The default admin password is `FileCodeBox2023`. Be sure to change this password in production environments. See [Security Settings](/en/guide/security) for details.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Show Admin Entry
|
||||||
|
|
||||||
|
By default, the admin panel entry is not shown on the homepage. You can control whether to show it via configuration:
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `showAdminAddr` | int | `0` | Show admin entry on homepage (1=show, 0=hide) |
|
||||||
|
|
||||||
|
::: warning Security Recommendation
|
||||||
|
For public services, it's recommended to keep `showAdminAddr` at `0` and access the admin panel directly via the `/admin` path to reduce the risk of malicious scanning.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Authentication Mechanism
|
||||||
|
|
||||||
|
The admin panel uses JWT (JSON Web Token) for authentication:
|
||||||
|
|
||||||
|
1. After successful login, the server returns a Token containing admin identity
|
||||||
|
2. Subsequent requests carry the Token via `Authorization: Bearer <token>` header
|
||||||
|
3. Token is used to verify admin identity, ensuring only authorized users can access admin functions
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
After logging in, you first see the dashboard page, which displays the overall system status.
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
The dashboard displays the following key metrics:
|
||||||
|
|
||||||
|
| Metric | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Total Files** (`totalFiles`) | Total number of files stored in the system |
|
||||||
|
| **Storage Used** (`storageUsed`) | Total storage space occupied by all files (bytes) |
|
||||||
|
| **System Uptime** (`sysUptime`) | Time when the system was first started |
|
||||||
|
| **Yesterday's Uploads** (`yesterdayCount`) | Number of files uploaded yesterday |
|
||||||
|
| **Yesterday's Upload Size** (`yesterdaySize`) | Total size of files uploaded yesterday (bytes) |
|
||||||
|
| **Today's Uploads** (`todayCount`) | Number of files uploaded today so far |
|
||||||
|
| **Today's Upload Size** (`todaySize`) | Total size of files uploaded today (bytes) |
|
||||||
|
|
||||||
|
### Metric Notes
|
||||||
|
|
||||||
|
- **Total Files**: Includes all unexpired files and text shares
|
||||||
|
- **Storage Used**: Shows actual storage space occupied by files, excluding database and other system files
|
||||||
|
- **Yesterday/Today Statistics**: Calculated based on file creation time, useful for understanding system usage trends
|
||||||
|
|
||||||
|
::: tip Tip
|
||||||
|
Storage usage is displayed in bytes. For example, `10485760` represents approximately 10MB.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## File Management
|
||||||
|
|
||||||
|
### File List
|
||||||
|
|
||||||
|
The file management page displays all shared files in the system, supporting pagination and search.
|
||||||
|
|
||||||
|
**List information includes:**
|
||||||
|
- File ID
|
||||||
|
- Extraction code (code)
|
||||||
|
- Filename prefix (prefix)
|
||||||
|
- File extension (suffix)
|
||||||
|
- File size
|
||||||
|
- Creation time
|
||||||
|
- Expiration time
|
||||||
|
- Remaining download count
|
||||||
|
|
||||||
|
### Search Files
|
||||||
|
|
||||||
|
Use the search function to quickly find specific files:
|
||||||
|
|
||||||
|
1. Enter keywords in the search box
|
||||||
|
2. System performs fuzzy matching based on filename prefix (prefix)
|
||||||
|
3. Search results update in real-time
|
||||||
|
|
||||||
|
**Search examples:**
|
||||||
|
- Enter `report` to find all files with "report" in the filename
|
||||||
|
- Enter `.pdf` to find all PDF files (if the filename contains this string)
|
||||||
|
|
||||||
|
### Pagination
|
||||||
|
|
||||||
|
The file list supports paginated display:
|
||||||
|
|
||||||
|
| Parameter | Default | Description |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| `page` | `1` | Current page number |
|
||||||
|
| `size` | `10` | Items per page |
|
||||||
|
|
||||||
|
|
||||||
|
### Delete Files
|
||||||
|
|
||||||
|
Administrators can delete any file:
|
||||||
|
|
||||||
|
1. Find the file to delete in the file list
|
||||||
|
2. Click the delete button
|
||||||
|
3. Confirm the delete operation
|
||||||
|
|
||||||
|
::: danger Warning
|
||||||
|
Delete operations are irreversible! Files will be permanently deleted from the storage backend, and database records will also be removed.
|
||||||
|
:::
|
||||||
|
|
||||||
|
**Delete process:**
|
||||||
|
1. System first deletes the actual file from the storage backend (local/S3/OneDrive, etc.)
|
||||||
|
2. Then deletes the file record from the database
|
||||||
|
3. After deletion, the corresponding extraction code becomes invalid
|
||||||
|
|
||||||
|
### Download Files
|
||||||
|
|
||||||
|
Administrators can directly download any file:
|
||||||
|
|
||||||
|
1. Find the target file in the file list
|
||||||
|
2. Click the download button
|
||||||
|
3. File will be downloaded via browser
|
||||||
|
|
||||||
|
For text shares, the system returns text content directly instead of downloading a file.
|
||||||
|
|
||||||
|
### Modify File Information
|
||||||
|
|
||||||
|
Administrators can modify some information of shared files:
|
||||||
|
|
||||||
|
| Modifiable Field | Description |
|
||||||
|
|------------------|-------------|
|
||||||
|
| `code` | Extraction code (must be unique, cannot duplicate other files) |
|
||||||
|
| `prefix` | Filename prefix |
|
||||||
|
| `suffix` | File extension |
|
||||||
|
| `expired_at` | Expiration time |
|
||||||
|
| `expired_count` | Remaining download count |
|
||||||
|
|
||||||
|
**Modify extraction code:**
|
||||||
|
```
|
||||||
|
Original code: abc123
|
||||||
|
New code: myfile2024
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
When modifying extraction codes, the system checks if the new code is already in use. If an identical extraction code exists, the modification will fail.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Local File Management
|
||||||
|
|
||||||
|
In addition to managing shared files, the admin panel also provides local file management functionality for managing files in the `data/local` directory.
|
||||||
|
|
||||||
|
### View Local Files
|
||||||
|
|
||||||
|
The local file list displays all files in the `data/local` directory:
|
||||||
|
|
||||||
|
| Information | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Filename | Complete filename |
|
||||||
|
| Creation Time | File creation time |
|
||||||
|
| File Size | File size (bytes) |
|
||||||
|
|
||||||
|
### Share Local Files
|
||||||
|
|
||||||
|
You can quickly share local files:
|
||||||
|
|
||||||
|
1. Select the file to share in the local file list
|
||||||
|
2. Set expiration method and value
|
||||||
|
3. Click the share button
|
||||||
|
4. System generates extraction code
|
||||||
|
|
||||||
|
**Share parameters:**
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `filename` | Filename to share |
|
||||||
|
| `expire_style` | Expiration method (day/hour/minute/forever/count) |
|
||||||
|
| `expire_value` | Expiration value (days/hours/minutes/download count) |
|
||||||
|
|
||||||
|
### Delete Local Files
|
||||||
|
|
||||||
|
You can delete files in the `data/local` directory:
|
||||||
|
|
||||||
|
1. Find the file to delete in the local file list
|
||||||
|
2. Click the delete button
|
||||||
|
3. Confirm deletion
|
||||||
|
|
||||||
|
::: tip Use Cases
|
||||||
|
Local file management is useful for:
|
||||||
|
- Sharing files after batch uploading to the server
|
||||||
|
- Managing files uploaded to the server through other means
|
||||||
|
- Cleaning up unnecessary local files
|
||||||
|
:::
|
||||||
|
|
||||||
|
## System Settings
|
||||||
|
|
||||||
|
### View Configuration
|
||||||
|
|
||||||
|
On the system settings page, you can view the current values of all configuration items. Configuration items are grouped by category:
|
||||||
|
|
||||||
|
- Basic settings (site name, description, etc.)
|
||||||
|
- Upload settings (file size limits, rate limits, etc.)
|
||||||
|
- Storage settings (storage type, path, etc.)
|
||||||
|
- Theme settings (theme selection, opacity, etc.)
|
||||||
|
- Security settings (admin password, error limits, etc.)
|
||||||
|
|
||||||
|
### Modify Configuration
|
||||||
|
|
||||||
|
Administrators can modify most configurations through the admin panel:
|
||||||
|
|
||||||
|
1. Go to the system settings page
|
||||||
|
2. Find the configuration item to modify
|
||||||
|
3. Enter the new value
|
||||||
|
4. Click the save button
|
||||||
|
|
||||||
|
**Modifiable configuration items:**
|
||||||
|
|
||||||
|
| Category | Example Settings |
|
||||||
|
|----------|------------------|
|
||||||
|
| Basic Settings | `name`, `description`, `keywords`, `notify_title`, `notify_content` |
|
||||||
|
| Upload Settings | `uploadSize`, `uploadMinute`, `uploadCount`, `openUpload`, `enableChunk` |
|
||||||
|
| Expiration Settings | `expireStyle`, `max_save_seconds` |
|
||||||
|
| Theme Settings | `themesSelect`, `opacity`, `background` |
|
||||||
|
| Security Settings | `admin_token`, `showAdminAddr`, `errorMinute`, `errorCount` |
|
||||||
|
| Storage Settings | `file_storage`, `storage_path` and storage backend-specific configurations |
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
- `admin_token` (admin password) cannot be set to empty
|
||||||
|
- `themesChoices` (theme list) cannot be modified through the admin panel
|
||||||
|
- After modifying storage settings, existing files will not be automatically migrated
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Configuration Effect
|
||||||
|
|
||||||
|
Configuration changes take effect immediately without restarting the service. Configurations are saved in the database and persist after restart.
|
||||||
|
|
||||||
|
**Configuration storage location:**
|
||||||
|
- Database: `data/filecodebox.db`
|
||||||
|
- Table name: `keyvalue`
|
||||||
|
- Key name: `settings`
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
All admin panel functions are implemented through REST APIs. Here are the main endpoints:
|
||||||
|
|
||||||
|
### Authentication Endpoint
|
||||||
|
|
||||||
|
**Login**
|
||||||
|
```
|
||||||
|
POST /admin/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "your-admin-password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
||||||
|
"token_type": "Bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Dashboard Endpoint
|
||||||
|
|
||||||
|
**Get Statistics**
|
||||||
|
```
|
||||||
|
GET /admin/dashboard
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Management Endpoints
|
||||||
|
|
||||||
|
**Get File List**
|
||||||
|
```
|
||||||
|
GET /admin/file/list?page=1&size=10&keyword=
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete File**
|
||||||
|
```
|
||||||
|
DELETE /admin/file/delete
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 123
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Download File**
|
||||||
|
```
|
||||||
|
GET /admin/file/download?id=123
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Modify File Information**
|
||||||
|
```
|
||||||
|
PATCH /admin/file/update
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"code": "newcode",
|
||||||
|
"expired_at": "2024-12-31T23:59:59"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Local File Endpoints
|
||||||
|
|
||||||
|
**Get Local File List**
|
||||||
|
```
|
||||||
|
GET /admin/local/lists
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete Local File**
|
||||||
|
```
|
||||||
|
DELETE /admin/local/delete
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"filename": "example.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Share Local File**
|
||||||
|
```
|
||||||
|
POST /admin/local/share
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"filename": "example.txt",
|
||||||
|
"expire_style": "day",
|
||||||
|
"expire_value": 7
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Endpoints
|
||||||
|
|
||||||
|
**Get Configuration**
|
||||||
|
```
|
||||||
|
GET /admin/config/get
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Update Configuration**
|
||||||
|
```
|
||||||
|
PATCH /admin/config/update
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"admin_token": "new-password",
|
||||||
|
"uploadSize": 52428800
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### Forgot Admin Password
|
||||||
|
|
||||||
|
If you forgot the admin password, you can reset it through the following methods:
|
||||||
|
|
||||||
|
1. Stop the FileCodeBox service
|
||||||
|
2. Open `data/filecodebox.db` using an SQLite tool
|
||||||
|
3. Query the record with `key='settings'` in the `keyvalue` table
|
||||||
|
4. Modify the `admin_token` value in the JSON
|
||||||
|
5. Restart the service
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- View current configuration
|
||||||
|
SELECT * FROM keyvalue WHERE key = 'settings';
|
||||||
|
|
||||||
|
-- Or delete configuration to restore default password
|
||||||
|
DELETE FROM keyvalue WHERE key = 'settings';
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Deletion Failed
|
||||||
|
|
||||||
|
If an error occurs when deleting files, possible reasons:
|
||||||
|
|
||||||
|
1. **Storage backend connection failed**: Check if storage configuration is correct
|
||||||
|
2. **File no longer exists**: File may have been manually deleted
|
||||||
|
3. **Insufficient permissions**: Check write permissions for storage directory
|
||||||
|
|
||||||
|
### Configuration Changes Not Taking Effect
|
||||||
|
|
||||||
|
If configuration changes don't take effect:
|
||||||
|
|
||||||
|
1. Check if you clicked the save button
|
||||||
|
2. Refresh the page to see if configuration was saved
|
||||||
|
3. Check browser console for error messages
|
||||||
|
4. Confirm configuration value format is correct (e.g., don't enter strings for numeric types)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Configuration Guide](/en/guide/configuration) - Learn detailed descriptions of all configuration options
|
||||||
|
- [Security Settings](/en/guide/security) - Learn how to enhance system security
|
||||||
|
- [Storage Configuration](/en/guide/storage) - Configure different storage backends
|
||||||
|
|||||||
@@ -0,0 +1,325 @@
|
|||||||
|
# Security Settings
|
||||||
|
|
||||||
|
FileCodeBox provides multiple layers of security mechanisms to protect your file sharing service. This document explains how to properly configure security options to ensure secure system operation.
|
||||||
|
|
||||||
|
## Admin Password
|
||||||
|
|
||||||
|
### Change Default Password
|
||||||
|
|
||||||
|
::: danger Important Security Warning
|
||||||
|
FileCodeBox's default admin password is `FileCodeBox2023`. **You must change this password immediately in production environments!** Using the default password allows anyone to access your admin panel.
|
||||||
|
:::
|
||||||
|
|
||||||
|
There are two ways to change the admin password:
|
||||||
|
|
||||||
|
**Method 1: Via Admin Panel (Recommended)**
|
||||||
|
|
||||||
|
1. Access `/admin` to enter the admin panel
|
||||||
|
2. Log in with the current password
|
||||||
|
3. Go to the "System Settings" page
|
||||||
|
4. Find the `admin_token` configuration item
|
||||||
|
5. Enter a new secure password and save
|
||||||
|
|
||||||
|
**Method 2: Via Database**
|
||||||
|
|
||||||
|
Configuration is stored in the `keyvalue` table of the `data/filecodebox.db` database. You can directly modify the `admin_token` value.
|
||||||
|
|
||||||
|
### Password Security Recommendations
|
||||||
|
|
||||||
|
- Use a strong password with at least 16 characters
|
||||||
|
- Include uppercase and lowercase letters, numbers, and special characters
|
||||||
|
- Avoid common words or personal information
|
||||||
|
- Change password regularly
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Recommended password format example
|
||||||
|
"admin_token": "Xk9#mP2$vL5@nQ8&wR3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hide Admin Entry
|
||||||
|
|
||||||
|
By default, the admin panel entry is hidden. You can control whether to show the admin entry on the homepage via the `showAdminAddr` configuration:
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `showAdminAddr` | int | `0` | Show admin entry (1=show, 0=hide) |
|
||||||
|
|
||||||
|
::: tip Recommendation
|
||||||
|
For public services, it's recommended to keep `showAdminAddr` at `0` and access the admin panel directly via the `/admin` path.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## IP Rate Limiting
|
||||||
|
|
||||||
|
FileCodeBox has built-in IP-based rate limiting mechanisms to effectively prevent abuse and attacks.
|
||||||
|
|
||||||
|
### Upload Rate Limiting
|
||||||
|
|
||||||
|
Limit the number of uploads from a single IP within a specified time:
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `uploadMinute` | int | `1` | Upload limit time window (minutes) |
|
||||||
|
| `uploadCount` | int | `10` | Maximum uploads allowed within the time window |
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- System records upload requests from each IP
|
||||||
|
- When an IP's upload count reaches `uploadCount` within `uploadMinute` minutes
|
||||||
|
- Subsequent upload requests from that IP will be rejected with HTTP 423 error
|
||||||
|
- Counter resets after the time window expires
|
||||||
|
|
||||||
|
**Configuration examples:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Relaxed configuration: Max 20 uploads in 5 minutes
|
||||||
|
{
|
||||||
|
"uploadMinute": 5,
|
||||||
|
"uploadCount": 20
|
||||||
|
}
|
||||||
|
|
||||||
|
# Strict configuration: Max 3 uploads in 1 minute
|
||||||
|
{
|
||||||
|
"uploadMinute": 1,
|
||||||
|
"uploadCount": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Error Rate Limiting
|
||||||
|
|
||||||
|
Limit the number of error attempts from a single IP to prevent brute-force attacks on extraction codes:
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `errorMinute` | int | `1` | Error limit time window (minutes) |
|
||||||
|
| `errorCount` | int | `1` | Maximum errors allowed within the time window |
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- When a user enters an incorrect extraction code, the system records the error count for that IP
|
||||||
|
- When error count reaches `errorCount`, that IP will be temporarily locked
|
||||||
|
- Lock duration is `errorMinute` minutes
|
||||||
|
- During lockout, all extraction requests from that IP will be rejected
|
||||||
|
|
||||||
|
**Configuration example:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Anti-brute-force configuration: Max 3 errors in 5 minutes
|
||||||
|
{
|
||||||
|
"errorMinute": 5,
|
||||||
|
"errorCount": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
The default configuration `errorMinute=1, errorCount=1` is very strict, meaning you need to wait 1 minute after entering one incorrect extraction code before retrying. Adjust this configuration based on actual needs.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Upload Restrictions
|
||||||
|
|
||||||
|
### File Size Limit
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `uploadSize` | int | `10485760` | Maximum single file upload size (bytes), default 10MB |
|
||||||
|
| `openUpload` | int | `1` | Enable upload functionality (1=enabled, 0=disabled) |
|
||||||
|
|
||||||
|
**Common size conversions:**
|
||||||
|
- 10MB = 10 * 1024 * 1024 = `10485760`
|
||||||
|
- 50MB = 50 * 1024 * 1024 = `52428800`
|
||||||
|
- 100MB = 100 * 1024 * 1024 = `104857600`
|
||||||
|
- 1GB = 1024 * 1024 * 1024 = `1073741824`
|
||||||
|
|
||||||
|
### File Expiration Settings
|
||||||
|
|
||||||
|
Through file expiration mechanisms, you can automatically clean up expired files, reducing storage usage and security risks:
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `expireStyle` | list | `["day","hour","minute","forever","count"]` | Available expiration methods |
|
||||||
|
| `max_save_seconds` | int | `0` | Maximum file retention time (seconds), 0 means no limit |
|
||||||
|
|
||||||
|
**Expiration methods explained:**
|
||||||
|
- `day` - Expire by days
|
||||||
|
- `hour` - Expire by hours
|
||||||
|
- `minute` - Expire by minutes
|
||||||
|
- `forever` - Never expire (requires alphanumeric extraction code)
|
||||||
|
- `count` - Expire by download count
|
||||||
|
|
||||||
|
**Security recommendations:**
|
||||||
|
|
||||||
|
For public services, it's recommended to:
|
||||||
|
1. Remove the `forever` option to avoid permanent file storage
|
||||||
|
2. Set `max_save_seconds` to limit maximum retention time
|
||||||
|
3. Prefer using `count` method for automatic deletion after download
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Recommended configuration for public services
|
||||||
|
{
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400 # Max retention 1 day
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable Upload Functionality
|
||||||
|
|
||||||
|
In some cases, you may need to temporarily disable upload functionality:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"openUpload": 0 # Disable upload functionality
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reverse Proxy Security Configuration
|
||||||
|
|
||||||
|
In production environments, Nginx or other reverse proxy servers are typically used. Here are security configuration recommendations:
|
||||||
|
|
||||||
|
### Nginx Configuration Example
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# Force HTTPS redirect
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# SSL certificate configuration
|
||||||
|
ssl_certificate /path/to/cert.pem;
|
||||||
|
ssl_certificate_key /path/to/key.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
# Limit request body size (match uploadSize configuration)
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
|
# Pass real IP
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:12345;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Static resource caching
|
||||||
|
location /assets {
|
||||||
|
proxy_pass http://127.0.0.1:12345;
|
||||||
|
proxy_cache_valid 200 7d;
|
||||||
|
add_header Cache-Control "public, max-age=604800";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Security Configuration Notes
|
||||||
|
|
||||||
|
**1. Pass Real IP**
|
||||||
|
|
||||||
|
FileCodeBox's IP limiting functionality depends on obtaining the client's real IP. The system obtains IP in the following order:
|
||||||
|
1. `X-Real-IP` request header
|
||||||
|
2. `X-Forwarded-For` request header
|
||||||
|
3. Direct client connection IP
|
||||||
|
|
||||||
|
Ensure the reverse proxy correctly sets these headers:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Request Body Size Limit**
|
||||||
|
|
||||||
|
Nginx's `client_max_body_size` should match or be slightly larger than FileCodeBox's `uploadSize` configuration:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
client_max_body_size 100M; # Allow max 100MB uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. HTTPS Encryption**
|
||||||
|
|
||||||
|
It's strongly recommended to enable HTTPS in production environments:
|
||||||
|
- Protect uploaded file content
|
||||||
|
- Protect admin login credentials
|
||||||
|
- Prevent man-in-the-middle attacks
|
||||||
|
|
||||||
|
### Caddy Configuration Example
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
your-domain.com {
|
||||||
|
reverse_proxy localhost:12345
|
||||||
|
|
||||||
|
header {
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
Before deploying FileCodeBox, confirm the following security configurations:
|
||||||
|
|
||||||
|
- [ ] Changed default admin password `admin_token`
|
||||||
|
- [ ] Hidden admin entry `showAdminAddr: 0`
|
||||||
|
- [ ] Configured appropriate upload rate limiting
|
||||||
|
- [ ] Configured error rate limiting to prevent brute-force attacks
|
||||||
|
- [ ] Set reasonable file size limits
|
||||||
|
- [ ] Configured file expiration policy
|
||||||
|
- [ ] Enabled HTTPS encryption
|
||||||
|
- [ ] Reverse proxy correctly passes real IP
|
||||||
|
- [ ] Set security response headers
|
||||||
|
|
||||||
|
## Recommended Security Configurations
|
||||||
|
|
||||||
|
### Public Service Configuration
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"admin_token": "your-very-secure-password",
|
||||||
|
"showAdminAddr": 0,
|
||||||
|
"uploadSize": 10485760, # 10MB
|
||||||
|
"uploadMinute": 1,
|
||||||
|
"uploadCount": 5,
|
||||||
|
"errorMinute": 5,
|
||||||
|
"errorCount": 3,
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400, # Max 1 day
|
||||||
|
"openUpload": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Internal Service Configuration
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"admin_token": "internal-secure-password",
|
||||||
|
"showAdminAddr": 1,
|
||||||
|
"uploadSize": 104857600, # 100MB
|
||||||
|
"uploadMinute": 5,
|
||||||
|
"uploadCount": 50,
|
||||||
|
"errorMinute": 1,
|
||||||
|
"errorCount": 5,
|
||||||
|
"expireStyle": ["day", "hour", "forever"],
|
||||||
|
"max_save_seconds": 0, # No limit
|
||||||
|
"openUpload": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Configuration Guide](/en/guide/configuration) - Learn about all configuration options
|
||||||
|
- [Storage Configuration](/en/guide/storage) - Configure secure storage backends
|
||||||
|
- [File Sharing](/en/guide/share) - Learn about file sharing features
|
||||||
|
|||||||
@@ -0,0 +1,342 @@
|
|||||||
|
# File Sharing
|
||||||
|
|
||||||
|
FileCodeBox provides simple and easy-to-use file and text sharing functionality. Users can securely share and retrieve files using extraction codes.
|
||||||
|
|
||||||
|
## Sharing Methods
|
||||||
|
|
||||||
|
FileCodeBox supports two sharing methods:
|
||||||
|
|
||||||
|
1. **Text Sharing** - Share text content directly, suitable for code snippets, configuration files, etc.
|
||||||
|
2. **File Sharing** - Upload files for sharing, supports various file formats
|
||||||
|
|
||||||
|
## Text Sharing
|
||||||
|
|
||||||
|
### How to Use
|
||||||
|
|
||||||
|
1. Select the "Text Share" tab on the homepage
|
||||||
|
2. Enter or paste the content to share in the text box
|
||||||
|
3. Select expiration method and time
|
||||||
|
4. Click the "Share" button
|
||||||
|
5. Get the extraction code
|
||||||
|
|
||||||
|
### Text Size Limit
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
The maximum content size for text sharing is **222KB** (227,328 bytes). If content exceeds this limit, it's recommended to use file sharing instead.
|
||||||
|
:::
|
||||||
|
|
||||||
|
Text content size is calculated using UTF-8 encoding. Chinese characters typically occupy 3 bytes.
|
||||||
|
|
||||||
|
### API Endpoint
|
||||||
|
|
||||||
|
**POST** `/share/text/`
|
||||||
|
|
||||||
|
Request parameters:
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `text` | string | Yes | Text content to share |
|
||||||
|
| `expire_value` | int | No | Expiration value, default 1 |
|
||||||
|
| `expire_style` | string | No | Expiration method, default `day` |
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Sharing
|
||||||
|
|
||||||
|
### How to Use
|
||||||
|
|
||||||
|
1. Select the "File Share" tab on the homepage
|
||||||
|
2. Click the upload area or drag files to the upload area
|
||||||
|
3. Select expiration method and time
|
||||||
|
4. Click the "Upload" button
|
||||||
|
5. Get the extraction code
|
||||||
|
|
||||||
|
### File Size Limit
|
||||||
|
|
||||||
|
The default maximum single file upload size is **10MB**. Administrators can modify this limit via the `uploadSize` configuration.
|
||||||
|
|
||||||
|
::: tip Tip
|
||||||
|
If you need to upload large files, contact the administrator to enable chunked upload functionality or adjust the `uploadSize` configuration.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Supported Upload Methods
|
||||||
|
|
||||||
|
- **Click Upload** - Click the upload area to select files
|
||||||
|
- **Drag Upload** - Drag files to the upload area
|
||||||
|
- **Paste Upload** - Paste images from clipboard (supported by some themes)
|
||||||
|
|
||||||
|
### API Endpoint
|
||||||
|
|
||||||
|
**POST** `/share/file/`
|
||||||
|
|
||||||
|
Request parameters (multipart/form-data):
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `file` | file | Yes | File to upload |
|
||||||
|
| `expire_value` | int | No | Expiration value, default 1 |
|
||||||
|
| `expire_style` | string | No | Expiration method, default `day` |
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "654321",
|
||||||
|
"name": "example.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Expiration Settings
|
||||||
|
|
||||||
|
FileCodeBox supports multiple flexible expiration methods:
|
||||||
|
|
||||||
|
| Expiration Method | Parameter Value | Description |
|
||||||
|
|-------------------|-----------------|-------------|
|
||||||
|
| By Days | `day` | File expires after specified days |
|
||||||
|
| By Hours | `hour` | File expires after specified hours |
|
||||||
|
| By Minutes | `minute` | File expires after specified minutes |
|
||||||
|
| Never Expire | `forever` | File is permanently valid |
|
||||||
|
| By Count | `count` | File expires after specified download count |
|
||||||
|
|
||||||
|
::: info Note
|
||||||
|
- Administrators can control available expiration methods via the `expireStyle` configuration
|
||||||
|
- Administrators can limit maximum file retention time via the `max_save_seconds` configuration
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Expiration Method Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# File expires after 3 days
|
||||||
|
expire_value=3, expire_style=day
|
||||||
|
|
||||||
|
# File expires after 12 hours
|
||||||
|
expire_value=12, expire_style=hour
|
||||||
|
|
||||||
|
# File expires after 30 minutes
|
||||||
|
expire_value=30, expire_style=minute
|
||||||
|
|
||||||
|
# File never expires
|
||||||
|
expire_value=1, expire_style=forever
|
||||||
|
|
||||||
|
# File expires after 5 downloads
|
||||||
|
expire_value=5, expire_style=count
|
||||||
|
```
|
||||||
|
|
||||||
|
## Retrieving Files
|
||||||
|
|
||||||
|
### How to Use
|
||||||
|
|
||||||
|
1. Enter the extraction code in the "Retrieve File" area on the homepage
|
||||||
|
2. Click the "Retrieve" button
|
||||||
|
3. System displays file information (filename, size, etc.)
|
||||||
|
4. Click the "Download" button to download the file, or view text content directly
|
||||||
|
|
||||||
|
### Extraction Code Notes
|
||||||
|
|
||||||
|
- Extraction codes are typically **6-digit numbers**
|
||||||
|
- Files that never expire use **alphanumeric** extraction codes
|
||||||
|
- Extraction codes are case-sensitive (for alphanumeric codes)
|
||||||
|
|
||||||
|
### API Endpoints
|
||||||
|
|
||||||
|
**Query File Information**
|
||||||
|
|
||||||
|
**POST** `/share/select/`
|
||||||
|
|
||||||
|
Request parameters:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response example (file):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456",
|
||||||
|
"name": "example.pdf",
|
||||||
|
"size": 1048576,
|
||||||
|
"text": "https://example.com/download/..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response example (text):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456",
|
||||||
|
"name": "Text",
|
||||||
|
"size": 1024,
|
||||||
|
"text": "This is the shared text content..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct File Download**
|
||||||
|
|
||||||
|
**GET** `/share/select/?code=123456`
|
||||||
|
|
||||||
|
This endpoint returns file content directly, suitable for direct browser access.
|
||||||
|
|
||||||
|
## Chunked Upload (Large Files)
|
||||||
|
|
||||||
|
For large file uploads, FileCodeBox supports chunked upload functionality. This feature requires administrator enablement (`enableChunk=1`).
|
||||||
|
|
||||||
|
### Chunked Upload Flow
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant C as Client
|
||||||
|
participant S as Server
|
||||||
|
|
||||||
|
C->>S: 1. Initialize upload (POST /chunk/upload/init/)
|
||||||
|
S-->>C: Return upload_id and chunk info
|
||||||
|
|
||||||
|
loop Each chunk
|
||||||
|
C->>S: 2. Upload chunk (POST /chunk/upload/chunk/{upload_id}/{chunk_index})
|
||||||
|
S-->>C: Return chunk hash
|
||||||
|
end
|
||||||
|
|
||||||
|
C->>S: 3. Complete upload (POST /chunk/upload/complete/{upload_id})
|
||||||
|
S-->>C: Return extraction code
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. Initialize Upload
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/init/`
|
||||||
|
|
||||||
|
Request parameters:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_name": "large_file.zip",
|
||||||
|
"file_size": 104857600,
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"file_hash": "sha256_hash_of_file"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `file_name` | string | Yes | Filename |
|
||||||
|
| `file_size` | int | Yes | Total file size (bytes) |
|
||||||
|
| `chunk_size` | int | No | Chunk size, default 5MB |
|
||||||
|
| `file_hash` | string | Yes | SHA256 hash of the file |
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"existed": false,
|
||||||
|
"upload_id": "abc123def456",
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"total_chunks": 20,
|
||||||
|
"uploaded_chunks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Upload Chunk
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/chunk/{upload_id}/{chunk_index}`
|
||||||
|
|
||||||
|
- `upload_id` - Upload session ID returned during initialization
|
||||||
|
- `chunk_index` - Chunk index, starting from 0
|
||||||
|
|
||||||
|
Request body: Chunk file data (multipart/form-data)
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"chunk_hash": "sha256_hash_of_chunk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Complete Upload
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/complete/{upload_id}`
|
||||||
|
|
||||||
|
Request parameters:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expire_value": 1,
|
||||||
|
"expire_style": "day"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Response example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "789012",
|
||||||
|
"name": "large_file.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resume Upload
|
||||||
|
|
||||||
|
Chunked upload supports resume functionality. If upload is interrupted:
|
||||||
|
|
||||||
|
1. Call the initialization endpoint again with the same `file_hash`
|
||||||
|
2. Server returns `uploaded_chunks` list containing already uploaded chunk indices
|
||||||
|
3. Client only needs to upload chunks not in the list
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Error Codes
|
||||||
|
|
||||||
|
| Error Code | Description | Solution |
|
||||||
|
|------------|-------------|----------|
|
||||||
|
| 403 | File size exceeds limit | Reduce file size or contact administrator to adjust limit |
|
||||||
|
| 403 | Content too large | Text exceeds 222KB, use file sharing instead |
|
||||||
|
| 403 | Upload rate limit | Wait a while before retrying |
|
||||||
|
| 404 | File not found | Check if extraction code is correct |
|
||||||
|
| 404 | File expired | File has expired or download count exhausted |
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
To prevent abuse, the system has rate limits on upload and retrieval operations:
|
||||||
|
|
||||||
|
- **Upload limit**: Default max 10 uploads per minute
|
||||||
|
- **Error limit**: Default max 1 error attempt per minute
|
||||||
|
|
||||||
|
::: tip Tip
|
||||||
|
If you encounter rate limiting, wait for the limit time window to pass before retrying.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Configuration Guide](/en/guide/configuration) - Learn how to configure sharing-related settings
|
||||||
|
- [Storage Configuration](/en/guide/storage) - Learn about file storage methods
|
||||||
|
- [Security Settings](/en/guide/security) - Learn about security-related configurations
|
||||||
|
- [Admin Panel](/en/guide/management) - Learn how to manage shared files
|
||||||
|
|||||||
@@ -0,0 +1,395 @@
|
|||||||
|
# Storage Configuration
|
||||||
|
|
||||||
|
FileCodeBox supports multiple storage backends. You can choose the appropriate storage method based on your needs. This document details the configuration methods for various storage backends.
|
||||||
|
|
||||||
|
## Storage Types Overview
|
||||||
|
|
||||||
|
| Storage Type | Config Value | Description |
|
||||||
|
|--------------|--------------|-------------|
|
||||||
|
| Local Storage | `local` | Default storage method, files saved on local server |
|
||||||
|
| S3-Compatible Storage | `s3` | Supports AWS S3, Aliyun OSS, MinIO, etc. |
|
||||||
|
| OneDrive | `onedrive` | Microsoft OneDrive cloud storage (work/school accounts only) |
|
||||||
|
| WebDAV | `webdav` | Storage services supporting WebDAV protocol |
|
||||||
|
| OpenDAL | `opendal` | Integrate more storage services via OpenDAL |
|
||||||
|
|
||||||
|
## Local Storage
|
||||||
|
|
||||||
|
Local storage is the default storage method. Files are saved in the server's `data/` directory.
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `file_storage` | string | `local` | Storage type |
|
||||||
|
| `storage_path` | string | `""` | Custom storage path (optional) |
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=local
|
||||||
|
storage_path=
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- Files are stored by default in `data/share/data/` directory
|
||||||
|
- Subdirectories are automatically created by date: `year/month/day/fileID/`
|
||||||
|
- In production, it's recommended to mount the `data/` directory to persistent storage
|
||||||
|
|
||||||
|
## S3-Compatible Storage
|
||||||
|
|
||||||
|
Supports all S3-compatible object storage services, including AWS S3, Aliyun OSS, MinIO, Tencent Cloud COS, etc.
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `file_storage` | string | - | Set to `s3` |
|
||||||
|
| `s3_access_key_id` | string | `""` | Access Key ID |
|
||||||
|
| `s3_secret_access_key` | string | `""` | Secret Access Key |
|
||||||
|
| `s3_bucket_name` | string | `""` | Bucket name |
|
||||||
|
| `s3_endpoint_url` | string | `""` | S3 endpoint URL |
|
||||||
|
| `s3_region_name` | string | `auto` | Region name |
|
||||||
|
| `s3_signature_version` | string | `s3v2` | Signature version (`s3v2` or `s3v4`) |
|
||||||
|
| `s3_hostname` | string | `""` | S3 hostname (alternative) |
|
||||||
|
| `s3_proxy` | int | `0` | Download through server proxy (1=yes, 0=no) |
|
||||||
|
| `aws_session_token` | string | `""` | AWS session token (optional) |
|
||||||
|
|
||||||
|
|
||||||
|
### AWS S3 Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=AKIAIOSFODNN7EXAMPLE
|
||||||
|
s3_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||||
|
s3_bucket_name=my-filecodebox-bucket
|
||||||
|
s3_endpoint_url=https://s3.amazonaws.com
|
||||||
|
s3_region_name=us-east-1
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Aliyun OSS Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=YourAccessKeyId
|
||||||
|
s3_secret_access_key=YourSecretAccessKey
|
||||||
|
s3_bucket_name=bucket-name
|
||||||
|
s3_endpoint_url=https://bucket-name.oss-cn-hangzhou.aliyuncs.com
|
||||||
|
s3_region_name=oss-cn-hangzhou
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip Aliyun OSS Endpoint Format
|
||||||
|
Endpoint URL format: `https://<bucket-name>.<region>.aliyuncs.com`
|
||||||
|
|
||||||
|
Common regions:
|
||||||
|
- Hangzhou: `oss-cn-hangzhou`
|
||||||
|
- Shanghai: `oss-cn-shanghai`
|
||||||
|
- Beijing: `oss-cn-beijing`
|
||||||
|
- Shenzhen: `oss-cn-shenzhen`
|
||||||
|
:::
|
||||||
|
|
||||||
|
### MinIO Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=minioadmin
|
||||||
|
s3_secret_access_key=minioadmin
|
||||||
|
s3_bucket_name=filecodebox
|
||||||
|
s3_endpoint_url=http://localhost:9000
|
||||||
|
s3_region_name=us-east-1
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning MinIO Notes
|
||||||
|
- `s3_endpoint_url` should be the MinIO API interface address
|
||||||
|
- `s3_region_name` should match the `Server Location` in MinIO configuration
|
||||||
|
- Ensure the bucket is created and has correct access permissions
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Tencent Cloud COS Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=YourSecretId
|
||||||
|
s3_secret_access_key=YourSecretKey
|
||||||
|
s3_bucket_name=bucket-name-1250000000
|
||||||
|
s3_endpoint_url=https://cos.ap-guangzhou.myqcloud.com
|
||||||
|
s3_region_name=ap-guangzhou
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Proxy Download
|
||||||
|
|
||||||
|
When `s3_proxy=1`, file downloads are proxied through the server instead of directly from S3. This is useful when:
|
||||||
|
|
||||||
|
- S3 bucket doesn't allow public access
|
||||||
|
- Need to hide the actual storage address
|
||||||
|
- Network environment restricts direct S3 access
|
||||||
|
|
||||||
|
## OneDrive Storage
|
||||||
|
|
||||||
|
OneDrive storage supports saving files to Microsoft OneDrive cloud storage.
|
||||||
|
|
||||||
|
::: warning Important Limitation
|
||||||
|
OneDrive storage **only supports work or school accounts** and requires admin permissions to authorize the API. Personal accounts cannot use this feature.
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `file_storage` | string | - | Set to `onedrive` |
|
||||||
|
| `onedrive_domain` | string | `""` | Azure AD domain |
|
||||||
|
| `onedrive_client_id` | string | `""` | Application (client) ID |
|
||||||
|
| `onedrive_username` | string | `""` | Account email |
|
||||||
|
| `onedrive_password` | string | `""` | Account password |
|
||||||
|
| `onedrive_root_path` | string | `filebox_storage` | Storage root directory in OneDrive |
|
||||||
|
| `onedrive_proxy` | int | `0` | Download through server proxy |
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=onedrive
|
||||||
|
onedrive_domain=contoso.onmicrosoft.com
|
||||||
|
onedrive_client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
onedrive_username=user@contoso.onmicrosoft.com
|
||||||
|
onedrive_password=your_password
|
||||||
|
onedrive_root_path=filebox_storage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure App Registration Steps
|
||||||
|
|
||||||
|
To use OneDrive storage, you need to register an application in the Azure portal:
|
||||||
|
|
||||||
|
#### 1. Get Domain
|
||||||
|
|
||||||
|
Log in to [Azure Portal](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade), hover over your account in the top right corner, and the **Domain** shown in the popup is the `onedrive_domain` value.
|
||||||
|
|
||||||
|
#### 2. Register Application
|
||||||
|
|
||||||
|
1. Click **+ New registration** in the top left
|
||||||
|
2. Enter application name (e.g., FileCodeBox)
|
||||||
|
3. **Supported account types**: Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant) and personal Microsoft accounts"
|
||||||
|
4. **Redirect URI**: Select `Web`, enter `http://localhost`
|
||||||
|
5. Click **Register**
|
||||||
|
|
||||||
|
#### 3. Get Client ID
|
||||||
|
|
||||||
|
After registration, find the **Application (client) ID** in the **Essentials** section on the app overview page. This is the `onedrive_client_id` value.
|
||||||
|
|
||||||
|
#### 4. Configure Authentication
|
||||||
|
|
||||||
|
1. Select **Authentication** in the left menu
|
||||||
|
2. Find **Allow public client flows**, select **Yes**
|
||||||
|
3. Click **Save**
|
||||||
|
|
||||||
|
#### 5. Configure API Permissions
|
||||||
|
|
||||||
|
1. Select **API permissions** in the left menu
|
||||||
|
2. Click **+ Add a permission**
|
||||||
|
3. Select **Microsoft Graph** → **Delegated permissions**
|
||||||
|
4. Check the following permissions:
|
||||||
|
- `openid`
|
||||||
|
- `Files.Read`
|
||||||
|
- `Files.Read.All`
|
||||||
|
- `Files.ReadWrite`
|
||||||
|
- `Files.ReadWrite.All`
|
||||||
|
- `User.Read`
|
||||||
|
5. Click **Add permissions**
|
||||||
|
6. Click **Grant admin consent for xxx**
|
||||||
|
7. After confirmation, permission status should show **Granted**
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
Using OneDrive storage requires additional Python dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install msal Office365-REST-Python-Client
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Configuration
|
||||||
|
|
||||||
|
You can use the following code to test if the configuration is correct:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import msal
|
||||||
|
from office365.graph_client import GraphClient
|
||||||
|
|
||||||
|
domain = 'your_domain'
|
||||||
|
client_id = 'your_client_id'
|
||||||
|
username = 'your_username'
|
||||||
|
password = 'your_password'
|
||||||
|
|
||||||
|
def acquire_token_pwd():
|
||||||
|
authority_url = f'https://login.microsoftonline.com/{domain}'
|
||||||
|
app = msal.PublicClientApplication(
|
||||||
|
authority=authority_url,
|
||||||
|
client_id=client_id
|
||||||
|
)
|
||||||
|
result = app.acquire_token_by_username_password(
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
scopes=['https://graph.microsoft.com/.default']
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Test connection
|
||||||
|
client = GraphClient(acquire_token_pwd)
|
||||||
|
me = client.me.get().execute_query()
|
||||||
|
print(f"Login successful: {me.user_principal_name}")
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## WebDAV Storage
|
||||||
|
|
||||||
|
WebDAV storage supports saving files to any service that supports the WebDAV protocol, such as Nextcloud, ownCloud, Nutstore, etc.
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `file_storage` | string | - | Set to `webdav` |
|
||||||
|
| `webdav_url` | string | `""` | WebDAV server URL |
|
||||||
|
| `webdav_username` | string | `""` | WebDAV username |
|
||||||
|
| `webdav_password` | string | `""` | WebDAV password |
|
||||||
|
| `webdav_root_path` | string | `filebox_storage` | Storage root directory in WebDAV |
|
||||||
|
| `webdav_proxy` | int | `0` | Download through server proxy |
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://dav.example.com/remote.php/dav/files/username/
|
||||||
|
webdav_username=your_username
|
||||||
|
webdav_password=your_password
|
||||||
|
webdav_root_path=filebox_storage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nextcloud Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://your-nextcloud.com/remote.php/dav/files/username/
|
||||||
|
webdav_username=your_username
|
||||||
|
webdav_password=your_app_password
|
||||||
|
webdav_root_path=FileCodeBox
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip Nextcloud App Password
|
||||||
|
It's recommended to create an app password in Nextcloud instead of using your main password:
|
||||||
|
1. Log in to Nextcloud
|
||||||
|
2. Go to **Settings** → **Security**
|
||||||
|
3. Create a new app password in **Devices & sessions**
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Nutstore Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://dav.jianguoyun.com/dav/
|
||||||
|
webdav_username=your_email@example.com
|
||||||
|
webdav_password=your_app_password
|
||||||
|
webdav_root_path=FileCodeBox
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip Nutstore App Password
|
||||||
|
Nutstore requires an app password:
|
||||||
|
1. Log in to Nutstore web version
|
||||||
|
2. Go to **Account Info** → **Security Options**
|
||||||
|
3. Add an app password
|
||||||
|
:::
|
||||||
|
|
||||||
|
## OpenDAL Storage
|
||||||
|
|
||||||
|
OpenDAL is a unified data access layer that supports multiple storage services. Through OpenDAL, you can use Google Cloud Storage, Azure Blob Storage, and more.
|
||||||
|
|
||||||
|
### Configuration Parameters
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `file_storage` | string | Set to `opendal` |
|
||||||
|
| `opendal_scheme` | string | Storage service type (e.g., `gcs`, `azblob`) |
|
||||||
|
| `opendal_<scheme>_<setting>` | string | Service-specific configuration parameters |
|
||||||
|
|
||||||
|
### Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install opendal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Google Cloud Storage Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=opendal
|
||||||
|
opendal_scheme=gcs
|
||||||
|
opendal_gcs_root=/filecodebox
|
||||||
|
opendal_gcs_bucket=your-bucket-name
|
||||||
|
opendal_gcs_credential=base64_encoded_credential
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure Blob Storage Configuration Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=opendal
|
||||||
|
opendal_scheme=azblob
|
||||||
|
opendal_azblob_root=/filecodebox
|
||||||
|
opendal_azblob_container=your-container
|
||||||
|
opendal_azblob_account_name=your_account
|
||||||
|
opendal_azblob_account_key=your_key
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supported Services
|
||||||
|
|
||||||
|
OpenDAL supports numerous storage services. For the complete list, see the [OpenDAL Official Documentation](https://opendal.apache.org/docs/rust/opendal/services/index.html).
|
||||||
|
|
||||||
|
Common services include:
|
||||||
|
- `gcs` - Google Cloud Storage
|
||||||
|
- `azblob` - Azure Blob Storage
|
||||||
|
- `obs` - Huawei Cloud OBS
|
||||||
|
- `oss` - Aliyun OSS (via OpenDAL)
|
||||||
|
- `cos` - Tencent Cloud COS (via OpenDAL)
|
||||||
|
- `hdfs` - Hadoop HDFS
|
||||||
|
- `ftp` - FTP server
|
||||||
|
- `sftp` - SFTP server
|
||||||
|
|
||||||
|
::: warning OpenDAL Notes
|
||||||
|
1. Services integrated via OpenDAL download through server proxy, consuming both storage service and server bandwidth
|
||||||
|
2. Compared to native S3/OneDrive support, OpenDAL may lack some debugging information
|
||||||
|
3. OpenDAL is written in Rust with good performance
|
||||||
|
:::
|
||||||
|
|
||||||
|
## Storage Selection Recommendations
|
||||||
|
|
||||||
|
| Scenario | Recommended Storage | Reason |
|
||||||
|
|----------|---------------------|--------|
|
||||||
|
| Personal/Small deployment | Local storage | Simple and easy, no extra configuration needed |
|
||||||
|
| Enterprise intranet | MinIO + S3 | Self-hosted object storage, data control |
|
||||||
|
| Public cloud deployment | Corresponding cloud provider S3 | Fast same-region access, low cost |
|
||||||
|
| Existing OneDrive | OneDrive | Utilize existing resources |
|
||||||
|
| Existing WebDAV | WebDAV | Good compatibility |
|
||||||
|
| Special storage needs | OpenDAL | Supports more storage services |
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### S3 Upload Failure
|
||||||
|
|
||||||
|
1. Check if Access Key and Secret Key are correct
|
||||||
|
2. Confirm bucket name and region configuration are correct
|
||||||
|
3. Check bucket access permission settings
|
||||||
|
4. Confirm signature version (`s3v2` or `s3v4`) matches service provider requirements
|
||||||
|
|
||||||
|
### OneDrive Authentication Failure
|
||||||
|
|
||||||
|
1. Confirm using a work/school account, not a personal account
|
||||||
|
2. Check if Azure app has been granted admin consent
|
||||||
|
3. Confirm API permissions are fully configured
|
||||||
|
4. Verify username and password are correct
|
||||||
|
|
||||||
|
### WebDAV Connection Failure
|
||||||
|
|
||||||
|
1. Check if WebDAV URL format is correct
|
||||||
|
2. Confirm username and password (or app password) are correct
|
||||||
|
3. Check if server supports WebDAV protocol
|
||||||
|
4. Confirm network connection is normal
|
||||||
|
|||||||
@@ -0,0 +1,380 @@
|
|||||||
|
# File Upload
|
||||||
|
|
||||||
|
FileCodeBox provides multiple flexible file upload methods, supporting both regular upload and chunked upload to meet different scenario requirements.
|
||||||
|
|
||||||
|
## Upload Methods
|
||||||
|
|
||||||
|
FileCodeBox supports the following upload methods:
|
||||||
|
|
||||||
|
### Drag and Drop Upload
|
||||||
|
|
||||||
|
Drag files directly to the upload area to start uploading. This is the most convenient upload method.
|
||||||
|
|
||||||
|
1. Open the FileCodeBox homepage
|
||||||
|
2. Drag files from your file manager to the upload area
|
||||||
|
3. Release the mouse, file upload begins
|
||||||
|
4. Get the extraction code after upload completes
|
||||||
|
|
||||||
|
::: tip Tip
|
||||||
|
Drag and drop upload supports dragging multiple files simultaneously (depending on theme support).
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Click Upload
|
||||||
|
|
||||||
|
Click the upload area to select files through the system file picker.
|
||||||
|
|
||||||
|
1. Click the "Select File" button in the upload area
|
||||||
|
2. Select the file to upload in the popup file picker
|
||||||
|
3. File upload begins after confirming selection
|
||||||
|
4. Get the extraction code after upload completes
|
||||||
|
|
||||||
|
### Paste Upload
|
||||||
|
|
||||||
|
Supports pasting images directly from clipboard for upload (supported by some themes).
|
||||||
|
|
||||||
|
1. Copy an image to clipboard (screenshot or copy image)
|
||||||
|
2. Use `Ctrl+V` (Windows/Linux) or `Cmd+V` (macOS) to paste in the upload area
|
||||||
|
3. Image upload starts automatically
|
||||||
|
4. Get the extraction code after upload completes
|
||||||
|
|
||||||
|
::: warning Note
|
||||||
|
Paste upload only supports image formats, not other file types. Specific support depends on the theme being used.
|
||||||
|
:::
|
||||||
|
|
||||||
|
## File Size Limits
|
||||||
|
|
||||||
|
### Default Limits
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `uploadSize` | 10MB | Maximum single file upload size |
|
||||||
|
|
||||||
|
### Modify Upload Limits
|
||||||
|
|
||||||
|
Administrators can modify upload size limits through the admin panel or configuration file:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Set maximum upload size to 100MB
|
||||||
|
uploadSize = 104857600 # 100 * 1024 * 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
::: info Note
|
||||||
|
`uploadSize` is in bytes. Common conversions:
|
||||||
|
- 10MB = 10485760
|
||||||
|
- 50MB = 52428800
|
||||||
|
- 100MB = 104857600
|
||||||
|
- 500MB = 524288000
|
||||||
|
- 1GB = 1073741824
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Exceeding Limit Handling
|
||||||
|
|
||||||
|
When an uploaded file exceeds the size limit, the system returns a 403 error:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "Size exceeds limit, maximum is 10.00 MB"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Regular Upload API
|
||||||
|
|
||||||
|
### File Upload Endpoint
|
||||||
|
|
||||||
|
**POST** `/share/file/`
|
||||||
|
|
||||||
|
Content-Type: `multipart/form-data`
|
||||||
|
|
||||||
|
**Request parameters:**
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `file` | file | Yes | File to upload |
|
||||||
|
| `expire_value` | int | No | Expiration value, default 1 |
|
||||||
|
| `expire_style` | string | No | Expiration method, default `day` |
|
||||||
|
|
||||||
|
**Expiration method options:**
|
||||||
|
|
||||||
|
| Value | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `day` | Expire by days |
|
||||||
|
| `hour` | Expire by hours |
|
||||||
|
| `minute` | Expire by minutes |
|
||||||
|
| `forever` | Never expire |
|
||||||
|
| `count` | Expire by download count |
|
||||||
|
|
||||||
|
**Response example:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "654321",
|
||||||
|
"name": "example.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**cURL example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:12345/share/file/" \
|
||||||
|
-F "file=@/path/to/file.pdf" \
|
||||||
|
-F "expire_value=7" \
|
||||||
|
-F "expire_style=day"
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Chunked Upload API
|
||||||
|
|
||||||
|
For large files, FileCodeBox supports chunked upload functionality. Chunked upload splits large files into multiple small chunks for separate uploading, supporting resume capability.
|
||||||
|
|
||||||
|
::: warning Prerequisite
|
||||||
|
Chunked upload functionality requires administrator enablement: `enableChunk=1`
|
||||||
|
:::
|
||||||
|
|
||||||
|
### Chunked Upload Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ Initialize │ ──▶ │Upload Chunks│ ──▶ │ Complete │
|
||||||
|
│ /init/ │ │ /chunk/ │ │ /complete/ │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ Loop │
|
||||||
|
│each chunk │
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. Initialize Upload
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/init/`
|
||||||
|
|
||||||
|
**Request parameters:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_name": "large_file.zip",
|
||||||
|
"file_size": 104857600,
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"file_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `file_name` | string | Yes | - | Filename |
|
||||||
|
| `file_size` | int | Yes | - | Total file size (bytes) |
|
||||||
|
| `chunk_size` | int | No | 5MB | Chunk size (bytes) |
|
||||||
|
| `file_hash` | string | Yes | - | SHA256 hash of the file |
|
||||||
|
|
||||||
|
**Response example:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"existed": false,
|
||||||
|
"upload_id": "abc123def456789",
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"total_chunks": 20,
|
||||||
|
"uploaded_chunks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Description |
|
||||||
|
|-------|-------------|
|
||||||
|
| `existed` | Whether file already exists (instant upload) |
|
||||||
|
| `upload_id` | Upload session ID |
|
||||||
|
| `chunk_size` | Chunk size |
|
||||||
|
| `total_chunks` | Total number of chunks |
|
||||||
|
| `uploaded_chunks` | List of already uploaded chunk indices |
|
||||||
|
|
||||||
|
### 2. Upload Chunk
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/chunk/{upload_id}/{chunk_index}`
|
||||||
|
|
||||||
|
**Path parameters:**
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `upload_id` | Upload session ID returned during initialization |
|
||||||
|
| `chunk_index` | Chunk index, starting from 0 |
|
||||||
|
|
||||||
|
**Request body:**
|
||||||
|
|
||||||
|
Content-Type: `multipart/form-data`
|
||||||
|
|
||||||
|
| Parameter | Type | Description |
|
||||||
|
|-----------|------|-------------|
|
||||||
|
| `chunk` | file | Chunk data |
|
||||||
|
|
||||||
|
**Response example:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"chunk_hash": "a1b2c3d4e5f6..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**cURL example:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Upload first chunk (index 0)
|
||||||
|
curl -X POST "http://localhost:12345/chunk/upload/chunk/abc123def456789/0" \
|
||||||
|
-F "chunk=@/path/to/chunk_0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Complete Upload
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/complete/{upload_id}`
|
||||||
|
|
||||||
|
**Path parameters:**
|
||||||
|
|
||||||
|
| Parameter | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `upload_id` | Upload session ID |
|
||||||
|
|
||||||
|
**Request parameters:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expire_value": 7,
|
||||||
|
"expire_style": "day"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Description |
|
||||||
|
|-----------|------|----------|-------------|
|
||||||
|
| `expire_value` | int | Yes | Expiration value |
|
||||||
|
| `expire_style` | string | Yes | Expiration method |
|
||||||
|
|
||||||
|
**Response example:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "789012",
|
||||||
|
"name": "large_file.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Resume Upload
|
||||||
|
|
||||||
|
Chunked upload supports resume functionality. When upload is interrupted:
|
||||||
|
|
||||||
|
1. Call the initialization endpoint again with the same `file_hash`
|
||||||
|
2. Server returns `uploaded_chunks` list containing already uploaded chunk indices
|
||||||
|
3. Client only needs to upload chunks not in the list
|
||||||
|
4. Call the complete endpoint after all chunks are uploaded
|
||||||
|
|
||||||
|
**Example flow:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 1. Initialize upload
|
||||||
|
const initResponse = await fetch('/chunk/upload/init/', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_name: 'large_file.zip',
|
||||||
|
file_size: fileSize,
|
||||||
|
chunk_size: 5 * 1024 * 1024,
|
||||||
|
file_hash: fileHash
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const { upload_id, uploaded_chunks, total_chunks } = await initResponse.json();
|
||||||
|
|
||||||
|
// 2. Upload incomplete chunks
|
||||||
|
for (let i = 0; i < total_chunks; i++) {
|
||||||
|
if (!uploaded_chunks.includes(i)) {
|
||||||
|
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
||||||
|
await fetch(`/chunk/upload/chunk/${upload_id}/${i}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: chunk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Complete upload
|
||||||
|
await fetch(`/chunk/upload/complete/${upload_id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
expire_value: 7,
|
||||||
|
expire_style: 'day'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
| HTTP Status | Error Message | Cause | Solution |
|
||||||
|
|-------------|---------------|-------|----------|
|
||||||
|
| 403 | Size exceeds limit | File exceeds `uploadSize` limit | Reduce file size or contact administrator to adjust limit |
|
||||||
|
| 403 | Upload rate limit | Exceeded IP upload rate limit | Wait for limit time window before retrying |
|
||||||
|
| 400 | Invalid expiration type | `expire_style` value not in allowed list | Use a valid expiration method |
|
||||||
|
| 404 | Upload session not found | `upload_id` invalid or expired | Re-initialize upload |
|
||||||
|
| 400 | Invalid chunk index | `chunk_index` out of range | Check if chunk index is correct |
|
||||||
|
| 400 | Incomplete chunks | Chunk count insufficient when completing upload | Ensure all chunks are uploaded |
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
The system has rate limits on upload operations to prevent abuse:
|
||||||
|
|
||||||
|
| Setting | Default | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| `uploadMinute` | 1 | Limit time window (minutes) |
|
||||||
|
| `uploadCount` | 10 | Maximum uploads within time window |
|
||||||
|
|
||||||
|
When rate limit is exceeded, you need to wait for the time window to pass before continuing uploads.
|
||||||
|
|
||||||
|
### Error Response Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "Error message description"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload Configuration
|
||||||
|
|
||||||
|
### Related Settings
|
||||||
|
|
||||||
|
| Setting | Type | Default | Description |
|
||||||
|
|---------|------|---------|-------------|
|
||||||
|
| `openUpload` | int | 1 | Enable upload (1=enabled, 0=disabled) |
|
||||||
|
| `uploadSize` | int | 10485760 | Maximum upload size (bytes) |
|
||||||
|
| `enableChunk` | int | 0 | Enable chunked upload (1=enabled, 0=disabled) |
|
||||||
|
| `uploadMinute` | int | 1 | Upload rate limit time window (minutes) |
|
||||||
|
| `uploadCount` | int | 10 | Maximum uploads within time window |
|
||||||
|
| `expireStyle` | list | ["day","hour","minute","forever","count"] | Allowed expiration methods |
|
||||||
|
|
||||||
|
### Configuration Example
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Allow 100MB file uploads, enable chunked upload
|
||||||
|
uploadSize = 104857600
|
||||||
|
enableChunk = 1
|
||||||
|
|
||||||
|
# Relax upload rate limit: max 50 uploads per 5 minutes
|
||||||
|
uploadMinute = 5
|
||||||
|
uploadCount = 50
|
||||||
|
|
||||||
|
# Only allow expiration by days and count
|
||||||
|
expireStyle = ["day", "count"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [File Sharing](/en/guide/share) - Learn the complete sharing process
|
||||||
|
- [Configuration Guide](/en/guide/configuration) - Learn about all configuration options
|
||||||
|
- [Storage Configuration](/en/guide/storage) - Learn about file storage methods
|
||||||
|
- [Security Settings](/en/guide/security) - Learn about security-related configurations
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
# 配置说明
|
||||||
|
|
||||||
|
FileCodeBox 提供了丰富的配置选项,可以通过管理面板或直接修改配置来自定义系统行为。本文档详细介绍所有可用的配置项。
|
||||||
|
|
||||||
|
## 配置方式
|
||||||
|
|
||||||
|
FileCodeBox 支持两种配置方式:
|
||||||
|
|
||||||
|
1. **管理面板配置**(推荐):访问 `/admin` 进入管理面板,在设置页面修改配置
|
||||||
|
2. **数据库配置**:配置存储在 `data/filecodebox.db` 数据库中
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
首次启动时,系统会使用 `core/settings.py` 中的默认配置。修改后的配置会保存到数据库中。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 基础设置
|
||||||
|
|
||||||
|
### 站点信息
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `name` | string | `文件快递柜 - FileCodeBox` | 站点名称,显示在页面标题和导航栏 |
|
||||||
|
| `description` | string | `开箱即用的文件快传系统` | 站点描述,用于 SEO |
|
||||||
|
| `keywords` | string | `FileCodeBox, 文件快递柜...` | 站点关键词,用于 SEO |
|
||||||
|
| `port` | int | `12345` | 服务监听端口 |
|
||||||
|
|
||||||
|
### 通知设置
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `notify_title` | string | `系统通知` | 通知标题 |
|
||||||
|
| `notify_content` | string | 欢迎信息 | 通知内容,支持 HTML |
|
||||||
|
| `page_explain` | string | 法律声明 | 页面底部说明文字 |
|
||||||
|
| `robotsText` | string | `User-agent: *\nDisallow: /` | robots.txt 内容 |
|
||||||
|
|
||||||
|
## 上传设置
|
||||||
|
|
||||||
|
### 文件上传限制
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `openUpload` | int | `1` | 是否开启上传功能(1=开启,0=关闭) |
|
||||||
|
| `uploadSize` | int | `10485760` | 单文件最大上传大小(字节),默认 10MB |
|
||||||
|
| `enableChunk` | int | `0` | 是否启用分片上传(1=启用,0=禁用) |
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
`uploadSize` 的单位是字节。10MB = 10 * 1024 * 1024 = 10485760 字节
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 上传频率限制
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `uploadMinute` | int | `1` | 上传限制的时间窗口(分钟) |
|
||||||
|
| `uploadCount` | int | `10` | 在时间窗口内允许的最大上传次数 |
|
||||||
|
|
||||||
|
例如:默认配置表示每 1 分钟内最多允许上传 10 次。
|
||||||
|
|
||||||
|
### 文件过期设置
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `expireStyle` | list | `["day","hour","minute","forever","count"]` | 可选的过期方式 |
|
||||||
|
| `max_save_seconds` | int | `0` | 文件最大保存时间(秒),0 表示不限制 |
|
||||||
|
|
||||||
|
过期方式说明:
|
||||||
|
- `day` - 按天过期
|
||||||
|
- `hour` - 按小时过期
|
||||||
|
- `minute` - 按分钟过期
|
||||||
|
- `forever` - 永不过期
|
||||||
|
- `count` - 按下载次数过期
|
||||||
|
|
||||||
|
## 主题设置
|
||||||
|
|
||||||
|
### 主题选择
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `themesSelect` | string | `themes/2024` | 当前使用的主题 |
|
||||||
|
| `themesChoices` | list | 见下方 | 可用主题列表 |
|
||||||
|
|
||||||
|
默认可用主题:
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "2023",
|
||||||
|
"key": "themes/2023",
|
||||||
|
"author": "Lan",
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2024",
|
||||||
|
"key": "themes/2024",
|
||||||
|
"author": "Lan",
|
||||||
|
"version": "1.0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 界面样式
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `opacity` | float | `0.9` | 界面透明度(0-1) |
|
||||||
|
| `background` | string | `""` | 自定义背景图片 URL,为空则使用默认背景 |
|
||||||
|
|
||||||
|
## 管理员设置
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `admin_token` | string | `FileCodeBox2023` | 管理员登录密码 |
|
||||||
|
| `showAdminAddr` | int | `0` | 是否在首页显示管理入口(1=显示,0=隐藏) |
|
||||||
|
|
||||||
|
::: danger 安全警告
|
||||||
|
请务必在生产环境中修改默认的 `admin_token`!使用默认密码会导致严重的安全风险。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 安全设置
|
||||||
|
|
||||||
|
### 错误次数限制
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `errorMinute` | int | `1` | 错误限制的时间窗口(分钟) |
|
||||||
|
| `errorCount` | int | `1` | 在时间窗口内允许的最大错误次数 |
|
||||||
|
|
||||||
|
此设置用于防止暴力破解提取码。
|
||||||
|
|
||||||
|
## 存储设置
|
||||||
|
|
||||||
|
### 存储类型
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `file_storage` | string | `local` | 存储后端类型 |
|
||||||
|
| `storage_path` | string | `""` | 自定义存储路径 |
|
||||||
|
|
||||||
|
支持的存储类型:
|
||||||
|
- `local` - 本地存储
|
||||||
|
- `s3` - S3 兼容存储(AWS S3、阿里云 OSS、MinIO 等)
|
||||||
|
- `onedrive` - OneDrive 存储
|
||||||
|
- `webdav` - WebDAV 存储
|
||||||
|
- `opendal` - OpenDAL 存储
|
||||||
|
|
||||||
|
详细的存储配置请参考 [存储配置](/guide/storage)。
|
||||||
|
|
||||||
|
## 配置示例
|
||||||
|
|
||||||
|
### 示例 1:小型个人使用
|
||||||
|
|
||||||
|
适合个人或小团队使用,限制较宽松:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "我的文件分享",
|
||||||
|
"uploadSize": 52428800, # 50MB
|
||||||
|
"uploadMinute": 5, # 5分钟
|
||||||
|
"uploadCount": 20, # 最多20次
|
||||||
|
"expireStyle": ["day", "hour", "forever"],
|
||||||
|
"admin_token": "your-secure-password",
|
||||||
|
"showAdminAddr": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 2:公开服务
|
||||||
|
|
||||||
|
适合公开服务,需要更严格的限制:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "公共文件快递柜",
|
||||||
|
"uploadSize": 10485760, # 10MB
|
||||||
|
"uploadMinute": 1, # 1分钟
|
||||||
|
"uploadCount": 5, # 最多5次
|
||||||
|
"errorMinute": 5, # 5分钟
|
||||||
|
"errorCount": 3, # 最多3次错误
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400, # 最长保存1天
|
||||||
|
"admin_token": "very-secure-password-123",
|
||||||
|
"showAdminAddr": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 示例 3:企业内部使用
|
||||||
|
|
||||||
|
适合企业内部使用,支持大文件和分片上传:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"name": "企业文件中转站",
|
||||||
|
"uploadSize": 1073741824, # 1GB
|
||||||
|
"enableChunk": 1, # 启用分片上传
|
||||||
|
"uploadMinute": 10, # 10分钟
|
||||||
|
"uploadCount": 100, # 最多100次
|
||||||
|
"expireStyle": ["day", "forever"],
|
||||||
|
"file_storage": "s3", # 使用S3存储
|
||||||
|
"admin_token": "enterprise-secure-token",
|
||||||
|
"showAdminAddr": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [存储配置](/guide/storage) - 了解如何配置不同的存储后端
|
||||||
|
- [安全设置](/guide/security) - 了解如何增强系统安全性
|
||||||
|
- [文件分享](/guide/share) - 了解文件分享功能
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
# 管理面板
|
||||||
|
|
||||||
|
FileCodeBox 提供了功能完善的管理面板,让管理员可以方便地管理文件、查看系统状态和修改配置。本文档介绍管理面板的各项功能和使用方法。
|
||||||
|
|
||||||
|
## 访问管理面板
|
||||||
|
|
||||||
|
### 登录方式
|
||||||
|
|
||||||
|
管理面板位于 `/admin` 路径。访问方式:
|
||||||
|
|
||||||
|
1. 在浏览器中访问 `http://your-domain.com/admin`
|
||||||
|
2. 输入管理员密码(`admin_token` 配置项的值)
|
||||||
|
3. 点击登录按钮
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
默认管理员密码是 `FileCodeBox2023`。请务必在生产环境中修改此密码,详见 [安全设置](/guide/security)。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 显示管理入口
|
||||||
|
|
||||||
|
默认情况下,首页不显示管理面板入口。您可以通过配置控制是否显示:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `showAdminAddr` | int | `0` | 是否在首页显示管理入口(1=显示,0=隐藏) |
|
||||||
|
|
||||||
|
::: warning 安全建议
|
||||||
|
在公开服务中,建议保持 `showAdminAddr` 为 `0`,通过直接访问 `/admin` 路径进入管理面板,减少被恶意扫描的风险。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 认证机制
|
||||||
|
|
||||||
|
管理面板使用 JWT(JSON Web Token)进行身份认证:
|
||||||
|
|
||||||
|
1. 登录成功后,服务器返回一个包含管理员身份的 Token
|
||||||
|
2. 后续请求通过 `Authorization: Bearer <token>` 头部携带 Token
|
||||||
|
3. Token 用于验证管理员身份,确保只有授权用户可以访问管理功能
|
||||||
|
|
||||||
|
## 仪表盘
|
||||||
|
|
||||||
|
登录后首先看到的是仪表盘页面,展示系统的整体运行状态。
|
||||||
|
|
||||||
|
### 统计指标
|
||||||
|
|
||||||
|
仪表盘显示以下关键指标:
|
||||||
|
|
||||||
|
| 指标 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **文件总数** (`totalFiles`) | 系统中存储的文件总数量 |
|
||||||
|
| **存储使用量** (`storageUsed`) | 所有文件占用的总存储空间(字节) |
|
||||||
|
| **系统运行时间** (`sysUptime`) | 系统首次启动的时间 |
|
||||||
|
| **昨日上传数** (`yesterdayCount`) | 昨天一整天上传的文件数量 |
|
||||||
|
| **昨日上传量** (`yesterdaySize`) | 昨天上传文件的总大小(字节) |
|
||||||
|
| **今日上传数** (`todayCount`) | 今天到目前为止上传的文件数量 |
|
||||||
|
| **今日上传量** (`todaySize`) | 今天上传文件的总大小(字节) |
|
||||||
|
|
||||||
|
### 指标说明
|
||||||
|
|
||||||
|
- **文件总数**:包括所有未过期的文件和文本分享
|
||||||
|
- **存储使用量**:显示实际文件占用的存储空间,不包括数据库等系统文件
|
||||||
|
- **昨日/今日统计**:基于文件创建时间计算,用于了解系统使用趋势
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
存储使用量显示的是字节数。例如 `10485760` 表示约 10MB。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 文件管理
|
||||||
|
|
||||||
|
### 文件列表
|
||||||
|
|
||||||
|
文件管理页面展示系统中所有已分享的文件,支持分页浏览和搜索。
|
||||||
|
|
||||||
|
**列表信息包括:**
|
||||||
|
- 文件 ID
|
||||||
|
- 提取码(code)
|
||||||
|
- 文件名前缀(prefix)
|
||||||
|
- 文件后缀(suffix)
|
||||||
|
- 文件大小
|
||||||
|
- 创建时间
|
||||||
|
- 过期时间
|
||||||
|
- 剩余下载次数
|
||||||
|
|
||||||
|
### 搜索文件
|
||||||
|
|
||||||
|
使用搜索功能可以快速找到特定文件:
|
||||||
|
|
||||||
|
1. 在搜索框中输入关键词
|
||||||
|
2. 系统会根据文件名前缀(prefix)进行模糊匹配
|
||||||
|
3. 搜索结果实时更新
|
||||||
|
|
||||||
|
**搜索示例:**
|
||||||
|
- 输入 `report` 可以找到所有文件名包含 "report" 的文件
|
||||||
|
- 输入 `.pdf` 可以找到所有 PDF 文件(如果文件名包含此字符串)
|
||||||
|
|
||||||
|
### 分页浏览
|
||||||
|
|
||||||
|
文件列表支持分页显示:
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `page` | `1` | 当前页码 |
|
||||||
|
| `size` | `10` | 每页显示数量 |
|
||||||
|
|
||||||
|
### 删除文件
|
||||||
|
|
||||||
|
管理员可以删除任意文件:
|
||||||
|
|
||||||
|
1. 在文件列表中找到要删除的文件
|
||||||
|
2. 点击删除按钮
|
||||||
|
3. 确认删除操作
|
||||||
|
|
||||||
|
::: danger 警告
|
||||||
|
删除操作不可恢复!文件将从存储后端永久删除,同时删除数据库中的记录。
|
||||||
|
:::
|
||||||
|
|
||||||
|
**删除流程:**
|
||||||
|
1. 系统首先从存储后端(本地/S3/OneDrive 等)删除实际文件
|
||||||
|
2. 然后从数据库中删除文件记录
|
||||||
|
3. 删除后,对应的提取码将失效
|
||||||
|
|
||||||
|
### 下载文件
|
||||||
|
|
||||||
|
管理员可以直接下载任意文件:
|
||||||
|
|
||||||
|
1. 在文件列表中找到目标文件
|
||||||
|
2. 点击下载按钮
|
||||||
|
3. 文件将通过浏览器下载
|
||||||
|
|
||||||
|
对于文本分享,系统会直接返回文本内容而不是下载文件。
|
||||||
|
|
||||||
|
### 修改文件信息
|
||||||
|
|
||||||
|
管理员可以修改已分享文件的部分信息:
|
||||||
|
|
||||||
|
| 可修改字段 | 说明 |
|
||||||
|
|------------|------|
|
||||||
|
| `code` | 提取码(必须唯一,不能与其他文件重复) |
|
||||||
|
| `prefix` | 文件名前缀 |
|
||||||
|
| `suffix` | 文件后缀名 |
|
||||||
|
| `expired_at` | 过期时间 |
|
||||||
|
| `expired_count` | 剩余下载次数 |
|
||||||
|
|
||||||
|
**修改提取码:**
|
||||||
|
```
|
||||||
|
原提取码:abc123
|
||||||
|
新提取码:myfile2024
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
修改提取码时,系统会检查新提取码是否已被使用。如果已存在相同的提取码,修改将失败。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 本地文件管理
|
||||||
|
|
||||||
|
除了管理已分享的文件,管理面板还提供了本地文件管理功能,用于管理 `data/local` 目录中的文件。
|
||||||
|
|
||||||
|
### 查看本地文件
|
||||||
|
|
||||||
|
本地文件列表显示 `data/local` 目录中的所有文件:
|
||||||
|
|
||||||
|
| 信息 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 文件名 | 文件的完整名称 |
|
||||||
|
| 创建时间 | 文件的创建时间 |
|
||||||
|
| 文件大小 | 文件大小(字节) |
|
||||||
|
|
||||||
|
### 分享本地文件
|
||||||
|
|
||||||
|
可以将本地文件快速分享:
|
||||||
|
|
||||||
|
1. 在本地文件列表中选择要分享的文件
|
||||||
|
2. 设置过期方式和过期值
|
||||||
|
3. 点击分享按钮
|
||||||
|
4. 系统生成提取码
|
||||||
|
|
||||||
|
**分享参数:**
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `filename` | 要分享的文件名 |
|
||||||
|
| `expire_style` | 过期方式(day/hour/minute/forever/count) |
|
||||||
|
| `expire_value` | 过期值(天数/小时数/分钟数/下载次数) |
|
||||||
|
|
||||||
|
### 删除本地文件
|
||||||
|
|
||||||
|
可以删除 `data/local` 目录中的文件:
|
||||||
|
|
||||||
|
1. 在本地文件列表中找到要删除的文件
|
||||||
|
2. 点击删除按钮
|
||||||
|
3. 确认删除
|
||||||
|
|
||||||
|
::: tip 使用场景
|
||||||
|
本地文件管理功能适用于:
|
||||||
|
- 批量上传文件到服务器后进行分享
|
||||||
|
- 管理通过其他方式上传到服务器的文件
|
||||||
|
- 清理不需要的本地文件
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 系统设置
|
||||||
|
|
||||||
|
### 查看配置
|
||||||
|
|
||||||
|
在系统设置页面可以查看当前所有配置项的值。配置项按类别分组显示:
|
||||||
|
|
||||||
|
- 基础设置(站点名称、描述等)
|
||||||
|
- 上传设置(文件大小限制、频率限制等)
|
||||||
|
- 存储设置(存储类型、路径等)
|
||||||
|
- 主题设置(主题选择、透明度等)
|
||||||
|
- 安全设置(管理员密码、错误限制等)
|
||||||
|
|
||||||
|
### 修改配置
|
||||||
|
|
||||||
|
管理员可以通过管理面板修改大部分配置:
|
||||||
|
|
||||||
|
1. 进入系统设置页面
|
||||||
|
2. 找到要修改的配置项
|
||||||
|
3. 输入新的值
|
||||||
|
4. 点击保存按钮
|
||||||
|
|
||||||
|
**可修改的配置项:**
|
||||||
|
|
||||||
|
| 类别 | 配置项示例 |
|
||||||
|
|------|------------|
|
||||||
|
| 基础设置 | `name`, `description`, `keywords`, `notify_title`, `notify_content` |
|
||||||
|
| 上传设置 | `uploadSize`, `uploadMinute`, `uploadCount`, `openUpload`, `enableChunk` |
|
||||||
|
| 过期设置 | `expireStyle`, `max_save_seconds` |
|
||||||
|
| 主题设置 | `themesSelect`, `opacity`, `background` |
|
||||||
|
| 安全设置 | `admin_token`, `showAdminAddr`, `errorMinute`, `errorCount` |
|
||||||
|
| 存储设置 | `file_storage`, `storage_path` 及各存储后端的配置 |
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
- `admin_token`(管理员密码)不能设置为空
|
||||||
|
- `themesChoices`(主题列表)不可通过管理面板修改
|
||||||
|
- 修改存储设置后,已有文件不会自动迁移
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 配置生效
|
||||||
|
|
||||||
|
配置修改后立即生效,无需重启服务。配置保存在数据库中,重启后仍然有效。
|
||||||
|
|
||||||
|
**配置存储位置:**
|
||||||
|
- 数据库:`data/filecodebox.db`
|
||||||
|
- 表名:`keyvalue`
|
||||||
|
- 键名:`settings`
|
||||||
|
|
||||||
|
## API 接口
|
||||||
|
|
||||||
|
管理面板的所有功能都通过 REST API 实现,以下是主要接口:
|
||||||
|
|
||||||
|
### 认证接口
|
||||||
|
|
||||||
|
**登录**
|
||||||
|
```
|
||||||
|
POST /admin/login
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"password": "your-admin-password"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"token": "eyJhbGciOiJIUzI1NiIs...",
|
||||||
|
"token_type": "Bearer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 仪表盘接口
|
||||||
|
|
||||||
|
**获取统计数据**
|
||||||
|
```
|
||||||
|
GET /admin/dashboard
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件管理接口
|
||||||
|
|
||||||
|
**获取文件列表**
|
||||||
|
```
|
||||||
|
GET /admin/file/list?page=1&size=10&keyword=
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**删除文件**
|
||||||
|
```
|
||||||
|
DELETE /admin/file/delete
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 123
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**下载文件**
|
||||||
|
```
|
||||||
|
GET /admin/file/download?id=123
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改文件信息**
|
||||||
|
```
|
||||||
|
PATCH /admin/file/update
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"code": "newcode",
|
||||||
|
"expired_at": "2024-12-31T23:59:59"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 本地文件接口
|
||||||
|
|
||||||
|
**获取本地文件列表**
|
||||||
|
```
|
||||||
|
GET /admin/local/lists
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**删除本地文件**
|
||||||
|
```
|
||||||
|
DELETE /admin/local/delete
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"filename": "example.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**分享本地文件**
|
||||||
|
```
|
||||||
|
POST /admin/local/share
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"filename": "example.txt",
|
||||||
|
"expire_style": "day",
|
||||||
|
"expire_value": 7
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置接口
|
||||||
|
|
||||||
|
**获取配置**
|
||||||
|
```
|
||||||
|
GET /admin/config/get
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
```
|
||||||
|
|
||||||
|
**更新配置**
|
||||||
|
```
|
||||||
|
PATCH /admin/config/update
|
||||||
|
Authorization: Bearer <token>
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"admin_token": "new-password",
|
||||||
|
"uploadSize": 52428800
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### 忘记管理员密码
|
||||||
|
|
||||||
|
如果忘记了管理员密码,可以通过以下方式重置:
|
||||||
|
|
||||||
|
1. 停止 FileCodeBox 服务
|
||||||
|
2. 使用 SQLite 工具打开 `data/filecodebox.db`
|
||||||
|
3. 查询 `keyvalue` 表中 `key='settings'` 的记录
|
||||||
|
4. 修改 JSON 中的 `admin_token` 值
|
||||||
|
5. 重启服务
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 查看当前配置
|
||||||
|
SELECT * FROM keyvalue WHERE key = 'settings';
|
||||||
|
|
||||||
|
-- 或者删除配置,恢复默认密码
|
||||||
|
DELETE FROM keyvalue WHERE key = 'settings';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 文件删除失败
|
||||||
|
|
||||||
|
如果删除文件时出现错误,可能的原因:
|
||||||
|
|
||||||
|
1. **存储后端连接失败**:检查存储配置是否正确
|
||||||
|
2. **文件已不存在**:文件可能已被手动删除
|
||||||
|
3. **权限不足**:检查存储目录的写入权限
|
||||||
|
|
||||||
|
### 配置修改不生效
|
||||||
|
|
||||||
|
如果修改配置后没有生效:
|
||||||
|
|
||||||
|
1. 检查是否点击了保存按钮
|
||||||
|
2. 刷新页面查看配置是否已保存
|
||||||
|
3. 检查浏览器控制台是否有错误信息
|
||||||
|
4. 确认配置值的格式是否正确(如数字类型不要输入字符串)
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [配置说明](/guide/configuration) - 了解所有配置选项的详细说明
|
||||||
|
- [安全设置](/guide/security) - 了解如何增强系统安全性
|
||||||
|
- [存储配置](/guide/storage) - 配置不同的存储后端
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
# 安全设置
|
||||||
|
|
||||||
|
FileCodeBox 提供了多层安全机制来保护您的文件分享服务。本文档介绍如何正确配置安全选项,确保系统安全运行。
|
||||||
|
|
||||||
|
## 管理员密码
|
||||||
|
|
||||||
|
### 修改默认密码
|
||||||
|
|
||||||
|
::: danger 重要安全警告
|
||||||
|
FileCodeBox 的默认管理员密码是 `FileCodeBox2023`。**在生产环境中必须立即修改此密码!**使用默认密码会导致任何人都可以访问您的管理面板。
|
||||||
|
:::
|
||||||
|
|
||||||
|
修改管理员密码有两种方式:
|
||||||
|
|
||||||
|
**方式一:通过管理面板修改(推荐)**
|
||||||
|
|
||||||
|
1. 访问 `/admin` 进入管理面板
|
||||||
|
2. 使用当前密码登录
|
||||||
|
3. 进入「系统设置」页面
|
||||||
|
4. 找到 `admin_token` 配置项
|
||||||
|
5. 输入新的安全密码并保存
|
||||||
|
|
||||||
|
**方式二:通过数据库修改**
|
||||||
|
|
||||||
|
配置存储在 `data/filecodebox.db` 数据库的 `keyvalue` 表中,可以直接修改 `admin_token` 的值。
|
||||||
|
|
||||||
|
### 密码安全建议
|
||||||
|
|
||||||
|
- 使用至少 16 个字符的强密码
|
||||||
|
- 包含大小写字母、数字和特殊字符
|
||||||
|
- 避免使用常见词汇或个人信息
|
||||||
|
- 定期更换密码
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 推荐的密码格式示例
|
||||||
|
"admin_token": "Xk9#mP2$vL5@nQ8&wR3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 隐藏管理入口
|
||||||
|
|
||||||
|
默认情况下,管理面板入口是隐藏的。您可以通过 `showAdminAddr` 配置控制是否在首页显示管理入口:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `showAdminAddr` | int | `0` | 是否显示管理入口(1=显示,0=隐藏) |
|
||||||
|
|
||||||
|
::: tip 建议
|
||||||
|
在公开服务中,建议保持 `showAdminAddr` 为 `0`,通过直接访问 `/admin` 路径进入管理面板。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## IP 速率限制
|
||||||
|
|
||||||
|
FileCodeBox 内置了基于 IP 的速率限制机制,可以有效防止滥用和攻击。
|
||||||
|
|
||||||
|
### 上传频率限制
|
||||||
|
|
||||||
|
限制单个 IP 在指定时间内的上传次数:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `uploadMinute` | int | `1` | 上传限制的时间窗口(分钟) |
|
||||||
|
| `uploadCount` | int | `10` | 在时间窗口内允许的最大上传次数 |
|
||||||
|
|
||||||
|
**工作原理:**
|
||||||
|
- 系统记录每个 IP 的上传请求
|
||||||
|
- 当某 IP 在 `uploadMinute` 分钟内的上传次数达到 `uploadCount` 时
|
||||||
|
- 该 IP 的后续上传请求将被拒绝,返回 HTTP 423 错误
|
||||||
|
- 等待时间窗口过期后,计数器重置
|
||||||
|
|
||||||
|
**配置示例:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 宽松配置:5分钟内最多上传20次
|
||||||
|
{
|
||||||
|
"uploadMinute": 5,
|
||||||
|
"uploadCount": 20
|
||||||
|
}
|
||||||
|
|
||||||
|
# 严格配置:1分钟内最多上传3次
|
||||||
|
{
|
||||||
|
"uploadMinute": 1,
|
||||||
|
"uploadCount": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误次数限制
|
||||||
|
|
||||||
|
限制单个 IP 的错误尝试次数,防止暴力破解提取码:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `errorMinute` | int | `1` | 错误限制的时间窗口(分钟) |
|
||||||
|
| `errorCount` | int | `1` | 在时间窗口内允许的最大错误次数 |
|
||||||
|
|
||||||
|
**工作原理:**
|
||||||
|
- 当用户输入错误的提取码时,系统记录该 IP 的错误次数
|
||||||
|
- 当错误次数达到 `errorCount` 时,该 IP 将被暂时锁定
|
||||||
|
- 锁定时间为 `errorMinute` 分钟
|
||||||
|
- 锁定期间,该 IP 的所有提取请求都将被拒绝
|
||||||
|
|
||||||
|
**配置示例:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 防暴力破解配置:5分钟内最多允许3次错误
|
||||||
|
{
|
||||||
|
"errorMinute": 5,
|
||||||
|
"errorCount": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
默认配置 `errorMinute=1, errorCount=1` 非常严格,意味着输入一次错误的提取码后需要等待1分钟才能重试。根据实际需求调整此配置。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 上传限制
|
||||||
|
|
||||||
|
### 文件大小限制
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `uploadSize` | int | `10485760` | 单文件最大上传大小(字节),默认 10MB |
|
||||||
|
| `openUpload` | int | `1` | 是否开启上传功能(1=开启,0=关闭) |
|
||||||
|
|
||||||
|
**常用大小换算:**
|
||||||
|
- 10MB = 10 * 1024 * 1024 = `10485760`
|
||||||
|
- 50MB = 50 * 1024 * 1024 = `52428800`
|
||||||
|
- 100MB = 100 * 1024 * 1024 = `104857600`
|
||||||
|
- 1GB = 1024 * 1024 * 1024 = `1073741824`
|
||||||
|
|
||||||
|
### 文件过期设置
|
||||||
|
|
||||||
|
通过文件过期机制,可以自动清理过期文件,减少存储占用和安全风险:
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `expireStyle` | list | `["day","hour","minute","forever","count"]` | 可选的过期方式 |
|
||||||
|
| `max_save_seconds` | int | `0` | 文件最大保存时间(秒),0 表示不限制 |
|
||||||
|
|
||||||
|
**过期方式说明:**
|
||||||
|
- `day` - 按天数过期
|
||||||
|
- `hour` - 按小时过期
|
||||||
|
- `minute` - 按分钟过期
|
||||||
|
- `forever` - 永不过期(需要字符串提取码)
|
||||||
|
- `count` - 按下载次数过期
|
||||||
|
|
||||||
|
**安全建议:**
|
||||||
|
|
||||||
|
对于公开服务,建议:
|
||||||
|
1. 移除 `forever` 选项,避免文件永久存储
|
||||||
|
2. 设置 `max_save_seconds` 限制最长保存时间
|
||||||
|
3. 优先使用 `count` 方式,下载后自动删除
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 公开服务推荐配置
|
||||||
|
{
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400 # 最长保存1天
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关闭上传功能
|
||||||
|
|
||||||
|
在某些情况下,您可能需要临时关闭上传功能:
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"openUpload": 0 # 关闭上传功能
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 反向代理安全配置
|
||||||
|
|
||||||
|
在生产环境中,通常会使用 Nginx 或其他反向代理服务器。以下是安全配置建议:
|
||||||
|
|
||||||
|
### Nginx 配置示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# 强制 HTTPS 重定向
|
||||||
|
return 301 https://$server_name$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl http2;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
# SSL 证书配置
|
||||||
|
ssl_certificate /path/to/cert.pem;
|
||||||
|
ssl_certificate_key /path/to/key.pem;
|
||||||
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||||
|
ssl_prefer_server_ciphers on;
|
||||||
|
|
||||||
|
# 安全头部
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||||
|
|
||||||
|
# 限制请求体大小(与 uploadSize 配置一致)
|
||||||
|
client_max_body_size 100M;
|
||||||
|
|
||||||
|
# 传递真实 IP
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:12345;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 静态资源缓存
|
||||||
|
location /assets {
|
||||||
|
proxy_pass http://127.0.0.1:12345;
|
||||||
|
proxy_cache_valid 200 7d;
|
||||||
|
add_header Cache-Control "public, max-age=604800";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 关键安全配置说明
|
||||||
|
|
||||||
|
**1. 传递真实 IP**
|
||||||
|
|
||||||
|
FileCodeBox 的 IP 限制功能依赖于获取客户端真实 IP。系统会按以下顺序获取 IP:
|
||||||
|
1. `X-Real-IP` 请求头
|
||||||
|
2. `X-Forwarded-For` 请求头
|
||||||
|
3. 直接连接的客户端 IP
|
||||||
|
|
||||||
|
确保反向代理正确设置这些头部:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 请求体大小限制**
|
||||||
|
|
||||||
|
Nginx 的 `client_max_body_size` 应该与 FileCodeBox 的 `uploadSize` 配置一致或略大:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
client_max_body_size 100M; # 允许上传最大 100MB
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. HTTPS 加密**
|
||||||
|
|
||||||
|
强烈建议在生产环境中启用 HTTPS:
|
||||||
|
- 保护用户上传的文件内容
|
||||||
|
- 保护管理员登录凭据
|
||||||
|
- 防止中间人攻击
|
||||||
|
|
||||||
|
### Caddy 配置示例
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
your-domain.com {
|
||||||
|
reverse_proxy localhost:12345
|
||||||
|
|
||||||
|
header {
|
||||||
|
X-Frame-Options "SAMEORIGIN"
|
||||||
|
X-Content-Type-Options "nosniff"
|
||||||
|
X-XSS-Protection "1; mode=block"
|
||||||
|
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 安全检查清单
|
||||||
|
|
||||||
|
部署 FileCodeBox 前,请确认以下安全配置:
|
||||||
|
|
||||||
|
- [ ] 已修改默认管理员密码 `admin_token`
|
||||||
|
- [ ] 已隐藏管理入口 `showAdminAddr: 0`
|
||||||
|
- [ ] 已配置合适的上传频率限制
|
||||||
|
- [ ] 已配置错误次数限制防止暴力破解
|
||||||
|
- [ ] 已设置合理的文件大小限制
|
||||||
|
- [ ] 已配置文件过期策略
|
||||||
|
- [ ] 已启用 HTTPS 加密
|
||||||
|
- [ ] 反向代理已正确传递真实 IP
|
||||||
|
- [ ] 已设置安全响应头部
|
||||||
|
|
||||||
|
## 推荐安全配置
|
||||||
|
|
||||||
|
### 公开服务配置
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"admin_token": "your-very-secure-password",
|
||||||
|
"showAdminAddr": 0,
|
||||||
|
"uploadSize": 10485760, # 10MB
|
||||||
|
"uploadMinute": 1,
|
||||||
|
"uploadCount": 5,
|
||||||
|
"errorMinute": 5,
|
||||||
|
"errorCount": 3,
|
||||||
|
"expireStyle": ["hour", "minute", "count"],
|
||||||
|
"max_save_seconds": 86400, # 最长1天
|
||||||
|
"openUpload": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 内部服务配置
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"admin_token": "internal-secure-password",
|
||||||
|
"showAdminAddr": 1,
|
||||||
|
"uploadSize": 104857600, # 100MB
|
||||||
|
"uploadMinute": 5,
|
||||||
|
"uploadCount": 50,
|
||||||
|
"errorMinute": 1,
|
||||||
|
"errorCount": 5,
|
||||||
|
"expireStyle": ["day", "hour", "forever"],
|
||||||
|
"max_save_seconds": 0, # 不限制
|
||||||
|
"openUpload": 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [配置说明](/guide/configuration) - 了解所有配置选项
|
||||||
|
- [存储配置](/guide/storage) - 配置安全的存储后端
|
||||||
|
- [文件分享](/guide/share) - 了解文件分享功能
|
||||||
|
|||||||
@@ -0,0 +1,341 @@
|
|||||||
|
# 文件分享
|
||||||
|
|
||||||
|
FileCodeBox 提供了简单易用的文件和文本分享功能。用户可以通过提取码安全地分享和获取文件。
|
||||||
|
|
||||||
|
## 分享方式
|
||||||
|
|
||||||
|
FileCodeBox 支持两种分享方式:
|
||||||
|
|
||||||
|
1. **文本分享** - 直接分享文本内容,适合代码片段、配置文件等
|
||||||
|
2. **文件分享** - 上传文件进行分享,支持各种文件格式
|
||||||
|
|
||||||
|
## 文本分享
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 在首页选择「文本分享」标签
|
||||||
|
2. 在文本框中输入或粘贴要分享的内容
|
||||||
|
3. 选择过期方式和时间
|
||||||
|
4. 点击「分享」按钮
|
||||||
|
5. 获取提取码
|
||||||
|
|
||||||
|
### 文本大小限制
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
文本分享的最大内容大小为 **222KB**(227,328 字节)。如果内容超过此限制,建议使用文件分享方式。
|
||||||
|
:::
|
||||||
|
|
||||||
|
文本内容大小按 UTF-8 编码计算,中文字符通常占用 3 个字节。
|
||||||
|
|
||||||
|
### API 接口
|
||||||
|
|
||||||
|
**POST** `/share/text/`
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `text` | string | 是 | 要分享的文本内容 |
|
||||||
|
| `expire_value` | int | 否 | 过期数值,默认 1 |
|
||||||
|
| `expire_style` | string | 否 | 过期方式,默认 `day` |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 文件分享
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 在首页选择「文件分享」标签
|
||||||
|
2. 点击上传区域或拖拽文件到上传区域
|
||||||
|
3. 选择过期方式和时间
|
||||||
|
4. 点击「上传」按钮
|
||||||
|
5. 获取提取码
|
||||||
|
|
||||||
|
### 文件大小限制
|
||||||
|
|
||||||
|
默认单文件最大上传大小为 **10MB**。管理员可以通过 `uploadSize` 配置项修改此限制。
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
如果需要上传大文件,请联系管理员启用分片上传功能,或调整 `uploadSize` 配置。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 支持的上传方式
|
||||||
|
|
||||||
|
- **点击上传** - 点击上传区域选择文件
|
||||||
|
- **拖拽上传** - 将文件拖拽到上传区域
|
||||||
|
- **粘贴上传** - 从剪贴板粘贴图片(部分主题支持)
|
||||||
|
|
||||||
|
### API 接口
|
||||||
|
|
||||||
|
**POST** `/share/file/`
|
||||||
|
|
||||||
|
请求参数(multipart/form-data):
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `file` | file | 是 | 要上传的文件 |
|
||||||
|
| `expire_value` | int | 否 | 过期数值,默认 1 |
|
||||||
|
| `expire_style` | string | 否 | 过期方式,默认 `day` |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "654321",
|
||||||
|
"name": "example.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 过期设置
|
||||||
|
|
||||||
|
FileCodeBox 支持多种灵活的过期方式:
|
||||||
|
|
||||||
|
| 过期方式 | 参数值 | 说明 |
|
||||||
|
|----------|--------|------|
|
||||||
|
| 按天过期 | `day` | 文件在指定天数后过期 |
|
||||||
|
| 按小时过期 | `hour` | 文件在指定小时后过期 |
|
||||||
|
| 按分钟过期 | `minute` | 文件在指定分钟后过期 |
|
||||||
|
| 永不过期 | `forever` | 文件永久有效 |
|
||||||
|
| 按次数过期 | `count` | 文件在被下载指定次数后过期 |
|
||||||
|
|
||||||
|
::: info 说明
|
||||||
|
- 管理员可以通过 `expireStyle` 配置项控制用户可选的过期方式
|
||||||
|
- 管理员可以通过 `max_save_seconds` 配置项限制文件的最长保存时间
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 过期方式示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 文件 3 天后过期
|
||||||
|
expire_value=3, expire_style=day
|
||||||
|
|
||||||
|
# 文件 12 小时后过期
|
||||||
|
expire_value=12, expire_style=hour
|
||||||
|
|
||||||
|
# 文件 30 分钟后过期
|
||||||
|
expire_value=30, expire_style=minute
|
||||||
|
|
||||||
|
# 文件永不过期
|
||||||
|
expire_value=1, expire_style=forever
|
||||||
|
|
||||||
|
# 文件被下载 5 次后过期
|
||||||
|
expire_value=5, expire_style=count
|
||||||
|
```
|
||||||
|
|
||||||
|
## 提取文件
|
||||||
|
|
||||||
|
### 使用方法
|
||||||
|
|
||||||
|
1. 在首页的「提取文件」区域输入提取码
|
||||||
|
2. 点击「提取」按钮
|
||||||
|
3. 系统会显示文件信息(文件名、大小等)
|
||||||
|
4. 点击「下载」按钮下载文件,或直接查看文本内容
|
||||||
|
|
||||||
|
### 提取码说明
|
||||||
|
|
||||||
|
- 提取码通常为 **6 位数字**
|
||||||
|
- 永不过期的文件使用 **字母数字混合** 的提取码
|
||||||
|
- 提取码区分大小写(针对字母数字混合的情况)
|
||||||
|
|
||||||
|
### API 接口
|
||||||
|
|
||||||
|
**查询文件信息**
|
||||||
|
|
||||||
|
**POST** `/share/select/`
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": "123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例(文件):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456",
|
||||||
|
"name": "example.pdf",
|
||||||
|
"size": 1048576,
|
||||||
|
"text": "https://example.com/download/..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例(文本):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "123456",
|
||||||
|
"name": "Text",
|
||||||
|
"size": 1024,
|
||||||
|
"text": "这是分享的文本内容..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**直接下载文件**
|
||||||
|
|
||||||
|
**GET** `/share/select/?code=123456`
|
||||||
|
|
||||||
|
此接口会直接返回文件内容,适合在浏览器中直接访问。
|
||||||
|
|
||||||
|
## 分片上传(大文件)
|
||||||
|
|
||||||
|
对于大文件上传,FileCodeBox 支持分片上传功能。此功能需要管理员启用(`enableChunk=1`)。
|
||||||
|
|
||||||
|
### 分片上传流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant C as 客户端
|
||||||
|
participant S as 服务器
|
||||||
|
|
||||||
|
C->>S: 1. 初始化上传 (POST /chunk/upload/init/)
|
||||||
|
S-->>C: 返回 upload_id 和分片信息
|
||||||
|
|
||||||
|
loop 每个分片
|
||||||
|
C->>S: 2. 上传分片 (POST /chunk/upload/chunk/{upload_id}/{chunk_index})
|
||||||
|
S-->>C: 返回分片哈希
|
||||||
|
end
|
||||||
|
|
||||||
|
C->>S: 3. 完成上传 (POST /chunk/upload/complete/{upload_id})
|
||||||
|
S-->>C: 返回提取码
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. 初始化上传
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/init/`
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_name": "large_file.zip",
|
||||||
|
"file_size": 104857600,
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"file_hash": "sha256_hash_of_file"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `file_name` | string | 是 | 文件名 |
|
||||||
|
| `file_size` | int | 是 | 文件总大小(字节) |
|
||||||
|
| `chunk_size` | int | 否 | 分片大小,默认 5MB |
|
||||||
|
| `file_hash` | string | 是 | 文件的 SHA256 哈希值 |
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"existed": false,
|
||||||
|
"upload_id": "abc123def456",
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"total_chunks": 20,
|
||||||
|
"uploaded_chunks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 上传分片
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/chunk/{upload_id}/{chunk_index}`
|
||||||
|
|
||||||
|
- `upload_id` - 初始化时返回的上传会话 ID
|
||||||
|
- `chunk_index` - 分片索引,从 0 开始
|
||||||
|
|
||||||
|
请求体:分片文件数据(multipart/form-data)
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"chunk_hash": "sha256_hash_of_chunk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 完成上传
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/complete/{upload_id}`
|
||||||
|
|
||||||
|
请求参数:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expire_value": 1,
|
||||||
|
"expire_style": "day"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
响应示例:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "789012",
|
||||||
|
"name": "large_file.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 断点续传
|
||||||
|
|
||||||
|
分片上传支持断点续传。如果上传中断,可以:
|
||||||
|
|
||||||
|
1. 重新调用初始化接口,使用相同的 `file_hash`
|
||||||
|
2. 服务器会返回已上传的分片列表 `uploaded_chunks`
|
||||||
|
3. 客户端只需上传未完成的分片
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 常见错误码
|
||||||
|
|
||||||
|
| 错误码 | 说明 | 解决方案 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| 403 | 文件大小超过限制 | 减小文件大小或联系管理员调整限制 |
|
||||||
|
| 403 | 内容过多 | 文本超过 222KB,请使用文件分享 |
|
||||||
|
| 403 | 上传频率限制 | 等待一段时间后重试 |
|
||||||
|
| 404 | 文件不存在 | 检查提取码是否正确 |
|
||||||
|
| 404 | 文件已过期 | 文件已过期或下载次数已用完 |
|
||||||
|
|
||||||
|
### 频率限制
|
||||||
|
|
||||||
|
为防止滥用,系统对上传和提取操作有频率限制:
|
||||||
|
|
||||||
|
- **上传限制**:默认每分钟最多 10 次上传
|
||||||
|
- **错误限制**:默认每分钟最多 1 次错误尝试
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
如果遇到频率限制,请等待限制时间窗口过后再重试。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [配置说明](/guide/configuration) - 了解如何配置分享相关设置
|
||||||
|
- [存储配置](/guide/storage) - 了解文件存储方式
|
||||||
|
- [安全设置](/guide/security) - 了解安全相关配置
|
||||||
|
- [管理面板](/guide/management) - 了解如何管理分享的文件
|
||||||
|
|||||||
+383
-14
@@ -1,26 +1,395 @@
|
|||||||
# 阿里云设置
|
# 存储配置
|
||||||
S3 AccessKeyId: `AccessKeyId`
|
|
||||||
|
|
||||||
S3 SecretAccessKey: `SecretAccessKey`
|
FileCodeBox 支持多种存储后端,您可以根据需求选择合适的存储方式。本文档将详细介绍各种存储后端的配置方法。
|
||||||
|
|
||||||
S3 BucketName: `bucket-name`
|
## 存储类型概览
|
||||||
|
|
||||||
S3 EndpointUrl: `https://<bucket-name>.<region>.aliyuncs.com`
|
| 存储类型 | 配置值 | 说明 |
|
||||||
|
|---------|--------|------|
|
||||||
|
| 本地存储 | `local` | 默认存储方式,文件保存在服务器本地 |
|
||||||
|
| S3 兼容存储 | `s3` | 支持 AWS S3、阿里云 OSS、MinIO 等 |
|
||||||
|
| OneDrive | `onedrive` | 微软 OneDrive 云存储(仅支持工作/学校账户) |
|
||||||
|
| WebDAV | `webdav` | 支持 WebDAV 协议的存储服务 |
|
||||||
|
| OpenDAL | `opendal` | 通过 OpenDAL 集成更多存储服务 |
|
||||||
|
|
||||||
S3 Signature Version: `s3v4`
|
## 本地存储
|
||||||
|
|
||||||
S3 Region Name:`region`
|
本地存储是默认的存储方式,文件将保存在服务器的 `data/` 目录下。
|
||||||
|
|
||||||
# Minio设置
|
### 配置参数
|
||||||
|
|
||||||
S3 AccessKeyId: `AccessKeyId`
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `file_storage` | string | `local` | 存储类型 |
|
||||||
|
| `storage_path` | string | `""` | 自定义存储路径(可选) |
|
||||||
|
|
||||||
S3 SecretAccessKey: `SecretAccessKey`
|
### 配置示例
|
||||||
|
|
||||||
S3 BucketName: `bucket-name`
|
```bash
|
||||||
|
file_storage=local
|
||||||
|
storage_path=
|
||||||
|
```
|
||||||
|
|
||||||
S3 EndpointUrl: api接口地址
|
### 说明
|
||||||
|
|
||||||
S3 Signature Version: `s3v4`
|
- 文件默认存储在 `data/share/data/` 目录下
|
||||||
|
- 按日期自动创建子目录:`年/月/日/文件ID/`
|
||||||
|
- 建议在生产环境中将 `data/` 目录挂载到持久化存储
|
||||||
|
|
||||||
S3 Region Name:根据`configurations`里面设置的 `Server Location`
|
## S3 兼容存储
|
||||||
|
|
||||||
|
支持所有 S3 兼容的对象存储服务,包括 AWS S3、阿里云 OSS、MinIO、腾讯云 COS 等。
|
||||||
|
|
||||||
|
### 配置参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `file_storage` | string | - | 设置为 `s3` |
|
||||||
|
| `s3_access_key_id` | string | `""` | Access Key ID |
|
||||||
|
| `s3_secret_access_key` | string | `""` | Secret Access Key |
|
||||||
|
| `s3_bucket_name` | string | `""` | 存储桶名称 |
|
||||||
|
| `s3_endpoint_url` | string | `""` | S3 端点 URL |
|
||||||
|
| `s3_region_name` | string | `auto` | 区域名称 |
|
||||||
|
| `s3_signature_version` | string | `s3v2` | 签名版本(`s3v2` 或 `s3v4`) |
|
||||||
|
| `s3_hostname` | string | `""` | S3 主机名(备用) |
|
||||||
|
| `s3_proxy` | int | `0` | 是否通过服务器代理下载(1=是,0=否) |
|
||||||
|
| `aws_session_token` | string | `""` | AWS 会话令牌(可选) |
|
||||||
|
|
||||||
|
### AWS S3 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=AKIAIOSFODNN7EXAMPLE
|
||||||
|
s3_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||||
|
s3_bucket_name=my-filecodebox-bucket
|
||||||
|
s3_endpoint_url=https://s3.amazonaws.com
|
||||||
|
s3_region_name=us-east-1
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 阿里云 OSS 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=您的AccessKeyId
|
||||||
|
s3_secret_access_key=您的SecretAccessKey
|
||||||
|
s3_bucket_name=bucket-name
|
||||||
|
s3_endpoint_url=https://bucket-name.oss-cn-hangzhou.aliyuncs.com
|
||||||
|
s3_region_name=oss-cn-hangzhou
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip 阿里云 OSS 端点格式
|
||||||
|
端点 URL 格式为:`https://<bucket-name>.<region>.aliyuncs.com`
|
||||||
|
|
||||||
|
常用区域:
|
||||||
|
- 杭州:`oss-cn-hangzhou`
|
||||||
|
- 上海:`oss-cn-shanghai`
|
||||||
|
- 北京:`oss-cn-beijing`
|
||||||
|
- 深圳:`oss-cn-shenzhen`
|
||||||
|
:::
|
||||||
|
|
||||||
|
### MinIO 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=minioadmin
|
||||||
|
s3_secret_access_key=minioadmin
|
||||||
|
s3_bucket_name=filecodebox
|
||||||
|
s3_endpoint_url=http://localhost:9000
|
||||||
|
s3_region_name=us-east-1
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning MinIO 注意事项
|
||||||
|
- `s3_endpoint_url` 填写 MinIO 的 API 接口地址
|
||||||
|
- `s3_region_name` 根据 MinIO 配置中的 `Server Location` 设置
|
||||||
|
- 确保存储桶已创建且有正确的访问权限
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 腾讯云 COS 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=s3
|
||||||
|
s3_access_key_id=您的SecretId
|
||||||
|
s3_secret_access_key=您的SecretKey
|
||||||
|
s3_bucket_name=bucket-name-1250000000
|
||||||
|
s3_endpoint_url=https://cos.ap-guangzhou.myqcloud.com
|
||||||
|
s3_region_name=ap-guangzhou
|
||||||
|
s3_signature_version=s3v4
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代理下载
|
||||||
|
|
||||||
|
当 `s3_proxy=1` 时,文件下载将通过服务器中转,而不是直接从 S3 下载。这在以下情况下有用:
|
||||||
|
|
||||||
|
- S3 存储桶不允许公开访问
|
||||||
|
- 需要隐藏实际的存储地址
|
||||||
|
- 网络环境限制直接访问 S3
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## OneDrive 存储
|
||||||
|
|
||||||
|
OneDrive 存储支持将文件保存到微软 OneDrive 云存储。
|
||||||
|
|
||||||
|
::: warning 重要限制
|
||||||
|
OneDrive 存储**仅支持工作或学校账户**,并且需要有管理员权限以授权 API。个人账户无法使用此功能。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 配置参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `file_storage` | string | - | 设置为 `onedrive` |
|
||||||
|
| `onedrive_domain` | string | `""` | Azure AD 域名 |
|
||||||
|
| `onedrive_client_id` | string | `""` | 应用程序(客户端)ID |
|
||||||
|
| `onedrive_username` | string | `""` | 账户邮箱 |
|
||||||
|
| `onedrive_password` | string | `""` | 账户密码 |
|
||||||
|
| `onedrive_root_path` | string | `filebox_storage` | OneDrive 中的存储根目录 |
|
||||||
|
| `onedrive_proxy` | int | `0` | 是否通过服务器代理下载 |
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=onedrive
|
||||||
|
onedrive_domain=contoso.onmicrosoft.com
|
||||||
|
onedrive_client_id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||||
|
onedrive_username=user@contoso.onmicrosoft.com
|
||||||
|
onedrive_password=your_password
|
||||||
|
onedrive_root_path=filebox_storage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure 应用注册步骤
|
||||||
|
|
||||||
|
要使用 OneDrive 存储,您需要在 Azure 门户中注册应用程序:
|
||||||
|
|
||||||
|
#### 1. 获取域名
|
||||||
|
|
||||||
|
登录 [Azure 门户](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade),将鼠标置于右上角账号处,浮窗显示的**域**即为 `onedrive_domain` 的值。
|
||||||
|
|
||||||
|
#### 2. 注册应用
|
||||||
|
|
||||||
|
1. 点击左上角的 **+ 新注册**
|
||||||
|
2. 输入应用名称(如:FileCodeBox)
|
||||||
|
3. **受支持的帐户类型**:选择"任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户"
|
||||||
|
4. **重定向 URI**:选择 `Web`,输入 `http://localhost`
|
||||||
|
5. 点击**注册**
|
||||||
|
|
||||||
|
#### 3. 获取客户端 ID
|
||||||
|
|
||||||
|
注册完成后,在应用概述页面的**概要**中找到**应用程序(客户端)ID**,即为 `onedrive_client_id` 的值。
|
||||||
|
|
||||||
|
#### 4. 配置身份验证
|
||||||
|
|
||||||
|
1. 在左侧菜单选择**身份验证**
|
||||||
|
2. 找到**允许公共客户端流**,选择**是**
|
||||||
|
3. 点击**保存**
|
||||||
|
|
||||||
|
#### 5. 配置 API 权限
|
||||||
|
|
||||||
|
1. 在左侧菜单选择 **API 权限**
|
||||||
|
2. 点击 **+ 添加权限**
|
||||||
|
3. 选择 **Microsoft Graph** → **委托的权限**
|
||||||
|
4. 勾选以下权限:
|
||||||
|
- `openid`
|
||||||
|
- `Files.Read`
|
||||||
|
- `Files.Read.All`
|
||||||
|
- `Files.ReadWrite`
|
||||||
|
- `Files.ReadWrite.All`
|
||||||
|
- `User.Read`
|
||||||
|
5. 点击**添加权限**
|
||||||
|
6. 点击**代表 xxx 授予管理员同意**
|
||||||
|
7. 确认后,权限状态应显示为**已授予**
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
使用 OneDrive 存储需要安装额外的 Python 依赖:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install msal Office365-REST-Python-Client
|
||||||
|
```
|
||||||
|
|
||||||
|
### 验证配置
|
||||||
|
|
||||||
|
您可以使用以下代码测试配置是否正确:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import msal
|
||||||
|
from office365.graph_client import GraphClient
|
||||||
|
|
||||||
|
domain = 'your_domain'
|
||||||
|
client_id = 'your_client_id'
|
||||||
|
username = 'your_username'
|
||||||
|
password = 'your_password'
|
||||||
|
|
||||||
|
def acquire_token_pwd():
|
||||||
|
authority_url = f'https://login.microsoftonline.com/{domain}'
|
||||||
|
app = msal.PublicClientApplication(
|
||||||
|
authority=authority_url,
|
||||||
|
client_id=client_id
|
||||||
|
)
|
||||||
|
result = app.acquire_token_by_username_password(
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
scopes=['https://graph.microsoft.com/.default']
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
# 测试连接
|
||||||
|
client = GraphClient(acquire_token_pwd)
|
||||||
|
me = client.me.get().execute_query()
|
||||||
|
print(f"登录成功:{me.user_principal_name}")
|
||||||
|
```
|
||||||
|
|
||||||
|
## WebDAV 存储
|
||||||
|
|
||||||
|
WebDAV 存储支持将文件保存到任何支持 WebDAV 协议的服务,如 Nextcloud、ownCloud、坚果云等。
|
||||||
|
|
||||||
|
### 配置参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|
|------|------|--------|------|
|
||||||
|
| `file_storage` | string | - | 设置为 `webdav` |
|
||||||
|
| `webdav_url` | string | `""` | WebDAV 服务器 URL |
|
||||||
|
| `webdav_username` | string | `""` | WebDAV 用户名 |
|
||||||
|
| `webdav_password` | string | `""` | WebDAV 密码 |
|
||||||
|
| `webdav_root_path` | string | `filebox_storage` | WebDAV 中的存储根目录 |
|
||||||
|
| `webdav_proxy` | int | `0` | 是否通过服务器代理下载 |
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://dav.example.com/remote.php/dav/files/username/
|
||||||
|
webdav_username=your_username
|
||||||
|
webdav_password=your_password
|
||||||
|
webdav_root_path=filebox_storage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nextcloud 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://your-nextcloud.com/remote.php/dav/files/username/
|
||||||
|
webdav_username=your_username
|
||||||
|
webdav_password=your_app_password
|
||||||
|
webdav_root_path=FileCodeBox
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip Nextcloud 应用密码
|
||||||
|
建议在 Nextcloud 中创建应用密码,而不是使用主密码:
|
||||||
|
1. 登录 Nextcloud
|
||||||
|
2. 进入**设置** → **安全**
|
||||||
|
3. 在**设备与会话**中创建新的应用密码
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 坚果云配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=webdav
|
||||||
|
webdav_url=https://dav.jianguoyun.com/dav/
|
||||||
|
webdav_username=your_email@example.com
|
||||||
|
webdav_password=your_app_password
|
||||||
|
webdav_root_path=FileCodeBox
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip 坚果云应用密码
|
||||||
|
坚果云需要使用应用密码:
|
||||||
|
1. 登录坚果云网页版
|
||||||
|
2. 进入**账户信息** → **安全选项**
|
||||||
|
3. 添加应用密码
|
||||||
|
:::
|
||||||
|
|
||||||
|
## OpenDAL 存储
|
||||||
|
|
||||||
|
OpenDAL 是一个统一的数据访问层,支持多种存储服务。通过 OpenDAL,您可以使用 Google Cloud Storage、Azure Blob Storage 等更多存储服务。
|
||||||
|
|
||||||
|
### 配置参数
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `file_storage` | string | 设置为 `opendal` |
|
||||||
|
| `opendal_scheme` | string | 存储服务类型(如 `gcs`、`azblob`) |
|
||||||
|
| `opendal_<scheme>_<setting>` | string | 服务特定的配置参数 |
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install opendal
|
||||||
|
```
|
||||||
|
|
||||||
|
### Google Cloud Storage 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=opendal
|
||||||
|
opendal_scheme=gcs
|
||||||
|
opendal_gcs_root=/filecodebox
|
||||||
|
opendal_gcs_bucket=your-bucket-name
|
||||||
|
opendal_gcs_credential=base64_encoded_credential
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure Blob Storage 配置示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
file_storage=opendal
|
||||||
|
opendal_scheme=azblob
|
||||||
|
opendal_azblob_root=/filecodebox
|
||||||
|
opendal_azblob_container=your-container
|
||||||
|
opendal_azblob_account_name=your_account
|
||||||
|
opendal_azblob_account_key=your_key
|
||||||
|
```
|
||||||
|
|
||||||
|
### 支持的服务
|
||||||
|
|
||||||
|
OpenDAL 支持众多存储服务,完整列表请参考 [OpenDAL 官方文档](https://opendal.apache.org/docs/rust/opendal/services/index.html)。
|
||||||
|
|
||||||
|
常用服务包括:
|
||||||
|
- `gcs` - Google Cloud Storage
|
||||||
|
- `azblob` - Azure Blob Storage
|
||||||
|
- `obs` - 华为云 OBS
|
||||||
|
- `oss` - 阿里云 OSS(通过 OpenDAL)
|
||||||
|
- `cos` - 腾讯云 COS(通过 OpenDAL)
|
||||||
|
- `hdfs` - Hadoop HDFS
|
||||||
|
- `ftp` - FTP 服务器
|
||||||
|
- `sftp` - SFTP 服务器
|
||||||
|
|
||||||
|
::: warning OpenDAL 注意事项
|
||||||
|
1. 通过 OpenDAL 集成的服务均通过服务器中转下载,会同时消耗存储服务和服务器的流量
|
||||||
|
2. 相比原生 S3/OneDrive 支持,OpenDAL 方式可能缺少一些调试信息
|
||||||
|
3. OpenDAL 采用 Rust 编写,性能较好
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 存储选择建议
|
||||||
|
|
||||||
|
| 场景 | 推荐存储 | 原因 |
|
||||||
|
|------|----------|------|
|
||||||
|
| 个人/小型部署 | 本地存储 | 简单易用,无需额外配置 |
|
||||||
|
| 企业内网 | MinIO + S3 | 自建对象存储,数据可控 |
|
||||||
|
| 公有云部署 | 对应云厂商 S3 | 同区域访问快,成本低 |
|
||||||
|
| 已有 OneDrive | OneDrive | 利用现有资源 |
|
||||||
|
| 已有 WebDAV | WebDAV | 兼容性好 |
|
||||||
|
| 特殊存储需求 | OpenDAL | 支持更多存储服务 |
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### S3 上传失败
|
||||||
|
|
||||||
|
1. 检查 Access Key 和 Secret Key 是否正确
|
||||||
|
2. 确认存储桶名称和区域配置正确
|
||||||
|
3. 检查存储桶的访问权限设置
|
||||||
|
4. 确认签名版本(`s3v2` 或 `s3v4`)与服务商要求一致
|
||||||
|
|
||||||
|
### OneDrive 认证失败
|
||||||
|
|
||||||
|
1. 确认使用的是工作/学校账户,而非个人账户
|
||||||
|
2. 检查 Azure 应用是否已授予管理员同意
|
||||||
|
3. 确认 API 权限配置完整
|
||||||
|
4. 验证用户名和密码是否正确
|
||||||
|
|
||||||
|
### WebDAV 连接失败
|
||||||
|
|
||||||
|
1. 检查 WebDAV URL 格式是否正确
|
||||||
|
2. 确认用户名和密码(或应用密码)正确
|
||||||
|
3. 检查服务器是否支持 WebDAV 协议
|
||||||
|
4. 确认网络连接正常
|
||||||
|
|||||||
@@ -0,0 +1,379 @@
|
|||||||
|
# 文件上传
|
||||||
|
|
||||||
|
FileCodeBox 提供了多种灵活的文件上传方式,支持普通上传和分片上传,满足不同场景的需求。
|
||||||
|
|
||||||
|
## 上传方式
|
||||||
|
|
||||||
|
FileCodeBox 支持以下几种上传方式:
|
||||||
|
|
||||||
|
### 拖拽上传
|
||||||
|
|
||||||
|
将文件直接拖拽到上传区域即可开始上传。这是最便捷的上传方式。
|
||||||
|
|
||||||
|
1. 打开 FileCodeBox 首页
|
||||||
|
2. 将文件从文件管理器拖拽到上传区域
|
||||||
|
3. 松开鼠标,文件开始上传
|
||||||
|
4. 上传完成后获取提取码
|
||||||
|
|
||||||
|
::: tip 提示
|
||||||
|
拖拽上传支持同时拖拽多个文件(取决于主题支持)。
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 点击上传
|
||||||
|
|
||||||
|
点击上传区域,通过系统文件选择器选择文件。
|
||||||
|
|
||||||
|
1. 点击上传区域的「选择文件」按钮
|
||||||
|
2. 在弹出的文件选择器中选择要上传的文件
|
||||||
|
3. 确认选择后文件开始上传
|
||||||
|
4. 上传完成后获取提取码
|
||||||
|
|
||||||
|
### 粘贴上传
|
||||||
|
|
||||||
|
支持从剪贴板直接粘贴图片进行上传(部分主题支持)。
|
||||||
|
|
||||||
|
1. 复制图片到剪贴板(截图或复制图片)
|
||||||
|
2. 在上传区域使用 `Ctrl+V`(Windows/Linux)或 `Cmd+V`(macOS)粘贴
|
||||||
|
3. 图片自动开始上传
|
||||||
|
4. 上传完成后获取提取码
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
粘贴上传仅支持图片格式,不支持其他文件类型。具体支持情况取决于所使用的主题。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 文件大小限制
|
||||||
|
|
||||||
|
### 默认限制
|
||||||
|
|
||||||
|
| 配置项 | 默认值 | 说明 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| `uploadSize` | 10MB | 单文件最大上传大小 |
|
||||||
|
|
||||||
|
### 修改上传限制
|
||||||
|
|
||||||
|
管理员可以通过管理面板或配置文件修改上传大小限制:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 设置最大上传大小为 100MB
|
||||||
|
uploadSize = 104857600 # 100 * 1024 * 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
::: info 说明
|
||||||
|
`uploadSize` 的单位是字节。常用换算:
|
||||||
|
- 10MB = 10485760
|
||||||
|
- 50MB = 52428800
|
||||||
|
- 100MB = 104857600
|
||||||
|
- 500MB = 524288000
|
||||||
|
- 1GB = 1073741824
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 超出限制的处理
|
||||||
|
|
||||||
|
当上传文件超过大小限制时,系统会返回 403 错误:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "大小超过限制,最大为10.00 MB"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 普通上传 API
|
||||||
|
|
||||||
|
### 文件上传接口
|
||||||
|
|
||||||
|
**POST** `/share/file/`
|
||||||
|
|
||||||
|
Content-Type: `multipart/form-data`
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `file` | file | 是 | 要上传的文件 |
|
||||||
|
| `expire_value` | int | 否 | 过期数值,默认 1 |
|
||||||
|
| `expire_style` | string | 否 | 过期方式,默认 `day` |
|
||||||
|
|
||||||
|
**过期方式选项:**
|
||||||
|
|
||||||
|
| 值 | 说明 |
|
||||||
|
|----|------|
|
||||||
|
| `day` | 按天过期 |
|
||||||
|
| `hour` | 按小时过期 |
|
||||||
|
| `minute` | 按分钟过期 |
|
||||||
|
| `forever` | 永不过期 |
|
||||||
|
| `count` | 按下载次数过期 |
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "654321",
|
||||||
|
"name": "example.pdf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**cURL 示例:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://localhost:12345/share/file/" \
|
||||||
|
-F "file=@/path/to/file.pdf" \
|
||||||
|
-F "expire_value=7" \
|
||||||
|
-F "expire_style=day"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 分片上传 API
|
||||||
|
|
||||||
|
对于大文件,FileCodeBox 支持分片上传功能。分片上传将大文件分割成多个小块分别上传,支持断点续传。
|
||||||
|
|
||||||
|
::: warning 前提条件
|
||||||
|
分片上传功能需要管理员启用:`enableChunk=1`
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 分片上传流程
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ 初始化上传 │ ──▶ │ 上传分片 │ ──▶ │ 完成上传 │
|
||||||
|
│ /init/ │ │ /chunk/ │ │ /complete/ │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌───────────┐
|
||||||
|
│ 循环上传 │
|
||||||
|
│ 每个分片 │
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1. 初始化上传
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/init/`
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"file_name": "large_file.zip",
|
||||||
|
"file_size": 104857600,
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"file_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||||
|
|------|------|------|--------|------|
|
||||||
|
| `file_name` | string | 是 | - | 文件名 |
|
||||||
|
| `file_size` | int | 是 | - | 文件总大小(字节) |
|
||||||
|
| `chunk_size` | int | 否 | 5MB | 分片大小(字节) |
|
||||||
|
| `file_hash` | string | 是 | - | 文件的 SHA256 哈希值 |
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"existed": false,
|
||||||
|
"upload_id": "abc123def456789",
|
||||||
|
"chunk_size": 5242880,
|
||||||
|
"total_chunks": 20,
|
||||||
|
"uploaded_chunks": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 字段 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `existed` | 文件是否已存在(秒传) |
|
||||||
|
| `upload_id` | 上传会话 ID |
|
||||||
|
| `chunk_size` | 分片大小 |
|
||||||
|
| `total_chunks` | 总分片数 |
|
||||||
|
| `uploaded_chunks` | 已上传的分片索引列表 |
|
||||||
|
|
||||||
|
### 2. 上传分片
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/chunk/{upload_id}/{chunk_index}`
|
||||||
|
|
||||||
|
**路径参数:**
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `upload_id` | 初始化时返回的上传会话 ID |
|
||||||
|
| `chunk_index` | 分片索引,从 0 开始 |
|
||||||
|
|
||||||
|
**请求体:**
|
||||||
|
|
||||||
|
Content-Type: `multipart/form-data`
|
||||||
|
|
||||||
|
| 参数 | 类型 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `chunk` | file | 分片数据 |
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"chunk_hash": "a1b2c3d4e5f6..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**cURL 示例:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 上传第一个分片(索引为 0)
|
||||||
|
curl -X POST "http://localhost:12345/chunk/upload/chunk/abc123def456789/0" \
|
||||||
|
-F "chunk=@/path/to/chunk_0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 完成上传
|
||||||
|
|
||||||
|
**POST** `/chunk/upload/complete/{upload_id}`
|
||||||
|
|
||||||
|
**路径参数:**
|
||||||
|
|
||||||
|
| 参数 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `upload_id` | 上传会话 ID |
|
||||||
|
|
||||||
|
**请求参数:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"expire_value": 7,
|
||||||
|
"expire_style": "day"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 类型 | 必填 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| `expire_value` | int | 是 | 过期数值 |
|
||||||
|
| `expire_style` | string | 是 | 过期方式 |
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"detail": {
|
||||||
|
"code": "789012",
|
||||||
|
"name": "large_file.zip"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 断点续传
|
||||||
|
|
||||||
|
分片上传支持断点续传。当上传中断后:
|
||||||
|
|
||||||
|
1. 使用相同的 `file_hash` 重新调用初始化接口
|
||||||
|
2. 服务器返回 `uploaded_chunks` 列表,包含已上传的分片索引
|
||||||
|
3. 客户端只需上传不在列表中的分片
|
||||||
|
4. 所有分片上传完成后调用完成接口
|
||||||
|
|
||||||
|
**示例流程:**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 1. 初始化上传
|
||||||
|
const initResponse = await fetch('/chunk/upload/init/', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_name: 'large_file.zip',
|
||||||
|
file_size: fileSize,
|
||||||
|
chunk_size: 5 * 1024 * 1024,
|
||||||
|
file_hash: fileHash
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const { upload_id, uploaded_chunks, total_chunks } = await initResponse.json();
|
||||||
|
|
||||||
|
// 2. 上传未完成的分片
|
||||||
|
for (let i = 0; i < total_chunks; i++) {
|
||||||
|
if (!uploaded_chunks.includes(i)) {
|
||||||
|
const chunk = file.slice(i * chunkSize, (i + 1) * chunkSize);
|
||||||
|
await fetch(`/chunk/upload/chunk/${upload_id}/${i}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: chunk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 完成上传
|
||||||
|
await fetch(`/chunk/upload/complete/${upload_id}`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
expire_value: 7,
|
||||||
|
expire_style: 'day'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
|
||||||
|
### 常见错误
|
||||||
|
|
||||||
|
| HTTP 状态码 | 错误信息 | 原因 | 解决方案 |
|
||||||
|
|-------------|----------|------|----------|
|
||||||
|
| 403 | 大小超过限制 | 文件超过 `uploadSize` 限制 | 减小文件大小或联系管理员调整限制 |
|
||||||
|
| 403 | 上传频率限制 | 超过 IP 上传频率限制 | 等待限制时间窗口后重试 |
|
||||||
|
| 400 | 过期时间类型错误 | `expire_style` 值不在允许列表中 | 使用有效的过期方式 |
|
||||||
|
| 404 | 上传会话不存在 | `upload_id` 无效或已过期 | 重新初始化上传 |
|
||||||
|
| 400 | 无效的分片索引 | `chunk_index` 超出范围 | 检查分片索引是否正确 |
|
||||||
|
| 400 | 分片不完整 | 完成上传时分片数量不足 | 确保所有分片都已上传 |
|
||||||
|
|
||||||
|
### 频率限制
|
||||||
|
|
||||||
|
系统对上传操作有频率限制,防止滥用:
|
||||||
|
|
||||||
|
| 配置项 | 默认值 | 说明 |
|
||||||
|
|--------|--------|------|
|
||||||
|
| `uploadMinute` | 1 | 限制时间窗口(分钟) |
|
||||||
|
| `uploadCount` | 10 | 时间窗口内最大上传次数 |
|
||||||
|
|
||||||
|
当超过频率限制时,需要等待时间窗口过后才能继续上传。
|
||||||
|
|
||||||
|
### 错误响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "错误信息描述"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 上传配置
|
||||||
|
|
||||||
|
### 相关配置项
|
||||||
|
|
||||||
|
| 配置项 | 类型 | 默认值 | 说明 |
|
||||||
|
|--------|------|--------|------|
|
||||||
|
| `openUpload` | int | 1 | 是否开放上传(1=开放,0=关闭) |
|
||||||
|
| `uploadSize` | int | 10485760 | 最大上传大小(字节) |
|
||||||
|
| `enableChunk` | int | 0 | 是否启用分片上传(1=启用,0=禁用) |
|
||||||
|
| `uploadMinute` | int | 1 | 上传频率限制时间窗口(分钟) |
|
||||||
|
| `uploadCount` | int | 10 | 时间窗口内最大上传次数 |
|
||||||
|
| `expireStyle` | list | ["day","hour","minute","forever","count"] | 允许的过期方式 |
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 允许上传 100MB 文件,启用分片上传
|
||||||
|
uploadSize = 104857600
|
||||||
|
enableChunk = 1
|
||||||
|
|
||||||
|
# 放宽上传频率限制:每 5 分钟最多 50 次
|
||||||
|
uploadMinute = 5
|
||||||
|
uploadCount = 50
|
||||||
|
|
||||||
|
# 只允许按天和按次数过期
|
||||||
|
expireStyle = ["day", "count"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [文件分享](/guide/share) - 了解完整的分享流程
|
||||||
|
- [配置说明](/guide/configuration) - 了解所有配置选项
|
||||||
|
- [存储配置](/guide/storage) - 了解文件存储方式
|
||||||
|
- [安全设置](/guide/security) - 了解安全相关配置
|
||||||
|
|||||||
Reference in New Issue
Block a user