Hexo 博客 GitHub 贡献图加载失败排查与修复技术文档 1. 问题描述 在 Hexo 博客(Butterfly 主题)中使用 hexo-filter-gitcalendar 插件时,贡献图无法正常显示。表现为:
控制台报错 TypeError: can't access property "date", git_lastweek[git_thisdayindex] is undefined。
统计区域显示“过去一年提交 [object Object]”,而不是数字。
日历网格有时能渲染部分格子,但布局错乱。
2. 环境与工具
Hexo 版本:8.1.1
主题:Butterfly 5.5.4
插件:hexo-filter-gitcalendar 1.0.11
API 后端:自建 Flask API,部署于 Vercel
数据源:https://github-contributions-api.jogruber.de/v4/{username}
浏览器:Chrome / Firefox
3. 排查过程 3.1 初步检查 API 可用性 使用 curl 测试 API:
1 curl https://api.abbkirito.online/Sunrisepeak
返回了包含 contributions 数组的 JSON,但 contributions 是扁平的一维数组(每项包含 date 和 count)。API 本身工作正常,无 CORS 错误。
3.2 观察浏览器控制台错误 打开博客页面,F12 开发者工具发现错误:
1 TypeError: can't access property "date", git_lastweek[git_thisdayindex] is undefined
说明插件在访问 git_lastweek 数组的某个索引时,该位置不存在。这通常是由于 contributions 数组长度不足或结构不符合预期。
3.3 分析插件源码 查看 gitcalendar.js 源码发现插件期望的 data.contributions 是一个二维数组 ,外层长度至少为 53(代表一年 53 周),内层每个数组长度为 7(代表一周 7 天)。插件会通过以下方式提取数据:
1 2 3 git_lastweek = data.contributions [52 ]; git_thisdayindex = git_lastweek.length - 1 ; git_thisday = git_lastweek[git_thisdayindex].date ;
如果 contributions 不是二维数组,或者长度不足 53,就会导致越界错误。
3.4 检查 API 返回格式 原始 API 返回的是扁平数组(第三方 API 的原始格式),与插件要求的二维数组不符。虽然尝试过返回二维数组,但未补足到 53 周,导致 git_lastweek 可能为 undefined。
3.5 统计部分显示 [object Object] 的原因 统计部分(“过去一年提交”)显示 [object Object],是因为插件从 data.total 读取总提交数,但该字段可能被错误赋值为一个对象(例如,{"total": 0} 中的 total 是数字,但如果 API 返回的 total 字段是嵌套对象或未正确解析,就会显示 [object Object])。实际上,第三方 API 返回的 total 是正确的数字,但在某些情况下(如 API 包装时未正确处理)可能导致类型错误。
3.6 最终定位
根本原因 :API 返回的 contributions 格式不符合插件要求(需要严格 53 周的二维数组),且 total 字段可能因数据源问题被错误处理。
关键错误 :插件访问 git_lastweek[git_thisdayindex] 时越界,导致整个渲染流程中断。
4. 解决方案 4.1 修正 API 返回格式 修改 API 代码,确保:
contributions 为二维数组,且长度至少为 53 周,每周 7 天。
不足的周在前面补空数据(日期递减,count = 0)。
最后一周不足 7 天时,在后面补空数据(日期递增,count = 0)。
total 字段手动计算(对扁平数组的 count 求和),确保为整数。
4.2 完整 API 代码 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 80 81 82 83 84 85 86 from flask import Flask, jsonify, requestfrom flask_cors import CORSimport requestsfrom datetime import datetime, timedeltaapp = Flask(__name__) CORS(app) def list_split (items, n ): """将列表按每 n 个元素分割成子列表""" return [items[i:i + n] for i in range (0 , len (items), n)] def getdata (name ): try : url = f"https://github-contributions-api.jogruber.de/v4/{name} " resp = requests.get(url, timeout=10 ) resp.raise_for_status() data = resp.json() days = data.get("contributions" , []) total = sum (day.get("count" , 0 ) for day in days) weekly = list_split(days, 7 ) while len (weekly) < 53 : first_week_start = datetime.strptime(weekly[0 ][0 ]["date" ], "%Y-%m-%d" ) prev_week_start = first_week_start - timedelta(weeks=1 ) new_week = [] for i in range (7 ): date = prev_week_start + timedelta(days=i) new_week.append({"date" : date.strftime("%Y-%m-%d" ), "count" : 0 }) weekly.insert(0 , new_week) last_week = weekly[-1 ] if len (last_week) < 7 : last_date = datetime.strptime(last_week[-1 ]["date" ], "%Y-%m-%d" ) for i in range (1 , 7 - len (last_week) + 1 ): new_date = last_date + timedelta(days=i) last_week.append({"date" : new_date.strftime("%Y-%m-%d" ), "count" : 0 }) return { "total" : total, "contributions" : weekly[:53 ] } except Exception as e: return {"error" : str (e)} @app.route('/' ) def home (): return jsonify({ "message" : "GitHub Calendar API" , "usage" : [ "/?username - 例如 /?Sunrisepeak" , "/api?username=<name> - 同上" , "/<username> - 路径参数" ] }) @app.route('/api' , strict_slashes=False ) @app.route('/' , strict_slashes=False ) def get_calendar (): username = request.args.get('username' ) if not username: qs = request.query_string.decode('utf-8' ) if qs and '=' not in qs: username = qs if not username: return jsonify({"error" : "Missing username" }), 400 return jsonify(getdata(username)) @app.route('/<username>' , strict_slashes=False ) def get_calendar_by_path (username ): return jsonify(getdata(username)) app = app
4.3 部署配置
1 2 3 Flask requests flask-cors
1 2 3 { "rewrites" : [ { "source" : "/(.*)" , "destination" : "/api/index" } ] }
4.4 Hexo 插件配置 在 _config.yml 中确保配置正确:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 gitcalendar: enable: true priority: 5 enable_page: / layout: type: id name: recent-posts index: 0 user: Sunrisepeak apiurl: 'https://api.abbkirito.online' minheight: pc: 280px mobile: 0px color: "['#d9e0df', '#c6e0dc', '#a8dcd4', '#9adcd2', '#89ded1', '#77e0d0', '#5fdecb', '#47dcc6', '#39dcc3', '#1fdabe', '#00dab9']" container: '.recent-post-item' gitcalendar_css: https://npm.elemecdn.com/hexo-filter-gitcalendar/lib/gitcalendar.css gitcalendar_js: https://npm.elemecdn.com/hexo-filter-gitcalendar/lib/gitcalendar.js
5. 验证与效果 重新部署 API 和 Hexo 后:
贡献图日历网格正常渲染,显示月份和格子。
统计数字正确显示(如“过去一年提交 20”),不再是 [object Object]。
控制台无 JavaScript 错误。
6. 总结
核心教训 :使用第三方插件时,必须严格遵循其期望的数据格式。hexo-filter-gitcalendar 要求 contributions 为 53 周二维数组,total 为整数。
排查方法 :从外部 API 开始检查,逐步深入插件源码,结合浏览器错误信息定位问题。
解决技巧 :在 API 层面适配插件格式,补足数据并手动计算 total,避免依赖第三方数据的潜在问题。
通过本次修复,不仅解决了贡献图加载问题,也为今后类似问题提供了清晰的排查思路。