Compare commits

...

19 Commits

Author SHA1 Message Date
79f480f0ca xxx
Some checks failed
Clean ESA Versions on Main / clean-esa-versions (push) Has been cancelled
2026-01-22 00:06:37 +08:00
a4e0fb6cd8 feat: 添加EdgeOne函数示例和更新.gitignore
添加三个EdgeOne函数示例:
1. helloworld-cloud - 基础云函数示例
2. helloworld-edge - 返回JSON的Edge函数示例
3. random-acg - 带错误处理的图片代理函数

更新.gitignore以排除EdgeOne相关文件和目录
2026-01-19 23:21:15 +08:00
61a4f4b3c4 style(config): 调整背景透明度从1改为0.8
使背景与前景内容对比更柔和,提升可读性
2026-01-19 23:06:28 +08:00
e0614e1740 fix(config): 更新背景图片URL为备用链接
将背景图片URL从原链接切换为备用链接,确保背景图片能够正常显示
2026-01-19 23:05:57 +08:00
6af1909aab docs(Python): 添加Python面向对象编程系列文章
新增三篇关于Python面向对象编程的文档:
1. 基础入门介绍类与对象概念
2. 进阶讲解属性管理与魔术方法
3. 深度解析底层原理与元编程
同时添加一篇scoop配置国内源的开发日记
2026-01-19 23:02:01 +08:00
lirui
612a359c1d chore: 更新网站域名和背景图片配置
将网站域名从 blog.acofork.com 更改为 blog.meowrain.cn
修改背景图片源为新的随机图片API,并保留旧配置作为注释
2026-01-18 22:52:43 +08:00
lirui
afe7569729 docs(开发日记): 添加python包管理工具uv和springboot logback配置文档
添加两篇开发日记文档:
1. 详细介绍python包管理工具uv的使用指南
2. 提供springboot项目logback配置模板及详细说明
2026-01-18 20:06:57 +08:00
lirui
b890b18212 fix(Footer): 更新版权年份和GitHub仓库链接
- 将版权年份从2024改为2021
- 更新GitHub仓库链接为meowrain/blog
- 移除无用的服务器信息显示
2026-01-17 00:20:26 +08:00
lirui
6e6001e3cd fix: 更新背景图片URL并优化背景加载逻辑
移除未使用的分析脚本和广告代码
恢复背景图片加载检测功能
简化背景样式并移除冗余代码
2026-01-16 22:41:22 +08:00
lirui
6fe5592c2c style(Footer): 调整公安备案链接样式使其与ICP备案一致
将公安备案链接的样式调整为与ICP备案链接一致,包括添加过渡效果和字体样式,并将图标移至链接标签内以保持结构统一
2026-01-16 21:11:58 +08:00
lirui
bbd608a6cf feat(Footer): 添加公安备案信息和图标
在页脚添加公安备案信息链接及对应图标,以满足网站备案要求
2026-01-16 21:08:19 +08:00
lirui
b6abc32784 feat(ui): 实现ACG风格UI改造和动画效果
添加樱花飘落和表情点击特效组件,优化卡片动画和背景模糊效果
更新主题色为粉色并添加动漫风格背景图
引入圆角字体提升可爱风格
新增开发文档记录改造计划
2026-01-11 12:59:41 +08:00
lirui
9f4c61403e docs(开发日记): 更新SpringCloud端口占用问题的解决方案
添加了关于修改项目端口号到预留范围之外的解决方案
2026-01-11 12:33:04 +08:00
lirui
4d197699cb docs: 添加关于Windows端口被占用问题的开发日记
记录SpringCloud项目启动时端口被占用但查不到进程的问题及解决方法
2026-01-11 12:30:51 +08:00
b8905c488b docs(开发日记): 添加GitFlow学习文档
介绍GitFlow分支模型的核心概念、标准流程和团队落地建议,帮助团队理解分支协作规范
2026-01-10 00:24:12 +08:00
a1999b3cc1 docs: 添加MySQL数据库恢复后慢查询问题的解决记录
记录一次MySQL数据库恢复后出现的慢查询问题及解决方案。通过ANALYZE TABLE命令更新统计信息,解决了因统计信息不准确导致的索引失效问题。包含问题排查过程、解决方案和MySQL统计信息机制的深度解析。
2026-01-10 00:21:16 +08:00
f3e3d8a738 fix: 修正链接提取逻辑,确保包含完整路径 2026-01-06 20:41:40 +08:00
d561837a9e fix 2026-01-05 22:48:47 +08:00
63fa5274ba add: create Bash command lookup order documentation 2026-01-05 22:33:48 +08:00
30 changed files with 3304 additions and 77 deletions

View File

@@ -0,0 +1,987 @@
---
description:
globs:
alwaysApply: true
---
---
description: Guidelines and best practices for building full-stack website on EdgeOne Pages based on serverless deployment from frontend Pages to dynamic APIs, ideal for building modern Web projects like marketing websites and AI applications
globs: **/*.{ts,tsx,js,jsx,toml,json}
---
<system_context>
You are an advanced assistant specialized in generating EdgeOne Pages applications. You have deep knowledge of EdgeOne's full-stack architecture (Static Site Generation + Edge Functions + Node Functions), Next.js/React/Vue/Gatsby best practices, and various platform integrations.
EdgeOne Pages is a full-stack platform that combines:
- Static Site Generation (SSG) for fast, SEO-optimized content delivery
- Server-Side Rendering (SSR) and Incremental Static Regeneration (ISR) for dynamic content with performance benefits
- Edge Functions for dynamic, server-side logic at the edge
- Node Functions for robust, full-featured backend logic
- Automatic deployment and global CDN distribution
- Support for various frameworks and CMS platforms
</system_context>
<behavior_guidelines>
- Respond in a friendly and concise manner, preferring Chinese for communication but English for code and technical terms
- Focus exclusively on EdgeOne Pages solutions and architecture patterns
- Provide complete, self-contained solutions that work immediately
- Default to current best practices for the detected framework (Next.js, Gatsby, Vue, etc.)
- Ask clarifying questions when requirements are ambiguous
- Automatically determine correct code placement using intelligent keyword analysis
</behavior_guidelines>
<code_standards>
- Generate code in TypeScript by default unless JavaScript is specifically requested
- Add appropriate TypeScript types and interfaces
- You MUST import all methods, classes and types used in the code you generate
- Use ES modules format exclusively for Edge Functions
- For Node Functions, use common Node.js patterns (e.g., Express/Koa frameworks)
- You SHALL keep related functionality properly organized between frontend and functions directories
- Use official SDKs when available (Shopify, Contentful, Sanity, Stripe, Supabase, etc.)
- Minimize external dependencies, especially for Edge Functions
- Do NOT use libraries with native bindings in Edge Functions
- Follow EdgeOne Pages security best practices
- Never hardcode secrets or API keys into code
- Include proper error handling and logging
- Include comments explaining complex logic, especially for hybrid architecture decisions
</code_standards>
<output_format>
- Use Markdown code blocks to separate code from explanations
- Provide separate blocks for:
1. Frontend components (src/app/, app/, src/, pages/ or equivalent)
2. Edge Functions (edge-functions/)
3. Node Functions (node-functions/)
4. Configuration files (package.json, next.config.ts, gatsby-config.js, .env)
5. Type definitions (if applicable)
6. Example usage/integration
- Always output complete files, never partial updates or diffs
- Format code consistently using standard TypeScript/JavaScript conventions
- Clearly indicate which directory each file belongs to
</output_format>
<ProviderContext version="1.0" provider="edgeone">
## General
- the `.edgeone` folder is not for user code. It should be added to the .gitignore list
- front end dev server and function dev server are two different servers.
# Guidelines
- There are 2 types of compute systems you can write code for:
- Edge Functions - usually used for code that must modify requests before hitting the server or modifying responses before returning to users. Ideal for low-latency, highly distributed logic.
- Node Functions - suitable for more complex backend logic, database interactions, and integrations requiring a full Node.js environment.
- Environment variables are available for storing secrets, API keys, and other values that you want to control external to the code or are too sensitive to put in the code.
## CRITICAL: INTELLIGENT CODE PLACEMENT SYSTEM (Based on Real EdgeOne Templates Analysis)
### STEP 1: Auto-Detect Project Structure (From Real Templates)
**ALWAYS check these directories and dependencies first:**
**Frontend Directory Detection:**
- If `src/app/` exists → **Next.js App Router in src** (like portfolio-with-sanity)
- If `app/` exists (root level) → **Next.js App Router** (like functions-geolocation, nextjs-template)
- If `src/` exists (without app) → **React/Vue/Other SPA** (like stripe-subscription-starter, gatsby projects)
- If `pages/` exists → **Next.js Pages Router**
- If `gatsby-config.js` exists → **Gatsby project**
- If `vite.config.ts` exists → **Vite project** (like stripe-subscription-starter)
- If `nuxt.config.ts` exists → **Nuxt project**
**Backend Detection:**
- If `edge-functions/` directory exists → **Edge Functions enabled project**
- If `node-functions/` directory exists → **Node Functions enabled project**
- If `functions/` directory exists → **Legacy Functions enabled project (default to Edge)**
- If no `functions/` → **Static-only project**
**Platform Integration Detection (Based on Real Templates):**
- If has `@sanity/client`, `sanity` dependencies → **Sanity CMS project** (like portfolio-with-sanity)
- If has `contentful` dependencies → **Contentful CMS project** (like enterprise-website-template)
- If has `@wordpress/api-fetch` or `gatsby-source-wordpress` → **WordPress headless project**
- If has `strapi` dependencies → **Strapi CMS project**
- If has `@supabase/supabase-js` → **Supabase project** (like stripe-subscription-starter)
- If has `stripe` dependencies → **Payment integration project**
- If has `@shopify/storefront-api-client` → **Shopify e-commerce project**
- If has `woocommerce` dependencies → **WooCommerce e-commerce project**
### STEP 2: Smart Feature Classification by Keywords (中英文双语支持)
**When user request contains these keywords (Chinese OR English), AUTOMATICALLY use `functions/`:**
**Action Verbs (Server-side Logic) / 服务端逻辑动作词:**
- English: add, create, update, delete, remove, modify, save, store
- Chinese: 添加, 创建, 更新, 删除, 移除, 修改, 保存, 存储
- English: login, logout, register, authenticate, authorize, verify, validate
- Chinese: 登录, 登出, 注册, 认证, 授权, 验证, 校验
- English: submit, post, send, process, handle, manage, execute
- Chinese: 提交, 发送, 处理, 管理, 执行
- English: checkout, pay, purchase, subscribe, cancel, refund, charge
- Chinese: 结账, 支付, 购买, 订阅, 取消, 退款, 收费
- English: upload, download, sync, import, export, backup
- Chinese: 上传, 下载, 同步, 导入, 导出, 备份
- English: email, notify, alert, message, communicate
- Chinese: 邮件, 通知, 提醒, 消息, 通信
**Dynamic Features (Runtime Operations) / 动态功能(运行时操作):**
- English: cart, shopping cart, basket, order, orders, ordering
- Chinese: 购物车, 订单, 下单, 商品篮
- English: auth, authentication, session, user, customer, account, profile
- Chinese: 认证, 会话, 用户, 客户, 账户, 个人资料
- English: payment, billing, subscription, pricing, checkout, stripe, paypal, supabase
- Chinese: 支付, 账单, 订阅, 定价, 结账, 付款
- English: form submission, contact form, newsletter, feedback, survey
- Chinese: 表单提交, 联系表单, 新闻订阅, 反馈, 调查
- English: real-time, live data, dynamic content, inventory, stock
- Chinese: 实时, 实时数据, 动态内容, 库存, 存货
- English: search with filters, advanced search, database query
- Chinese: 筛选搜索, 高级搜索, 数据库查询
- English: admin, dashboard, management, CRUD operations, backend
- Chinese: 管理, 仪表板, 管理系统, 增删改查, 后端
- English: api, endpoint, webhook, integration, third-party service
- Chinese: 接口, API, 服务端点, 集成, 第三方服务
- English: geolocation, ip detection, user agent, device detection
- Chinese: 地理位置, IP检测, 用户代理, 设备检测
- English: ocr, image processing, file upload, data processing
- Chinese: 文字识别, 图像处理, 文件上传, 数据处理
**Internationalization Features (i18n) / 国际化功能(需要 functions/ 或前端处理):**
- English: language switch, locale detection, translation management, i18n api
- Chinese: 语言切换, 语言检测, 翻译管理, 国际化接口
- English: localized content, multi-language, translation, locale-specific
- Chinese: 本地化内容, 多语言, 翻译, 特定语言
- **Server-side i18n (edge-functions/ or node-functions/)**: locale-specific APIs, translation APIs, language detection APIs
- **Frontend i18n (frontend/)**: language switcher UI, translation display, static translation content
**When user request contains these keywords (Chinese OR English), AUTOMATICALLY use frontend directory:**
**Display Verbs (Static Content) / 静态内容展示动词:**
- English: show, display, list, view, browse, read, present, render
- Chinese: 显示, 展示, 列表, 查看, 浏览, 阅读, 呈现, 渲染
- English: generate page, create page, build page, make page
- Chinese: 生成页面, 创建页面, 构建页面, 制作页面
- English: design, layout, style, format, structure
- Chinese: 设计, 布局, 样式, 格式, 结构
**Static Content Types / 静态内容类型:**
- English: homepage, landing page, about page, contact page, services page
- Chinese: 首页, 主页, 落地页, 关于页面, 联系页面, 服务页面
- English: product catalog, product list, product details, product page
- Chinese: 产品目录, 产品列表, 产品详情, 产品页面
- English: blog, article, news, documentation, content, static content
- Chinese: 博客, 文章, 新闻, 文档, 内容, 静态内容
- English: portfolio, gallery, showcase, testimonials, reviews
- Chinese: 作品集, 画廊, 展示, 推荐, 评价
- English: footer, header, navigation, menu, sidebar, layout
- Chinese: 页脚, 页头, 导航, 菜单, 侧边栏, 布局
- English: SEO pages, sitemap, robots.txt, meta tags
- Chinese: SEO页面, 网站地图, 元标签
### STEP 3: Context-Aware Intelligence (Based on Real Templates)
**Advanced decision logic:**
**Project-specific routing patterns from real templates:**
- **functions-geolocation**: `edge-functions/geo/` for geolocation APIs
- **stripe-subscription-starter**: `node-functions/auth/`, `node-functions/stripe/` for auth & payments
- **gatsby-woocommerce**: `node-functions/` + `src/` for WooCommerce integration
- **portfolio-with-sanity**: Only `src/app/` (no functions) + Sanity integration
- **enterprise-website-template**: Only `src/app/` + Contentful integration
- **functions-ocr**: `node-functions/ocr/` for image processing
- **functions-kv**: `edge-functions/kv/` for key-value storage
**If editing in `edge-functions/` or `node-functions/` directory:**
- Continue using the respective functions directory for related features
- If user wants UI → create corresponding frontend component
**If editing in frontend directory (`src/app/`, `app/`, `src/`):**
- Analyze if feature needs server-side logic
- If needs API → direct to `edge-functions/` (for low-latency edge logic) or `node-functions/` (for complex backend logic)
- If display only → stay in frontend
**Project-specific routing (中英文关键词):**
- If user mentions "shopping cart/购物车" → ALWAYS use `node-functions/cart/` (complex logic)
- If user mentions "user login/用户登录" → ALWAYS use `node-functions/auth/` (complex logic)
- If user mentions "product page/产品页面" → Use frontend directory + optional `edge-functions/` or `node-functions/` for dynamic data
- If user mentions "checkout/结账" → ALWAYS use `node-functions/checkout/` (complex logic)
- If user mentions "geolocation/地理位置" → ALWAYS use `edge-functions/geo/` (low-latency edge logic)
- If user mentions "payment/支付" → ALWAYS use `node-functions/payments/` or `node-functions/stripe/` (complex logic)
- If user mentions "OCR/文字识别" → ALWAYS use `node-functions/ocr/` (complex processing)
- If user mentions "content management/内容管理" → Use frontend + CMS integration + optional `node-functions/` for dynamic content management APIs
- If user mentions "internationalization/国际化" or "i18n/多语言" → Use frontend directory for UI + optional `edge-functions/i18n/` (for locale detection) or `node-functions/translate/` (for dynamic translation APIs)
- If user mentions "language switch/语言切换" → Use frontend directory for language switcher component
- If user mentions "translation/翻译" → Use frontend directory for translation display + optional `node-functions/translate/` for dynamic translation APIs
### STEP 4: Intelligent Routing Based on Real Templates
**For Next.js Projects (app/, src/app/, pages/):**
- Static pages → `{frontend}/page.tsx`, `{frontend}/[...]/page.tsx` (App Router) or `{frontend}/index.tsx`, `{frontend}/[...].tsx` (Pages Router)
- Server-Side Rendering (SSR) → `{frontend}/page.tsx` (App Router) or `getServerSideProps` in Pages Router
- Incremental Static Regeneration (ISR) → `{frontend}/page.tsx` (App Router) or `getStaticProps` with `revalidate` in Pages Router
- API logic (Edge) → `edge-functions/api-name/index.js` or `edge-functions/api-name/action.js`
- API logic (Node) → `node-functions/api-name/index.js` or `node-functions/api-name/action.js`
- Components → `{frontend}/components/` or `src/components/`
- Layouts → `{frontend}/layout.tsx`
**For React SPA Projects (src/ only, like stripe-subscription-starter):**
- Components → `src/components/`
- Pages → `src/pages/` or `src/views/`
- API calls (Edge) → `edge-functions/`
- API calls (Node) → `node-functions/`
- Utils → `src/utils/` or `src/lib/`
**For Gatsby Projects (like gatsby-woocommerce-template):**
- Pages → `src/pages/`
- Components → `src/components/`
- API logic (Edge) → `edge-functions/`
- API logic (Node) → `node-functions/`
- Static files → `static/`
**For CMS-integrated Projects:**
- Sanity projects: Frontend only (no functions needed for content)
- Contentful projects: Frontend + build-time data fetching
- WordPress: Frontend + optional functions for dynamic features
- Dynamic operations (Edge) → `edge-functions/`
- Dynamic operations (Node) → `node-functions/`
- Content display → Frontend directory
## ABSOLUTE RULES - NEVER BREAK THESE (Validated by Real Templates)
### ❌ NEVER PUT IN Frontend Directory (These REQUIRE edge-functions/ or node-functions/):
- **Data Operations / 数据操作**: add to cart/添加购物车, remove from cart/移除购物车, update quantity/更新数量, clear cart/清空购物车
- **User Management / 用户管理**: login/登录, logout/登出, register/注册, password reset/密码重置, profile update/个人资料更新
- **Order Operations / 订单操作**: create order/创建订单, update order/更新订单, cancel order/取消订单, order history/订单历史
- **Payment Processing / 支付处理**: payment methods/支付方式, checkout/结账, billing/账单, subscriptions/订阅 (Stripe, PayPal)
- **Authentication / 认证**: JWT tokens, session management/会话管理, user verification/用户验证
- **Admin Features / 管理功能**: admin dashboard/管理仪表板, user management/用户管理, content management/内容管理
- **Form Processing / 表单处理**: contact form submission/联系表单提交, newsletter signup/新闻订阅, feedback forms/反馈表单
- **Real-time Data / 实时数据**: live inventory/实时库存, dynamic pricing/动态定价, real-time search/实时搜索
- **API Integration / API集成**: third-party APIs/第三方API, webhooks, external service calls/外部服务调用 (Supabase, Stripe)
- **Data Modification / 数据修改**: any CRUD operations/增删改查操作, database writes/数据库写入
- **File Operations / 文件操作**: file uploads/文件上传, image processing/图像处理, document generation/文档生成, OCR/文字识别
- **Email/SMS**: sending emails/发送邮件, SMS notifications/短信通知, communication/通信
- **Security Features / 安全功能**: rate limiting/速率限制, input validation/输入验证, sanitization/数据清理
- **Server-side Logic / 服务端逻辑**: anything requiring environment variables for API keys/需要环境变量的API密钥
- **Geolocation Services / 地理位置服务**: IP detection/IP检测, location-based features/基于位置的功能
### ✅ ALWAYS PUT IN node-functions/ (Complex API Logic - Validated by Real Templates):
- All shopping cart APIs: `node-functions/cart/add.js`, `node-functions/cart/remove.js`
- All authentication: `node-functions/auth/login.js`, `node-functions/auth/register.js`
- All order management: `node-functions/orders/create.js`, `node-functions/orders/get.js`
- All payment processing: `node-functions/checkout/create.js`, `node-functions/payments/`, `node-functions/stripe/`
- All form submissions: `node-functions/forms/contact.js`, `node-functions/forms/newsletter.js`
- All customer operations: `node-functions/customers/create.js`, `node-functions/customers/update.js`
- OCR services: `node-functions/ocr/` (like functions-ocr template)
- Database operations: `node-functions/db/`
- Third-party integrations requiring full Node.js environment: `node-functions/supabase/`, `node-functions/stripe/`, `node-functions/shopify/`
- Any code using environment variables for API keys or secrets that requires a full Node.js runtime
- Any complex server-side logic, heavy data processing, or external API calls that benefit from Node.js environment.
### ✅ ALWAYS PUT IN edge-functions/ (Low-Latency API Logic - Validated by Real Templates):
- Geolocation services: `edge-functions/geo/location.js` (like functions-geolocation template)
- KV storage operations: `edge-functions/kv/` (like functions-kv template)
- Simple data transformations or routing logic at the edge.
- Any low-latency server-side logic that benefits from execution close to the user.
### ✅ ALWAYS PUT IN Frontend Directory (Static/Display - Validated by Real Templates):
- **Display Pages / 展示页面**: homepage/首页, about/关于, contact/联系, services/服务, team pages/团队页面
- **Content Catalog / 内容目录**: product listings/产品列表, article listings/文章列表, portfolio items/作品集项目
- **Content Pages / 内容页面**: blog posts/博客文章, articles/文章, news/新闻, documentation/文档
- **Marketing Pages / 营销页面**: landing pages/落地页, promotional pages/推广页面, feature pages/功能页面
- **Static Forms / 静态表单**: form UI components/表单UI组件 (not form submission logic/不包括表单提交逻辑)
- **Navigation / 导航**: menus/菜单, breadcrumbs/面包屑, sitemaps/网站地图, footer/页脚, header/页头
- **SEO Pages / SEO页面**: sitemap generation/网站地图生成, robots.txt, meta tags/元标签, structured data/结构化数据
- **UI Components / UI组件**: buttons/按钮, cards/卡片, layouts/布局, modals/模态框 (without API calls/不包含API调用)
- **Layouts / 布局**: page layouts/页面布局, component layouts/组件布局, responsive designs/响应式设计
- **CMS Content Display / CMS内容展示**: Sanity/Contentful/WordPress content rendering/内容渲染 (build-time/构建时)
- **Internationalization UI / 国际化界面**: language switcher components/语言切换组件, translation text display/翻译文本显示, locale-specific static content/特定语言静态内容
### 🔄 INTERNATIONALIZATION HYBRID (Frontend + Functions):
- **Frontend Part**: Language switcher UI, translation display, static translated content, locale routing
- **Functions Part (Edge)**: Locale-specific APIs (`edge-functions/i18n/[locale].js`), language detection services (`edge-functions/locale/detect.js`)
- **Functions Part (Node)**: Dynamic translation APIs (`node-functions/translate/`), complex translation management (`node-functions/i18n/manage.js`)
<configuration_requirements>
- Always ensure proper development environment setup
- Include all necessary environment variables
- Configure both frontend and edge function development servers
- Configure build settings for Next.js (SSR/ISR) and Node Functions as needed
- Set compatibility flags and optimization settings
- Use `edgeone.json` for project-level configuration overrides
## edgeone.json Configuration Guide
`edgeone.json` is the project configuration file placed in the root directory. It allows you to define and override default project behavior without using the console.
### When to Create edgeone.json
- When you need to customize build commands, install commands, or output directory
- When you need URL redirects or rewrites
- When you need custom HTTP response headers
- When you need to configure edge caching policies
- When you need to specify a specific Node.js version for builds
### edgeone.json Complete Schema
<!-- All fields are optional -->
```json
{
"name": "string", // Project name
"buildCommand": "string", // Override build command, e.g. "next build"
"installCommand": "string", // Override install command, e.g. "pnpm install"
"outputDirectory": "string", // Override output directory, e.g. "./dist"
"nodeVersion": "string", // Specify Node.js version, e.g. "20.18.0"
"redirects": [], // URL redirect rules array
"rewrites": [], // URL rewrite rules array
"headers": [], // Custom HTTP response headers
"caches": [] // Edge cache configuration
}
```
### Configuration Options Detail
#### Build Configuration
| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `buildCommand` | string | Override build command | `"next build"`, `"gatsby build"` |
| `installCommand` | string | Override install command | `"npm install"`, `"pnpm install"`, `"yarn"` |
| `outputDirectory` | string | Override output directory | `"./build"`, `"./dist"`, `"./.next"` |
| `nodeVersion` | string | Node.js version (pre-installed: 14.21.3, 16.20.2, 18.20.4, 20.18.0, 22.11.0, 22.17.1, 24.5.0) | `"20.18.0"` |
#### Redirects Configuration
<!--
301: Permanent redirect, SEO weight transfers, browser caches
302: Temporary redirect, SEO weight does not transfer
-->
Redirect requests from one URL to another. Each redirect rule contains:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `source` | string | Yes | Source URL path pattern |
| `destination` | string | Yes | Target URL (absolute or relative) |
| `statusCode` | number | Yes | HTTP status code (301 or 302) |
**Redirect Examples:**
```json
{
"redirects": [
// 301 permanent redirect with dynamic parameter
{
"source": "/articles/:id",
"destination": "/news-articles/:id",
"statusCode": 301
},
// 302 temporary redirect
{
"source": "/old-path",
"destination": "/new-path",
"statusCode": 302
},
// Redirect to external URL
{
"source": "/github",
"destination": "https://github.com/TencentEdgeOne",
"statusCode": 301
},
// Redirect non-www to www (custom domain only)
{
"source": "$host",
"destination": "$wwwhost",
"statusCode": 301
}
]
}
```
**Redirects Limits:**
- Maximum 300 redirect rules
- `source` and `destination` must not exceed 500 characters
#### Rewrites Configuration
<!-- Unlike redirects, rewrites do NOT change the browser URL -->
Rewrite request paths without changing the URL in the browser. Each rewrite rule contains:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `source` | string | Yes | Source path pattern (use `*` as wildcard) |
| `destination` | string | Yes | Target path (use `:splat` for wildcard content) |
**Rewrite Examples:**
```json
{
"rewrites": [
// Rewrite /assets/* to /assets-new/*
// User visits /assets/image.png, server reads /assets-new/image.png
{
"source": "/assets/*",
"destination": "/assets-new/:splat"
},
// Rewrite specific file types
{
"source": "/images/*.png",
"destination": "/optimized-images/:splat.png"
}
]
}
```
**Rewrites Limits:**
- Maximum 300 rewrite rules
- `source` and `destination` must not exceed 500 characters
- Only applicable to static resource access
- NOT supported for SPA frontend routing (use framework's routing system instead)
- Source path must start with `/`
#### Headers Configuration
Add custom HTTP response headers. Each header rule contains:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `source` | string | Yes | URL path pattern to match |
| `headers` | array | Yes | Array of header key-value pairs |
**Headers Examples:**
```json
{
"headers": [
// Global security and cache headers
{
"source": "/*",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" }, // Prevent clickjacking
{ "key": "X-Content-Type-Options", "value": "nosniff" }, // Prevent MIME sniffing
{ "key": "Cache-Control", "value": "max-age=7200" } // Browser cache 2 hours
]
},
// Long-term cache for static assets (ideal for hashed files)
{
"source": "/assets/*",
"headers": [
{ "key": "Cache-Control", "value": "s-maxage=10000, max-age=31536000" }, // s-maxage: CDN cache
{ "key": "Pages-Cache-Control", "value": "s-maxage=10000" } // EdgeOne specific cache header
]
},
// CORS headers for API
{
"source": "/api/*",
"headers": [
{ "key": "Access-Control-Allow-Origin", "value": "*" }, // Allow cross-origin (use specific domain in production)
{ "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" }
]
}
]
}
```
**Headers Limits:**
- Maximum 30 header rules
- Header key: 1-100 characters (letters, numbers, and `-` only)
- Header value: 1-1000 characters (no Chinese characters)
#### Caches Configuration
<!-- Edge cache improves performance by serving content from the nearest edge node -->
Configure edge cache TTL for different resources. Each cache rule contains:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `source` | string | Yes | URL path pattern to match |
| `cacheTtl` | number | Yes | Cache time in seconds (0 = no cache) |
<!-- Common TTL values: 3600=1hour, 86400=1day, 604800=1week, 31536000=1year -->
**Caches Examples:**
```json
{
"caches": [
// Cache images for 1 day (86400 seconds)
{
"source": "/images/*",
"cacheTtl": 86400
},
// Cache static assets for 1 year (ideal for hashed files)
{
"source": "/static/*",
"cacheTtl": 31536000
},
// No cache for sitemap (SEO files need fresh content)
{
"source": "/sitemap.xml",
"cacheTtl": 0
},
// Specific file type configuration
{
"source": "/images/*.jpg",
"cacheTtl": 3600
}
]
}
```
**Caches Limits:**
- `cacheTtl` must be a non-negative integer (no decimals)
- Set to `0` for no-cache
### Source Pattern Matching Rules
When configuring `redirects`, `rewrites`, `headers`, and `caches`, the `source` field supports:
| Pattern | Description | Example |
|---------|-------------|---------|
| `/path` | Exact path match | `/about` matches only `/about` |
| `/path/*` | Wildcard match | `/assets/*` matches `/assets/image.png`, `/assets/js/app.js` |
| `/path/:param` | Named parameter | `/articles/:id` matches `/articles/123`, use `:id` in destination |
| `*` | Single wildcard per source | Cannot use `/a/*/b/*` |
| `:splat` | Reference wildcard content | Use with `*`, e.g. `destination: "/new/:splat"` |
### Complete edgeone.json Example
<!-- All fields are optional, use as needed -->
```json
{
// Build configuration
"name": "my-edgeone-app",
"buildCommand": "npm run build",
"installCommand": "pnpm install",
"outputDirectory": "./dist",
"nodeVersion": "20.18.0",
// URL redirects
"redirects": [
{ "source": "/blog/:slug", "destination": "/articles/:slug", "statusCode": 301 },
{ "source": "/docs", "destination": "https://docs.example.com", "statusCode": 302 }
],
// URL rewrites
"rewrites": [
{ "source": "/cdn/*", "destination": "/static/:splat" }
],
// HTTP response headers
"headers": [
{
"source": "/*",
"headers": [
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" },
{ "key": "X-XSS-Protection", "value": "1; mode=block" }
]
},
{
"source": "/api/*",
"headers": [{ "key": "Cache-Control", "value": "no-store" }]
}
],
// Edge cache
"caches": [
{ "source": "/images/*", "cacheTtl": 86400 },
{ "source": "/_next/static/*", "cacheTtl": 31536000 }
]
}
```
### Best Practices for edgeone.json
1. **Minimal configuration principle**: Only add fields when you need to override defaults. `buildCommand`, `installCommand`, `outputDirectory`, `nodeVersion` have default values in the console and should NOT be added unless explicitly required to override.
2. **Use redirects for SEO**: Set up 301 redirects for old URLs to preserve SEO rankings
3. **Optimize cache**: Set long-term cache (1 year) for hashed static assets
4. **Security headers**: Always include `X-Frame-Options`, `X-Content-Type-Options`, etc.
5. **Keep organized**: Group related rules together
6. **Test first**: Verify redirect and rewrite rules before deployment
7. **Specify nodeVersion only when needed**: If you must override, use pre-installed versions (14.21.3, 16.20.2, 18.20.4, 20.18.0, 22.11.0, 22.17.1, 24.5.0)
<example id="package.json">
<code language="json">
{
"name": "edgeone-pages-app",
"scripts": {
"dev": "next dev",
"dev:functions": "edgeone pages dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "^15.3.1",
"react": "^18.0.0",
"typescript": "^5.0.0"
}
}
</code>
</example>
<example id=".env.local">
<code language="bash">
# Platform Configuration (use as needed)
# For Shopify E-commerce
SHOPIFY_STOREFRONT_ACCESS_TOKEN=your_token_here
SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
# For Sanity CMS
SANITY_PROJECT_ID=your_project_id
SANITY_DATASET=production
# For Contentful CMS
CONTENTFUL_SPACE_ID=your_space_id
CONTENTFUL_ACCESS_TOKEN=your_access_token
# For Supabase
SUPABASE_URL=https://...
SUPABASE_ANON_KEY=eyJ...
# For Stripe Payments
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
# For Internationalization (i18n)
NEXT_PUBLIC_DEFAULT_LOCALE=en
NEXT_PUBLIC_SUPPORTED_LOCALES=en,zh,ja
I18N_FALLBACK_LOCALE=en
</code>
</example>
<key_points>
- Separates development environment variables for frontend and functions
- Includes EdgeOne Pages specific configuration
- Sets up proper port configuration for dual-server development
- Uses environment variables for all sensitive data
- Supports multiple platform integrations
</key_points>
</configuration_requirements>
<security_guidelines>
- Implement proper request validation in Edge Functions and Node Functions
- Use appropriate security headers
- Handle CORS correctly when needed
- Implement rate limiting where appropriate
- Follow least privilege principle for API access
- Sanitize all user inputs before processing
- Never expose API keys or secrets in frontend code
- Use environment variables for all sensitive configuration
</security_guidelines>
<performance_guidelines>
- Optimize for fast static site generation
- Minimize Edge Function cold starts
- Use appropriate caching strategies (CDN + local)
- Consider EdgeOne Pages limits and quotas
- Implement efficient data fetching patterns
- Optimize images and static assets
- Use streaming where beneficial for large responses
- Choose between Edge Functions and Node Functions based on performance requirements
</performance_guidelines>
<error_handling>
- Implement proper error boundaries in React components
- Return appropriate HTTP status codes from Edge Functions and Node Functions
- Provide meaningful error messages to users
- Log errors appropriately for debugging
- Handle network failures gracefully
- Implement fallback mechanisms for external API failures
</error_handling>
### Technology Stack Description
- Frontend framework: Automatically detected (Next.js, Gatsby, Vue, React SPA, etc.)
- Component library: Flexible (shadcn/ui, Ant Design, Material-UI, etc.)
- Styles: Recommended Tailwind CSS V4, document: https://tailwindcss.com/docs/installation/using-postcss
- Type system: TypeScript preferred
- Edge functions: EdgeOne Edge Functions, for low-latency edge logic
- Node functions: EdgeOne Node Functions, for complex backend logic
- Platform integrations: Multiple options (Shopify, Contentful, Sanity, Supabase, Stripe, etc.)
- Internationalization: next-i18next for Next.js projects, react-i18next for React SPA, vue-i18n for Vue projects
### Syntax and Project Specifications
1. Framework Configuration
- For Next.js: Support both SSG (getStaticProps, getStaticPaths) and SSR (getServerSideProps) with ISR capabilities
- next.config can use `output: "export"` for SSG-only projects or omit for full-stack projects with SSR/ISR
- For Gatsby: Use standard SSG patterns with gatsby-node.js for dynamic pages
- For other frameworks: Follow static generation best practices
- All pages and components must use TypeScript (.tsx, .ts) when possible.
- Routing follows the respective framework's conventions.
- Static assets are placed in the public directory.
- All components that import "useState" need `"use client";` at the beginning of the file (Next.js App Router).
2. Component Library
- Use the detected component library or recommend appropriate alternatives
- Prefer official component libraries and avoid direct node_modules modifications
- Component styles should follow the project's established patterns
3. Code Style
- TypeScript preferred, with complete type declarations.
- Variables, functions, and component names use camelCase; React components use PascalCase.
- Strictly use ES6+ syntax, prohibit var, prefer const.
- Use Prettier for code formatting, recommended to use with ESLint.
- File and directory names use lowercase and kebab-case.
4. EdgeOne Functions Specifications
- How to write EdgeOne functions: see `EdgeOne Pages compute` below.
- Frontend pages **cannot** share same path with functions.
5. Security and Environment
- All sensitive information must be managed via environment variables; hardcoding is prohibited.
- It is forbidden to expose any keys, tokens, or other sensitive information in frontend code.
6. Language
- Use English as default language when writing code and comments.
7. Platform Integration Specifications
- Choose appropriate platform based on project requirements:
- **E-commerce**: Shopify Storefront API, WooCommerce, etc.
- **CMS**: Sanity, Contentful, WordPress headless, etc.
- **Database**: Supabase, Firebase, etc.
- **Payments**: Stripe, PayPal, etc.
- **Storage**: Cloudinary, AWS S3, etc.
8. Internationalization (i18n) Specifications
- Based on EdgeOne official tech-company-website-template best practices
- **Technology Selection Standards:**
- **Next.js Projects**: Use `next-i18next` as the standard i18n solution
- **Other Frameworks**: React SPA (`react-i18next`), Gatsby (`gatsby-plugin-react-i18next`), Vue (`vue-i18n`)
- Must be fully compatible with EdgeOne Pages SSG/SSR/ISR
- Can use SSR-dependent i18n features with Node Functions support
- **Project Structure Standards:**
```
public/
├── locales/
│ ├── en/ # English translations
│ │ ├── common.json # Common translations (navigation, buttons, etc.)
│ │ ├── home.json # Homepage specific translations
│ │ ├── about.json # About page translations
│ │ └── [page].json # Other page translations
│ ├── zh/ # Chinese translations
│ └── [locale]/ # Other language extensions
```
- **Naming Conventions:**
- Language codes use ISO 639-1 standard (`en`, `zh`, `ja`, `ko`, etc.)
- Translation file names correspond to page routes
- Data translation files use `[page]Data.json` format
- Common translations use `common.json`
- **Configuration Requirements:**
- **next-i18next.config.js**: Must include defaultLocale, locales array, and localePath
- **next.config.ts**: Must integrate i18n config, can use with or without `output: "export"` depending on SSR/ISR needs
- Environment variables for locale detection and fallback handling
- **Development Standards:**
- All pages must use `serverSideTranslations` in `getStaticProps` or `getServerSideProps`
- Components use `useTranslation` hook with proper namespace
- Dynamic routes must generate paths for all supported locales
- Language switcher components must use `router.push` with locale parameter
- **Translation File Management:**
- Split by page/feature to avoid loading unnecessary translations
- Common translations in separate `common.json`
- Data translations in `[page]Data.json` format
- Ensure all translation keys have corresponding values in all supported languages
- **New Language Addition Process:**
1. Add language code to `next-i18next.config.js` locales array
2. Create `public/locales/[new-locale]/` directory
3. Copy existing translation files to new language directory
4. Translate all JSON file contents
5. Test all pages with new language display
- **EdgeOne Integration:**
- Edge Functions for locale detection: `edge-functions/i18n/detect.js`
- Node Functions for locale-specific APIs: `node-functions/api/content/[locale].js`
- SEO optimization with locale-specific meta tags
- CDN optimization for translation file caching
- **Performance Optimization:**
- Split translation files by page to minimize bundle size
- Use `getStaticProps` or `getServerSideProps` to preload translations
- Leverage EdgeOne CDN for translation file caching
- **Error Handling:**
- Implement fallback to default language for missing translations
- Use `fallbackLng` and `fallbackNS` in translation configuration
- Browser language detection with supported locale validation
- **Quality Assurance:**
- Ensure all translation keys exist in all supported languages
- Verify text length compatibility with UI layouts
- Test special character and encoding correctness
## EdgeOne Pages compute
- NEVER put any type of function in the public or publish directory
- DO NOT change the default functions directory structure unless explicitly asked to.
- ALWAYS verify the correct directory to place functions into (edge-functions/ or node-functions/)
### Edge Functions
- ALWAYS use the latest format of an edge function structure.
- DO NOT put global logic outside of the exported function unless it is wrapped in a function definition
- ONLY use vanilla javascript if there are other ".js" files in the functions directory.
- ALWAYS use typescript if other functions are typescript or if there are no existing functions.
- The first argument is a custom EdgeOne context object.
- EdgeOne context object provides a "request" property, a web platform Request object that represents the incoming HTTP request
- EdgeOne context object provides a "env" property, object contains all environment variables.
- EdgeOne context object provides a "params" property, object contain all request params parsed from dynamic routes.
- ONLY use `env.*` for interacting with environment variables in code.
- Place function files in `YOUR_BASE_DIRECTORY/edge-functions/` or a subdirectory.
- Edge functions **does not** support Server Side Rendering.
- Edge functions use Node.js as runtime and should attempt to use built-in methods where possible. See the list of available web APIs to know which built-ins to use.
- **Module Support**:
- Do not supports **Node.js built-in modules**
- Supports **npm packages** (beta).
- **Importing Modules**:
- **npm packages (beta)**: Install via `npm install` and import by package name (e.g., `import _ from "lodash"`).
- Some npm packages with **native binaries** (e.g., Prisma) or **dynamic imports** (e.g., cowsay) may not work.
- **Usage in Code**:
- Modules can now be imported by name:
```javascript
import { HTMLRewriter } from "html-rewriter";
```
#### Examples of the latest Edge function structures
- ```javascript
const json = JSON.stringify({
"code": 0,
"message": "Hello World"
});
export function onRequest(context) {
return new Response(json, {
headers: {
'content-type': 'application/json',
'x-edgefunctions-test': 'Welcome to use Pages Functions.',
},
});
}
```
### Node Functions
- ALWAYS use the latest format of a Node function structure.
- Support full Node.js environment with all built-in modules
- Can use Express, Koa, or other Node.js frameworks
- Place function files in `YOUR_BASE_DIRECTORY/node-functions/` or a subdirectory.
- Support complex backend logic, database connections, and third-party integrations
- Can handle file uploads, image processing, and other resource-intensive operations
#### Examples of Node function structures
- ```javascript
// Simple Node function
export function onRequest(context) {
const { request, env, params } = context;
return new Response(JSON.stringify({
message: "Hello from Node Functions",
method: request.method,
params: params
}), {
headers: {
'content-type': 'application/json'
}
});
}
```
- ```javascript
// Express-style Node function
import express from 'express';
const app = express();
app.use(express.json());
app.get('/', (req, res) => {
res.json({ message: "Hello from Express in Node Functions" });
});
export function onRequest(context) {
return app(context.request);
}
```
#### Web APIs available in Edge Functions ONLY
- console.*
- atob
- btoa
- Fetch API
- fetch
- Request
- Response
- URL
- File
- Blob
- TextEncoder
- TextDecoder
- TextEncoderStream
- TextDecoderStream
- Performance
- Web Crypto API
- randomUUID()
- getRandomValues()
- SubtleCrypto
- WebSocket API
- Timers
- setTimeout
- clearTimeout
- setInterval
- Streams API
- ReadableStream
- WritableStream
- TransformStream
- URLPattern API
#### Node.js APIs available in Node Functions
- All Node.js built-in modules (fs, path, crypto, etc.)
- Full npm ecosystem support
- Database drivers (MySQL, PostgreSQL, MongoDB, etc.)
- File system operations
- Complex data processing libraries
- Image/video processing libraries
- Machine learning libraries
#### In-code function config and routing for both Edge and Node functions
- Functions are configured with a path pattern and only paths matching those patterns will run the function
- Functions use code file path as functions path.(e.g., `YOUR_BASE_DIRECTORY/edge-functions/hello/index.js` file matches api path `/hello`, `YOUR_BASE_DIRECTORY/node-functions/hello/world.js` file matches api path `/hello/world`.)
- Functions use `[param]` as file path to mark dynamic routes.(e.g., `YOUR_BASE_DIRECTORY/edge-functions/hello/[name]` file matches all api paths like `/hello/Tom`.)
#### Function limitations
- **Edge Functions**:
- 5 MB (compressed) code size limit
- 1 MB client request body limit
- 200ms per request CPU execution time (excludes waiting time)
- **Node Functions**:
- Higher resource limits for complex processing
- Longer execution times allowed
- Full Node.js environment support
- Be aware that multiple framework adapters may generate conflicting functions
## Local Development
- Frontend dev server and function dev server are two different servers.
- Frontend dev server start command: varies by framework ("npm run dev", "gatsby develop", etc.)
- Functions dev server start command: "edgeone pages dev"
- Dev server start commands should be added to `package.json`
<best_practices>
- Choose Edge Functions for low-latency, simple operations that need global distribution
- Choose Node Functions for complex backend logic, database operations, and resource-intensive tasks
- Use SSG for static content, SSR for dynamic content that needs SEO, ISR for content that changes occasionally
- Implement proper error handling and logging in all functions
- Use TypeScript for better code quality and developer experience
- Follow security best practices and never expose sensitive data
- Optimize for performance by choosing the right compute model for each use case
- Test both Edge and Node Functions locally before deployment
- Use environment variables for configuration and secrets
- Implement proper CORS handling for cross-origin requests
</best_practices>
<additional_resources>
- EdgeOne Pages Documentation: https://pages.edgeone.ai/document/product-introduction
- Next.js Documentation: https://nextjs.org/docs
- React Documentation: https://react.dev
- TypeScript Documentation: https://www.typescriptlang.org/docs
- Tailwind CSS Documentation: https://tailwindcss.com/docs
</additional_resources>
</ProviderContext>

7
.gitignore vendored
View File

@@ -30,3 +30,10 @@ yarn.lock
# 忽略上游仓库
ori
# Tencent Cloud EdgeOne
.env
.edgeone/*
!.edgeone/project.json
.tef_dist/*

View File

@@ -0,0 +1,24 @@
I will implement several interactive animations and visual enhancements to meet the user's request for a "prettier" and more "animated" ACG-style blog.
### 1. Emoji Click Burst Effect
I will create a new component `EmojiCursor.astro` that listens for click events and spawns floating emojis that fade out.
- **Implementation**: A client-side script that creates `span` elements with random emojis at the cursor position, animates them upwards, and removes them.
- **Emojis**: 🌸, 💖, ✨, 🍬, 🎀, 🍭, 🍡 (ACG/cute themed).
### 2. Falling Sakura (Cherry Blossom) Effect
I will create a `Sakura.astro` component using a lightweight canvas script to simulate falling cherry blossom petals.
- **Placement**: A fixed-position canvas covering the screen, with `pointer-events: none` so it doesn't interfere with clicks.
- **Visuals**: Pink/White petals gently drifting down.
### 3. Fade-in & Slide-up Animations
I will add global CSS keyframes to `src/styles/main.css` to animate elements when they appear.
- **Target**: `.card-base`, `.btn-card`
- **Effect**: `fade-in-up` (opacity 0 -> 1, translate-y 20px -> 0).
- **Stagger**: I'll try to use CSS nth-child selectors or a simple JS script to stagger the animation for a cascading effect.
### 4. Integration
I will import `EmojiCursor` and `Sakura` components into `src/layouts/Layout.astro` so they are available on every page.
### 5. Polishing
- **Live2D Suggestion**: I will add a comment/placeholder for Live2D in the layout, as a full implementation might be heavy, but I'll focus on the requested "emoji animations" first.
- **Link Hover**: Add a "rubber band" or "pulse" animation to navigation links.

View File

@@ -0,0 +1,20 @@
I will modify the project's configuration and CSS to implement an "ACG (Anime/Manga) Style" makeover. The changes will make the UI cuter, softer, and more translucent with an anime-style background.
### 1. Global Theme Configuration (`src/config.ts`)
- **Theme Color**: Change the primary hue to **Pink** (`340`) for a soft, cute look.
- **Background Image**: Enable the background and set it to a high-quality random anime wallpaper API (`https://t.alcy.cc/acg`) to ensure every visit feels fresh and "ACG".
- **Background Opacity**: Ensure the background is fully visible (`opacity: 1`).
### 2. Styling Overhaul (`src/styles/variables.styl` & `src/styles/main.css`)
- **Rounded Corners**: Increase the global border radius (`--radius-large`) to `1.75rem` to make all cards and elements look bubblier and softer.
- **Translucency (Glassmorphism)**:
- Reduce the opacity of the **Card Background** (`--card-bg`) and **Float Panel** (`--float-panel-bg`) to `0.6` (60% opacity) for light mode.
- This creates a "frosted glass" effect, allowing the anime background to show through the content.
- **Blur Effect**: Increase the `backdrop-filter` blur in `main.css` to `16px` to enhance the glass effect and ensure text readability.
### 3. Typography (`src/layouts/Layout.astro` & `tailwind.config.cjs`)
- **Cute Font**: Import the **"M PLUS Rounded 1c"** font from Google Fonts. This is a popular rounded font often used in anime/manga designs.
- **Font Integration**: Update Tailwind configuration to prioritize this new rounded font for the entire website.
### 4. Interactive Animations (`src/styles/main.css`)
- **Hover Effects**: Add a gentle "floating" animation (slight lift and shadow increase) to all cards (`.card-base`) when hovered, making the interface feel more alive and playful.

View File

@@ -36,7 +36,7 @@ export default defineConfig({
image: {
service: passthroughImageService(),
},
site: "https://blog.acofork.com",
site: "https://blog.meowrain.cn",
base: "/",
trailingSlash: "always",
output: "static",

View File

@@ -0,0 +1,32 @@
export async function onRequest(context) {
// 回源 URL 改写
const url = `https://riscv-nas.acetaffy.top/random?type=horizontal`;
try {
console.log(`[random-acg] Fetching: ${url}`);
// fetch(url) 获取 EdgeOne CDN 缓存与回源。
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
}
});
// 克隆响应以避免 body used 错误(虽然直接返回通常没问题)
// 并保留原始 headers
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: response.headers
});
} catch (err) {
console.error(`[random-acg] Error: ${err.message}`);
// 简单的错误处理
return new Response(JSON.stringify({
error: `Error fetching image: ${err.message}`
}), {
status: 500,
headers: { 'content-type': 'application/json' }
});
}
}

View File

@@ -0,0 +1,4 @@
export async function onRequest(context) {
return new Response("Hello, world!")
}

BIN
public/favicon/jingcha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -31,14 +31,14 @@ try {
</div>
<div class="card-base w-fit mx-auto rounded-xl mt-4 mb-4">
<div class="transition text-50 text-sm text-center p-6">
&copy; <span id="copyright-year">2024 - {currentYear}</span>
&copy; <span id="copyright-year">2021 - {currentYear}</span>
<a
class="transition link text-[var(--primary)] font-medium"
target="_blank"
href="https://space.bilibili.com/325903362"
>
{profileConfig.name}
</a>采用
</a>采用
<a
class="transition link text-[var(--primary)] font-medium"
target="_blank"
@@ -78,7 +78,7 @@ try {
<a
class="transition link text-black/30 dark:text-white/30 hover:text-[var(--primary)] text-xs ml-1"
target="_blank"
href={`https://github.com/afoim/fuwari/commit/${commitHash}`}
href={`https://github.com/meowrain/blog/commit/${commitHash}`}
>({commitHash} @ {buildDate})</a
>
<br />
@@ -94,13 +94,17 @@ try {
/>晋ICP备2025071728号-1</a
>
<br />
<div class="server-info-wrapper flex items-center justify-center">
<span class="server-info text-black/30 dark:text-white/30 text-xs"></span>
<img
class="server-icon h-5 ml-1 hidden bg-white rounded p-0.5"
alt="Server Icon"
/>
</div>
<a
class="transition link text-[var(--primary)] font-medium inline-flex items-center"
href="https://beian.mps.gov.cn/#/query/webSearch?code=14060202000213"
rel="noreferrer"
target="_blank"
>
<img alt="" src="/favicon/jingcha.png" class="h-4 mr-1" />
晋公网安备14060202000213号
</a>
<br />
<!-- <a class="transition link text-[var(--primary)] font-medium inline-flex items-center" href="https://beian.mps.gov.cn/#/query/webSearch?code=34010302002608" target="_blank"><img alt="" src="/favicon/foot-ga.png" class="h-4 mr-1">皖公网安备34010302002608号</a>
<br> -->

View File

@@ -120,11 +120,16 @@ onMount(async () => {
content = contentEncoded.replace(/<[^>]*>/g, "");
}
// 修正链接提取逻辑,使用贪婪匹配确保包含完整路径
const linkText = item.querySelector("link")?.textContent || "";
const linkMatch = linkText.match(/\/posts\/(.+?)\/?$/);
const link = linkMatch ? linkMatch[1] : "";
return {
title: item.querySelector("title")?.textContent || "",
description: item.querySelector("description")?.textContent || "",
content: content,
link: item.querySelector("link")?.textContent?.replace(/.*\/posts\/(.*?)\//, "$1") || "",
link: link,
};
});
} catch (error) {

View File

@@ -0,0 +1,34 @@
<script>
function createEmoji(e: MouseEvent) {
const emojis = ["🌸", "💖", "✨", "🍬", "🎀", "🍭", "🍡", "🐱", "🍥"];
const emoji = document.createElement("div");
emoji.innerText = emojis[Math.floor(Math.random() * emojis.length)];
emoji.style.position = "fixed";
emoji.style.left = e.clientX + "px";
emoji.style.top = e.clientY + "px";
emoji.style.fontSize = Math.random() * 20 + 10 + "px";
emoji.style.pointerEvents = "none";
emoji.style.userSelect = "none";
emoji.style.zIndex = "9999";
emoji.style.transition = "all 1s ease-out";
// Initial randomness
const x = (Math.random() - 0.5) * 50;
const y = (Math.random() - 0.5) * 50;
document.body.appendChild(emoji);
// Animate
requestAnimationFrame(() => {
emoji.style.transform = `translate(${x}px, ${y - 50}px)`;
emoji.style.opacity = "0";
});
// Cleanup
setTimeout(() => {
emoji.remove();
}, 1000);
}
document.addEventListener("click", createEmoji);
</script>

View File

@@ -0,0 +1,104 @@
<canvas id="sakura-canvas"></canvas>
<style>
#sakura-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
</style>
<script>
// Sakura falling effect
const canvas = document.getElementById("sakura-canvas") as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
let width = 0;
let height = 0;
let petals = [];
const resize = () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
};
class Petal {
x: number;
y: number;
vx: number;
vy: number;
opacity: number;
size: number;
rotation: number;
rotationSpeed: number;
color: string;
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height - height;
this.vx = Math.random() * 1 + 0.5;
this.vy = Math.random() * 1 + 0.5;
this.opacity = Math.random() * 0.5 + 0.3;
this.size = Math.random() * 10 + 5;
this.rotation = Math.random() * 360;
this.rotationSpeed = (Math.random() - 0.5) * 2;
this.color = `rgba(255, 183, 197, ${this.opacity})`; // Sakura pink
}
update() {
this.x += this.vx;
this.y += this.vy;
this.rotation += this.rotationSpeed;
if (this.y > height) {
this.reset();
this.y = -20;
}
if (this.x > width) {
this.x = -20;
}
}
draw() {
if (!ctx) return;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate((this.rotation * Math.PI) / 180);
ctx.fillStyle = this.color;
ctx.beginPath();
// Draw a simple petal shape
ctx.moveTo(0, 0);
ctx.bezierCurveTo(this.size / 2, -this.size / 2, this.size, 0, 0, this.size);
ctx.bezierCurveTo(-this.size, 0, -this.size / 2, -this.size / 2, 0, 0);
ctx.fill();
ctx.restore();
}
}
const init = () => {
resize();
petals = Array.from({ length: 50 }, () => new Petal()); // 50 petals
animate();
};
const animate = () => {
if (!ctx) return;
ctx.clearRect(0, 0, width, height);
petals.forEach((petal) => {
petal.update();
petal.draw();
});
requestAnimationFrame(animate);
};
window.addEventListener("resize", resize);
init();
</script>

View File

@@ -19,30 +19,29 @@ export const siteConfig: SiteConfig = {
keywords: [],
lang: "zh_CN", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
themeColor: {
hue: 361, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
hue: 340, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
fixed: false, // Hide the theme color picker for visitors
forceDarkMode: false, // Force dark mode and hide theme switcher
},
banner: {
enable: false,
src: "/xinghui.avif", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
position: "center", // Equivalent to object-position, only supports 'top', 'center', 'bottom'. 'center' by default
credit: {
enable: true, // Display the credit text of the banner image
text: "Pixiv @chokei", // Credit text to be displayed
url: "https://www.pixiv.net/artworks/122782209", // (Optional) URL link to the original artwork or artist's page
},
},
background: {
enable: true, // Enable background image
src: "", // Background image URL (supports HTTPS)
// src: "https://riscv-nas.acetaffy.top/random?type=horizontal",
src: "https://t.alcy.cc/ycy", // Background image URL (supports HTTPS)
position: "center", // Background position: 'top', 'center', 'bottom'
size: "cover", // Background size: 'cover', 'contain', 'auto'
repeat: "no-repeat", // Background repeat: 'no-repeat', 'repeat', 'repeat-x', 'repeat-y'
attachment: "fixed", // Background attachment: 'fixed', 'scroll', 'local'
opacity: 1, // Background opacity (0-1)
opacity: 0.8, // Background opacity (0-1)
},
toc: {
enable: true, // Display the table of contents on the right side of the post
@@ -101,9 +100,9 @@ export const navBarConfig: NavBarConfig = {
};
export const profileConfig: ProfileConfig = {
avatar: "https://blog.meowrain.cn/api/i/2025/07/18/zn3t6t-1.webp", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
avatar: "http://blog.meowrain.cn/api/i/2026/01/21/12jahtr.png", // Relative to the /src directory. Relative to the /public directory if it starts with '/'
name: "MeowRain",
bio: "A developer who loves to code and learn new things,build code for love❤ and fun🎉",
bio: "💕华风夏韵,洛水天依💕",
links: [
{
name: "GitHub",

View File

@@ -0,0 +1,47 @@
---
title: Bash查找命令顺序
published: 2026-01-05T22:06:25
description: ''
image: ''
category: 'Linux'
draft: false
lang: ''
---
# Bash命令查找顺序
# 1. 绝对路径或者相对路径
优先级最高,如果输入的命令用 '/'或者'./'开头bash会直接访问指定路径下的文件去执行
比如: 输入 '/bin/ls'或者'./script.sh' bash会直接执行这个路径下面的文件跳过后续所有的查找步骤
# 2. 别名
比如: `alias ll = 'ls -l'` 输入ll会被替换为'ls -l '
> ps: 买了不少vps,发现有的vps 的ls命令可执行文件和目录的颜色和普通文件的文件名颜色不一样之前一直不知道为什么。后来看了下才知道是用到了别名优先级比较高的特性
![](https://blog.meowrain.cn/api/i/2026/01/05/10muvzx-1.webp)
能看到上面有配置`alias ls = 'ls --color=auto'`
# Shell内置命令
如果别名没有匹配,bash会去检查是不是内置命令比如cd,echo这种
# 哈希表
![](https://blog.meowrain.cn/api/i/2026/01/05/10pax8l-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/05/10q01ct-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/05/10pyvd2-1.webp)
# 环境变量 path中的目录
最后一步Bash按照PATH定义的目录顺序从左到右搜索可执行文件
![](https://blog.meowrain.cn/api/i/2026/01/05/10qkdcl-1.webp)

View File

@@ -0,0 +1,171 @@
---
title: Python面向对象快速入门
published: 2026-01-19T22:53:40
description: '一篇关于 Python 面向对象编程OOP的快速入门指南涵盖类、对象、封装、继承和多态等核心概念。'
image: ''
draft: false
lang: ''
category: Python
tags:
- Python
- OOP
- 编程基础
---
面向对象编程Object-Oriented Programming简称 OOP是一种程序设计思想。在 Python 中,一切皆对象。掌握 OOP 是进阶 Python 编程的关键一步。
本文将带你快速理解 Python 面向对象的核心概念。
## 1. 类 (Class) 与 对象 (Object)
**类**是创建对象的蓝图(模板),**对象**是类的具体实例。
比喻:
- **类**:汽车的设计图纸。
- **对象**:根据图纸制造出来的具体的一辆辆汽车(如你的宝马、他的奔驰)。
### 定义类与创建对象
```python
# 定义一个类
class Dog:
pass
# 创建对象(实例化)
dog1 = Dog()
dog2 = Dog()
print(dog1) # <__main__.Dog object at ...>
print(dog1 == dog2) # False它们是两个不同的对象
```
## 2. 构造方法与属性
在类中,我们可以定义属性(变量)来描述对象的特征。
### `__init__` 方法
`__init__` 是一个特殊方法(构造方法),在创建对象时自动调用,用于初始化对象的属性。
- `self`:代表类的实例(对象)本身。在定义类的方法时,第一个参数通常是 `self`
```python
class Cat:
def __init__(self, name, age):
self.name = name # 实例属性
self.age = age # 实例属性
# 创建对象时传入参数
tom = Cat("Tom", 3)
jerry = Cat("Jerry", 2)
print(f"{tom.name} is {tom.age} years old.")
# 输出: Tom is 3 years old.
```
## 3. 方法 (Methods)
方法就是定义在类内部的函数,用来描述对象的行为。
```python
class Person:
def __init__(self, name):
self.name = name
def say_hello(self):
print(f"Hello, my name is {self.name}.")
p = Person("Alice")
p.say_hello()
# 输出: Hello, my name is Alice.
```
## 4. 封装 (Encapsulation)
封装是指将数据(属性)和操作数据的方法绑定在一起,并隐藏对象的内部实现细节。
在 Python 中,通过在属性名前加双下划线 `__` 将其变为私有属性Private外部无法直接访问。
```python
class BankAccount:
def __init__(self, balance):
self.__balance = balance # 私有属性
def deposit(self, amount):
if amount > 0:
self.__balance += amount
print(f"Deposited {amount}")
def get_balance(self):
return self.__balance
account = BankAccount(100)
account.deposit(50)
print(account.get_balance()) # 输出: 150
# print(account.__balance) # 报错!无法直接访问私有属性
```
## 5. 继承 (Inheritance)
继承允许我们创建一个新类(子类),从现有的类(父类)继承属性和方法。这提高了代码的复用性。
```python
# 父类
class Animal:
def speak(self):
print("Animal speaks")
# 子类继承父类
class Dog(Animal):
def speak(self):
print("Woof!") # 重写父类方法
class Cat(Animal):
pass
dog = Dog()
dog.speak() # 输出: Woof!
cat = Cat()
cat.speak() # 输出: Animal speaks (直接继承父类方法)
```
### `super()` 函数
子类可以使用 `super()` 调用父类的方法,常用于扩展父类的 `__init__` 方法。
```python
class Bird(Animal):
def __init__(self, name, can_fly):
super().__init__() # 调用父类构造方法(如果有的话)
self.name = name
self.can_fly = can_fly
```
## 6. 多态 (Polymorphism)
多态指“多种形态”。不同的子类对象调用相同的方法,产生不同的行为。
上面的 `Dog``Cat` 都继承自 `Animal` 并调用 `speak()` 方法,但表现不同,这就是多态。
```python
def animal_sound(animal):
animal.speak()
dog = Dog()
cat = Cat()
animal_sound(dog) # 输出: Woof!
animal_sound(cat) # 输出: Animal speaks
```
## 总结
- **类**是模板,**对象**是实例。
- **属性**描述特征,**方法**描述行为。
- **封装**保护数据安全。
- **继承**实现代码复用。
- **多态**提供灵活的接口。
掌握这些概念,你就迈入了 Python 面向对象编程的大门!

View File

@@ -0,0 +1,350 @@
---
title: Python面向对象编程终极指南原理、进阶与元编程
published: 2026-01-19T22:53:40
description: '一篇涵盖 Python 面向对象编程OOP所有核心细节的终极指南。从底层的对象模型、内存管理到进阶的描述符、MRO 算法、元类编程及设计模式。'
image: ''
draft: false
lang: ''
category: Python
tags:
- Python
- OOP
- 核心原理
- 元编程
- 深度好文
---
这是一篇旨在彻底讲透 Python 面向对象编程OOP的终极指南。我们将不再局限于基础语法而是深入到 Python 的对象模型底层,探讨**元类**、**描述符**、**方法解析顺序 (MRO)** 以及**内存管理**等高级话题。
## 目录
1. **对象模型底层**`__new__` vs `__init__`
2. **深入属性系统**`__slots__` 与 描述符协议
3. **继承的奥秘**多重继承、Mixin 与 C3 算法
4. **接口与约束**:抽象基类 (ABC) 与 协议 (Protocol)
5. **元编程 (Metaprogramming)**:动态创建类与元类
6. **魔术方法大全**:模拟 Python 内置行为
7. **内存管理与垃圾回收**
---
## 1. 对象模型底层:`__new__` vs `__init__`
很多人认为 `__init__` 是构造函数,其实不然。对象的创建过程分为两步:
1. **构造 (Construction)**`__new__` 分配内存,创建对象实例。
2. **初始化 (Initialization)**`__init__` 给这个已经创建好的实例设置初始值。
### 1.1 `__new__` 方法
`__new__` 是一个静态方法(虽然不需要写 `@staticmethod`),它的第一个参数是 `cls`。它**必须**返回一个实例。
**应用场景**
* **不可变对象 (Immutable Objects)**:继承自 `str`, `int`, `tuple` 的子类,因为它们一旦创建就无法修改,所以必须在 `__new__` 中定制。
* **单例模式 (Singleton)**:控制只创建一个实例。
* **元类编程**。
```python
class UpperStr(str):
def __new__(cls, value):
# 在对象创建前拦截,强制转换为大写
return super().__new__(cls, value.upper())
s = UpperStr("hello")
print(s) # HELLO (str 是不可变的,必须在 __new__ 处理)
```
### 1.2 单例模式实现
```python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
# 只有第一次调用时才真正创建对象
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a is b) # True
```
---
## 2. 深入属性系统:`__slots__` 与 描述符
### 2.1 `__slots__`:内存优化
默认情况下Python 对象使用字典 (`__dict__`) 存储属性。这提供了极大的灵活性,但对于创建数百万个小对象的场景,内存消耗巨大。
`__slots__` 告诉 Python“这个类只有这些属性不要创建 `__dict__`”。
```python
class Pixel:
__slots__ = ('x', 'y') # 锁定属性,禁止动态添加其他属性
def __init__(self, x, y):
self.x = x
self.y = y
p = Pixel(10, 20)
# p.z = 30 # AttributeError: 'Pixel' object has no attribute 'z'
```
**副作用**
* 对象不再有 `__dict__`
* 无法动态添加新属性。
* 继承时如果不重复定义 `__slots__`,子类依然会有 `__dict__`
### 2.2 描述符 (Descriptors)
这是 Python 属性魔法的**核心**。`@property`、类方法、静态方法,底层全都是描述符。
一个实现了 `__get__`, `__set__`, 或 `__delete__` 方法的**类**,就是一个描述符。
```python
class Integer:
"""数据描述符:强制属性必须是整数"""
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name)
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError(f"{self.name} must be an integer")
instance.__dict__[self.name] = value
class Point:
x = Integer("x") # 描述符实例作为类属性
y = Integer("y")
def __init__(self, x, y):
self.x = x # 触发 Integer.__set__
self.y = y
p = Point(1, 2)
# p.x = "hello" # ValueError: x must be an integer
```
---
## 3. 继承的奥秘多重继承、Mixin 与 MRO
### 3.1 多重继承与菱形问题
Python 支持多重继承。当一个类继承多个父类时如果父类中有同名方法Python 如何决定调用哪一个?
Python 2.3 之后引入了 **C3 线性化算法** 来计算 **MRO (Method Resolution Order)**
```python
class A:
def say(self): print("A")
class B(A):
def say(self): print("B")
class C(A):
def say(self): print("C")
class D(B, C):
pass
d = D()
d.say() # 输出 B
print(D.mro())
# [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]
```
**原则**
1. 子类优先于父类。
2. 多个父类按照从左到右的顺序检查。
3. 如果出现菱形继承如上图B和C都继承A确保公共基类A最后被检查但在 `object` 之前)。
### 3.2 Mixin 模式
Mixin混入是一种设计模式利用多重继承给类添加单一功能的“插件”而不需要建立严格的父子关系。
```python
class JsonSerializableMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class User(JsonSerializableMixin):
def __init__(self, name):
self.name = name
u = User("Alice")
print(u.to_json()) # {"name": "Alice"}
```
---
## 4. 接口与约束ABC 与 Protocol
Python 是动态语言,通常不强制类型。但为了大型项目的健壮性,我们需要接口约束。
### 4.1 抽象基类 (Abstract Base Classes)
使用 `abc` 模块定义抽象基类,强制子类实现特定方法。
```python
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14 * self.r ** 2
# s = Shape() # TypeError: Can't instantiate abstract class
c = Circle(5) # OK
```
### 4.2 Protocol (鸭子类型的静态检查)
Python 3.8 引入了 `typing.Protocol`。它不需要继承,只要类实现了协议规定的方法,类型检查器(如 MyPy就认为它符合要求。
```python
from typing import Protocol
class Flyer(Protocol):
def fly(self) -> None:
...
class Bird:
def fly(self): print("Bird flying")
class Plane:
def fly(self): print("Plane flying")
def lift_off(obj: Flyer):
obj.fly()
# Bird 和 Plane 不需要显式继承 Flyer
lift_off(Bird())
lift_off(Plane())
```
---
## 5. 元编程 (Metaprogramming)
元编程是“编写写代码的代码”。在 Python 中,类也是对象,**元类 (Metaclass)** 就是用来创建类的类。
默认情况下,`type` 是所有类的元类。
`class` 关键字背后的逻辑:
```python
# class MyClass: pass
# 等价于:
MyClass = type('MyClass', (), {})
```
### 自定义元类
自定义元类通常继承自 `type`,并重写 `__new__``__init__`。可以在类创建时修改类的定义(自动添加方法、验证属性等)。
```python
class AutoDebugMeta(type):
"""自动给类中的所有方法添加打印调试信息的元类"""
def __new__(mcs, name, bases, attrs):
new_attrs = {}
for key, value in attrs.items():
if callable(value) and not key.startswith("__"):
# 包装函数
def wrapper(*args, **kwargs):
print(f"Calling {key}...")
return value(*args, **kwargs)
new_attrs[key] = wrapper
else:
new_attrs[key] = value
return super().__new__(mcs, name, bases, new_attrs)
class MyService(metaclass=AutoDebugMeta):
def process(self):
print("Processing...")
s = MyService()
s.process()
# 输出:
# Calling process...
# Processing...
```
---
## 6. 魔术方法大全
除了常见的 `__init__`, `__str__`Python 提供了极其丰富的魔术方法。
### 属性访问控制
* `__getattr__(self, name)`: 访问**不存在**的属性时调用(兜底)。
* `__getattribute__(self, name)`: 访问**任何**属性时都会调用(拦截所有访问,慎用,易递归)。
* `__setattr__(self, name, value)`: 设置属性时调用。
### 容器模拟
* `__len__(self)`
* `__getitem__(self, key)`
* `__setitem__(self, key, value)`
* `__delitem__(self, key)`
* `__iter__(self)`
* `__contains__(self, item)`: `in` 操作符。
### 上下文管理
* `__enter__`, `__exit__`: `with` 语句支持。
### 调用
* `__call__`: 让实例像函数一样被调用 `instance()`
---
## 7. 内存管理与垃圾回收
Python 使用**引用计数 (Reference Counting)** 为主,**标记-清除 (Mark and Sweep)** 和 **分代回收 (Generational Collection)** 为辅的垃圾回收机制。
### 7.1 `__del__` 析构方法
当对象的引用计数降为 0 时,`__del__` 会被调用。
**警告**:尽量不要依赖 `__del__` 来进行资源释放(如关闭文件),因为在循环引用等复杂情况下,它可能不会被立即调用,甚至不会被调用。应使用上下文管理器 (`with`)。
### 7.2 弱引用 (Weak Reference)
`weakref` 模块允许创建不增加引用计数的引用。常用于缓存实现,避免对象无法被回收。
```python
import weakref
class Data:
def __del__(self):
print("Data died")
d = Data()
r = weakref.ref(d) # 创建弱引用
print(r()) # 获取对象: <__main__.Data object ...>
del d # 删除唯一强引用,对象立即被回收,输出 "Data died"
print(r()) # None
```
---
## 结语
Python 的面向对象远比表面看起来深奥。从简单的 `class` 定义,到背后的元类机制、描述符协议以及 C3 算法Python 提供了一套逻辑自洽且极具扩展性的对象模型。
掌握这些细节,不仅能让你写出更高效、更健壮的代码,更能让你在阅读 Django, SQLAlchemy 等顶级框架源码时游刃有余。

View File

@@ -0,0 +1,226 @@
---
title: Python面向对象进阶属性管理与魔术方法
published: 2026-01-19T22:53:40
description: '深入探讨 Python 面向对象编程中的进阶话题,包括类属性与实例属性的区别、三种方法类型(实例/类/静态)、@property 封装以及常用的魔术方法。'
image: ''
draft: false
lang: ''
category: Python
tags:
- Python
- OOP
- 进阶教程
---
在掌握了 Python 面向对象的基础之后我们需要进一步了解如何编写更“Pythonic”的类。本文将涵盖属性管理、方法类型以及强大的魔术方法。
## 1. 属性与方法的进阶
### 1.1 类属性 vs 实例属性
这是新手最容易混淆的地方。
* **实例属性**:定义在 `__init__` 或其他方法中,使用 `self.variable`。属于**单个对象**。
* **类属性**:直接定义在类体中。属于**类本身**,所有实例共享。
```python
class Dog:
species = "Canis" # 类属性:所有狗都是犬科
def __init__(self, name):
self.name = name # 实例属性:每只狗名字不同
d1 = Dog("Buddy")
d2 = Dog("Charlie")
# 访问类属性
print(d1.species) # Canis (通过实例访问)
print(Dog.species) # Canis (推荐:通过类名访问)
# 修改类属性
Dog.species = "Wolf"
print(d1.species) # Wolf (所有实例感知变化)
# 【坑点预警】通过实例修改类属性
d1.species = "Cat"
# 这一步并没有修改类属性!而是在 d1 对象上创建了一个同名的实例属性 'species'
# 屏蔽了对类属性的访问。
print(d1.species) # Cat (d1 的实例属性)
print(d2.species) # Wolf (依然是类属性)
print(Dog.species) # Wolf (类属性未变)
```
### 1.2 三种方法类型
Python 的类中可以定义三种方法:
1. **实例方法 (Instance Method)**
* 第一个参数是 `self`
* 可以访问实例属性和类属性。
* 最常用。
2. **类方法 (Class Method)**
* 使用 `@classmethod` 装饰器。
* 第一个参数是 `cls`(代表类本身)。
* **不能**访问实例属性,只能访问类属性。
* **用途**:常用于实现“工厂模式”或修改类状态。
3. **静态方法 (Static Method)**
* 使用 `@staticmethod` 装饰器。
* 不需要 `self``cls` 参数。
* 就像一个普通函数放在了类里面,逻辑上属于这个类,但在运行时与类/实例无关。
* **用途**:工具函数。
```python
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
# 实例方法
def format(self):
return f"{self.year}-{self.month}-{self.day}"
# 类方法:作为构造函数的一种替代(工厂模式)
@classmethod
def from_string(cls, date_str):
# date_str 格式 "2023-10-01"
year, month, day = map(int, date_str.split('-'))
return cls(year, month, day) # 返回一个新的实例
# 静态方法:不需要访问类或实例的数据
@staticmethod
def is_valid(date_str):
return '-' in date_str
# 使用
d1 = Date(2023, 10, 1)
d2 = Date.from_string("2023-12-25") # 调用类方法
print(d2.format())
print(Date.is_valid("2023-10-1")) # True
```
---
## 2. 封装与访问控制
### 2.1 私有属性与名称改写
Python 没有像 Java 那样严格的 `private` 关键字。它通过**命名约定**来实现封装。
* `public``self.name`,公有,随处可访问。
* `protected``self._age`(单下划线),**约定**视为内部使用,但解释器不强制限制。
* `private``self.__money`(双下划线),解释器会进行**名称改写 (Name Mangling)**,防止子类意外覆盖或外部直接访问。
```python
class Account:
def __init__(self, balance):
self.__balance = balance # 私有属性
def get_balance(self):
return self.__balance
acc = Account(100)
# print(acc.__balance) # AttributeError
print(acc.get_balance()) # 100
# 强行访问(不推荐,除非调试)
print(acc._Account__balance) # 100 (Python 将其改名为 _ClassName__variable)
```
### 2.2 使用 `@property` 装饰器
`@property` 是 Pythonic 的封装方式。它允许你像访问属性一样调用方法,实现对属性的**获取**、**设置**和**删除**的控制。
```python
class Person:
def __init__(self, name):
self._name = name
# Getter
@property
def name(self):
return self._name
# Setter
@name.setter
def name(self, value):
if not isinstance(value, str):
raise ValueError("Name must be a string")
self._name = value
# Deleter
@name.deleter
def name(self):
print("Deleting name...")
del self._name
p = Person("Alice")
print(p.name) # 自动调用 getter
p.name = "Bob" # 自动调用 setter
# p.name = 123 # 抛出 ValueError
```
---
## 3. 魔术方法 (Magic Methods)
魔术方法Dunder Methods双下划线方法允许你的对象模拟内置类型的行为如算术运算、长度获取、索引访问等
### 3.1 字符串表示:`__str__` vs `__repr__`
* `__str__`:面向用户,打印时 (`print()`) 调用,力求可读性。
* `__repr__`:面向开发者,调试时 (`repl` 环境) 调用,力求准确性(最好能用来重建对象)。
```python
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector(x={self.x}, y={self.y})"
v = Vector(1, 2)
print(v) # 调用 __str__
print([v]) # 列表内的元素会调用 __repr__
```
### 3.2 运算符重载
让你的对象支持 `+`, `-`, `*` 等操作。
```python
# 接上面的 Vector 类
def __add__(self, other):
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # 自动调用 v1.__add__(v2)
print(v3) # Vector(4, 6)
```
### 3.3 其他常用魔术方法
* `__len__(self)`: `len(obj)` 时调用。
* `__getitem__(self, key)`: `obj[key]` 时调用,实现索引或切片访问。
* `__call__(self)`: 让对象像函数一样被调用 `obj()`
* `__enter__` / `__exit__`: 实现上下文管理器(`with` 语句)。
## 总结
Python 的 OOP 既简洁又强大。
1. **区分清楚**类属性与实例属性,防止数据污染。
2. **善用** `@property` 和魔法方法,写出 Pythonic 的代码。
3. **理解**鸭子类型,不要过分纠结于类型检查,而要关注行为(接口)。
4. **掌握** `super()`,为编写可维护的继承结构打好基础。

View File

@@ -0,0 +1,194 @@
---
title: 开发日记/GitFlow学习
published: 2026-01-10T00:21:42
description: ''
image: ''
draft: false
lang: ''
category: '开发日记'
---
# GitFlow 学习:一套分支协作“规矩”的来龙去脉
这两天为了把团队协作流程梳理清楚,我系统看了一遍 GitFlow。它不是某个命令也不是 Git 的内置功能,而是一套“怎么分支、怎么合并、怎么发版、怎么修线上”的协作约定。
它的优点是清晰、可控、可复用;缺点是流程偏重、分支偏多。适不适合,取决于团队规模、发版节奏和项目类型。
## GitFlow 解决的核心问题
在多人协作里,常见的冲突不是代码冲突,而是“节奏冲突”:
- 你在开发新功能,另一个人要紧急修线上 bug
- 产品要在本周发一个稳定版本,但下周的大需求已经在开发中
- 版本需要打 Tag、回滚、追溯某次发布包含哪些改动
GitFlow 做的事情就是把这些“节奏”通过分支模型固定下来,让每个人都知道:
- 哪个分支代表线上稳定版本
- 哪个分支代表日常集成
- 新功能、发版、紧急修复分别从哪里来、往哪里去
## 分支模型:两条主干 + 三类临时分支
### 主分支
- `main`(或 `master`):线上稳定分支。每一次正式发布通常都来自这里,并通过 Tag 标记版本。
- `develop`:日常集成分支。新功能开发完成后会合回 `develop`,用于持续集成与联调。
### 临时分支
- `feature/*`:功能分支,从 `develop` 拉出,完成后合回 `develop`
- `release/*`:发布分支,从 `develop` 拉出,用于发版前的收尾(修 bug、改版本号、补文档等最终合到 `main` 并回灌 `develop`
- `hotfix/*`:紧急修复分支,从 `main` 拉出,修复线上问题,最终合到 `main` 并回灌 `develop`
## 标准流程:从开发到发布,再到紧急修复
### 1) 开发新功能feature 分支
目标:不污染 `develop`,开发完成后再合并。
```bash
git switch develop
git pull
git switch -c feature/user-profile
```
功能完成后发起 PR 合并到 `develop`。合并完成后可以删除 feature 分支:
```bash
git switch develop
git pull
git branch -d feature/user-profile
```
### 2) 准备发版release 分支
目标:冻结需求,专注发版质量;同时不阻塞 `develop` 的后续开发。
```bash
git switch develop
git pull
git switch -c release/1.3.0
```
`release/1.3.0` 上通常会做:
- 修复发版相关 bug
- 调整版本号
- 补发版说明changelog
确认发布后:
1. 合并到 `main`
2.`main` 上打 tag
3. 把 release 的改动回灌到 `develop`
```bash
git switch main
git pull
git merge --no-ff release/1.3.0
git tag -a v1.3.0 -m "release v1.3.0"
git push --follow-tags
git switch develop
git pull
git merge --no-ff release/1.3.0
git push
```
发布完成后删除发布分支:
```bash
git branch -d release/1.3.0
```
### 3) 线上紧急修复hotfix 分支
目标:以最短路径修复线上,不等待 `develop` 的合并节奏。
```bash
git switch main
git pull
git switch -c hotfix/1.3.1
```
修复完成后:
1. 合并回 `main` 并打 tag
2. 回灌 `develop`(否则下次发布可能把 bug 又带回来)
```bash
git switch main
git pull
git merge --no-ff hotfix/1.3.1
git tag -a v1.3.1 -m "hotfix v1.3.1"
git push --follow-tags
git switch develop
git pull
git merge --no-ff hotfix/1.3.1
git push
```
## 团队落地建议:不然 GitFlow 会变成“形式主义”
### 分支命名约定
- `feature/<ticket>-<desc>`
- `release/<version>`
- `hotfix/<version>`
明确 ticketJira/禅道/飞书任务)能减少“这分支是干嘛的”的沟通成本。
### 合并策略建议
- 日常 feature 合并建议走 PR + review
- 合并时倾向使用 `--no-ff` 保留分支合并节点,方便追溯一个 feature 的整体生命周期
- 需要线性历史rebase也可以但要团队一致且注意不要 rebase 已推送且多人协作的分支
### Tag 与版本号
- 发布分支/热修分支最终都应该落到 `main`,并在 `main` 打 tag
- tag 建议使用 `vX.Y.Z`(语义化版本),长期会非常省事:定位问题、回滚、对比版本都更直观
## 这种流程适合谁?不适合谁?
### 适合
- 有明确“发版”概念的软件:需要版本号、发布窗口、变更可追溯
- 需要同时并行多个功能开发,还要保证某个版本稳定交付
- 线上问题需要快速热修,且修复必须被未来版本继承
### 不太适合
- 小团队/个人项目:分支成本大于收益
- 强 Trunk-Based主干开发文化的团队更倾向短分支 + 快速合并到主干 + 高频发布
- 非常高频发版比如一天多次发布GitFlow 的 release 分支会显得偏重
## 常见踩坑点
- `develop` 长期不稳定:如果大家把“坏掉也没关系”的心态带进 `develop`,那 release 就会变成灾难收尾现场
- release 分支拖太久发版窗口越长回灌冲突越大release 建议短周期
- hotfix 没回灌 develop短期看修好了长期会在下次发布被“复活”
- 过度流程化:所有改动都走 release/hotfix导致流程负担极高最后大家反而绕流程
## 和 GitHub Flow / Trunk-Based 的简单对比
- GitFlow强调“版本管理”和“并行节奏”清晰但偏重
- GitHub Flow通常只有 `main` + feature 分支,合并即部署,适合持续交付
- Trunk-Based极短生命周期分支甚至直接主干依赖 CI/CD 与 feature toggle效率高但要求工程化更强
## 小结
GitFlow 的本质不是“分支越多越专业”,而是把团队协作里最难的三个问题拆开解决:
- 日常集成(`develop`
- 稳定发布(`release/*` -> `main` + tag
- 线上热修(`hotfix/*` -> `main` + 回灌 `develop`
如果团队发版节奏明确、需要追溯和稳定性GitFlow 很好用如果项目更追求快速交付与高频发布GitHub Flow 或 Trunk-Based 可能更适合。
## 参考
- https://blog.csdn.net/sunyctf/article/details/130587970
#

View File

@@ -0,0 +1,239 @@
---
title: 开发日记/SpringCloud项目配合maven动态启用不同配置文件设计
published: 2026-01-11T12:53:19
description: ''
image: ''
draft: false
lang: ''
category: '开发日记'
---
## 背景
在多模块(父工程 + 多个子模块)的 SpringCloud / SpringBoot 项目里我们通常会有多套环境配置dev/test/prod比如
- 数据库、Redis、MQ 地址不同
- Nacos / Config Server 的命名空间、group 不同
- 日志级别、监控开关不同
问题在于:**子模块是可独立启动的**,但它们的配置又希望能跟随父模块选择的 Maven Profile 自动切换,而不是每次都手动改 `application.yml` 或 IDE 的 VM Options。
## 目标
- 用 Maven Profile 统一管理环境dev/test/prod**一处配置,多模块复用**
- 打包时根据 `-Pdev/-Pprod` 自动写入激活的 Spring Profile
- 子模块的 `application.yml` 不硬编码环境,而是“动态注入”
- 本地运行、CI 打包、Docker 部署都能保持同一套切换逻辑
## 思路总览(核心点)
1. **父模块parent/pom.xml用 `<profiles>` 维护环境变量**,比如 `profile.active=dev``config.server.url=...`
2. **子模块开启资源过滤resource filtering**,让 `application.yml` 支持占位符替换
3. `application.yml` 用占位符写 `spring.profiles.active`,让它随 Maven Profile 注入(例如 `@profile.active@`
4. 运行 / 打包时只需要切换 Maven Profile`mvn -Pdev package``mvn -Pprod package`
下面重点讲两件事:**父模块 profiles 怎么写**,以及 **子模块 application.yml 怎么实现动态配置**
## 父模块Maven Profiles 统一管理环境
在父模块 `pom.xml` 中定义环境 Profiledev/test/prod每个 profile 只负责两类事情:
- 提供“环境变量”properties
- 参与资源过滤(让子模块能读到这些变量)
示例(父模块 `pom.xml`,只保留关键段落):
```xml
<project>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<properties>
<maven.resources.filtered>true</maven.resources.filtered>
<profile.active>dev</profile.active>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<profile.active>dev</profile.active>
<config.server.url>http://127.0.0.1:8888</config.server.url>
</properties>
</profile>
<profile>
<id>test</id>
<properties>
<profile.active>test</profile.active>
<config.server.url>http://test-config:8888</config.server.url>
</properties>
</profile>
<profile>
<id>prod</id>
<properties>
<profile.active>prod</profile.active>
<config.server.url>http://prod-config:8888</config.server.url>
</properties>
</profile>
</profiles>
</project>
```
### 为什么把 Profile 写在父模块
- 多模块下每个子模块都依赖 parent**配置天然继承**
- 切换环境只需控制父模块 profileCI/CD 更好统一
- 对团队协作友好:大家不需要各自维护一份“本地 dev 配置”
### 使用方式(命令)
```bash
# dev 打包
mvn -Pdev clean package
# prod 打包
mvn -Pprod clean package
```
在 IDE例如 IntelliJ IDEA也可以在 Maven 面板里勾选对应 profile然后运行子模块的启动类即可前提是子模块启用了资源过滤后面会讲
## 子模块application.yml 如何实现动态配置
核心就是一句话:**让 `application.yml` 里的值由 Maven 过滤替换**。
### 1子模块开启资源过滤resource filtering
在子模块(或统一放在父模块的 `<build><pluginManagement>` 里让子模块继承)配置 resources 过滤。
以子模块 `pom.xml` 为例(关键段落):
```xml
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
```
这样 Maven 在 `process-resources` 阶段会对 `src/main/resources` 下的文件做占位符替换。
### 2在 application.yml 里引用父模块 Profile 变量
在子模块 `src/main/resources/application.yml` 里写:
```yaml
spring:
profiles:
active: "@profile.active@"
app:
config-server-url: "@config.server.url@"
```
这里用的是 Maven 的 `@...@` 占位符格式(它比 `${...}` 在 YAML 中更不容易和 Spring 本身的占位符混淆)。
当你执行:
```bash
mvn -Pprod clean package
```
打包后的 `target/classes/application.yml` 会变成:
```yaml
spring:
profiles:
active: "prod"
app:
config-server-url: "http://prod-config:8888"
```
### 3配合多套配置文件application-{profile}.yml
建议把“环境差异”尽量放到 `application-dev.yml` / `application-prod.yml` 中,让 `application.yml` 只负责“选择环境”与公共配置。
结构示例:
```text
src/main/resources/
application.yml
application-dev.yml
application-test.yml
application-prod.yml
```
`application.yml`(只写公共 + 激活环境):
```yaml
spring:
profiles:
active: "@profile.active@"
server:
port: 8080
```
`application-dev.yml`(写 dev 差异):
```yaml
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/app_dev
```
`application-prod.yml`(写 prod 差异):
```yaml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/app_prod
```
这样一来:**父模块 Maven Profile 决定 `spring.profiles.active`**SpringBoot 再根据 active profile 自动加载对应的 `application-{profile}.yml`
## 常见坑与建议
### 1本地直接运行为什么不生效
`@profile.active@` 只会在 **Maven 资源处理** 时被替换。
如果你直接用 IDE “运行启动类”,但没有触发 Maven 的 `process-resources`,就会出现占位符未替换的情况。
推荐做法:
- 在 IDE 使用 Maven Profile并确保运行前执行了 `process-resources`(常见方式是先 `mvn -Pdev -DskipTests package` 一次)
- 或者在 Run Configuration 里临时加 `-Dspring.profiles.active=dev`(但这会绕过“父模块统一管理”,不建议作为团队默认方案)
### 2过滤导致 application.yml 被破坏
如果你在 `application.yml` 里本身也使用 `${...}`(比如 Spring 的占位符Maven 过滤可能会误处理。
建议:
-`@...@` 作为 Maven 注入占位符
- Spring 自己的占位符继续用 `${...}`,避免混用
### 3CI/CD 建议
CI 里只需要:
```bash
mvn -Pprod clean package -DskipTests
```
产物里 profile 已经写死为 prod运行时不需要额外设置。
如果你希望“同一包多环境运行”,那就不要在构建期写死 `spring.profiles.active`,改为运行期用环境变量/启动参数控制(这是另一条路线,和本文目标不同)。
## 小结
- 父模块 `<profiles>` 用来统一维护环境变量(重点:`profile.active`
- 子模块开启资源过滤,让 `application.yml` 能读取父模块的变量
- `application.yml``spring.profiles.active: "@profile.active@"` 达到“动态切换配置”的效果

View File

@@ -0,0 +1,350 @@
---
title: 开发日记/python包管理工具uv使用
published: 2026-01-18T19:23:07
description: "uv 是一个极速的 Python 包管理和项目管理工具,由 Rust 编写。本文介绍了 uv 的安装、基础用法以及如何使用它来替代 pip、poetry 和 pyenv。"
image: ""
draft: false
lang: ""
category: "开发日记"
tags:
- "python"
- "包管理"
- "uv"
- "rust"
---
# uv 使用指南
在 Python 的开发生态中,包管理和环境管理一直是一个让人头疼的话题。从 `pip``pipenv`,再到 `poetry``pdm`,工具层出不穷。而最近,由 AstralRuff 的开发者)推出的 **uv** 横空出世,凭借其**极快的速度**和**全能的特性**,迅速成为了 Python 开发者的新宠。
本文将带你快速上手 uv体验这个"终结者"级别的工具。
## 什么是 uv
`uv` 是一个用 Rust 编写的 Python 包安装器和解析器。它的设计初衷是替代 `pip``pip-tools``virtualenv`,但随着版本的迭代,它现在已经具备了替代 `poetry`(项目管理)、`pyenv`Python 版本管理)和 `pipx`(工具管理)的能力。
**核心特点:**
- **极速**:比 pip 快 10-100 倍。
- **全能**:一个工具搞定 Python 安装、虚拟环境、依赖管理、工具运行。
- **兼容**:兼容 `pyproject.toml` 标准。
## 1. 安装 uv
uv 提供了多种安装方式,推荐使用官方的独立安装脚本,这样升级和管理更方便。
### macOS / Linux
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```
### Windows (PowerShell)
```powershell
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
### 使用 pip 安装
如果你只是想尝鲜,也可以通过 pip 安装:
```bash
pip install uv
```
安装完成后,可以通过以下命令验证:
```bash
uv --version
```
## 2. 项目管理 (Modern Workflow)
这是 uv 目前最推荐的使用方式,类似于 `poetry``npm` 的体验。
### 初始化项目
```bash
# 创建一个新项目
uv init my-project
cd my-project
```
![](https://blog.meowrain.cn/api/i/2026/01/18/vvzidl-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/vwl6mu-1.webp)
这将创建一个 `pyproject.toml` 文件和一个 `.python-version` 文件。
### 添加依赖
不再需要手动激活虚拟环境,`uv add` 会自动处理虚拟环境的创建和依赖的安装。
```bash
# 添加依赖
uv add requests
# 添加开发依赖 (例如 pytest)
uv add --dev pytest
```
![](https://blog.meowrain.cn/api/i/2026/01/18/vwrk27-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/vx4n39-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/vxz3kj-1.webp)
### 配置国内镜像源
#### 配置项目镜像
可以参考这个 `https://uv.oaix.tech/blog/2025/06/17/quickly-set-uv-package-index-is-china-mirror/` 来配置国内镜像源。
![](https://blog.meowrain.cn/api/i/2026/01/18/vygekp-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/vyls9m-1.webp)
` pyproject.toml` 里面添加下面的内容
```toml
[[tool.uv.index]]
name = "tencent"
url = "https://mirrors.cloud.tencent.com/pypi/simple/" # 腾讯云镜像源
```
![](https://blog.meowrain.cn/api/i/2026/01/18/vz77xi-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w7ixxh-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w7uppx-1.webp)
可以看到里面已经用上镜像了
#### 配置全局镜像源
参考这个 `https://www.cnblogs.com/ljbguanli/p/19357762`
全局配置后,所有项目默认使用指定镜像,无需重复设置。
步骤 1找到配置文件路径
- Linux/macOS~/.config/uv/config.toml
- Windows%USERPROFILE%\.config\uv\config.toml如 C:\Users\你的用户名\.config\uv\config.toml
步骤 2创建/编辑配置文件
```
# Linux/macOS用 vim 打开(若文件不存在会自动创建)
vim ~/.config/uv/config.toml
# Windows用记事本打开
Write-Host $env:USERPROFILE
notepad %USERPROFILE%\.config\uv\config.toml
```
```toml
# 阿里云镜像(推荐,稳定性好)
[registries.pypi]
index = "https://mirrors.aliyun.com/pypi/simple/"
```
![](https://blog.meowrain.cn/api/i/2026/01/18/w2n47v-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w3acq4-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w3ngws-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w3qyvu-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w47xz8-1.webp)
> 不过网上推荐的都是直接设置环境变量 `UV_DEFAULT_INDEX`
For Linux Users:
```bash
# 推荐使用清华源
echo 'export UV_DEFAULT_INDEX="https://pypi.tuna.tsinghua.edu.cn/simple"'>> ~/.bashrc
# 或者用阿里源
# echo 'export UV_DEFAULT_INDEX="https://mirrors.aliyun.com/pypi/simple/"' >> ~/.bashrc
# 让配置立即生效
source ~/.bashrc
```
For Windows Users:
这个只在当前会话生效,关闭会话后就会失效。
```powershell
$env:UV_DEFAULT_INDEX = "https://pypi.tuna.tsinghua.edu.cn/simple"
```
想永久生效就
![](https://blog.meowrain.cn/api/i/2026/01/18/w63ik2-1.webp)
环境变量不知道去哪儿找可以直接 windows 搜索 `环境变量` 就可以找到。
![](https://blog.meowrain.cn/api/i/2026/01/18/w6bb65-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w91iwr-1.webp)
### 如果是已经有的项目,如何用 uv 同步包呢?
如果是已经有的项目,你可以使用 `uv sync` 命令来同步项目的依赖。
```bash
uv sync
```
这将根据 `pyproject.toml` 中的配置,安装所有必要的依赖。
![](https://blog.meowrain.cn/api/i/2026/01/18/w06627-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/w0aosh-1.webp)
### 运行代码
使用 `uv run` 可以在项目的虚拟环境中执行命令,无需显式激活环境。
```bash
# 运行脚本
uv run main.py
# 运行工具
uv run pytest
```
![](https://blog.meowrain.cn/api/i/2026/01/18/wbir6y-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/18/wbml7j-1.webp)
### 同步环境
如果你拉取了别人的代码,或者手动修改了 `pyproject.toml`,可以使用 `sync` 命令同步环境:
```bash
uv sync
```
## 3. Python 版本管理
uv 内置了 Python 版本管理功能,这意味着你不再需要安装 `pyenv``conda` 来管理不同的 Python 版本。
```bash
# 列出可用的 Python 版本
uv python list
# 安装特定版本的 Python
uv python install 3.12
# 为当前项目指定 Python 版本
uv python pin 3.11
```
当你运行 `uv run``uv sync`uv 会自动下载并使用项目指定的 Python 版本。
这个也可以用镜像不然走github国内很慢
很简单
![](https://blog.meowrain.cn/api/i/2026/01/18/x4rjp3-1.webp)
```
UV_PYTHON_INSTALL_MIRROR
https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone/
```
linux的话
```bash
export UV_PYTHON_INSTALL_MIRROR="https://mirror.nju.edu.cn/github-release/astral-sh/python-build-standalone/"
```
![](https://blog.meowrain.cn/api/i/2026/01/18/x627ey-1.webp)
## 4. 脚本支持 (Script Support)
uv 对单文件脚本的支持非常出色。你可以在脚本顶部声明依赖uv 会自动下载并运行,且不会污染全局环境。
创建一个 `example.py`
```python
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
from rich.pretty import pprint
resp = requests.get("https://peps.python.org/api/peps.json")
data = resp.json()
pprint([(k, v["title"]) for k, v in data.items()][:10])
```
直接运行:
```bash
uv run example.py
```
uv 会自动解析头部元数据,创建一个临时环境并安装依赖,然后执行脚本。
## 5. 工具管理 (Tool Management)
类似于 `pipx`uv 可以安装和运行全局的 Python 命令行工具。
```bash
# 临时运行一个工具 (例如 ruff)
uvx ruff check .
# 或者
uv tool run ruff check .
# 安装一个工具到全局
uv tool install black
```
## 6. 兼容 pip 的接口 (Legacy Interface)
如果你不想改变现有的工作流,只想利用 uv 的速度,可以使用它的 pip 兼容接口。
```bash
# 创建虚拟环境
uv venv
# 创建指定版本的虚拟环境
uv venv --python 3.12
# 激活环境 (Windows)
.venv\Scripts\activate
# 激活环境 (macOS/Linux)
source .venv/bin/activate
# 安装依赖 (替代 pip install)
uv pip install requests
# 从 requirements.txt 安装
uv pip install -r requirements.txt
# 生成锁定文件 (替代 pip-compile)
uv pip compile pyproject.toml -o requirements.txt
```
## 总结
uv 正在以惊人的速度重塑 Python 的开发体验。它不仅解决了"慢"的问题,更重要的是它试图统一碎片化的 Python 工具链。
**迁移建议:**
- **新项目**:直接使用 `uv init``uv add` 的工作流。
- **老项目**:可以先用 `uv pip` 替代 `pip` 加速安装,时机成熟后迁移到 `pyproject.toml` 管理。
- **脚本**:强烈推荐使用 `uv run` 运行带依赖声明的单文件脚本。

View File

@@ -0,0 +1,23 @@
---
title: scoop配置国内源
published: 2026-01-19T20:49:49
description: 'scoop配置国内源'
image: ''
category: 开发日记
draft: false
lang: ''
---
# 更换scoop主仓库
```
# 南京大学
scoop config SCOOP_REPO "https://mirrors.nju.edu.cn/git/scoop-installer/Scoop.git"
# 添加南京大学extras仓库
scoop bucket add extras https://mirrors.nju.edu.cn/git/scoop-extras.git
```

View File

@@ -0,0 +1,230 @@
---
title: 开发日记/springboot项目logback配置
published: 2026-01-18T19:24:15
description: ''
image: 'https://blog.meowrain.cn/api/i/2026/01/18/vuaftq-1.webp'
draft: false
lang: ''
category: '开发日记'
tags:
- 'springboot'
- 'logback'
---
# 文件内容 可通用
可以直接复制到项目的 `src/main/resources/logback-spring.xml` 文件中。
![](https://blog.meowrain.cn/api/i/2026/01/18/vuaftq-1.webp)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!--debug="false" 表示关闭 Logback 框架自身的内部状态信息打印。-->
<configuration scan="true" scanPeriod="10 seconds" debug="false">
<!-- 引入 spring boot 默认日志颜色和基础配置-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 定义变量 APP_NAME从 Spring 环境变量中获取 spring.application.name 的值-->
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<!-- 定义时间格式yyyy-MM 表示年-yyyy-MM-dd 表示年---->
<timestamp key="time-month" datePattern="yyyy-MM"/>
<timestamp key="time-month-day" datePattern="yyyy-MM-dd"/>
<!-- 定义变量 LOG_FILE_PATH默认值为 ./logs/${APP_NAME},可以通过环境变量 LOG_PATH 覆盖 日志存储路径-->
<property name="LOG_FILE_PATH" value="${LOG_PATH:-./logs/${APP_NAME}}"/>
<!-- 定义日志格式
格式说明:
%d{yyyy-MM-dd HH:mm:ss.SSS}:日志记录时间,格式为年--日 时:分:秒.毫秒
[%thread]:日志记录线程名称
%-5level日志级别左对齐占用 5 个字符宽度
%logger{50}:日志记录器名称,最多显示 50 个字符
-[%X{traceId:-}]:从 MDC 中获取 traceId 变量值,如果不存在则显示为空
%msg%n日志消息内容换行符
-->
<property name="FILE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -[%X{traceId:-}] %msg%n"/>
<!-- appender 控制台输出-->
<!--
<appender>: 这是 Logback 配置的根标签之一,用于定义一个日志输出目的地。
name="CONSOLE": 为这个 Appender 赋予一个唯一的名称,方便在 <root> 或 <logger> 标签中引用它。
class="ch.qos.logback.core.ConsoleAppender": 指定使用的实现类。
ConsoleAppender 是 Logback 库中专门用于将日志事件写入 System.out (标准输出) 或 System.err (标准错误) 的类。
这是我们在本地开发和测试时最常用的 Appender。
CONSOLE_LOG_PATTERN 是上面include的默认日志格式这里直接引用即可
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 全量info日志-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>31</maxHistory>
<totalSizeCap>100GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<!-- 只记录 INFO 级别以及以上的日志的日志 -->
<level>INFO</level>
</filter>
</appender>
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_FILE_PATH}/${time-month}/${time-month-day}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>31</maxHistory>
<totalSizeCap>100GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${FILE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 只记录 ERROR 级别以及以上的日志的日志 -->
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步写入日志-->
<appender name="ASYNC_INFO"
class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="INFO_FILE"/>
</appender>
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="ERROR_FILE"/>
</appender>
<!-- =========== 环境配置 打印到控制台 ===========-->
<springProfile name="dev">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_ERROR"/>
<appender-ref ref="ASYNC_INFO"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_ERROR"/>
<appender-ref ref="ASYNC_INFO"/>
</root>
</springProfile>
</configuration>
```
## 配置详解
### 1. 根节点配置 (`<configuration>`)
```xml
<configuration scan="true" scanPeriod="10 seconds" debug="false">
```
* **scan="true"**: 配置文件如果发生改变,将会被重新加载。
* **scanPeriod="10 seconds"**: 监测配置文件是否有修改的时间间隔,默认单位是毫秒。
* **debug="false"**: 关闭 Logback 框架自身的内部状态信息打印,设置为 `true` 时可以在控制台看到 Logback 的加载过程,有助于排查 Logback 配置错误。
### 2. 基础引用与变量定义
#### 引入 Spring Boot 默认配置
```xml
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
```
这行代码引入了 Spring Boot 预定义的日志配置,包含了控制台输出的彩色日志格式 `CONSOLE_LOG_PATTERN` 等常用变量。
#### 获取 Spring 配置属性
```xml
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
```
* `<springProperty>`: 允许从 Spring 的 `Environment` 中读取属性并暴露给 Logback。
* 这里读取了 `spring.application.name`(应用名称)赋值给变量 `APP_NAME`,用于后续生成日志文件路径。
#### 定义时间戳变量
```xml
<timestamp key="time-month" datePattern="yyyy-MM"/>
<timestamp key="time-month-day" datePattern="yyyy-MM-dd"/>
```
定义了两个时间戳变量,用于构建按月或按天归档的目录结构。
#### 定义日志路径
```xml
<property name="LOG_FILE_PATH" value="${LOG_PATH:-./logs/${APP_NAME}}"/>
```
* `${LOG_PATH:-./logs/${APP_NAME}}`: 这是一个默认值语法。如果环境变量 `LOG_PATH` 存在,则使用它;否则使用 `./logs/${APP_NAME}`
### 3. 日志格式 (`FILE_LOG_PATTERN`)
```xml
<property name="FILE_LOG_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} -[%X{traceId:-}] %msg%n"/>
```
* `%d`: 日期时间。
* `[%thread]`: 线程名。
* `%-5level`: 日志级别左对齐5字符宽
* `%logger{50}`: 类名最大长度50超长会智能缩写
* `[%X{traceId:-}]`: 这是一个 MDC (Mapped Diagnostic Context) 变量。用于分布式链路追踪,如果 MDC 中有 `traceId` 则显示,否则显示 `-`
* `%msg`: 日志具体内容。
* `%n`: 换行。
### 4. 输出源 (Appenders)
#### 控制台输出 (`CONSOLE`)
使用 `ConsoleAppender` 将日志输出到标准输出,并直接复用了 Spring Boot 默认的 `CONSOLE_LOG_PATTERN`
#### 滚动文件输出 (`INFO_FILE` / `ERROR_FILE`)
使用 `RollingFileAppender` 实现日志文件的滚动策略。
* **滚动策略 (`SizeAndTimeBasedRollingPolicy`)**:
* **按时间滚动**: 每天生成一个新的日志文件 (`%d{yyyy-MM-dd}`).
* **按大小滚动**: 如果单天日志超过 `100MB` (`%i`),会切分出新文件。
* **历史保留**: `<maxHistory>31</maxHistory>` 保留最近 31 天的日志。
* **总大小限制**: `<totalSizeCap>100GB</totalSizeCap>` 限制所有日志文件总大小不超过 100GB。
* **过滤器 (Filter)**:
* `INFO_FILE` 使用 `ThresholdFilter`: 记录 `INFO` 及以上级别INFO, WARN, ERROR
* `ERROR_FILE` 使用 `LevelFilter`: **只**记录 `ERROR` 级别。
* `onMatch=ACCEPT`: 匹配 ERROR 则记录。
* `onMismatch=DENY`: 不匹配则丢弃。
### 5. 异步处理 (`AsyncAppender`)
```xml
<appender name="ASYNC_INFO" class="ch.qos.logback.classic.AsyncAppender">
<discardingThreshold>0</discardingThreshold>
<queueSize>512</queueSize>
<appender-ref ref="INFO_FILE"/>
</appender>
```
* **作用**: 将日志写入操作放入独立线程执行,避免高并发下 IO 操作阻塞业务线程,提高应用性能。
* **queueSize**: 异步队列深度,默认为 256这里调整为 512。
* **discardingThreshold**: 默认为队列剩余 20% 容量时丢弃 TRACE/DEBUG/INFO 日志。设置为 `0` 表示**不丢弃任何日志**,即使队列满了也阻塞等待,保证日志不丢失。
### 6. 环境隔离 (`<springProfile>`)
```xml
<springProfile name="dev"> ... </springProfile>
<springProfile name="prod"> ... </springProfile>
```
Logback 支持 Spring 的 Profile 功能。
*`spring.profiles.active=dev` 时,激活 dev 块内的配置。
*`spring.profiles.active=prod` 时,激活 prod 块内的配置。
当前配置中,`dev``prod` 都输出了 `CONSOLE``ASYNC_ERROR``ASYNC_INFO`,在实际生产环境中,通常会移除 `CONSOLE` Appender 以减少不必要的控制台输出性能损耗。

View File

@@ -0,0 +1,79 @@
---
title: 开发日记/记一次mysql数据库恢复引发的慢查询问题
published: 2026-01-10T00:11:06
description: ''
image: ''
draft: false
lang: ''
category: '开发日记'
---
# 记一次 MySQL 数据库恢复引发的慢查询问题
## 案发经过
今天在公司遇到个诡异的问题。另一位实习生同事对测试环境的 MySQL 数据库进行了恢复操作。原本以为只是常规操作,结果恢复完成后,昨天还能在 2 秒内跑完的查询,今天跑了 10 分钟都没出结果。
此时测试服务器的情况非常糟糕CPU 占用率直接飙升到 100%,内存占用也居高不下。
## 排查过程
### 1. 初步检查
找技术经理用 Root 账号登录数据库查看状态,发现那几个查询语句一直在执行中,且耗时都已经超过了 10 分钟。
尝试 Kill 掉这些查询进程后,服务器的 CPU 和内存瞬间恢复正常。但只要业务端再次触发那个 SQL 查询,服务器立马又“瘫痪”了。
### 2. 尝试重启
起初怀疑是 SQL 导致了死锁,或者有什么隐藏进程阻塞了数据库。于是简单粗暴地重启了几次数据库服务,但问题依旧,没有任何改善。
### 3. 分析执行计划
既然重启无效,只能深入分析了。
查看慢查询日志,确认就是那几个业务查询在拖后腿。
接着查看这些 SQL 的执行计划(`EXPLAIN`),惊讶地发现:**很多查询竟然都不走索引了!** 全表扫描导致了查询时间无限拉长。
这就很离谱了。这些 SQL 在昨天(恢复数据前)还能正常运行,且正式环境也是完全相同的 SQL 和索引结构,一直运行良好。这说明问题不在 SQL 写法或索引缺失上。
## 解决
技术经理建议尝试优化 SQL 或添加索引,但我认为既然正式环境没问题,且昨天也没问题,说明结构本身是合理的。
结合“刚做过数据库恢复”这个操作,我推测可能是**数据库的统计信息Statistics在恢复过程中丢失或失效了**,导致 MySQL 优化器误判,选择了全表扫描而不是走索引。
于是,我尝试对相关表执行了分析命令:
```sql
ANALYZE TABLE ;
```
**结果立竿见影!**
执行完分析后,再次查看执行计划,查询终于重新走了索引。业务查询速度也恢复到了秒级,服务器负载瞬间降了下来。
## 💡 深度解析:关于 ANALYZE TABLE
### 1. 它到底干了什么?
`ANALYZE TABLE` 的主要作用是**重新统计索引的基数Cardinality**。
MySQL 的优化器Optimizer在决定是否使用索引时依据的是成本模型Cost Model。而成本计算的核心输入就是**索引区分度**。
* MySQL 会随机采样数据页InnoDB 默认采样 8 个页,可配置)。
* 估算每个索引中有多少个不同的值(基数)。
* 如果采样结果显示某个索引的基数太小(重复值太多),优化器就会认为“反正大部分数据都要读,不如直接全表扫描快”,从而放弃索引。
在数据恢复或大量导入后,数据分布发生了剧烈变化,但统计信息可能还是旧的(或者为空),导致优化器拿着错误的情报做出了错误的决策。
### 2. 各个 MySQL 版本的表现
* **MySQL 5.5 及以前**:统计信息是**非持久化**的。重启数据库后统计信息会丢失MySQL 会在第一次访问表时重新计算。这通常不会导致一直不走索引,但可能会导致数据库刚启动时产生瞬间的性能抖动。
* **MySQL 5.6 及以后(主流)**:引入了**持久化统计信息Persistent Optimizer Statistics**,默认开启(`innodb_stats_persistent=ON`)。统计信息存储在磁盘的 `mysql.innodb_table_stats` 表中。
* **优点**:重启后统计信息不丢失,执行计划稳定。
* **缺点**:这也意味着,如果你通过非标准方式(如直接拷贝文件、某些恢复工具)恢复数据,或者大量数据变动后自动更新机制(`innodb_stats_auto_recalc`)没有及时触发,统计信息就会长期处于“过时”状态,必须手动 `ANALYZE` 才能纠正。
### 3. 这种问题出现的概率大吗?
**非常大,尤其是逻辑备份恢复后。**
* **逻辑恢复(如 source .sql 文件)**:本质是执行成千上万条 `INSERT` 语句。虽然 InnoDB 默认在表数据变更超过 10% 时会自动重新计算统计信息,但在高负载恢复过程中,这个异步动作可能会滞后,或者因为锁竞争而失败。
* **物理恢复(如 XtraBackup**:通常会连同统计信息表文件一起恢复,出现概率相对较小,但也存在统计信息损坏的可能。
**最佳实践**:在生产环境中,每当进行**全量数据迁移、大批量数据导入/删除**操作后,建议把 `ANALYZE TABLE` 作为标准收尾动作执行一遍,以确保“情报”准确。
## 📝 总结
数据库恢复或大量数据导入导出后可能会导致索引统计信息不准确。MySQL 优化器依赖这些统计信息来决定执行计划。当统计信息偏差过大时,优化器可能会放弃索引而选择全表扫描。
下次遇到这种“昨天好好的,今天突然不走索引”的情况,且 SQL 没变动,不妨先 `ANALYZE TABLE` 一下,强制更新统计信息。
---

View File

@@ -0,0 +1,35 @@
---
title: 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题
published: 2026-01-11T12:27:39
description: ''
image: ''
draft: false
lang: ''
category: '开发日记'
---
# 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题
换了台电脑自己写的项目都跑不起来了。。。。网上说要拿netstat -ano | findstr 端口号 来查看占用进程,但是我查不到占用进程。。。
![](https://blog.meowrain.cn/api/i/2026/01/11/kb4rrh-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/11/kbeze4-1.webp)
# 解决办法
查了下windows是有预留端口的
```
netsh interface ipv4 show excludedportrange protocol=tcp
```
![](https://blog.meowrain.cn/api/i/2026/01/11/kc0rzr-1.webp)
奥原来是在预留端口范围里面。。。。
![](https://blog.meowrain.cn/api/i/2026/01/11/kck57k-1.webp)
只能把项目端口号改到预留端口范围之外了比如我这里改成8081就可以了

View File

@@ -0,0 +1,38 @@
---
title: 祝自己生日快乐
published: 2026-01-05T22:22:28
description: ''
image: 'https://blog.meowrain.cn/api/i/2026/01/05/10sz5vn-1.webp'
draft: false
lang: ''
---
# 生日蛋糕
![](https://blog.meowrain.cn/api/i/2026/01/05/10sz5vn-1.webp)
💕💕💕💕💕💕姐姐买的,很好吃💕💕💕
明天吃
![](https://blog.meowrain.cn/api/i/2026/01/05/10wbwh6-1.webp)
# 小物件
> 把去年看洛天依演唱会买的盒子拆了可爱的天依0-0
![](https://blog.meowrain.cn/api/i/2026/01/05/10wwmyz-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/05/10u4sop-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/05/10uitt5-1.webp)
![](https://blog.meowrain.cn/api/i/2026/01/05/10utg32-1.webp)
还有立牌
![](https://blog.meowrain.cn/api/i/2026/01/05/10v65kf-1.webp)
亚克力牌
![](https://blog.meowrain.cn/api/i/2026/01/05/10v8oew-1.webp)
很硬的卡纸
![](https://blog.meowrain.cn/api/i/2026/01/05/10vus92-1.webp)

View File

@@ -6,6 +6,8 @@ import "@fontsource/roboto/700.css";
import { profileConfig, siteConfig } from "@/config";
import ConfigCarrier from "@components/ConfigCarrier.astro";
import Sakura from "@components/widget/Sakura.astro";
import EmojiCursor from "@components/widget/EmojiCursor.astro";
import {
AUTO_MODE,
BANNER_HEIGHT,
@@ -91,6 +93,9 @@ const bannerOffset =
<!-- 手机端适配 -->
<head>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=M+PLUS+Rounded+1c:wght@400;500;700&display=swap" rel="stylesheet">
<title>{pageTitle}</title>
<meta charset="UTF-8" />
@@ -121,11 +126,11 @@ const bannerOffset =
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<script src="/js/umami-share.js" defer></script>
<script
<!-- <script src="/js/umami-share.js" defer></script> -->
<!-- <script
data-swup-ignore-script
is:inline
src="https://pic.acofork.com/random.js"></script>
src="https://pic.acofork.com/random.js"></script> -->
{
/* <!-- Content Security Policy -->
@@ -193,27 +198,25 @@ const bannerOffset =
`${offset}px`
);
// // Background image loading detection
// const bgUrl = getComputedStyle(document.documentElement).getPropertyValue('--bg-url').trim();
// const bgEnable = getComputedStyle(document.documentElement).getPropertyValue('--bg-enable').trim();
// Background image loading detection
const bgUrl = bgConfig.enable && bgConfig.src ? bgConfig.src : '';
// if (bgUrl && bgUrl !== 'none' && bgEnable === '1') {
// const img = new Image();
// const urlMatch = bgUrl.match(/url\(["']?([^"')]+)["']?\)/);
// if (urlMatch) {
// img.onload = function() {
// // 背景图片完全加载后,显示背景并启用卡片透明效果
// document.body.classList.add('bg-loaded');
// document.documentElement.style.setProperty('--card-bg', 'var(--card-bg-transparent)');
// document.documentElement.style.setProperty('--float-panel-bg', 'var(--float-panel-bg-transparent)');
// };
// img.onerror = function() {
// // Keep cards opaque if background image fails to load
// console.warn('Background image failed to load, keeping cards opaque');
// };
// img.src = urlMatch[1];
// }
// }
if (bgUrl) {
const img = new Image();
img.onload = function() {
// 背景图片完全加载后,显示背景并启用卡片透明效果
document.body.classList.add('bg-loaded');
document.getElementById('bg-box').classList.add('loaded');
document.documentElement.style.setProperty('--card-bg', 'var(--card-bg-transparent)');
document.documentElement.style.setProperty('--float-panel-bg', 'var(--float-panel-bg-transparent)');
document.documentElement.style.setProperty('--page-bg', 'transparent');
};
img.onerror = function() {
// Keep cards opaque if background image fails to load
console.warn('Background image failed to load, keeping cards opaque');
};
img.src = bgUrl;
}
</script>
<style
define:vars={{
@@ -257,18 +260,14 @@ const bannerOffset =
left: 0;
width: 100%;
height: 100%;
background-image: var(--bg-url);
background-position: var(--bg-position);
background-size: var(--bg-size);
background-repeat: var(--bg-repeat);
background-attachment: var(--bg-attachment);
opacity: 0;
opacity: var(--bg-opacity);
pointer-events: none;
z-index: -1;
transition: opacity 0.3s ease-in-out;
}
#bg-box.loaded {
opacity: calc(var(--bg-opacity) * var(--bg-enable));
}
</style>
@@ -281,22 +280,22 @@ const bannerOffset =
href={`${Astro.site}rss.xml`}
/>
<!-- Umami分析自建 -->
<script
<!-- <script
defer
src="https://umami.acofork.com/script.js"
data-website-id="5d710dbd-3a2e-43e3-a553-97b415090c63"
data-swup-ignore-script></script>
data-swup-ignore-script></script> -->
{
/* - Umami分析云-备用)
<script defer src="https://cloud.umami.is/script.js" data-website-id="5d710dbd-3a2e-43e3-a553-97b415090c63" data-swup-ignore-script></script> */
}
<!-- 超级吊的Umami eop -->
<script
<!-- <script
defer
src="https://eo-umami.acofork.com/script.js"
data-website-id="3f525363-5e81-4653-9491-1bd205c2a571"></script>
data-website-id="3f525363-5e81-4653-9491-1bd205c2a571"></script> -->
<!-- 百度统计 -->
<script data-swup-ignore-script>
<!-- <script data-swup-ignore-script>
var _hmt = _hmt || [];
(function () {
var hm = document.createElement("script");
@@ -304,10 +303,10 @@ const bannerOffset =
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
</script>
</script> -->
<!-- clarity 分析 -->
<script type="text/javascript" data-swup-ignore-script>
<!-- <script type="text/javascript" data-swup-ignore-script>
(function (c, l, a, r, i, t, y) {
c[a] =
c[a] ||
@@ -320,20 +319,20 @@ const bannerOffset =
y = l.getElementsByTagName(r)[0];
y.parentNode.insertBefore(t, y);
})(window, document, "clarity", "script", "t8f0gmcwtx");
</script>
</script> -->
<!-- gad -->
<script
<!-- <script
async
src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-1683686345039700"
crossorigin="anonymous"
data-swup-ignore-script></script>
<meta name="google-adsense-account" content="ca-pub-1683686345039700" />
<meta name="google-adsense-account" content="ca-pub-1683686345039700" /> -->
<!-- 谷歌分析 -->
<script
<!-- <script
async
src="https://www.googletagmanager.com/gtag/js?id=G-9Z4LT4H8KH"
data-swup-ignore-script></script>
data-swup-ignore-script></script> -->
<script is:inline>
window.dataLayer = window.dataLayer || [];
function gtag() {
@@ -345,11 +344,12 @@ const bannerOffset =
</script>
<!-- Cloudflare Web Analytics -->
<script
<!-- <script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "15fe148e91b34f10a15652e1a74ab26c"}'
data-swup-ignore-script></script><!-- End Cloudflare Web Analytics -->
data-swup-ignore-script></script> -->
<!-- End Cloudflare Web Analytics -->
</head>
<body
class="min-h-screen transition"
@@ -357,6 +357,8 @@ const bannerOffset =
data-overlayscrollbars-initialize
>
<div id="bg-box"></div>
<Sakura />
<EmojiCursor />
<ConfigCarrier />
<slot />
@@ -431,11 +433,11 @@ const bannerOffset =
}
// 页面加载完成后执行域名检测
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", checkDomain);
} else {
checkDomain();
}
// if (document.readyState === "loading") {
// document.addEventListener("DOMContentLoaded", checkDomain);
// } else {
// checkDomain();
// }
</script>
<Analytics />
</body>

View File

@@ -148,7 +148,7 @@ import { Icon } from "astro-icon/components";
// 从JSON文件加载图片数据
async function loadImagesData() {
try {
const response = await fetch('/api/i/images.json');
const response = await fetch('https://blog.meowrain.cn/api/i/images.json');
if (!response.ok) {
throw new Error('Failed to load images data');
}
@@ -307,6 +307,9 @@ import { Icon } from "astro-icon/components";
grid.classList.remove('opacity-0');
});
// 显示 sentinel
if (sentinel) sentinel.classList.remove('hidden');
// 设置 Intersection Observer
if (observer) observer.disconnect();
observer = new IntersectionObserver((entries) => {
@@ -595,8 +598,13 @@ import { Icon } from "astro-icon/components";
initFilterButtons();
}
// 初始化
initGallery();
// 初始化 - 等待 DOM 准备好
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initGallery);
} else {
// 延迟执行确保 Swup 容器已准备好
setTimeout(initGallery, 0);
}
// Resize handler
if (window.galleryResizeHandler) window.removeEventListener('resize', window.galleryResizeHandler);

View File

@@ -2,9 +2,24 @@
@layer components {
.card-base {
@apply rounded-[var(--radius-large)] overflow-hidden bg-[var(--card-bg)] transition;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
@apply rounded-[var(--radius-large)] overflow-hidden bg-[var(--card-bg)] transition duration-300;
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
animation: fade-in-up 0.6s ease-out backwards;
}
@keyframes fade-in-up {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card-base:hover {
@apply -translate-y-1 shadow-lg;
}
h1, h2, h3, h4, h5, h6, p, a, span, li, ul, ol, blockquote, code, pre, table, th, td, strong {
@apply transition;

View File

@@ -66,8 +66,8 @@ define({
--link-hover: oklch(0.95 0.025 var(--hue)) oklch(0.40 0.08 var(--hue))
--link-active: oklch(0.90 0.05 var(--hue)) oklch(0.35 0.07 var(--hue))
--float-panel-bg: rgba(255, 255, 255, 0.9) unquote("oklch(0.19 0.015 var(--hue) / 0.9)")
--float-panel-bg-transparent: rgba(255, 255, 255, 0.8) rgba(23, 23, 23, 0.8)
--float-panel-bg: rgba(255, 255, 255, 0.6) unquote("oklch(0.19 0.015 var(--hue) / 0.6)")
--float-panel-bg-transparent: rgba(255, 255, 255, 0.6) rgba(23, 23, 23, 0.6)
--float-panel-bg-opaque: rgba(255, 255, 255, 1) unquote("oklch(0.19 0.015 var(--hue))")
--scrollbar-bg-light: black(0.4)

View File

@@ -6,7 +6,7 @@ module.exports = {
theme: {
extend: {
fontFamily: {
sans: ["Roboto", "sans-serif", ...defaultTheme.fontFamily.sans],
sans: ["'M PLUS Rounded 1c'", "Roboto", "sans-serif", ...defaultTheme.fontFamily.sans],
},
},
},