Compare commits

..

15 Commits

Author SHA1 Message Date
Docker7530 521496f2df 1779195890 2026-05-19 21:04:53 +08:00
Docker7530 7f14056210 1778682052 2026-05-13 22:20:54 +08:00
Docker7530 6b50219f55 1776654103 2026-04-20 11:01:47 +08:00
Docker7530 ab41c81a53 1775206638 2026-04-03 16:57:21 +08:00
Docker7530 30063feba4 1774919088 2026-03-31 09:04:50 +08:00
Docker7530 5b32d919f3 1774877281 2026-03-30 21:28:03 +08:00
Docker7530 78804225bd 1774789246 2026-03-29 21:00:48 +08:00
Docker7530 d34d77c34d 1774789119 2026-03-29 20:58:42 +08:00
Docker7530 10b7d60a86 1774773887 2026-03-29 16:44:49 +08:00
Docker7530 6b9ac80177 1774767884 2026-03-29 15:04:47 +08:00
Docker7530 798019bbbf 1774707388 2026-03-28 22:16:30 +08:00
Docker7530 d17550351d 1774673562 2026-03-28 12:52:49 +08:00
Docker7530 e4a339bd77 1774597379 2026-03-27 15:43:03 +08:00
Docker7530 ab0cbad418 1774315861 2026-03-24 09:31:04 +08:00
Docker7530 7ab924d668 1774110743 2026-03-22 00:32:26 +08:00
388 changed files with 10364 additions and 3184 deletions
+1 -3
View File
@@ -22,7 +22,5 @@
"showIndentGuide": false, "showIndentGuide": false,
"spellcheck": false, "spellcheck": false,
"foldIndent": false, "foldIndent": false,
"userIgnoreFilters": [ "userIgnoreFilters": null
"attachment/"
]
} }
+1 -3
View File
@@ -1,7 +1,5 @@
{ {
"enabledCssSnippets": [ "enabledCssSnippets": [],
"myTheme"
],
"monospaceFontFamily": "JetBrainsMono Nerd Font Mono", "monospaceFontFamily": "JetBrainsMono Nerd Font Mono",
"textFontFamily": "思源黑体", "textFontFamily": "思源黑体",
"interfaceFontFamily": "JetBrainsMono Nerd Font Mono", "interfaceFontFamily": "JetBrainsMono Nerd Font Mono",
+2 -9
View File
@@ -7,15 +7,8 @@
}, },
{ {
"type": "file", "type": "file",
"ctime": 1768964533760, "ctime": 1778660024443,
"path": "work/移动杭研/开发记录/7.18.0/开发笔记.md", "path": "work/移动杭研/开发记录/7.21.0/开发笔记 7.21.0.md"
"title": "7.18.0 开发笔记"
},
{
"type": "file",
"ctime": 1772680366639,
"path": "work/移动杭研/开发记录/7.19.0/开发笔记.md",
"title": "7.19.0 开发笔记"
} }
] ]
} }
-1
View File
@@ -2,7 +2,6 @@
"pinned": [ "pinned": [
"file-explorer:reveal-active-file", "file-explorer:reveal-active-file",
"file-explorer:move-file", "file-explorer:move-file",
"open-with-default-app:show",
"app:reload" "app:reload"
] ]
} }
+3 -4
View File
@@ -1,11 +1,10 @@
[ [
"obsidian-paste-image-rename",
"update-relative-links", "update-relative-links",
"quickadd",
"calendar", "calendar",
"recent-files-obsidian",
"oz-clear-unused-images", "oz-clear-unused-images",
"obsidian-excalidraw-plugin", "obsidian-excalidraw-plugin",
"templater-obsidian", "templater-obsidian",
"obsidian-linter" "obsidian-linter",
"recent-files-obsidian",
"quickadd"
] ]
+1 -1
View File
@@ -11,7 +11,7 @@
"page-preview": true, "page-preview": true,
"daily-notes": true, "daily-notes": true,
"templates": false, "templates": false,
"note-composer": false, "note-composer": true,
"command-palette": true, "command-palette": true,
"slash-command": false, "slash-command": false,
"editor-status": true, "editor-status": true,
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"folder": "calendar/diary", "folder": "calendar/diary",
"template": "attachment/templates/日记模板", "template": "attachment/templates/日常记录-日记模板",
"autorun": false "autorun": false
} }
+2 -2
View File
@@ -5,7 +5,7 @@
"showAttachments": false, "showAttachments": false,
"hideUnresolved": false, "hideUnresolved": false,
"showOrphans": true, "showOrphans": true,
"collapse-color-groups": true, "collapse-color-groups": false,
"colorGroups": [], "colorGroups": [],
"collapse-display": false, "collapse-display": false,
"showArrow": false, "showArrow": false,
@@ -17,6 +17,6 @@
"repelStrength": 10, "repelStrength": 10,
"linkStrength": 1, "linkStrength": 1,
"linkDistance": 250, "linkDistance": 250,
"scale": 0.09848280266864458, "scale": 0.13807120506649795,
"close": true "close": true
} }
+3 -12
View File
@@ -1,4 +1,5 @@
{ {
"app:open-help": [],
"workspace:goto-tab-1": [], "workspace:goto-tab-1": [],
"workspace:goto-tab-2": [], "workspace:goto-tab-2": [],
"workspace:goto-tab-3": [], "workspace:goto-tab-3": [],
@@ -7,6 +8,7 @@
"workspace:goto-tab-6": [], "workspace:goto-tab-6": [],
"workspace:goto-tab-7": [], "workspace:goto-tab-7": [],
"workspace:goto-tab-8": [], "workspace:goto-tab-8": [],
"workspace:goto-last-tab": [],
"editor:set-heading-1": [ "editor:set-heading-1": [
{ {
"modifiers": [ "modifiers": [
@@ -31,21 +33,10 @@
"key": "3" "key": "3"
} }
], ],
"workspace:goto-last-tab": [],
"app:open-help": [],
"app:open-settings": [],
"obsidian-memos:show-thino-editor": [
{
"modifiers": [],
"key": "F1"
}
],
"editor:toggle-comments": [],
"quickadd:runQuickAdd": [ "quickadd:runQuickAdd": [
{ {
"modifiers": [], "modifiers": [],
"key": "F1" "key": "F1"
} }
], ]
"workspace:close-window": []
} }
+179 -11
View File
@@ -2,6 +2,7 @@
"copyLinkToElemenetAnchorTo100": false, "copyLinkToElemenetAnchorTo100": false,
"copyFrameLinkByName": false, "copyFrameLinkByName": false,
"disableDoubleClickTextEditing": false, "disableDoubleClickTextEditing": false,
"phoneFooterSafeAreaPadding": false,
"folder": "excalidraw", "folder": "excalidraw",
"cropFolder": "excalidraw/cropped", "cropFolder": "excalidraw/cropped",
"annotateFolder": "excalidraw/annotations", "annotateFolder": "excalidraw/annotations",
@@ -29,8 +30,10 @@
"annotateSuffix": "", "annotateSuffix": "",
"annotatePrefix": "annotated_", "annotatePrefix": "annotated_",
"annotatePreserveSize": false, "annotatePreserveSize": false,
"displaySVGInPreview": false,
"previewImageType": "SVGIMG", "previewImageType": "SVGIMG",
"renderingConcurrency": 3, "renderingConcurrency": 3,
"imageCacheRetentionDays": 30,
"allowImageCache": true, "allowImageCache": true,
"allowImageCacheInScene": true, "allowImageCacheInScene": true,
"displayExportedImageIfAvailable": false, "displayExportedImageIfAvailable": false,
@@ -107,13 +110,14 @@
"addDummyTextElement": false, "addDummyTextElement": false,
"zoteroCompatibility": false, "zoteroCompatibility": false,
"fieldSuggester": true, "fieldSuggester": true,
"enableOnloadScripts": false,
"compatibilityMode": false, "compatibilityMode": false,
"drawingOpenCount": 0, "drawingOpenCount": 0,
"library": "deprecated", "library": "deprecated",
"library2": { "library2": {
"type": "excalidrawlib", "type": "excalidrawlib",
"version": 2, "version": 2,
"source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.20.6", "source": "https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/2.22.3",
"libraryItems": [] "libraryItems": []
}, },
"imageElementNotice": true, "imageElementNotice": true,
@@ -126,6 +130,7 @@
"scriptEngineSettings": {}, "scriptEngineSettings": {},
"previousRelease": "2.20.6", "previousRelease": "2.20.6",
"showReleaseNotes": false, "showReleaseNotes": false,
"excalidrawMasteryPromoCollapsed": false,
"compareManifestToPluginVersion": true, "compareManifestToPluginVersion": true,
"showNewVersionNotification": true, "showNewVersionNotification": true,
"latexBoilerplate": "\\color{blue}", "latexBoilerplate": "\\color{blue}",
@@ -482,15 +487,178 @@
"canvasImmersiveEmbed": true, "canvasImmersiveEmbed": true,
"startupScriptPath": "", "startupScriptPath": "",
"aiEnabled": true, "aiEnabled": true,
"openAIAPIToken": "sk-JynMarAEOuLlYb3_bdQAdHEhKp-vK9az-uQXGlw_VSKQIEHnCb86PeqtxLQ", "aiVerboseLogging": false,
"openAIDefaultTextModel": "gpt-5.2", "aiProviderProfiles": {
"openAIDefaultTextModelMaxTokens": 0, "OpenAI": {
"openAIDefaultVisionModel": "gemini-3.1-pro-preview", "provider": "openai",
"openAIDefaultImageGenerationModel": "gemini-3.1-pro-preview", "apiKey": "",
"openAIURL": "https://api.ai-wave.org/v1/chat/completions", "baseURL": "https://api.openai.com/v1"
"openAIImageGenerationURL": "https://api.openai.com/v1/images/generations", },
"openAIImageEditsURL": "https://api.openai.com/v1/images/edits", "Anthropic": {
"openAIImageVariationURL": "https://api.openai.com/v1/images/variations", "provider": "anthropic",
"apiKey": "",
"baseURL": "https://api.anthropic.com/v1"
},
"Google Gemini": {
"provider": "google",
"apiKey": "",
"baseURL": "https://generativelanguage.googleapis.com/v1beta"
},
"xAI": {
"provider": "xai",
"apiKey": "",
"baseURL": "https://api.x.ai/v1"
},
"OpenAI-compatible": {
"provider": "openai-compatible",
"apiKey": "",
"baseURL": "https://api.openai.com/v1"
}
},
"aiTextModelConfigs": {
"gpt-5-mini": {
"providerId": "OpenAI",
"model": "gpt-5-mini",
"endpoint": "",
"multimodalSupport": true
},
"claude-sonnet-4-5": {
"providerId": "Anthropic",
"model": "claude-sonnet-4-5",
"endpoint": "",
"multimodalSupport": true
},
"gemini-2.5-pro": {
"providerId": "Google Gemini",
"model": "gemini-2.5-pro",
"endpoint": "",
"multimodalSupport": true
},
"grok-4-fast": {
"providerId": "xAI",
"model": "grok-4-fast",
"endpoint": "",
"multimodalSupport": true
}
},
"aiImageModelConfigs": {
"dall-e-2": {
"providerId": "OpenAI",
"model": "dall-e-2",
"supportedSizes": [
"256x256",
"512x512",
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": true
},
"dall-e-3": {
"providerId": "OpenAI",
"model": "dall-e-3",
"supportedSizes": [
"1024x1024",
"1792x1024",
"1024x1792"
],
"supportsPromptImageTransforms": false,
"supportsMaskImageEdits": false
},
"gpt-image-1": {
"providerId": "OpenAI",
"model": "gpt-image-1",
"supportedSizes": [
"1024x1024",
"1536x1024",
"1024x1536"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": true
},
"gpt-image-1-mini": {
"providerId": "OpenAI",
"model": "gpt-image-1-mini",
"supportedSizes": [
"1024x1024",
"1536x1024",
"1024x1536"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": true
},
"gpt-image-1.5": {
"providerId": "OpenAI",
"model": "gpt-image-1.5",
"supportedSizes": [
"1024x1024",
"1536x1024",
"1024x1536"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": true
},
"gpt-image-2": {
"providerId": "OpenAI",
"model": "gpt-image-2",
"supportedSizes": [
"1024x1024",
"1536x1024",
"1024x1536",
"2048x2048"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": true
},
"gemini-2.5-flash-image": {
"providerId": "Google Gemini",
"model": "gemini-2.5-flash-image",
"supportedSizes": [
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": false
},
"gemini-3.1-flash-image-preview": {
"providerId": "Google Gemini",
"model": "gemini-3.1-flash-image-preview",
"supportedSizes": [
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": false
},
"gemini-3-pro-image-preview": {
"providerId": "Google Gemini",
"model": "gemini-3-pro-image-preview",
"supportedSizes": [
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": false
},
"grok-imagine-image-quality": {
"providerId": "xAI",
"model": "grok-imagine-image-quality",
"supportedSizes": [
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": false
},
"grok-imagine-image-pro": {
"providerId": "xAI",
"model": "grok-imagine-image-pro",
"supportedSizes": [
"1024x1024"
],
"supportsPromptImageTransforms": true,
"supportsMaskImageEdits": false
}
},
"aiDefaultTextModel": "gpt-5-mini",
"aiDefaultImageGenerationModel": "gpt-image-1",
"aiDefaultMaxOutgoingTokens": 0,
"aiDefaultMaxResponseTokens": 4096,
"modifierKeyConfig": { "modifierKeyConfig": {
"Mac": { "Mac": {
"LocalFileDragAction": { "LocalFileDragAction": {
@@ -780,7 +948,6 @@
"longPressDesktop": 500, "longPressDesktop": 500,
"longPressMobile": 500, "longPressMobile": 500,
"doubleClickLinkOpenViewMode": true, "doubleClickLinkOpenViewMode": true,
"isDebugMode": false,
"rank": "Bronze", "rank": "Bronze",
"modifierKeyOverrides": [ "modifierKeyOverrides": [
{ {
@@ -813,6 +980,7 @@
"margin": "normal" "margin": "normal"
}, },
"disableContextMenu": false, "disableContextMenu": false,
"isDebugMode": false,
"defaultTrayMode": true, "defaultTrayMode": true,
"compactModeOnTablets": true "compactModeOnTablets": true
} }
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -1,9 +1,9 @@
{ {
"id": "obsidian-excalidraw-plugin", "id": "obsidian-excalidraw-plugin",
"name": "Excalidraw", "name": "Excalidraw",
"version": "2.21.0", "version": "2.23.3",
"minAppVersion": "1.5.7", "minAppVersion": "1.5.7",
"description": "Sketch Your Mind. An Obsidian plugin to edit and view Excalidraw drawings. Enter the world of 4D Visual PKM.", "description": "Sketch Your Mind. Edit and view Excalidraw drawings. Enter the world of 4D Visual PKM.",
"author": "Zsolt Viczian", "author": "Zsolt Viczian",
"authorUrl": "https://excalidraw-obsidian.online", "authorUrl": "https://excalidraw-obsidian.online",
"fundingUrl": "https://ko-fi.com/zsolt", "fundingUrl": "https://ko-fi.com/zsolt",
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
{
"imageNamePattern": "image-{{DATE:YYYYMMDDHHmmssSSS}}",
"dupNumberAtStart": false,
"dupNumberDelimiter": "-",
"dupNumberAlways": false,
"autoRename": true,
"handleAllAttachments": false,
"excludeExtensionPattern": "",
"disableRenameNotice": true
}
-942
View File
@@ -1,942 +0,0 @@
/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD */
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
// package.json
var require_package = __commonJS({
"package.json"(exports, module2) {
module2.exports = {
name: "obsidian-paste-image-rename",
version: "1.6.1",
main: "main.js",
scripts: {
start: "node esbuild.config.mjs",
build: "tsc -noEmit -skipLibCheck && BUILD_ENV=production node esbuild.config.mjs && cp manifest.json build",
version: "node version-bump.mjs && git add manifest.json versions.json",
release: "npm run build && gh release create ${npm_package_version} build/*"
},
keywords: [],
author: "Reorx",
license: "MIT",
devDependencies: {
"@types/node": "^18.11.18",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"builtin-modules": "^3.3.0",
esbuild: "0.16.17",
obsidian: "^1.1.1",
tslib: "2.5.0",
typescript: "4.9.4"
},
dependencies: {
"cash-dom": "^8.1.2"
}
};
}
});
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => PasteImageRenamePlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian2 = require("obsidian");
// src/batch.ts
var import_obsidian = require("obsidian");
// src/utils.ts
var DEBUG = false;
if (DEBUG)
console.log("DEBUG is enabled");
function debugLog(...args) {
if (DEBUG) {
console.log(new Date().toISOString().slice(11, 23), ...args);
}
}
function createElementTree(rootEl, opts) {
const result = {
el: rootEl.createEl(opts.tag, opts),
children: []
};
const children = opts.children || [];
for (const child of children) {
result.children.push(createElementTree(result.el, child));
}
return result;
}
var path = {
// Credit: @creationix/path.js
join(...partSegments) {
let parts = [];
for (let i = 0, l = partSegments.length; i < l; i++) {
parts = parts.concat(partSegments[i].split("/"));
}
const newParts = [];
for (let i = 0, l = parts.length; i < l; i++) {
const part = parts[i];
if (!part || part === ".")
continue;
else
newParts.push(part);
}
if (parts[0] === "")
newParts.unshift("");
return newParts.join("/");
},
// returns the last part of a path, e.g. 'foo.jpg'
basename(fullpath) {
const sp = fullpath.split("/");
return sp[sp.length - 1];
},
// return extension without dot, e.g. 'jpg'
extension(fullpath) {
const positions = [...fullpath.matchAll(new RegExp("\\.", "gi"))].map((a) => a.index);
return fullpath.slice(positions[positions.length - 1] + 1);
}
};
var filenameNotAllowedChars = /[^\p{L}0-9~`!@$&*()\-_=+{};'",<.>? ]/ug;
var sanitizer = {
filename(s) {
return s.replace(filenameNotAllowedChars, "").trim();
},
delimiter(s) {
s = this.filename(s);
if (!s)
s = "-";
return s;
}
};
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function lockInputMethodComposition(el) {
const state = {
lock: false
};
el.addEventListener("compositionstart", () => {
state.lock = true;
});
el.addEventListener("compositionend", () => {
state.lock = false;
});
return state;
}
// src/batch.ts
var ImageBatchRenameModal = class extends import_obsidian.Modal {
constructor(app, activeFile, renameFunc, onClose) {
super(app);
this.activeFile = activeFile;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
this.state = {
namePattern: "",
extPattern: "",
nameReplace: "",
renameTasks: []
};
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Batch rename embeded files");
const namePatternSetting = new import_obsidian.Setting(contentEl).setName("Name pattern").setDesc("Please input the name pattern to match files (regex)").addText((text) => text.setValue(this.state.namePattern).onChange(
(value) => __async(this, null, function* () {
this.state.namePattern = value;
})
));
const npInputEl = namePatternSetting.controlEl.children[0];
npInputEl.focus();
const npInputState = lockInputMethodComposition(npInputEl);
npInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !npInputState.lock) {
e.preventDefault();
if (!this.state.namePattern) {
errorEl.innerText = 'Error: "Name pattern" could not be empty';
errorEl.style.display = "block";
return;
}
this.matchImageNames(tbodyEl);
}
}));
const extPatternSetting = new import_obsidian.Setting(contentEl).setName("Extension pattern").setDesc("Please input the extension pattern to match files (regex)").addText((text) => text.setValue(this.state.extPattern).onChange(
(value) => __async(this, null, function* () {
this.state.extPattern = value;
})
));
const extInputEl = extPatternSetting.controlEl.children[0];
extInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter") {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const nameReplaceSetting = new import_obsidian.Setting(contentEl).setName("Name replace").setDesc("Please input the string to replace the matched name (use $1, $2 for regex groups)").addText((text) => text.setValue(this.state.nameReplace).onChange(
(value) => __async(this, null, function* () {
this.state.nameReplace = value;
})
));
const nrInputEl = nameReplaceSetting.controlEl.children[0];
const nrInputState = lockInputMethodComposition(nrInputEl);
nrInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nrInputState.lock) {
e.preventDefault();
this.matchImageNames(tbodyEl);
}
}));
const matchedContainer = contentEl.createDiv({
cls: "matched-container"
});
const tableET = createElementTree(matchedContainer, {
tag: "table",
children: [
{
tag: "thead",
children: [
{
tag: "tr",
children: [
{
tag: "td",
text: "Original path"
},
{
tag: "td",
text: "Renamed Name"
}
]
}
]
},
{
tag: "tbody"
}
]
});
const tbodyEl = tableET.children[1].el;
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename all").setClass("mod-cta").onClick(() => {
new ConfirmModal(
this.app,
"Confirm rename all",
`Are you sure? This will rename all the ${this.state.renameTasks.length} images matched the pattern.`,
() => {
this.renameAll();
this.close();
}
).open();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
renameAll() {
return __async(this, null, function* () {
debugLog("renameAll", this.state);
for (const task of this.state.renameTasks) {
yield this.renameFunc(task.file, task.name);
}
});
}
matchImageNames(tbodyEl) {
const { state } = this;
const renameTasks = [];
tbodyEl.empty();
const fileCache = this.app.metadataCache.getFileCache(this.activeFile);
if (!fileCache || !fileCache.embeds)
return;
const namePatternRegex = new RegExp(state.namePattern, "g");
const extPatternRegex = new RegExp(state.extPattern);
fileCache.embeds.forEach((embed) => {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, this.activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
if (state.extPattern) {
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
}
const stem = file.basename;
namePatternRegex.lastIndex = 0;
const m1 = namePatternRegex.exec(stem);
if (!m1)
return;
let renamedName = file.name;
if (state.nameReplace) {
namePatternRegex.lastIndex = 0;
renamedName = stem.replace(namePatternRegex, state.nameReplace);
renamedName = `${renamedName}.${file.extension}`;
}
renameTasks.push({
file,
name: renamedName
});
createElementTree(tbodyEl, {
tag: "tr",
children: [
{
tag: "td",
children: [
{
tag: "span",
text: file.name
},
{
tag: "div",
text: file.path,
attr: {
class: "file-path"
}
}
]
},
{
tag: "td",
children: [
{
tag: "span",
text: renamedName
},
{
tag: "div",
text: path.join(file.parent.path, renamedName),
attr: {
class: "file-path"
}
}
]
}
]
});
});
debugLog("new renameTasks", renameTasks);
state.renameTasks = renameTasks;
}
};
var ConfirmModal = class extends import_obsidian.Modal {
constructor(app, title, message, onConfirm) {
super(app);
this.title = title;
this.message = message;
this.onConfirm = onConfirm;
}
onOpen() {
const { contentEl, titleEl } = this;
titleEl.setText(this.title);
contentEl.createEl("p", {
text: this.message
});
new import_obsidian.Setting(contentEl).addButton((button) => {
button.setButtonText("Yes").setClass("mod-warning").onClick(() => {
this.onConfirm();
this.close();
});
}).addButton((button) => {
button.setButtonText("No").onClick(() => {
this.close();
});
});
}
};
// src/template.ts
var dateTmplRegex = /{{DATE:([^}]+)}}/gm;
var frontmatterTmplRegex = /{{frontmatter:([^}]+)}}/gm;
var replaceDateVar = (s, date) => {
const m = dateTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], date.format(m[1]));
};
var replaceFrontmatterVar = (s, frontmatter) => {
if (!frontmatter)
return s;
const m = frontmatterTmplRegex.exec(s);
if (!m)
return s;
return s.replace(m[0], frontmatter[m[1]] || "");
};
var renderTemplate = (tmpl, data, frontmatter) => {
const now = window.moment();
let text = tmpl;
let newtext;
while ((newtext = replaceDateVar(text, now)) != text) {
text = newtext;
}
while ((newtext = replaceFrontmatterVar(text, frontmatter)) != text) {
text = newtext;
}
text = text.replace(/{{imageNameKey}}/gm, data.imageNameKey).replace(/{{fileName}}/gm, data.fileName).replace(/{{dirName}}/gm, data.dirName).replace(/{{firstHeading}}/gm, data.firstHeading);
return text;
};
// src/main.ts
var DEFAULT_SETTINGS = {
imageNamePattern: "{{fileName}}",
dupNumberAtStart: false,
dupNumberDelimiter: "-",
dupNumberAlways: false,
autoRename: false,
handleAllAttachments: false,
excludeExtensionPattern: "",
disableRenameNotice: false
};
var PASTED_IMAGE_PREFIX = "Pasted image ";
var PasteImageRenamePlugin = class extends import_obsidian2.Plugin {
constructor() {
super(...arguments);
this.modals = [];
}
onload() {
return __async(this, null, function* () {
const pkg = require_package();
console.log(`Plugin loading: ${pkg.name} ${pkg.version} BUILD_ENV=${"production"}`);
yield this.loadSettings();
this.registerEvent(
this.app.vault.on("create", (file) => {
if (!(file instanceof import_obsidian2.TFile))
return;
const timeGapMs = new Date().getTime() - file.stat.ctime;
if (timeGapMs > 1e3)
return;
if (isMarkdownFile(file))
return;
if (isPastedImage(file)) {
debugLog("pasted image created", file);
this.startRenameProcess(file, this.settings.autoRename);
} else {
if (this.settings.handleAllAttachments) {
debugLog("handleAllAttachments for file", file);
if (this.testExcludeExtension(file)) {
debugLog("excluded file by ext", file);
return;
}
this.startRenameProcess(file, this.settings.autoRename);
}
}
})
);
const startBatchRenameProcess = () => {
this.openBatchRenameModal();
};
this.addCommand({
id: "batch-rename-embeded-files",
name: "Batch rename embeded files (in the current file)",
callback: startBatchRenameProcess
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename embeded files", startBatchRenameProcess);
}
const batchRenameAllImages = () => {
this.batchRenameAllImages();
};
this.addCommand({
id: "batch-rename-all-images",
name: "Batch rename all images instantly (in the current file)",
callback: batchRenameAllImages
});
if (DEBUG) {
this.addRibbonIcon("wand-glyph", "Batch rename all images instantly (in the current file)", batchRenameAllImages);
}
this.addSettingTab(new SettingTab(this.app, this));
});
}
startRenameProcess(file, autoRename = false) {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
if (!activeFile) {
new import_obsidian2.Notice("Error: No active file found.");
return;
}
const { stem, newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful || !autoRename) {
this.openRenameModal(file, isMeaningful ? stem : "", activeFile.path);
return;
}
this.renameFile(file, newName, activeFile.path, true);
});
}
renameFile(file, inputNewName, sourcePath, replaceCurrentLine) {
return __async(this, null, function* () {
const { name: newName } = yield this.deduplicateNewName(inputNewName, file);
debugLog("deduplicated newName:", newName);
const originName = file.name;
const linkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
const newPath = path.join(file.parent.path, newName);
try {
yield this.app.fileManager.renameFile(file, newPath);
} catch (err) {
new import_obsidian2.Notice(`Failed to rename ${newName}: ${err}`);
throw err;
}
if (!replaceCurrentLine) {
return;
}
const newLinkText = this.app.fileManager.generateMarkdownLink(file, sourcePath);
debugLog("replace text", linkText, newLinkText);
const editor = this.getActiveEditor();
if (!editor) {
new import_obsidian2.Notice(`Failed to rename ${newName}: no active editor`);
return;
}
const cursor = editor.getCursor();
const line = editor.getLine(cursor.line);
const replacedLine = line.replace(linkText, newLinkText);
debugLog("current line -> replaced line", line, replacedLine);
editor.transaction({
changes: [
{
from: __spreadProps(__spreadValues({}, cursor), { ch: 0 }),
to: __spreadProps(__spreadValues({}, cursor), { ch: line.length }),
text: replacedLine
}
]
});
if (!this.settings.disableRenameNotice) {
new import_obsidian2.Notice(`Renamed ${originName} to ${newName}`);
}
});
}
openRenameModal(file, newName, sourcePath) {
const modal = new ImageRenameModal(
this.app,
file,
newName,
(confirmedName) => {
debugLog("confirmedName:", confirmedName);
this.renameFile(file, confirmedName, sourcePath, true);
},
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
debugLog("modals count", this.modals.length);
}
openBatchRenameModal() {
const activeFile = this.getActiveFile();
const modal = new ImageBatchRenameModal(
this.app,
activeFile,
(file, name) => __async(this, null, function* () {
yield this.renameFile(file, name, activeFile.path);
}),
() => {
this.modals.splice(this.modals.indexOf(modal), 1);
}
);
this.modals.push(modal);
modal.open();
}
batchRenameAllImages() {
return __async(this, null, function* () {
const activeFile = this.getActiveFile();
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (!fileCache || !fileCache.embeds)
return;
const extPatternRegex = /jpe?g|png|gif|tiff|webp/i;
for (const embed of fileCache.embeds) {
const file = this.app.metadataCache.getFirstLinkpathDest(embed.link, activeFile.path);
if (!file) {
console.warn("file not found", embed.link);
return;
}
const m0 = extPatternRegex.exec(file.extension);
if (!m0)
return;
const { newName, isMeaningful } = this.generateNewName(file, activeFile);
debugLog("generated newName:", newName, isMeaningful);
if (!isMeaningful) {
new import_obsidian2.Notice("Failed to batch rename images: the generated name is not meaningful");
break;
}
yield this.renameFile(file, newName, activeFile.path, false);
}
});
}
// returns a new name for the input file, with extension
generateNewName(file, activeFile) {
let imageNameKey = "";
let firstHeading = "";
let frontmatter;
const fileCache = this.app.metadataCache.getFileCache(activeFile);
if (fileCache) {
debugLog("frontmatter", fileCache.frontmatter);
frontmatter = fileCache.frontmatter;
imageNameKey = (frontmatter == null ? void 0 : frontmatter.imageNameKey) || "";
firstHeading = getFirstHeading(fileCache.headings);
} else {
console.warn("could not get file cache from active file", activeFile.name);
}
const stem = renderTemplate(
this.settings.imageNamePattern,
{
imageNameKey,
fileName: activeFile.basename,
dirName: activeFile.parent.name,
firstHeading
},
frontmatter
);
const meaninglessRegex = new RegExp(`[${this.settings.dupNumberDelimiter}\\s]`, "gm");
return {
stem,
newName: stem + "." + file.extension,
isMeaningful: stem.replace(meaninglessRegex, "") !== ""
};
}
// newName: foo.ext
deduplicateNewName(newName, file) {
return __async(this, null, function* () {
const dir = file.parent.path;
const listed = yield this.app.vault.adapter.list(dir);
debugLog("sibling files", listed);
const newNameExt = path.extension(newName), newNameStem = newName.slice(0, newName.length - newNameExt.length - 1), newNameStemEscaped = escapeRegExp(newNameStem), delimiter = this.settings.dupNumberDelimiter, delimiterEscaped = escapeRegExp(delimiter);
let dupNameRegex;
if (this.settings.dupNumberAtStart) {
dupNameRegex = new RegExp(
`^(?<number>\\d+)${delimiterEscaped}(?<name>${newNameStemEscaped})\\.${newNameExt}$`
);
} else {
dupNameRegex = new RegExp(
`^(?<name>${newNameStemEscaped})${delimiterEscaped}(?<number>\\d+)\\.${newNameExt}$`
);
}
debugLog("dupNameRegex", dupNameRegex);
const dupNameNumbers = [];
let isNewNameExist = false;
for (let sibling of listed.files) {
sibling = path.basename(sibling);
if (sibling == newName) {
isNewNameExist = true;
continue;
}
const m = dupNameRegex.exec(sibling);
if (!m)
continue;
dupNameNumbers.push(parseInt(m.groups.number));
}
if (isNewNameExist || this.settings.dupNumberAlways) {
const newNumber = dupNameNumbers.length > 0 ? Math.max(...dupNameNumbers) + 1 : 1;
if (this.settings.dupNumberAtStart) {
newName = `${newNumber}${delimiter}${newNameStem}.${newNameExt}`;
} else {
newName = `${newNameStem}${delimiter}${newNumber}.${newNameExt}`;
}
}
return {
name: newName,
stem: newName.slice(0, newName.length - newNameExt.length - 1),
extension: newNameExt
};
});
}
getActiveFile() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
const file = view == null ? void 0 : view.file;
debugLog("active file", file == null ? void 0 : file.path);
return file;
}
getActiveEditor() {
const view = this.app.workspace.getActiveViewOfType(import_obsidian2.MarkdownView);
return view == null ? void 0 : view.editor;
}
onunload() {
this.modals.map((modal) => modal.close());
}
testExcludeExtension(file) {
const pattern = this.settings.excludeExtensionPattern;
if (!pattern)
return false;
return new RegExp(pattern).test(file.extension);
}
loadSettings() {
return __async(this, null, function* () {
this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData());
});
}
saveSettings() {
return __async(this, null, function* () {
yield this.saveData(this.settings);
});
}
};
function getFirstHeading(headings) {
if (headings && headings.length > 0) {
for (const heading of headings) {
if (heading.level === 1) {
return heading.heading;
}
}
}
return "";
}
function isPastedImage(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.name.startsWith(PASTED_IMAGE_PREFIX)) {
return true;
}
}
return false;
}
function isMarkdownFile(file) {
if (file instanceof import_obsidian2.TFile) {
if (file.extension === "md") {
return true;
}
}
return false;
}
var ImageRenameModal = class extends import_obsidian2.Modal {
constructor(app, src, stem, renameFunc, onClose) {
super(app);
this.src = src;
this.stem = stem;
this.renameFunc = renameFunc;
this.onCloseExtra = onClose;
}
onOpen() {
this.containerEl.addClass("image-rename-modal");
const { contentEl, titleEl } = this;
titleEl.setText("Rename image");
const imageContainer = contentEl.createDiv({
cls: "image-container"
});
imageContainer.createEl("img", {
attr: {
src: this.app.vault.getResourcePath(this.src)
}
});
let stem = this.stem;
const ext = this.src.extension;
const getNewName = (stem2) => stem2 + "." + ext;
const getNewPath = (stem2) => path.join(this.src.parent.path, getNewName(stem2));
const infoET = createElementTree(contentEl, {
tag: "ul",
cls: "info",
children: [
{
tag: "li",
children: [
{
tag: "span",
text: "Origin path"
},
{
tag: "span",
text: this.src.path
}
]
},
{
tag: "li",
children: [
{
tag: "span",
text: "New path"
},
{
tag: "span",
text: getNewPath(stem)
}
]
}
]
});
const doRename = () => __async(this, null, function* () {
debugLog("doRename", `stem=${stem}`);
this.renameFunc(getNewName(stem));
});
const nameSetting = new import_obsidian2.Setting(contentEl).setName("New name").setDesc("Please input the new name for the image (without extension)").addText((text) => text.setValue(stem).onChange(
(value) => __async(this, null, function* () {
stem = sanitizer.filename(value);
infoET.children[1].children[1].el.innerText = getNewPath(stem);
})
));
const nameInputEl = nameSetting.controlEl.children[0];
nameInputEl.focus();
const nameInputState = lockInputMethodComposition(nameInputEl);
nameInputEl.addEventListener("keydown", (e) => __async(this, null, function* () {
if (e.key === "Enter" && !nameInputState.lock) {
e.preventDefault();
if (!stem) {
errorEl.innerText = 'Error: "New name" could not be empty';
errorEl.style.display = "block";
return;
}
doRename();
this.close();
}
}));
const errorEl = contentEl.createDiv({
cls: "error",
attr: {
style: "display: none;"
}
});
new import_obsidian2.Setting(contentEl).addButton((button) => {
button.setButtonText("Rename").onClick(() => {
doRename();
this.close();
});
}).addButton((button) => {
button.setButtonText("Cancel").onClick(() => {
this.close();
});
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
this.onCloseExtra();
}
};
var imageNamePatternDesc = `
The pattern indicates how the new name should be generated.
Available variables:
- {{fileName}}: name of the active file, without ".md" extension.
- {{imageNameKey}}: this variable is read from the markdown file's frontmatter, from the same key "imageNameKey".
- {{DATE:$FORMAT}}: use "$FORMAT" to format the current date, "$FORMAT" must be a Moment.js format string, e.g. {{DATE:YYYY-MM-DD}}.
Here are some examples from pattern to image names (repeat in sequence), variables: fileName = "My note", imageNameKey = "foo":
- {{fileName}}: My note, My note-1, My note-2
- {{imageNameKey}}: foo, foo-1, foo-2
- {{imageNameKey}}-{{DATE:YYYYMMDD}}: foo-20220408, foo-20220408-1, foo-20220408-2
`;
var SettingTab = class extends import_obsidian2.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
new import_obsidian2.Setting(containerEl).setName("Image name pattern").setDesc(imageNamePatternDesc).setClass("long-description-setting-item").addText((text) => text.setPlaceholder("{{imageNameKey}}").setValue(this.plugin.settings.imageNamePattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.imageNamePattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number at start (or end)").setDesc(`If enabled, duplicate number will be added at the start as prefix for the image name, otherwise it will be added at the end as suffix for the image name.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAtStart).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAtStart = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Duplicate number delimiter").setDesc(`The delimiter to generate the number prefix/suffix for duplicated names. For example, if the value is "-", the suffix will be like "-1", "-2", "-3", and the prefix will be like "1-", "2-", "3-". Only characters that are valid in file names are allowed.`).addText((text) => text.setValue(this.plugin.settings.dupNumberDelimiter).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberDelimiter = sanitizer.delimiter(value);
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Always add duplicate number").setDesc(`If enabled, duplicate number will always be added to the image name. Otherwise, it will only be added when the name is duplicated.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.dupNumberAlways).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.dupNumberAlways = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Auto rename").setDesc(`By default, the rename modal will always be shown to confirm before renaming, if this option is set, the image will be auto renamed after pasting.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.autoRename).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.autoRename = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Handle all attachments").setDesc(`By default, the plugin only handles images that starts with "Pasted image " in name,
which is the prefix Obsidian uses to create images from pasted content.
If this option is set, the plugin will handle all attachments that are created in the vault.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.handleAllAttachments).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.handleAllAttachments = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Exclude extension pattern").setDesc(`This option is only useful when "Handle all attachments" is enabled.
Write a Regex pattern to exclude certain extensions from being handled. Only the first line will be used.`).setClass("single-line-textarea").addTextArea((text) => text.setPlaceholder("docx?|xlsx?|pptx?|zip|rar").setValue(this.plugin.settings.excludeExtensionPattern).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.excludeExtensionPattern = value;
yield this.plugin.saveSettings();
})
));
new import_obsidian2.Setting(containerEl).setName("Disable rename notice").setDesc(`Turn off this option if you don't want to see the notice when renaming images.
Note that Obsidian may display a notice when a link has changed, this option cannot disable that.`).addToggle((toggle) => toggle.setValue(this.plugin.settings.disableRenameNotice).onChange(
(value) => __async(this, null, function* () {
this.plugin.settings.disableRenameNotice = value;
yield this.plugin.saveSettings();
})
));
}
};
@@ -1,10 +0,0 @@
{
"id": "obsidian-paste-image-rename",
"name": "Paste image rename",
"version": "1.6.1",
"minAppVersion": "0.12.0",
"description": "Rename pasted images and all the other attchments added to the vault",
"author": "Reorx",
"authorUrl": "https://github.com/reorx",
"isDesktopOnly": false
}
@@ -1,79 +0,0 @@
/* src/styles.css */
:root {
--shadow-color: 0deg 0% 0%;
--shadow-elevation-medium:
0.5px 0.5px 0.7px hsl(var(--shadow-color) / 0.14),
1.1px 1.1px 1.5px -0.9px hsl(var(--shadow-color) / 0.12),
2.4px 2.5px 3.3px -1.8px hsl(var(--shadow-color) / 0.1),
5.3px 5.6px 7.3px -2.7px hsl(var(--shadow-color) / 0.09),
11px 11.4px 15.1px -3.6px hsl(var(--shadow-color) / 0.07);
}
.image-rename-modal .modal {
width: 65%;
min-width: 600px;
}
.image-rename-modal .modal-content {
padding: 10px 5px;
}
.image-rename-modal .image-container {
display: flex;
justify-content: center;
}
.image-rename-modal .info {
padding: 10px 0;
color: var(--text-muted);
user-select: text;
}
.image-rename-modal .info li > span:nth-of-type(1) {
display: inline-block;
width: 6em;
margin-right: .5em;
}
.image-rename-modal .info li > span:nth-of-type(1):after {
content: ":";
float: right;
}
.image-rename-modal .image-container img {
display: block;
max-height: 300px;
box-shadow: var(--shadow-elevation-medium);
}
.image-rename-modal .setting-item-control input {
min-width: 300px;
}
.image-rename-modal .error {
border: 1px solid rgb(201, 90, 90);
color: rgb(134, 22, 22);
padding: 10px;
}
.image-rename-modal table {
font-size: .9em;
line-height: 1.8;
margin-bottom: 1.5em;
user-select: text;
}
.image-rename-modal table td {
padding-right: 1em;
}
.image-rename-modal table thead td {
font-weight: 700;
}
.image-rename-modal table tbody td .file-path {
font-size: .8em;
color: var(--text-faint);
line-height: 1;
}
.long-description-setting-item {
flex-wrap: wrap;
}
.long-description-setting-item .setting-item-description {
white-space: pre-wrap;
line-height: 1.3em;
}
.long-description-setting-item .setting-item-control {
padding-top: 10px;
}
.long-description-setting-item .setting-item-control input {
min-width: 300px;
width: 50%;
}
+89 -83
View File
@@ -1,140 +1,116 @@
{ {
"choices": [ "choices": [
{ {
"id": "0462f0ba-4349-4531-b1a0-6634e5fc146f", "id": "c31732d3-4333-4aa1-9963-4f2ee5580251",
"name": "日常记录-闪念笔记", "name": "日常记录-闪念笔记",
"type": "Template", "type": "Template",
"command": false, "command": false,
"templatePath": "attachment/templates/时间戳笔记.md", "templatePath": "attachment/templates/日常记录-闪念笔记.md",
"fileNameFormat": { "fileNameFormat": {
"enabled": true, "enabled": true,
"format": "{{DATE:YYYYMMDDHHmmSS}}" "format": "{{DATE:MMDDHHmmss}}"
}, },
"folder": { "folder": {
"enabled": true, "enabled": false,
"folders": [ "folders": [],
"000-Inbox"
],
"chooseWhenCreatingNote": false, "chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false, "createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false "chooseFromSubfolders": false
}, },
"appendLink": false, "appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true, "openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false,
"fileOpening": {
"location": "tab",
"direction": "vertical",
"focus": true,
"mode": "default"
}
},
{
"id": "eccc9792-8b8b-42bd-a20d-8c061ece98cf",
"name": "移动杭研-问题记录",
"type": "Template",
"command": false,
"templatePath": "attachment/templates/移动杭研-问题记录.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:MMDD}}-{{value}}"
},
"folder": {
"enabled": true,
"folders": [
"000-Inbox"
],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false,
"fileOpening": { "fileOpening": {
"location": "split", "location": "split",
"direction": "vertical", "direction": "vertical",
"focus": true, "mode": "default",
"mode": "default" "focus": true
} },
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false
}, },
{ {
"id": "4ad23b51-d9fd-445e-aed8-f332e99f2350", "id": "d9111ada-faa6-4d0f-8260-9c3cb97eade8",
"name": "移动杭研-开发笔记", "name": "移动杭研-开发笔记",
"type": "Template", "type": "Template",
"command": false, "command": false,
"templatePath": "attachment/templates/移动杭研-开发笔记.md", "templatePath": "attachment/templates/移动杭研-开发笔记.md",
"fileNameFormat": { "fileNameFormat": {
"enabled": true, "enabled": true,
"format": "开发笔记" "format": "开发笔记 {{VALUE:version}}"
}, },
"folder": { "folder": {
"enabled": true, "enabled": false,
"folders": [ "folders": [],
"000-Inbox"
],
"chooseWhenCreatingNote": false, "chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false, "createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false "chooseFromSubfolders": false
}, },
"appendLink": false, "appendLink": false,
"openFileInNewTab": {
"enabled": true,
"direction": "vertical",
"focus": true
},
"openFile": true, "openFile": true,
"openFileInMode": "default",
"fileExistsMode": "Nothing",
"setFileExistsBehavior": true,
"fileOpening": { "fileOpening": {
"location": "split", "location": "split",
"direction": "vertical", "direction": "vertical",
"focus": true, "mode": "default",
"mode": "default" "focus": true
} },
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false
}, },
{ {
"id": "83f6f73d-a391-4096-b07f-a9626f10980e", "id": "9b9829e0-5cb5-4cc1-907c-7eb0626a2c3d",
"name": "openTerminal", "name": "移动杭研-问题记录",
"type": "Template",
"command": false,
"templatePath": "attachment/templates/移动杭研-问题记录.md",
"fileNameFormat": {
"enabled": true,
"format": "{{DATE:MMDD}}-{{VALUE:info|{{DATE:HHmmss}}}}"
},
"folder": {
"enabled": false,
"folders": [],
"chooseWhenCreatingNote": false,
"createInSameFolderAsActiveFile": false,
"chooseFromSubfolders": false
},
"appendLink": false,
"openFile": true,
"fileOpening": {
"location": "split",
"direction": "vertical",
"mode": "default",
"focus": true
},
"fileExistsMode": "Increment the file name",
"setFileExistsBehavior": false
},
{
"id": "170cca5b-3037-4b3b-9f1c-82235ecde717",
"name": "打开控制台",
"type": "Macro", "type": "Macro",
"command": false, "command": false,
"runOnStartup": false, "runOnStartup": false,
"macro": { "macro": {
"name": "openTerminal", "name": "打开控制台",
"id": "2417c310-3ca2-4a95-9962-761529241e84", "id": "244eb44a-c362-440c-a21e-8065a482ab68",
"commands": [ "commands": [
{ {
"name": "openTerminal", "name": "openTerminal",
"type": "UserScript", "type": "UserScript",
"id": "22516ce4-03d7-4f5b-b210-b15dd82340e5", "id": "2144335d-f088-4156-a81c-6fb4ab30bf55",
"path": "resource/工具/obsidian/scripts/openTerminal.js", "path": "attachment/scripts/openTerminal.js",
"settings": {} "settings": {}
} }
] ]
} }
} }
], ],
"inputPrompt": "multi-line", "inputPrompt": "single-line",
"persistInputPromptDrafts": true, "persistInputPromptDrafts": true,
"useSelectionAsCaptureValue": true, "useSelectionAsCaptureValue": true,
"devMode": false, "devMode": false,
"templateFolderPath": "attachment/templates", "templateFolderPath": "attachment/templates",
"announceUpdates": "all", "announceUpdates": "major",
"version": "2.12.0", "version": "2.12.0",
"globalVariables": {}, "globalVariables": {},
"onePageInputEnabled": false, "onePageInputEnabled": false,
@@ -190,11 +166,41 @@
"maxTokens": 128000 "maxTokens": 128000
}, },
{ {
"name": "text-davinci-003", "name": "gpt-4-turbo",
"maxTokens": 4096 "maxTokens": 128000
},
{
"name": "gpt-4o",
"maxTokens": 128000
},
{
"name": "gpt-4o-mini",
"maxTokens": 128000
} }
], ],
"modelSource": "auto" "autoSyncModels": false,
"modelSource": "modelsDev"
},
{
"name": "Gemini",
"endpoint": "https://generativelanguage.googleapis.com",
"apiKey": "",
"models": [
{
"name": "gemini-1.5-pro",
"maxTokens": 1000000
},
{
"name": "gemini-1.5-flash",
"maxTokens": 1000000
},
{
"name": "gemini-1.5-flash-8b",
"maxTokens": 1000000
}
],
"autoSyncModels": false,
"modelSource": "modelsDev"
} }
] ]
}, },
@@ -207,8 +213,8 @@
"addDefaultAIProviders": true, "addDefaultAIProviders": true,
"removeMacroIndirection": true, "removeMacroIndirection": true,
"migrateFileOpeningSettings": true, "migrateFileOpeningSettings": true,
"setProviderModelDiscoveryMode": true,
"backfillFileOpeningDefaults": true, "backfillFileOpeningDefaults": true,
"setProviderModelDiscoveryMode": true,
"migrateProviderApiKeysToSecretStorage": true "migrateProviderApiKeysToSecretStorage": true
} }
} }
+163 -164
View File
@@ -1,69 +1,65 @@
{ {
"recentFiles": [ "recentFiles": [
{
"basename": "tg-bot",
"path": "000-inbox/tg-bot.md"
},
{
"basename": "Redis 安装",
"path": "000-inbox/Redis 安装.md"
},
{
"basename": "杭州服务器-AI",
"path": "000-inbox/杭州服务器-AI.md"
},
{
"basename": "常用命令",
"path": "resource/常用命令.md"
},
{
"basename": "20260316213546",
"path": "000-Inbox/20260316213546.md"
},
{
"basename": "IBS 智能体具体落实技术方案",
"path": "work/移动杭研/AI 项目/IBS 智能体具体落实技术方案.md"
},
{
"basename": "20260316160264",
"path": "000-Inbox/20260316160264.md"
},
{
"basename": "20260316102936",
"path": "000-Inbox/20260316102936.md"
},
{
"basename": "20260316105203",
"path": "000-Inbox/20260316105203.md"
},
{
"basename": "0316-配管任务限流问题",
"path": "work/移动杭研/问题处理/2026-03/0316-配管任务限流问题.md"
},
{
"basename": "0316-配管任务限流问题",
"path": "000-Inbox/0316-配管任务限流问题.md"
},
{
"basename": "1107-试用单删除问题",
"path": "work/移动杭研/问题处理/2025-11/1107-试用单删除问题.md"
},
{
"basename": "20260313151563",
"path": "000-Inbox/20260313151563.md"
},
{ {
"basename": "Prompt 02 COSMIC 功能过程", "basename": "Prompt 02 COSMIC 功能过程",
"path": "resource/ai/prompts/cosmic/Prompt 02 COSMIC 功能过程.md" "path": "resource/ai/prompts/cosmic/Prompt 02 COSMIC 功能过程.md"
}, },
{ {
"basename": "Prompt 03 COSMIC 子过程", "basename": "开发笔记 7.21.0",
"path": "resource/ai/prompts/cosmic/Prompt 03 COSMIC 子过程.md" "path": "work/移动杭研/开发记录/7.21.0/开发笔记 7.21.0.md"
},
{
"basename": "05月18日 154321 华为、中兴、自研解封禁联调",
"path": "000-inbox/05月18日 154321 华为、中兴、自研解封禁联调.md"
}, },
{ {
"basename": "Prompt 01 COSMIC 需求扩写", "basename": "Prompt 01 COSMIC 需求扩写",
"path": "resource/ai/prompts/cosmic/Prompt 01 COSMIC 需求扩写.md" "path": "resource/ai/prompts/cosmic/Prompt 01 COSMIC 需求扩写.md"
}, },
{
"basename": "Prompt 00 COSMIC v1",
"path": "resource/ai/prompts/cosmic/Prompt 00 COSMIC v1.md"
},
{
"basename": "05月19日 154820",
"path": "000-inbox/05月19日 154820.md"
},
{
"basename": "开发笔记 7.20.0",
"path": "work/移动杭研/开发记录/7.20.0/开发笔记 7.20.0.md"
},
{
"basename": "服务器-美国",
"path": "personal/服务器-美国.md"
},
{
"basename": "服务器-香港",
"path": "personal/服务器-香港.md"
},
{
"basename": "试用业务-时长、流量到期",
"path": "work/移动杭研/业务梳理/业务工单/试用业务-时长、流量到期.md"
},
{
"basename": "ubuntu",
"path": "resource/系统/ubuntu.md"
},
{
"basename": "001 大模型启动流程",
"path": "resource/ai/大模型安装笔记/001 大模型启动流程.md"
},
{
"basename": "Hermes 命令 cli",
"path": "resource/ai/Hermes 命令 cli.md"
},
{
"basename": "Hermes",
"path": "resource/ai/Hermes.md"
},
{
"basename": "个人债务测算",
"path": "000-inbox/个人债务测算.md"
},
{ {
"basename": "Prompt 06 COSMIC 锐评", "basename": "Prompt 06 COSMIC 锐评",
"path": "resource/ai/prompts/cosmic/Prompt 06 COSMIC 锐评.md" "path": "resource/ai/prompts/cosmic/Prompt 06 COSMIC 锐评.md"
@@ -77,24 +73,124 @@
"path": "resource/ai/prompts/cosmic/Prompt 04 COSMIC PRD 文档.md" "path": "resource/ai/prompts/cosmic/Prompt 04 COSMIC PRD 文档.md"
}, },
{ {
"basename": "Prompt 00 COSMIC v1", "basename": "Prompt 03 COSMIC 子过程",
"path": "resource/ai/prompts/cosmic/Prompt 00 COSMIC v1.md" "path": "resource/ai/prompts/cosmic/Prompt 03 COSMIC 子过程.md"
}, },
{ {
"basename": "BUSI_REQUIREMENT_COSMIC", "basename": "数据库信息",
"path": "resource/ai/prompts/cosmic 业务版本/BUSI_REQUIREMENT_COSMIC.md" "path": "work/移动杭研/项目备忘/数据库信息.md"
}, },
{ {
"basename": "BUSI_REQUIREMENT_FLOWCHART", "basename": "Hermes 命令 slash",
"path": "resource/ai/prompts/cosmic 业务版本/BUSI_REQUIREMENT_FLOWCHART.md" "path": "resource/ai/Hermes 命令 slash.md"
}, },
{ {
"basename": "Prompt 高级彩虹屁", "basename": "数据库设计-监控系统",
"path": "resource/ai/prompts/Prompt 高级彩虹屁.md" "path": "excalidraw/工作相关/数据库设计-监控系统.md"
}, },
{ {
"basename": "Prompt 考勤数据规整助手 小杨", "basename": "VSCode 配置文件",
"path": "resource/ai/prompts/Prompt 考勤数据规整助手 小杨.md" "path": "resource/配置/VSCode 配置文件.md"
},
{
"basename": "vim 快捷键与操作手册",
"path": "resource/系统/vim 快捷键与操作手册.md"
},
{
"basename": "openpalm",
"path": "openpalm/openpalm.md"
},
{
"basename": "Claude Code 配置文件",
"path": "resource/配置/Claude Code 配置文件.md"
},
{
"basename": "Codex 配置文件",
"path": "resource/配置/Codex 配置文件.md"
},
{
"basename": "ibs-ai-mcp",
"path": "000-inbox/ibs-ai-mcp.md"
},
{
"basename": "gitleaks.toml",
"path": "resource/工具/gitleaks.toml.md"
},
{
"basename": "ibs-export",
"path": "work/移动杭研/业务梳理/ibs-export.md"
},
{
"basename": "常用命令",
"path": "resource/常用命令.md"
},
{
"basename": "s-ui",
"path": "resource/系统/网络/s-ui.md"
},
{
"basename": "论文系统",
"path": "personal/专升本/毕业论文/论文系统.md"
},
{
"basename": "MCP 学习",
"path": "000-inbox/MCP 学习.md"
},
{
"basename": "labelImg",
"path": "resource/ai/labelImg.md"
},
{
"basename": "月报 202604",
"path": "work/移动杭研/项目备忘/月报 202604.md"
},
{
"basename": "CLAUDE",
"path": "work/移动杭研/AI 项目/CLAUDE.md"
},
{
"basename": "cache+域名配置工单",
"path": "work/移动杭研/业务梳理/运营工单/cache+域名配置工单.md"
},
{
"basename": "IBS 智能体具体落实技术方案",
"path": "work/移动杭研/AI 项目/IBS 智能体具体落实技术方案.md"
},
{
"basename": "AI 工具需求-MCP 请求数统计",
"path": "work/移动杭研/AI 项目/AI 工具需求-MCP 请求数统计.md"
},
{
"basename": "AI 工具需求-命中率计算",
"path": "work/移动杭研/AI 项目/AI 工具需求-命中率计算.md"
},
{
"basename": "图纸-IBS 智能体-v1-流量查询 demo",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v1-流量查询 demo.md"
},
{
"basename": "图纸-IBS 智能体-v1-流量查询 demo 对象存储",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v1-流量查询 demo 对象存储.md"
},
{
"basename": "图纸-IBS 智能体-v2-项目架构 原生 MCP",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v2-项目架构 原生 MCP.md"
},
{
"basename": "图纸-IBS 智能体-v3-项目架构",
"path": "work/移动杭研/AI 项目/图纸-IBS 智能体-v3-项目架构.md"
},
{
"basename": "getHitRatio接口分析",
"path": "work/移动杭研/AI 项目/getHitRatio接口分析.md"
},
{
"basename": "settings.local.json",
"path": "work/移动杭研/AI 项目/settings.local.json.md"
},
{
"basename": "RAG Flow 部署",
"path": "work/移动杭研/AI 项目/RAG Flow 部署.md"
}, },
{ {
"basename": "RAG Flow", "basename": "RAG Flow",
@@ -103,110 +199,13 @@
{ {
"basename": "ibs-ai 项目梳理", "basename": "ibs-ai 项目梳理",
"path": "work/移动杭研/AI 项目/ibs-ai 项目梳理.md" "path": "work/移动杭研/AI 项目/ibs-ai 项目梳理.md"
},
{
"basename": "开发备注",
"path": "work/移动杭研/AI 项目/开发备注.md"
},
{
"basename": "CLAUDE.md",
"path": "work/移动杭研/AI 项目/CLAUDE.md.md"
},
{
"basename": "RAG Flow 部署",
"path": "work/移动杭研/AI 项目/RAG Flow 部署.md"
},
{
"basename": "settings.local.json",
"path": "work/移动杭研/AI 项目/settings.local.json.md"
},
{
"basename": "Prompt 公司智能体",
"path": "resource/ai/prompts/临时/Prompt 公司智能体.md"
},
{
"basename": "Prompt 需求-考勤转换-小杨",
"path": "resource/ai/prompts/临时/Prompt 需求-考勤转换-小杨.md"
},
{
"basename": "Prompt 专家模板",
"path": "resource/ai/prompts/Prompt 专家模板.md"
},
{
"basename": "Prompt ChatBI 查询规划器",
"path": "resource/ai/prompts/Prompt ChatBI 查询规划器.md"
},
{
"basename": "Prompt Linus",
"path": "resource/ai/prompts/Prompt Linus.md"
},
{
"basename": "Prompt Lisp 语言",
"path": "resource/ai/prompts/Prompt Lisp 语言.md"
},
{
"basename": "Prompt PPT 大纲制作专家",
"path": "resource/ai/prompts/Prompt PPT 大纲制作专家.md"
},
{
"basename": "Prompt PPT 逐字稿编写专家",
"path": "resource/ai/prompts/Prompt PPT 逐字稿编写专家.md"
},
{
"basename": "开发笔记",
"path": "work/移动杭研/开发记录/7.19.0/开发笔记.md"
},
{
"basename": "20260312105716",
"path": "000-Inbox/20260312105716.md"
},
{
"basename": "服务器-香港",
"path": "personal/服务器-香港.md"
},
{
"basename": "20260311142461",
"path": "000-Inbox/20260311142461.md"
},
{
"basename": "K-V 功能",
"path": "work/移动杭研/项目备忘/K-V 功能.md"
},
{
"basename": "IBS NG 日志模板",
"path": "work/移动杭研/项目备忘/IBS NG 日志模板.md"
},
{
"basename": "项目杂记",
"path": "work/移动杭研/项目备忘/项目杂记.md"
},
{
"basename": "数据库信息",
"path": "work/移动杭研/项目备忘/数据库信息.md"
},
{
"basename": "获取企业 token 脚本",
"path": "work/移动杭研/项目备忘/获取企业 token 脚本.md"
},
{
"basename": "环境账号",
"path": "work/移动杭研/项目备忘/环境账号.md"
},
{
"basename": "工作账号",
"path": "work/移动杭研/项目备忘/工作账号.md"
},
{
"basename": "20260311083458",
"path": "000-Inbox/20260311083458.md"
} }
], ],
"omittedPaths": [ "omittedPaths": [
"^attachment/", "^calendar/",
"^calendar/" "^attachment/"
], ],
"omittedTags": [], "omittedTags": [],
"updateOn": "file-open", "updateOn": "file-open",
"omitBookmarks": false, "omitBookmarks": false
"maxLength": null
} }
File diff suppressed because one or more lines are too long
+4 -5
View File
@@ -1,9 +1,9 @@
{ {
"id": "recent-files-obsidian", "id": "recent-files-obsidian",
"name": "Recent Files", "name": "Recent Files",
"version": "1.7.6", "version": "1.7.9",
"minAppVersion": "0.16.3", "minAppVersion": "0.16.3",
"description": "List files by most recently opened", "description": "List files by most recently opened.",
"author": "Tony Grosinger", "author": "Tony Grosinger",
"authorUrl": "https://grosinger.net", "authorUrl": "https://grosinger.net",
"isDesktopOnly": false, "isDesktopOnly": false,
@@ -11,6 +11,5 @@
"Github Sponsor": "https://github.com/sponsors/tgrosinger", "Github Sponsor": "https://github.com/sponsors/tgrosinger",
"Buy me a Coffee": "https://buymeacoffee.com/tgrosinger", "Buy me a Coffee": "https://buymeacoffee.com/tgrosinger",
"Paypal": "https://paypal.me/tgrosinger" "Paypal": "https://paypal.me/tgrosinger"
}, }
"donation": "https://buymeacoffee.com/tgrosinger" }
}
File diff suppressed because one or more lines are too long
+9 -3
View File
@@ -1,11 +1,17 @@
{ {
"id": "templater-obsidian", "id": "templater-obsidian",
"name": "Templater", "name": "Templater",
"version": "2.18.1", "version": "2.20.4",
"description": "Create and use templates", "description": "Advanced templating and automation using handlebars-like syntax.",
"minAppVersion": "1.5.0", "minAppVersion": "1.12.2",
"author": "SilentVoid", "author": "SilentVoid",
"authorUrl": "https://github.com/SilentVoid13", "authorUrl": "https://github.com/SilentVoid13",
"fundingUrl": {
"GitHub Sponser (Zachatoo, maintainer)": "https://github.com/sponsors/Zachatoo",
"Ko-fi (Zachatoo, maintainer)": "https://ko-fi.com/zachatoo",
"GitHub Sponser (SilentVoid13, creator)": "https://github.com/sponsors/SilentVoid13",
"Paypal (SilentVoid13, creator)": "https://www.paypal.com/donate?hosted_button_id=U2SRGAFYXT32Q"
},
"helpUrl": "https://silentvoid13.github.io/Templater/", "helpUrl": "https://silentvoid13.github.io/Templater/",
"isDesktopOnly": false "isDesktopOnly": false
} }
+1 -1
View File
@@ -221,6 +221,6 @@ textarea.templater-prompt-input:focus {
} }
li.CodeMirror-hint-active { li.CodeMirror-hint-active {
background: #08f; background: #0088ff;
color: white; color: white;
} }
-25
View File
@@ -1,25 +0,0 @@
/* === 颜色变量 === */
:root {
--h1-color: #172a4f;
--h2-color: #4a5c7a;
--h3-color: #6b7280;
--bold-color: var(--h1-color);
}
/* === 标题样式 === */
h1 {
color: var(--h1-color);
}
h2 {
color: var(--h2-color);
}
h3 {
color: var(--h3-color);
}
/* === 粗体样式 === */
div.cm-line span.cm-strong,
div p strong {
color: var(--bold-color) !important;
}
+2 -2
View File
@@ -10,6 +10,7 @@
"excalidraw-export-padding": "number", "excalidraw-export-padding": "number",
"excalidraw-export-pngscale": "number", "excalidraw-export-pngscale": "number",
"excalidraw-export-embed-scene": "checkbox", "excalidraw-export-embed-scene": "checkbox",
"excalidraw-export-internal-links": "checkbox",
"excalidraw-link-prefix": "text", "excalidraw-link-prefix": "text",
"excalidraw-url-prefix": "text", "excalidraw-url-prefix": "text",
"excalidraw-link-brackets": "checkbox", "excalidraw-link-brackets": "checkbox",
@@ -23,7 +24,6 @@
"excalidraw-autoexport": "text", "excalidraw-autoexport": "text",
"excalidraw-embeddable-theme": "text", "excalidraw-embeddable-theme": "text",
"excalidraw-open-md": "checkbox", "excalidraw-open-md": "checkbox",
"excalidraw-embed-md": "checkbox", "excalidraw-embed-md": "checkbox"
"excalidraw-export-internal-links": "checkbox"
} }
} }
+122 -74
View File
@@ -4,20 +4,71 @@
"type": "split", "type": "split",
"children": [ "children": [
{ {
"id": "b29f1eccf46b0980", "id": "64438af04f4b395c",
"type": "tabs", "type": "tabs",
"children": [ "children": [
{ {
"id": "431eb252187a1635", "id": "7642168f74ef821a",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "empty", "type": "markdown",
"state": {}, "state": {
"file": "resource/ai/Hermes 命令 cli.md",
"mode": "source",
"source": false,
"backlinks": false
},
"icon": "lucide-file", "icon": "lucide-file",
"title": "新标签页" "title": "Hermes 命令 cli"
}
},
{
"id": "6fa615aca5f7108e",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "personal/服务器-香港.md",
"mode": "source",
"source": false,
"backlinks": false
},
"icon": "lucide-file",
"title": "服务器-香港"
}
},
{
"id": "e366a44a66017faf",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "personal/服务器-美国.md",
"mode": "source",
"source": false,
"backlinks": false
},
"icon": "lucide-file",
"title": "服务器-美国"
}
},
{
"id": "3e12e0f7fca28148",
"type": "leaf",
"state": {
"type": "markdown",
"state": {
"file": "calendar/diary/2026-05-19.md",
"mode": "source",
"source": false,
"backlinks": false
},
"icon": "lucide-file",
"title": "2026-05-19"
} }
} }
] ],
"currentTab": 3
} }
], ],
"direction": "vertical" "direction": "vertical"
@@ -29,7 +80,7 @@
{ {
"id": "6836caf2765d7139", "id": "6836caf2765d7139",
"type": "tabs", "type": "tabs",
"dimension": 69.27016645326505, "dimension": 73.04860088365243,
"children": [ "children": [
{ {
"id": "c8718c0c63702202", "id": "c8718c0c63702202",
@@ -50,7 +101,7 @@
"state": { "state": {
"type": "search", "type": "search",
"state": { "state": {
"query": "培训", "query": "file: ubu",
"matchingCase": false, "matchingCase": false,
"explainSearch": true, "explainSearch": true,
"collapseAll": false, "collapseAll": false,
@@ -74,25 +125,25 @@
] ]
}, },
{ {
"id": "729e97c586d5eaa9", "id": "2184ba625789cef2",
"type": "tabs", "type": "tabs",
"dimension": 30.72983354673495, "dimension": 26.95139911634757,
"children": [ "children": [
{ {
"id": "4c0b938080320781", "id": "910ff34a625157e6",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "recent-files", "type": "recent-files",
"state": {}, "state": {},
"icon": "clock", "icon": "clock",
"title": "Recent Files" "title": "Recent files"
} }
} }
] ]
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 222.5 "width": 261.5
}, },
"right": { "right": {
"id": "ca733f6d5936ae40", "id": "ca733f6d5936ae40",
@@ -101,7 +152,7 @@
{ {
"id": "1f21045435bfa327", "id": "1f21045435bfa327",
"type": "tabs", "type": "tabs",
"dimension": 57.8883495145631, "dimension": 57.03883495145632,
"children": [ "children": [
{ {
"id": "43a2e8e1d9201229", "id": "43a2e8e1d9201229",
@@ -109,12 +160,13 @@
"state": { "state": {
"type": "outline", "type": "outline",
"state": { "state": {
"file": "calendar/diary/2026-05-19.md",
"followCursor": true, "followCursor": true,
"showSearch": false, "showSearch": false,
"searchQuery": "" "searchQuery": ""
}, },
"icon": "lucide-list", "icon": "lucide-list",
"title": "大纲" "title": "2026-05-19 的大纲"
} }
}, },
{ {
@@ -123,7 +175,7 @@
"state": { "state": {
"type": "backlink", "type": "backlink",
"state": { "state": {
"file": "000-Inbox/20260305103657.md", "file": "attachment/templates/查找未引用图片.md",
"collapseAll": false, "collapseAll": false,
"extraContext": false, "extraContext": false,
"sortOrder": "alphabetical", "sortOrder": "alphabetical",
@@ -133,7 +185,7 @@
"unlinkedCollapsed": false "unlinkedCollapsed": false
}, },
"icon": "links-coming-in", "icon": "links-coming-in",
"title": "20260305103657 的反向链接列表" "title": "查找未引用图片 的反向链接列表"
} }
}, },
{ {
@@ -142,12 +194,11 @@
"state": { "state": {
"type": "outgoing-link", "type": "outgoing-link",
"state": { "state": {
"file": "000-Inbox/20260305103657.md",
"linksCollapsed": false, "linksCollapsed": false,
"unlinkedCollapsed": true "unlinkedCollapsed": true
}, },
"icon": "links-going-out", "icon": "links-going-out",
"title": "20260305103657 的出链列表" "title": "出链"
} }
}, },
{ {
@@ -184,9 +235,7 @@
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "footnotes", "type": "footnotes",
"state": { "state": {},
"file": "000-Inbox/20260305103657.md"
},
"icon": "lucide-file-signature", "icon": "lucide-file-signature",
"title": "脚注" "title": "脚注"
} }
@@ -194,12 +243,12 @@
] ]
}, },
{ {
"id": "ea8a40d57ef90e62", "id": "e2c44b9c5d0747bb",
"type": "tabs", "type": "tabs",
"dimension": 42.1116504854369, "dimension": 42.96116504854368,
"children": [ "children": [
{ {
"id": "499dbd491c6fb256", "id": "736f0891b55bede0",
"type": "leaf", "type": "leaf",
"state": { "state": {
"type": "calendar", "type": "calendar",
@@ -212,7 +261,7 @@
} }
], ],
"direction": "horizontal", "direction": "horizontal",
"width": 287.5 "width": 291.5
}, },
"left-ribbon": { "left-ribbon": {
"hiddenItems": { "hiddenItems": {
@@ -223,57 +272,56 @@
"canvas:新建白板": false, "canvas:新建白板": false,
"bases:新建数据库": false, "bases:新建数据库": false,
"templater-obsidian:Templater": true, "templater-obsidian:Templater": true,
"obsidian-excalidraw-plugin:New drawing": false "obsidian-excalidraw-plugin:新建绘图文件": false
} }
}, },
"active": "c8718c0c63702202", "active": "c8718c0c63702202",
"lastOpenFiles": [ "lastOpenFiles": [
"calendar/diary/2026-03-17.md", "resource/ai/prompts/cosmic/Prompt 02 COSMIC 功能过程.md",
"calendar/diary/2026-03-16.md", "work/移动杭研/开发记录/7.21.0/开发笔记 7.21.0.md",
"000-inbox/tg-bot.md", "000-inbox/05月18日 154321 华为、中兴、自研解封禁联调.md",
"000-inbox/Redis 安装.md", "attachment/Pasted image 20260519174702.png",
"000-inbox/杭州服务器-AI.md", "calendar/diary/2026-05-19.md",
"000-inbox/20260316213546.md", "attachment/Pasted image 20260519170953.png",
"resource/常用命令.md", "attachment/Pasted image 20260519170006.png",
"000-Inbox/20260316213546.md", "attachment/Pasted image 20260519163906.png",
"work/移动杭研/AI 项目/IBS 智能体具体落实技术方案.md", "attachment/Pasted image 20260519163145.png",
"000-Inbox/20260316160264.md", "resource/ai/prompts/cosmic/Prompt 01 COSMIC 需求扩写.md",
"000-inbox/20260316105203.md", "resource/ai/prompts/cosmic/Prompt 00 COSMIC v1.md",
"000-Inbox/20260316102936.md", "000-inbox/05月19日 154820.md",
"000-Inbox/20260316105203.md", "attachment/Pasted image 20260519140133.png",
"calendar/diary/2026-03-13.md", "calendar/diary/2026-05-18.md",
"calendar/diary/2026-03-15.md", "work/移动杭研/开发记录/7.20.0/开发笔记 7.20.0.md",
"calendar/weeks/2026-W11.md", "personal/服务器-美国.md",
"calendar/diary/2026-03-14.md", "personal/服务器-香港.md",
"calendar/diary/2026-03-12.md", "calendar/diary/2026-05-17.md",
"calendar/diary/2026-03-11.md", "calendar/weeks/2026-W20.md",
"calendar/diary/2026-03-10.md", "calendar/diary/2026-05-16.md",
"calendar/diary/2026-03-09.md", "calendar/diary/2026-05-15.md",
"work/移动杭研/问题处理/2026-03/0316-配管任务限流问题.md", "calendar/diary/2026-05-14.md",
"work/移动杭研/问题处理/2026-03", "calendar/diary/2026-05-13.md",
"000-inbox/20260313151563.md", "calendar/diary/2026-05-12.md",
"000-Inbox/0316-配管任务限流问题.md", "calendar/diary/2026-05-11.md",
"work/移动杭研/问题处理/2025-11/1107-试用单删除问题.md", "work/移动杭研/业务梳理/业务工单/试用业务-时长、流量到期.md",
"000-Inbox/20260313151563.md", "calendar/diary/2025年/2025-12-30.md",
"resource/ai/prompts/cosmic 业务版本", "resource/系统/ubuntu.md",
"resource/ai/prompts/cosmic", "resource/ai/大模型安装笔记/001 大模型启动流程.md",
"resource/ai/prompts/临时", "attachment/b0a6584f6636a36fa59bd0c5054c8c97.png",
"resource/工具/rime", "resource/ai/Hermes 命令 cli.md",
"attachment/images-paste/image-20260304153545122.png", "resource/ai/Hermes.md",
"work/移动杭研/开发记录/7.19.0", "attachment/Pasted image 20260513222338.png",
"work/移动杭研/AI 项目", "attachment/7e1e741c766535189b32065c7a64eac1.png",
"resource/前端", "attachment/Pasted image 20260512224844.png",
"resource/英语", "work/移动杭研/开发记录/7.21.0",
"attachment/images-paste/image-20260305082403915.png", "openpalm",
"attachment/image-20260305103708566.png", "resource/rust",
"attachment/Pasted image 20260305103645.png", "kids-coding",
"attachment/Pasted image 20260305103633.png", "resource/python",
"000-inbox/未命名.canvas", "work/移动杭研/问题处理/2026-04",
"attachment/images-uuid/dd32291e71724c52a904aa80ce1fc59a.png", "work/移动杭研/开发记录/7.20.0",
"attachment/images-uuid/e9d6d33cd6a44bd0ab62b5999e04c7be.png", "attachment/scripts/openTerminal.js",
"attachment/image-20260302225213302.png", "attachment/scripts",
"attachment/Pasted image 20260302225154.png", "attachment/jsscripts/openTerminal.js",
"resource/mermaid", "000-inbox/未命名.canvas"
"attachment/images-uuid/1b6c8b1c84064481ac7c72347ba8e259.png"
] ]
} }
@@ -0,0 +1,100 @@
# 华为
```
http://test.okkc.top/heb.jpg
http://test.okkc.top/nmgdx.jpg
http://test.okkc.top/wkc.png
```
## 封禁
### 下发
```
2026-05-18 08:38:46 [e694222eb3c14d19] [e694222eb3c14d19] [] [ibs-portal] [http-nio-8080-exec-9] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 104 urlBanOrLift - URL封禁/解封下发配管参数:{"cp_id":"83537947","operate":0,"task_id":"WG_84e454ab445df9ad78bdecf7a2c6c","urls":[{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[1],"url":"http://test.okkc.top/heb.jpg"}]}
2026-05-18 08:38:47 [e694222eb3c14d19] [e694222eb3c14d19] [] [ibs-portal] [http-nio-8080-exec-9] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 106 urlBanOrLift - 向统一网管下发URL封禁/解禁返回参数e{"error_code":0,"error_msg":"success"}
```
### 回调
```
2026-05-19 08:38:29 [5af0086375c91839] [5af0086375c91839] [] [ibs-portal] [http-nio-8080-exec-3] INFO com.cmcc.cdn.platform.selfservice.controller.OmsApiDomainController 1466 urlBanLiftCallback - 统一网管URL封禁/解禁回调参数,urlBanLiftCallBackVO:{"tasks":[{"operate":0,"status":1,"task_id":"WG_84e454ab445df9ad78bdecf7a2c6c"}]}
```
## 解禁
### 下发
```
2026-05-19 08:58:13 [40cc38f9c31c4b22] [40cc38f9c31c4b22] [] [ibs-portal] [http-nio-8080-exec-7] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 104 urlBanOrLift - URL封禁/解封下发配管参数:{"cp_id":"83537947","operate":1,"task_id":"WG_af52d3c8f4890ae8c313c68b84d27","urls":[{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[1],"url":"http://test.okkc.top/heb.jpg"}]}
2026-05-19 08:58:13 [40cc38f9c31c4b22] [40cc38f9c31c4b22] [] [ibs-portal] [http-nio-8080-exec-7] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 106 urlBanOrLift - 向统一网管下发URL封禁/解禁返回参数e{"error_code":0,"error_msg":"success"}
```
### 回调
```
2026-05-19 09:04:29 [5e61bcc746cd302d] [5e61bcc746cd302d] [] [ibs-portal] [http-nio-8080-exec-3] INFO com.cmcc.cdn.platform.selfservice.controller.OmsApiDomainController 1466 urlBanLiftCallback - 统一网管URL封禁/解禁回调参数,urlBanLiftCallBackVO:{"tasks":[{"operate":1,"status":1,"task_id":"WG_af52d3c8f4890ae8c313c68b84d27"}]}
```
## 多 URL
### 下发
```
2026-05-19 09:09:36 [09b677d484384b4f] [09b677d484384b4f] [] [ibs-portal] [http-nio-8080-exec-8] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 104 urlBanOrLift - URL封禁/解封下发配管参数:{"cp_id":"83537947","operate":0,"task_id":"WG_0a4e9ca06497b883ab2c1435851f0","urls":[{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[1],"url":"http://test.okkc.top/nmgdx.jpg"},{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[1],"url":"http://test.okkc.top/wkc.png"}]}
2026-05-19 09:09:36 [09b677d484384b4f] [09b677d484384b4f] [] [ibs-portal] [http-nio-8080-exec-8] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 106 urlBanOrLift - 向统一网管下发URL封禁/解禁返回参数e{"error_code":0,"error_msg":"success"}
```
### 回调
```
2026-05-19 09:14:29 [3df12b80589f53fa] [3df12b80589f53fa] [] [ibs-portal] [http-nio-8080-exec-2] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 164 urlBanLiftUpdate - URL封禁/解封回调参数:param{"tasks":[{"operate":0,"status":1,"task_id":"WG_0a4e9ca06497b883ab2c1435851f0"}]}
```
![](../attachment/Pasted%20image%2020260519170953.png)
# 中兴
```
https://sms-drcn.hihonorcdn.com/sms/20260512_1778575271708/patch_20260512161.zip
https://push.hihonorcdn.com/103412129/95ec15bc36347c636815599f863438ac1.png
```
## 下发
```
2026-05-19 09:51:54 [37d64e05cd954f5e] [37d64e05cd954f5e] [] [ibs-portal] [http-nio-8080-exec-7] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 104 urlBanOrLift - URL封禁/解封下发配管参数:{"cp_id":"83537947","operate":0,"task_id":"WG_20cb8e5274608a97651b261e07491","urls":[{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[2],"url":"https://sms-drcn.hihonorcdn.com/sms/20260512_1778575271708/patch_20260512161.zip"},{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[2],"url":"https://push.hihonorcdn.com/103412129/95ec15bc36347c636815599f863438ac1.png"}]}
2026-05-19 09:51:54 [37d64e05cd954f5e] [37d64e05cd954f5e] [] [ibs-portal] [http-nio-8080-exec-7] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 106 urlBanOrLift - 向统一网管下发URL封禁/解禁返回参数e{"error_code":0,"error_msg":"success"}
```
## 回调
```
2026-05-19 09:52:29 [cb930538344a7daf] [cb930538344a7daf] [] [ibs-portal] [http-nio-8080-exec-9] INFO com.cmcc.cdn.platform.selfservice.controller.OmsApiDomainController 1466 urlBanLiftCallback - 统一网管URL封禁/解禁回调参数,urlBanLiftCallBackVO:{"tasks":[{"operate":0,"status":0,"task_id":"WG_20cb8e5274608a97651b261e07491"}]}
```
## 下发
```
2026-05-19 09:54:10 [6723e252542e4d27] [6723e252542e4d27] [] [ibs-portal] [http-nio-8080-exec-10] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 104 urlBanOrLift - URL封禁/解封下发配管参数:{"cp_id":"83537947","operate":1,"task_id":"WG_364e5c36c43e9bd58c96151f66bbc","urls":[{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[2],"url":"https://sms-drcn.hihonorcdn.com/sms/20260512_1778575271708/patch_20260512161.zip"},{"areas":["ZJ"],"forbid_share_cache":false,"method":"exact","plain":[2],"url":"https://push.hihonorcdn.com/103412129/95ec15bc36347c636815599f863438ac1.png"}]}
2026-05-19 09:54:10 [6723e252542e4d27] [6723e252542e4d27] [] [ibs-portal] [http-nio-8080-exec-10] INFO com.cmcc.cdn.platform.selfservice.service.impl.URLBanLiftServiceImpl 106 urlBanOrLift - 向统一网管下发URL封禁/解禁返回参数e{"error_code":0,"error_msg":"success"}
```
## 回调
```
2026-05-19 09:54:29 [873eaa002f421e4b] [873eaa002f421e4b] [] [ibs-portal] [http-nio-8080-exec-2] INFO com.cmcc.cdn.platform.selfservice.controller.OmsApiDomainController 1466 urlBanLiftCallback - 统一网管URL封禁/解禁回调参数,urlBanLiftCallBackVO:{"tasks":[{"operate":1,"status":0,"task_id":"WG_364e5c36c43e9bd58c96151f66bbc"}]}
```
![](../attachment/Pasted%20image%2020260519174702.png)
# 自研
```
http://yanlian.e.cdn.chinamobile.com/video.ts
http://yanlian.e.cdn.chinamobile.com/intro.html
http://yanlian.e.cdn.chinamobile.com/image.jpg
http://yanlian.e.cdn.chinamobile.com/index.html
```
+78
View File
@@ -0,0 +1,78 @@
我梳理了一下,这块是“URL 封禁/解封”能力,入口是 [ProhibitionController.java](D:/MyCode/Work/yd-hy/ibs-portal/cdn-web/src/main/java/com/cmcc/cdn/platform/selfservice/controller/ProhibitionController.java:217) 的 `POST /api/url/ban`,回调是 [OmsApiDomainController.java](D:/MyCode/Work/yd-hy/ibs-portal/cdn-web/src/main/java/com/cmcc/cdn/platform/selfservice/controller/OmsApiDomainController.java:1462) 的 `POST /action?commandType=banCallBack&version=1`
**主流程**
`/api/url/ban` 根据两个配置开关分流:
`WG_TOTAL_SWITCH=true`,或当前企业在 `WG_ENTERPRISE_ID_SWITCH` 里时,走统一网管新链路:
1. 从请求头 `Tenant-Id` 查企业。
2. 把 `URLDenyAllowTaskVO.urls` 转成统一网管 `UrlBanLiftTaskVO.urls`
3. 校验 URL、域名归属、操作类型、重复封禁等。
4. 生成 `WG_` 开头的 `taskId`
5. 调统一网管/BPM`bpmService.urlBanOrLiftOperate(…)`
6. 下发成功后落库,返回 `taskId`
不命中开关时,走旧链路:
1. 生成 `WS_` 开头的 `itemId`
2. 校验 URL、域名归属、操作类型、重复封禁等。
3. 调旧配管/各平面下发。
4. 落库任务、URL 明细、配管记录、平面下发记录。
**统一网管新链路写入的表**
在 [URLBanLiftServiceImpl.java](D:/MyCode/Work/yd-hy/ibs-portal/cdn-service/src/main/java/com/cmcc/cdn/platform/selfservice/service/impl/URLBanLiftServiceImpl.java:76) 里,下发成功后写:
- `ban_lift_task`
对应实体 `NetworkURLBanLiftTask`。记录任务主表:`task_id``cp_id``operate`
- `ban_lift_url`
对应实体 `NetworkBanLiftURL`。记录每个 URL`url``task_id``areas``method``forbid_share_cache``operate``status``start_time`。初始 `status=11`,表示进行中。
- `ban_lift_task_url`
`ban_lift_task``ban_lift_url` 的关联表。
统一网管链路下发失败时,代码里没有保存这几张表,只返回失败。
**旧链路写入的表**
旧链路在 [ProhibitionServiceImpl.java](D:/MyCode/Work/yd-hy/ibs-portal/cdn-service/src/main/java/com/cmcc/cdn/platform/selfservice/service/impl/ProhibitionServiceImpl.java:253) 的 `pipeUrlDenyAllowed` 里写:
- `deny_allow_task`
任务主表,记录 `item_id``type``status``start_time``cp_id``task_state` 等。
- `deny_allow_url`
URL 明细表,记录 `url``domain``method``areas`
- `deny_allow_task_url`
任务和 URL 的关联表。
- `pipe_config_entity`
配管消息记录,保存 `message_id``target_name=itemId``type=WANG_SU_DENY/WANG_SU_DENY_RESET` 等,用于后续配管回调匹配。
- `ban_or_filter_record`
平面下发记录,保存 `task_id``distribution_plane``success_plane``error_plane``reason``urls``type=ban/unban`
另外,旧链路会更新 Redis 里的企业每日操作次数限制,不是数据库表。
**统一网管回调改哪些表**
你贴的 `banCallBack` 回调只处理统一网管新链路。逻辑在 [URLBanLiftServiceImpl.java](D:/MyCode/Work/yd-hy/ibs-portal/cdn-service/src/main/java/com/cmcc/cdn/platform/selfservice/service/impl/URLBanLiftServiceImpl.java:163)
1. 按回调里的 `taskId``ban_lift_task`
2. 找到该任务下所有 `ban_lift_url`
3. 更新每条 URL 的:
- `status`
- `operate`
- `start_time`
- `end_time`
4. 再通过 `urlBanLiftTaskDao.save(networkURLBanLiftTask)` 保存。
所以业务上主要修改的是:
- `ban_lift_url`
同时因为是通过父对象 `ban_lift_task` 保存,JPA 可能会触发 `ban_lift_task.updated_time` 更新;但任务主表没有业务字段被主动改,关联表 `ban_lift_task_url` 也不会变。
旧链路的回调不是这个接口,它会更新 `deny_allow_task``success_rate``end_time``status``task_state`;但你贴的 `banCallBack` 不会改 `deny_allow_task`
+77
View File
@@ -0,0 +1,77 @@
MCP 协议的实现,可以有2种方式,
1. stdio,把 jar 提供出去,别人本地配置引入即可使用,但这样的方式不太适合做统一网关服务。
2. sse
项目采用 [DDD](DDD.md) 架构,首先聚焦 domain 层。
### 会话管理服务
```java
private final ScheduledExecutorService cleanupScheduler = Executors.newSingleThreadScheduledExecutor();
```
其中 private 说明这个变量这能在类内部访问,声明为 final 则此变量只能赋值一次。`newSingleThreadScheduledExecutor` 代表只有一个工作线程。
> 一个带有定时功能的单线程定时队列。
```java
private final Map<String, SessionConfigVO> activeSessions = new ConcurrentHashMap<>();
```
考虑点在于这个会话是否会被多个线程操作处理。
```java
public SessionManagementService() {
cleanupScheduler.scheduleAtFixedRate(this::cleanupExpiredSessions, 5, 5, TimeUnit.MINUTES);
log.info("会话管理服务已启动,会话超时时间: {} 分钟", SESSION_TIMEOUT_MINUTES);
}
```
可以在构造中执行一些方法。
### 实时通讯
> 对立面轮训
SSEServer sent Events
基于普通的 HTTP 长连接,Content-type: text/event-stream
消息格式,每条消息以 \n\n 结尾,
![](../attachment/images-paste/Pasted%20image%2020260413135806.png)
```
SSE(协议)
需要一个好的服务器框架来实现它
Spring WebFlux(响应式 Web 框架) ← 最适合实现 SSE 的框架
因为它是响应式(非阻塞),非常适合长连接
在 WebFlux 里,用 Sinks + Flux<ServerSentEvent> 来产生和推送 SSE 事件
```
```
客户端发起 SSE 请求
Netty EventLoop 接收请求(线程A
Controller 返回 sink.asFlux() → 注册“有新事件时推送”的回调
线程A 立刻释放,去处理其他请求
...(连接保持打开,线程A 忙别的)
你的服务层调用 sink.tryEmitNext(新消息)
Reactor 通知 Netty:“这个连接有数据要写”
EventLoop 线程(可能是线程B)被唤醒
执行回调:把消息转成 "data: xxx\n\n" 格式 → 写入 Socket
写完后,线程B 立刻释放,继续干别的
```
+30
View File
@@ -0,0 +1,30 @@
| 方法 | 接口名称 | 入参描述 | 一句话作用 |
| ------ | ----------------------------------------------- | ------------------------------------------------ | ---------------------- |
| GET | `/api/mcp-tools` | `toolGroupId``displayName``pageNum``pageSize` | 分页查询动态工具列表 |
| GET | `/api/mcp-tools/{id}` | 工具 ID | 查询动态工具详情 |
| POST | `/api/mcp-tools` | `McpToolRequest` | 创建动态工具并注册到 MCP Server |
| PUT | `/api/mcp-tools/{id}` | 工具 ID、`McpToolRequest` | 更新动态工具并重新注册 |
| DELETE | `/api/mcp-tools/{id}` | 工具 ID | 删除动态工具并从 MCP Server 移除 |
| POST | `/api/mcp-tools/{id}/toggle` | 工具 ID | 启用或禁用动态工具 |
| POST | `/api/mcp-tools/{id}/tool-groups/{groupId}` | 工具 ID、工具组 ID | 将工具绑定到工具组 |
| DELETE | `/api/mcp-tools/{id}/tool-groups/{groupId}` | 工具 ID、工具组 ID | 解除工具与工具组绑定 |
| | | | |
| GET | `/api/tool-groups` | `pageNum``pageSize` | 分页查询工具组列表 |
| GET | `/api/tool-groups/{id}` | 工具组 ID | 查询工具组详情 |
| GET | `/api/tool-groups/{id}/tools` | 工具组 ID、`displayName` | 查询工具组关联的全部工具 |
| POST | `/api/tool-groups` | `McpToolGroupRequest` | 创建工具组并生成 API Key |
| PUT | `/api/tool-groups/{id}` | 工具组 ID、`McpToolGroupRequest` | 更新工具组名称或描述 |
| DELETE | `/api/tool-groups/{id}` | 工具组 ID | 删除工具组并解除相关关联 |
| POST | `/api/tool-groups/{id}/toggle` | 工具组 ID | 启用或禁用工具组 |
| POST | `/api/tool-groups/{id}/reset-api-key` | 工具组 ID | 重置工具组 API Key |
| | | | |
| GET | `/api/roles` | `pageNum``pageSize` | 分页查询角色列表 |
| GET | `/api/roles/{id}` | 角色 ID | 查询角色详情 |
| POST | `/api/roles` | `RoleRequest` | 创建角色并生成角色 Key |
| PUT | `/api/roles/{id}` | 角色 ID、`RoleRequest` | 更新角色名称或描述 |
| DELETE | `/api/roles/{id}` | 角色 ID | 删除角色并解除工具组关联 |
| POST | `/api/roles/{id}/toggle` | 角色 ID | 启用或禁用角色 |
| POST | `/api/roles/{id}/reset-key` | 角色 ID | 重置角色 Key |
| GET | `/api/roles/{id}/tool-groups` | 角色 ID | 查询角色可用工具组 |
| POST | `/api/roles/tool-groups` | `roleId``toolGroupId` | 给角色关联工具组 |
| DELETE | `/api/roles/{roleId}/tool-groups/{toolGroupId}` | 角色 ID、工具组 ID | 移除角色的工具组关联 |
+37
View File
@@ -0,0 +1,37 @@
```java
package cn.bugstack.ai.infrastructure.gateway;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.HeaderMap;
import retrofit2.http.POST;
import retrofit2.http.QueryMap;
import retrofit2.http.Url;
import java.util.Map;
/**
* 资料:<a href="https://bugstack.cn/md/road-map/http.html">HTTP 框架案例</a>
*/
public interface GenericHttpGateway {
@POST
Call<ResponseBody> post(
@Url String url,
@HeaderMap Map<String, Object> headers,
@Body RequestBody body
);
@GET
Call<ResponseBody> get(
@Url String url,
@HeaderMap Map<String, Object> headers,
@QueryMap Map<String, Object> queryParams
);
}
```
-20
View File
@@ -1,20 +0,0 @@
```
telegrambots 相关源码路径:D:\MyCode\Study\TelegramBots
<dependency>
<groupId>org.telegram</groupId>
<artifactId>telegrambots-longpolling</artifactId>
<version>${telegrambots.version}</version>
</dependency>
因为我是 Spring Boot 项目,那这里是不是用官方推荐的 telegrambots-springboot-longpolling-starter 更方便呢?
现在请帮我实现一个最基础的机器人的功能:
1. 双向机器人的功能,当有人给这个机器人发消息的时候,可以通知到我。可以参考下比较成熟的双项机器人实现的手段。
2. 给我提供一个前台页面,让我可以在上面发布通知,使得所有使用我这个机器人的用户都能收到消息
```
```
7786028912:AAGxQ1jmD4a7j6k-KGsSainVpsFGplSXp-0
```
View File
-20
View File
@@ -1,20 +0,0 @@
```
测试云主机信息 172.21.30.23 用户名:root 密码:Melo@3023
测试mysql 172.21.33.224 用户名:cosmic_ai 密码:Cosmic@3306
测试redis: 172.21.43.68:6379 密码:chRdw@redis
1.DeepSeek-671B http://36.137.208.165:5020/v1/chat/completions
2.Qwen3-8B http://172.21.9.104:31554/v1/chat/completions
redis-cli -h 127.0.0.1 -p 6379 -a '5sTb7fHFbsYl6KmI6pvC^XMw!7Y^Pbc1'
```
| 项目 | 配置 |
| --- | ------------------ |
| 系统 | Anolis OS 8.6 |
| 内核 | 4.18 |
| CPU | Intel Xeon Skylake |
| 核心 | **8 vCPU** |
| 主频 | 2.2 GHz |
| 虚拟化 | KVM |
| 架构 | x86_64 |
Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 683 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Some files were not shown because too many files have changed in this diff Show More