作者: chentushen

  • ESP8266电子时钟

    ESP8266电子时钟

    功能:自定义API访问
    显示天气
    显示时间

    材料
    • 一块大小不符合的面包板
    • NodeMCU ESP-12E(ESP8266 WiFi 开发板)
    • 0.96 寸 I2C 接口 SSD1306 OLED 显示屏
    • 有源蜂鸣器
    • 几根杜邦线
    arduino 代码
    #include <U8g2lib.h>
    #include <Wire.h>
    #include <ESP8266WiFi.h>
    #include <ESP8266HTTPClient.h>
    #include <ArduinoJson.h>
    
    // ==================== OLED配置 ====================
    U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE, /* clock=*/ 5, /* data=*/ 4);
    // ==================== 蜂鸣器配置 ====================
    #define BUZZER_PIN 0  // D3 (GPIO0)
    
    
    // ==================== WiFi配置 ====================
    const char* ssid = "N/A";
    const char* password = "N/A";
    
    // ==================== API配置 ====================
    const char* timeUrl = "N/A";
    const char* serverUrl = "N/A";
    
    // 心知天气配置(免费注册:https://www.seniverse.com/)
    const char* xinzhiKey = "N/A";
    const char* city = "N/A";
    const char* weatherUrl = "https://api.seniverse.com/v3/weather/now.json";
    
    WiFiClientSecure client;
    
    // ==================== 全局变量 ====================
    int currentHour = 0;
    int currentMinute = 0;
    int currentSecond = 0;
    int currentWeekday = 0;
    String currentDateFormatted = "";
    
    // 天气
    String weatherText = "";
    String weatherTemp = "";
    
    // 通知
    String notifications[30];
    int notificationCount = 0;
    int currentNotificationIndex = 0;
    
    // 定时
    unsigned long lastTimeUpdate = 0;
    unsigned long lastWeatherUpdate = 0;
    unsigned long lastDataUpdate = 0;
    unsigned long lastModeSwitch = 0;
    
    int displayMode = 0;  // 0:时间界面, 1:通知界面
    bool notificationJustShown = false;
    
    // ==================== 辅助函数 ====================
    String getWeekdayCN(int w) {
      String weekdays[] = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"};
      if (w >= 0 && w <= 6) return weekdays[w];
      return "周一";
    }
    
    String getWeatherCN(String weather) {
      if (weather.indexOf("Clear") >= 0) return "晴";
      if (weather.indexOf("Cloud") >= 0) return "多云";
      if (weather.indexOf("Overcast") >= 0) return "阴";
      if (weather.indexOf("Light Rain") >= 0) return "小雨";
      if (weather.indexOf("Moderate Rain") >= 0) return "中雨";
      if (weather.indexOf("Heavy Rain") >= 0) return "大雨";
      if (weather.indexOf("Rain") >= 0) return "雨";
      if (weather.indexOf("Snow") >= 0) return "雪";
      if (weather.indexOf("Fog") >= 0) return "雾";
      return weather;
    }
    
    void beepLight() {
      digitalWrite(BUZZER_PIN, LOW);
      delay(80);
      digitalWrite(BUZZER_PIN, HIGH);
    }
    
    // ==================== 获取数据 ====================
    void updateTime() {
      if (WiFi.status() != WL_CONNECTED) return;
      
      HTTPClient https;
      client.setInsecure();
      https.setTimeout(5000);
      
      if (!https.begin(client, timeUrl)) return;
      
      int httpCode = https.GET();
      if (httpCode == HTTP_CODE_OK) {
        String payload = https.getString();
        
        DynamicJsonDocument doc(512);
        deserializeJson(doc, payload);
        
        currentHour = doc["hour"];
        currentMinute = doc["minute"];
        currentWeekday = doc["weekday"];
        
        String timeStr = doc["time"].as<String>();
        if (timeStr.length() >= 19) {
          currentSecond = timeStr.substring(17, 19).toInt();
        }
        
        String dateStr = doc["date"].as<String>();
        if (dateStr.length() >= 10) {
          String month = dateStr.substring(5, 7);
          String day = dateStr.substring(8, 10);
          currentDateFormatted = month + "月" + day + "日";
        }
      }
      https.end();
    }
    
    void updateWeather() {
      if (WiFi.status() != WL_CONNECTED) return;
      if (String(xinzhiKey).length() == 0) return;
      
      String url = String(weatherUrl) + "?key=" + xinzhiKey + "&location=" + city + "&language=en&unit=c";
      
      HTTPClient https;
      client.setInsecure();
      https.setTimeout(5000);
      
      if (!https.begin(client, url)) return;
      
      int httpCode = https.GET();
      if (httpCode == HTTP_CODE_OK) {
        String payload = https.getString();
        
        DynamicJsonDocument doc(1024);
        deserializeJson(doc, payload);
        
        if (doc.containsKey("results")) {
          JsonObject now = doc["results"][0]["now"];
          weatherText = now["text"].as<String>();
          float temp = now["temperature"];
          weatherTemp = String(temp, 0);
        }
      }
      https.end();
    }
    
    void fetchNotifications() {
      if (WiFi.status() != WL_CONNECTED) return;
      
      HTTPClient https;
      client.setInsecure();
      https.setTimeout(10000);
      
      if (!https.begin(client, serverUrl)) return;
      
      int httpCode = https.GET();
      if (httpCode == HTTP_CODE_OK) {
        String payload = https.getString();
        
        DynamicJsonDocument doc(4096);
        deserializeJson(doc, payload);
        
        if (doc["status"] == "success") {
          notificationCount = 0;
          JsonArray notificationsArr = doc["notifications"];
          for (JsonObject n : notificationsArr) {
            if (notificationCount < 30) {
              notifications[notificationCount] = n["content"].as<String>();
              notificationCount++;
            }
          }
        }
      }
      https.end();
    }
    
    // ==================== 手表界面 ====================
    void displayWatchFace() {
      u8g2.firstPage();
      do {
        // ========== 顶部区域 ==========
        u8g2.setFont(u8g2_font_wqy12_t_chinese3);
        u8g2.drawHLine(0, 15, 128);  // 顶部分割线
        
        // 日期(左上角)
        u8g2.setCursor(5, 12);
        if (currentDateFormatted.length() > 0) {
          u8g2.print(currentDateFormatted);
        } else {
          u8g2.print("03月24日");
        }
        
        // 星期(右上角)
        u8g2.setCursor(95, 12);
        u8g2.print(getWeekdayCN(currentWeekday));
        
        // ========== 中间时间区域 ==========
        // 时间往下移(从y=42移到y=48)
        u8g2.setFont(u8g2_font_logisoso24_tf);
        u8g2.setCursor(25, 48);
        char timeBuf[6];
        sprintf(timeBuf, "%02d:%02d", currentHour, currentMinute);
        u8g2.print(timeBuf);
        
        // 秒数(放在时间右侧)
        u8g2.setFont(u8g2_font_helvB08_tf);
        u8g2.setCursor(105, 38);
        char secBuf[3];
        sprintf(secBuf, "%02d", currentSecond);
        u8g2.print(secBuf);
        
        // ========== 底部区域 ==========
        
        // 底部文字(放在线下方,y=63)
        u8g2.setCursor(10, 63);
        if (weatherText.length() > 0 && weatherTemp.length() > 0) {
          u8g2.print(getWeatherCN(weatherText));
          u8g2.print(" ");
          u8g2.print(weatherTemp);
          u8g2.print("°C");
        } else if (String(xinzhiKey).length() > 0) {
          u8g2.print("天气加载中");
        } else {
          u8g2.print("WiFi:");
          u8g2.print(WiFi.RSSI());
          u8g2.print("dBm");
        }
        
        // 通知数量(右侧)
        if (notificationCount > 0) {
          u8g2.setCursor(100, 63);
          u8g2.print("[");
          u8g2.print(notificationCount);
          u8g2.print("]");
        }
        
      } while (u8g2.nextPage());
    }
    
    // ==================== 通知界面 ====================
    void displayNotification() {
      u8g2.firstPage();
      do {
        u8g2.setFont(u8g2_font_wqy12_t_chinese3);
        
        u8g2.drawHLine(0, 15, 128);
        u8g2.setCursor(45, 12);
        u8g2.print("通 知");
        u8g2.drawHLine(0, 22, 128);
        
        if (notificationCount > 0 && currentNotificationIndex < notificationCount) {
          String content = notifications[currentNotificationIndex];
          
          int lineHeight = 12;
          int startPos = 0;
          int lines = 0;
          
          while (startPos < content.length() && lines < 3) {
            int endPos = startPos + 18;
            if (endPos > content.length()) endPos = content.length();
            
            u8g2.setCursor(5, 32 + lines * lineHeight);
            u8g2.print(content.substring(startPos, endPos));
            
            startPos = endPos;
            lines++;
          }
          
          u8g2.setCursor(100, 63);
          u8g2.print(currentNotificationIndex + 1);
          u8g2.print("/");
          u8g2.print(notificationCount);
          
        } else {
          u8g2.setCursor(35, 40);
          u8g2.print("暂无通知");
        }
        
        
      } while (u8g2.nextPage());
    }
    
    // ==================== 初始化 ====================
    void setup() {
      Serial.begin(115200);
      delay(100);
      
      pinMode(BUZZER_PIN, OUTPUT);
      digitalWrite(BUZZER_PIN, HIGH);
      
      u8g2.begin();
      u8g2.enableUTF8Print();
      u8g2.setFont(u8g2_font_wqy12_t_chinese3);
      
      u8g2.firstPage();
      do {
        u8g2.setCursor(35, 30);
        u8g2.print("连接WiFi");
      } while (u8g2.nextPage());
      
      WiFi.begin(ssid, password);
      int attempts = 0;
      while (WiFi.status() != WL_CONNECTED && attempts < 25) {
        delay(500);
        Serial.print(".");
        attempts++;
      }
      
      if (WiFi.status() == WL_CONNECTED) {
        Serial.println("\nWiFi已连接");
        u8g2.firstPage();
        do {
          u8g2.setCursor(40, 30);
          u8g2.print("WiFi已连");
        } while (u8g2.nextPage());
        delay(1000);
      }
      
      client.setInsecure();
      
      updateTime();
      updateWeather();
      fetchNotifications();
      
      beepLight();
    }
    
    // ==================== 主循环 ====================
    void loop() {
      unsigned long now = millis();
      
      if (now - lastTimeUpdate >= 1000) {
        updateTime();
        lastTimeUpdate = now;
      }
      
      if (now - lastWeatherUpdate >= 300000) {
        updateWeather();
        lastWeatherUpdate = now;
      }
      
      // 每15分钟检查通知
      if (now - lastDataUpdate >= 900000) {
        fetchNotifications();
        lastDataUpdate = now;
        
        if (notificationCount > 0) {
          displayMode = 1;
          lastModeSwitch = now;
          currentNotificationIndex = 0;
          notificationJustShown = true;
          beepLight();
        }
      }
      
      // 通知界面显示10秒后切回
      if (displayMode == 1 && notificationJustShown) {
        if (now - lastModeSwitch >= 10000) {
          displayMode = 0;
          notificationJustShown = false;
          lastModeSwitch = now;
        }
      }
      
      // 通知界面自动翻页
      if (displayMode == 1 && notificationCount > 0 && notificationJustShown) {
        static unsigned long lastNotiSwitch = 0;
        if (now - lastNotiSwitch >= 3000) {
          currentNotificationIndex = (currentNotificationIndex + 1) % notificationCount;
          lastNotiSwitch = now;
        }
      }
      
      if (displayMode == 0) {
        displayWatchFace();
      } else {
        displayNotification();
      }
      
      delay(50);
    }
    C++

    “记住他人名字带给他人被记住的感觉是这个时代所稀缺的,因为效率至上的时代,每个人都忽视了彼此包括自己”

    Making someone feel remembered just by knowing their name—that’s something rare in today’s world. In an era that prioritizes efficiency, people often neglect others, and even themselves.

    The heavier the burden, the closer our lives come to the earth, the more real and truthful they become.

    负担越重,我们的生命越贴近大地,它就越真切实在。

    —— 《不能承受的生命之轻》米兰·昆德拉

  • 旧甲与无路之途

    ——摘自《坦纳伯斯伯爵手记·卷五》

    坦纳伯斯伯爵途经边陲时,曾在一间小酒馆里,听见邻桌的商人低声议论:“听说了吗?莱恩纳德伯爵家出事了。”

    他放下酒杯,侧耳倾听。

    莱恩纳德家族的起家,在边地并非秘密。老莱恩纳德并非生而显贵,年轻时不过是戍边军团的一名普通甲士。某次蛮族夜袭,他独守隘口至援军抵达,战后先帝感其勇,赐了一小块边陲封地——便是如今的莱恩纳德郡下。

    老莱恩纳德卸甲归田后,始终忘不了那些戍边的岁月。他将那套随他出生入死的旧甲胄偷偷藏在谷仓深处,每年擦拭一回,仿佛这样就能留住自己曾经的身份。

    如今老莱恩纳德年事已高,思乡之情日甚。他想回到幼年居住的怡人之滨,在故土终老。临行前,他舍不得那套旧甲,便将其装入箱中,随车队一同带走。

    途经王都检查站时,守兵打开了那口箱子。

    甲胄在阳光下泛着冷光。按帝国律法,私藏制式甲胄者,以谋逆论处。

    消息传到王都时,昔日的先帝早已驾崩。如今的皇帝坐在金殿上听完奏报,只淡淡问了句:“此人如今任何官职?”

    “回陛下,一介边陲老卒,已无军职。”

    皇帝便不再多问。他没有兴趣为一个没有用处的小人物翻旧账,也没有兴趣展现仁慈。重罚便是了。

    判决很快下达:老莱恩纳德罚没半数家产,本人监禁三年。其子孙三代不得入仕,不得担任任何公职,不得参与帝国任何层级的人才选拔。

    三代不得触及权力核心。

    判决书上甚至附了一笔——有人曾提议将莱恩纳德郡下收回王室,但经吏员核查,那片封地“过于偏远贫瘠,年贡不足修缮王都一段城墙”,便作罢了。

    消息传到莱恩纳德伯爵耳中时,他正在书案前批阅秋收账册。

    他沉默了许久,然后继续批阅。

    但坦纳伯斯伯爵后来听说,那几天夜里,城堡的书房灯火彻夜未熄。管家送进去的晚餐,常常原样端出来。

    其实莱恩纳德早有经商之意。他秉公廉洁多年,未曾借职权敛过一枚铜板,积蓄寥寥。他也曾试着做几回买卖——贩过毛皮,倒过铁器,甚至动过酿酒的心思——但每每浅尝辄止,多以亏损收尾。他骨子里终究是个务实的人,对商贾之事既无天赋,也无热忱,只是觉得或许该为郡下多寻一条出路。

    如今这判决落下,他未来的路,又少了一条。

    他本就在犹豫:郡下的未来,是该倾力发展商贸,还是该广纳贤才,兴办学堂?前者见效快但根基不稳,后者见效慢却利在长远。他为此辗转了无数个夜晚,始终未能决断。

    如今倒好,不必决断了。

    三代不得入仕,不得参与选拔。他的子孙,他的郡下子弟,即便再有才华,也触摸不到帝国权力的门槛。兴办学堂、培养人才,又有什么意义呢?

    而商贸之路——他自己都做不成的买卖,又如何指望郡下能靠它繁荣?

    某个黄昏,坦纳伯斯伯爵在城墙上找到了莱恩纳德。他独自站在那里,望着西沉的日头,面色平静,只是眉心拧着一道深深的纹路。

    “你可还好?”坦纳伯斯问。

    莱恩纳德沉默片刻,缓缓开口:“我父亲那套甲胄,其实早已锈蚀。他留着它,不过是想记住自己曾经是谁。”

    他顿了顿,又说:“先帝赐他封地时,说这是‘酬忠义之士’。如今新帝罚他三代,也是依律而行。我怨不得谁。”

    “只是……”他的声音低了下去,“我勤勤恳恳治理这片土地,从未懈怠一日。我清廉自守,不取分毫不义之财。我以为只要把郡下治理好,便算对得起先帝的恩赐,对得起父亲留下的这份家业。”

    “可现在我才明白——在权力中心的眼里,我和我的郡下,不过是一片‘过于偏远贫瘠、不值得收回’的地方。”

    他苦笑了一下。

    “连被剥夺的资格,都是因为太不值钱。”

    坦纳伯斯没有说话。

    风吹过城墙,带着秋末的凉意。

    良久,莱恩纳德直起身,整了整衣襟,朝城墙下走去。

    “但郡下还是需要治理的,不是吗?”他在经过坦纳伯斯身边时,说了这么一句,便头也不回地走了。

    坦纳伯斯站在原地,看着他的背影消失在暮色里。

    那背影依旧挺直,只是脚步比来时沉重了些。

    (坦纳伯斯伯爵的结语)

    沼泽女巫在翻看旧羊皮卷时曾说:忠诚是一种极其脆弱的货币,它的价值完全取决于发行它的人是否还在位。先帝用它换来了一块封地,新帝却连兑换的机会都不再给予。最荒谬的莫过于此——那套甲胄从未伤人,它只是提醒了一位老者他曾是谁。而帝国惩罚他的,恰恰是他曾经忠心的证明。至于莱恩纳德……他如今才知,勤恳和廉洁,在权力的账簿上,从不被记作资产。

  • 未射出的箭与迟来的靶

    ——摘自《坦纳伯斯伯爵手记·卷四》

    据传,莱恩纳德伯爵年少时,曾在王立治术学堂修习。彼时他一心钻研法典、粮秣与城防,视同窗间的宴饮嬉游为无用之物,独来独往,如一头不合群的幼狼。

    直到一个名叫奎因桑的女子出现在他的世界里。

    她是友人之友,初时不过是巡猎途中偶然同行的身影。但古怪之人自有古怪之人才能辨认的磁场——莱恩纳德发现,她同样不善言辞,同样会在旁人谈笑时走神去看天边的云,同样对猎犬的耳朵比对人脸更感兴趣。

    他们开始结伴巡猎。

    那些日子里,莱恩纳德第一次觉得,清晨的露水是有温度的,林间的风声是有语言的。他学会了辨认她箭囊上那根歪斜的羽毛,她记住了他整理马鬃时那种近乎偏执的认真。他们可以在沉默中共度整个下午,谁也不觉得尴尬。

    他没有说。

    他有一千个理由:学业未成,领地未继,前途未卜。他告诉自己,真正的爱意应当等到配得上她的那一天。于是那支箭始终搭在弦上,引而不发。

    毕业那日,她朝他挥了挥手,转身消失在王都的街道里。他没有追。

    此后的年月,莱恩纳德伯爵果然将封地治理得井井有条。只是每当巡猎经过那片旧林,他总会在某个树桩前勒马片刻——那是她曾经坐过的地方。他暗自发愿:待领地再无饥馑,待城墙再无裂缝,待所有麻烦都平息,他便去找她。

    然而领地如活物,旧的沟渠填平,新的税案便来;春荒刚过,夏汛又至。他永远在“还差一点”的路上。

    直到某个秋日午后,他在边境集市处理牲畜贸易纠纷时,听见两个皮毛商贩的闲谈。

    “听说了吗?北境那位传奇猎人,去年娶了个怪脾气的女人。”
    “怎么个怪法?”
    “据说她能在林中静坐半日不动,就为看一只松鼠埋橡果。还能叫出每只猎犬的名字。那猎人逢人便夸,说她是森林赐给他的礼物。”

    商贩们哄笑。莱恩纳德手中的账册滑落在地。

    他派了最可靠的人去北境查探。半月后回报:奎因桑,确已嫁与那位以箭术闻名的猎人,夫妻二人居于林畔木屋,出入成双,据说颇为和睦。

    那天夜里,莱恩纳德的城堡灯火通明。他没有处理任何政务,只是坐在议事厅的高背椅上,对着壁炉里跳动的火焰,沉默了一整夜。

    次日清晨,他的管家发现伯爵已经坐在书案前批阅积压的文书,字迹如常工整,只是墨痕有几处洇开了。

    后来,坦纳伯斯伯爵在路过他的领地时,曾与他共饮一杯。席间问及此事,莱恩纳德放下酒杯,目光落在窗外远处那片模糊的林线上,缓缓说道:

    “我用了很久才想明白。我许下的那个‘等我治好领地就去找她’的誓言,不过是一张永远不必兑现的借口。因为领地永远会有新的麻烦——正如我永远会有新的理由。我以为自己在等一个对的时机,其实只是在等自己足够勇敢。而勇气这种东西,等是等不来的。”

    他顿了顿,又说:“但有一件事,我确实没有骗自己。治理这片领地,从来不是为了她,也不是为了逃避她。我脚下的土地和土地上的人,才是真实的。那支没有射出的箭,就让它永远留在箭囊里吧。至少它提醒我,有些东西,错过了就是错过了。但手里剩下的箭,还得射向该去的地方。”

    (坦纳伯斯伯爵的结语)

    沼泽女巫在占卜时曾说:世间最锋利的箭,往往不是射中靶心的那一支,而是搭在弦上太久、最终被收回箭囊的那一支。它不曾射向何,却日复一日地磨损着持弓者的掌心。世人总以为遗憾是因为没有勇气,却不知有时候,正是因为没有勇气,我们才得以保全那些本会在日常琐碎中碎裂的幻梦。莱恩纳德的悲剧不在于他错过了她,而在于他用一个永远无法兑现的“以后”,欺骗了自己那么多年。所幸,他终于学会了区分——什么是可以弥补的,什么是只能咽下的。

  • Do And Done


    Do And Done

    风很大,但我还站着。这就够了。这就是我的 Done。

    一、站在风里的那个人

    2026年的某一天,我站在学校操场上,吹着冷风,和自己说了很久的话。

    那天我知道了:她有了男朋友。

    这件事我早就模拟过无数次——从我意识到“她会有男朋友”的那天起,我就在心里预演过这个场景。但当它真的发生时,我还是去了操场。不是去哭的。一滴眼泪都流不出来。

    我只是觉得有点冷。

    然后我开始和自己说话。不是自言自语,是真的说话——像对着另一个自己,把那些年积压的东西,全部摊开来,一件一件地看。

    那段录音,我今天翻出来听了。声音被风吹得有点抖,但每句话都像钉子,钉在那个晚上的空气里。

    这是我“Do and Done”诞生的夜晚。


    二、我曾经是个拯救者

    “我做任何事,都是为了拯救去的。这个课题很高尚。”

    这是我过去人生的全部脚本。

    我有一个“白骑士综合征”。我把喜欢的人变成了一个需要被拯救的符号,然后把“拯救她”设定为我活着的意义。考大学是为了拯救她,变好是为了拯救她,做一切事情都是为了那个宏大的目标。

    这个剧本有一个致命的漏洞:扮演英雄的人,自己却没有血包。

    “连自己都拯救不了的人,遑论拯救他人”——这句话我曾经说得理直气壮。我以为这是谦卑,后来才明白,这是逃避。我把爱“私有化”了:爱是需要先“配得上”才能领取的东西。而我永远配不上,因为我永远“还没拯救好自己”。

    所以我什么都不用真正去做。

    高尚的口号挂在嘴边,行动却停在原地。口号没有让我多10%的效率,但我仍然不肯放下。为什么?因为放下它,我就不知道我是谁了。


    三、神像的倒塌

    “现在好了,梦碎了。你连这个理由都没有了。”

    她找到那个人的时候,我的神像塌了。

    不是突然塌的。我早就模拟过这一天。从上学期末开始,我已经在慢慢接受她不在我生命中的事实。我只是没想到,当预言被验证的那一刻,我会这么……平静。

    一滴眼泪都没有。

    我难过吗?不难过。我只是把过去所有积压的情绪,重新过了一遍。然后发现:那个我曾经以为非她不可的人,其实早就不是我生活的一部分了。

    最讽刺的是什么?

    她过得很好。比我好。

    我一直在担心“她别死了”,结果活得比我还滋润。那个我曾经想要“拯救”的人,根本不需要我拯救。她靠自己,或者靠别人,走到了我当初设定的“拯救目标”那里——比如,找到一个可靠的男友。

    而我自己呢?

    “你考了个破大专,躺了两年。你做出什么成绩了?你连一等奖学金都没得。你做什么事情都不做出百分百的努力?”

    这是我对自己说的话。每句都扎在自己身上。


    四、空转的人生

    “你一直在做计划,但是你去执行了吗?很少。你的脑子一直在空想我该怎么解决问题,但是你真的落地去解决了吗?”

    这是我对自己的第二个发现。

    过去的我,活在一个奇怪的循环里:

    做计划 → 空想怎么执行 → 焦虑 → 再做计划

    就像一个传动轴,拼命转,却没有接上发动机。我以为自己在努力,其实只是在空转。我做项目,做题库,做小程序——但碰到一点困难就掉头。

    “你只敢撞一次南墙,你不敢撞第二次。你分不清哪些是小石子,哪些是真正的南墙。”

    小石子,掉头就走。
    真正的南墙,从来不敢撞。

    因为撞墙会痛。而我已经没有“为了她”这个理由,去承受那个痛了。


    五、“Do and Done”的诞生

    “事情的意义大多是事后赋予的。我只需要 Do and Done 就行了。”

    这句话,是在那片废墟上长出来的。

    它不是一句漂亮的口号。它是我在发现自己所有“高尚”都是骗局之后,能找到的、唯一可以握住的、最小的东西。

    什么是“Do and Done”?

    • 不做计划,先做一件事。做完,就打一个勾。
    • 不为“拯救谁”,就为“今天把这件事做完”。
    • 不问“这有什么意义”,做完之后,再回头去看。

    它和“得过且过”的区别是什么?

    得过且过是:反正没意义,所以我什么都不做。
    Do and Done是:正因为意义不明,所以我要先做点什么。

    每一个“Done”,都是在虚无里钉下的木桩。

    我按时吃饭了——Done。
    我跑完步了——Done。
    我把这个项目做完了——Done。

    这些木桩可能很小,但它们证明一件事:我存在过,我行动过,我没有停在原地。


    六、带着疑惑,走下去

    “接着走下去,带着疑惑,接着走下去。生命会自己找出出路。”

    我不知道这个哲学能带我走多远。

    我不知道做完一堆“Done”之后,我的人生会不会有答案。

    但我站在那个晚上的风里时,想明白了一件事:

    出路不是想出来的,是走出来的。

    我过去一直在空想“出路在哪里”。现在我决定,先迈一步。迈完一步,再迈一步。走完之后,回头看,那条路可能就是出路。

    那个晚上,风很大,人很多,每个人都拿着手机,每个人都像一座孤岛。

    “我觉得我是一个内部极大丰富的人,但是没有人能理解我,因为大家都被手机给困住了。”

    我也想被理解。但我知道,在被理解之前,我得先把自己活出来。

    活出来,不是靠“高尚的口号”活出来,而是靠一个又一个微小的“Do”,把自己从废墟里一点点挖出来。


    七、风吹过的时候

    “风吹过我的指尖,风吹过我的脸颊,风吹过我的发梢,好凉快,好舒服。但是感受过后呢,我确确实实的活着,但我活着我干嘛呢?”

    这是那天晚上,我问自己的最后一个问题。

    我没有答案。

    但我选择继续活着。继续感受风。继续做下一件小事。继续在每个夜晚,对自己说:

    今天,我又完成了一个 Done。

    那个曾经需要靠拯救别人来证明自己有价值的人,现在只需要靠做完一件小事,就能在深夜里对自己说:

    看,我今天存在过了。

    这就够了。

    风很大,但我还站着。

    一、重点原话摘录


    关于“拯救者”的幻灭:

    “我做任何事,都是为了拯救去的。这个课题很高尚,现在这份高尚被击碎了,你的高尚一点都不高尚,他一点都不在乎。这只是你一直一直在欺骗自己。”

    关于“私有化的爱”:

    “你不敢面对他的根本原因,就是你不敢面对你自己。你不敢把这个自己都拿不出手的人去面对他。所以你会说:连自己都拯救不了的人,遑论拯救他人呢。”

    关于“神像倒塌”:

    “梦碎了,你现在连这个理由都没有了。现在好了,梦碎了,你现在连这个理由都没有了。”

    关于“被验证的预言”:

    “你接触了你一早就模拟好这种——她会有男朋友,她已经有男朋友,现在只是把事实摆在你面前。这件事你已经试想过发生了,但是现在确实发生了,它只是验证了你的预言。”


    【旧模式的本质:空转的人生】

    关于“计划与执行的断裂”:

    “你一直在做计划,在做计划,但是你去执行了吗?很少。你的脑子一直在空想我该怎么解决问题,但是你真的落地去解决这个问题了没有?……就像那个传动轴没有接上那个发动机。”

    关于“不敢撞南墙”:

    “你只敢撞一次南墙,你不敢撞第二次。拿你的破头,一个项目撞一次南墙,哪怕是轻微的,哪怕是踩了一个石头,你都要往回掉头……你现在分不清哪些该调头,哪些是小石子,哪些是真正的南墙。”

    关于“用微小成就欺骗自己”:

    “还是在骗自己,拿一点微不足道的成就去骗自己。我今天做完一个项目,但是你真的学到了吗?你只是会用AI而已,给你的幻觉把你骗住了。”

    关于“缺乏宏大目标”:

    “所有事情都完成之后,你现在还是跟个无头苍蝇一样乱撞,因为你缺乏一个宏大的目标。你之前宏大的目标就是我要去变得更好,我要去拯救他……现在我缺乏一个宏大目标。”


    【新哲学的萌芽:Do and Done】

    关于“意义在事后赋予”:

    “事情的意义大多是事后赋予的。我只需要 Do and Done 就行了。”

    关于“带着疑惑走下去”:

    “接着走下去,带着疑惑,接着走下去。坚持下去,然后呢?然后说,生命会自己找出出路。”

    关于“真实的活着”:

    “我能感受到我有些时候我确实在感受到,风吹过我的指尖,风吹过我的脸颊,风吹过我的发梢,好凉快,好舒服。但是感受过后呢,我确确实实的活着,但我活着我干嘛呢?”


    【最后的诚实:站在废墟上】

    关于“哭不出来”:

    “一滴眼泪都流不出来。你难过吗?你这些事情你早预料到了。你难过吗?你不难过。我只是觉得有点冷。”

    关于“人和人的隔阂”:

    “每一个人拿着手机,都像一座无形的牢笼,把这彼此隔开了。我们再也不对话,然后矛盾越来越深。”

    关于“渴望被理解”:

    “我觉得我是一个内部极大丰富的人,但是没有人能理解我、来解读我,因为大家都被手机给困住了。”


    二、核心思维点梳理


    【思维点 01】“拯救者”的虚伪性

    核心洞察:

    “高尚的口号并没有为我的行事+10%效率,但我仍然不肯放下。”

    本质剖析:

    • 所谓“拯救他人”,本质是逃避面对真实的自己
    • “连自己都拯救不了”不是谦卑,而是把爱“私有化”的借口——把爱变成需要“配得上”才能领取的东西
    • 当拯救对象不再需要拯救,神像自然崩塌

    【思维点 02】“意义先于存在”的陷阱

    核心洞察:

    “在做之前就赋予意义的,叫预设意义,或本质先于存在。”

    本质剖析:

    • 预设意义让人获得方向感,但也让人依赖外部对象赋予价值
    • 一旦外部对象(如“她”)变化,意义大厦随之倾覆
    • 这是“空转人生”的根源:所有行动都指向一个虚幻的终点,而非过程本身

    【思维点 03】“传动轴没有接上发动机”

    核心洞察:

    “你一直在做计划,但不去执行;你在空想如何解决问题,但不落地解决。”

    本质剖析:

    • 思维与行动脱节,形成“空想-焦虑-再空想”的死循环
    • 看似比他人努力(做计划、做项目),实则没有真正的产出
    • 本质是恐惧:恐惧真实的行动会带来真实的失败

    【思维点 04】“只敢撞一次南墙”

    核心洞察:

    “你只敢撞一次南墙,你不敢撞第二次。你分不清哪些是小石子,哪些是真正的南墙。”

    本质剖析:

    • 缺乏持续试错的勇气,遇到一点阻力就调头
    • 这导致无法积累真正的经验,也无法区分“该放弃的”和“该坚持的”
    • 根源是:预设意义崩塌后,缺乏新的内在驱动力去支撑坚持

    【思维点 05】“意义是事后赋予的”

    核心洞察:

    “事情的意义大多是做完之后被赋予的。在做之前就赋予意义的,叫预设意义。”

    本质剖析:

    • 这是对“本质先于存在”的根本性翻转
    • 意义不再是行动的起点,而是行动可能带来的终点
    • 这让行动本身获得独立价值:行动不是为了实现某个预设的“高尚”,而是为了在行动后,自己可以回看、命名、赋予意义

    【思维点 06】“Do and Done”作为最低限度的行动哲学

    核心洞察:

    “我只需要 Do and Done 就行了。”

    本质剖析:

    • 在宏大目标缺失、意义感真空的状态下,这是唯一可行的起点
    • “Do”是行动的最小单位,“Done”是对行动的确认
    • 它不是“得过且过”,而是:用最小的行动单位,对抗宏大的虚无
    • 每一个“Done”,都是在废墟上钉下的木桩,证明自己存在过

    【思维点 07】“带着疑惑,走下去”

    核心洞察:

    “接着走下去,带着疑惑,接着走下去。生命会自己找出出路。”

    本质剖析:

    • 这是对“不确定性”的接纳——不再强求在行动前就看清全部意义
    • 这是一种“存在主义式”的勇气:在意义缺席时,依然选择行动
    • 出路不是想出来的,是走出来的

    【思维点 08】“被手机困住的一代人”

    核心洞察:

    “每一个人拿着手机,都像一座无形的牢笼,把彼此隔开了。我觉得我是一个内部极大丰富的人,但是没有人能理解我。”

    本质剖析:

    • 科技带来连接的幻觉,却加深了真实的隔阂
    • 每个人都渴望被深度理解,却没有人有勇气走出手机,去真正对话
    • 这构成了当代人普遍的孤独与迷茫

    【思维点 09】“遗憾被证实后,就不再是遗憾”

    核心洞察:

    “遗憾只是被证实了,现在遗憾就是遗憾了。你已经无法做到遗憾的事情,那你就不用担心那么多了。”

    本质剖析:

    • 真正折磨人的不是遗憾本身,而是“可能还未确定”的悬置状态
    • 当最坏的结果被证实,悬着的心反而落地
    • 这是一种奇怪的解脱:你终于可以不再为“如果”而内耗

    【思维点 10】“风吹过的时候,我确实活着”

    核心洞察:

    “风吹过我的指尖,风吹过我的脸颊,风吹过我的发梢,好凉快,好舒服。但是感受过后呢,我确确实实的活着,但我活着我干嘛呢?”

    本质剖析:

    • 这是最根本的存在主义困境:我感知到我活着,但我不知道为何而活
    • “Do and Done”无法回答“为何而活”,但它提供了一个起点:先活着,先行动,答案可能在后头
    • 这也是一种诚实:承认自己暂时没有答案,但不因此停止呼吸和行动

    2026年3月12日,一滴眼泪都没有,只是觉得有点冷”


    Who controls the past controls the future. Who controls the present controls the past.

    谁控制了过去,谁就控制了未来;谁控制了现在,谁就控制了过去。

    —— 《1984》乔治·奥威尔

  • 我的爱发电主页

    我创建了个爱发电账号,如果觉得我的“屎山”项目很有用的可以去浅浅支持一下

    https://afdian.com/a/Kunnector

  • LifeDrive-个人计划&记录系统

    今天是昨天的明天

    写了个轻量级个人知识库系统,融合了笔记、时间追踪、关系图谱和日历视图。它支持类 Obsidian 的 [[@页面链接]] 和 [[标签]] 语法,内置待办清单、提醒组件和 Markdown 渲染。

    Notiobsidian(Nobsidian)作为开源版本


    🌐 Notiobsidian

    Stars
    Forks
    Issues
    MIT License
    Python
    Flask
    Local First
    Notion Style


    本地优先 · 可编程 · Agent-ready 的下一代个人工作空间

    Notiobsidian

    把 Notion 的交互美学 + Obsidian 的本地自由 + AI Agent 原生接口 揉在一起
    一个完全自托管、可编程、跑在你电脑上的个人第二大脑(目前已是我本人的主力生产力工具)

    ✨ 核心特性(已实现 & 日常在用)

    • 本地优先 + Markdown 文件存储 —— 所有数据就是你硬盘上的文件,永不锁仓
    • Notion 风格块式编辑器 —— 支持拖拽、/命令、富媒体、数据库视图雏形
    • 自定义语法糖 —— {{TODO}}、{{image}}、{{video}}、{{calc}}、{{notice}} 等扩展块
    • 变量系统 & 数据面板 —— 记录习惯/开销/体重等数值,自动生成折线图/饼图/分布图
    • 每日追踪器 —— 时间统计 + 情绪日记 + 模板一键插入(我每天写日记都在这里)
    • 全局日历视图 —— 从笔记里自动提取 @2026-02-18 [会议] 事件,支持 ICS 导入导出
    • 知识图谱 —— vis-network 驱动,拖拽节点、按标签分组、cabinet 文件柜
    • 实时提醒 & 桌面通知 —— WebSocket + 浏览器通知(定时/间隔/周几都支持)
    • 极简自托管 —— 一条命令 python Notiobsidian.py 就能跑
    • 暗黑模式友好 + Tailwind 美化(手机也能凑合看)

    📸 截图预览

    Sidebar & Dashboard
    Daily Tracker with Charts
    Knowledge Graph View
    Calendar Events

    🚀 快速开始(3 分钟跑起来)

    1. 克隆仓库
       git clone https://github.com/NanamiChiaki-7/Notiobsidian.git
       cd Notiobsidian
    Bash
    1. 安装依赖(Python 3.8+)
       pip install -r requirements.txt
    Bash
    1. 启动
       python Notiobsidian.py
    Bash

    → 默认监听 http://0.0.0.0:5004

    1. 登录(首次使用)
    • 用户名:admin
    • 密码:PASSWORD(启动后立刻去改!见下方安全提示)
    1. 开始使用:浏览器打开 http://localhost:5004 即可看到欢迎页和示例内容

    生产建议

    • 用环境变量设置 SECRET_KEY 和密码
    • --host 0.0.0.0 --port 你的端口 或用 gunicorn / uvicorn 部署
    • 数据文件:nation_pro_v3.db(SQLite),记得定期备份!

    🛤️ 路线图(2026 计划)

    • [x] 基本笔记 + TODO + 日历 + 图谱 + 追踪器
    • [ ] 完善图片/视频上传 & 拖拽排序
    • [ ] 提醒真正定时推送(apscheduler / 系统托盘 /手机APP FCM)
    • [ ] Agent 接口(Python 函数暴露给 LLM 调用)
    • [ ] 移动端响应式优化
    • [ ] 主题切换(光暗 + 自定义配色)
    • [ ] 数据导入(Notion / Obsidian / Markdown 文件夹)
    • [ ] Docker 一键部署

    欢迎 PR 加速这些功能!

    🤝 贡献

    欢迎 issue、PR、想法!

    1. Fork → branch
    2. 改动后加测试(如果有)→ 提交清晰 commit
    3. 发 PR,描述清楚做了什么

    喜欢就点个 ⭐ 支持一下~


    Last updated: February 2026 · 最后更新:2026年2月

    I’ve known you for years. Everyone says you were beautiful when you were young, but I want to tell you I think you’re more beautiful now than then.

    我认识你,我永远记得你。那时候,你还很年轻,人人都说你美,现在,我是特为来告诉你,对我来说,我觉得现在你比年轻的时候更美。

    —— 《情人》玛格丽特·杜拉斯

  • 多项目挂载单一域名下子域名反代问题处理

    🏆 BUG本质归档:多项目同域名反代下的「路径双重转义」问题


    🎯 一句话总结

    静态资源请求被错误地当成了动态路由处理,导致路径被二次编码。


    🔍 问题根源拓扑

    你的预期:
    浏览器请求 /LifeDrive/static/css/style.css 
        → Nginx 静态 location 
        → 直接读硬盘文件 ✅
    
    实际发生:
    浏览器请求 /LifeDrive/static/css/style.css
        → Nginx 无静态 location(或被覆盖)
        → 转发给 Flask 代理
        → Flask 的 ReverseProxied 剥离 /LifeDrive
        → Flask 收到 /static/css/style.css
        → Flask 把它当作路由去找 @app.route('/static/...')
        → 找不到,返回 404 ❌

    触发条件(三要素缺一不可)

    1. Nginx 静态 location 缺失/写错/顺序不对
    • ^~ /Project/static/ 没写
    • 或写在代理 location 后面
    • alias 路径错误/少斜杠
    1. Flask 启用了 ReverseProxied
    • 正确剥离了前缀
    • 但把静态请求也剥离了
    1. HTML 用了 url_for 生成静态路径
       <!-- 这会让静态请求也经过 Flask 路由系统 -->
       <link href="{{ url_for('static', filename='css/style.css') }}">

    url_for('static') → 生成 /LifeDrive/static/css/style.css → 同上死循环


    解决方案对比

    方案静态文件处理动态路由HTML写法适用场景
    ❌ 全代理Flask处理Flask处理url_for单项目,无Nginx静态
    ✅ 组合拳Nginx直出Flask处理写死路径多项目同域名,性能最优
    ⚠️ 全直出Nginx直出Nginx直出写死路径纯静态站

    你最终选的是「组合拳」——工业标准做法。


    📚 经验法则(以后照着做)

    法则1:Nginx 静态 location 必须写在代理前面

    # ✅ 正确顺序
    location ^~ /Project/static/ { ... }  # 1. 精确匹配优先
    location /Project/ { ... }            # 2. 模糊匹配在后

    法则2:静态文件用写死路径,动态链接用 url_for

    <!-- ✅ 静态资源:直接写死,不走 Flask -->
    <link href="/Project/static/css/style.css">
    
    <!-- ✅ 动态链接:用 url_for,自动加前缀 -->
    <a href="{{ url_for('view_page', id=1) }}">

    法则3:BASE_PATH 是给动态路由用的,静态文件不要

    # ✅ 只有 Flask 需要知道
    export BASE_PATH=/Project
    
    # ❌ Nginx 静态 location 完全不需要
    # alias 直接写绝对路径即可

    法则4:新项目部署检查清单

    1. [ ] Nginx 加静态 location(^~ /项目名/static/
    2. [ ] Nginx 加代理 location(/项目名/
    3. [ ] 静态写在代理前面
    4. [ ] HTML 里写死 /项目名/static/xxx
    5. [ ] Flask 启动前 export BASE_PATH=/项目名
    6. [ ] 测试静态文件直出(应200)
    7. [ ] 测试动态页面(应200)

    🎖️ 本次BUG修复贡献者

    问题发现:浏览器控制台 404
    定位过程:curl → Nginx 404 → 检查 alias → 发现少斜杠
    深层原因:发现静态请求进了 Flask
    解决方案:Nginx 静态直出 + HTML写死路径
    最终验证:静态文件 200,动态页面 200,完美

    耗时:一下午
    收获:多项目反代的标准模式固化


    📌 以后再也不犯的错误

    1. Nginx 静态 location 不加 ^~(被正则覆盖)
    2. 静态 location 写在代理后面(永远匹配不到)
    3. alias 路径末尾少斜杠staticcss 经典错误)
    4. HTML 里对静态文件用 url_for(本末倒置)
    5. Flask 里写死 BASE_PATH(环境不通用)
    6. 试图让 Flask 既当路由又当静态服务器(性能差、易错)

    这次 BUG 修完,你已经是 Nginx + Flask 多项目部署的专家了。
    这套组合拳可以复用到以后所有项目,不会再卡一下午了。

    One must still have chaos in oneself to be able to give birth to a dancing star.

    人必须在自身中仍有混沌,才能生出跳舞的星星。

    —— 尼采

    *当初为了项目反代问题处理一下午,今天又处理一下午,要吸取教训了*

  • LifeDrive

    我常常想的太多 做的太少 太多想法没得到记录 太多想法没得到事实践 那么这种思考本身就是种逃避 我希望LifeDrive能将我那些天马行空的空中楼阁般的想法托举到地上 虽然这些楼阁大抵是云做的虚无缥缈且摇摇欲坠 我将为我的想法接生 这是就是我对LifeDrive的寄语

    *该图为我生日时NASA每日一图 是猫爪星云!

  • 健身房小程序-更新日志

    最终,幸福不在于石头是否停留在山顶,而在于你每一次走向山脚的步伐。”

    -读《坦纳伯斯寓言》有感

    为有牺牲多壮志,敢教日月换新天.

    学了下怎么用agent 现在AI技术真的是日新月异
    搭建了一个框架 现在往里面填充血肉
    具体更新了(已经链接云函数实现的):
    1. 获取微信账号注册登录 头像名称修改
    2. 教练页面的详细展示 图片插入与修改
    头像修改 介绍修改等
    3. 修复了2个框架BUG
    妥协性修复了一个BUG

    未来更新目标

    增值版

    内部植入AI agent根据健身房器械和教练
    制定和推荐训练计划

    Education is not preparation for life; education is life itself.

    教育不是为生活做准备,教育就是生活本身。

    —— 约翰·杜威
  • 忠诚与远见的赛会

    ——摘自《坦纳伯斯寓言》

    坎迪亚大公颁布谕令,要听取麾下各位伯爵的领地发展汇报,美其名曰“未来之路”。坦纳伯斯伯爵恰逢其会,饶有兴致地旁观了这场被他私下称为“伯爵领主演说大赛”的闹剧。

    会场设于一座旧礼堂,布置得极为潦草。甚至连让汇报者远程指示羊皮卷轴的“指挥棒”都未曾准备,演讲者需跑到幕布旁,狼狈地对着操作员比划:“往上!往下!就是那段!”

    第一位被抽中的,正是以务实著称的莱恩纳德领主。许是命运的嘲弄,他刚踏上那简陋的舞台,便被一根翘起的木板绊了个趔趄,引得台下诸位贵族哄堂大笑。他稳住心神,开始展示。他的汇报如同他的领地一般,没有华丽的辞藻,只有扎实的数据:新垦农田的亩数、疏通河渠的长度、仓库里实实在在的粮食储备。他体恤民情,治下虽不为最富,但民生最为安稳。

    然而,他下去之后,后续的汇报者才真正展现了何为“未来之路”。

    一位伯爵完全无视汇报要求,滔滔不绝地讲述他狩猎时遇到的奇闻异事,大公听得津津有味,并未出言打断。另一位伯爵,则花了四分之三的时间来详述他与某强大王国君主的远方血缘关系,这番与发展毫无关联的长篇大论,却频频引来惊叹。最后,他宣称自己的领地必将成为大公领之首,描绘了一幅堪比英雄史诗的宏伟蓝图。

    坦纳伯斯伯爵清楚地记得,自己上月才从那位伯爵的领地游历归来,所见唯有饥民与荒田,与他描绘的这番盛景,仅有几处吹嘘出来的皮毛相似。

    可偏偏,大公殿下就爱听这些。最终,那位“未来王”伯爵获得了最高评价,而首个登场、勤勤恳恳的莱恩纳德,名字却落在了榜单的最末尾。

    大会结束时,仁慈的大公为每一位“愿意前来展示忠诚”的伯爵,都颁发了一张署有他大名、写满赞誉之词的“忠诚领状”。

    坦纳伯斯伯爵站在廊柱的阴影里,看见莱恩纳德面无表情地接过那张写着最低评价的领状,随即在转身的瞬间,将它紧紧揉成一团废纸。他脸色铁青,带着寥寥几位部下,沉默地离开了会场。

    坦纳伯斯的目光最后落在了马厩。他注意到一个有趣的细节:其他伯爵那些善于夸夸其谈的使者,他们的坐骑大多毛色光亮却肩骨嶙峋;唯有莱恩纳德伯爵那几匹不算雄壮的战马,脖颈滚圆,四肢强健,皮毛下涌动着真正的、沉默的力量。

    (坦纳伯斯伯爵的结语)

    沼泽女巫在熬煮令人产生幻觉的汤药时曾说:在盛大的舞台上,真相往往因过于朴实而成为不合时宜的绊脚石。最能取悦上位者的,从来不是仓廪里实实在在的谷物,而是夜空中那颗永远无法企及、却足以让人沉醉的金色苹果。你若想获得嘉奖,就该学会豢养会唱歌的云雀,而非埋头喂养那些只会下蛋的母鸡。