今天是2025年7月7日,我想起来我还有一个Hexo的blog。于是,我就打开,想着写点什么。之前,因为中文的文章标题,在URL内会很长很长,这一分享给别人,里面还带上日期啥的,非常长。其次,如果标题一改,之前的链接直接失效了,这样子非常不好,还是需要一个稳定的链接,另外就是要短。
于是,我就尝试使用hexo-abbrlink这个插件来做索引,这个插件是基于文章标题做crc啥的,然后计算出来一串数字,不过,我觉得这个方案不是很好。因为这串数字没什么含义,而且如果修改了它依赖的值,那么就破坏了这串数字。并且hexo-abbrlink这个插件有一定的侵入性,它会在每个markdown文件里面,塞进去一个abbrlink:xxxxxxxx这样子的东西,非常恶心。
索引生成
于是,我就在想,是否可以为每个文章分配一个ID,这样子就解决了文章的URL过长的问题。其次,就是要保证这个ID的唯一性和不变性,所以最好的方案就是用一个递增的索引。因此,我决定使用json作为数据存储的个数,我构造一个passage_index.json文件来存储,已经分配的文章的ID和下一个索引,这样子我不会每次重新分配索引,而是基于上一次的下一个索引分配给没有分配索引的文章。
因此,我写了一个generate_passage_index.js,它的作用是生成这个passage_index.json文件。这样子,如果需要重新建立索引,那么只需要把这个passage_index.json文件删除即可,而且会根据文章里面的date字段进行升序排序,也就是更老的文章的索引会小一些。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| const fs = require('fs'); const path = require('path'); const matter = require('gray-matter');
const POSTS_DIR = path.join(__dirname, 'source', '_posts'); const INDEX_FILE = path.join(__dirname, 'passage_index.json');
function getAllPostFiles(dir) { return fs.readdirSync(dir) .filter(f => f.endsWith('.md')) .map(f => path.join(dir, f)); }
function getPostDate(file) { try { const content = fs.readFileSync(file, 'utf-8'); const fm = matter(content); return fm.data.date ? new Date(fm.data.date) : new Date(0); } catch { return new Date(0); } }
function loadIndex() { if (!fs.existsSync(INDEX_FILE)) return null; try { return JSON.parse(fs.readFileSync(INDEX_FILE, 'utf-8')); } catch { return null; } }
function saveIndex(indexObj) { fs.writeFileSync(INDEX_FILE, JSON.stringify(indexObj, null, 2), 'utf-8'); }
function main() { const postFiles = getAllPostFiles(POSTS_DIR); let indexObj = null; if (fs.existsSync(INDEX_FILE)) { indexObj = JSON.parse(fs.readFileSync(INDEX_FILE, 'utf-8')); const knownPaths = new Set(indexObj.posts.map(p => p.path)); let changed = false; postFiles.forEach(file => { const normPath = file.replace(/\\/g, '/'); if (!knownPaths.has(normPath)) { indexObj.posts.push({ path: normPath, id: String(indexObj.next_index) }); indexObj.next_index++; changed = true; } }); if (changed) { saveIndex(indexObj); console.log('已为新文章分配索引'); } else { console.log('无新增文章,无需更新索引'); } return; } const postsWithDate = postFiles.map(file => ({ path: file.replace(/\\/g, '/'), date: getPostDate(file), })); postsWithDate.sort((a, b) => a.date - b.date); indexObj = { next_index: postsWithDate.length + 1, posts: [] }; postsWithDate.forEach((post, i) => { indexObj.posts.push({ path: post.path, id: String(i + 1) }); }); saveIndex(indexObj); console.log('索引已按 date 字段重建'); }
main();
|
控制hexo的页面生成
hexo页面的索引是通过_config.yml的permalink控制的。因此,我设置为下面这样子。
1
| permalink: article/:id.html
|
并且,需要在hexo的博客项目的根目录下建一个scripts目录。然后加一个set_permalink_by_index.js负责控制这个链接的生成。因为之前的generate_passage_index.js这个脚本,我是放在根目录下的,因为这个需要先运行,然后才能运行设置链接的脚本,我用了一个比较笨的方法。那就是在set_permalink_by_index.js里面运行那个生成索引的脚本,这样子就可以保证顺序了。
下面是set_permalink_by_index.js这个脚本的内容,主要就是利用hexo的过滤器接口来控制。参考:https://hexo.io/zh-cn/api/filter。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process');
console.log('set_permalink_by_index.js loaded');
hexo.extend.filter.register('before_generate', function() { const scriptPath = path.join(hexo.base_dir, 'generate_passage_index.js'); try { execSync(`node "${scriptPath}"`, { stdio: 'inherit' }); console.log('已自动运行 generate_passage_index.js'); } catch (e) { console.error('自动运行 generate_passage_index.js 失败', e); } });
hexo.extend.filter.register('before_post_render', function(data) { const indexPath = path.join(hexo.base_dir, 'passage_index.json'); if (!fs.existsSync(indexPath)) { console.log('passage_index.json 不存在'); return data; } const indexData = JSON.parse(fs.readFileSync(indexPath, 'utf-8')); const norm1 = data.full_source.replace(/\\/g, '/'); const match = indexData.posts.find(p => { const norm2 = p.path.replace(/\\/g, '/'); return norm1 === norm2; }); if (match) { data.id = String(match.id); console.log('分配id:', match.id, '->', data.full_source); } else { console.log('未匹配到 passage_index:', data.full_source); } return data; });
|