diff --git a/docs/en-gb/blog/contents/cooking-seasoning-guide.md b/docs/en-gb/blog/contents/cooking-seasoning-guide.md new file mode 100644 index 0000000..1dc7e00 --- /dev/null +++ b/docs/en-gb/blog/contents/cooking-seasoning-guide.md @@ -0,0 +1,31 @@ +--- +title: Cooking Seasoning Guide +tags: + - cooking + - cheatsheet +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## When to Add Seasonings + +| Seasoning Type | Timing | Suitable Dishes | Effect | +|----------------------------------------|----------------------------------------|------------------------------------------------------|-------------------------------------------------------------------------------| +| **Salt** | Last | Leafy greens | Prevents leaves from wilting and releasing water, keeps them crisp and tender | +| | Mid-way | Shredded potatoes, green beans, garlic shoots | Better flavour absorption | +| **MSG, Light Soy Sauce, Oyster Sauce** | Towards the end | Dishes needing umami and aroma enhancement | Adds umami and aroma; high heat destroys freshness and fragrance | +| **Cooking Wine** | Together with ingredients | Ingredients needing deodorisation | Better deodorisation when blanching | +| | At highest wok heat | Stir-fried ingredients needing deodorisation | High heat accelerates alcohol evaporation, effective aroma searing | +| **Vinegar** | Early | Hot and sour shredded potatoes, hot and sour cabbage | Makes shredded potatoes crisper and more flavourful | +| | Drizzle around wok edge before serving | Stir-fried chilli pork and similar high-heat dishes | High-heat searing enhances aroma | +| **Dark Soy Sauce** | After half-cooked | Dishes needing colour | Achieves optimal colouring effect | + +## Quick Reference + +| Seasoning | Timing | Effect | Example | +|-----------------------------------------------|------------------------------|-----------------------------------------------------------------------------|---------------------------------------------------------------------------| +| **Salt** | 10–15 seconds before serving | Prevents premature water loss and wilting of leafy greens; keeps them crisp | Sprinkle salt and toss before turning off the heat for lettuce or spinach | +| **Minced Garlic / Ginger** | First, after oil is hot | Releases fragrance and enhances flavour | Sauté minced garlic in hot oil, then add greens | +| **Light Soy Sauce / Oyster Sauce** (optional) | After salt | Adds umami; use sparingly to avoid overpowering the vegetables | Add 1–2 drops of light soy sauce after salting Shanghai greens | +| **Cooking Oil** | Hot wok, hot oil | Quick stir-fry at high heat locks in moisture | Use slightly more oil and high heat for fast stir-frying | diff --git a/docs/en-gb/blog/contents/docker-deployment-standards.md b/docs/en-gb/blog/contents/docker-deployment-standards.md new file mode 100644 index 0000000..ace6bef --- /dev/null +++ b/docs/en-gb/blog/contents/docker-deployment-standards.md @@ -0,0 +1,18 @@ +--- +title: Docker Deployment Standards +tags: + - docker + - deployment + - standards + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +- **Dockerfiles**: Provide a `Dockerfile` for the application to enable containerised deployment. +- **Lightweight Images**: Strive for lightweight Docker images by using appropriate base images and multi-stage builds. +- **Configuration**: Ensure environment-specific configuration (e.g., database connection strings, external service + URLs) is managed through environment variables injected into Docker containers. +- **Logging**: Configure containerised logging to output to `stdout` and `stderr` so that log aggregation systems can + collect logs easily. diff --git a/docs/en-gb/blog/contents/ecmascript-2025-syntax-sugar.md b/docs/en-gb/blog/contents/ecmascript-2025-syntax-sugar.md new file mode 100644 index 0000000..02021ec --- /dev/null +++ b/docs/en-gb/blog/contents/ecmascript-2025-syntax-sugar.md @@ -0,0 +1,55 @@ +--- +title: ECMAScript 2025 Syntax Sugar Guide +tags: + - javascript + - ecmascript + - pattern-matching + - frontend +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Pattern Matching + +### Traditional Approach + +```javascript +function processResponse(response) { + if (response.status === 200 && response.data) { + return { success: true, data: response.data }; + } else if (response.status === 404) { + return { success: false, error: 'Not found' }; + } else if (response.status >= 500) { + return { success: false, error: 'Server error' }; + } else { + return { success: false, error: 'Unknown error' }; + } +} +``` + +### Pattern Matching Approach + +```javascript +function processResponse(response) { + return match (response) { + when ({ status: 200, data }) -> ({ success: true, data }) + when ({ status: 404 }) -> ({ success: false, error: 'Not found' }) + when ({ status: status if status >= 500 }) -> ({ success: false, error: 'Server error' }) + default -> ({ success: false, error: 'Unknown error' }) + }; +} +``` + +### Handling Array Length Branches Gracefully + +```javascript +function handleArray(arr) { + return match (arr) { + when ([]) -> "Empty array" + when ([first]) -> `Only one element: ${first}` + when ([first, second]) -> `Two elements: ${first}, ${second}` + when ([first, ...rest]) -> `First element: ${first}, others: ${rest.length} items` + }; +} +``` diff --git a/docs/en-gb/blog/contents/email-like-a-boss.md b/docs/en-gb/blog/contents/email-like-a-boss.md new file mode 100644 index 0000000..331e3f7 --- /dev/null +++ b/docs/en-gb/blog/contents/email-like-a-boss.md @@ -0,0 +1,43 @@ +--- +title: Email Like a Boss +tags: + - communication + - email + - soft-skill + - career + - productivity +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +Email at work is about more than sharing information — it's about building trust and shaping your professional image. +The same message, phrased differently, can leave an entirely different impression. + +Below are 9 common email scenarios, contrasting "low-power" expressions with "high-power" alternatives that make you +sound more confident and professional. + +## Scenario Cheatsheet + +| Scenario | ❌ Don't Use (sounds…) | ✅ Use Instead (sounds…) | Why It Works | +|----------------------------|----------------------------------------------|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Late reply | **Sorry for the delay** | **Thanks for your patience** | Swaps an apology for gratitude — the focus shifts from "I was wrong" to "you were generous," acknowledging the delay while making the recipient feel respected | +| Scheduling | **What works best for you?** | **Could you do …?** | The former throws the decision entirely back to the other person; the latter offers a concrete option, cutting down on back-and-forth | +| After helping someone | **No problem / No worries** | **Always happy to help** | The former implies the task *could* have been a problem; the latter signals you enjoyed it and would happily do it again | +| Making a suggestion | **I think maybe we should …** | **It'd be best if we …** | The former oozes hesitation and self-doubt; the latter delivers a clear judgment — like someone with experience making a decision | +| Text isn't working | **_Spending 30 minutes rewriting an email_** | **It'd be easier to discuss in person** | Recognising the medium itself is the bottleneck and switching channels can be the most efficient move | +| Checking for understanding | **Hopefully that makes sense?** | **Let me know if you have questions** | The former betrays doubt about your own clarity; the latter calmly shares responsibility — the reader now has an action item too | +| Following up on progress | **Just wanted to check in** | **When can I expect an update?** | The former tiptoes around the ask; the latter names the time frame directly — clear, polite, and professional | +| Owned a small mistake | **Ahh sorry my bad totally missed that** | **Thanks for letting me know** | Over-apologising makes things awkward; this acknowledges the catch while keeping the focus on moving forward | +| Need to leave early | **Could I possibly leave early?** | **I will need to leave at …** | The former asks for permission; the latter states a plan — you're a professional and don't need to apologise for reasonable needs | + +## Core Principles + +Writing great emails is less about vocabulary and more about **stance**. Keep three rules in mind: + +1. **State instead of ask** — "I need…" carries more weight than "Could I possibly…" +2. **Thank instead of apologise** — Shift the focus from "my shortcoming" to "their support" +3. **Be specific instead of vague** — Offer exact times, options, and action items rather than lobbing the ball back + into their court + +Next time you open your inbox, take five seconds to ask: can I phrase this more like someone who makes decisions? diff --git a/docs/en-gb/blog/contents/fix-macos-monterey-sleep-wake.md b/docs/en-gb/blog/contents/fix-macos-monterey-sleep-wake.md new file mode 100644 index 0000000..c5f23fa --- /dev/null +++ b/docs/en-gb/blog/contents/fix-macos-monterey-sleep-wake.md @@ -0,0 +1,40 @@ +--- +title: Fix macOS Monterey+ Devices Waking Frequently from Sleep +tags: + - macos + - sleep + - power-management + - bug +--- + +> This article was originally written by **落格博客 +**: [落格博客](https://www.logcg.com/) » [Frequent Wake-from-Sleep Issues After Upgrading to macOS Monterey](https://www.logcg.com/archives/3528.html) + +After upgrading to macOS Monterey, my screen kept lighting up in the middle of the night for no apparent reason. It had +happened before, but only when notifications came in. Now the screen lights up on its own with no trigger — same +hardware, so it must be a software issue. + +After searching online, I first +found [Apple's official guide](https://support.apple.com/en-gb/guide/mac-help/mchlp2995/mac). It's very detailed, but +clearly of no help whatsoever. + +Digging deeper, I found the root cause. Run **`pmset -g log | grep DarkWake`** and you'll see your Mac hasn't been +resting while you slept... + +Several typical patterns appear, most with a DarkWake immediately followed by a Wake. The issue: DarkWake is meant to +wake the computer in the background to update data, but somehow a peripheral gets triggered, causing a full system wake. + +In any case, I don't want this feature — Power Nap. For me, I'd rather it save as much power as possible. The fix path: +disable network access wake, disable Power Nap... But here's the catch — on M1 devices, there is no Power Nap option in +settings. (Clearly, Apple is confident in their battery life but overlooked the power of bugs.) + +So for Power Nap, we have to go through the command line. First, check the current status with **`pmset -g`**. Find the +**`powernap`** value — if it isn't 0, it's enabled. Disable it with **`sudo pmset -a powernap 0`**. + +Also check **`tcpkeepalive`** — it likely isn't 0 either and should also be turned off. It controls whether your Mac +maintains TCP connections while sleeping. Run **`sudo pmset -a tcpkeepalive 0`** — you'll see a terminal warning: +***Warning: This option disables TCP Keep Alive mechanism when system is sleeping. This will result in some critical +features like 'Find My Mac' not to function properly.*** + +Essentially, turning it off limits some features — the system simply won't connect to the network while asleep. I'm +fairly confident that if someone actually steals your Mac, they won't be getting it online anyway. diff --git a/docs/en-gb/blog/contents/font-size-conversion-table.md b/docs/en-gb/blog/contents/font-size-conversion-table.md new file mode 100644 index 0000000..7cedc71 --- /dev/null +++ b/docs/en-gb/blog/contents/font-size-conversion-table.md @@ -0,0 +1,30 @@ +--- +title: Font Size Conversion Table +tags: + - typography + - font + - cheatsheet + - design +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +| Chinese Size Name | English Point Size (pt) | mm | px | +|--------------------|-------------------------|-------|------| +| 初号 (Primary) | 42 | 14.82 | 56 | +| 小初 (Small Primary) | 36 | 12.7 | 48 | +| 一号 (No. 1) | 26 | 9.17 | 34.7 | +| 小一 (Small No. 1) | 24 | 8.47 | 32 | +| 二号 (No. 2) | 22 | 7.76 | 29.3 | +| 小二 (Small No. 2) | 18 | 6.35 | 24 | +| 三号 (No. 3) | 16 | 5.64 | 21.3 | +| 小三 (Small No. 3) | 15 | 5.29 | 20 | +| 四号 (No. 4) | 14 | 4.94 | 18.7 | +| 小四 (Small No. 4) | 12 | 4.23 | 16 | +| 五号 (No. 5) | 10.5 | 3.7 | 14 | +| 小五 (Small No. 5) | 9 | 3.18 | 12 | +| 六号 (No. 6) | 7.5 | 2.56 | 10 | +| 小六 (Small No. 6) | 6.5 | 2.29 | 8.7 | +| 七号 (No. 7) | 5.5 | 1.94 | 7.3 | +| 八号 (No. 8) | 5 | 1.76 | 6.7 | diff --git a/docs/en-gb/blog/contents/frontend-development-standards.md b/docs/en-gb/blog/contents/frontend-development-standards.md new file mode 100644 index 0000000..f0d4039 --- /dev/null +++ b/docs/en-gb/blog/contents/frontend-development-standards.md @@ -0,0 +1,47 @@ +--- +title: Frontend Development Standards +tags: + - frontend + - react + - standards + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Dependency Management (pnpm) + +- **Strictness**: Leverage pnpm's strict dependency management to ensure a more deterministic `node_modules` structure + and efficient disk space usage. +- **Workspaces**: When using a monorepo approach, configure pnpm workspaces to streamline dependency management across + multiple frontend packages. +- **Auditing**: Regularly audit frontend dependencies for known vulnerabilities using `pnpm audit`. + +## API Communication (Axios) + +- **Axios Instance**: Create a centralised Axios instance for API calls to apply common configuration (base URL, + headers, interceptors). +- **Interceptors**: Use Axios interceptors to: + - Add authentication tokens to outgoing requests. + - Handle global error responses (e.g., display a notification for `401 Unauthorized`). + - Log requests/responses in development environments. +- **Error Handling**: Centralise API error handling in Axios interceptors or custom utility functions to provide + consistent user feedback. + +## React and Component Standards + +- **Function Components & Hooks**: Prefer function components with React Hooks over class components for new + development. +- **Props**: + - Define `interface` or `type` for component props to ensure type safety. + - Destructure props at the component entry point for clarity. +- **State Management (Redux)**: + - Use Redux Toolkit for efficient, boilerplate-reduced Redux development. + - Use `createSlice` to organise Redux logic into "slices" (feature-specific reducers, actions, and selectors). + - Follow the "ducks" pattern or "slices" approach to co-locate Redux logic. +- **Component Composition**: Break down complex UIs into smaller, reusable, single-responsibility components. +- **Ant Design**: + - Leverage Ant Design components for consistent UI/UX. + - Use CSS-in-JS solutions to consistently customise Ant Design themes and styles across the application if needed. +- **Accessibility**: Design and implement components with web accessibility (a11y) in mind from the start. diff --git a/docs/en-gb/blog/contents/frontend-tips-and-solutions.md b/docs/en-gb/blog/contents/frontend-tips-and-solutions.md new file mode 100644 index 0000000..0fd5678 --- /dev/null +++ b/docs/en-gb/blog/contents/frontend-tips-and-solutions.md @@ -0,0 +1,75 @@ +--- +title: Frontend Tips and Solutions Cheatsheet +tags: + - frontend + - react + - ant-design + - tailwind + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Decoupling Form Components + +Split form components into UI components and logic components. Use the UI components inside the logic components for rendering styles. + +## React Entry Component Order + +```typescript +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { Provider as ReduxProvider } from "react-redux" +import { PersistGate } from "redux-persist/integration/react" +import { RouterProvider } from "react-router" +import { App as AntApp, ConfigProvider as AntConfigProvider } from "antd" +import { StyleProvider } from "@ant-design/cssinjs" +import AntSimplifiedChinese from "antd/locale/zh_CN" +import "./index.css" +import "@/config/dayjs-config" +import store, { persistor } from "@/store" +import router from "@/router" + +createRoot(document.getElementById("root")!).render( + + + + {/* Note: StyleProvider must be the parent of ConfigProvider!!! */} + + + + + + + + + + +) +``` + +## Integrating Ant Design with Tailwind CSS in React + +> Reference: [https://github.com/ant-design/ant-design/discussions/56152](https://github.com/ant-design/ant-design/discussions/56152) + +```css +/* index.css */ + +@layer theme, base, antd, components, utilities; +@import "tailwindcss"; +``` + +```typescript + + + + + + + +``` diff --git a/docs/en-gb/blog/contents/general-development-standards.md b/docs/en-gb/blog/contents/general-development-standards.md new file mode 100644 index 0000000..4c95ce0 --- /dev/null +++ b/docs/en-gb/blog/contents/general-development-standards.md @@ -0,0 +1,26 @@ +--- +title: General Application Development Standards +tags: + - standards + - best-practice + - engineering +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Introduction + +This document outlines coding standards and best practices for application development. Adhering to these guidelines +ensures code quality, maintainability, and consistency across all projects. + +## General Principles + +- **Clarity & Readability**: Code must be easy to read and understand. Prefer clear, self-documenting code over clever, + terse, but obscure solutions. +- **Consistency**: Maintain a consistent coding style, naming conventions, and architectural patterns across the + project. +- **Modularity**: Design components with loose coupling and high cohesion to promote reusability and simpler testing. +- **Testability**: Write code that is inherently testable. +- **Security-First Design**: Incorporate security considerations at every stage of development. +- **Performance Awareness**: Be mindful of performance implications for critical code paths and API endpoints. diff --git a/docs/en-gb/blog/contents/gitlab-ops.md b/docs/en-gb/blog/contents/gitlab-ops.md index 9535316..080198b 100644 --- a/docs/en-gb/blog/contents/gitlab-ops.md +++ b/docs/en-gb/blog/contents/gitlab-ops.md @@ -10,9 +10,11 @@ author: ## Setting the Default Language to Chinese in GitLab -GitLab filters out languages with less than 90% translation coverage via the `SWITCHER_MINIMUM_TRANSLATION_LEVEL` variable in the code. Simplified Chinese sits at 84% and Traditional Chinese at 83%. +GitLab filters out languages with less than 90% translation coverage via the `SWITCHER_MINIMUM_TRANSLATION_LEVEL` +variable in the code. Simplified Chinese sits at 84% and Traditional Chinese at 83%. -Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb` and modify the code as follows: +Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb` and modify +the code as follows: ```diff title="/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb" - SWITCHER_MINIMUM_TRANSLATION_LEVEL = 90 @@ -23,7 +25,8 @@ Then restart GitLab with `gitlab-ctl restart`. ## Adding ICP Filing Information to the Homepage -Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shared/_footer.html.haml` and add the following: +Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shared/_footer.html.haml` and add the +following: ```diff + = link_to _("Your ICP Filing Number"), "https://beian.miit.gov.cn/", target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer' @@ -33,7 +36,8 @@ Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shar ### Downloading the GitLab CE Package -Download the deb package from the [GitLab Package page](https://packages.gitlab.com/gitlab/gitlab-ce) and transfer it to the server using `scp` or a similar tool. +Download the deb package from the [GitLab Package page](https://packages.gitlab.com/gitlab/gitlab-ce) and transfer it to +the server using `scp` or a similar tool. ### Stopping Memory-Intensive Services diff --git a/docs/en-gb/blog/contents/google-code-review-standards.md b/docs/en-gb/blog/contents/google-code-review-standards.md new file mode 100644 index 0000000..28eb6c1 --- /dev/null +++ b/docs/en-gb/blog/contents/google-code-review-standards.md @@ -0,0 +1,131 @@ +--- +title: Google Code Review Standards +tags: + - code-review + - best-practice + - engineering + - google +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +Code review is a step in the development process where one or more developers review code written by another developer ( +the author) to ensure that: + +- The code is free of errors, bugs, and issues; +- The code conforms to quality and style guide requirements and standards; +- The code fulfils all intended functionality; +- Once merged, the codebase continues to function properly and is left in a better state. + +This is why code review is a vital part of software development. Code reviewers act as gatekeepers, responsible for +deciding whether the code is ready to become part of the codebase and enter production. + +## The Code Should Improve the Overall Health of the System + +Every code change (pull request) should improve the overall health of the system. The key point is that even small +improvements, once merged, enhance the health of the software or codebase. + +## Review Code Quickly and Provide Active Responses and Feedback + +First and foremost, do not delay the merging of code. There is no such thing as perfect code. If the code improves the +overall health of the system, it should be delivered promptly. + +> The key insight is that there is no perfect code — only better code. — Google Engineering Practices Documentation + +If there are no urgent tasks at hand, review code as soon as it is submitted. The maximum response time for a pull +request should not exceed one working day. Multiple rounds of partial or complete code review should be completed for a +single pull request within a day. + +## Educate and Inspire During Code Review + +During code review, provide guidance by sharing knowledge and experience wherever possible. + +## Review Code Against Standards + +Always remember that style guides, coding standards, and relevant documentation should serve as the absolute authority +in code review. For example, when consistency of tabs versus spaces is in question, cite the coding conventions. + +> If you use Java, the following article may be helpful — it summarises Java coding best practices from major tech +> companies: [A Short Summary of Java Coding Best Practices](https://rhamedy.medium.com/a-short-summary-of-java-coding-best-practices-31283d0167d3) + +## Resolving Code Review Conflicts + +When resolving conflicts during code review, follow the agreed best practices in the style guide and coding standards, +and seek advice from others with more domain knowledge and experience. + +If your comment is optional or relatively unimportant, indicate so in the comment and let the author decide whether to +address or skip it. + +As a reviewer, when there is no style guide or coding standard to reference, you can at minimum suggest that the code +change remain consistent with the rest of the codebase. + +## UI Change Demonstrations Are Part of Code Review + +If a code change involves user interface modifications, a demonstration is required in addition to the code review to +ensure the UI meets expectations and aligns with the interface design. + +For frontend code changes, you must provide a demonstration or ensure that the code change includes the necessary UI +automation tests to verify the added or updated functionality. + +## Ensure All Tests Are Included in the Code Review + +Unless in an emergency, pull requests should include all necessary tests, such as unit tests, integration tests, and +end-to-end tests. + +An emergency here means a bug or security vulnerability that needs fixing as soon as possible, and tests can be added +later. In such cases, ensure that appropriate tickets/issues are created and someone is responsible for completing the +tests immediately after the hotfix or deployment. + +Skipping tests must never be accepted. If time is short and certain goals risk not being met, the solution is not to +skip tests but to scope down the deliverables. + +## Don't Interrupt Your Own Work for Code Review + +If you are deeply focused on your work, don't interrupt yourself — it takes a long time to get back into the flow. In +other words, the cost of interrupting a developer in flow far exceeds the cost of making them wait for a code review. Do +your code reviews after a break (lunch, coffee, etc.). + +Most of the time, an entire code review and merge cannot be completed in a single day. What matters is giving the author +prompt feedback. For example, even if you cannot complete a full review, you can quickly point out a few areas worth +discussing. This significantly reduces frustration during the review process. + +## Review All Code — Make No Assumptions + +Review every line of code that is submitted. Do not make assumptions about manually written classes and methods, and +make sure you understand what the code is doing. + +Make sure you understand the code you are reviewing. If you don't, ask the author for clarification or a code +walkthrough and explanation. If you are not qualified to review part of the code, ask another qualified developer to +review it in your place. + +## Keep the Big Picture in Mind During Code Review + +It helps to look at code changes from a broader perspective. For example, a file is modified and four new lines of code +are added. Don't just look at those four lines — consider reviewing the entire file and examining what was added. Do the +additions degrade the quality of existing code? Do they make existing functionality a candidate for refactoring? + +Reviewing added code outside the context of the function/method or class will, over time, lead to classes that are +unmaintainable, tangled, difficult to test, and hard to extend or refactor. + +Remember that just as trivial improvements can compound into a better product over time, even minor code degradation or +technical debt can accumulate to the point where the product becomes difficult to maintain and extend. + +## Recognise and Encourage Great Work During Code Review + +If you see an excellent code change, don't forget to acknowledge and encourage the author generously. The purpose of +code review is not only to find errors but also to encourage and guide developers towards great work. + +## Be Considerate, Respectful, Kind, and Clear During Code Review + +It is critical to remain kind, clear, polite, and respectful during code review, while also providing the author with +clear feedback and positive assistance. When reviewing code, comment on the code — not the developer. + +## Explain Code Review Comments Thoroughly, with a Sense of Proportion + +Whenever a code review comment proposes an alternative or points out a problem, it's important to explain the reasoning +and provide examples based on your knowledge and experience to help the developer understand why your suggestion +improves the code quality. + +When suggesting modifications or changes, find the right balance in how you guide the author. For example, I prefer +guidance, explanation, hints, or suggestions over providing the entire solution. diff --git a/docs/en-gb/blog/contents/java-development-cheatsheet.md b/docs/en-gb/blog/contents/java-development-cheatsheet.md new file mode 100644 index 0000000..949454d --- /dev/null +++ b/docs/en-gb/blog/contents/java-development-cheatsheet.md @@ -0,0 +1,210 @@ +--- +title: Java Development Cheatsheet +tags: + - java + - tips +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Comparison for `BigDecimal` + +In Java, comparing `BigDecimal` values requires care because `equals` and `compareTo` behave differently. + +### `equals` vs `compareTo` + +`BigDecimal#equals` checks both **value** and **scale**, while `BigDecimal#compareTo` only checks **value** (ignoring scale). This means: + +```java +var a = new BigDecimal("100"); // scale = 0 +var b = new BigDecimal("100.00"); // scale = 2 + +a.equals(b); // false — different scale +a.compareTo(b); // 0 — same mathematical value + +var c = new BigDecimal("200"); + +a.compareTo(c); // -1 (negative) — a is less than c +c.compareTo(a); // 1 (positive) — c is greater than a +``` + +### Why this matters + +The scale mismatch often appears when values come from different sources — e.g., parsing user input, reading from a database (`DECIMAL(10,2)` columns), or receiving JSON payloads. You might think two values are equal when `equals` says they aren't. + +### Rule of thumb + +- Use **`compareTo`** for numeric equality checks: `a.compareTo(b) == 0` +- Use **`equals`** only when you mean "identical representation" (same value and same scale) +- Use **`stripTrailingZeros()`** if you need to normalise scale before `equals`: + + ```java + a.stripTrailingZeros().equals(b.stripTrailingZeros()); // true + ``` + +### Comparing with zero + +Avoid `==` or `.equals(BigDecimal.ZERO)` to check for zero — prefer `compareTo`: + +```java +if (value.compareTo(BigDecimal.ZERO) == 0) { ... } +``` + +## How to Retrieve Data from a BlockingQueue + +- `take()` — retrieves and removes the head of the queue, waiting if necessary until an element becomes available. +- `poll()` — retrieves and removes the head of the queue, or returns `null` if the queue is empty. +- `poll(long timeout, TimeUnit unit)` — retrieves and removes the head of the queue, waiting up to the specified wait time if necessary for an element to become available. Returns `null` if the timeout expires. +- `peek()` — retrieves but does not remove the head of the queue. Returns `null` if the queue is empty. + +## Spring Cloud Alibaba FAQs + +### How to prevent Nacos from creating a `nacos` folder in the user's home directory? + +Add the following two configuration properties to specify the Nacos storage path: + +- `JM.LOG.PATH` +- `JM.SNAPSHOT.PATH` + +### How to deal with Sentinel's scattered log files? + +Add the configuration property `csp.sentinel.log.dir` to change Sentinel's log directory. + +### How to add Configuration Properties in JetBrains IntelliJ IDEA? + +In JetBrains IntelliJ IDEA, click **`Edit Configurations…`** in the run configuration dropdown at the top right. + +Click the **`Modify options`** button on the page, then add the properties you need to reset in the **`Override configuration properties`** table that appears below. + +## Spring Data JPA FAQs + +### How to fix the "Serializing `PageImpl` instances as-is not supported" warning? + +Spring Data JPA warns about unstable JSON serialization of `PageImpl`. To resolve this, enable VIA_DTO serialization mode on your application's main class: + +```java +@EnableSpringDataWebSupport(pageSerializationMode = + EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) +``` + +### Why does the first page return no results? + +JPA pagination is **zero-indexed**. Page `0` is the first page. If your frontend sends `page=1`, you need to pass `page - 1` to Spring Data: + +```java +Pageable pageable = PageRequest.of(requestPage - 1, pageSize); +``` + +### How to avoid the N+1 query problem? + +The N+1 problem occurs when JPA executes one query for the parent entity, then N additional queries for each child association. + +**Detection** — look for repetitive SQL queries in the logs, or configure `spring.jpa.properties.hibernate.generate_statistics=true` to spot it. + +**Fixes:** + +| Approach | When to use | +|--------------------------|---------------------------------------------------| +| `@EntityGraph` | Declarative, good for entity-specific fetch plans | +| `JOIN FETCH` in `@Query` | Fine-grained control per query | +| `@BatchSize` | Reduces N+1 to N/k+1 by batching | + +```java +// Option 1: EntityGraph +@EntityGraph(attributePaths = {"roles", "permissions"}) +Optional findById(long id); + +// Option 2: JOIN FETCH +@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id") +Optional findByIdWithRoles(@Param("id") long id); +``` + +### `findById` vs `getReferenceById` — which one to use? + +- **`findById`** — hits the database immediately, returns the entity or `Optional.empty()`. Use this when you need the data. +- **`getReferenceById`** — returns a lazy proxy **without** hitting the database. Throws `EntityNotFoundException` only when you access a non-existent proxy's properties. Use this when you only need the ID to set a foreign key relationship. + +```java +// Good: only need the user reference to set a FK +Post post = new Post(); +post.setAuthor(userRepository.getReferenceById(userId)); +``` + +### How to fix `LazyInitializationException`? + +This happens when you access a lazily-loaded association outside the persistence context (e.g., in a controller or serializer after the transaction has closed). + +**Solutions:** + +1. **Use `JOIN FETCH` or `@EntityGraph`** to eagerly load needed associations. +2. **Use DTO projections** — return only the fields you need instead of whole entities: + + ```java + @Query("SELECT new com.example.UserDto(u.id, u.name) FROM User u WHERE u.id = :id") + UserDto findUserDtoById(@Param("id") long id); + ``` +3. **`@Transactional(readOnly = true)`** on the service method — keep the session open for the entire method scope. + +### When should I use `@Transactional(readOnly = true)`? + +Use `@Transactional(readOnly = true)` on **read-only** service methods for three benefits: + +- Hibernate skips dirty checking (no snapshots, less memory). +- The JDBC driver may route to read replicas. +- It documents the intent clearly. + +```java +@Service +public class UserService { + + @Transactional(readOnly = true) + public UserDto getUser(long id) { ... } + + @Transactional + public UserDto createUser(CreateUserRequest request) { ... } +} +``` + +### `save()` vs `saveAll()` — which is faster for batch inserts? + +`saveAll()` uses a single transaction and can benefit from JDBC batching. Configure the batch size: + +```yaml +spring: + jpa: + properties: + hibernate: + jdbc: + batch_size: 20 + order_inserts: true + order_updates: true +``` + +For large bulk inserts (thousands of rows), consider `JdbcTemplate` batch operations instead — Hibernate's entity management overhead is significant at that scale. + +### How to use dynamic queries with `Specification`? + +For complex search forms with optional filters, use `JpaSpecificationExecutor`: + +```java +public interface UserRepository extends JpaRepository, + JpaSpecificationExecutor { +} + +// Usage +Specification spec = (root, query, cb) -> { + List predicates = new ArrayList<>(); + if (name != null) { + predicates.add(cb.like(root.get("name"), "%" + name + "%")); + } + if (status != null) { + predicates.add(cb.equal(root.get("status"), status)); + } + return cb.and(predicates.toArray(new Predicate[0])); +}; + +Page page = userRepository.findAll(spec, pageable); +``` + + diff --git a/docs/en-gb/blog/contents/java-development-standards.md b/docs/en-gb/blog/contents/java-development-standards.md new file mode 100644 index 0000000..f3eab54 --- /dev/null +++ b/docs/en-gb/blog/contents/java-development-standards.md @@ -0,0 +1,239 @@ +--- +title: Java Development Standards +tags: + - java + - spring-boot + - standards + - best-practice + - backend +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Java Language and Coding Style + +- **Java Version**: Projects should use the latest LTS version of the JDK whenever possible. +- **Naming Conventions**: + - Classes: `PascalCase` (e.g., `UserService`, `OrderController`). + - Methods: `camelCase` (e.g., `getUserById`, `saveOrder`). + - Variables: `camelCase` (e.g., `username`, `statusCode`). + - Constants: `SCREAMING_SNAKE_CASE` (e.g., `DEFAULT_PAGE_SIZE`). +- **Immutability**: Prefer immutability for domain objects and DTOs where possible, using `records` or immutable classes to reduce side effects and improve thread safety. +- **Optional**: Use `Optional` to explicitly handle potentially absent values and avoid `NullPointerException`. +- **Streams API**: Prefer the Java Streams API for collection processing, promoting functional and declarative programming. +- **Exception Handling**: + - Use specific exceptions. Avoid catching generic `Exception`. + - Throw runtime exceptions for unrecoverable errors. + - Define custom checked exceptions for recoverable errors if required by business logic. + - Leverage Spring's `@ControllerAdvice` and `@ExceptionHandler` for centralised global exception handling and consistent API error responses. +- **Code Review**: All backend code must undergo thorough manual code review before merging, particularly following GitFlow principles. IntelliJ IDEA's integrated code analysis tools should be used as a first-pass review. + +## Documentation and Comments + +- All **public** classes, methods, and significant fields in backend Java code must include comprehensive Javadoc comments. +- Javadoc should explain the purpose, parameters (`@param`), return values (`@return`), and thrown exceptions (`@throws`). +- Javadoc formatting: + - Javadoc must follow a maximum of 100 characters per line (including whitespace for formatting). If the content exceeds 100 characters, break at the last word that ends within the 100-character limit. However, if after the line break only a single word remains at the start of the next line, break one word earlier. + + ```java + /** + * Enables configuration properties for S3 file storage services. Individual service beans are + * created by their respective service classes to better support conditional configuration. + */ + ``` + + - Use `

` to separate paragraphs. + + ```java + /** + * This is the first paragraph of the Javadoc. + *

+ * This is the second paragraph of the Javadoc. + */ + ``` + + - Each paragraph must be a grammatically correct and semantically complete paragraph following English sentence conventions. + - All `@param`, `@return`, `@throws`, and `@see` explanations must follow these rules: + - Do not start with a capital letter. + - If the description ends with a declarative sentence, do not use punctuation at the end. + + ```java + /** + * Returns the greater of two {@code int} values. That is, the + * result is the argument closer to the value of + * {@link Integer#MAX_VALUE}. If the arguments have the same value, + * the result is that same value. + * + * @param a an argument + * @param b another argument + * @return the larger of {@code a} and {@code b} + */ + public static int max(int a, int b) { + return (a >= b) ? a : b; + } + ``` + +## Dependency Management (Gradle) + +- **Build File**: `build.gradle.kts` must be well-organised with clear separation of plugins, dependencies, and tasks. +- **Dependency Versions**: Dependency versions must be centrally managed in the `gradle/libs.versions.toml` file to ensure consistency. +- **Plugin Management**: Explicitly declare Gradle plugins and their versions. +- **Avoid Unnecessary Dependencies**: Only include dependencies actually used by the project. Regularly review and clean up unused dependencies. + +## API Design (RESTful) + +- **RESTful Principles**: Follow RESTful principles: + - **Resources**: Model data as resources identifiable by URI. + - **HTTP Methods**: Use standard HTTP methods appropriately (GET for retrieval, POST for creation, PUT for full update, PATCH for partial update, DELETE for removal). + - **Statelessness**: APIs should be stateless; each request from client to server must contain all the information needed to understand the request. +- **URIs**: + - Use plural nouns for collection resources (e.g., `/users`, `/products`). + - Use hyphens in URIs for readability (e.g., `/user-accounts`). + - Avoid verbs in URIs (e.g., use `/users` instead of `/getAllUsers`). +- **Status Codes**: Use appropriate HTTP status codes to indicate the result of API requests (e.g., `200 OK`, `201 Created`, `204 No Content`, `400 Bad Request`, `401 Unauthorized`, `403 Forbidden`, `404 Not Found`, `500 Internal Server Error`). +- **Response Format**: JSON is the preferred response format. +- **Versioning**: Where version control is required, use the `X-Endpoint-Version` header parameter to control API versions. + +## Spring Boot Best Practices & Layered Architecture + +- **Layered Architecture (MVC with Manager Layer)**: Our backend applications follow a strict multi-layered architecture, ensuring clear separation of responsibilities and improving maintainability and testability. The layers and their responsibilities are: + + - **Controller Layer**: Located in the `controller` package. Responsible for exposing RESTful APIs, handling HTTP requests, and mapping request parameters/bodies to service layer calls. Controllers should remain lightweight, focusing primarily on input validation (using DTOs) and coordinating calls to the `Service` layer. + - **Service Layer**: Located in the `service` package. This layer encapsulates core business logic. Services expose a directly consumable API to the `Controller` layer, abstracting business processes and transaction management. Services coordinate calls to the `Manager` layer to execute business operations. + - **Manager Layer**: Located in the `manager` package. This layer provides atomic business operations that can be composed by the `Service` layer. Managers typically handle more complex business logic and may involve interacting with multiple repositories or other external systems at a finer granularity. + - **Repository Layer (MyBatis)**: Located in the `repository` package and `src/main/resources/repository` (for XML mapping files). This layer is responsible for providing atomic database interaction operations. + + **Inter-Layer Communication Strategy**: + - **Components within a layer may only call components in the layer directly below it.** + - **Cross-layer calls are strictly prohibited** (e.g., Controller directly calling Manager, Service directly calling Repository). + - **Upward calls are strictly prohibited** (e.g., Service calling Controller). + - **Lateral calls** (e.g., Service A calling Service B for a different domain) should be carefully considered and typically indicate a need to refactor shared logic into a `Manager` or a dedicated `Service` for that shared concern. + + - **Configuration**: Prefer `application.yml` over `application.properties` for configuration properties, offering better readability and hierarchical structure. Use `@ConfigurationProperties` for type-safe configuration. + - **Dependency Injection**: All dependencies should use constructor injection (mandatory dependencies) or setter injection (optional dependencies). Avoid `@Autowired` on fields, as it makes testing more difficult and hides dependencies. + - **Services**: Business logic classes are annotated with `@Service`. Services should be kept lean and focused on orchestrating domain operations, typically involving business logic processing through `Manager` layer components and interaction through `Manager` or directly with MyBatis repositories (if the particular operation does not require intermediate Manager logic, though the Manager layer is preferred for all repository interactions consistent with the layered architecture definition). + +- **Repositories (MyBatis & JPA)**: + - **The project uses both MyBatis and JPA for database operations**. Repository interfaces in the `repository` and `mapper` packages define the data access contract. + - Corresponding SQL definitions are managed in XML mapping files located in `src/main/resources/mapper`. + - Data operation conventions: + - Use JPA for simple database operations. + - Use MyBatis for complex database operations. + - When performing paginated queries, page numbers should start from 0 (*compatible with Spring Data JPA*). + - **Data Access Method Naming Conventions**: + - For **querying data lists**: methods **must** start with `selectListBy`, followed by the filter criteria (e.g., `selectListByUserId`, `selectListByDepartmentIdAndStatus`). These methods must also include a `PageRequest` parameter for pagination. + - For querying **single data records**: methods **must** start with `selectOne` (e.g., `selectOneById`, `selectOneByUsername`). + - For **saving new data**: methods **must** be named `save`, and Mapper method return type must be `int` (affected row count). + - For **updating existing data**: methods **must** be named `update`, and Mapper method return type must be `int` (affected row count). + - For **deleting data**: methods **must** start with `deleteBy`, clearly indicating the deletion criteria (e.g., `deleteById`). Mapper method return type must be `int` (affected row count). + +- **Controllers**: + - Annotate REST controllers with `@RestController`. + - Use `@GetMapping`, `@PostMapping`, etc. to map HTTP methods (GET, POST, PUT, DELETE) to appropriate controller methods. + - Ensure request and response payloads are clearly defined (DTOs) and documented. + - Controllers should primarily handle HTTP request/response mapping and delegate actual business logic to the service layer. + +- **DTOs (Data Transfer Objects)**: Define separate DTOs for request and response bodies to decouple the internal domain model from the API contract. Use validation annotations on DTOs (e.g., `@Valid`, `@NotNull`, `@Size`). + +- **Logging (SLF4J & Logback)**: + - All logging in the application uses `org.slf4j.Logger`. + - Configure `application-dev.yml` to set logging levels, appenders, and output formats. + - Log messages should be descriptive and provide sufficient context. Avoid logging sensitive information. + - Use parameterised logging for performance and to prevent string concatenation overhead (e.g., `log.debug("Processing user: {}", userId);`). + - Standard log levels: `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE`. + +## Project Structure + +Backend applications follow a structured Gradle project layout. The core application should be clearly divided into various sub-packages. + +```text +backend-application +├── build.gradle.kts // Project's main Gradle build script +├── config // External configuration directory +│ ├── application-dev.yml // Application properties for the development environment +│ └── application-prod.yml.example // Example production properties (to be copied and configured) +├── database // Database-related files +│ └── init.d // Database initialisation scripts +│ └── init-en_GB.sql // SQL script for database schema and initial data using the specified locale (British English) +├── gradle // Gradle Wrapper and configuration files +│ ├── libs.versions.toml // Centralised dependency version management (Gradle Version Catalog) +│ └── wrapper // Gradle Wrapper files +│ ├── gradle-wrapper.jar +│ └── gradle-wrapper.properties +├── gradle.properties // Project-specific Gradle properties +├── gradlew // Gradle Wrapper executable (Linux/macOS) +├── gradlew.bat // Gradle Wrapper executable (Windows) +├── settings.gradle.kts // Gradle settings for multi-project builds (if applicable) +└── src + ├── main + │ ├── java + │ │ └── com/onixbyte/application // Application root package + │ │ ├── Application.java // Spring Boot application main entry point + │ │ ├── config // Spring configuration classes + │ │ ├── constant // Classes defining application-wide constants + │ │ ├── controller // REST API endpoints + │ │ ├── domain // Core domain models and related types + │ │ │ ├── common // Common domain objects/utilities + │ │ │ ├── entity // JPA/MyBatis entities representing database tables + │ │ │ ├── model // General-purpose entity class models + │ │ │ ├── view // Data transfer objects specifically for read-only operations (e.g., query results, report structures) + │ │ │ └── web // Data transfer objects specifically for web request/response bodies + │ │ │ ├── request // Request DTOs + │ │ │ └── response // Response DTOs + │ │ ├── exception // Custom application-specific exceptions + │ │ ├── extension // Extension points or custom functionality + │ │ │ ├── jackson // Jackson serialisation/deserialisation extensions + │ │ │ └── redis // Redis-related extensions + │ │ │ └── serializer + │ │ ├── filter // Servlet filters or Spring Security filters + │ │ ├── manager // Business logic coordinators, typically orchestrating multiple services or repositories + │ │ ├── mapper // Data access layer (MyBatis interfaces) + │ │ ├── processor // General-purpose processing components or business workflows + │ │ ├── properties // Classes for type-safe configuration properties (`@ConfigurationProperties`) + │ │ ├── repository // Data access layer (Spring Data JPA interfaces) + │ │ ├── security // Spring Security-specific components + │ │ │ ├── authentication // Custom authentication mechanisms + │ │ │ └── provider // Custom authentication providers + │ │ ├── service // Core business logic (transactional layer) + │ │ ├── utils // General-purpose utility classes + │ │ └── validation // Custom validation logic + │ │ └── group // Validation groups for different contexts (e.g., create, update) + │ └── resources + │ ├── application.yml // Default application properties + │ └── mapper // MyBatis XML mapping files + └── test + └── java + └── com/onixbyte/helix + └── HelixApplicationTests.java // Spring Boot integration tests +``` + +**Key Observations and Specific Instructions:** + +- **External `config` Directory** + - Environment configurations (`application-dev.yml`, `application-prod.yml.example`) are managed in the top-level `config` directory, separate from `src/main/resources`. This facilitates environment-specific property management, allowing different configurations to be mounted or linked at deployment time. + - **Do not upload any configuration files other than `src/main/resources/application.yml` to the Git repository.** +- **Database Initialisation**: The `database/init.d` directory is reserved for SQL scripts, specifically database schema initialisation (`init-en_GB.sql`), which is critical for environment setup and CI/CD pipelines. This structure suggests a "schema-first" or "code-driven schema evolution" approach. +- **`client` Package**: This package is used to provide services for all middleware to the application, such as HTTP, S3 storage, Redis calls, etc. For self-coded functional implementations that need to be added to the Spring context (such as JSON Web Token generation and parsing), placing them in this package is also recommended. +- **MyBatis & Spring Data JPA Integration** + - The `src/main/java/.../mapper` package contains MyBatis mapper interfaces, while the actual SQL definitions reside in `src/main/resources/mapper/*.xml` files. This separation is key to keeping code clean while leveraging MyBatis's powerful XML mapping capabilities. + - The `src/main/java/.../repository` package contains Spring Data JPA interfaces. +- **`domain` Package Granularity**: + - `domain.entity`: Reserved for classes directly mapped to database tables (POJOs for MyBatis). + - `domain.model`: For more general-purpose domain objects or aggregate roots that do not map one-to-one with individual tables. + - `domain.view`: Specifically for Data Transfer Objects (DTOs) used in read-only scenarios (e.g., query results, report structures). + - `domain.web.request` / `domain.web.response`: Clearly separated DTOs for incoming API requests and outgoing API responses, strictly adhering to the API contract and decoupled from internal domain entities. +- **`manager` and `processor` Packages**: These packages imply a layered architecture where "managers" coordinate operations involving multiple services or repositories, while "processors" may handle specific aspects of business processes. It is mandatory to clearly define the responsibilities of classes in these packages to prevent anti-patterns such as "anaemic domain model" or "god objects". +- **`security` Package**: This sub-package contains custom Spring Security components beyond the initial configuration, such as custom authentication types and providers, indicating a tailored security implementation. +- **`properties` Package**: This package is for custom `@ConfigurationProperties` classes, facilitating type-safe access to application settings defined in YML files — well-positioned. +- **`extension` Package**: This is a flexible area for application-specific extensions, such as custom Jackson serialisers or Redis customisations. It should be used sparingly to avoid becoming a "miscellaneous" dumping ground. + +## Security (Spring Security) + +- **Mandatory Use**: Spring Security is mandatory in all Spring Boot web applications. +- **Authentication & Authorisation**: Configure authentication mechanisms (e.g., OAuth 2.0, JWT, session-based) and authorisation rules in `SecurityConfig.java`. +- **CSRF Protection**: Ensure CSRF protection is enabled for state-modifying operations, unless there is a strong reason to disable it (e.g., stateless APIs with other mechanisms already in place). +- **CORS**: Correctly configure Cross-Origin Resource Sharing (CORS) according to frontend deployment requirements. +- **Third-Party Identity Providers**: When integrating with third-party identity providers (e.g., Microsoft Entra ID), follow best practices for secure token handling and user provisioning. Sensitive credentials must be securely managed (e.g., environment variables, Vault). +- **Input Validation**: Always validate all user input on the server side to prevent common vulnerabilities such as SQL injection, XSS, etc. +- **Content Security Policy (CSP)**: Consider implementing a robust CSP for the frontend to mitigate XSS attacks. diff --git a/docs/en-gb/blog/contents/minio-admin-guide.md b/docs/en-gb/blog/contents/minio-admin-guide.md new file mode 100644 index 0000000..7769d08 --- /dev/null +++ b/docs/en-gb/blog/contents/minio-admin-guide.md @@ -0,0 +1,200 @@ +--- +title: MinIO Administration Guide for New Versions +tags: + - minio + - storage + - s3 + - devops + - cheatsheet +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +In newer versions, MinIO has removed administrative functionality from the Web UI. You now need to use the **MinIO Client (mc)** command-line tool for all management operations. + +## Installing MinIO Client (mc) + +### Windows: + +```powershell +# Download mc.exe +Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "mc.exe" + +# Or using Chocolatey +choco install minio-client +``` + +### Linux/macOS: + +```bash +# Linux +wget https://dl.min.io/client/mc/release/linux-amd64/mc +chmod +x mc +sudo mv mc /usr/local/bin/ + +# macOS +brew install minio/stable/mc +``` + +## Configuring the MinIO Client + +```bash +# Add a MinIO server alias +mc alias set myminio http://localhost:9000 minioadmin minioadmin + +# Verify the connection +mc admin info myminio +``` + +## User Management + +### Creating Users + +```bash +# Create a new user +mc admin user add myminio newuser newpassword + +# List all users +mc admin user list myminio +``` + +### Creating Access Keys and Secret Keys + +```bash +# Create a service account for a user (generates AccessKey/SecretKey) +mc admin user svcacct add myminio newuser + +# Or specify custom AccessKey and SecretKey +mc admin user svcacct add myminio newuser --access-key "MYACCESSKEY123" --secret-key "MYSECRETKEY456" + +# View a user's service accounts +mc admin user svcacct list myminio newuser +``` + +## Permission Management + +### Creating Policies + +```bash +# Create a policy file policy.json +cat > policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::mybucket/*" + ] + } + ] +} +EOF + +# Add the policy +mc admin policy add myminio mypolicy policy.json + +# Assign the policy to a user +mc admin policy set myminio mypolicy user=newuser +``` + +## Bucket Management + +```bash +# Create a bucket +mc mb myminio/mybucket + +# List buckets +mc ls myminio + +# Set bucket policy +mc policy set public myminio/mybucket +``` + +## Common Administration Commands + +```bash +# View server information +mc admin info myminio + +# View server configuration +mc admin config get myminio + +# Restart the server +mc admin service restart myminio + +# View logs +mc admin logs myminio + +# View statistics +mc admin prometheus metrics myminio +``` + +## Practical Script Example + +Create an administration script `setup-minio.sh`: + +```bash +#!/bin/bash + +MINIO_ALIAS="myminio" +MINIO_URL="http://localhost:9000" +ADMIN_USER="minioadmin" +ADMIN_PASS="minioadmin" + +# Configure the MinIO client +mc alias set $MINIO_ALIAS $MINIO_URL $ADMIN_USER $ADMIN_PASS + +# Create an application user +APP_USER="appuser" +APP_PASS="apppassword" +mc admin user add $MINIO_ALIAS $APP_USER $APP_PASS + +# Create a service account and retrieve AccessKey/SecretKey +echo "Creating service account for $APP_USER..." +CREDENTIALS=$(mc admin user svcacct add $MINIO_ALIAS $APP_USER --json) +ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.accessKey') +SECRET_KEY=$(echo $CREDENTIALS | jq -r '.secretKey') + +echo "Generated credentials:" +echo "Access Key: $ACCESS_KEY" +echo "Secret Key: $SECRET_KEY" + +# Create a bucket +mc mb $MINIO_ALIAS/app-bucket + +# Set a read-only policy +mc policy set download $MINIO_ALIAS/app-bucket +``` + +## Web Console Access + +Although administrative functionality has been removed, you can still access the MinIO Console via: + +```bash +# Launch the MinIO Console (if installed separately) +mc admin console myminio +``` + +Alternatively, specify the console address when starting the MinIO server: + +```bash +minio server /data --console-address ":9001" +``` + +## Summary + +Managing the new MinIO relies entirely on the `mc` command-line tool: + +1. **Install the mc client** +2. **Configure the server alias** +3. **Use `mc admin` commands for user, permission, and bucket management** +4. **Generate AccessKeys/SecretKeys via `mc admin user svcacct`** + +While this approach requires command-line operations, it provides more powerful and flexible management capabilities, particularly suited for automated deployment and script-based management. diff --git a/docs/en-gb/blog/contents/mybatis-flex.md b/docs/en-gb/blog/contents/mybatis-flex.md new file mode 100644 index 0000000..a5a7a09 --- /dev/null +++ b/docs/en-gb/blog/contents/mybatis-flex.md @@ -0,0 +1,31 @@ +--- +title: MyBatis Flex +tags: + - java + - mybatis + - spring-boot + - orm + - framework +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +> This article only aims at using `MyBatis Flex` in Spring Boot 3 + +## Installation and Configuration + +### Installation + +Add the following codes to `libs.versions.toml` in `gradle` : + +```toml +[versions] +mybatisFlexVersion = "X.Y.Z" +hikariVersion = "X.Y.Z" + +[libraries] +hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariVersion" } +mybatisFlex-processor = { group = "com.mybatis-flex", name = "mybatis-flex-processor", version.ref = "mybatisFlexVersion" } +mybatisFlex-starter = { group = "com.mybatis-flex", name = "mybatis-flex-spring-boot3-starter", version.ref = "mybatisFlexVersion" } +``` diff --git a/docs/en-gb/blog/contents/postgresql-zhparser-fuzzy-search.md b/docs/en-gb/blog/contents/postgresql-zhparser-fuzzy-search.md new file mode 100644 index 0000000..fca75a6 --- /dev/null +++ b/docs/en-gb/blog/contents/postgresql-zhparser-fuzzy-search.md @@ -0,0 +1,212 @@ +--- +title: Accelerating Fuzzy Search in PostgreSQL with Tokenisation +tags: + - postgresql + - zhparser + - full-text-search + - performance +author: + name: Siu Jam Oh + email: jamo.siu@gmail.com +--- + +## Background and Challenges + +As our business data surpassed **2 million rows**, traditional `LIKE '%keyword%'` fuzzy queries triggered frequent database I/O alerts, with query response times degrading from milliseconds to seconds. To improve search efficiency and support Chinese semantics, we decided to introduce the `zhparser` extension for full-text search. + +## Evolution Path and Environment Adaptation + +This implementation went through four key phases, each addressing distinct technical challenges: + +### CentOS 7.9 VM (Feasibility Validation) + +- **Goal**: Validate the compatibility of `SCWS` + `zhparser` on older systems. +- **Core Action**: Manually compiled `postgresql-16.2` from source in a CentOS 7.9 environment, got the extension working end-to-end. + +**Conclusion**: Confirmed significant performance improvements from the tokenisation approach for Chinese search. + +### Local Docker Container (Containerisation Exploration) + +- **Goal**: Initial testing in a complete local system. +- **Core Action**: Injected binary `.so` files via `docker cp`, resolved `ldconfig` dynamic library path visibility issues. +- **Discovery**: Identified that **missing dictionary files** cause tokenisation to degrade into single-character (particle-level) tokenisation — a critical failure point. + +### EulerOS 2.0 Test Server (Self-Compiled Environment Adaptation) + +- **Goal**: Adapt to the production OS architecture (x86_64) and self-compiled PostgreSQL installation. +- **Core Issue**: Resolved `libscws.so.1` loading errors. +- **Key Solutions**: + - Ensured the `postgres` runtime user has access permissions to `/usr/local/scws/lib`. + - Modified `systemd` service environment variables or created `/usr/lib64` symlinks to force refresh library search paths. + +### Production Deployment Preparation (Final Tuning) + +- **Goal**: Ensure query stability at 2M+ data volume. +- **Optimisation**: Addressed cases where non-semantic fragments (e.g., "古唐合") returned no results by establishing a "full-text search first + `pg_trgm` index assist" degraded query strategy. + +## Core Installation and Configuration Steps (Self-Compiled Environments) + +### Installing the `SCWS` Tokenisation Engine + +SCWS is the underlying core dependency of `zhparser` and must be installed first. + +1. **Download and Extract**: Download the source package (e.g., `scws-1.2.3`). +2. **Compile and Install**: + + ```bash + ./configure --prefix=/usr/local/scws + make && make install + ``` + +3. **Verify the Library**: Ensure `/usr/local/scws/lib/libscws.so.1` exists. + +### Compiling and Installing `zhparser` + +This step requires `pg_config` from the self-compiled PostgreSQL installation. + +1. **Get the Source**: Clone the `zhparser` project from GitHub. +2. **Compile with Specified Path**: + + ```bash + # Ensure pg_config is in PATH, or specify manually + make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config + make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config install + ``` + + *Note: The `install` step automatically places `zhparser.so` into PG's `pkglibdir` and extension scripts into the `extension` directory.* + +### Resolving Dynamic Library Dependencies + +1. **Refresh System Cache**: + + ```bash + echo "/usr/local/scws/lib" > /etc/ld.so.conf.d/scws.conf + ldconfig + ``` + +2. **Permission Check**: Ensure the OS user running `postgres` has `rx` permission on `/usr/local/scws/lib`. +3. **Force Symlink (Alternative)**: If `ldconfig` fails, symlink the library file to `/usr/lib64`. + +### Restarting the Database + +After modifying system shared library configuration, the PostgreSQL process must be restarted to reload environment variables and linked libraries. + +```bash +## Restart using pg_ctl (paths may vary for self-compiled installations) +/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart + +## Or restart via systemd (if registered as a service) +systemctl restart postgresql +``` + +### Database-Level Initialisation + +Connect to `psql` and run the logical configuration: + +```sql +-- Create the extension +CREATE EXTENSION zhparser; + +-- Create a full-text search configuration and bind the tokeniser +CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); + +-- Add token mappings +ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l,t,b WITH simple; + +-- [Optional] Specify the dictionary path for self-compiled installations +-- ALTER DATABASE postgres SET zhparser.dict_path = '/usr/local/scws/etc/dict.utf8.xdb'; +``` + +## Performance Analysis and Pitfalls + +### Index Performance Bottleneck Analysis + +During testing, it was found that even exact queries suffered from `Bitmap Index Scan` due to an improperly designed composite index (with `create_time` as the leading column), resulting in query times as high as **482 ms**. + +- **Improvement**: Created single-column **B-tree** indexes on frequently searched columns, reducing response time to under **10 ms** with `Index Scan`. + +### GIN Index and Non-Semantic Matching + +- **Cross-Word Truncation**: The tokenisation engine is semantic-based, so truncated strings like "古唐合" may fail to match with `@@` due to tokenisation boundaries. +- **Mitigation Strategy**: Adopt a "waterfall search" approach. Full-text search (FTS) first; if the result set is empty, automatically degrade to `LIKE` fuzzy queries, assisted by `pg_trgm` indexing. + +## Final Deployment Strategy: Dual-Track Parallel Retrieval + +After analysing the data, we found that approximately 1.24% of account names contain non-standard Simplified Chinese characters, and some company names in the database are unusual enough to cause search failures. We adopted a "stepwise degradation" strategy: + +- **Step 1: Full-Text Search (Fast Track)**: Use GIN index for `@@` matching. +- **Step 2: Result Evaluation**: If the result set is empty, check whether the search term contains letters or suspected Traditional Chinese characters. +- **Step 3: Fuzzy Fallback (Safe Fallback)**: Execute `LIKE '%keyword%'`. Although slower, since this serves as a "gap-fill" logic triggered only ~1% of the time, it won't impose overall system pressure. + +## Search Optimisation: Integrating a Custom Business Lexicon + +To address issues like company brand names being incorrectly segmented by full-text search (e.g., "元一" being split into a numeral and a quantifier), we built an automated maintenance pipeline from data extraction to index rebuild. + +### Lexicon Extraction and Preprocessing + +Leverage the structural parsing capabilities of **`companynameparser`** to strip region names and industry suffixes, and use **`jieba`** for semantic validation to ensure core brand name integrity: + +- **Extraction Logic**: Traverse all `buyer_unique_name` values via a Python script, extracting the core `brand` field. +- **Weight Compensation**: For words prone to fragmentation (e.g., those containing "元", "一", "三"), manually boost TF (term frequency weight) to **50.0–60.0** to ensure their priority overrides built-in quantifier rules. +- **Output Specification**: Produce SCWS-compliant 4-field `UTF-8` text (WORD, TF, IDF, ATTR). Use tab `\t` separators to avoid parsing anomalies. + +### Lexicon Compilation and Deployment + +:::tip +Users compiling `xdb` binary dictionary files on Windows can visit OnixByte’s [GitHub](https://github.com/onixbyte/scws/releases/tag/1.2.3) or [GitLab](https://git.onixbyte.cn/onixbyte/scws/-/releases/1.2.3) pages to download the native scws command-line tool for Windows, pre-compiled using MingW. +::: + +Convert the text dictionary to SCWS's efficient binary format (XDB): + +1. **Compile the Binary Dictionary**: + + ```bash + # Use scws-gen-dict to generate an encrypted binary lexicon + /usr/local/scws/bin/scws-gen-dict -i custom_company.txt -o /usr/local/scws/etc/custom_company.xdb -c utf8 + ``` + +2. **File Distribution and Permissions**: Move the generated `.xdb` file to the tokenisation data directory and ensure the `postgres` user has read permission: + + ```bash + cp custom_company.xdb /usr/local/pgsql/share/tsearch_data/ + chown postgres:postgres /usr/local/pgsql/share/tsearch_data/custom_company.xdb + ``` + +### Database Parameter Configuration + +Modify `postgresql.conf` to force-load `zhparser` and its custom extension lexicon: + +```plain text +## Preload the extension library (requires restart to take effect) +shared_preload_libraries = 'zhparser' + +## Load custom external dictionaries (use paths relative to tsearch_data) +zhparser.extra_dicts = 'custom_company.xdb' +``` + +### Hot Index Rebuild and Verification + +Since tokenisation rules have changed, existing data must be semantically synchronised via index rebuild: + +**Physically Restart the Service**: + +```bash +su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart" +``` + +**Online Index Rebuild**: Use the `CONCURRENTLY` keyword to refresh the GIN index without blocking DML operations on 400K rows: + +```bash +REINDEX INDEX CONCURRENTLY index_name; +``` + +**Tokenisation Effectiveness Verification**: + +```sql +-- Expected part-of-speech should show as n (noun), not x (unknown) +SELECT * FROM ts_debug('chinese', '元一能源'); +``` + +**Optimisation Notes:** +- **Explicit Weight Compensation**: This is the key technique that resolved the "元一" tokenisation failure (shown as `x`). +- **Distinguish Restart from Reload**: `shared_preload_libraries` must be activated via `restart`, not a simple reload. diff --git a/docs/en-gb/blog/contents/quartile-method.md b/docs/en-gb/blog/contents/quartile-method.md new file mode 100644 index 0000000..dafad33 --- /dev/null +++ b/docs/en-gb/blog/contents/quartile-method.md @@ -0,0 +1,22 @@ +--- +title: The Quartile Method +tags: + - statistics + - algorithm + - data-analysis + - math +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +The quartile method is a commonly used statistical technique primarily employed for data analysis and presentation. The method divides a data set into four equal parts, each containing one quarter of the data. The key statistics include the first quartile (Q1), second quartile (Q2, the median), and third quartile (Q3). + +- The first quartile (`Q1`), also known as the lower quartile, is the value at the 25th percentile of a data set sorted in ascending order. +- The second quartile (`Q2`), also known as the median, is the value at the 50th percentile of a data set sorted in ascending order. +- The third quartile (`Q3`), also known as the upper quartile, is the value at the 75th percentile of a data set sorted in ascending order. +- The interquartile range (`IQR`) is the difference between the third quartile and the first quartile, used to measure the dispersion of the middle 50% of data. The formula is IQR = Q3 - Q1. The IQR is commonly used in constructing box plots, an effective way to describe data distribution, particularly useful for identifying outliers. + +Upper bound = Q3 + 1.5 × IQR. + +Lower bound = Q1 - 1.5 × IQR. diff --git a/docs/en-gb/blog/contents/setup-ldap-service.md b/docs/en-gb/blog/contents/setup-ldap-service.md new file mode 100644 index 0000000..43c789b --- /dev/null +++ b/docs/en-gb/blog/contents/setup-ldap-service.md @@ -0,0 +1,140 @@ +--- +title: Setting Up an LDAP Service +tags: + - ldap + - openldap + - authentication + - linux + - devops +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +Setting up an LDAP (Lightweight Directory Access Protocol) server is a core step in implementing enterprise-level **centralised identity authentication** and **permission management**. The most commonly used open-source implementation is **OpenLDAP**. + +Below are detailed steps for setting up an OpenLDAP server on Debian/Ubuntu-based Linux systems. + +## Environment Preparation and Installation + +Before starting, ensure your system packages are up to date and the hostname is properly configured. + +```bash +## Update the system +sudo apt update && sudo apt upgrade -y + +## Install OpenLDAP and management tools +## slapd is the daemon, ldap-utils is the command-line client tool +sudo apt install slapd ldap-utils -y +``` + +> During installation, you will be prompted to set the **LDAP administrator password**. Be sure to remember this password — it will be used frequently during configuration. + +## Configuring the OpenLDAP Server + +Although the installation performs some initial setup, we typically need to customise the configuration for a specific domain (e.g., `example.com`). + +### Reconfigure slapd + +Run the following command to enter the interactive configuration interface: + +```bash +sudo dpkg-reconfigure slapd +``` + +**Configuration recommendations**: + +1. **Omit OpenLDAP server configuration?** Choose **No**. +2. **DNS domain name:** Enter your domain (e.g., `centre.example.com`). This determines your Base DN, such as `dc=centre,dc=example,dc=com`. +3. **Organization name:** Enter your organisation name. +4. **Administrator password:** Enter the administrator password you set earlier. +5. **Database backend:** **MDB** is recommended. +6. **Remove database when slapd is purged?** Choose **No**. +7. **Move old database?** Choose **Yes**. + +## Understanding the LDAP Hierarchy + +LDAP data is stored in a **tree structure**. To facilitate management, we typically create two "Organisational Units" (OUs): one for users (People) and one for groups (Groups). + +## Creating Organisational Units (OUs) + +In LDAP, we use **LDIF** (LDAP Data Interchange Format) files to add or modify directory entries. + +Create a file named `base.ldif`: + +```text +## Create the users organisational unit +dn: ou=people,dc=centre,dc=example,dc=com +objectClass: organizationalUnit +ou: people + +## Create the groups organisational unit +dn: ou=groups,dc=centre,dc=example,dc=com +objectClass: organizationalUnit +ou: groups +``` + +**Import the data:** + +```bash +## Import the LDIF file into the database as the administrator +ldapadd -x -D "cn=admin,dc=centre,dc=example,dc=com" -W -f base.ldif +``` + +## Adding Users and Groups + +Create a file named `groups.ldif` to define a new group: + +```text +## Define a new group +dn: cn=developers,ou=groups,dc=centre,dc=example,dc=com +objectClass: inetOrgGroup +cn: developers +``` + +Create a file named `users.ldif` to define a new user: + +```text +## Define a new user +dn: uid=jbloggs,ou=people,dc=centre,dc=example,dc=com # The Distinguished Name (DN) of this entry. +objectClass: inetOrgPerson # Object class — inetOrgPerson represents an internet organisation person. It enables common contact attributes such as email, displayName, telephoneNumber, etc. +objectClass: posixAccount # Makes this entry compatible with Unix/Linux system accounts. With it, the user can log into Linux servers and has a UID, GID, and home directory. +objectClass: shadowAccount # Used to manage password ageing (expiry, change warnings, etc.), corresponding to /etc/shadow functionality in Linux. +uid: jbloggs # The user's login name (User ID). This is typically what you enter on the Linux login screen. +sn: Bloggs # Surname. Required by the inetOrgPerson class. +givenName: Joe # First name. +cn: Joe Bloggs # Common Name. The standard display name for an LDAP entry. +displayName: Joe Bloggs # The friendly name displayed in graphical interfaces or email clients. +uidNumber: 10000 # The user's numeric ID in the Linux system. +gidNumber: 5000 # The numeric ID of the user's primary group. +userPassword: {SSHA}password-hash-or-plaintext # The user's encrypted password. +homeDirectory: /home/jbloggs # The path to the user's home directory after logging into Linux. +loginShell: /bin/bash # The shell environment the user gets after logging in. +``` + +> If you plan to integrate **GitLab**, **Jenkins**, or a **VPN** later, they typically search the `uid` attribute to verify login names. + +### FAQ + +#### If the user doesn't need server login permissions, can the `objectClass: posixAccount` be removed? + +**Yes, it can be completely removed**, but you need to be aware of the "binding relationships" between attributes. + +If you only need this user for web application logins (e.g., GitLab, Jenkins, Wiki) or as an email contact, and they don't need to log into a Linux server via SSH or console, removing `posixAccount` is the more standard approach. + +When you remove `objectClass: posixAccount`, the following attributes **must also be deleted**, as they belong to that class's mandatory or optional attributes: + +- `uidNumber` +- `gidNumber` +- `homeDirectory` +- `loginShell` + +Additionally, `shadowAccount` is also typically associated with system logins — if you don't need to manage Linux password expiry policies, it can also be removed. + +## Follow-up Suggestions and Management Tools + +Command-line LDAP management can be cumbersome. The following tools are recommended for visual administration: + +- **phpLDAPAdmin**: A web-based management interface, ideal for quick onboarding. +- **Apache Directory Studio**: A powerful cross-platform desktop client, suitable for complex architecture design. +- **Security Hardening**: By default, LDAP transmits data in plain text. It is recommended to configure **LDAPS (LDAP over SSL/TLS)** to encrypt communication on port 636. diff --git a/docs/en-gb/blog/contents/use-json-in-mysql.md b/docs/en-gb/blog/contents/use-json-in-mysql.md new file mode 100644 index 0000000..b103dd4 --- /dev/null +++ b/docs/en-gb/blog/contents/use-json-in-mysql.md @@ -0,0 +1,89 @@ +--- +title: Using JSON in MySQL +tags: + - mysql + - json + - database +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +MySQL (since version 5.7) **does not directly support a data type called `jsonb`**. `jsonb` is a data type specific to PostgreSQL, which stores JSON data in a binary format with pre-parsing for faster access and manipulation during queries. + +However, MySQL's `JSON` data type is functionally and internally similar to PostgreSQL's `jsonb` in many respects, particularly when it comes to querying data. + +The `JSON` data type in MySQL was introduced in MySQL 5.7 and has the following characteristics: + +1. **Binary Storage**: Like PostgreSQL's `jsonb`, MySQL's `JSON` type data is stored in an **internal binary format** rather than as a plain text string. This makes reading and manipulating JSON data more efficient, as the database does not need to parse text-format JSON strings on every query. +2. **Automatic Validation**: When you insert or update a `JSON` column, MySQL automatically validates that its content is a valid JSON document. If not, it throws an error. +3. **Optimised Storage**: The binary format is also space-optimised, typically more compact than storing JSON in raw text format. + +MySQL provides a powerful set of functions and operators for querying and manipulating `JSON` data, very similar to what you'd expect from `jsonb`: + +1. **`->` (JSON Extract Operator)**: Extracts a value from a JSON document. It returns a JSON value. + + ```sql + SELECT my_json_column->'$.key' FROM my_table; + -- Example: extract the name property of a user object + -- Assuming my_json_column stores {'user': {'name': 'Alice'}} + SELECT json_data->'$.user.name' FROM my_table; + ``` + +2. **`->>` (JSON Unquote Operator)**: Extracts a value from a JSON document and **automatically unquotes it**, typically returning a scalar value (e.g., string, number). This is equivalent to `JSON_UNQUOTE(JSON_EXTRACT(...))`. + + ```sql + SELECT my_json_column->>'$.key' FROM my_table; + -- Example: extract the name property of a user object (returns the string 'Alice' directly) + SELECT json_data->>'$.user.name' FROM my_table; + ``` + +3. **`JSON_EXTRACT(json_doc, path, ...)`**: Explicitly extracts data from a JSON document. + + ```sql + SELECT JSON_EXTRACT(my_json_column, '$.key') FROM my_table; + ``` + +4. **`JSON_CONTAINS(json_doc, candidate, path)`**: Checks whether a JSON document contains a specified value. + + ```sql + -- Check whether the tags array contains 'backend' + -- Assuming my_json_column stores {'tags': ['frontend', 'backend']} + SELECT * FROM my_table WHERE JSON_CONTAINS(json_data->'$.tags', '"backend"'); + ``` + +5. **`JSON_SEARCH(json_doc, one_or_all, search_str, escape_char, path, ...)`**: Returns the path to a specified string within a JSON document. + + ```sql + -- Find the path to a value of 'test' + SELECT JSON_SEARCH(my_json_column, 'one', 'test') FROM my_table; + ``` + +6. **`JSON_TABLE(json_doc, path COLUMNS ... )` (MySQL 8.0 and later)**: A very powerful function that "expands" JSON data into relational rows and columns, ideal for complex queries and reporting. + + ```sql + -- Assuming json_data stores {'items': [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]} + SELECT * + FROM my_table, + JSON_TABLE(json_data, '$.items[*]' COLUMNS( + itemId INT PATH '$.id', + itemName VARCHAR(50) PATH '$.name' + )) AS jt; + ``` + +Like PostgreSQL's `jsonb`, efficient querying on JSON fields typically requires indexing. Since the content of JSON fields is dynamic, MySQL does not directly support creating traditional B-tree indexes on a specific internal path of a JSON field. However, you can achieve this through **Virtual Generated Columns**: + +1. **Create a Virtual Column**: Define a virtual column whose value is extracted from a specific path in the JSON field. + + ```sql + ALTER TABLE my_table + ADD COLUMN user_name VARCHAR(255) AS (json_data->>'$.user.name') VIRTUAL; + ``` + +2. **Create an Index on the Virtual Column**: This way, when you query `WHERE json_data->>'$.user.name' = 'Alice'`, the MySQL optimiser can use the `idx_user_name` index, significantly improving query performance. + + ```sql + CREATE INDEX idx_user_name ON my_table (user_name); + ``` + +Although MySQL does not have the exact name `jsonb`, its `JSON` data type provides highly similar functionality: binary storage optimisation, automatic validation, and rich query operators and functions. By combining virtual columns with indexes, MySQL can deliver query performance and flexibility comparable to PostgreSQL's `jsonb` when working with JSON data. diff --git a/docs/en-gb/blog/contents/version-control-and-code-review.md b/docs/en-gb/blog/contents/version-control-and-code-review.md new file mode 100644 index 0000000..335a473 --- /dev/null +++ b/docs/en-gb/blog/contents/version-control-and-code-review.md @@ -0,0 +1,38 @@ +--- +title: Version Control and Code Review +tags: + - git + - code-review + - best-practice + - workflow +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## GitFlow Workflow + +Version control will use the GitFlow branching model, consisting of `main`, `develop`, `feature`, `release`, and +`hotfix` branches. + +- `main`: Production-ready code. Only `release` and `hotfix` branches are merged into `main`. +- `develop`: Integration branch for upcoming features. +- `feature/*`: Branches for new features, branched off `develop`. +- `release/*`: Branches for preparing new production releases, branched off `develop`. +- `hotfix/*`: Branches for urgent production bug fixes, branched off `main`. + +## Pull Requests/Merge Requests + +All code changes (except direct pushes to feature branches) must be submitted via pull requests. + +## Code Review + +- Each pull request must be reviewed by at least one other developer. +- Reviewers are responsible for checking compliance with these coding standards, code quality, logical correctness, and + test coverage. +- IntelliJ IDEA's integrated code analysis tools should be run locally before creating a PR. + +## Commit Messages + +Write clear, concise, and descriptive commit messages that explain what was changed and why. If possible, follow the +Conventional Commits format (e.g., `feat: add user registration endpoint`). diff --git a/docs/zh-hans/blog/contents/cooking-seasoning-guide.md b/docs/zh-hans/blog/contents/cooking-seasoning-guide.md new file mode 100644 index 0000000..2497008 --- /dev/null +++ b/docs/zh-hans/blog/contents/cooking-seasoning-guide.md @@ -0,0 +1,31 @@ +--- +title: 炒菜调料指南 +tags: + - cooking + - cheatsheet +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## 调料投放时机 + +| 调料类型 | 放调料时机 | 适用菜品 | 作用 | +|--------------|----------|----------------|---------------------| +| **盐** | 最后放 | 菜叶子 | 防止菜叶变软出水,保证口感干爽脆嫩 | +| | 中间放 | 土豆丝、豆角、蒜苔等 | 更好入味 | +| **味精、生抽、蚝油** | 偏后放 | 各类需要增鲜提香的菜品 | 增鲜提香,避免高温破坏鲜味和香味 | +| **料酒** | 和食材一起放 | 需要去腥的食材 | 焯水时去腥效果更好 | +| | 锅温最高的时候放 | 需要炒菜去腥的食材 | 炒菜时高温促进酒气挥发,炝锅去腥效果好 | +| **醋** | 提前放 | 酸辣土豆丝、酸辣大白菜 | 使土豆丝变脆更入味 | +| | 出锅前锅边醋 | 辣椒炒肉等需要猛火爆炒类菜品 | 大火炝锅增香 | +| **老抽** | 炒半熟后放 | 需要上色的菜品 | 达到最佳上色效果 | + +## 快速参考 + +| 调料 | 投放时机 | 作用 | 示例 | +|-----------------|----------------|------------------------|------------------------| +| **盐** | 出锅前 10 - 15 秒放 | 避免提前放盐导致菜叶出水、变软,保持脆嫩口感 | 炒生菜、菠菜时,关火前撒盐翻炒均匀 | +| **蒜末 / 姜末** | 热油后先放 | 爆香提味,激发香味 | 油热后下蒜末炝锅,再放青菜翻炒 | +| **生抽 / 蚝油**(可选) | 盐之后放 | 增鲜提味,少量即可,避免盖过蔬菜原味 | 炒上海青时,加盐后滴 1 - 2 滴生抽翻炒 | +| **食用油** | 热锅热油 | 油热后快速炒,锁住水分 | 大火快炒时,油稍多一点,油温要高 | diff --git a/docs/zh-hans/blog/contents/docker-deployment-standards.md b/docs/zh-hans/blog/contents/docker-deployment-standards.md new file mode 100644 index 0000000..6ece16f --- /dev/null +++ b/docs/zh-hans/blog/contents/docker-deployment-standards.md @@ -0,0 +1,16 @@ +--- +title: Docker 部署规范 +tags: + - docker + - deployment + - standards + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +- **Dockerfiles**: 为应用程序提供 `Dockerfile`,实现容器化部署。 +- **轻量级镜像**: 通过使用适当的基础镜像和多阶段构建,力求实现轻量级 Docker 镜像。 +- **配置**: 确保环境特定配置(例如,数据库连接字符串、外部服务 URL)通过注入到 Docker 容器中的环境变量进行管理。 +- **日志记录**: 配置容器化日志记录,将输出发送到 `stdout` 和 `stderr`,以便日志聚合系统轻松收集。 diff --git a/docs/zh-hans/blog/contents/ecmascript-2025-syntax-sugar.md b/docs/zh-hans/blog/contents/ecmascript-2025-syntax-sugar.md new file mode 100644 index 0000000..066a1f2 --- /dev/null +++ b/docs/zh-hans/blog/contents/ecmascript-2025-syntax-sugar.md @@ -0,0 +1,55 @@ +--- +title: ECMAScript 2025 语法糖大全 +tags: + - javascript + - ecmascript + - pattern-matching + - frontend +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## 模式匹配 + +### 传统写法 + +```javascript +function processResponse(response) { + if (response.status === 200 && response.data) { + return { success: true, data: response.data }; + } else if (response.status === 404) { + return { success: false, error: 'Not found' }; + } else if (response.status >= 500) { + return { success: false, error: 'Server error' }; + } else { + return { success: false, error: 'Unknown error' }; + } +} +``` + +### 模式匹配写法 + +```javascript +function processResponse(response) { + return match (response) { + when ({ status: 200, data }) -> ({ success: true, data }) + when ({ status: 404 }) -> ({ success: false, error: 'Not found' }) + when ({ status: status if status >= 500 }) -> ({ success: false, error: 'Server error' }) + default -> ({ success: false, error: 'Unknown error' }) + }; +} +``` + +### 数组长度分支也能优雅处理 + +```javascript +function handleArray(arr) { + return match (arr) { + when ([]) -> "Empty array" + when ([first]) -> `Only one element: ${first}` + when ([first, second]) -> `Two elements: ${first}, ${second}` + when ([first, ...rest]) -> `First element: ${first}, others: ${rest.length} items` + }; +} +``` diff --git a/docs/zh-hans/blog/contents/email-like-a-boss.md b/docs/zh-hans/blog/contents/email-like-a-boss.md new file mode 100644 index 0000000..1ebac29 --- /dev/null +++ b/docs/zh-hans/blog/contents/email-like-a-boss.md @@ -0,0 +1,40 @@ +--- +title: 像老板一样写邮件 +tags: + - communication + - email + - soft-skill + - career + - productivity +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +工作中的邮件沟通不仅仅是传递信息,更是建立信任、塑造专业形象的过程。同样一件事情,换一种表达方式,给人的印象可能是天壤之别。 + +以下整理了 9 个常见邮件场景中「低姿态表达」与「高阶表达」的对比,让你的邮件读起来更自信、更专业。 + +## 场景速查表 + +| 场景 | ❌ 别用(听起来……) | ✅ 改用(听起来……) | 为什么有效 | +|----------|--------------------------------------------------------------|----------------------------------------------------------|-----------------------------------------------| +| 回复晚了 | **Sorry for the delay**
(抱歉回复晚了) | **Thanks for your patience**
(感谢你的耐心等待) | 前者在道歉,后者在感谢——从「我做错了」转变为「你太好了」,既表达了歉意又让对方感到被尊重 | +| 约时间 | **What works best for you?**
(你什么时候方便?) | **Could you do …?**
(你可以在某某时间吗?) | 前者把决定权完全推给对方,后者主动给出选项,减少来回沟通的成本 | +| 帮助了别人之后 | **No problem / No worries**
(没问题 / 别担心) | **Always happy to help**
(乐于效劳) | 前者暗示这件事"本来可能是个问题",后者传递出你乐在其中、下次还愿意帮 | +| 提出建议 | **I think maybe we should …**
(我觉得也许我们应该……) | **It'd be best if we …**
(最好是……) | 前者透着犹豫和不自信,后者直接给出判断——像有经验的人在做决策 | +| 文字沟通费劲 | **_花 30 分钟反复修改一封邮件_** | **It'd be easier to discuss in person**
(当面/电话聊会更轻松) | 意识到沟通渠道本身就是问题,果断切换方式反而是效率最高的选择 | +| 确认对方是否理解 | **Hopefully that makes sense?**
(希望我说清楚了?) | **Let me know if you have questions**
(有问题随时问我) | 前者在怀疑自己的表达能力,后者把责任合理地分担给对方,姿态更稳 | +| 催进度 | **Just wanted to check in**
(只是想确认一下) | **When can I expect an update?**
(我什么时候可以收到更新?) | 前者显得小心翼翼不敢打扰,后者直接问时间节点——清楚、礼貌、专业 | +| 犯了小错 | **Ahh sorry my bad totally missed that**
(啊啊抱歉我的错,完全漏掉了) | **Thanks for letting me know**
(感谢提醒) | 前者过度道歉反而让对方尴尬,后者承认了问题但把焦点放在了"解决"上 | +| 需要早退 | **Could I possibly leave early?**
(我能提前走吗?) | **I will need to leave at …**
(我需要在某某时间离开) | 前者在请求批准,后者在陈述安排——你是专业人士,不需要为合理需求道歉 | + +## 核心原则 + +写好邮件的关键不在于词汇量,而在于**姿态**。时刻提醒自己三条规则: + +1. **用陈述代替请求** —— 「我需要……」比「我能不能……」更有分量 +2. **用感谢代替道歉** —— 把焦点从「我的过失」转移到「对方的支持」 +3. **用明确代替模糊** —— 给出具体的时间、选项、行动项,而不是把球踢回给对方 + +下次打开邮箱,花 5 秒想一想:这句话能不能说得更像一个做决定的人? diff --git a/docs/zh-hans/blog/contents/fix-macos-monterey-sleep-wake.md b/docs/zh-hans/blog/contents/fix-macos-monterey-sleep-wake.md new file mode 100644 index 0000000..eefd685 --- /dev/null +++ b/docs/zh-hans/blog/contents/fix-macos-monterey-sleep-wake.md @@ -0,0 +1,26 @@ +--- +title: 解决 macOS Monterey+ 设备休眠频繁自动唤醒的问题 +tags: + - macos + - sleep + - power-management + - bug +--- + +> 本文由 **落格博客** 原创撰写:[落格博客](https://www.logcg.com/) » [升级 macOS Monterey 后设备休眠半夜频繁唤醒问题](https://www.logcg.com/archives/3528.html) + +更新到了 macOS Monterey,半夜总会被屏幕照醒,就觉得很诡异,以前也有过,但都是有通知的时候才会点亮屏幕,现在是没有任何理由的自己点亮,硬件还是那个硬件,那就应该是软件的锅了。 + +在网上查了一圈,先是找到了[苹果官方的教程](https://support.apple.com/zh-cn/guide/mac-help/mchlp2995/mac)。写的很详细,但显然是没有任何用处的。 + +于是继续深挖,还真找到了原因,使用命令 **`pmset -g log | grep DarkWake`** 就可以看到,在你睡觉的过程中,你的 Mac 并没有歇着…… + +看看输出,这几个比较典型,大部分都是这样的,一个 DarkWake, 一个 Wake, 两个仅仅相连,那么问题就出来了, DarkWake 是给电脑在后台唤醒来更新数据的,但不知怎么的,外设被触发,从而导致了全局唤醒。 + +总之,我并不想要这个功能, 就那个 PowerNap,对我来说,我更希望它能给我多省点电,于是大概解决思路就这么多:关掉网络访问唤醒,关掉 PowerNap……不过问题来了,在 m1 设备上,其实是没有 PowerNap 选项的……(显然,苹果对自己的续航很有信心,但他们忽略了Bug的威力) + +于是对于 PowerNap 这个功能来说,我们只能从命令行下手,首先使用 **`pmset -g`** 命令查看当前状态,找到 **`powernap`** 的值,如果不是 0 ,说明是启用的状态,使用命令 **`sudo pmset -a powernap 0`** 关掉它。 + +同时,还有另外一个 **`tcpkeepalive`** ,这个默认应该也不是 0 ,也要关掉,它决定了你的 rmbp 在休眠时是否要保持 tcp 连接。 **`sudo pmset -a tcpkeepalive 0`** ,执行这条命令会导致终端提示:***Warning: This option disables TCP Keep Alive mechanism when system is sleeping. This will result in some critical features like 'Find My Mac' not to function properly.*** + +大概是说关了的话某些功能会受到限制,其实就是系统功能不能休眠时联网了,我相信真有人偷了你 Mac,它也连不上网的。 diff --git a/docs/zh-hans/blog/contents/font-size-conversion-table.md b/docs/zh-hans/blog/contents/font-size-conversion-table.md new file mode 100644 index 0000000..35cd1b4 --- /dev/null +++ b/docs/zh-hans/blog/contents/font-size-conversion-table.md @@ -0,0 +1,30 @@ +--- +title: 字号榜值转换表 +tags: + - typography + - font + - cheatsheet + - design +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +| 中文字号 | 英文字号(磅)/pt | 毫米/mm | 像素/px | +|------|------------|-------|-------| +| 初号 | 42 | 14.82 | 56 | +| 小初 | 36 | 12.7 | 48 | +| 一号 | 26 | 9.17 | 34.7 | +| 小一 | 24 | 8.47 | 32 | +| 二号 | 22 | 7.76 | 29.3 | +| 小二 | 18 | 6.35 | 24 | +| 三号 | 16 | 5.64 | 21.3 | +| 小三 | 15 | 5.29 | 20 | +| 四号 | 14 | 4.94 | 18.7 | +| 小四 | 12 | 4.23 | 16 | +| 五号 | 10.5 | 3.7 | 14 | +| 小五 | 9 | 3.18 | 12 | +| 六号 | 7.5 | 2.56 | 10 | +| 小六 | 6.5 | 2.29 | 8.7 | +| 七号 | 5.5 | 1.94 | 7.3 | +| 八号 | 5 | 1.76 | 6.7 | diff --git a/docs/zh-hans/blog/contents/frontend-development-standards.md b/docs/zh-hans/blog/contents/frontend-development-standards.md new file mode 100644 index 0000000..1c682c7 --- /dev/null +++ b/docs/zh-hans/blog/contents/frontend-development-standards.md @@ -0,0 +1,42 @@ +--- +title: 前端开发规范 +tags: + - frontend + - react + - standards + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## 依赖管理 (pnpm) + +- **严格性**: 利用 pnpm 的严格依赖管理,以确保更确定性的 `node_modules` 结构和高效的磁盘空间使用。 +- **工作区**: 如果使用 monorepo 方法,配置 pnpm 工作区以简化多个前端包的依赖管理。 +- **审计**: 定期使用 `pnpm audit` 审计前端依赖项是否存在已知漏洞。 + +## API 通信 (Axios) + +- **Axios 实例**: 创建一个集中的 Axios 实例用于 API 调用,以应用通用配置(基本 URL、请求头、拦截器)。 +- **拦截器**: 使用 Axios 拦截器进行以下操作: + - 向传出请求添加认证令牌。 + - 处理全局错误响应(例如,显示 `401 Unauthorized` 的通知)。 + - 在开发环境中记录请求/响应。 +- **错误处理**: 在 Axios 拦截器或自定义工具函数中集中处理 API 错误,以提供一致的用户反馈。 + +## React 与组件标准 + +- **函数组件与 Hooks**: 对于新开发,优先使用带有 React Hooks 的函数组件,而非类组件。 +- **Props**: + - 为组件 props 定义 `interface` 或 `type` 以确保类型安全。 + - 在组件入口处解构 props 以提高清晰度。 +- **状态管理 (Redux)**: + - 使用 Redux Toolkit 进行高效且减少样板代码的 Redux 开发。 + - 使用 `createSlice` 将 Redux 逻辑组织成"切片"(特定功能的 reducer、action 和 selector)。 + - 遵循"ducks"模式或"slices"方法来协同定位 Redux 逻辑。 +- **组件组合**: 将复杂的 UI 分解为更小、可重用且职责单一的组件。 +- **Ant Design**: + - 利用 Ant Design 组件以实现一致的 UI/UX。 + - 如果需要,使用 CSS-in-JS 解决方案在整个应用程序中一致地定制 Ant Design 主题和样式。 +- **可访问性**: 从一开始就设计和实现考虑到 Web 可访问性 (a11y) 的组件。 diff --git a/docs/zh-hans/blog/contents/frontend-tips-and-solutions.md b/docs/zh-hans/blog/contents/frontend-tips-and-solutions.md new file mode 100644 index 0000000..dc4a760 --- /dev/null +++ b/docs/zh-hans/blog/contents/frontend-tips-and-solutions.md @@ -0,0 +1,75 @@ +--- +title: 前端开发技巧与方案速查手册 +tags: + - frontend + - react + - ant-design + - tailwind + - best-practice +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## 表单组件的解耦 + +将表单组件拆分为 UI 组件和逻辑组件,并在逻辑组件中使用 UI 组件渲染样式。 + +## React 入口组件顺序 + +```typescript +import { StrictMode } from "react" +import { createRoot } from "react-dom/client" +import { Provider as ReduxProvider } from "react-redux" +import { PersistGate } from "redux-persist/integration/react" +import { RouterProvider } from "react-router" +import { App as AntApp, ConfigProvider as AntConfigProvider } from "antd" +import { StyleProvider } from "@ant-design/cssinjs" +import AntSimplifiedChinese from "antd/locale/zh_CN" +import "./index.css" +import "@/config/dayjs-config" +import store, { persistor } from "@/store" +import router from "@/router" + +createRoot(document.getElementById("root")!).render( + + + + {/* 注意:StyleProvider 必须是 ConfigProvider 的父组件!!! */} + + + + + + + + + + +) +``` + +## React 中 Ant Design 与 Tailwind 整合 + +> 参考 [https://github.com/ant-design/ant-design/discussions/56152](https://github.com/ant-design/ant-design/discussions/56152) + +```css +/* index.css */ + +@layer theme, base, antd, components, utilities; +@import "tailwindcss"; +``` + +```typescript + + + + + + + +``` diff --git a/docs/zh-hans/blog/contents/general-development-standards.md b/docs/zh-hans/blog/contents/general-development-standards.md new file mode 100644 index 0000000..a117542 --- /dev/null +++ b/docs/zh-hans/blog/contents/general-development-standards.md @@ -0,0 +1,23 @@ +--- +title: 应用开发通用规范 +tags: + - standards + - best-practice + - engineering +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## 引言 + +本文件旨在概述应用程序开发的编码标准和最佳实践。遵守这些指导方针可确保所有项目的代码质量、可维护性和一致性。 + +## 通用原则 + +- **清晰性与可读性**: 代码必须易于阅读和理解。优先选择清晰、自文档化的代码,而非巧妙、简洁但晦涩的解决方案。 +- **一致性**: 在整个项目中保持一致的编码风格、命名约定和架构模式。 +- **模块化**: 设计组件时应实现松耦合和高内聚,以促进重用性和更简单的测试。 +- **可测试性**: 编写本质上可测试的代码。 +- **安全先行设计**: 在开发的每个阶段都将安全考虑融入其中。 +- **性能意识**: 对关键代码段和 API 端点考虑其性能影响。 diff --git a/docs/zh-hans/blog/contents/google-code-review-standards.md b/docs/zh-hans/blog/contents/google-code-review-standards.md new file mode 100644 index 0000000..c0548a4 --- /dev/null +++ b/docs/zh-hans/blog/contents/google-code-review-standards.md @@ -0,0 +1,98 @@ +--- +title: Google 代码审查标准 +tags: + - code-review + - best-practice + - engineering + - google +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +代码审查是开发过程中的一个环节,顾名思义,代码审查需要一位或多位开发人员审查另一位开发人员(即代码的作者)编写的代码,以确保: + +- 代码没有任何错误,没有 bug,也没有问题; +- 代码符合质量与样式指南的要求和标准; +- 代码完成了所有预期功能; +- 合并代码后,代码库仍然能够正常运行,且达到更好的状态。 + +这就是为什么代码审查是软件开发的重要环节的原因。代码审查者担当着把关者的职责,负责决定这些代码是否能够成为代码库的一部分并进入生产环境。 + +## 这些代码能够提升系统整体的运行状况 + +每次代码变更(拉取请求)都能够提升系统整体的运行状况。重点在于,即便是很小的改进,合并代码后都会提升软件或代码库的运行状况。 + +## 快速审查代码,并给出积极地响应和反馈 + +首先也是最重要的一点,不可延误代码的合并。世上没有完美的代码。如果代码可以提升系统的整体运行状况,则应该立即交付这些代码。 + +> 关键在于,世上没有完美的代码,只有更好的代码。—— Google 工程实践文档 + +如果手头没有紧急任务,那么请在代码提交上来后立即进行审查。响应拉取请求的时间最长不得超过一个工作日。一天之内,应针对一次拉取请求完成多轮的部分或完整的代码审查。 + +## 在代码审查的过程中开展教育和启发 + +在代码审查的过程中,应尽可能通过共享知识和经验提供指导。 + +## 审查代码应遵循标准 + +请始终牢记,样式指南、编程标准以及相关的文档应该作为代码审查的绝对权威。例如,制表符与空格的使用应保持一致,此时你可以引用编程约定。 + +> 如果你选用的是 Java,那么以下文章可能会有所帮助,文中总结了大型科技公司 Java 编程的最佳实践:《[Java 编程最佳实践摘要](https://rhamedy.medium.com/a-short-summary-of-java-coding-best-practices-31283d0167d3)》 + +## 解决代码审查冲突 + +解决代码审查冲突时,应遵循样式指南以及编程标准中商定的最佳实践,并征求其他拥有更多产品领域知识和经验的人的建议。 + +如果你的意见是可选或不怎么重要的,请在注释中说明,然后由作者来决定是解决还是略过。 + +作为代码审查者,在没有样式指南或编程标准的情况下,你至少可以建议此次代码变更与其余代码库保持一致。 + +## 演示 UI 变更是代码审查的一部分 + +如果代码变更涉及用户界面变化,则除了代码审查外,还需要提供演示,确保界面符合预期且与界面设计一致。 + +对于前端代码变更,你需要进行演示,或确保代码变更包括必要的UI自动化测试,以验证添加或更新的功能。 + +## 确保代码审查中包含了所有测试 + +除非遇到紧急情况,否则拉取请求应包含所有必要的测试,例如单元测试、集成测试以及端到端测试等。 + +这里所说的紧急情况指的是,某个需要尽快修复的 bug 或安全漏洞,而测试可以等到以后再添加。在这种情况下,请确保创建了适当的票证/问题,并确保有人负责在完成热修复或部署后立即完成测试。 + +我们绝对不可以跳过测试。如果时间有限,某些目标有无法实现的风险,那么解决方案不是跳过测试,而是限定可交付成果的范围。 + +## 不要为了代码审查打断手头的工作 + +如果你正在专心致志地工作,那么请不要打断自己,因为你需要花费很长时间才能重新投入工作。换句话说,打断专心工作的开发人员所付出的代价远远超过了让开发人员等待代码审查。你可以在休息(午餐或咖啡等)过后,进行代码审查。 + +大多数时候,整个代码审查以及代码的合并无法在一天内完成。重要的是迅速给作者一些反馈。例如,虽然可能无法完成完整的审查,但你可以快速指出一些有待探讨的地方。这可以极大地降低代码审查期间的挫败感。 + +## 审查所有代码,不要做任何假设 + +你需要审查提交上来的每一行代码。不要对人工编写的类和方法做任何假设,而且应该确保你理解代码在做什么。 + +确保你理解正在审核的代码。如果不理解,则请作者澄清或提供代码演示和解释。如果你不具备审核部分代码的资格,则请其他有资格的开发人员代为审查。 + +## 审查代码时需要保持大局观 + +从更广阔的视野来看待代码变更会更有帮助。例如,某个文件被修改,并添加了4行新代码。请不要只看这4行代码,你应该考虑审查整个文件,并检查新添加的内容。它们是否会降低现有代码的质量?它们是否会导致现有功能成为重构的候选对象? + +如果不在函数/方法或类的背景下审查添加的代码,则随着时间的流逝,你将会得到一个面临无法维护、纠缠不清、不易于测试等问题的类,而且这个类很难扩展或重构。 + +请记住,即便是微不足道的改进,随着时间的推移,也可能导致产品出现缺陷,同样,即便是轻微的代码降级或技术负债也可能在日积月累下导致产品难以维护和扩展。 + +## 在代码审查期间认同和鼓励出色的工作 + +如果看到出色的代码变更,请别忘了大力表扬和鼓励作者。代码审查的目的不仅仅是发现错误,而且还应该鼓励和指导开发人员出色的工作。 + +## 在代码审查期间应保持谨慎、尊重、友善和思路清晰 + +在代码审查期间,你应该保持友善、思路清晰、有礼貌和尊重别人,这一点至关重要,同时也要给予作者清晰的反馈和积极的帮助。在审查代码时,你需要做到对事不对人,即对代码做出评论,而非开发人员。 + +## 详细解释代码审查的意见,并注意尺度 + +每当代码审查意见提出替代方案或指出某些问题时,重要的是你需要解释其中的原因,并根据个人的知识和经验提供示例,以帮助开发人员理解为何你的建议能够提升代码质量。 + +在建议修改或变更代码时,你需要在如何指导作者修改代码方面找到适当的平衡。例如,我更喜欢指导、解释、提示或建议,而不是整个解决方案。 diff --git a/docs/zh-hans/blog/contents/java-development-cheatsheet.md b/docs/zh-hans/blog/contents/java-development-cheatsheet.md new file mode 100644 index 0000000..f487da7 --- /dev/null +++ b/docs/zh-hans/blog/contents/java-development-cheatsheet.md @@ -0,0 +1,203 @@ +--- +title: Java 开发小技巧 +tags: + - java + - tips +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## BigDecimal 对比 + +在 Java 中对 `BigDecimal` 进行比较需要格外注意,因为 `equals` 和 `compareTo` 的行为并不相同。 + +### `equals` 与 `compareTo` 的区别 + +`BigDecimal#equals` 会同时比较**数值**和**标度(scale)**,而 `BigDecimal#compareTo` 仅比较**数值**(忽略标度)。例如: + +```java +var a = new BigDecimal("100"); // scale = 0 +var b = new BigDecimal("100.00"); // scale = 2 + +a.equals(b); // false — 标度不同 +a.compareTo(b); // 0 — 数学数值相同 + +var c = new BigDecimal("200"); + +a.compareTo(c); // -1(负数)— a 小于 c +c.compareTo(a); // 1(正数)— c 大于 a +``` + +### 为什么这很重要 + +当值来自不同来源时,标度不匹配的情况很常见——例如解析用户输入、从数据库(`DECIMAL(10,2)` 列)读取、或接收 JSON 数据。你可能会认为两个值相等,但 `equals` 却说它们不相等。 + +### 使用建议 + +- 使用 **`compareTo`** 进行数值相等判断:`a.compareTo(b) == 0` +- 仅在需要「完全相同的表示」(数值和标度都相同)时使用 **`equals`** +- 如果需要在 `equals` 前统一标度,可使用 **`stripTrailingZeros()`**: + + ```java + a.stripTrailingZeros().equals(b.stripTrailingZeros()); // true + ``` + +### 与零比较 + +避免使用 `==` 或 `.equals(BigDecimal.ZERO)` 来判断是否为零——推荐使用 `compareTo`: + +```java +if (value.compareTo(BigDecimal.ZERO) == 0) { ... } +``` + +## 如何从 BlockingQueue 中获得数据? + +- 使用 `take()` 方法可以拿取数据,如果队列中没有数据会阻塞线程,一直等待; +- 使用 `poll()` 方法拿取数据,如果没有数据会返回 `null` ; +- 使用 `poll(long timeout, TimeUnit unit)` 方法拿去数据,将会在 `poll()` 的基础上等待特定的时间,如果时间到了还没有拿到数据则会返回 `null` ; +- 使用 `peek()` 拿取数据,但是这种方式不会将已经拿到的数据从队列中移除。 + +## Spring Cloud Alibaba 常见问题 + +### Nacos 在个人文件夹中创建 `nacos` 文件夹怎么办? + +添加下面两个 Configuration properties 即可指定 nacos 的存储路径: + +- `JM.LOG.PATH` +- `JM.SNAPSHOT.PATH` + +### Sentinel 乱拉屎怎么办? + +添加 Configuration property `csp.sentinel.log.dir` 即可修改 sentinel 的日志路径。 + +### 怎么在 JetBrains IntelliJ IDEA 中添加 Configuration Properties 配置? + +在 JetBrains IntelliJ IDEA 中,在右上角的运行配置中,点击 **`Edit Configurations…`** ,即可看到配置页面。 + +点击页面中的 **`Modify options`** 按钮,并在下方附加上的 **`Override configuration properties`** 表格中添加需要重置的配置属性。 + +## Spring Data JPA 常见问题 + +### 使用 JPA 分页查询时提示 "Serializing `PageImpl` instances as-is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure" 怎么办? + +在应用程序主启动类上添加下面的代码: + +```java +@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO) +``` + +### 使用 JPA 分页查询时查询第一页都没数据是为什么? + +JPA 的分页页码是从 0 开始的,请求中的页码数据 = 你实际想查询页码数 - 1。 + +### 如何避免 N+1 查询问题? + +N+1 问题是指 JPA 先执行 1 条查询获取父实体,再为每条父实体的关联执行 N 条额外的子查询。 + +**检测方法** — 观察日志中是否有大量重复的 SQL 查询,或者配置 `spring.jpa.properties.hibernate.generate_statistics=true` 来发现。 + +**修复方案:** + +| 方式 | 适用场景 | +|---------------------------|----------------------| +| `@EntityGraph` | 声明式,适合为特定实体定制抓取计划 | +| `@Query` 中使用 `JOIN FETCH` | 按查询精细控制 | +| `@BatchSize` | 将 N+1 降低为 N/k+1,批量加载 | + +```java +// 方式一:EntityGraph +@EntityGraph(attributePaths = {"roles", "permissions"}) +Optional findById(long id); + +// 方式二:JOIN FETCH +@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id") +Optional findByIdWithRoles(@Param("id") long id); +``` + +### `findById` 与 `getReferenceById` 该用哪个? + +- **`findById`** — 立即查询数据库,返回实体或 `Optional.empty()`。需要实际数据时使用。 +- **`getReferenceById`** — 返回一个惰性代理(proxy),**不查询数据库**。只有当访问不存在的代理属性时才会抛出 `EntityNotFoundException`。只需要 ID 来设置外键关系时使用。 + +```java +// 推荐:只需要引用 User 来设置外键 +Post post = new Post(); +post.setAuthor(userRepository.getReferenceById(userId)); +``` + +### 如何解决 `LazyInitializationException`? + +当你在持久化上下文之外(例如 Controller 或序列化器中,事务已关闭后)访问懒加载的关联属性时,就会抛出此异常。 + +**解决方案:** + +1. **使用 `JOIN FETCH` 或 `@EntityGraph`** 提前加载所需的关联数据。 +2. **使用 DTO 投影** — 只返回需要的字段,而不是整个实体: + + ```java + @Query("SELECT new com.example.UserDto(u.id, u.name) FROM User u WHERE u.id = :id") + UserDto findUserDtoById(@Param("id") long id); + ``` +3. **在 Service 方法上使用 `@Transactional(readOnly = true)`** — 使 Session 在整个方法作用域内保持打开。 + +### 什么时候该用 `@Transactional(readOnly = true)`? + +在**只读**的 Service 方法上使用 `@Transactional(readOnly = true)` 有三个好处: + +- Hibernate 会跳过脏检查(无需快照,占用内存更少)。 +- JDBC 驱动可能会将请求路由到只读副本。 +- 清晰地表达了方法的意图。 + +```java +@Service +public class UserService { + + @Transactional(readOnly = true) + public UserDto getUser(long id) { ... } + + @Transactional + public UserDto createUser(CreateUserRequest request) { ... } +} +``` + +### `save()` 与 `saveAll()` — 批量插入哪个更快? + +`saveAll()` 在单个事务中执行,可以受益于 JDBC 批处理。需要配置批处理大小: + +```yaml +spring: + jpa: + properties: + hibernate: + jdbc: + batch_size: 20 + order_inserts: true + order_updates: true +``` + +对于大量数据的批量插入(数千行),建议改用 `JdbcTemplate` 的批处理操作——在这种规模下 Hibernate 的实体管理开销会非常大。 + +### 如何用 `Specification` 实现动态查询? + +对于带有多个可选筛选条件的复杂搜索表单,使用 `JpaSpecificationExecutor`: + +```java +public interface UserRepository extends JpaRepository, + JpaSpecificationExecutor { +} + +// 使用方式 +Specification spec = (root, query, cb) -> { + List predicates = new ArrayList<>(); + if (name != null) { + predicates.add(cb.like(root.get("name"), "%" + name + "%")); + } + if (status != null) { + predicates.add(cb.equal(root.get("status"), status)); + } + return cb.and(predicates.toArray(new Predicate[0])); +}; + +Page page = userRepository.findAll(spec, pageable); +``` diff --git a/docs/zh-hans/blog/contents/java-development-standards.md b/docs/zh-hans/blog/contents/java-development-standards.md new file mode 100644 index 0000000..22e48c0 --- /dev/null +++ b/docs/zh-hans/blog/contents/java-development-standards.md @@ -0,0 +1,238 @@ +--- +title: Java 开发规范 +tags: + - java + - spring-boot + - standards + - best-practice + - backend +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## Java 语言与编码风格 + +- **Java 版本**: 项目所使用的 JDK 应当在可能的情况下,使用最新的 LTS 版本。 +- **命名约定**: + - 类: 帕斯卡命名法 (`PascalCase`) (例如,`UserService`, `OrderController`)。 + - 方法: 驼峰命名法 (`camelCase`) (例如,`getUserById`, `saveOrder`)。 + - 变量: 驼峰命名法 (`camelCase`) (例如,`username`, `statusCode`)。 + - 常量: 全大写下划线命名法 (SCREAMING_SNAKE_CASE) (例如,`DEFAULT_PAGE_SIZE`)。 +- **不变性**: 在可能的情况下,优先选择领域对象和 DTOs 的不变性,使用 `records` 或不可变类来减少副作用并提高线程安全性。 +- **Optional**: 使用 `Optional` 显式处理可能缺失的值,避免 `NullPointerException`。 +- **Streams API**: 优先使用 Java Streams API 进行集合处理,提倡函数式和声明式编程。 +- **异常处理**: + - 使用特定异常。避免捕获通用 `Exception`。 + - 对于不可恢复的错误,抛出运行时异常。 + - 对于可恢复的错误,如果业务逻辑需要,定义自定义受检异常。 + - 利用 Spring 的 `@ControllerAdvice` 和 `@ExceptionHandler` 进行集中式全局异常处理和一致的 API 错误响应。 +- **代码审查**: 所有后端代码在合并前必须经过彻底的人工代码审查,特别是遵循 GitFlow 原则。IntelliJ IDEA 的集成代码分析工具应作为初次审查使用。 + +## 文档与注释 + +- 后端 Java 代码中的所有**公共**类、方法和重要字段必须包含全面的 Javadoc 注释。 +- Javadoc 应解释其目的、参数 (`@param`)、返回值 (`@return`) 和抛出的异常 (`@throws`)。 +- Javadoc 的格式如下: + - Javadoc 需要遵循每行最多 100 个字符(包括用于调整格式的空白字符)。若内容超出了 100 字符,则应当在小于 100 字符的最后一个单词结尾处用换行符换行。但是如果在换行后,下一行句子的开头只有一个单词后就结束,则应当提前一个单词换行。 + + ```java + /** + * Enables configuration properties for S3 file storage services. Individual service beans are + * created by their respective service classes to better support conditional configuration. + */ + ``` + + - 每个段落之间使用一个 `

` 分割。 + + ```java + /** + * This is the first paragraph of the Javadoc. + *

+ * This is the second paragraph of the Javadoc. + */ + ``` + + - 每个段落的内容必须是语法正确且含义完整的段落,遵循英文句子规范。 + - 所有参数(`@param`)、返回值(`@return`)、异常抛出(`@throws`)及参考(`@see`)的解释需要遵循下面的规则: + - 无需使用大写字母开头 + - 若描述的最后为陈述句,则无需使用标点符号结尾 + + ```java + /** + * Returns the greater of two {@code int} values. That is, the + * result is the argument closer to the value of + * {@link Integer#MAX_VALUE}. If the arguments have the same value, + * the result is that same value. + * + * @param a an argument + * @param b another argument + * @return the larger of {@code a} and {@code b} + */ + public static int max(int a, int b) { + return (a >= b) ? a : b; + } + ``` + +## 依赖管理 (Gradle) + +- **构建文件**: `build.gradle.kts` 必须组织良好,对插件、依赖项和任务有清晰的划分。 +- **依赖版本**: 必须在 `gradle/libs.versions.toml` 文件中集中管理依赖项版本,以确保一致性。 +- **插件管理**: 显式声明 Gradle 插件及其版本。 +- **避免不必要的依赖项**: 仅包含项目实际使用的依赖项。定期审查并清理未使用的依赖项。 + +## API 设计 (RESTful) + +- **RESTful 原则**: 遵循 RESTful 原则: + - **资源**: 将数据建模为可通过 URI 标识的资源。 + - **HTTP 方法**: 适当地使用标准 HTTP 方法(GET 用于检索,POST 用于创建,PUT 用于完全更新,PATCH 用于部分更新,DELETE 用于删除)。 + - **无状态性**: API 应是无状态的;客户端向服务器发送的每个请求都必须包含理解请求所需的所有信息。 +- **URI**: + - 集合资源使用复数名词(例如,`/users`,`/products`)。 + - URI 中使用连字符以提高可读性(例如,`/user-accounts`)。 + - 避免在 URI 中使用动词(例如,使用 `/users` 而不是 `/getAllUsers`)。 +- **状态码**: 使用适当的 HTTP 状态码来指示 API 请求的结果(例如,`200 OK`、`201 Created`、`204 No Content`、`400 Bad Request`、`401 Unauthorized`、`403 Forbidden`、`404 Not Found`、`500 Internal Server Error`)。 +- **响应格式**: JSON 是首选的响应格式。 +- **版本控制**: 建议在需要版本控制的接口中使用 Header 参数 `X-Endpoint-Version` 控制接口版本。 + +## Spring Boot 最佳实践与分层架构 + +- **分层架构 (MVC 配合 Manager 层)**: 我们的后端应用程序遵循严格的多层架构,确保职责清晰分离,并提高可维护性和可测试性。各层及其职责如下: + - **控制器层 (Controller Layer)**: 位于 `controller` 包中。负责暴露 RESTful API,处理 HTTP 请求,并将请求参数/主体映射到服务层调用。控制器应保持轻量,主要关注输入验证(使用 DTOs)和协调对 `Service` 层的调用。 + - **服务层 (Service Layer)**: 位于 `service` 包中。此层封装了核心业务逻辑。服务层向 `Controller` 层提供可以直接使用的 API,抽象业务流程和事务管理。服务层协调对 `Manager` 层的调用以执行业务操作。 + - **管理器层 (Manager Layer)**: 位于 `manager` 包中。此层提供原子化的业务操作,可由 `Service` 层组合使用。管理器通常处理更复杂的业务逻辑,可能涉及在更细粒度级别上与多个仓库或其他外部系统进行交互。 + - **仓库层 (Repository Layer) (MyBatis)**: 位于 `repository` 包和 `src/main/resources/repository`(用于 XML 映射文件)中。此层负责提供原子化的数据库交互操作。 + + **层间通信策略**: + - **每一层内的组件仅可调用直接位于其下方的层中的组件。** + - **严格禁止跨层调用(例如,Controller 直接调用 Manager,Service 直接调用 Repository)。** + - **严格禁止向上调用(例如,Service 调用 Controller)。** + - **横向调用(例如,Service A 调用 Service B 处理不同领域)应仔细考虑,通常表明需要将共享逻辑重构到 `Manager` 或为该共享关注点重构一个专用 `Service` 中。** + + - **配置**: 优先选择 `application.yml` 进行配置属性,而不是 `application.properties`,以获得更好的可读性和分层结构。使用 `@ConfigurationProperties` 进行类型安全配置。 + - **依赖注入**: 所有依赖项都使用构造函数(强制依赖)或 Setter(非强制依赖)注入。避免在字段上使用 `@Autowired`,因为它会使测试更困难并隐藏依赖项。 + - **服务**: 业务逻辑类使用 `@Service` 进行注解。服务应保持精简,并专注于协调领域操作,这些操作通常涉及通过 `Manager` 层组件进行业务逻辑处理,以及通过 `Manager` 或直接与 MyBatis 仓库进行交互(如果该特定操作不需要中间 Manager 逻辑,尽管对于所有仓库交互,Manager 层是首选,符合分层架构定义)。 + +- **仓库 (MyBatis 与 JPA)**: + - **项目使用 MyBatis 及 JPA 进行数据库操作**。`repository` 包及 `mapper` 包中的仓库接口定义了数据访问契约。 + - 相应的 SQL 定义在位于 `src/main/resources/mapper` 的 XML 映射文件中管理。 + - 数据操作约定: + - 对于简单数据库操作,使用 JPA。 + - 对于复杂数据库操作,使用 MyBatis。 + - 当进行分页查询时,页码应从 0 开始(*兼容 Spring Data JPA*)。 + - **数据操作方法命名约定**: + - 对于**查询数据列表**:方法**必须**以 `selectListBy` 开头,紧随其后是筛选条件(例如,`selectListByUserId`、`selectListByDepartmentIdAndStatus`)。这些方法也必须包含一个 `PageRequest` 参数用于分页。 + - 对于查询**单个数据记录**:方法**必须**以 `selectOne` 开头(例如,`selectOneById`、`selectOneByUsername`)。 + - 对于**保存新数据**:方法**必须**命名为 `save`,Mapper 方法的返回值必须是 `int`(受影响的行数)。 + - 对于**更新现有数据**:方法**必须**命名为 `update`,Mapper 方法的返回值必须是 `int`(受影响的行数)。 + - 对于**删除数据**:方法**必须**以 `deleteBy` 开头,清晰地指明删除的依据(例如,`deleteById`)。Mapper 方法的返回值必须是 `int`(受影响的行数)。 + +- **控制器**: + - 使用 `@RestController` 注解 REST 控制器。 + - 使用 `@GetMapping`、`@PostMapping` 等将 HTTP 方法(GET、POST、PUT、DELETE)映射到适当的控制器方法。 + - 确保请求和响应负载定义明确(DTOs)并有文档说明。 + - 控制器应主要处理 HTTP 请求/响应映射,并将实际业务逻辑委托给服务层。 + +- **DTOs (数据传输对象)**: 定义单独的 DTOs 用于请求和响应主体,以将内部领域模型与 API 契约解耦。在 DTOs 上使用验证注解(例如,`@Valid`、`@NotNull`、`@Size`)。 + +- **日志 (Slf4j & Logback)**: + - 应用程序中的所有日志记录都使用 `org.slf4j.Logger`。 + - 配置 `application-dev.yml` 以设置日志级别、Appender 和输出格式。 + - 日志消息应具有描述性并提供足够的上下文。避免记录敏感信息。 + - 使用参数化日志记录以提高性能并防止字符串拼接开销(例如,`log.debug("Processing user: {}", userId);`)。 + - 标准日志级别:`ERROR`、`WARN`、`INFO`、`DEBUG`、`TRACE`。 + +## 项目结构 + +后端应用程序遵循结构化的 Gradle 项目布局。核心应用程序应清晰地划分为各种子包。 + +```text +backend-application +├── build.gradle.kts // 项目的主 Gradle 构建脚本 +├── config // 外部配置目录 +│ ├── application-dev.yml // 开发环境的应用程序属性 +│ └── application-prod.yml.example // 生产环境属性示例(待复制和配置) +├── database // 数据库相关文件 +│ └── init.d // 数据库初始化脚本 +│ └── init-en_GB.sql // 使用指定语言的数据库模式和初始数据的 SQL 脚本(英式英语区域设置) +├── gradle // Gradle Wrapper 和配置文件 +│ ├── libs.versions.toml // 集中管理依赖版本(Gradle 版本目录) +│ └── wrapper // Gradle Wrapper 文件 +│ ├── gradle-wrapper.jar +│ └── gradle-wrapper.properties +├── gradle.properties // 项目特定的 Gradle 属性 +├── gradlew // Gradle Wrapper 可执行文件 (Linux/macOS) +├── gradlew.bat // Gradle Wrapper 可执行文件 (Windows) +├── settings.gradle.kts // 多项目构建的 Gradle 设置(如果适用) +└── src + ├── main + │ ├── java + │ │ └── com/onixbyte/application // 应用程序的根包 + │ │ ├── Application.java // Spring Boot 应用程序主入口点 + │ │ ├── config // Spring 配置类 + │ │ ├── constant // 定义应用程序范围常量的类 + │ │ ├── controller // REST API 端点 + │ │ ├── domain // 核心领域模型和相关类型 + │ │ │ ├── common // 通用领域对象/工具类 + │ │ │ ├── entity // 表示数据库表的 JPA/MyBatis 实体 + │ │ │ ├── model // 通用的实体类模型 + │ │ │ ├── view // 专门用于只读操作的数据传输对象(例如,查询结果、报告结构) + │ │ │ └── web // 专门用于 Web 请求/响应体的数据传输对象 + │ │ │ ├── request // 请求 DTO + │ │ │ └── response // 响应 DTO + │ │ ├── exception // 自定义应用程序特定异常 + │ │ ├── extension // 扩展点或自定义功能 + │ │ │ ├── jackson // Jackson 序列化/反序列化扩展 + │ │ │ └── redis // Redis 相关扩展 + │ │ │ └── serializer + │ │ ├── filter // Servlet 过滤器或 Spring Security 过滤器 + │ │ ├── manager // 业务逻辑协调器,通常协调多个服务或仓库 + │ │ ├── mapper // 数据访问层 (MyBatis 接口) + │ │ ├── processor // 通用处理组件或业务工作流 + │ │ ├── properties // 用于类型安全配置属性的类 (`@ConfigurationProperties`) + │ │ ├── repository // 数据访问层 (Spring Data JPA 接口) + │ │ ├── security // Spring Security 特定组件 + │ │ │ ├── authentication // 自定义认证机制 + │ │ │ └── provider // 自定义认证提供者 + │ │ ├── service // 核心业务逻辑(事务层) + │ │ ├── utils // 通用工具类 + │ │ └── validation // 自定义验证逻辑 + │ │ └── group // 针对不同上下文(例如,创建、更新)的验证分组 + │ └── resources + │ ├── application.yml // 默认应用程序属性 + │ └── mapper // MyBatis XML 映射文件 + └── test + └── java + └── com/onixbyte/helix + └── HelixApplicationTests.java // Spring Boot 集成测试 +``` + +**关键观察与具体指令:** + +- **外部 `config` 目录** + - 环境配置 (`application-dev.yml`、`application-prod.yml.example`) 在顶层 `config` 目录中管理,与 `src/main/resources` 分开。这促进了特定于环境的属性管理,允许在部署时挂载或链接不同的配置。 + - **禁止上传除 `src/main/resources/application.yml` 之外的任何配置文件到 Git 仓库。** +- **数据库初始化**: `database/init.d` 目录保留用于 SQL 脚本,特别是数据库模式初始化 (`init-en_GB.sql`),这对于环境设置和 CI/CD 流水线至关重要。这种结构暗示了"模式优先"或"代码驱动的模式演进"方法。 +- **`client` 包**: 该包用于向应用提供所有中间件的服务,如 HTTP、S3 存储、Redis 调用等。对于需要添加到 Spring 上下文中的,自行编码实现的功能(如 JSON Web Token 生成与解析)也推荐放到该包中。 +- **MyBatis 与 Spring Data JPA 集成** + - `src/main/java/.../mapper` 包包含 MyBatis mapper 接口,而实际的 SQL 定义位于 `src/main/resources/mapper/*.xml` 文件中。这种分离是保持代码整洁同时利用 MyBatis 强大 XML 映射能力的关键。 + - `src/main/java/.../repository` 包包含 Spring Data JPA 接口。 +- **`domain` 包的粒度**: + - `domain.entity`: 保留用于直接映射到数据库表的类(MyBatis 的 POJO)。 + - `domain.model`: 用于不直接与单个表一对一映射的更通用领域对象或聚合根。 + - `domain.view`: 专门用于在只读场景(例如,查询结果、报告结构)中呈现数据的数据传输对象 (DTO)。 + - `domain.web.request` / `domain.web.response`: 明确分离的用于传入 API 请求和传出 API 响应的 DTO,严格遵守 API 契约并与内部领域实体解耦。 +- **`manager` 和 `processor` 包**: 这些包暗示了一个分层架构,其中"管理器"协调涉及多个服务或仓库的操作,而"处理器"可能处理业务流程的特定方面。强制明确定义这些包中类的职责,以防止出现"贫血领域模型"或"上帝对象"等反模式。 +- **`security` 包**: 此子包包含除初始配置之外的自定义 Spring Security 组件,例如自定义认证类型和提供者,表明了定制的安全实现。 +- **`properties` 包**: 此包用于自定义 `@ConfigurationProperties` 类,促进对 YML 文件中定义的应用程序设置进行类型安全访问,位置得当。 +- **`extension` 包**: 这是一个灵活的区域,用于应用程序特定的扩展,例如自定义 Jackson 序列化器或 Redis 定制。应谨慎使用,以防止成为"杂项"的堆放地。 + +## 安全性 (Spring Security) + +- **强制使用**: Spring Security 在所有 Spring Boot Web 应用程序中都是强制的。 +- **认证与授权**: 在 `SecurityConfig.java` 中配置认证机制(例如,OAuth 2.0、JWT、基于会话)和授权规则。 +- **CSRF 保护**: 确保对修改状态的操作启用 CSRF 保护,除非有充分的理由禁用它(例如,已存在其他机制的无状态 API)。 +- **CORS**: 根据前端部署要求正确配置跨域资源共享 (CORS)。 +- **第三方身份提供者**: 在与第三方身份提供者(例如 Microsoft Entra ID)集成时,遵循安全令牌处理和用户配置的最佳实践。敏感凭据必须安全管理(例如,环境变量、Vault)。 +- **输入验证**: 始终在服务器端验证所有用户输入,以防止常见的漏洞,如 SQL 注入、XSS 等。 +- **内容安全策略 (CSP)**: 考虑为前端实施强大的 CSP 以减轻 XSS 攻击。 diff --git a/docs/zh-hans/blog/contents/minio-admin-guide.md b/docs/zh-hans/blog/contents/minio-admin-guide.md new file mode 100644 index 0000000..2386316 --- /dev/null +++ b/docs/zh-hans/blog/contents/minio-admin-guide.md @@ -0,0 +1,200 @@ +--- +title: MinIO 新版本管理中心 +tags: + - minio + - storage + - s3 + - devops + - cheatsheet +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +MinIO 在新版本中将管理功能从 Web UI 中移除了。现在需要使用 **MinIO Client (mc)** 命令行工具来进行管理操作。 + +## 安装 MinIO Client (mc) + +### Windows: + +```powershell +# 下载 mc.exe +Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "mc.exe" + +# 或使用 Chocolatey +choco install minio-client +``` + +### Linux/macOS: + +```bash +# Linux +wget https://dl.min.io/client/mc/release/linux-amd64/mc +chmod +x mc +sudo mv mc /usr/local/bin/ + +# macOS +brew install minio/stable/mc +``` + +## 配置 MinIO Client + +```bash +# 添加 MinIO 服务器别名 +mc alias set myminio http://localhost:9000 minioadmin minioadmin + +# 验证连接 +mc admin info myminio +``` + +## 用户管理操作 + +### 创建用户 + +```bash +# 创建新用户 +mc admin user add myminio newuser newpassword + +# 查看所有用户 +mc admin user list myminio +``` + +### 创建 Access Key 和 Secret Key + +```bash +# 为用户创建服务账户(生成 AccessKey/SecretKey) +mc admin user svcacct add myminio newuser + +# 或者指定自定义的 AccessKey +mc admin user svcacct add myminio newuser --access-key "MYACCESSKEY123" --secret-key "MYSECRETKEY456" + +# 查看用户的服务账户 +mc admin user svcacct list myminio newuser +``` + +## 权限管理 + +### 创建策略 + +```bash +# 创建策略文件 policy.json +cat > policy.json << EOF +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::mybucket/*" + ] + } + ] +} +EOF + +# 添加策略 +mc admin policy add myminio mypolicy policy.json + +# 将策略分配给用户 +mc admin policy set myminio mypolicy user=newuser +``` + +## 存储桶管理 + +```bash +# 创建存储桶 +mc mb myminio/mybucket + +# 列出存储桶 +mc ls myminio + +# 设置存储桶策略 +mc policy set public myminio/mybucket +``` + +## 常用管理命令 + +```bash +# 查看服务器信息 +mc admin info myminio + +# 查看服务器配置 +mc admin config get myminio + +# 重启服务器 +mc admin service restart myminio + +# 查看日志 +mc admin logs myminio + +# 查看统计信息 +mc admin prometheus metrics myminio +``` + +## 实用脚本示例 + +创建一个管理脚本 `setup-minio.sh`: + +```bash +#!/bin/bash + +MINIO_ALIAS="myminio" +MINIO_URL="http://localhost:9000" +ADMIN_USER="minioadmin" +ADMIN_PASS="minioadmin" + +# 配置 MinIO 客户端 +mc alias set $MINIO_ALIAS $MINIO_URL $ADMIN_USER $ADMIN_PASS + +# 创建应用用户 +APP_USER="appuser" +APP_PASS="apppassword" +mc admin user add $MINIO_ALIAS $APP_USER $APP_PASS + +# 创建服务账户并获取 AccessKey/SecretKey +echo "Creating service account for $APP_USER..." +CREDENTIALS=$(mc admin user svcacct add $MINIO_ALIAS $APP_USER --json) +ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.accessKey') +SECRET_KEY=$(echo $CREDENTIALS | jq -r '.secretKey') + +echo "Generated credentials:" +echo "Access Key: $ACCESS_KEY" +echo "Secret Key: $SECRET_KEY" + +# 创建存储桶 +mc mb $MINIO_ALIAS/app-bucket + +# 设置只读策略 +mc policy set download $MINIO_ALIAS/app-bucket +``` + +## Web Console 访问 + +虽然管理功能被移除,但您仍然可以通过以下方式访问 MinIO Console: + +```bash +# 启动 MinIO Console(如果单独安装) +mc admin console myminio +``` + +或者在启动 MinIO 服务器时指定 Console 地址: + +```bash +minio server /data --console-address ":9001" +``` + +## 总结 + +新版 MinIO 的管理完全依赖 `mc` 命令行工具: + +1. **安装 mc 客户端** +2. **配置服务器别名** +3. **使用 `mc admin` 命令进行用户、权限、存储桶管理** +4. **通过 `mc admin user svcacct` 生成 AccessKey/SecretKey** + +这种方式虽然需要命令行操作,但提供了更强大和灵活的管理能力,特别适合自动化部署和脚本化管理。 diff --git a/docs/zh-hans/blog/contents/mybatis-flex.md b/docs/zh-hans/blog/contents/mybatis-flex.md new file mode 100644 index 0000000..6c22aad --- /dev/null +++ b/docs/zh-hans/blog/contents/mybatis-flex.md @@ -0,0 +1,31 @@ +--- +title: MyBatis Flex +tags: + - java + - mybatis + - spring-boot + - orm + - framework +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +> 本文仅针对在 Spring Boot 3 中使用 `MyBatis Flex` + +## 安装与配置 + +### 安装 + +在 `gradle` 目录下的 `libs.versions.toml` 中添加如下代码: + +```toml +[versions] +mybatisFlexVersion = "X.Y.Z" +hikariVersion = "X.Y.Z" + +[libraries] +hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariVersion" } +mybatisFlex-processor = { group = "com.mybatis-flex", name = "mybatis-flex-processor", version.ref = "mybatisFlexVersion" } +mybatisFlex-starter = { group = "com.mybatis-flex", name = "mybatis-flex-spring-boot3-starter", version.ref = "mybatisFlexVersion" } +``` diff --git a/docs/zh-hans/blog/contents/postgresql-zhparser-fuzzy-search.md b/docs/zh-hans/blog/contents/postgresql-zhparser-fuzzy-search.md new file mode 100644 index 0000000..783ef92 --- /dev/null +++ b/docs/zh-hans/blog/contents/postgresql-zhparser-fuzzy-search.md @@ -0,0 +1,212 @@ +--- +title: 使用分词在 PostgreSQL 中加速模糊查询 +tags: + - postgresql + - zhparser + - full-text-search + - performance +author: + name: Siu Jam Oh + email: jamo.siu@gmail.com +--- + +## 背景与挑战 + +随着业务数据量突破 **200 万行**,传统的 `LIKE '%关键词%'` 模糊查询导致数据库 I/O 频繁告警,查询响应时间从毫秒级退化至秒级。为提升检索效率并支持中文语义,我们决定引入 `zhparser` 插件实现全文检索。 + +## 演进路线与环境适配 + +本次技术落地经历了四个关键阶段,每个阶段解决了不同的技术核心: + +### CentOS 7.9 虚拟机(可行性验证) + +- **目标**:验证 `SCWS` + `zhparser` 在旧版系统下的兼容性。 +- **核心操作**:在 CentOS 7.9 环境下手动编译 `postgresql-16.2` 源码,完成插件初步跑通。 + +**结论**:确认了分词方案对中文检索性能的显著提升。 + +### 本地 Docker 容器(容器化探索) + +- **目标**:在本地的完整系统进行初步测试。 +- **核心操作**:通过 `docker cp` 注入二进制 `.so` 文件,解决 `ldconfig` 动态链接库路径可见性问题。 +- **发现**:识别出 **"词典文件缺失"** 会导致分词退化为单字(助词)的故障点。 + +### EulerOS 2.0 测试服(自编译环境适配) + +- **目标**:适配生产环境的 OS 架构(x86_64)与自编译安装的 PostgreSQL。 +- **核心问题**:解决了 `libscws.so.1` 无法加载的报错。 +- **关键方案**: + - 明确了 `postgres` 运行用户对 `/usr/local/scws/lib` 的访问权限需求。 + - 通过修改 `systemd` 服务环境变量或创建 `/usr/lib64` 软链接强制刷新库搜索路径。 + +### 生产部署预备(最终调优) + +- **目标**:确保 200w+ 数据量下的查询稳定性。 +- **优化点**:针对"非语义片段(如:古唐合)"查询不到的问题,制定了"全文检索优先 + `pg_trgm` 索引辅助"的降级查询策略。 + +## 核心安装与配置步骤 (自编译环境) + +### 安装 `SCWS` 分词引擎 + +SCWS 是 `zhparser` 依赖的底层核心,必须先行安装。 + +1. **下载并解压**:下载源码包(如 `scws-1.2.3`)。 +2. **编译安装**: + + ```bash + ./configure --prefix=/usr/local/scws + make && make install + ``` + +3. **验证库文件**:确保 `/usr/local/scws/lib/libscws.so.1` 存在。 + +### 编译与安装 `zhparser` + +这一步需要用到 PostgreSQL 自编译产生的 `pg_config`。 + +1. **获取源码**:从 GitHub 克隆 `zhparser` 项目。 +2. **指定路径编译**: + + ```bash + # 确保 pg_config 在 PATH 中,或手动指定 + make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config + make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config install + ``` + + *注:`install` 步骤会自动将 `zhparser.so` 放入 PG 的 `pkglibdir`,将脚本放入 `extension` 目录。* + +### 解决动态链接库依赖 + +1. **刷新系统缓存**: + + ```bash + echo "/usr/local/scws/lib" > /etc/ld.so.conf.d/scws.conf + ldconfig + ``` + +2. **权限检查**:确保运行 `postgres` 的 OS 用户对 `/usr/local/scws/lib` 有 `rx` 权限。 +3. **强制软链 (备选)**:若 `ldconfig` 失效,可将库文件软链至 `/usr/lib64`。 + +### 重启数据库 + +修改完系统共享库配置后,必须重启 PostgreSQL 进程以重新加载环境变量和链接库。 + +```bash +## 使用 pg_ctl 重启(自编译路径需根据实际情况调整) +/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart + +## 或通过 systemd 重启(如果已注册服务) +systemctl restart postgresql +``` + +### 数据库内初始化 + +连接到 `psql`,执行逻辑配置: + +```sql +-- 创建扩展 +CREATE EXTENSION zhparser; + +-- 创建全文检索配置并绑定分词器 +CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser); + +-- 添加 Token 映射 +ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l,t,b WITH simple; + +-- 【可选】指定自编译路径下的词典位置 +-- ALTER DATABASE postgres SET zhparser.dict_path = '/usr/local/scws/etc/dict.utf8.xdb'; +``` + +## 性能分析与避坑指南 + +### 索引性能瓶颈分析 + +在测试中发现,即使是精确查询,由于使用了不当的联合索引(`create_time` 在首列),导致产生 `Bitmap Index Scan`,耗时高达 **482ms**。 + +- **改进**:对检索高频列建立单列 **B-tree** 索引,利用 `Index Scan` 将响应降至 **10ms** 以内。 + +### GIN 索引与非语义匹配 + +- **跨词截断问题**:分词引擎基于语义,类似"古唐合"这种截断词可能因为分词边界导致 `@@` 无法命中。 +- **应对方案**:采用"瀑布式搜索"。全文检索(FTS)优先;若结果为空,自动降级为 `LIKE` 模糊查询,并配合 `pg_trgm` 索引加速。 + +## 终极部署方案:双轨并行检索 + +经过对数据的分析,发现对于账户名称中出现了约 1.24% 的非标准简体中文的数据,并且数据库中也存在部分因为公司名称怪异而导致数据查询不出来的问题,决定采取"分步降级"策略: + +- **第一步:全文检索(Fast Track):** 利用 GIN 索引进行 `@@` 匹配。 +- **第二步:结果评估:** 若返回结果为空,且搜索词包含字母或疑似繁体字符。 +- **第三步:模糊兜底(Safe Fallback):** 执行 `LIKE '%关键词%'`。虽然速度较慢,但由于其作为"补漏"逻辑,触发频率仅为 1% 左右,不会对数据库造成整体压力。 + +## 搜索优化:集成自定义业务词库 + +为了解决全文检索中公司品牌名被误切(如"元一"被切分为数词/量词)的问题,需构建一套从数据提取到索引重建的自动化维护链路。 + +### 词库提取与预处理 + +利用 **`companynameparser`** 的结构化解析能力剥离地名与行业后缀,并结合 **`jieba`** 进行语义复核,确保核心品牌词的完整性: + +- **提取逻辑**:通过 Python 脚本遍历全量 `buyer_unique_name`,提取核心 `brand` 字段。 +- **权重补偿**:针对包含"元"、"一"、"三"等易碎词条,手动将 TF(词频权重)提升至 **50.0 - 60.0**,确保其优先级高于内置量词规则。 +- **输出规范**:输出符合 SCWS 标准的 4 字段 `UTF-8` 文本(WORD, TF, IDF, ATTR),建议使用制表符 `\t` 分隔以规避解析异常。 + +### 词库编译与部署 + +:::tip +对于使用 Windows 进行编译 `xdb` 二进制词典文件的用户,可以前往 OnixByte 的 [GitHub](https://github.com/onixbyte/scws/releases/tag/1.2.3) 或 [GitLab](https://git.onixbyte.cn/onixbyte/scws/-/releases/1.2.3) 页面下载使用 MingW 预编译好的,适用于 Windows 的原生 scws 命令行工具。 +::: + +将文本词典转换为 SCWS 高效二进制格式(XDB): + +1. **编译二进制词典**: + + ```bash + # 使用 scws-gen-dict 工具生成加密二进制词库 + /usr/local/scws/bin/scws-gen-dict -i custom_company.txt -o /usr/local/scws/etc/custom_company.xdb -c utf8 + ``` + +2. **文件分发与权限**:将生成的 `.xdb` 文件移动至分词数据目录,并确保 `postgres` 用户具备读取权限: + + ```bash + cp custom_company.xdb /usr/local/pgsql/share/tsearch_data/ + chown postgres:postgres /usr/local/pgsql/share/tsearch_data/custom_company.xdb + ``` + +### 数据库参数固化 + +修改 `postgresql.conf` 配置文件,强制加载 `zhparser` 及其自定义扩展词库: + +```plain text +## 预加载插件库(必须重启生效) +shared_preload_libraries = 'zhparser' + +## 加载自定义外部词典(需使用相对 tsearch_data 的路径) +zhparser.extra_dicts = 'custom_company.xdb' +``` + +### 索引热更新与验证 + +由于分词规则变动,存量数据必须通过重建索引实现语义同步: + +**物理重启服务**: + +```bash +su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart" +``` + +**在线重建索引**:利用 `CONCURRENTLY` 关键字,在不阻塞 40 万条数据 DML 操作的前提下刷新 GIN 索引: + +```bash +REINDEX INDEX CONCURRENTLY index_name; +``` + +**分词效能验证**: + +```sql +-- 预期结果词性应显示为 n (noun),而非 x (unknown) +SELECT * FROM ts_debug('chinese', '元一能源'); +``` + +**优化建议:** +- **显式提及权重补偿**:这是解决"元一"分词失败(显示为 `x`)的关键技术点。 +- **区分重启与重载**:明确 `shared_preload_libraries` 必须通过 `restart` 激活。 diff --git a/docs/zh-hans/blog/contents/quartile-method.md b/docs/zh-hans/blog/contents/quartile-method.md new file mode 100644 index 0000000..db80f03 --- /dev/null +++ b/docs/zh-hans/blog/contents/quartile-method.md @@ -0,0 +1,22 @@ +--- +title: 四分位法 +tags: + - statistics + - algorithm + - data-analysis + - math +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +四分位法是统计学中的一种常用方法,主要用于数据的分析和表示。这种方法涉及将一组数据分为四个等分,每份包含四分之一的数据量。主要的统计量包括第一四分位数(Q1)、第二四分位数(Q2),即中位数和第三四分位数(Q3)。 + +- 第一四分位数(`Q1`),也称为下四分位数,是指数据集中所有数值按大小排列后位于第 25% 位置的数值。 +- 第二四分位数(`Q2`),即中位数,是指数据集中所有数值按大小排列后位于第 50% 位置的数值。 +- 第三四分位数(`Q3`),也称为上四分位数,是指数据集中所有数值按大小排列后位于第 75% 位置的数值。 +- 四分位距(`IQR`),即第三四分位数与第一四分位数之间的差值,用于表示数据集中间50%数据的离散程度。四分位距的计算公式为 IQR = Q3 - Q1。四分位距常用于构建箱形图,是一种描述数据分布的有效方式,特别适用于识别数据中的异常值。 + +上界值 = Q3 + 1.5 IQR。 + +下界值 = Q1 - 1.5 IQR。 diff --git a/docs/zh-hans/blog/contents/setup-ldap-service.md b/docs/zh-hans/blog/contents/setup-ldap-service.md new file mode 100644 index 0000000..2509f9b --- /dev/null +++ b/docs/zh-hans/blog/contents/setup-ldap-service.md @@ -0,0 +1,140 @@ +--- +title: 搭建 LDAP 服务 +tags: + - ldap + - openldap + - authentication + - linux + - devops +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +搭建 LDAP(轻型目录访问协议)服务器是实现企业级**集中身份认证**和**权限管理**的核心步骤。最常用的开源实现是 **OpenLDAP**。 + +以下是在基于 Debian/Ubuntu 的 Linux 系统上搭建 OpenLDAP 服务器的详细步骤。 + +## 环境准备与安装 + +在开始之前,请确保你的系统软件包是最新的,并设置好主机名。 + +```bash +## 更新系统 +sudo apt update && sudo apt upgrade -y + +## 安装 OpenLDAP 及管理工具 +## slapd 是守护进程,ldap-utils 是命令行客户端工具 +sudo apt install slapd ldap-utils -y +``` + +> 在安装过程中,系统会提示你设置 **LDAP 管理员密码**。请务必记住这个密码,稍后配置时会频繁使用。 + +## 配置 OpenLDAP 服务器 + +虽然安装时已经初始化了部分设置,但通常我们需要根据具体的域名(如 `example.com`)进行自定义配置。 + +### 重新配置 slapd + +执行以下命令进入交互式配置界面: + +```bash +sudo dpkg-reconfigure slapd +``` + +**配置建议**: + +1. **Omit OpenLDAP server configuration?** 选择 **No**。 +2. **DNS domain name:** 输入你的域名(例如 `centre.example.com`)。这将决定你的基准识别名(Base DN),如 `dc=centre,dc=example,dc=com`。 +3. **Organization name:** 输入你的组织名称。 +4. **Administrator password:** 输入之前设置的管理员密码。 +5. **Database backend:** 建议选择 **MDB**。 +6. **Remove database when slapd is purged?** 选择 **No**。 +7. **Move old database?** 选择 **Yes**。 + +## 理解 LDAP 层级结构 + +LDAP 的数据是以**树状结构**存储的。为了方便管理,我们通常会创建两个"组织单元"(Organizational Units, OU):一个存放用户(Users),一个存放组(Groups)。 + +## 创建组织单元 (OU) + +在 LDAP 中,我们使用 **LDIF** (LDAP Data Interchange Format) 文件来添加或修改目录。 + +创建一个名为 `base.ldif` 的文件: + +```text +## 创建用户组 OU +dn: ou=people,dc=centre,dc=example,dc=com +objectClass: organizationalUnit +ou: people + +## 创建用户组别 OU +dn: ou=groups,dc=centre,dc=example,dc=com +objectClass: organizationalUnit +ou: groups +``` + +**导入数据:** + +```bash +## 使用管理员身份将 LDIF 文件导入数据库 +ldapadd -x -D "cn=admin,dc=centre,dc=example,dc=com" -W -f base.ldif +``` + +## 添加用户和组 + +创建一个名为 `groups.ldif` 的文件来定义一个新组: + +```text +## 定义一个新组 +dn: cn=developers,ou=groups,dc=centre,dc=example,dc=com +objectClass: inetOrgGroup +cn: developers +``` + +创建一个名为 `users.ldif` 的文件来定义一个新用户: + +```text +## 定义一个新用户 +dn: uid=jbloggs,ou=people,dc=centre,dc=example,dc=com # 该条目的身份证号,即 Distinguished Name (DN)。 +objectClass: inetOrgPerson # 对象类,inetOrgPerson 代表这是一个互联网机构人员。它允许你使用 email、displayName、telephoneNumber 等常见的联系人属性 +objectClass: posixAccount # 它使该条目兼容 Unix/Linux 系统账号。有了它,用户才能登录 Linux 服务器,并拥有 UID、GID 和家目录。 +objectClass: shadowAccount # 用于管理密码老化(如过期、更改警告等),对应 Linux 中的 /etc/shadow 文件功能。 +uid: jbloggs # 用户的登录名(User ID)。在 Linux 登录界面输入的通常就是这个。 +sn: Bloggs # 姓(Surname)。inetOrgPerson 类要求必须填写。 +givenName: Joe # 名(First Name)。 +cn: Joe Bloggs # 全名(Common Name)。这是 LDAP 条目的标准显示名称。 +displayName: Joe Bloggs # 在图形界面或邮件客户端显示的友好名称。 +uidNumber: 10000 # Linux 系统中的用户 ID 数字 +gidNumber: 5000 # 用户主组的 ID 数字。 +userPassword: {SSHA}密码哈希值或明文 # 存储用户的加密密码。 +homeDirectory: /home/jbloggs # 用户登录 Linux 后的家目录路径。 +loginShell: /bin/bash # 用户登录后使用的 Shell 环境。 +``` + +> 如果您将来要集成 **GitLab**、**Jenkins** 或 **VPN**,它们通常会搜索 `uid` 属性来验证登录名。 + +### FAQ + +#### 如果不需要为该用户授予服务器的登录权限,是不是可以移除 objectClass: posixAccount? + +**是的,完全可以移除**,但你需要注意属性之间的"绑定关系"。 + +如果你只需要这个用户用于 Web 应用登录(如 GitLab, Jenkins, Wiki)或作为电子邮件联系人,而不需要他通过 SSH 或控制台登录 Linux 服务器,移除 `posixAccount` 是更规范的做法。 + +当你移除 `objectClass: posixAccount` 时,以下属性**必须一并删除**,因为它们属于该类定义的"强制或可选属性": + +- `uidNumber` +- `gidNumber` +- `homeDirectory` +- `loginShell` + +此外, `shadowAccount` 通常也与系统登录挂钩,如果不需要管理 Linux 密码过期策略,也可以一并移除。 + +## 后续建议与管理工具 + +命令行管理 LDAP 较为繁琐,建议使用以下工具进行可视化操作: + +- **phpLDAPAdmin**: 基于 Web 的管理界面,适合快速上手; +- **Apache Directory Studio**: 强大的跨平台桌面客户端,适合复杂的架构设计。 +- **安全加固**: 默认情况下 LDAP 是明文传输的,建议配置 **LDAPS (LDAP over SSL/TLS)** 来加密 636 端口的通信。 diff --git a/docs/zh-hans/blog/contents/use-json-in-mysql.md b/docs/zh-hans/blog/contents/use-json-in-mysql.md new file mode 100644 index 0000000..1ebb922 --- /dev/null +++ b/docs/zh-hans/blog/contents/use-json-in-mysql.md @@ -0,0 +1,89 @@ +--- +title: 在 MySQL 中使用 JSON +tags: + - mysql + - json + - database +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +MySQL (自 5.7 版本开始) **不直接支持名为 `jsonb` 的数据类型**。`jsonb` 是 PostgreSQL 数据库特有的数据类型,它以二进制格式存储 JSON 数据,并对其进行预解析,以便在查询时更快地访问和操作。 + +然而,MySQL 提供的 `JSON` 数据类型在功能和内部实现上与 PostgreSQL 的 `jsonb` 有很多相似之处,尤其是在查询数据方面。 + +MySQL 的 `JSON` 数据类型于 MySQL 5.7 版本引入,其特点如下: + +1. **二进制存储**:与 PostgreSQL 的 `jsonb` 类似,MySQL 的 `JSON` 类型数据也是以**内部二进制格式**存储的,而不是简单的文本字符串。这使得 JSON 数据的读取和操作效率更高,因为数据库不必在每次查询时都解析文本格式的 JSON 字符串。 +2. **自动验证**:当您插入或更新 `JSON` 列时,MySQL 会自动验证其内容是否是有效的 JSON 文档。如果不是,则会抛出错误。 +3. **优化存储**:二进制格式的存储也对空间进行了优化,通常比存储原始文本格式的 JSON 更加紧凑。 + +MySQL 提供了一系列强大的函数和操作符来查询和操作 `JSON` 数据,这些功能与您对 `jsonb` 的期望非常相似: + +1. **`->` (JSON Extract Operator)**:用于从 JSON 文档中提取值。它返回一个 JSON 值。 + + ```sql + SELECT my_json_column->'$.key' FROM my_table; + -- 示例:提取 user 对象的 name 属性 + -- 假设 my_json_column 存储 {'user': {'name': 'Alice'}} + SELECT json_data->'$.user.name' FROM my_table; + ``` + +2. **`->>` (JSON Unquote Operator)**:用于从 JSON 文档中提取值并**自动取消引用 (unquote)**,通常返回一个标量值(如字符串、数字)。这等同于 `JSON_UNQUOTE(JSON_EXTRACT(...))`。 + + ```sql + SELECT my_json_column->>'$.key' FROM my_table; + -- 示例:提取 user 对象的 name 属性(直接返回字符串 'Alice') + SELECT json_data->>'$.user.name' FROM my_table; + ``` + +3. **`JSON_EXTRACT(json_doc, path, ...)`**:显式地从 JSON 文档中提取数据。 + + ```sql + SELECT JSON_EXTRACT(my_json_column, '$.key') FROM my_table; + ``` + +4. **`JSON_CONTAINS(json_doc, candidate, path)`**:检查 JSON 文档中是否包含指定的值。 + + ```sql + -- 检查tags数组是否包含 'backend' + -- 假设 my_json_column 存储 {'tags': ['frontend', 'backend']} + SELECT * FROM my_table WHERE JSON_CONTAINS(json_data->'$.tags', '"backend"'); + ``` + +5. **`JSON_SEARCH(json_doc, one_or_all, search_str, escape_char, path, ...)`**:返回指定字符串在 JSON 文档中的路径。 + + ```sql + -- 查找值为 'test' 的路径 + SELECT JSON_SEARCH(my_json_column, 'one', 'test') FROM my_table; + ``` + +6. **`JSON_TABLE(json_doc, path COLUMNS ...) ` (MySQL 8.0 及更高版本)**:这是一个非常强大的函数,可以将 JSON 数据 "展开" 成关系型的行和列,非常适合进行复杂查询和报表。 + + ```sql + -- 假设 json_data 存储 {'items': [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]} + SELECT * + FROM my_table, + JSON_TABLE(json_data, '$.items[*]' COLUMNS( + itemId INT PATH '$.id', + itemName VARCHAR(50) PATH '$.name' + )) AS jt; + ``` + +像 PostgreSQL 的 `jsonb` 一样,为了对 JSON 字段进行高效查询,您通常需要创建索引。由于 JSON 字段的内容是动态的,MySQL 不直接支持在 JSON 字段的某个内部路径上直接创建传统 B-tree 索引。但是,可以通过 **虚拟列 (Virtual Generated Columns)** 来实现: + +1. **创建虚拟列**:定义一个虚拟列,其值是从 JSON 字段中提取的特定路径。 + + ```sql + ALTER TABLE my_table + ADD COLUMN user_name VARCHAR(255) AS (json_data->>'$.user.name') VIRTUAL; + ``` + +2. **在虚拟列上创建索引**:这样,当您查询 `WHERE json_data->>'$.user.name' = 'Alice'` 时,MySQL 优化器可以选择使用 `idx_user_name` 索引,从而大大提高查询性能。 + + ```sql + CREATE INDEX idx_user_name ON my_table (user_name); + ``` + +尽管 MySQL 没有 `jsonb` 这个名字,但其 `JSON` 数据类型提供了高度相似的功能:二进制存储优化、自动验证和丰富的查询操作符及函数。通过结合虚拟列和索引,MySQL 可以在处理 JSON 数据时提供与 PostgreSQL 的 `jsonb` 相似的查询性能和灵活性。 diff --git a/docs/zh-hans/blog/contents/version-control-and-code-review.md b/docs/zh-hans/blog/contents/version-control-and-code-review.md new file mode 100644 index 0000000..83a6e76 --- /dev/null +++ b/docs/zh-hans/blog/contents/version-control-and-code-review.md @@ -0,0 +1,36 @@ +--- +title: 版本控制与代码审查 +tags: + - git + - code-review + - best-practice + - workflow +author: + name: Zihlu Wang + email: real@zihluwang.me +--- + +## GitFlow 工作流 + +版本控制将使用 GitFlow 分支模型,包括 `main`、`develop`、`feature`、`release` 和 `hotfix` 分支。 + +- `main`: 生产就绪代码。只有 `release` 和 `hotfix` 分支会合并到 `main`。 +- `develop`: 即将开发的功能集成分支。 +- `feature/*`: 用于新功能的分支,从 `develop` 分支出来。 +- `release/*`: 用于准备新生产版本的分支,从 `develop` 分支出来。 +- `hotfix/*`: 用于紧急生产错误修复的分支,从 `main` 分支出来。 + +## 拉取请求 (PRs) + +所有代码更改(直接推送到功能分支除外)都必须通过拉取请求提交。 + +## 代码审查 + +- 每个拉取请求必须由至少一名其他开发人员进行审查。 +- 审查者负责检查是否符合本代码标准、代码质量、逻辑正确性和测试覆盖率。 +- 在创建 PR 之前,应在本地运行 IntelliJ IDEA 的集成代码分析工具。 + +## 提交消息 + +编写清晰、简洁、描述性的提交消息,解释更改了什么以及为什么进行更改。如果可能,遵循约定式提交格式(例如, +`feat: add user registration endpoint`)。