[{"content":"昨天手误 rm -rf ~/.claude，把 Claude Code 的整个配置目录删了——CLAUDE.md、MCP 配置、settings、自定义 skills，几乎全部归零。后来靠 VS Code 的缓存找回了一点，但也只够勉强止损。\n非常痛，吸取教训，做了两件事。\n用 rmtrash 替代 rm 安装 rmtrash，让 rm 变为移到废纸篓而非直接删除：\n1 brew install rmtrash 在 ~/.zshrc 中加入 alias：\n1 alias rm=\u0026#39;rmtrash\u0026#39; 这样即使再次误删，也能从废纸篓恢复。\n创建配置仓库 将需要版本管理的配置文件抽取到独立仓库，通过同步机制映射回各工具期望的路径。\n仓库结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 claude-config/ ├── claude/ # Claude Code 配置 │ ├── CLAUDE.md │ ├── settings.json │ └── mcp.json ├── codebuddy/ # CodeBuddy Code 配置 │ ├── CODEBUDDY.md │ ├── AGENTS.md │ ├── settings.json │ └── mcp.json ├── rules/ # 共享规则（两个工具复用） ├── skills/ # 共享 skills（两个工具复用） ├── sync.conf # 同步规则声明 └── sync.sh # 同步脚本（含 launchd agent 管理） 实现 配置分离，规则共享：claude/ 和 codebuddy/ 各自维护独立的 settings 和 MCP 配置，但 rules/ 和 skills/ 目录两者共享。\n同步 Claude Code / CodeBuddy 使用原子写入（write-file-atomic）保存配置文件——写临时文件再 rename 覆盖目标。这会破坏硬链接和文件级软链接。因此配置文件不使用链接，改为 launchd WatchPaths + 双向 rsync 实现自动同步。\nsync.conf 用简单的 DSL 声明两种同步关系：\n[sync] — 文件双向同步，按 mtime 判断方向，较新者覆盖较旧者 [link] — 目录软链接（rules/、skills/），不受原子写入影响 用法：\n1 2 3 4 5 6 7 8 9 10 11 # 手动执行一次同步 bash sync.sh # 安装 launchd agent（文件变更自动触发同步） bash sync.sh install # 查看 agent 状态 bash sync.sh status # 卸载 agent bash sync.sh uninstall 安装后，任何被监听文件的变更都会在 3 秒内自动触发双向同步，零人工干预。\n日志位于 ~/Library/Logs/claude-config/sync.log。\n","date":"2026-04-18T18:00:00+08:00","image":"/2026/claude-config-management/cover.png","permalink":"/2026/claude-config-management/","title":"痛，在 Claude Code 配置目录被误删之后"},{"content":"迁移新Blog啦🎉 WordPress上的最后一篇博客停留在2014年，算起来也是12年前了，中间除了写过几篇公众号，没有其它记录了。\n我对个人数据一直有备份，WordPress之前的Sablog的数据还在，但更早以前发布在外部平台的，百度Hi上的彻底没了。这些年，陆续有业务关停，大的像猫扑、网易的Blog和相册、天涯等，伴随着的就是数据消失。这些丢弃的数据，对个人来还是挺可惜的，想要保留好，还是回归博客，放在自己的手里更安全。\n新年折腾了一下新博客，基于 Hugo + Git + Workflows，发布方式非常符合现在的使用习惯。尤其是日常的 Markdown 笔记，转一下就可以发布出来。\nGitHub Pages需要公开仓库，可以先将私有仓库同步到GitHub上，再通过Cloudflare的Pages搞个备份。\n回顾2025、展望2026 博客搞好了，正好也是新的一年开始，回顾一下2025，展望一下2026。 2025最大的变化，就是对AI的力量有了具象化的感受。\n工作中大量使用GitHub Copilot。在片段代码、具体含义实现基本上可以替代人，有时候还会给一些“原来还可以这样写”的惊喜。 2025年小朋友升到了五年级，进入了另一个阶段，有很多自己的主意。暑假通关了《双人成行》，好游戏带给人的驱动力还是很特别的。 回顾2件让我感受到情绪上涌的事情，一件是省心租的合约、另一件是高铁。这两件事在发生时，自己能明显感受到，也控制住了，应对的非常好。 不要求人也不预设人，只接受人的参差，只专注事，破除焦虑，接受就是这个结果。 2026年，我和老婆的工作都会迎来很大变化，继续保持耐心、专注、开放以及学习的心态，继续在事上练。\n","date":"2026-02-24T11:45:40+08:00","image":"/2026/new-year-2026/e1.jpg","permalink":"/2026/new-year-2026/","title":"迁移新Blog啦🎉，回顾2025、展望2026"},{"content":" 重要 👍Gemini 3，脚本全是它写的！\n主要功能 1. 自动生成 Slug 脚本会自动读取文章的 title：\n如果标题是中文，它可以利用 deep-translator 自动将其翻译成英文，然后生成 Slug。 或者使用 --pinyin 参数将其转换为拼音 Slug。 它还会智能判断是否已经存在 Slug，避免覆盖手动设置好的内容。 2. 自动下载封面图 对于使用了 post-bundle 格式（即文件夹形式）的文章：\n脚本会先检查目录中是否已经存在 cover.jpg、feature.jpg 等常见的封面文件。 如果没有，且 Front Matter 中的 image 字段为空，它会根据文章标题生成关键词，从 Picsum 下载一张高质量的随机图片作为封面。 3. 支持多语言 脚本考虑到了 Hugo 的多语言特性，如果在同一个文件夹下有 index.zh.md 和 index.en.md，它们会共享下载好的封面图，避免重复下载。\n如何使用 安装依赖：\n1 pip install python-frontmatter python-slugify deep-translator requests 运行脚本：\n仅完善 Slug: python3 tools/front_matter.py content --slug 一键全搞定: python3 tools/front_matter.py content --slug --image --add-missing 脚本代码 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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 import os import argparse import logging import sys import re import yaml import requests from pathlib import Path # Try to import required libraries, if missing, we will catch ImportError later or let it fail try: import frontmatter from slugify import slugify from deep_translator import GoogleTranslator except ImportError: pass logging.basicConfig(level=logging.INFO, format=\u0026#39;%(asctime)s - %(levelname)s - %(message)s\u0026#39;) logger = logging.getLogger(__name__) class HugoContentProcessor: def __init__(self, content_dir, enable_slug=False, enable_pinyin=False, enable_image=False, add_missing=False): self.content_dir = Path(content_dir) self.enable_slug = enable_slug self.enable_pinyin = enable_pinyin # New flag for Pinyin self.enable_image = enable_image self.add_missing = add_missing # Initialize translator only if needed to avoid overhead or connection checks immediately self._translator = None @property def translator(self): if self._translator is None: self._translator = GoogleTranslator(source=\u0026#39;auto\u0026#39;, target=\u0026#39;en\u0026#39;) return self._translator def process(self): if not self.content_dir.exists(): logger.error(f\u0026#34;Directory not found: {self.content_dir}\u0026#34;) return logger.info(f\u0026#34;Scanning directory: {self.content_dir}\u0026#34;) for root, dirs, files in os.walk(self.content_dir): for file in files: if file.endswith((\u0026#39;.md\u0026#39;, \u0026#39;.markdown\u0026#39;)): file_path = Path(root) / file self.process_file(file_path) def process_file(self, file_path): try: # Read file content as text to preserve formatting content = file_path.read_text(encoding=\u0026#39;utf-8\u0026#39;) # Extract Front Matter block using Regex # Matches content between the first two \u0026#39;---\u0026#39; lines match = re.search(r\u0026#39;^---\\s*\\n(.*?)\\n---\\s*\\n\u0026#39;, content, re.DOTALL) if not match: logger.debug(f\u0026#34;Skipped (no front matter): {file_path}\u0026#34;) return fm_text = match.group(1) original_fm_text = fm_text # Use PyYAML just to parse the structure for logic checking try: metadata = yaml.safe_load(fm_text) except yaml.YAMLError: logger.warning(f\u0026#34;Skipped (invalid YAML): {file_path}\u0026#34;) return if not metadata: return if self.enable_slug: fm_text = self._process_slug_logic(fm_text, metadata) if self.enable_image: fm_text = self._process_image_logic(fm_text, metadata, file_path) if fm_text != original_fm_text: # Reconstruct the full file content new_content = content[:match.start(1)] + fm_text + content[match.end(1):] file_path.write_text(new_content, encoding=\u0026#39;utf-8\u0026#39;) logger.info(f\u0026#34;Updated: {file_path}\u0026#34;) else: logger.debug(f\u0026#34;Skipped (no changes): {file_path}\u0026#34;) except Exception as e: logger.error(f\u0026#34;Failed to process {file_path}: {e}\u0026#34;) def _process_slug_logic(self, fm_text, metadata): # 1. Check if \u0026#39;slug\u0026#39; key exists slug_exists = \u0026#39;slug\u0026#39; in metadata if not slug_exists and not self.add_missing: return fm_text # 2. Check if slug is already set (non-empty) existing_slug = metadata.get(\u0026#39;slug\u0026#39;) if existing_slug and str(existing_slug).strip(): return fm_text # 3. Get Title for generation title = metadata.get(\u0026#39;title\u0026#39;) if not title: return fm_text # --- Generation Logic --- logger.info(f\u0026#34;Generating slug for title: \u0026#39;{title}\u0026#39;\u0026#34;) slug_text = title # Check if translation is needed # Logic: If pinyin is enabled, skip translation and use title directly (slugify converts to pinyin) # Otherwise, translate non-English titles to English. if not self.enable_pinyin and not self.is_english(title): try: # Translate to English translated = self.translator.translate(title) slug_text = translated logger.info(f\u0026#34;Translated \u0026#39;{title}\u0026#39; to \u0026#39;{slug_text}\u0026#39;\u0026#34;) except Exception as e: logger.warning(f\u0026#34;Translation failed for \u0026#39;{title}\u0026#39;: {e}. Falling back to raw title.\u0026#34;) new_slug = slugify(slug_text) if not new_slug: return fm_text # --- Text Replacement --- slug_pattern = r\u0026#39;^(\\s*slug:)(\\s*)$\u0026#39; def replace_callback(m): return f\u0026#34;{m.group(1)} {new_slug}\u0026#34; new_fm_text, count = re.subn(slug_pattern, replace_callback, fm_text, count=1, flags=re.MULTILINE) # If not found but we want to add new slugs if count == 0 and self.add_missing and not slug_exists: # Add slug field to the end of front matter if not fm_text.endswith(\u0026#39;\\n\u0026#39;): new_fm_text = fm_text + \u0026#39;\\n\u0026#39; else: new_fm_text = fm_text new_fm_text += f\u0026#34;slug: {new_slug}\\n\u0026#34; return new_fm_text def _process_image_logic(self, fm_text, metadata, file_path): # 1. Check if \u0026#39;image\u0026#39; key exists image_exists = \u0026#39;image\u0026#39; in metadata if not image_exists and not self.add_missing: return fm_text # 2. Check if image is already set (non-empty) existing_image = metadata.get(\u0026#39;image\u0026#39;) if existing_image and str(existing_image).strip(): return fm_text # --- Check for existing image in the same directory (Multi-language support) --- # If we have index.zh.md and index.en.md, they share the same folder. # If cover.jpg exists, we should just use it instead of downloading a new one. parent_dir = file_path.parent existing_cover = self._find_existing_cover(parent_dir) if existing_cover: logger.info(f\u0026#34;Found existing image for {file_path.name}: {existing_cover}\u0026#34;) image_filename = existing_cover else: # --- Download Logic --- title = metadata.get(\u0026#39;title\u0026#39;, \u0026#39;\u0026#39;) # Prepare prompt for image generation prompt = self._generate_image_prompt(title, content_preview=None) # We could pass content if needed logger.info(f\u0026#34;Downloading image for: {file_path.name} with prompt: \u0026#39;{prompt}\u0026#39;\u0026#34;) image_filename = self._download_image(parent_dir, prompt) if not image_filename: return fm_text # --- Text Replacement --- image_pattern = r\u0026#39;^(\\s*image:)(\\s*)$\u0026#39; def replace_callback(m): return f\u0026#34;{m.group(1)} {image_filename}\u0026#34; new_fm_text, count = re.subn(image_pattern, replace_callback, fm_text, count=1, flags=re.MULTILINE) # If not found but we want to add new image field if count == 0 and self.add_missing and not image_exists: # Add image field to the end of front matter if not fm_text.endswith(\u0026#39;\\n\u0026#39;): new_fm_text = fm_text + \u0026#39;\\n\u0026#39; else: new_fm_text = fm_text new_fm_text += f\u0026#34;image: {image_filename}\\n\u0026#34; else: new_fm_text = fm_text if count == 0 else new_fm_text return new_fm_text def _find_existing_cover(self, directory): \u0026#34;\u0026#34;\u0026#34;Look for common cover image filenames in the directory.\u0026#34;\u0026#34;\u0026#34; common_names = [\u0026#39;cover.jpg\u0026#39;, \u0026#39;cover.png\u0026#39;, \u0026#39;feature.jpg\u0026#39;, \u0026#39;featured.jpg\u0026#39;, \u0026#39;thumbnail.jpg\u0026#39;] # Also check for any file starting with cover_ try: for file in directory.iterdir(): if file.is_file(): if file.name.lower() in common_names: return file.name if file.name.lower().startswith(\u0026#39;cover\u0026#39;) and file.suffix.lower() in [\u0026#39;.jpg\u0026#39;, \u0026#39;.png\u0026#39;, \u0026#39;.jpeg\u0026#39;, \u0026#39;.webp\u0026#39;]: return file.name except Exception: pass return None def _generate_image_prompt(self, title, content_preview=None): \u0026#34;\u0026#34;\u0026#34;Translate title to English to serve as a prompt.\u0026#34;\u0026#34;\u0026#34; if not title: return \u0026#34;abstract minimal background\u0026#34; prompt = title if not self.is_english(title): try: translated = self.translator.translate(title) prompt = translated except Exception as e: logger.warning(f\u0026#34;Translation failed for prompt \u0026#39;{title}\u0026#39;: {e}\u0026#34;) # Add some style keywords to make the image better for a blog cover return f\u0026#34;{prompt}, aesthetic, minimalist, high quality, 4k, wallpaper\u0026#34; def _download_image(self, target_dir, prompt): # Determine strict save path filename = \u0026#34;cover.jpg\u0026#34; save_path = target_dir / filename # Ensure unique filename if cover.jpg exists and we are genuinely downloading new one # (Though logic outside should often catch existing ones, this is a safety net) counter = 1 while save_path.exists(): filename = f\u0026#34;cover_{counter}.jpg\u0026#34; save_path = target_dir / filename counter += 1 # Common Headers to avoid being blocked (403/503) headers = { \u0026#39;User-Agent\u0026#39;: \u0026#39;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36\u0026#39; } # Download from Picsum (Random high quality placeholders) try: logger.info(f\u0026#34;Downloading image from Picsum (Random)\u0026#34;) url = \u0026#34;https://picsum.photos/1200/630\u0026#34; response = requests.get(url, headers=headers, timeout=20, allow_redirects=True) if response.status_code == 200: with open(save_path, \u0026#39;wb\u0026#39;) as f: f.write(response.content) logger.info(f\u0026#34;Saved image from Picsum to: {save_path}\u0026#34;) return filename except Exception as e: logger.error(f\u0026#34;Download attempt failed: {e}\u0026#34;) return None def is_english(self, text): \u0026#34;\u0026#34;\u0026#34;Check if text contains only ASCII characters.\u0026#34;\u0026#34;\u0026#34; try: text.encode(encoding=\u0026#39;utf-8\u0026#39;).decode(\u0026#39;ascii\u0026#39;) except UnicodeDecodeError: return False return True def check_dependencies(): missing = [] try: import frontmatter except ImportError: missing.append(\u0026#34;python-frontmatter\u0026#34;) try: from slugify import slugify except ImportError: missing.append(\u0026#34;python-slugify\u0026#34;) try: from deep_translator import GoogleTranslator except ImportError: missing.append(\u0026#34;deep-translator\u0026#34;) if missing: print(\u0026#34;Missing required libraries. Please install them:\u0026#34;) print(f\u0026#34;pip install {\u0026#39; \u0026#39;.join(missing)}\u0026#34;) sys.exit(1) if __name__ == \u0026#34;__main__\u0026#34;: check_dependencies() parser = argparse.ArgumentParser( description=\u0026#34;Process Hugo content front matter to generate English slugs from titles and download cover images.\u0026#34;, formatter_class=argparse.RawTextHelpFormatter, epilog=\u0026#34;\u0026#34;\u0026#34; Examples: 1. Default mode (No action by default now, must specify flag): python3 tools/front_matter.py content --slug 2. Process images only: python3 tools/front_matter.py content --image 3. Add missing slugs and images: python3 tools/front_matter.py content --slug --image --add-missing \u0026#34;\u0026#34;\u0026#34; ) parser.add_argument(\u0026#34;dir\u0026#34;, nargs=\u0026#34;?\u0026#34;, default=\u0026#34;content\u0026#34;, help=\u0026#34;Content directory to process (relative to current path)\u0026#34;) parser.add_argument(\u0026#34;--slug\u0026#34;, action=\u0026#34;store_true\u0026#34;, help=\u0026#34;Enable slug processing\u0026#34;) parser.add_argument(\u0026#34;--pinyin\u0026#34;, action=\u0026#34;store_true\u0026#34;, help=\u0026#34;Use Pinyin for slug generation instead of English translation\u0026#34;) parser.add_argument(\u0026#34;--image\u0026#34;, action=\u0026#34;store_true\u0026#34;, help=\u0026#34;Enable image processing (downloads from Picsum)\u0026#34;) parser.add_argument(\u0026#34;--add-missing\u0026#34;, action=\u0026#34;store_true\u0026#34;, help=\u0026#34;Add fields (\u0026#39;slug\u0026#39;/\u0026#39;image\u0026#39;) if they are missing\u0026#34;) args = parser.parse_args() # If neither is specified, maybe warn? Or just do nothing? if not (args.slug or args.image): parser.print_help() sys.exit(0) # Resolve absolute path base_dir = Path(os.getcwd()) target_dir = base_dir / args.dir processor = HugoContentProcessor( target_dir, enable_slug=args.slug, enable_pinyin=args.pinyin, enable_image=args.image, add_missing=args.add_missing ) processor.process() ","date":"2026-02-15T17:47:11+08:00","image":"/2026/hugo-script-automatically-improve-slug-and-image/cover.webp","permalink":"/2026/hugo-script-automatically-improve-slug-and-image/","title":"Hugo小脚本：自动完善slug及image属性"},{"content":"Webp转换及水印 Hugo图片优化：Fancybox、Webp和水印 | 月青悠 完全按照这个操作，写的非常清晰。有一点改动的是，我有图片Exif不是空，但里面的Orientation是nil，另外将水印调整到了右下角。\n不打包进publish文件夹 Set page build options (publishResources) in the site (or theme) configuration? - support - HUGO Build options 我按这个配置一直不生效。最后发现是stack模板加载图片helper/image会引用RelPermalink，导到打进了publish目录。\n修改点 hugo.toml 1 2 3 4 5 6 7 # https://discourse.gohugo.io/t/set-page-build-options-publishresources-in-the-site-or-theme-configuration/55942/5 # https://gohugo.io/content-management/build-options/ [[cascade]] [cascade.target] path = \u0026#34;/**\u0026#34; [cascade.build] publishResources = false render-image.html 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 {{/* {{- $image := partial \u0026#34;helper/image\u0026#34; (dict \u0026#34;Resources\u0026#34; .Page.Resources \u0026#34;Image\u0026#34; .Destination) -}} */}} {{- $alt := .PlainText | safeHTML -}} {{/* 将helper/image中的访问图片的代码copy过，防止访问RelPermalink，原图又会打包进去 */}} {{ $url := urls.Parse .Destination }} {{ $resource := \u0026#34;\u0026#34; }} {{ if not (in (slice \u0026#34;https\u0026#34; \u0026#34;http\u0026#34;) $url.Scheme) }} {{ $resource = .Page.Resources.Get (printf \u0026#34;%s\u0026#34; (.Destination | safeURL)) }} {{ else }} {{ with try (resources.GetRemote $url) }} {{ with .Err }} {{ errorf \u0026#34;%s\u0026#34; . }} {{ else with .Value }} {{ $resource = . }} {{ else }} {{ errorf \u0026#34;Unable to get remote resource %q\u0026#34; $url }} {{ end }} {{ end }} {{ end }} {{/* 获取当前页面匹配.Destination的资源图像 */}} {{ $hugoImage := $resource }} {{/* 从资源中获取logo图像，透明背景图片效果最好 */}} {{/* 该图像需要放置在根目录的assets/images目录下 */}} {{ $logo := (resources.Get \u0026#34;img/watermark.png\u0026#34;) }} {{/* 检查图像的媒体类型是否不是gif或svg */}} {{ if not (in (slice \u0026#34;gif\u0026#34; \u0026#34;svg\u0026#34;) $hugoImage.MediaType.SubType) }} {{/* 保留原始图像的引用 */}} {{ $rawImage := $hugoImage }} {{/* 计算图像高度的80%，并确保最小值为250 */}} {{ $size := math.Round (mul $hugoImage.Height 0.80) }} {{ $size := cond (ge $size 250) $size 250.0 }} {{/* 调整logo的透明度为50% */}} {{ $logo := $logo.Filter (images.Opacity 0.5) }} {{/* 调整logo的大小，高度为计算出的$size，保持纵横比，圆角半径为30 */}} {{/* $logo := $logo.Resize (printf \u0026#34;%.0fx png r30\u0026#34; $size) */}} {{/* 如果原始图像有EXIF信息 */}} {{ with $rawImage.Exif }} {{/* 根据图像的方向调整图像的大小和旋转 */}} {{ if or (eq .Tags.Orientation 1) (eq .Tags.Orientation nil) }} {{ $hugoImage = $hugoImage.Resize (print $hugoImage.Width \u0026#34;x\u0026#34; $hugoImage.Height \u0026#34; webp\u0026#34;) }} {{ else if eq .Tags.Orientation 3 }} {{ $hugoImage = $hugoImage.Resize (print $hugoImage.Width \u0026#34;x\u0026#34; $hugoImage.Height \u0026#34; webp r180\u0026#34;) }} {{ else if eq .Tags.Orientation 6 }} {{ $hugoImage = $hugoImage.Resize (print $hugoImage.Height \u0026#34;x\u0026#34; $hugoImage.Width \u0026#34; webp r270\u0026#34;) }} {{ else if eq .Tags.Orientation 8 }} {{ $hugoImage = $hugoImage.Resize (print $hugoImage.Height \u0026#34;x\u0026#34; $hugoImage.Width \u0026#34; webp r90\u0026#34;) }} {{ end }} {{ else }} {{/* 如果没有EXIF信息，则只调整大小，不旋转 */}} {{ $hugoImage = $hugoImage.Resize (print $hugoImage.Width \u0026#34;x\u0026#34; $hugoImage.Height \u0026#34; webp\u0026#34;) }} {{ end }} {{/* 在图像上添加logo水印，位置在图像的右下，fm可以通过unwatermarked关闭水印 */}} {{ if and (ge $hugoImage.Width 500) (ne .Page.Params.unwatermarked true) }} {{ $hugoImage = $hugoImage.Filter (images.Overlay $logo (sub $hugoImage.Width $logo.Width) (sub $hugoImage.Height $logo.Height)) }} {{ end }} {{ end }} {{ $image := $hugoImage }} {{/* SVG and external images won\u0026#39;t work with gallery layout, because their width and height attributes are unknown */}} {{- $galleryImage := and $image.Height $image.Width -}} \u0026lt;img src=\u0026#34;{{ $image.RelPermalink }}\u0026#34; {{ with $image.Width }}width=\u0026#34;{{ . }}\u0026#34;{{ end }} {{ with $image.Height }}height=\u0026#34;{{ . }}\u0026#34;{{ end }} loading=\u0026#34;lazy\u0026#34; {{ with $alt }} alt=\u0026#34;{{ . }}\u0026#34; {{ end }} {{ if $galleryImage }} class=\u0026#34;gallery-image\u0026#34; data-flex-grow=\u0026#34;{{ div (mul $image.Width 100) $image.Height }}\u0026#34; data-flex-basis=\u0026#34;{{ div (mul $image.Width 240) $image.Height }}px\u0026#34; {{ end }} \u0026gt; 清理exif Image processing 转换过程中会hugo会删除掉exif，不过为了隐私，最好还是自已先删掉\nexiftool -r -all= ./\n","date":"2026-02-14T00:31:55+08:00","image":"/2026/hugo-stack-image-process/cover.webp","permalink":"/2026/hugo-stack-image-process/","title":"图片优化：Webp、水印、清除exif及原图不打包"},{"content":"Gitea Actions与GitHub Actions相似且兼容，actions基本都可以复用\n安装 Runner 1 2 wget https://dl.gitea.com/act_runner/0.2.13/act_runner-0.2.13-linux-amd64 -O ./act_runner ./act_runner generate-config \u0026gt; config.yaml 注册 在Gitea中获取token：管理后台 -\u0026gt; Actions -\u0026gt; Runners -\u0026gt; Create new Runner，可以获取token Gitea instance URL, 注意使用不要localhost，容器会请求Gitea仓库，可以使用内网IP 1 2 3 4 5 # 注册 ./act_runner register # 运行容器 echo \u0026#34;nohup ./act_runner daemon --config config.yaml \u0026gt; ./runner.log 2\u0026gt;\u0026amp;1 \u0026amp;\u0026#34; \u0026gt; start.sh chmod +x start.sh \u0026amp;\u0026amp; ./start.sh workflow 支持部署到多台机器 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 87 88 89 90 name: Build ❗Formal blog and Deploy on: push: branches: - main pull_request: branches: - main env: OUTPUT_DIR: hugo_output PUBLISH_DIR: hugo_output # 远端目录 LINODE_PUBLISH_DIR: ${{ secrets.M1_REMOTE_TARGET }}/${{ env.PUBLISH_DIR }} jobs: build: # https://gitea.com/gitea/runner-images runs-on: ubuntu-latest env: HUGO_CACHEDIR: /tmp/hugo_cache # \u0026lt;- Define the env variable here, so that Hugo\u0026#39;s cache dir is now predictible in your workflow and doesn\u0026#39;t depend on the Hugo\u0026#39;s version you\u0026#39;re using. steps: - run: echo \u0026#34;🎉 任务已被 ${{ gitea.event_name }} 事件自动触发。\u0026#34; - run: echo \u0026#34;🔎 您的分支是 ${{ gitea.ref }}，仓库是 ${{ gitea.repository }}。\u0026#34; - run: echo \u0026#34;👤 此次提交由 ${{ gitea.actor }} 完成。\u0026#34; - name: Checkout Repository uses: actions/checkout@v4 with: submodules: true # 增加 Hugo 模块缓存 - uses: actions/cache@v4 with: path: ${{ env.HUGO_CACHEDIR }} # \u0026lt;- Use the same env variable just right here key: ${{ runner.os }}-hugomod-${{ hashFiles(\u0026#39;**/go.sum\u0026#39;) }} restore-keys: | ${{ runner.os }}-hugomod- - name: Validate Environment Variables run: | if [ -z \u0026#34;${{ env.OUTPUT_DIR }}\u0026#34; ] || [ -z \u0026#34;${{ env.LINODE_PUBLISH_DIR }}\u0026#34; ]; then echo \u0026#34;❌ 错误：目录没有设置！\u0026#34; echo \u0026#34;OUTPUT_DIR: ${{ env.OUTPUT_DIR }}\u0026#34; echo \u0026#34;LINODE_PUBLISH_DIR: ${{ env.LINODE_PUBLISH_DIR }}\u0026#34; exit 1 fi echo \u0026#34;✅ 环境变量验证通过\u0026#34; echo \u0026#34;OUTPUT_DIR: ${{ env.OUTPUT_DIR }}\u0026#34; echo \u0026#34;M1_PUBLISH_DIR: ${{ env.LINODE_PUBLISH_DIR }}\u0026#34; - name: Install Hugo uses: peaceiris/actions-hugo@v2 with: hugo-version: \u0026#39;latest\u0026#39; extended: true with-deps: true - name: Clear Previous Build Output run: | mkdir -p ${{ env.OUTPUT_DIR }} rm -rf ${{ env.OUTPUT_DIR }}/* - name: Build Hugo Site run: hugo --minify --destination ${{ env.OUTPUT_DIR }} --cleanDestinationDir # - name: Install rsync # run: sudo apt-get update \u0026amp;\u0026amp; sudo apt-get install -y rsync # # - name: Deploy Hugo Site # run: | # mkdir -p ${{ env.PUBLISH_DIR }} # rsync -av --delete --checksum --no-times ${{ env.OUTPUT_DIR }}/ ${{ env.PUBLISH_DIR }}/ - name: Deploy to Linode uses: easingthemes/ssh-deploy@main with: SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} ARGS: \u0026#34;-rlgoDzvc -i --delete\u0026#34; SOURCE: ${{ env.OUTPUT_DIR }}/* REMOTE_HOST: ${{ secrets.LINODE_REMOTE_HOST }} REMOTE_PORT: ${{ secrets.LINODE_REMOTE_PORT }} REMOTE_USER: ${{ secrets.REMOTE_USER }} TARGET: ${{ env.LINODE_PUBLISH_DIR }} EXCLUDE: \u0026#34;/dist/, /node_modules/\u0026#34; - name: List files in the repository run: | ls ${{ gitea.workspace }} - run: echo \u0026#34;🍏 执行结束，此任务的状态是 ${{ job.status }}。\u0026#34; Ubuntu换源 部署阿里云，如果慢的话，可以替换一下源，有点效果不大\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 jobs: build: steps: - name: Configure Aliyun Mirror run: | sudo tee /etc/apt/sources.list.d/ubuntu.sources \u0026gt;/dev/null \u0026lt;\u0026lt;EOF Types: deb URIs: http://mirrors.cloud.aliyuncs.com/ubuntu/ Suites: noble noble-updates noble-backports Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg Types: deb URIs: http://mirrors.cloud.aliyuncs.com/ubuntu/ Suites: noble-security Components: main restricted universe multiverse Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg EOF echo \u0026#34;✅ 已配置阿里云内网镜像源\u0026#34; sudo apt-get update VPS配置 1 2 3 4 5 6 7 8 9 10 11 12 13 # 本地生成密钥 ssh-keygen -t ed25519 -C \u0026#34;xx@xx.com\u0026#34; # 创建账号 useradd -m -s /bin/bash git # 上传公钥 su git mkdir -p ~/.ssh \u0026amp;\u0026amp; echo \u0026#34;ssh-ed25519 xxxx\u0026#34; \u0026gt; ~/.ssh/authorized_keys chmod 700 ~/.ssh \u0026amp;\u0026amp; chmod 600 ~/.ssh/authorized_keys #验证 ssh git@xxx -i ~/.keys/git 参考文档 Actions | Gitea Documentation GitHub Actions 入门教程 - 阮一峰的网络日志) Using secrets in GitHub Actions - GitHub Docs ","date":"2026-02-10T14:51:25+08:00","image":"/2026/gitea-actions/cover.webp","permalink":"/2026/gitea-actions/","title":"在Gitea中使用workflows实现持续部署"},{"content":"二种转换方式，一种是直接获得vector首地址的指针，别一种再copy一份\nvector\u0026lt;char*\u0026gt; vecChar; char* p1 = \"p12113\"; vecChar.push_back(p1); char* p2 = \"fdsafdsa\"; vecChar.push_back(p2); char* p3 = \"sasssssss\"; vecChar.push_back(p3); char** a1 = \u0026vecChar[0]; for (int z = 0; z \u0026lt; vecChar.size(); z++) { cout \u0026lt;\u0026lt; \"pointer: \" \u0026lt;\u0026lt; *(a1 + z) \u0026lt;\u0026lt; \" \"\u0026lt;\u0026lt; z \u0026lt;\u0026lt; \" \" \u0026lt;\u0026lt; vecChar[z] \u0026lt;\u0026lt; endl; } char* arr[vecChar.size()]; std::copy(vecChar.begin(), vecChar.end(), arr); for (int z = 0; z \u0026lt; vecChar.size(); z++) { cout \u0026lt;\u0026lt; \"copy: \" \u0026lt;\u0026lt; arr[z] \u0026lt;\u0026lt; \" \"\u0026lt;\u0026lt; z \u0026lt;\u0026lt; \" \" \u0026lt;\u0026lt; vecChar[z] \u0026lt;\u0026lt; endl; } ","date":"2014-07-23T01:00:00Z","permalink":"/2014/07/c%E7%AC%94%E8%AE%B0%EF%BC%9Avector%E8%BD%AC%E6%95%B0%E7%BB%84/","title":"c++笔记：vector转数组"},{"content":"内存主要有四块，栈，堆，全局和静态，常量。\n栈，堆和java一样，开发主要是集中在堆里，能过new,malloc来操作，其它内存都是系统申请释放的。\n关于常量如果用指针修改会报段错误：\nchar* charP1 = \"test const\"; *(charP1+1) = \u0026#39;c\u0026#39;; cout \u0026lt;\u0026lt; charP1 \u0026lt;\u0026lt; endl; 堆操作：\n#include \u0026lt;iostream\u0026gt; #include\u0026lt;stdlib.h\u0026gt; using namespace std; void test(string \u0026str); void test(char* charP); void getPos(); int main() { // stack string str = \"abcdefghijk\"; test(str); // error: type \u0026#39;struct std::string\u0026#39; argument given to \u0026#39;delete\u0026#39;, expected pointer // str没有用new分配的是heap空间，数据在stack中，超出作用域自动释放，手动删除会报错 // 看错误提示，delete后需要跟指针，所以只能delete new返回的 //delete str; cout \u0026lt;\u0026lt; str \u0026lt;\u0026lt; endl; // heap string* str1 = new string(); *str1 = \"abcdefghijk\"; test(*str1); cout \u0026lt;\u0026lt; *str1 \u0026lt;\u0026lt; \" str1 address:\" \u0026lt;\u0026lt; (void*)str1 \u0026lt;\u0026lt; endl; // new分配的heap空间，不删除会内存泄露 // 删除后可以看到\u0026pos = str1; delete str1; str1 = NULL; getPos(); int mallSize = 10; char* str2 = (char*)malloc(mallSize * sizeof(char)); memset(str2, 0x00, mallSize); strcpy(str2, \"abcdef\"); test(str2); cout \u0026lt;\u0026lt; str2 \u0026lt;\u0026lt; endl; free(str2); return 0; } void test(string \u0026str) { int size = str.size(); cout \u0026lt;\u0026lt; size \u0026lt;\u0026lt; endl; char* charP = new char[str.size() + 1]; memset(charP, 0x00, str.size() + 1); for (int i = 0; i \u0026lt; str.size(); i++) { *(charP + str.size() - i - 1) = *(str.c_str() + i); } //memcpy(charP, str.c_str(), str.size()); //string \u0026r = str; //cout \u0026lt;\u0026lt; (void*) charP \u0026lt;\u0026lt; endl; //r = charP; str = charP; delete[] charP; charP = NULL; cout \u0026lt;\u0026lt; \"return: \" \u0026lt;\u0026lt; (void*) charP \u0026lt;\u0026lt; endl; } void test(char* charP) { char* tmpP = new char[strlen(charP)]; strcpy(tmpP, charP); for (int i = 0; i \u0026lt; strlen(charP); i++) { *(charP + strlen(charP) - i - 1) = *(tmpP + i); } delete[] tmpP; } void getPos() { char* pos = new char[1]; *pos = 0; cout \u0026lt;\u0026lt; \"pos address:\" \u0026lt;\u0026lt; (void*)pos \u0026lt;\u0026lt; endl; } ","date":"2014-07-18T17:45:18Z","permalink":"/2014/07/c%E7%AC%94%E8%AE%B0%EF%BC%9A%E5%8A%A8%E6%80%81%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/","title":"c++笔记：动态内存管理"},{"content":"char c = \u0026#39;张\u0026#39;; byte b[] = new byte[3]; b[0] = (byte) ((c \u0026 0xFF0000) \u0026gt;\u0026gt; 16); b[1] = (byte) ((c \u0026 0x00FF00) \u0026gt;\u0026gt; 8); b[2] = (byte) (c \u0026 0x0000FF); System.out.println(Arrays.toString(b)); System.out.println(Arrays.toString(new Character(c).toString().getBytes(\"UNICODE\"))); 输出：\n[0, 95, 32]\n[-2, -1, 95, 32]\n“张”的code point：U+5F20，可以看出java内部char使用的是双字节来存储unicode，这样的话，除了BPM，其它其它辅助平面里面的字符是没有办法直接使用，只能当成byte来处理。但是，当我显式获得字符的unicode的时候却是4个字节，看情况应该是ucs4，但是高16位却是-2，-1而不是0，我试了一下其它的字也都是这样。记一下这个问题。\n文档：\nhttp://zh.wikipedia.org/wiki/Unicode%E5%AD%97%E7%AC%A6%E5%88%97%E8%A1%A8#Unicode_.E7.BC.96.E7.A0.81.E8.A1.A8\nupdate: 显式也是双字节，输出的4个字，其中前两个字节-2， -1就是oxFEFF，是个BOM头。。。\nSystem.out.println(Arrays.toString(new String(\"张张\").getBytes(\"UNICODE\"))); [-2, -1, 95, 32, 95, 32] 只输出了一次BOM头\nBOM：http://baike.baidu.com/view/126558.htm#sub5073178\n","date":"2013-01-07T07:55:02Z","permalink":"/2013/01/java_charunicode/","title":"java中char,unicode"},{"content":"在快速开发，频繁上线的项目中，代码很容易出现问题，这时候做好单元测试就非常的重要。\n安装 项目地址：https://github.com/sebastianbergmann/phpunit/\n手动安装很麻烦，推荐使用pear直接安装\nyum install php-pear.noarch\npear install phpunit/PHPUnit\npear install phpunit/PHP_Invoker\npear install phpunit/PHPUnit_Selenium\npear install phpunit/PHPUnit_Story\n使用 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 87 88 89 90 91 92 93 94 95 \u0026lt;?php class CarTest extends CTestCase { public $queue; public $fixtures=array( \u0026#39;car\u0026#39;=\u0026gt;\u0026#39;Car\u0026#39;, ); /** * 不是test开头的测试方法使用@test * * @test 注解么？ */ public function noTest() { $this-\u0026gt;assertEquals(1, 1); $this-\u0026gt;assertEquals(1, 1); return false; } /** * 依赖于noTest noTest返回值是这个参数值，noTest不过，这个检查跳过 * * @depends noTest */ public function testCreate($return) { $this-\u0026gt;assertFalse($return, \u0026#39;failed\u0026#39;); } /** * 使用数据源验验证测试 * * @dataProvider testProvider */ public function testArry($arr) { $this-\u0026gt;assertArrayHasKey(\u0026#34;return\u0026#34;, $arr); } public function testProvider() { return array( array(array(\u0026#39;return\u0026#39; =\u0026gt; \u0026#39;ss\u0026#39;)), array(array(\u0026#39;return\u0026#39; =\u0026gt; \u0026#39;ss\u0026#39;)), array(array(\u0026#39;return\u0026#39; =\u0026gt; \u0026#39;ss\u0026#39;)), ); } public function testException() { // 期待的异常 $this-\u0026gt;setExpectedException(\u0026#39;Exception\u0026#39;); // 抛出异常，测试通过 throw new Exception(\u0026#39;test\u0026#39;); } /** * 错误异常 * * @expectedException PHPUnit_Framework_Error */ public function testError() { // $this-\u0026gt;fail(\u0026#39;test\u0026#39;); // PHP错误会抛出框架内置的异常 PHPUnit_Framework_Error include \u0026#39;nofileffa.php\u0026#39;; } public function setUp() { fwrite(STDOUT, \u0026#39;set up\u0026#39;); $this-\u0026gt;queue = array(1, 2, 3); } public function tearDown() { fwrite(STDOUT, \u0026#39;tear down\u0026#39;); $this-\u0026gt;queue = null; } public function testFixture() { $this-\u0026gt;assertEquals(array(1, 2, ), $this-\u0026gt;queue); } public function onNotSuccessfulTest(Exception $e) { fwrite(STDOUT, \u0026#39;not success\u0026#39;); parent::onNotSuccessfulTest($e); } } 部分资料 断言：http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.assertions\nfixtures：http://www.phpunit.de/manual/current/en/fixtures.html\n注解：http://www.phpunit.de/manual/current/en/appendixes.annotations.html\nyii test：http://www.yiiframework.com/doc/guide/1.1/zh_cn/test.overview PHPUnit 3.7的authload换成了匿名函数，需要改一下，不然yii加载会有问题.\n","date":"2012-12-12T06:37:57Z","permalink":"/2012/12/phpunit/","title":"使用phpunit"},{"content":"yii在CListIterator中使用引用，导致迭代在特殊情况下会出现BUG\npublic function actionIndex() { $data = array('a', 'b', 'c'); $a = new CList($data); foreach ($a as $k =\u003e $v) { if ($k == 0) { unset($a[0]); } echo $v; } } 如果预先不知道这个情况，很容易出现问题，框架中也有这个问题，看一段代码：\npublic function setGlobalState($key,$value,$defaultValue=null) { // ... if($this-\u003e_stateChanged!==$changed) $this-\u003eattachEventHandler('onEndRequest',array($this,'saveGlobalState')); } // ... /** * Saves the global state data into persistent storage. * @see getStatePersister * @throws CException if the state persister is not available */ public function saveGlobalState() { if($this-\u003e_stateChanged) { $this-\u003e_stateChanged=false; $this-\u003edetachEventHandler('onEndRequest',array($this,'saveGlobalState')); $this-\u003egetStatePersister()-\u003esave($this-\u003e_globalState); } } public function raiseEvent($name,$event) { $name=strtolower($name); if(isset($this-\u003e_e[$name])) { foreach($this-\u003e_e[$name] as $handler) { // ... } } // ... } 当runtime目录被清空时，onEndRequest事件触发saveGlobalState动作，在操作的过程中却又解除了saveGlobalState动作的绑定。这会导致raiseEvent事件的时候，原来N个动作变成N-1个动作，但是计算器还是N个，就会下标越界。\n向官方提交了这个BUG，想修正的话可以先注释掉这行：\n$this-\u003edetachEventHandler('onEndRequest',array($this,'saveGlobalState')); ","date":"2012-08-10T06:22:45Z","permalink":"/2012/08/yii%E7%BB%91%E5%AE%9Aonendrequest%E4%BA%8B%E4%BB%B6%E7%9A%84bug/","title":"yii绑定onEndRequest事件的bug"},{"content":"之前是.php后缀的页面，如何只需把后缀改成.html，像：http://www.gudalu.com/index.php，访问：http://www.gudalu.com/index.html 就会自成生成静态文件呢？\n前段时间古大陆的cpu有点高，处理txt章节不给，决定搞成这种自动静态，zend framework很方便，三步搞定\n1.创建一个ZendEx_Plugin_Html的插件，用来处理写html\n\u003c?Php /** * 静态化插件 * * @package tool */ class ZendEx_Plugin_Html extends Zend_Controller_Plugin_Abstract { protected $_filePath; public function setFilePath($filePath) { $this-\u003e_filePath = $filePath; } public function dispatchLoopShutdown() { $htmlContent = $this-\u003e_response-\u003e__toString(); $pathinfo = pathinfo($this-\u003e_filePath); !is_dir($pathinfo['dirname']) \u0026\u0026 mkdir($pathinfo['dirname'], 0777, true); file_put_contents($filePath, $htmlContent); } } 在入口把所有.html的请求全部转发到HtmlController中~ $router = $front-\u003egetRouter(); $htmlRoute = new Zend_Controller_Router_Route_Regex( '[\\d\\w\\/]+\\.html', array( 'module' =\u003e 'default', 'controller' =\u003e 'html', 'action' =\u003e 'handle' ) ); 2.HtmlController注册ZendEx_Plugin_Html插件,这里可以对uri进行处理\n\u003c?php /** * 静态化控制器 * * @author yuehei * @package ex */ class HtmlController extends Core_Controller_Action { public function handleAction() { $requestUri = $this-\u003e_request-\u003egetRequestUri(); $requestUri = parse_url($requestUri); $requestPath = $requestUri['path']; // --- 通过条件定位对应的参数来静态化数据 $module = 'default'; $controller = $action = ''; $params = array(); if (preg_match('/^\\/index.html$/', $requestPath, $rs)) { $controller = 'index'; $action = 'index'; } // --- 文章页 if (preg_match('/^\\/file\\/([\\d]+)\\/index\\.html$/', $requestPath, $rs)) { $id = $rs[1]; $controller = 'file'; $action = 'view'; $params = array('id' =\u003e $id); } if (empty($controller) || empty($action)) { //$this-\u003e_showError('无法解析地址'); $this-\u003e_closeView(); $this-\u003e_response-\u003esetHeader('Status', '404 Not Found', true); return; } $this-\u003e_forward($action, $controller, $module, $params); $this-\u003e_closeView(); $filePath = str_replace('_app', '_www', APP_PATH) . $requestPath; // --- 给更低的运行级别，在render之后运行 $helperHtml = new ZendEx_Plugin_Html(); $helperHtml-\u003esetFilePath($filePath); $helperHtml-\u003esetRequest($this-\u003e_request)-\u003esetResponse($this-\u003e_response); $this-\u003egetFrontController()-\u003eregisterPlugin($helperHtml); } } ","date":"2011-12-12T10:23:44Z","permalink":"/2011/12/zend-framework-plugin-html/","title":"zf增加.html的后缀，即可以实现静态化的插件"},{"content":"\n昨天刚更换完，速度很给力，电信100以下，基本上可以媲美国内的服务器了。\n换机房也很简单，提个ticket工作人员立马响应，二个小时左右换完，以后用联通的线路再也不用天天掉线了。鼓舞\u0026hellip;\n","date":"2011-09-22T02:57:14Z","permalink":"/2011/09/linode_tokyo_server_room/","title":"linode的东京机房太给力了"},{"content":"fastdfs是一个分布式的文件存储系统，\n项目主页：http://code.google.com/p/fastdfs/\n支持论坛：http://bbs.chinaunix.net/forum-240-1.html\n测试了一下，发现很好用，优点：\n1，简单：tracker做负载均衡，storage存储数据。\n2，良好的横行和纵向扩展：增加group即可实现容量扩充，增加group内的storage即可实现性能扩充\n3，节省成本：数据不分块存储，前端无须再加一层缓存，直接将storage当做web server来使用\n4，良好的灾备：不需要raid，group内的storage存储相同的数据\n5，持续的的更新，作者很用心\n配置文件：http://bbs.chinaunix.net/thread-1941456-1-1.html\n","date":"2011-07-21T03:53:18Z","permalink":"/2011/07/fastdfs/","title":"FastDFS"},{"content":" 今天的年终总结是坚持，明年的计划仍然是坚持。 从去年11.4开始跑步，这一年一直在跑，今早上秤81.7kg。 三年又三年，这是第二个三年的结束，也是第三个三年的开始。上次我给自己画了一个大饼，结果变成了一烧饼，这次不画饼了，改成给自己吹个泡泡。 2010年再见，2011年你好。分享一首05年超级女生，就是李宇春那一届，有个叫赵静怡的选手唱了一首歌。\n我的左手旁边是你的右手\n","date":"2010-12-31T16:55:48Z","permalink":"/2011/01/2010/","title":"回望2010"},{"content":"收拾衣服的时候，翻出了一本日记，从05年6月12号，一直写到07年1月20号，断断续续写了一年多。\n随手翻翻，看着深浅不一的笔迹，依稀能回忆起当时的心境。\n06/23/2005 正在看让我痴迷不已的诛仙，写了二页感想，这要是发到网上，那就是长评。\n07/27/2005 非常想家\n09/30/2005 连续好几篇都在写做游戏帮派论坛，做金山游戏推广员，怎么在学校里帖小广告，以及如何是被坑的悲惨故事。\n12/05/2005 反思了自己的一些想法，以及看了《乱世铜炉》的感想。现在的我和5年前的我在一些价值观上仍然一致。\n12/09/2005 第一次用java写程序的兴奋。\n12/24/2005 平安夜让我回忆起了昔日的网友。\n12/31/2005 年终的我发现又混过了一年\n05/26/2006 确定电脑是我的饭碗\n01/20/2007 年终总结：害怕\n之后就开始工作了，刚工作时睡眠严重不足，就没有时间扯蛋了，整个日记也就断了。不写日记，我发现脑子里记的全是事，关于个人的感受已经模糊不清。现在又开始续写这本日记，不过留了一页空白，代表已逝去近四载的时光。\n","date":"2010-12-29T13:43:48Z","permalink":"/2010/12/diary/","title":"旧的记事本"},{"content":"晚上去吃饭，回来已经8点多了，折腾折腾，9点半左右才出去跑步，比平时晚了40多分钟。穿过像伊拉克一样的唐家岭废墟，沿着土井路跑，到达一个三叉路口，正常情况下我会沿着这条路一直跑到东北旺然后再原路返回。就在拐弯的时候，我看三十多米开外，有一条硕大的黑狗，慢悠悠的穿过马路，消失在黑暗里，当时没引起我警惕，因为一般情况下，这么大的狗都是有人带着的。\n谁曾想，我刚跑到这个地方，这条连个绳子都没有的狗，突然从黑暗里窜了出来冲我狂叫，我当时第一反应是，这狗不会过来这狗不会过来，还没想完，这狗就冲我扑过来了。当时离的很近，我后退侧身闪过，手好像砸到什么，大拇指有点疼，当时第一感觉就是手破了手破了，要打狂犬疫苗了。大狗见没有扑到，就往后退了一点，冲我狂叫，我也面对着大狗戒备着，边对峙脚边往后退，当时不是很怕，感觉这条狗不像野狗，扑的时候不凶，也不快，可能是从家跑出来的，退到7，8米左右，这条狗又扑过来了，一个1米多高的狗快速冲人跑过来，还是非常恐怖的，我再次侧身闪过，幸好这时候来了一辆车，灯打过来时这狗就有点虚，一击不中，就又往后退，在那儿冲我吼，这时候已经离的比较远了，我也象征性在路边找了找砖头，可惜也没有找到，望了我一会儿，这狗就又消失在黑暗中了。没有找到砖头的我，思考了一下，决定这条路今天先不跑了，在路灯下看了看大拇指也没破皮，万幸。\n这条路我跑了一年多，经常碰到流浪狗，尤其是唐家岭拆迁以后，更时不时的碰到拉帮结派5，6条在一起的狗群。有时候跑步也会被执著的狗追，但一般只要我停一下，这些狗就不敢靠近了，有些不长眼的狗，我要反过来追它们一下。但这次碰到这么大的，这么凶猛的，上来就扑的，还真是第一次，两个字总结：肝颤。明天早点，可不想再和这狗搏斗了。\n","date":"2010-12-28T16:32:36Z","permalink":"/2010/12/dog/","title":"心跳那几秒"},{"content":"国庆前买了 linode 的VPS，算是edong撤走了，不用再去照相了，照相这种备案方式和以前BBS专项备案一样，没有什么操作性，迟早也要取消的。以前用edong vps，跑的是nginx+apache，这次直接换成nginx+spawn-fcgi。\n# BEGIN WordPress \u0026lt;IfModule mod_rewrite.c\u003e RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] \u0026lt;/IfModule\u003e # END WordPress 上面是推荐的rewrite规则，但是这样规则会将不存在的图片,css等文件也都转发到index.php，这些文件应该报个404的。\n#现在nginx上使用的rewrite server { server_name yuehei.37net.com; access_log /var/log/nginx/yuehei_37net_com_access.log main; error_log /var/log/nginx/yuehei_37net_com_error.log info; root /data/html/37net.com/yuehei; index index.html index.htm index.php; error_page 403 =200 /403.html; location = /403.html { root /data/html; } error_page 404 =200 /404.html; location = /404.html { root /data/html; } location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php last; break; } } location ~ \\.(gif|jpg|jpeg|png|css|js)$ { expires 365d; } location ~ \\.php$ { include /etc/nginx/fastcgi_params; } } 测试一下: https://yuehei.37net.com/test_test.png 报的是404了\n","date":"2010-10-08T04:26:21Z","permalink":"/2010/10/nginx_wordpress_rewrite/","title":"nginx下wordpress的rewrite"},{"content":"5公里，这段时间下雨，聚会，玩三国杀，跑的比较懈怠，85.5KG\n1，EasyUI for jQuery\n2，Persona——Web人物角色介绍\n3，Web服务器压力测试工具http_load、webbench、ab、Siege使用教程\n4，wxpython in action 非常详细的wxpython讲解，对我这种没有做GUI编程的人用处很大。刚看完到11章，准备写一个简单的使用SQLite的报表。\n","date":"2010-06-05T05:12:51Z","permalink":"/2010/06/5-2_6-5/","title":"5.2 – 6.5收藏"},{"content":"\n这本书断断续续看了快了一个月，今天把最后几页看完。我打了四颗星，总体上来说还是不错，但是感觉太零碎，作者像是随手拾来，看不到严密的逻辑，感觉天马行空，这种书一般都不讨我喜。\n很少人能坦然面对负面情绪，一出现就会想办法消除或者转移走，就像这个时代每个人都在强调自信，强调勇气，每个都在害怕自卑，害怕懦弱一样。人有七情六欲，交替博弈，是一个正反合的过程，一个再自信的人也有自卑的时候，一个再勇敢的人也有懦弱的时候，只要是个人它就无法摆脱这些情绪。不能坦然面对这些负面的情绪，就不能认识自我，更无法获得真正的自信，勇气。\n这是一本很诚恳的书，很喜欢刚开始的一句：孤独没有什么不好，使孤独变得不好的，是害怕孤独。\n这么晚了还不睡，主要是因为今天很点背，晚上取钱的时候，银行卡忘记拿出来了，一个杯具。\n","date":"2010-05-07T18:34:37Z","permalink":"/2010/05/book_%E5%AD%A4%E7%8B%AC%E5%85%AD%E8%AE%B2/","title":"看完《孤独六讲》"},{"content":"这个webgame虽然还有bug，有一些功能要完善，界面也要重新更换，但基本上告一段落，我会一般修bug，一边做总结，这是这篇总结的第一章，算是一个备忘。\n做这个项目之前，我有2年左右的PHP开发经验，对python有一些了解，javascript一般般，但用起来倒也得心应手。之前对webgame有一些了解，自己也玩过，也debug其中一些认为做的比较好的，这样也就不算陌生。\n我的技术背景决定整个webgame是基于PHP+javascript来完成，为了更好的用户体验，我决定采用OPOA的形式。javascript项目有一个问题，就是连接保持，现在流行的comet方式，我都试了一遍，如果没有一个支持这一特性的web server配合的话，都不理想，最后还是使用了flash的socket，后面我会用专门一章节来讲。\n其次，在做的过程中也走过一些弯路，比如刚开始，我过于强调所谓的性能，而把很多计算放到客户端来做，为了保证客户端时间正确，又把服务器的时间同步过来等等，做了一些无用功。javascript虽然功能很强大，但过于依赖javascript，限于个人的水平，会导致前端不可控，错误不可重现，bug无法排查，最后会被活活急死。\n另一个重要的问题，就是整个后端返回的是数据还是html，我曾经debug一个知名的webgame，发现它整个后端都返回html，当时我不以为然，认为完全没有必要，所以我采用的就是返回json。现在看来其实两个都各有好处，返回html可以避免前端再进行处理（譬如非常烦人的数据组装），只要一个dom就可搞定。返回数据则可以减少数据量，加快响应速度，我现在采用的是数据+javascript模板的方式，javascript模板很少发现有网站在用，模板对于普通网站来说，可能弊大于利，但对于OPOA类的程序，好处显而易见的，配合延迟加载也可以减少一些弊端。\n一般应用的过程是：前端 -\u0026gt; 调用后端 -\u0026gt; 处理结束 -\u0026gt; 返回前端，这个过程webgame也有，但webgame有时候会回调前端，即：\n前端 -\u0026gt; 调用后端 -\u0026gt; 处理结束 -\u0026gt; 返回前端 -\u0026gt; 再回调前端。\n/* 一个例子：购买一个道具，就更新银两的显示。把JRun.loadBack看成是一个ajax的封装。 一般情况下代码会像下面这样写，但这样有一个弊端，不止买道具，其它很多地方(比如卖)也可能要更新银两显示， 难道每一次更新都要这样写吗？ */ JRun.loadBack('/goods/buy', {goods_id: 1, num: 5}, function(data) { var status = data.status; if (status == 1) { $('#role_tael').html(data.tael); } }); /* 下面的代码换了一种写法，所有更新银两的显示都调用了JRole.updateTael。 但是又有一个新的问题，买道具不一定非要银两，有可能是声望，购买成功以后也更新声望的显示， 这时候就需要扩展status，来告诉前端如何处理。 */ JRun.loadBack('/goods/buy', {goods_id: 1, num: 5}, function(data) { var status = data.status; if (status == 1) { JRole.updateTael(data.tael); } else if (status == 2) { JRole.updateRepute(data.repute); } }); var JRole = { updateTael: function(tael) { $('#role_tael').html(tael); }, updateRepute: function(repute) { $('#role_repute').html(repute); } }; 随着要处理的类型的增多，status会越来越多，整个前端会很臃肿，而且会越来越不可控。如何让后端更自由的调用前端？有一些webgame返回的是html，里面内嵌javascript，这种方式并不推荐。可以采用格式化的返回数据来解决这个问题。\n// 前端调用 JRun.loadBack('/goods/buy', {goods_id: 1, num: 5}); // goods/buy返回数据格式 {status: 1, data: {tael: 5}, callBack: \"JRole.updateTael(data.tael);\"} 这样就清晰简单多了，前端只需一行代码。服务器处理完成以后，自定义callBack作为返回内容，前端会自动调用并将数据传入。只要对JRun.loadBack进行简单的封闭，让它识别callBack的内容。后端对前端的可控性会降低前端的代码量，同时也可应对复杂的交互。\n这样的前端设计基本上能满足我目前的应用。另外如果我能在开发早期注意到问题，完全可以在实现控制(JRun.loadBack)，模型(后端PHP)，模板(javascript渲染)的分离。\n","date":"2010-05-06T12:38:42Z","permalink":"/2010/05/webgame_development_1/","title":"WebGame 开发过程与备忘（一）前端基础框架的封装"},{"content":"早上躺在床上，初升的太阳顺着后楼的空隙，穿过窗台，照在我喝完水的杯子上。看着光线中微尘翻滚，心就像宽阔的湖面，无限平静。\n我经常搬家，住过很多房子，屋子里的东西往往还没堆满的就要搬，碰到那些认为有用的东西，也会留下来了，但搬家的时候却全被扔掉了，仿佛是一个杯子，不停的接水倒掉。回家的时候也一样，我会打包衣服，思量什么东西要用到，却很少带全。其实我知道，任何时候我能孤身一人，很多东西都能丢，不能丢的都带在身上。\n孤身一人的时候我会迟疑，也能说服自己，继续前行，知道当下即是过往，期待明日有所收获。往往事与愿违，可能等待了很长时间，走了许多的路，到结束却连个痕迹也没有留下。回望的时候，才发现当初的迟疑徘徊即是全部，明日只是一个结束。有人说，希望是至善，我却认为希望是可有可无的，身陷囹圄的人才会觉得无比重要。有勇气面对自己，有勇气正视环境，听从内心，做自己能做的事，知道自己不能做的事，明白通过练习可以做到的事，就已足够。更多时候，希望是一些人用来证明自己生命的意义，而我的生命没有意义，我的时光就是用来感受时光的消逝，我的生命就是用来感受生命的终结。当然，这只是一种理想的状态，看着九零后前仆后继，身为八零后，身为唐家岭的蚁族，我感到压力很大。\n漫步时，许多的背影在身边穿行，我仿佛赤着脚，行走于路间，感受赤热冰凉，喜怒哀乐，我知道你也一样，却不知道你是否如我一样。我常怀疑自己，也常贪图片刻的安宁，我能正视自己，也能感受内心的坚定。我活在当下，不期许虚妄的未来，不纠缠无谓的过往。知盈虚有数，不奢望恒久的快乐，不排斥安静的孤独。我喜欢平静，知道平静才能长久，我想做真正的自己，虽然这话听起来是在蛋逼，却不知道你是怎么想？\n","date":"2010-05-03T15:06:59Z","permalink":"/2010/05/danbi/","title":"旭日东升，照我窗台"},{"content":"玩三国杀我比较喜欢5人局，1主公1忠臣2反贼1内奸，这种人数搭配进度快，也能看出忠臣的无奈，反贼的徘徊，内奸的摇摆不定。做为一个英明的主公，如何能识别出这些面具背后的身份？\n首先，第一局如果有牌就要出手，逻辑上来说，蒙的概率还是很高的，有3/4的机会杀到反贼和内奸。先手之后，就很容易根据大家的表现猜出大致的身份。\n1，忠臣，因为无所顾忌，在战斗中会表现出一往无前的气势，逮谁踹谁，一踹到底，不死不休，而且还喜欢杀体力少的，容易杀的。\n2，内奸，我遇过两种风格，一种很低调很安静，走反贼路线，这种我一般是放到最后才收拾。另一种则是表现出很强大的气场，走忠臣路线，到了后期就很难分辨谁是忠臣谁是内奸。做为一个英明的主公，在这个时候应该低调，忠臣死可以，但不能死在你手里。让他们自相残杀，如果有什么【南蛮入侵】，【万箭齐发】之类的，可以统统放出来通杀。至于装备，那是一定要拆的。等他们分出胜负，伤痕累累的面对一个武装到牙齿的你，胜利是一定的。\n3，反贼，识别这个没有什么难度，同伙身份未明，下手就会顾忌，怕错杀自己人。我也遇过两种风格，一种是上次来就跳，攻击主公，向同伙表明反贼身份。我经常这么干，尤其是用许禇的时候，配合连弩 ，再裸衣，绝对是猛将中的猛将，无敌。另一种就是继续扮演，这就有难度了，只要反贼不傻，就不会像忠臣那样猛干，他可能会左打一下，右打一下，谁都不打死，直到忠臣的身份明朗后才会跳出来猛杀。\n做为一个刚入门的菜鸟，在选择主公的时候，还是推荐使用曹操，至少可以保障手中有能进攻的牌。武将推荐夏候墩（刚烈），赵子龙（龙胆），大乔（国色，流离），甄姬（洛神，倾国），技能都偏向于防御，比较好用。\n","date":"2010-05-01T19:11:29Z","permalink":"/2010/05/sgs/","title":"三国杀忠臣，内奸，反贼的识别"},{"content":"这段时间一直在跑5公里，体重85.6KG\n1，MVC模式已死\n2，90后的用户形态研究 90后用户形态研究（第二期）\n3，Python于Web 2.0网站的应用\n4，一个危机公关的实例 案例分析：一份道歉信\n５，有时候梦想未必是好事 梦想有时也许只不过是陷阱\n","date":"2010-05-01T18:12:33Z","permalink":"/2010/05/4-20-5-2%E6%94%B6%E8%97%8F/","title":"4.20 \u0026ndash; 5.2收藏"},{"content":" 这是第一篇用 live writer写的blog，住的地方网速很不理想，用这种方法免得丢失。我用rss阅读，但大部都是看了过，过了忘，忘了就一片空白，这样囫囵吞枣也没有什么收获。新加了一个收藏的分类备忘一下，一来让自己零碎的阅读有点记录，二来也是分享一下我感觉有用处的文章。\n1，微博之战 微博方便了作者，却麻烦了读者。大量的消息虽然及时却凌乱，分享还可以，发表观点却不够。\n2，我从11楼跳下去……才知道……\n3，给你一个真实的芙蓉姐姐 可以从8分钟演讲的时候看，我看到一个自信，从容，淡定的女人。\n4，多服务器同步：rsync使用小结 金山逍遥网 sersync 服务器实时镜像、自动同步程序开源\n5，30岁MAN 6年尴尬的IT人生\n6，当我们的想法与别人不同时\n7，当我们开始反思得时候，就开始成长了\n8，互联网常见Open API文档资源\n9，网站内容决定网站的前途\n10，适用于 PHP 开发人员的 Python 基础知识\n11，囚徒困境\n12，戈森定律\n13，斯坦福监狱实验\n","date":"2010-04-18T10:37:13Z","permalink":"/2010/04/4-12-4-19%E6%94%B6%E8%97%8F/","title":"4.12 \u0026ndash; 4.19收藏"},{"content":"盘今的主机，是我用过最烂的，虽然客服很尽责，但是速度已经不能用慢来形容，ping可以ping，网页只是偶尔能打开。买了一下E动VPS，界面很方便，速度还不错，稳定性有待观察。\n我是按装这个【文档】来进行awstats日志的配置。有一些写的不详细：\n第（2）步我改成了虚拟主机 \u0026lt;VirtualHost *:80\u003e Alias /awstats/icon/ /var/www/awstats/icon/ Alias /awstats/ /var/www/awstats/ \u0026lt;Directory /var/www/awstats/\u003e DirectoryIndex index.html Options ExecCGI order deny,allow allow from all \u0026lt;/Directory\u003e \u0026lt;/VirtualHost\u003e 打开浏览器，如果看到是pl的源代码，在apache的配置文件中增加\nAddHandler cgi-script .cgi .pl 如果显示的界面是英文，则在第（4）部中，将LANG=“auto\u0026quot; 改成 LANG=\u0026ldquo;cn\u0026rdquo; ","date":"2010-02-11T15:44:05Z","permalink":"/2010/02/awstats/","title":"在vps主机配置awstats日志数据分析软件"},{"content":"2009年底，空间商抱着机器从上海跑到重庆再跑到北京，也没让我赶在年末写上一份总结。也能理解，都不容易，现在正常了，也顺道展望一下2010。\n回首2009，是有些收获：\n1，学习了python。虽然是刚刚入门，总算是写了一个在线聊天的服务器。php虽然好使，但语言特性还是很难遇上诸如内存释放，线程，管道之类的问题，不遇到问题就没有机会搞懂。\n2，封装了一个PHP框架。虽然我很鄙视我在做重复的工作，但是thinkphp，zf真的让我感觉很不方便。我有自已习惯，也知道程序部署的环境，所以就很简单，借鉴了部门老大对zf的修改，使用autoload自动加载，用xdebug测试的时候，基本上效率没有什么影响。\n3，从09年4月份开始，中间虽然有断层，但基本上我全部精力都放在一个网页游戏上，遇到了很多问题，有的解决有的绕过。一个深刻的教训是，我以后绝不对没有做过的东西说“好做”。为了给用户更自然的体现，应用只用了一个html页面，前端js写了有5000多行，比5w行php都难写。更别提遇到的一些js内存泄漏，莫名其妙的卡，浏览器关闭。在firefox下还好，总有工具可以用。ie下那些debug工具都是不靠谱的，我只能一行一行的删除，alert调试。总算在年终的时候内测，虽然还有很多问题。\n展望2010：\n1，我知道js做ria的不足，所以今年的一个重要的任务就是学习flex。\n2，有武侠情结，想写一个武侠类的网页游戏。因为对实时要求比较高，架构采用我熟悉的js和我不熟悉的python。一直在关注一个框架eurasia，准备尝试一下，主要还是想深入一下python，手熟一下python。\n3，存xx的钱，08年09年我原地踏步走，积累了两年应该再上个台阶了。\n还有一些我不想说出来的计划，我在心中也默默的说一遍。事情总有反复，事情总有后退，事情总有一日不如一日，事情总在一个漫长的周期上一直向上。\n2009是很好的一年，希望2010也是很好的一年。\n","date":"2010-01-12T13:15:40Z","permalink":"/2010/01/2010_plan/","title":"回首2009，展望2010"},{"content":"一直绕操场跑圈圈，今天和志远跑了一次环线。遥想07年初我和鸭子也是跑这条线，一直跑到我吐，那滋味真不好受。今天速度控制的还可以，跑的比较轻松。\n","date":"2009-11-16T14:57:27Z","permalink":"/2009/11/running/","title":"今天跑完一个环线"},{"content":"开始主要想看一下qq群所使用的javascript模板，后来发现他们用的也是一个jquery的插件。\n不过这个加载写的也很不错，分析了一下，原JS： http://qun.qq.com/god/m/js/loader.zh-cn.js\n; (function() { var window = this, undefined, jLoader = window.jLoader = window.jL = { _version: \"1.2.1\", _sequence: [], _queue: {}, _xhr: function() { return window.ActiveXObject ? new ActiveXObject(\"Microsoft.XMLHTTP\") : new XMLHttpRequest(); }, // 直接加载脚本 _script: function(uri, context) { context = context || document; var head = context.getElementsByTagName(\"head\")[0] || context.documentElement, script = context.createElement(\"script\"); script.type = \"text/javascript\"; script.src = uri; head.appendChild(script); return script; }, _style: function(uri, context) { context = context || document; var head = context.getElementsByTagName(\"head\")[0] || context.documentElement, link = context.createElement(\"link\"), links = context.getElementsByTagName(\"link\", context); link.type = \"text/css\"; link.rel = \"stylesheet\"; link.href = uri; if (0 \u0026lt; links.length) { var _last = links[links.length - 1]; _last.parentNode.insertBefore(_link, _last.nextSibling); } else { head.appendChild(link); } return link; }, _replace: function(pattern, uri, context) { if (!pattern || !uri) { return; } context = context || document; var links = context.getElementsByTagName(\"link\", context); for (var i = links.length - 1; i \u0026gt;= 0; i--) { if ( - 1 != links[i].href.indexOf(pattern)) { links[i].parentNode.removeChild(links[i]); } } if (uri) { jLoader._style(uri, context); } }, // 将脚本内容插入当前页面来执行 _globalEval: function(data, context) { if (data \u0026\u0026 /\\S/.test(data)) { context = context || document; var head = context.getElementsByTagName(\"head\")[0] || context.documentElement, script = context.createElement(\"script\"); script.type = \"text/javascript\"; if (jLoader._excute) { script.appendChild(context.createTextNode(data)); } else { script.text = data; } head.insertBefore(script, head.firstChild); head.removeChild(script); return true; } return false; }, // 执行回调方法数组。有可能请求多次，绑定了多个的回调方法。 _call: function(mark) { if (jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e.callbacks) { var cl = jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e.callbacks.length; for (var j = 0; j \u0026lt; cl; j++) { jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e.callbacks[j][0](jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e.callbacks[j][1]); } } }, // 这是一个顺序加载的过程，每加载一个JS，都会放入sequence队列，如果depend=true,则该脚本之前的脚本全部加载完后该脚本才执行 _inject: function() { var len = jLoader._sequence.length; for (var i = 0; i \u0026lt; len; i++) { var seq = jLoader._queue[jLoader._sequence[i]]; if (!seq.done) { if (!seq.response) { return; } else { seq.done = true; seq.response = [seq.response, \";jLoader._call(\\\"\", jLoader._sequence[i], \"\\\");\"].join(\"\"); jLoader._globalEval(seq.response); } } } }, scripted: function(mark) { if (\"undefined\" == typeof jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e) { return false; } else { if (jLoader._queue\u0026lt;mark\u003e\u0026lt;/mark\u003e.done) { return true; } else { return false; } } }, script: function(options) { options = options || {}; // 参数不全 if (!options.mark || !options.uri) return; options.depend = options.depend || false; options.params = options.params || {}; options.onload = options.onload || null; // 如果没有依赖关系和回调方法，则直接引入 if (!options.onload \u0026\u0026 !options.depend) { jLoader._script(options.uri); jLoader._queue[options.mark] = { uri: options.uri, response: null, done: true }; return; } // 是否为第一次请求 if (\"undefined\" == typeof jLoader._queue[options.mark]) { jLoader._queue[options.mark] = { uri: options.uri, response: null, done: false }; // 设置回调方法和回调的参数 if (\"function\" == typeof options.onload) { jLoader._queue[options.mark].callbacks = [[options.onload, options.params]]; } } else { // 虽然不是第一次请求，但有没有真正加载。没有真正加载的原因此时正在异步请求脚本内容 if (false == jLoader._queue[options.mark].done) { // 如果已经设置为callbacks, 则追加callbacks, 前面设置的callbacks是数组 if (\"function\" == typeof options.onload) { if (\"undefined\" == typeof jLoader._queue[options.mark].callbacks) { jLoader._queue[options.mark].callbacks = [[options.onload, options.params]]; } else { var cl = jLoader._queue[options.mark].callbacks.length; jLoader._queue[options.mark].callbacks[cl] = [options.onload, options.params]; } } } else { // 已真正加载，直接执行回调方法 if (\"function\" == typeof options.onload) { options.onload(options.params); } } return; } // 将脚本按顺序放入队列，self._inject会用到 var len = jLoader._sequence.length; if (options.depend) { jLoader._sequence[len] = options.mark; } var xhr = jLoader._xhr(); xhr.onreadystatechange = function() { if (4 == xhr.readyState) { // 如果设置了依赖关系，则检查依赖。如果没有设置，则执行脚本内容和回调函数 if (options.depend) { jLoader._queue[options.mark].response = xhr.responseText; jLoader._inject(); } else { jLoader._queue[options.mark].done = true; var response = [xhr.responseText, \";jLoader._call(\\\"\", options.mark, \"\\\");\"].join(\"\"); jLoader._globalEval(response); } } }; xhr.open(\"GET\", options.uri, true); xhr.send(\"\"); } }; // 很好，很强大，因为getTime()结果不一样，所以用这种方法可以准确的定位到某一个类有没有执行 jLoader._excute = false; var root = document.documentElement, script = document.createElement(\"script\"), id = \"script\" + (new Date).getTime(); script.type = \"text/javascript\"; try { script.appendChild(document.createTextNode(\"window.\" + id + \"=1;\")); } catch(e) {} root.insertBefore(script, root.firstChild); if (window[id]) { jLoader._excute = true; delete window[id]; } root.removeChild(script); })();","date":"2009-11-16T07:51:58Z","permalink":"/2009/11/qq_javascript_loade/","title":"QQ群前端使用的 javascript loader"},{"content":"在唐家岭住了2年多，终于看到一本描述唐家岭的书《蚁族》，立马和同在这儿住的同事买了，看完后发现虽然不是很全面但还是真实客观的。只是那些调查人员感觉唐家岭脏，乱在我眼里却很平常，或许我习惯了，除了下雨天路难走，别的还可以。\n在唐家岭住，生活成本比较低，吃饭的地方比较多，而且租一间房子比合租小区更自由。除了难挤的公交车，我觉得这里倒是暂住的好地方。另外，这本书后面个别章节有一种在上俯视众生，我很不喜欢。唐家岭虽然没那么好，但也没那么惨，在这里住两年就知道了。\n还是值一买本，当当链接：http://product.dangdang.com/product.aspx?product_id=20684969\n前三章节试读：http://book.ifeng.com/book.php?book_id=2414\nupdate 100210：做为一个在唐家岭的蚁族，我认为张鸣的这篇文章远比廉思的那本书要清醒和现实 http://news.qq.com/a/20100210/001085.htm\n","date":"2009-11-12T15:32:42Z","permalink":"/2009/11/80_year/","title":"看完《蚁族》"},{"content":"在众多网站阵亡之后，我最常用，最必不可少的xmark也倒下了。\n因为平时在公司和在家里用的不是一台电脑，很多收藏要同步，每次同步都要挂代理，太麻烦了。在xmark里有选项可以使用自己的服务器，试了一下，只要是简单的FTP权限即可以，而且速度比以前也理想。\n1。在 【设置】- 【status】里面填上自己的FTP用户名和密码\n2。在 【设置】 - 【高级】中， 填上在FTP的保存收藏的URI，xmark会在服务器自动建立文件。\n","date":"2009-10-29T15:22:31Z","permalink":"/2009/10/ftp_server_xmark/","title":"使用自己的FTP做为xmark的服务器"},{"content":"基于dabr212修改的，下载附件，然后覆盖 common 目录下的 twitter.php 文件，请求API的速度大概在1秒左右比较慢，最好是自己用。\n下载文件\nupdate 20091030：国内的虚拟主机可以架设一个，将文件覆盖即可，方便手机访问。主要是将dabr原来curl请求 twitter api的方式，换成一个socket方试请求，使用一个twitter上推荐的，号称不会被封的ip，并且使用了https，测试功能一切正常。\n","date":"2009-10-22T01:43:17Z","permalink":"/2009/10/dabr/","title":"国内虚拟主机架设dabr"},{"content":"sablog的代码高亮不怎么方便，换成新版的wp，感觉还不错。\n旧BLOG：https://yuehei.37net.com/old/\n更旧BLOG：http://hi.baidu.com/wooo\n\u003c?php /** * 探险 * * @package exp * @version $Id: ExpController.php 269 2009-09-24 01:31:11Z zhangguangzhong $ */ class ExpController extends Core_Controller { // 有无正在进行中的探险 public function listAction() { $modelExp = new Model_Exp($this-\u003e_role); $ret = $modelExp-\u003elistExp(); if ($ret === false) { exit(json_encode(array('status' =\u003e 0))); } else { $timestamp = time(); $sec = strtotime($ret['end_time']) - $timestamp; exit(json_encode(array('status' =\u003e 1, 'sec' =\u003e $sec, 'map_id' =\u003e $ret['map_id']))); } } } #!/usr/bin/python#Filename:socket_server.py # coding=utf-8 # 聊天网关 from twisted.internet import protocol, reactor import time, os, qing_game PORT = 8888 ","date":"2009-09-16T11:05:39Z","permalink":"/2009/09/hello-world/","title":"hello world"}]