Compare commits
19 Commits
f522bc1c5b
...
79f480f0ca
| Author | SHA1 | Date | |
|---|---|---|---|
| 79f480f0ca | |||
| a4e0fb6cd8 | |||
| 61a4f4b3c4 | |||
| e0614e1740 | |||
| 6af1909aab | |||
|
|
612a359c1d | ||
|
|
afe7569729 | ||
|
|
b890b18212 | ||
|
|
6e6001e3cd | ||
|
|
6fe5592c2c | ||
|
|
bbd608a6cf | ||
|
|
b6abc32784 | ||
|
|
9f4c61403e | ||
|
|
4d197699cb | ||
| b8905c488b | |||
| a1999b3cc1 | |||
| f3e3d8a738 | |||
| d561837a9e | |||
| 63fa5274ba |
987
.cursor/rules/pages-llms.mdc
Normal file
987
.cursor/rules/pages-llms.mdc
Normal 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
7
.gitignore
vendored
@@ -30,3 +30,10 @@ yarn.lock
|
||||
|
||||
# 忽略上游仓库
|
||||
ori
|
||||
|
||||
# Tencent Cloud EdgeOne
|
||||
.env
|
||||
.edgeone/*
|
||||
!.edgeone/project.json
|
||||
.tef_dist/*
|
||||
|
||||
24
.trae/documents/ACG Animation Enhancement Plan.md
Normal file
24
.trae/documents/ACG Animation Enhancement Plan.md
Normal 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.
|
||||
20
.trae/documents/ACG Style UI Makeover Plan.md
Normal file
20
.trae/documents/ACG Style UI Makeover Plan.md
Normal 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.
|
||||
@@ -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",
|
||||
|
||||
32
edge-functions/random-acg/index.js
Normal file
32
edge-functions/random-acg/index.js
Normal 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' }
|
||||
});
|
||||
}
|
||||
}
|
||||
4
node-functions/helloworld-cloud/index.js
Normal file
4
node-functions/helloworld-cloud/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export async function onRequest(context) {
|
||||
return new Response("Hello, world!")
|
||||
}
|
||||
|
||||
BIN
public/favicon/jingcha.png
Normal file
BIN
public/favicon/jingcha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
@@ -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">
|
||||
© <span id="copyright-year">2024 - {currentYear}</span>
|
||||
© <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> -->
|
||||
|
||||
@@ -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) {
|
||||
|
||||
34
src/components/widget/EmojiCursor.astro
Normal file
34
src/components/widget/EmojiCursor.astro
Normal 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>
|
||||
104
src/components/widget/Sakura.astro
Normal file
104
src/components/widget/Sakura.astro
Normal 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>
|
||||
@@ -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",
|
||||
|
||||
47
src/content/posts/Linux/bash查找顺序.md
Normal file
47
src/content/posts/Linux/bash查找顺序.md
Normal 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命令,可执行文件和目录的颜色和普通文件的文件名颜色不一样,之前一直不知道为什么。后来看了下才知道是用到了别名优先级比较高的特性
|
||||
|
||||

|
||||
|
||||
|
||||
能看到上面有配置`alias ls = 'ls --color=auto'`
|
||||
|
||||
|
||||
# Shell内置命令
|
||||
如果别名没有匹配,bash会去检查是不是内置命令(比如cd,echo这种)
|
||||
|
||||
|
||||
# 哈希表
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
# 环境变量 path中的目录
|
||||
最后一步,Bash按照PATH定义的目录顺序从左到右搜索可执行文件
|
||||
|
||||

|
||||
171
src/content/posts/Python/Python面向对象快速入门.md
Normal file
171
src/content/posts/Python/Python面向对象快速入门.md
Normal 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 面向对象编程的大门!
|
||||
350
src/content/posts/Python/Python面向对象深度解析.md
Normal file
350
src/content/posts/Python/Python面向对象深度解析.md
Normal 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 等顶级框架源码时游刃有余。
|
||||
226
src/content/posts/Python/Python面向对象进阶.md
Normal file
226
src/content/posts/Python/Python面向对象进阶.md
Normal 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()`,为编写可维护的继承结构打好基础。
|
||||
194
src/content/posts/开发日记/GitFlow学习.md
Normal file
194
src/content/posts/开发日记/GitFlow学习.md
Normal 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>`
|
||||
|
||||
明确 ticket(Jira/禅道/飞书任务)能减少“这分支是干嘛的”的沟通成本。
|
||||
|
||||
### 合并策略建议
|
||||
|
||||
- 日常 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
|
||||
#
|
||||
239
src/content/posts/开发日记/SpringCloud项目配合maven动态启用不同配置文件设计.md
Normal file
239
src/content/posts/开发日记/SpringCloud项目配合maven动态启用不同配置文件设计.md
Normal 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` 中定义环境 Profile(dev/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,**配置天然继承**
|
||||
- 切换环境只需控制父模块 profile,CI/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 自己的占位符继续用 `${...}`,避免混用
|
||||
|
||||
### 3)CI/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@"` 达到“动态切换配置”的效果
|
||||
350
src/content/posts/开发日记/python包管理工具uv使用.md
Normal file
350
src/content/posts/开发日记/python包管理工具uv使用.md
Normal 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`,工具层出不穷。而最近,由 Astral(Ruff 的开发者)推出的 **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
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
这将创建一个 `pyproject.toml` 文件和一个 `.python-version` 文件。
|
||||
|
||||
### 添加依赖
|
||||
|
||||
不再需要手动激活虚拟环境,`uv add` 会自动处理虚拟环境的创建和依赖的安装。
|
||||
|
||||
```bash
|
||||
# 添加依赖
|
||||
uv add requests
|
||||
|
||||
# 添加开发依赖 (例如 pytest)
|
||||
uv add --dev pytest
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 配置国内镜像源
|
||||
|
||||
#### 配置项目镜像
|
||||
|
||||
可以参考这个 `https://uv.oaix.tech/blog/2025/06/17/quickly-set-uv-package-index-is-china-mirror/` 来配置国内镜像源。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
在 ` pyproject.toml` 里面添加下面的内容
|
||||
|
||||
```toml
|
||||
[[tool.uv.index]]
|
||||
name = "tencent"
|
||||
url = "https://mirrors.cloud.tencent.com/pypi/simple/" # 腾讯云镜像源
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
可以看到里面已经用上镜像了
|
||||
|
||||
#### 配置全局镜像源
|
||||
|
||||
参考这个 `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/"
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
> 不过网上推荐的都是直接设置环境变量 `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"
|
||||
```
|
||||
|
||||
想永久生效就
|
||||

|
||||
|
||||
环境变量不知道去哪儿找可以直接 windows 搜索 `环境变量` 就可以找到。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
### 如果是已经有的项目,如何用 uv 同步包呢?
|
||||
|
||||
如果是已经有的项目,你可以使用 `uv sync` 命令来同步项目的依赖。
|
||||
|
||||
```bash
|
||||
uv sync
|
||||
```
|
||||
|
||||
这将根据 `pyproject.toml` 中的配置,安装所有必要的依赖。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 运行代码
|
||||
|
||||
使用 `uv run` 可以在项目的虚拟环境中执行命令,无需显式激活环境。
|
||||
|
||||
```bash
|
||||
# 运行脚本
|
||||
uv run main.py
|
||||
|
||||
# 运行工具
|
||||
uv run pytest
|
||||
```
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 同步环境
|
||||
|
||||
如果你拉取了别人的代码,或者手动修改了 `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国内很慢
|
||||
很简单
|
||||
|
||||

|
||||
|
||||
```
|
||||
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/"
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 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` 运行带依赖声明的单文件脚本。
|
||||
|
||||
23
src/content/posts/开发日记/scoop配置国内源.md
Normal file
23
src/content/posts/开发日记/scoop配置国内源.md
Normal 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
|
||||
|
||||
```
|
||||
|
||||
|
||||
230
src/content/posts/开发日记/springboot项目logback配置.md
Normal file
230
src/content/posts/开发日记/springboot项目logback配置.md
Normal 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` 文件中。
|
||||
|
||||

|
||||
|
||||
```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 以减少不必要的控制台输出性能损耗。
|
||||
79
src/content/posts/开发日记/记一次mysql数据库恢复引发的慢查询问题.md
Normal file
79
src/content/posts/开发日记/记一次mysql数据库恢复引发的慢查询问题.md
Normal 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` 一下,强制更新统计信息。
|
||||
|
||||
|
||||
---
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
title: 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题
|
||||
published: 2026-01-11T12:27:39
|
||||
description: ''
|
||||
image: ''
|
||||
|
||||
draft: false
|
||||
lang: ''
|
||||
category: '开发日记'
|
||||
---
|
||||
|
||||
# 开发日记/记一次springcloud项目启动显示端口被占用但是查不到占用进程的问题
|
||||
|
||||
换了台电脑,自己写的项目都跑不起来了。。。。网上说要拿netstat -ano | findstr 端口号 来查看占用进程,但是我查不到占用进程。。。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
# 解决办法
|
||||
查了下,windows是有预留端口的
|
||||
|
||||
```
|
||||
netsh interface ipv4 show excludedportrange protocol=tcp
|
||||
```
|
||||
|
||||

|
||||
|
||||
奥原来是在预留端口范围里面。。。。
|
||||
|
||||

|
||||
|
||||
|
||||
只能把项目端口号改到预留端口范围之外了,比如我这里改成8081就可以了
|
||||
38
src/content/posts/生活/祝自己生日快乐.md
Normal file
38
src/content/posts/生活/祝自己生日快乐.md
Normal 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: ''
|
||||
---
|
||||
|
||||
# 生日蛋糕
|
||||

|
||||
|
||||
💕💕💕💕💕💕姐姐买的,很好吃💕💕💕
|
||||
|
||||
明天吃
|
||||

|
||||
# 小物件
|
||||
|
||||
> 把去年看洛天依演唱会买的盒子拆了(可爱的天依0-0)
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
还有立牌
|
||||
|
||||

|
||||
|
||||
亚克力牌
|
||||

|
||||
|
||||
很硬的卡纸
|
||||

|
||||
@@ -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();
|
||||
|
||||
// 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];
|
||||
// }
|
||||
// }
|
||||
// Background image loading detection
|
||||
const bgUrl = bgConfig.enable && bgConfig.src ? bgConfig.src : '';
|
||||
|
||||
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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user