chore: remove blog, open-source and news sections
- Delete the blogs, opensource-projects and notifications trees - Drop their nav entries and i18n keys - Point the nav at the new open-source site instead - Drop the opensource-projects sitemap priority
This commit is contained in:
92 files changed
+20
-8112
No files matched your search
+1
-12
@@ -14,19 +14,8 @@
|
||||
"link": "/services/",
|
||||
"activeMatch": "/services/"
|
||||
},
|
||||
{
|
||||
"text": "nav.insights",
|
||||
"link": "/blogs/",
|
||||
"activeMatch": "/blogs/"
|
||||
},
|
||||
{
|
||||
"text": "nav.news",
|
||||
"link": "/notifications/",
|
||||
"activeMatch": "/notifications/"
|
||||
},
|
||||
{
|
||||
"text": "nav.opensource",
|
||||
"link": "/opensource-projects/",
|
||||
"activeMatch": "/opensource-projects/"
|
||||
"link": "https://opensource.onixbyte.com"
|
||||
}
|
||||
]
|
||||
@@ -1,33 +0,0 @@
|
||||
[
|
||||
"index",
|
||||
{ "type": "section-header", "label": "insights.group.frontend" },
|
||||
{ "type": "file", "name": "ecmascript-2025-syntax-sugar" },
|
||||
{ "type": "file", "name": "frontend-development-standards" },
|
||||
{ "type": "file", "name": "frontend-tips-and-solutions" },
|
||||
{ "type": "file", "name": "font-size-conversion-table" },
|
||||
{ "type": "section-header", "label": "insights.group.backend" },
|
||||
{ "type": "file", "name": "java-development-standards" },
|
||||
{ "type": "file", "name": "java-development-cheatsheet" },
|
||||
{ "type": "file", "name": "mybatis-flex" },
|
||||
{ "type": "file", "name": "postgresql-zhparser-fuzzy-search" },
|
||||
{ "type": "file", "name": "use-json-in-mysql" },
|
||||
{ "type": "section-header", "label": "insights.group.devops" },
|
||||
{ "type": "file", "name": "docker-deployment-standards" },
|
||||
{ "type": "file", "name": "gitlab-ops" },
|
||||
{ "type": "file", "name": "setup-ldap-service" },
|
||||
{ "type": "file", "name": "minio-admin-guide" },
|
||||
{ "type": "section-header", "label": "insights.group.practice" },
|
||||
{ "type": "file", "name": "general-development-standards" },
|
||||
{ "type": "file", "name": "google-code-review-standards" },
|
||||
{ "type": "file", "name": "version-control-and-code-review" },
|
||||
{ "type": "file", "name": "git-cheatsheet" },
|
||||
{ "type": "section-header", "label": "insights.group.tooling" },
|
||||
{ "type": "file", "name": "fix-idea-application-yaml-schema-validation" },
|
||||
{ "type": "file", "name": "windows-driver-backup-restore" },
|
||||
{ "type": "file", "name": "fix-macos-monterey-sleep-wake" },
|
||||
{ "type": "file", "name": "macos-private-dns-hostname" },
|
||||
{ "type": "file", "name": "quartile-method" },
|
||||
{ "type": "file", "name": "use-of-gravatar" },
|
||||
{ "type": "file", "name": "email-like-a-boss" },
|
||||
{ "type": "file", "name": "cooking-seasoning-guide" }
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
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 |
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
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`
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
---
|
||||
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?
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
title: Fix IntelliJ IDEA Missing Spring Boot application.yaml Code Completion
|
||||
tags:
|
||||
- intellij-idea
|
||||
- spring-boot
|
||||
- yaml
|
||||
- schema-validation
|
||||
- troubleshooting
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
import { Kbd } from "@rspress/core/theme"
|
||||
|
||||
Recently, while developing a Spring Boot project in IntelliJ IDEA, I noticed that the smart code completion for `application.yaml` and `application.yml` had suddenly vanished. Worse, the editor was flooded with warnings reading:
|
||||
|
||||
> Schema validation: Missing required property 'kind' = 'Application'
|
||||
|
||||
## The Symptom
|
||||
|
||||
When opening any `application.yaml` or `application.yml` under `src/main/resources/`, IDEA would treat it as an Enonic XP application descriptor rather than a Spring Boot configuration file. All Spring-specific property hints were gone, replaced by schema validation errors complaining about a missing `kind` property — a field that has nothing to do with Spring Boot.
|
||||
|
||||
Restarting IDEA, invalidating caches, and re-importing the project made no difference. The problem persisted across restarts.
|
||||
|
||||
## The Quick Fix
|
||||
|
||||
After some digging, I found the culprit. IDEA downloads JSON Schemas from remote sources (specifically, the [SchemaStore](https://www.schemastore.org/) catalogue) and uses them to validate configuration files. A recent schema update had introduced a `fileMatch` pattern that was too aggressive.
|
||||
|
||||
To disable this behaviour immediately:
|
||||
|
||||
1. Open **Settings** (<Kbd>Ctrl</Kbd>+<Kbd>Alt</Kbd>+<Kbd>S</Kbd> for Windows / <Kbd mac="cmd" /> + <Kbd>,</Kbd> for macOS)
|
||||
2. Navigate to **Languages & Frameworks → Schemas and DTDs → Remote JSON Schemas**
|
||||
3. Uncheck **Allow downloading JSON Schemas from remote sources**
|
||||
4. Click **OK**
|
||||
|
||||
Your Spring Boot configuration hints will return instantly.
|
||||
|
||||
## Root Cause
|
||||
|
||||
The real culprit traces back to **19 May 2026**, when a developer from [Enonic](https://www.enonic.com/) submitted a pull request to the SchemaStore repository ([PR #5704](https://github.com/SchemaStore/schemastore/pull/5704)) that added the Enonic XP application descriptor schema. The schema's `fileMatch` property was set to:
|
||||
|
||||
```json
|
||||
"fileMatch": [
|
||||
"**/src/main/resources/application.yaml",
|
||||
"**/src/main/resources/application.yml"
|
||||
]
|
||||
```
|
||||
|
||||
This pattern is far too broad — it matches **every** `application.yaml` and `application.yml` placed under `src/main/resources/`, which happens to be exactly where Spring Boot projects store their configuration files. Once this schema was merged into SchemaStore, any IntelliJ IDEA instance with remote JSON schema downloading enabled would fetch it and begin validating Spring Boot configs against the Enonic XP schema.
|
||||
|
||||
The developer has since acknowledged the issue and submitted a follow-up fix ([PR #5711](https://github.com/SchemaStore/schemastore/pull/5711)) to tighten the `fileMatch` patterns. As of writing, the fix has not yet been merged into SchemaStore.
|
||||
|
||||
## Why This Matters
|
||||
|
||||
SchemaStore is a community-maintained catalogue of JSON Schemas used by many editors (VS Code, IntelliJ IDEA, Neovim, etc.) to provide validation and autocompletion for common configuration files. When a schema in this catalogue declares overly broad matching rules, it affects users across all platforms and editors — not just IDEA.
|
||||
|
||||
For Spring Boot developers, the takeaway is simple: if your `application.yaml` hints disappear overnight, check what schema your IDE thinks the file belongs to. It might not be Spring Boot at all.
|
||||
@@ -1,40 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,30 +0,0 @@
|
||||
---
|
||||
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 |
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,75 +0,0 @@
|
||||
---
|
||||
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(
|
||||
<StrictMode>
|
||||
<ReduxProvider store={store}>
|
||||
<PersistGate loading={null} persistor={persistor}>
|
||||
{/* Note: StyleProvider must be the parent of ConfigProvider!!! */}
|
||||
<StyleProvider layer>
|
||||
<AntConfigProvider
|
||||
locale={AntSimplifiedChinese}
|
||||
button={{
|
||||
autoInsertSpace: false,
|
||||
}}>
|
||||
<AntApp className="h-full w-full">
|
||||
<RouterProvider router={router} />
|
||||
</AntApp>
|
||||
</AntConfigProvider>
|
||||
</StyleProvider>
|
||||
</PersistGate>
|
||||
</ReduxProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
```
|
||||
|
||||
## 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 "../../../node_modules/.pnpm/tailwindcss@4.3.0/node_modules/tailwindcss/dist/lib.d.mts";
|
||||
```
|
||||
|
||||
```typescript
|
||||
<StyleProvider>
|
||||
<ConfigProvider>
|
||||
<App>
|
||||
<RouterProvider/>
|
||||
</App>
|
||||
</ConfigProvider>
|
||||
</StyleProvider>
|
||||
```
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,161 +0,0 @@
|
||||
---
|
||||
title: Git Cheatsheet
|
||||
tags:
|
||||
- git
|
||||
- tips
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
## Ignoring Local Modifications to Tracked Files: `git update-index --skip-worktree`
|
||||
|
||||
When you need to modify a Git-tracked file locally but don't want those changes to show up in `git status` or in commits (e.g. a local `application-local.yml`, `.env`, or IDE configuration), you can use `--skip-worktree` to make Git ignore local modifications to that file.
|
||||
|
||||
### Marking and Unmarking
|
||||
|
||||
```bash
|
||||
# Make Git ignore local modifications to the file
|
||||
git update-index --skip-worktree <file>
|
||||
|
||||
# Unmark the file and track it again
|
||||
git update-index --no-skip-worktree <file>
|
||||
```
|
||||
|
||||
### Listing Ignored Files
|
||||
|
||||
Marked files are shown with an `S` flag in `git ls-files -v`:
|
||||
|
||||
```bash
|
||||
git ls-files -v | grep '^S'
|
||||
```
|
||||
|
||||
### Committing Changes to a Marked File
|
||||
|
||||
To commit new changes to the file, unmark it first, commit, then mark it again:
|
||||
|
||||
```bash
|
||||
git update-index --no-skip-worktree <file>
|
||||
git add <file>
|
||||
git commit -m "chore: update config"
|
||||
git update-index --skip-worktree <file>
|
||||
```
|
||||
|
||||
### `--skip-worktree` vs `--assume-unchanged`
|
||||
|
||||
Both options stop Git from reporting modifications to tracked files, but they serve different purposes:
|
||||
|
||||
| Option | Purpose |
|
||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||
| `--assume-unchanged` | A performance optimisation for large files that rarely change; Git may re-track the file once it notices a change |
|
||||
| `--skip-worktree` | Deliberately ignore local changes for "keep local modifications, don't commit" scenarios; more persistent |
|
||||
|
||||
So for "ignoring local changes", use `--skip-worktree`.
|
||||
|
||||
### Caveats
|
||||
|
||||
- `--skip-worktree` only affects the working tree — it is not a permanent ignore. Operations that rewrite the file, such as `git pull`, `git checkout`, or `git stash`, may overwrite your local changes or cause a conflict. If the file is also likely to change upstream, unmark it before pulling.
|
||||
- It only applies to **tracked** files; for untracked files, use `.gitignore`.
|
||||
|
||||
## Stashing Unfinished Work: `git stash`
|
||||
|
||||
When you need to switch branches mid-task, use `git stash` to set your working-tree changes aside:
|
||||
|
||||
```bash
|
||||
git stash push -m "work in progress" # save with a message
|
||||
git stash -u # include untracked files
|
||||
git stash list # list saved stashes
|
||||
git stash pop # restore and drop the stash
|
||||
git stash apply # restore but keep the stash
|
||||
git stash drop # delete a stash
|
||||
```
|
||||
|
||||
## Amending the Latest Commit: `git commit --amend`
|
||||
|
||||
When you commit and then realise you forgot a file or want to change the message:
|
||||
|
||||
```bash
|
||||
git add <forgotten-file>
|
||||
git commit --amend # keep the same message, fold in new changes
|
||||
git commit --amend -m "new message" # also change the message
|
||||
```
|
||||
|
||||
Note: if the commit has already been pushed, prefer adding a new commit instead of rewriting shared history.
|
||||
|
||||
## Undoing Changes: `git restore` and `git reset`
|
||||
|
||||
```bash
|
||||
git restore <file> # discard unstaged working-tree changes
|
||||
git restore --staged <file> # unstage but keep the changes (like git reset HEAD <file>)
|
||||
git restore --source=HEAD <file> # restore the file to its HEAD version
|
||||
```
|
||||
|
||||
To discard all uncommitted changes (including the staging area):
|
||||
|
||||
```bash
|
||||
git reset --hard HEAD
|
||||
```
|
||||
|
||||
`--hard` also resets the working tree, so double-check that nothing you want to keep would be lost.
|
||||
|
||||
## Recovering Lost Commits: `git reflog`
|
||||
|
||||
When a commit seems to have vanished after a bad `git reset --hard` or `git rebase`, `reflog` records every place HEAD has been:
|
||||
|
||||
```bash
|
||||
git reflog
|
||||
git reset --hard HEAD@{2} # go back to an earlier recorded position
|
||||
```
|
||||
|
||||
As long as the commit still exists in the object database, reflog can get you back to it.
|
||||
|
||||
## Bisecting a Regression: `git bisect`
|
||||
|
||||
When you don't know which commit introduced a bug, `git bisect` uses binary search to locate it:
|
||||
|
||||
```bash
|
||||
git bisect start
|
||||
git bisect bad # the current commit is bad
|
||||
git bisect good <sha> # mark a known-good commit
|
||||
```
|
||||
|
||||
Git checks out a midpoint commit; you just mark each as `good` or `bad` until it narrows down the offending commit:
|
||||
|
||||
```bash
|
||||
git bisect good # or git bisect bad
|
||||
git bisect reset # finish and return to your branch
|
||||
```
|
||||
|
||||
## Friendlier History: `git log`
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --decorate --all # graph view across all branches
|
||||
git log -S "some-string" --all # find commits that added/removed a string
|
||||
git blame <file> # show the last modifier of each line
|
||||
git blame -L 10,20 <file> # restrict to a line range
|
||||
```
|
||||
|
||||
## Cleaning Up Untracked Files: `git clean`
|
||||
|
||||
```bash
|
||||
git clean -n -fd # preview which files would be removed (always run this first)
|
||||
git clean -fd # delete untracked files and directories
|
||||
```
|
||||
|
||||
`-n` only shows what would be deleted without deleting it — preview before you execute.
|
||||
|
||||
## Picking Commits: `git cherry-pick`
|
||||
|
||||
Apply a specific commit from another branch onto your current branch:
|
||||
|
||||
```bash
|
||||
git cherry-pick <sha>
|
||||
```
|
||||
|
||||
## Deleting Merged Local Branches
|
||||
|
||||
```bash
|
||||
git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
|
||||
```
|
||||
|
||||
This deletes all local branches merged into the current branch (except the current one). Preview with `git branch --merged` first.
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
title: GitLab Operations
|
||||
tags:
|
||||
- gitlab
|
||||
- devops
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
## 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%.
|
||||
|
||||
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
|
||||
+ SWITCHER_MINIMUM_TRANSLATION_LEVEL = 80
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```diff
|
||||
+ = link_to _("Your ICP Filing Number"), "https://beian.miit.gov.cn/", target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
|
||||
```
|
||||
|
||||
## Upgrading GitLab
|
||||
|
||||
### 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.
|
||||
|
||||
### Stopping Memory-Intensive Services
|
||||
|
||||
```shell
|
||||
gitlab-ctl stop puma
|
||||
gitlab-ctl stop sidekiq
|
||||
```
|
||||
|
||||
### Running a Backup
|
||||
|
||||
```shell
|
||||
# Example: skip build artefacts and container registry, backing up only the database and repositories
|
||||
sudo gitlab-backup create SKIP=artifacts,registry
|
||||
```
|
||||
|
||||
### Installing
|
||||
|
||||
```shell
|
||||
sudo dpkg -i gitlab-package.deb
|
||||
```
|
||||
@@ -1,131 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: Insights
|
||||
---
|
||||
|
||||
# Insights
|
||||
|
||||
Practical guides, cheatsheets, and standards distilled from daily engineering
|
||||
work.
|
||||
|
||||
## Topics
|
||||
|
||||
- **Frontend** — React patterns, TailwindCSS tips, ECMAScript syntax guides, browser quirks
|
||||
- **Backend & Database** — Java development, MyBatis, PostgreSQL, MySQL JSON
|
||||
- **DevOps & Infrastructure** — Docker, GitLab CI/CD, LDAP, MinIO, macOS troubleshooting
|
||||
- **Engineering Standards** — code review, version control, general and language-specific conventions
|
||||
- **Tools & Productivity** — email etiquette, font sizing, data analysis methods
|
||||
|
||||
Browse the sidebar or scroll through the posts below — each article is self-contained and written to be immediately useful.
|
||||
@@ -1,282 +0,0 @@
|
||||
---
|
||||
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<User> findById(long id);
|
||||
|
||||
// Option 2: JOIN FETCH
|
||||
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
|
||||
Optional<User> 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<User, Long>,
|
||||
JpaSpecificationExecutor<User> {
|
||||
}
|
||||
|
||||
// Usage
|
||||
Specification<User> spec = (root, query, cb) -> {
|
||||
List<Predicate> 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<User> page = userRepository.findAll(spec, pageable);
|
||||
```
|
||||
|
||||
## Flattening Objects or Maps into JSON with Jackson
|
||||
|
||||
When serialising to JSON, you sometimes need to "flatten" a nested object's properties into the parent JSON rather than having them appear as a nested field. Jackson provides two annotations for handling objects and `Map` flattening separately.
|
||||
|
||||
### `@JsonUnwrapped` — flattening objects with a known structure
|
||||
|
||||
`@JsonUnwrapped` expands the properties of a nested object directly into the parent JSON:
|
||||
|
||||
```java
|
||||
public class Address {
|
||||
private String street;
|
||||
private String city;
|
||||
// getters and setters
|
||||
}
|
||||
|
||||
public class User {
|
||||
private String name;
|
||||
|
||||
@JsonUnwrapped
|
||||
private Address address;
|
||||
// getters and setters
|
||||
}
|
||||
```
|
||||
|
||||
Serialised output:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "John Doe",
|
||||
"street": "Oxford Street",
|
||||
"city": "London"
|
||||
}
|
||||
```
|
||||
|
||||
Without `@JsonUnwrapped`, `address` would appear as a nested JSON object: `"address": { "street": "...", "city": "..." }`.
|
||||
|
||||
### `@JsonAnyGetter` — flattening a Map with dynamic keys
|
||||
|
||||
`@JsonAnyGetter` flattens a `Map`'s key-value pairs directly into the parent JSON, ideal for dynamic or open-ended fields:
|
||||
|
||||
```java
|
||||
public class Product {
|
||||
private String name;
|
||||
|
||||
private Map<String, Object> attributes = new HashMap<>();
|
||||
|
||||
@JsonAnyGetter
|
||||
public Map<String, Object> getAttributes() {
|
||||
return attributes;
|
||||
}
|
||||
// other getters and setters
|
||||
}
|
||||
```
|
||||
|
||||
With `attributes` containing `{"colour": "red", "size": "XL"}`, the serialised output is:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Example Product",
|
||||
"colour": "red",
|
||||
"size": "XL"
|
||||
}
|
||||
```
|
||||
|
||||
Note that `@JsonAnyGetter` must be placed on the **getter method**, not the field itself. You will also need `@JsonAnySetter` to support deserialisation.
|
||||
|
||||
### How to choose
|
||||
|
||||
| Scenario | Annotation |
|
||||
| --------------------------------------------------------------- | ---------------- |
|
||||
| Flatten nested objects with fixed fields (e.g. address, config) | `@JsonUnwrapped` |
|
||||
| Flatten dynamic key-value pairs with unknown keys | `@JsonAnyGetter` |
|
||||
@@ -1,238 +0,0 @@
|
||||
---
|
||||
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<T>` 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 `<p>` to separate paragraphs.
|
||||
|
||||
```java
|
||||
/**
|
||||
* This is the first paragraph of the Javadoc.
|
||||
* <p>
|
||||
* 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.
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
title: Fix macOS Terminal Host Name Showing IP Segments Under Private DNS
|
||||
tags:
|
||||
- macos
|
||||
- dns
|
||||
- terminal
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
In some enterprise or home private network environments, reverse DNS lookups may resolve a device's private IP to a hostname starting with `192`, `172`, or `10`. When this happens, the macOS terminal prompt changes from the normal `user@MacBook-Pro` to something like `user@192-168-1-100`, which can be distracting.
|
||||
|
||||
## Cause
|
||||
|
||||
When starting a terminal session, macOS performs a reverse DNS lookup to determine the hostname for the current IP address. If the private DNS server returns a hostname derived from IP octets (e.g. `192-168-1-100.example.com`), the system adopts it as the Host Name and the terminal prompt reflects it.
|
||||
|
||||
## Fix
|
||||
|
||||
Use the built-in `scutil` (System Configuration Utility) to pin the Host Name to your preferred value.
|
||||
|
||||
### Check Current State
|
||||
|
||||
```shell
|
||||
# View the current Host Name (may be empty or overridden by DNS)
|
||||
scutil --get HostName
|
||||
|
||||
# View the local Bonjour name
|
||||
scutil --get LocalHostName
|
||||
|
||||
# View the computer name shown in Finder
|
||||
scutil --get ComputerName
|
||||
```
|
||||
|
||||
### Set the Host Name
|
||||
|
||||
```shell
|
||||
sudo scutil --set HostName "MacBook-Pro"
|
||||
```
|
||||
|
||||
Prefer a name without spaces or special characters, such as `MacBook-Pro`, `My-Mac`, or your device serial number.
|
||||
|
||||
### Verify
|
||||
|
||||
Open a new terminal window — the value after `@` in the prompt should now show your chosen hostname.
|
||||
|
||||
```shell
|
||||
scutil --get HostName
|
||||
# Output: MacBook-Pro
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `HostName` only affects the network-level hostname. `LocalHostName` (Bonjour) and `ComputerName` (Finder display) are managed independently.
|
||||
- If the issue returns after a reboot, check `/etc/hosts` for conflicting entries or verify whether the DHCP/DNS server continues to push an undesired hostname.
|
||||
@@ -1,200 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,31 +0,0 @@
|
||||
---
|
||||
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" }
|
||||
```
|
||||
@@ -1,220 +0,0 @@
|
||||
---
|
||||
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
|
||||
|
||||
<!-- TODO(link): this tip used to offer OnixByte's pre-compiled Windows build of
|
||||
scws. Both URLs were dead — the GitHub one referenced an account that does not
|
||||
exist, and the Gitea one is being retired. Pointing at upstream in the meantime;
|
||||
restore a download link here if one is published again. -->
|
||||
|
||||
:::tip
|
||||
The native scws command-line tool for Windows is compiled with MingW. Source and
|
||||
build instructions are available from the upstream
|
||||
[SCWS project](https://github.com/hightman/scws).
|
||||
:::
|
||||
|
||||
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.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,140 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,89 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: Use Gravatar in First-party Systems
|
||||
tags:
|
||||
- gravatar
|
||||
- avatar
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
[Gravatar](https://gravatar.com) (Globally Recognised Avatar) is a service that associates avatar images with email
|
||||
addresses. When a user registers on your platform with their email, you can display their Gravatar as a default avatar
|
||||
without building your own image upload and storage pipeline.
|
||||
|
||||
## How It Works
|
||||
|
||||
Gravatar exposes a simple HTTP endpoint. You compute the SHA256 hash of the user's **lowercased and trimmed** email
|
||||
address, then embed it in an image URL:
|
||||
|
||||
```
|
||||
https://gravatar.com/avatar/<hash>
|
||||
```
|
||||
|
||||
## Generating the Hash
|
||||
|
||||
### Node.js
|
||||
|
||||
```js
|
||||
import { createHash } from "node:crypto"
|
||||
|
||||
const email = "user@example.com".trim().toLowerCase()
|
||||
const hash = createHash("sha256").update(email).digest("hex")
|
||||
const url = `https://gravatar.com/avatar/${hash}`
|
||||
```
|
||||
|
||||
### Python
|
||||
|
||||
```python
|
||||
import hashlib
|
||||
|
||||
email = "user@example.com".strip().lower()
|
||||
hash = hashlib.sha256(email.encode()).hexdigest()
|
||||
url = f"https://gravatar.com/avatar/{hash}"
|
||||
```
|
||||
|
||||
### Shell
|
||||
|
||||
```shell
|
||||
echo -n "user@example.com" | tr '[:upper:]' '[:lower:]' | sha256sum | cut -d ' ' -f1
|
||||
```
|
||||
|
||||
## URL Parameters
|
||||
|
||||
The `/avatar/` endpoint accepts several query parameters to customise the result:
|
||||
|
||||
| Parameter | Description | Example |
|
||||
| --------- | --------------------------------------- | -------------- |
|
||||
| `s` | Size in pixels (default: 80) | `?s=200` |
|
||||
| `d` | Default image when no Gravatar is found | `?d=identicon` |
|
||||
| `r` | Content rating (`g`, `pg`, `r`, `x`) | `?r=g` |
|
||||
|
||||
### Default Image Options (`d`)
|
||||
|
||||
- `identicon` — a geometric pattern based on the hash
|
||||
- `robohash` — a generated robot image
|
||||
- `retro` — an 8-bit style pixelated face
|
||||
- `monsterid` — a generated monster cartoon
|
||||
- `wavatar` — a generated face
|
||||
- `mp` — a generic silhouette (Mystery Person)
|
||||
- `blank` — a transparent PNG
|
||||
- A custom URL (must be URL-encoded)
|
||||
|
||||
### Putting It Together
|
||||
|
||||
```html
|
||||
<img
|
||||
src="https://gravatar.com/avatar/a3b4c5d6e7f8?s=160&d=robohash&r=g"
|
||||
alt="User avatar"
|
||||
width="160"
|
||||
height="160" />
|
||||
```
|
||||
|
||||
Always pass the `d` parameter to avoid broken images for users who have not set up a Gravatar. `identicon` and
|
||||
`robohash` are popular choices because they generate a unique, recognisable image for every hash.
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
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`).
|
||||
@@ -1,85 +0,0 @@
|
||||
---
|
||||
title: Backup and Restore Windows Drivers When Reinstalling the System
|
||||
tags:
|
||||
- windows
|
||||
- driver
|
||||
- backup
|
||||
- system-reinstall
|
||||
- dism
|
||||
author:
|
||||
name: Zihlu Wang
|
||||
email: real@zihluwang.me
|
||||
---
|
||||
|
||||
import { Kbd } from "@rspress/core/theme"
|
||||
|
||||
One of the most tedious parts of reinstalling Windows is getting all the drivers back — graphics, network, audio, Bluetooth, touchpad, chipset — missing any one of them disrupts your workflow. Windows Update can fetch some automatically, but the versions are often outdated, and niche hardware drivers may not be found at all.
|
||||
|
||||
Fortunately, Windows' built-in DISM tool provides driver export and restore functionality. Spend one minute backing up before the reinstall, and you can restore every driver in a single command afterwards.
|
||||
|
||||
## Before Reinstalling: Back Up All Drivers
|
||||
|
||||
Open a terminal as **Administrator** (PowerShell or Command Prompt) and run:
|
||||
|
||||
```powershell
|
||||
dism /online /export-driver /destination:D:\DriverBackup
|
||||
```
|
||||
|
||||
Replace `D:\DriverBackup` with your desired backup path. It is best to save the backup to an external drive, USB stick, or a non-system partition so it survives the reformat.
|
||||
|
||||
The command enumerates every third-party driver installed on the system and exports them to the destination folder. The whole process typically completes within a minute. You should see output similar to:
|
||||
|
||||
```
|
||||
The operation completed successfully.
|
||||
```
|
||||
|
||||
Keep the backup directory safe, and you are ready to reinstall.
|
||||
|
||||
### What gets backed up?
|
||||
|
||||
The command exports **third-party drivers** — i.e. drivers that are not built into Windows. This covers graphics cards, audio, network adapters, chipsets, Bluetooth, card readers, and any other drivers you have installed over the lifetime of the system. Generic inbox drivers that ship with Windows are skipped, as they will be restored automatically after the reinstall.
|
||||
|
||||
## After Reinstalling: Restore All Drivers
|
||||
|
||||
### Method 1: One-command restore (recommended)
|
||||
|
||||
This is the most convenient approach. Open a terminal as **Administrator** and run:
|
||||
|
||||
```powershell
|
||||
dism /online /add-driver /driver:D:\DriverBackup /recurse
|
||||
```
|
||||
|
||||
The `/recurse` flag tells DISM to search all subfolders within the backup directory and install every driver it finds. **You must restart the computer** afterwards for the changes to take effect.
|
||||
|
||||
### Method 2: Via Device Manager (GUI)
|
||||
|
||||
If you prefer a graphical interface or only need to restore specific drivers:
|
||||
|
||||
1. Press <Kbd>Win</Kbd> + <Kbd>X</Kbd> and select **Device Manager**
|
||||
2. Locate devices marked with a yellow exclamation mark (missing drivers)
|
||||
3. Right-click the device and choose **Update driver**
|
||||
4. Select **Browse my computer for drivers**
|
||||
5. Point the path to your driver backup directory and tick **Include subfolders**
|
||||
6. Click Next — Windows will search and install the matching driver
|
||||
|
||||
Repeat for each device until all are restored.
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "The driver package could not be installed"
|
||||
|
||||
In rare cases, certain drivers may fail to install via DISM due to signature issues or version incompatibility. As a workaround, locate the corresponding `.inf` file in the backup directory, right-click it, and select **Install**.
|
||||
|
||||
### Some devices still lack drivers after restore
|
||||
|
||||
DISM only backs up drivers that were currently installed. If a device was already undriven before the backup, its driver will obviously not be included. It is worth checking Device Manager beforehand to ensure all hardware is properly functional.
|
||||
|
||||
### Can I restore across Windows versions?
|
||||
|
||||
Restoring drivers from Windows 10 to Windows 11 on the same machine is generally viable, but migrating driver backups across different hardware is not recommended. Each machine should have its own independent backup.
|
||||
|
||||
## Summary
|
||||
|
||||
The key advantage of this workflow is **zero third-party dependencies** — DISM is a built-in Windows component, and no extra software is required. One minute to export before reinstalling, one command to restore afterwards. Far more efficient than manually downloading and installing each driver one by one. Make driver backup a habit before every system reinstall, and each migration will be that much smoother.
|
||||
|
||||
> This guide is based on a video tutorial by Douyin user [@科技碎碎念](https://v.douyin.com/Cbf0ruVdbUM/).
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
title: News
|
||||
---
|
||||
|
||||
## Microsoft Email Delivery Issues (@outlook, @hotmail, @live, @msn and custom domain emails hosted on Microsoft)
|
||||
|
||||
> Updated at _19 May 2026_.
|
||||
|
||||
**Urgent Action Required for Users with Microsoft-Hosted Emails.**
|
||||
|
||||
Please be advised that our automated email service is currently experiencing severe delivery issues to all email addresses hosted by Microsoft. Due to recent aggressive filtering policies by their Exchange Online Protection (EOP), our mail delivery server has been blocked by Microsoft. As a consequence, our system is **unable to send any notifications, password resets, or activation emails** to accounts ending in **@outlook.com**, **@hotmail.com**, **@live.com**, **@msn.com**, or **indeed any custom domains hosted by Microsoft**.
|
||||
|
||||
To ensure uninterrupted service, please follow the guidance below based on your account status:
|
||||
|
||||
- For Prospective Users (Before Registration): Please **do not** use a Microsoft-hosted email address to register your account. You will not receive the essential activation link. Kindly use an alternative email service provider, such as your corporate email, Gmail, etc. to complete your registration.
|
||||
- For Existing Registered Users: If your profile is currently linked to an Outlook or Hotmail address, please log in immediately and update your primary email address. You can change this by navigating to your **[User Settings -> Preferences -> Emails](https://github.com/settings/emails)** page. Failing to do so will result in missing critical repository updates, CI/CD pipeline failure logs, and Merge Request notifications.
|
||||
|
||||
We are actively working to appeal this block with the Microsoft Postmaster Centre. We sincerely apologise for any inconvenience caused and appreciate your prompt cooperation in updating your contact details.
|
||||
@@ -1,14 +0,0 @@
|
||||
[
|
||||
"index",
|
||||
{ "type": "section-header", "label": "opensource.group.libraries" },
|
||||
{ "type": "dir", "name": "onixbyte-toolbox" },
|
||||
{ "type": "dir", "name": "regions4j" },
|
||||
{ "type": "dir", "name": "captcha" },
|
||||
{ "type": "dir", "name": "calendar-toolbox" },
|
||||
{ "type": "section-header", "label": "opensource.group.applications" },
|
||||
{ "type": "dir", "name": "helix" },
|
||||
{ "type": "dir", "name": "delta-force-guide" },
|
||||
{ "type": "section-header", "label": "opensource.group.tools" },
|
||||
{ "type": "dir", "name": "claude-code-skill-manager" },
|
||||
{ "type": "dir", "name": "vite-plugins" }
|
||||
]
|
||||
@@ -1,390 +0,0 @@
|
||||
---
|
||||
title: Calendar Toolbox API
|
||||
---
|
||||
|
||||
## Package Overview
|
||||
|
||||
The library is organised under the base package `com.onixbyte.calendar` with the following structure:
|
||||
|
||||
| Package | Description |
|
||||
| ------------------------------------------ | ------------------------------------------ |
|
||||
| `com.onixbyte.calendar` | Root calendar object |
|
||||
| `com.onixbyte.calendar.component` | iCalendar components (VEVENT, VTODO, etc.) |
|
||||
| `com.onixbyte.calendar.component.property` | Component-level properties |
|
||||
| `com.onixbyte.calendar.property` | Calendar-level properties |
|
||||
| `com.onixbyte.calendar.parameter` | iCalendar property parameters |
|
||||
| `com.onixbyte.calendar.recurrence` | Recurrence rule types |
|
||||
| `com.onixbyte.calendar.value` | Value types |
|
||||
| `com.onixbyte.calendar.util` | Formatting utilities |
|
||||
|
||||
---
|
||||
|
||||
## Calendar
|
||||
|
||||
The top-level object representing an iCalendar (RFC 5545) VCALENDAR container.
|
||||
|
||||
```java
|
||||
Calendar calendar = Calendar.builder()
|
||||
.withCalendarScale(...)
|
||||
.withMethod(...)
|
||||
.withProductIdentifier(...)
|
||||
.withVersion(...)
|
||||
.withOwner(...)
|
||||
.withPrimaryCalendar(...)
|
||||
.withPublishedTTL(...)
|
||||
.withCalendarDescription(...)
|
||||
.withCalendarName(...)
|
||||
.withCalendarId(...)
|
||||
.withCustomProperties(...)
|
||||
.withComponents(event, todo, ...)
|
||||
.build();
|
||||
|
||||
String icsContent = calendar.formatted();
|
||||
```
|
||||
|
||||
### Calendar Properties
|
||||
|
||||
All calendar-level properties live in `com.onixbyte.calendar.property` and implement `CalendarProperty`.
|
||||
|
||||
| Class | iCalendar Property | Description |
|
||||
| ------------------------ | -------------------- | ----------------------------------------- |
|
||||
| `CalendarScale` | `CALSCALE` | Calendar system (e.g., GREGORIAN) |
|
||||
| `Method` | `METHOD` | iCalendar method (e.g., PUBLISH, REQUEST) |
|
||||
| `ProductIdentifier` | `PRODID` | Product identifier of the calendar |
|
||||
| `Version` | `VERSION` | iCalendar version (e.g., 2.0) |
|
||||
| `Owner` | `X-OWNER` | Owner of the calendar |
|
||||
| `PrimaryCalendar` | `X-PRIMARY-CALENDAR` | Whether this is a primary calendar |
|
||||
| `PublishedTTL` | `X-PUBLISHED-TTL` | Time-to-live for published calendar |
|
||||
| `CalendarDescription` | `X-CALENDAR-DESC` | Calendar description |
|
||||
| `CalendarName` | `X-CALENDAR-NAME` | Calendar name |
|
||||
| `CalendarId` | `X-CALENDAR-ID` | Calendar identifier |
|
||||
| `CustomCalendarProperty` | `X-*` | Custom calendar properties |
|
||||
|
||||
Custom properties are created using a builder:
|
||||
|
||||
```java
|
||||
var customProp = CustomCalendarProperty.builder()
|
||||
.withParameters(...)
|
||||
.build("X-MY-PROPERTY", "my-value");
|
||||
```
|
||||
|
||||
> Note: Custom property names **must** start with `X-`.
|
||||
|
||||
---
|
||||
|
||||
## Components
|
||||
|
||||
Components represent the core entities in an iCalendar object. Each component implements `CalendarComponent` and is rendered between `BEGIN:TYPE` and `END:TYPE` delimiters.
|
||||
|
||||
### Event (VEVENT)
|
||||
|
||||
Represents a scheduled event.
|
||||
|
||||
```java
|
||||
Event event = Event.builder()
|
||||
.withDateTimeStamp(dtstamp) // required
|
||||
.withUniqueIdentifier(uid) // required
|
||||
.withDateTimeStart(dtstart)
|
||||
.withDateTimeEnd(dtend) // mutually exclusive with duration
|
||||
.withDuration(duration) // mutually exclusive with dtend
|
||||
.withSummary(summary)
|
||||
.withDescription(description)
|
||||
.withClassification(classification)
|
||||
.withDateTimeCreated(dateTimeCreated)
|
||||
.withGeographicPosition(geoPos)
|
||||
.withLastModified(lastModified)
|
||||
.withLocation(location)
|
||||
.withOrganiser(organiser)
|
||||
.withPriority(priority)
|
||||
.withSequenceNumber(seqNumber)
|
||||
.withStatus(status)
|
||||
.withTimeTransparency(transparency)
|
||||
.withUniformResourceLocator(url)
|
||||
.withRecurrenceId(recurrenceId)
|
||||
.withRecurrenceRule(rrule)
|
||||
.withAttachments(attachments)
|
||||
.withAttendees(attendees)
|
||||
.withCategories(categories)
|
||||
.withComments(comments)
|
||||
.withContacts(contacts)
|
||||
.withExceptionDateTimes(exDates)
|
||||
.withRequestStatuses(statuses)
|
||||
.withRelated(related)
|
||||
.withResources(resources)
|
||||
.withRecurrenceDateTimes(rDates)
|
||||
.build();
|
||||
```
|
||||
|
||||
### Todo (VTODO)
|
||||
|
||||
Represents a to-do item or task.
|
||||
|
||||
```java
|
||||
Todo todo = Todo.builder()
|
||||
.withDateTimeStamp(dtstamp) // required
|
||||
.withUniqueIdentifier(uid) // required
|
||||
.withSummary(summary)
|
||||
.withDateTimeStart(dtstart)
|
||||
.withDateTimeDue(due) // mutually exclusive with duration
|
||||
.withDuration(duration) // mutually exclusive with due
|
||||
.withDateTimeCompleted(completed)
|
||||
.withPercentComplete(percent)
|
||||
.withClassification(classification)
|
||||
.withDateTimeCreated(created)
|
||||
.withDescription(description)
|
||||
.withGeographicPosition(geoPos)
|
||||
.withLastModified(lastModified)
|
||||
.withLocation(location)
|
||||
.withOrganiser(organiser)
|
||||
.withPriority(priority)
|
||||
.withSequenceNumber(seq)
|
||||
.withStatus(status)
|
||||
.withRecurrenceId(recurId)
|
||||
.withRecurrenceRule(rrule)
|
||||
.withUniformResourceLocator(url)
|
||||
.withAttachments(attachments)
|
||||
.withAttendees(attendees)
|
||||
.withCategories(categories)
|
||||
.withComments(comments)
|
||||
.withContacts(contacts)
|
||||
.withExceptionDateTimes(exDates)
|
||||
.withRequestStatuses(statuses)
|
||||
.withRelatedToList(related)
|
||||
.withResources(resources)
|
||||
.withRecurrenceDateTimes(rDates)
|
||||
.build();
|
||||
```
|
||||
|
||||
### Journal (VJOURNAL)
|
||||
|
||||
Represents a journal entry or diary note.
|
||||
|
||||
```java
|
||||
Journal journal = Journal.builder()
|
||||
.withDateTimeStamp(dtstamp) // required
|
||||
.withUniqueIdentifier(uid) // required
|
||||
.withSummary(summary)
|
||||
.withClassification(classification)
|
||||
.withDateTimeCreated(created)
|
||||
.withDateTimeStart(dtstart)
|
||||
.withLastModified(lastModified)
|
||||
.withOrganiser(organiser)
|
||||
.withRecurrenceId(recurId)
|
||||
.withSequenceNumber(seq)
|
||||
.withStatus(status)
|
||||
.withUniformResourceLocator(url)
|
||||
.withRecurrenceRule(rrule)
|
||||
.withAttachments(attachments)
|
||||
.withAttendees(attendees)
|
||||
.withCategories(categories)
|
||||
.withComments(comments)
|
||||
.withContacts(contacts)
|
||||
.withDescriptions(descriptions)
|
||||
.withExceptionDateTimes(exDates)
|
||||
.withRelatedToList(related)
|
||||
.withRecurrenceDate(rDates)
|
||||
.withRequestStatuses(statuses)
|
||||
.build();
|
||||
```
|
||||
|
||||
### FreeBusy (VFREEBUSY)
|
||||
|
||||
Represents free/busy time information.
|
||||
|
||||
```java
|
||||
FreeBusy freeBusy = FreeBusy.builder()
|
||||
.withDateTimeStamp(dtstamp) // required
|
||||
.withUniqueIdentifier(uid) // required
|
||||
.withContact(contact)
|
||||
.withDateTimeStart(dtstart)
|
||||
.withDateTimeEnd(dtend)
|
||||
.withOrganiser(organiser)
|
||||
.withUniformResourceLocator(url)
|
||||
.withAttendees(attendees)
|
||||
.withComments(comments)
|
||||
.withFreeBusyTimes(freeBusyTimes)
|
||||
.withRequestStatuses(statuses)
|
||||
.build();
|
||||
```
|
||||
|
||||
### TimeZone (VTIMEZONE)
|
||||
|
||||
Defines time zone rules including standard and daylight saving time transitions.
|
||||
|
||||
```java
|
||||
TimeZone timezone = TimeZone.builder()
|
||||
.withTimeZoneIdentifier(tzId)
|
||||
.withLastModified(lastModified)
|
||||
.withTimeZoneUrl(tzUrl)
|
||||
.withTimeZoneProperties(standardProp, daylightProp)
|
||||
.build();
|
||||
```
|
||||
|
||||
Time zone properties define the actual transition rules:
|
||||
|
||||
```java
|
||||
TimeZoneProperty standard = TimeZoneProperty.builder()
|
||||
.withDateTimeStart(dtstart)
|
||||
.withTimeZoneOffsetTo(offsetTo)
|
||||
.withTimeZoneOffsetFrom(offsetFrom)
|
||||
.withRecurrenceRule(rrule)
|
||||
.withTimeZoneNames(tzName)
|
||||
.buildStandard(); // or buildDaylight()
|
||||
|
||||
TimeZoneProperty daylight = TimeZoneProperty.builder()
|
||||
.withDateTimeStart(dtstart)
|
||||
.withTimeZoneOffsetTo(offsetTo)
|
||||
.withTimeZoneOffsetFrom(offsetFrom)
|
||||
.buildDaylight();
|
||||
```
|
||||
|
||||
### Alarm (VALARM)
|
||||
|
||||
Defines alarm/reminder notifications. Three alarm types are supported:
|
||||
|
||||
```java
|
||||
// Audio alarm
|
||||
Alarm audioAlarm = Alarm.builder()
|
||||
.withTrigger(trigger)
|
||||
.withDuration(duration)
|
||||
.withRepeatCount(repeatCount)
|
||||
.withAttachments(audioAttachment)
|
||||
.buildAudio();
|
||||
|
||||
// Display alarm
|
||||
Alarm displayAlarm = Alarm.builder()
|
||||
.withTrigger(trigger)
|
||||
.withDescription(description) // required
|
||||
.withDuration(duration)
|
||||
.withRepeatCount(repeatCount)
|
||||
.buildDisplay();
|
||||
|
||||
// Email alarm
|
||||
Alarm emailAlarm = Alarm.builder()
|
||||
.withTrigger(trigger)
|
||||
.withDescription(description) // required
|
||||
.withSummary(summary) // required
|
||||
.withAttendees(attendees) // required
|
||||
.withDuration(duration)
|
||||
.withRepeatCount(repeatCount)
|
||||
.buildEmail();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Properties
|
||||
|
||||
Component properties live in `com.onixbyte.calendar.component.property` and implement `ComponentProperty`.
|
||||
|
||||
| Class | iCalendar Property | Description |
|
||||
| ------------------------- | ------------------ | -------------------------------------------------------- |
|
||||
| `Action` | `ACTION` | Alarm action type (AUDIO, DISPLAY, EMAIL) |
|
||||
| `Attachment` | `ATTACH` | Document attachment |
|
||||
| `Attendee` | `ATTENDEE` | Event/task attendee |
|
||||
| `Categories` | `CATEGORIES` | Categories or tags |
|
||||
| `Classification` | `CLASS` | Access classification (PUBLIC, PRIVATE, CONFIDENTIAL) |
|
||||
| `Comment` | `COMMENT` | Comment |
|
||||
| `Contact` | `CONTACT` | Contact information |
|
||||
| `DateTimeCompleted` | `COMPLETED` | Completion date-time |
|
||||
| `DateTimeCreated` | `CREATED` | Creation date-time |
|
||||
| `DateTimeDue` | `DUE` | Due date-time |
|
||||
| `DateTimeEnd` | `DTEND` | End date-time |
|
||||
| `DateTimeStamp` | `DTSTAMP` | Date-time stamp |
|
||||
| `DateTimeStart` | `DTSTART` | Start date-time |
|
||||
| `Description` | `DESCRIPTION` | Description |
|
||||
| `ExceptionDateTimes` | `EXDATE` | Exception date-times |
|
||||
| `FreeBusyTime` | `FREEBUSY` | Free/busy time periods |
|
||||
| `GeographicPosition` | `GEO` | Geographic position (lat/long) |
|
||||
| `LastModified` | `LAST-MODIFIED` | Last modified date-time |
|
||||
| `Location` | `LOCATION` | Location |
|
||||
| `Organiser` | `ORGANIZER` | Event organiser |
|
||||
| `PercentComplete` | `PERCENT-COMPLETE` | Completion percentage |
|
||||
| `Priority` | `PRIORITY` | Priority level |
|
||||
| `RecurrenceDateTimes` | `RDATE` | Recurrence date-times |
|
||||
| `RecurrenceId` | `RECURRENCE-ID` | Recurrence instance identifier |
|
||||
| `RecurrenceRule` | `RRULE` | Recurrence rule |
|
||||
| `RelatedTo` | `RELATED-TO` | Related component reference |
|
||||
| `RepeatCount` | `REPEAT` | Alarm repeat count |
|
||||
| `RequestStatus` | `REQUEST-STATUS` | Request status |
|
||||
| `Resources` | `RESOURCES` | Resources |
|
||||
| `SequenceNumber` | `SEQUENCE` | Sequence number (revision) |
|
||||
| `Status` | `STATUS` | Component status (TENTATIVE, CONFIRMED, CANCELLED, etc.) |
|
||||
| `Summary` | `SUMMARY` | Title or summary |
|
||||
| `TimeTransparency` | `TRANSP` | Time transparency (OPAQUE, TRANSPARENT) |
|
||||
| `TimeZoneIdentifier` | `TZID` | Time zone identifier |
|
||||
| `TimeZoneName` | `TZNAME` | Time zone name |
|
||||
| `TimeZoneOffsetFrom` | `TZOFFSETFROM` | Time zone offset from UTC |
|
||||
| `TimeZoneOffsetTo` | `TZOFFSETTO` | Time zone offset to UTC |
|
||||
| `TimeZoneUrl` | `TZURL` | Time zone URL |
|
||||
| `Trigger` | `TRIGGER` | Alarm trigger |
|
||||
| `UniformResourceLocator` | `URL` | Associated URL |
|
||||
| `UniqueIdentifier` | `UID` | Unique identifier |
|
||||
| `CustomComponentProperty` | `X-*` | Custom component properties |
|
||||
|
||||
---
|
||||
|
||||
## Parameters
|
||||
|
||||
Parameters in `com.onixbyte.calendar.parameter` provide additional qualifiers on component properties.
|
||||
|
||||
| Class | iCalendar Parameter | Description |
|
||||
| ----------------------------- | ------------------- | ------------------------------------------------------ |
|
||||
| `AlarmTriggerRelationship` | `RELATED` | Trigger relationship (START, END) |
|
||||
| `AlternateTextRepresentation` | `ALTREP` | Alternate text URI |
|
||||
| `CalendarUserType` | `CUTYPE` | Calendar user type (INDIVIDUAL, GROUP, RESOURCE, etc.) |
|
||||
| `CommonName` | `CN` | Common name |
|
||||
| `Delegatees` | `DELEGATED-TO` | Delegatees |
|
||||
| `Delegators` | `DELEGATED-FROM` | Delegators |
|
||||
| `DirectoryEntryReference` | `DIR` | Directory entry URI |
|
||||
| `FormatType` | `FMTTYPE` | Format type (MIME type) |
|
||||
| `FreeBusyTimeType` | `FBTYPE` | Free/busy time type |
|
||||
| `InlineEncoding` | `ENCODING` | Inline encoding (BASE64) |
|
||||
| `Language` | `LANGUAGE` | Language |
|
||||
| `Membership` | `MEMBER` | Group membership |
|
||||
| `ParticipationRole` | `ROLE` | Participation role (CHAIR, REQ-PARTICIPANT, etc.) |
|
||||
| `ParticipationStatus` | `PARTSTAT` | Participation status (ACCEPTED, DECLINED, etc.) |
|
||||
| `RecurrenceIdentifierRange` | `RANGE` | Recurrence range (THISANDPRIOR, THISANDFUTURE) |
|
||||
| `RelationshipType` | `RELTYPE` | Relationship type (PARENT, CHILD, SIBLING) |
|
||||
| `RsvpExpectation` | `RSVP` | RSVP expectation (TRUE, FALSE) |
|
||||
| `SentBy` | `SENT-BY` | Sent by |
|
||||
| `TimeZoneIdentifier` | `TZID` | Time zone identifier |
|
||||
| `ValueDataType` | `VALUE` | Value data type (DATE, DATE-TIME, etc.) |
|
||||
|
||||
---
|
||||
|
||||
## Recurrence Types
|
||||
|
||||
Located in `com.onixbyte.calendar.recurrence`.
|
||||
|
||||
| Class | Description |
|
||||
| ------------ | ------------------------------------------------------------------------- |
|
||||
| `Frequency` | Frequency constants (DAILY, WEEKLY, MONTHLY, YEARLY) for recurrence rules |
|
||||
| `WeekdayNum` | Weekday number for recurrence rules (e.g., 2nd Monday) |
|
||||
|
||||
---
|
||||
|
||||
## Value Types
|
||||
|
||||
Located in `com.onixbyte.calendar.value`.
|
||||
|
||||
| Class | Description |
|
||||
| ------------------- | ---------------------------------------------- |
|
||||
| `FreeBusyTimeValue` | Represents free/busy time value |
|
||||
| `PeriodOfTime` | Represents a period of time with start and end |
|
||||
| `PropertyValue` | Base value type for properties |
|
||||
| `UtcOffset` | UTC offset value (e.g., `-05:00`, `+01:00`) |
|
||||
|
||||
---
|
||||
|
||||
## Formatted Output
|
||||
|
||||
All components and properties provide a `formatted()` method that returns an iCalendar-compliant string representation. The `Calendar.formatted()` method produces a complete `.ics` output wrapped in `BEGIN:VCALENDAR`/`END:VCALENDAR`.
|
||||
|
||||
```java
|
||||
String ics = calendar.formatted();
|
||||
// BEGIN:VCALENDAR
|
||||
// CALSCALE:GREGORIAN
|
||||
// METHOD:PUBLISH
|
||||
// ...
|
||||
// END:VCALENDAR
|
||||
```
|
||||
@@ -1,100 +0,0 @@
|
||||
---
|
||||
title: Calendar Toolbox
|
||||
---
|
||||
|
||||
import { Tabs, Tab } from "@rspress/core/theme"
|
||||
|
||||
## Introduction
|
||||
|
||||
Calendar Toolbox is a Java library designed to simplify the creation of `.ics` files, adhering to the iCalendar specification (RFC 5545). This library provides developers with a straightforward API to generate calendar events that can be imported into popular calendar applications such as Microsoft Outlook, Google Calendar, and Apple Calendar.
|
||||
|
||||
With Calendar Toolbox, you can effortlessly create, customise, and export calendar event files programmatically, enabling integration of calendar functionalities into your Java applications with minimal effort.
|
||||
|
||||
## Features
|
||||
|
||||
- **Full RFC 5545 Compliance** — Supports standard iCalendar components including VEVENT, VTODO, VJOURNAL, VFREEBUSY, VTIMEZONE, and VALARM.
|
||||
- **Fluent Builder Pattern** — All components and properties use builder pattern for readable and intuitive construction.
|
||||
- **Type-Safe Properties** — Strongly typed properties and parameters ensure correctness at compile time.
|
||||
- **Custom Extensions** — Support for custom `X-` properties at both calendar and component level.
|
||||
- **Lightweight** — Zero external dependencies for core functionality.
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Gradle with Kotlin DSL">
|
||||
```kotlin title="build.gradle.kts" dependencies{" "}
|
||||
{implementation("com.onixbyte:calendar-toolbox:1.1.0")}
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="Maven">
|
||||
```xml title="pom.xml"
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.onixbyte</groupId>
|
||||
<artifactId>calendar-toolbox</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Quick Start
|
||||
|
||||
```java
|
||||
import com.onixbyte.calendar.Calendar;
|
||||
import com.onixbyte.calendar.component.Event;
|
||||
import com.onixbyte.calendar.component.property.*;
|
||||
import com.onixbyte.calendar.property.*;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
public class CalendarExample {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Create required date-time properties
|
||||
var now = ZonedDateTime.now(ZoneOffset.UTC);
|
||||
var dtstamp = DateTimeStamp.of(now);
|
||||
var uid = UniqueIdentifier.of("meeting-001@example.com");
|
||||
var dtstart = DateTimeStart.of(
|
||||
LocalDateTime.of(2025, 8, 15, 9, 0),
|
||||
"America/New_York"
|
||||
);
|
||||
var dtend = DateTimeEnd.of(
|
||||
LocalDateTime.of(2025, 8, 15, 10, 0),
|
||||
"America/New_York"
|
||||
);
|
||||
var summary = Summary.of("Team Standup");
|
||||
var description = Description.of("Daily team standup meeting.");
|
||||
|
||||
// Build the event
|
||||
var event = Event.builder()
|
||||
.withDateTimeStamp(dtstamp)
|
||||
.withUniqueIdentifier(uid)
|
||||
.withDateTimeStart(dtstart)
|
||||
.withDateTimeEnd(dtend)
|
||||
.withSummary(summary)
|
||||
.withDescription(description)
|
||||
.build();
|
||||
|
||||
// Build the calendar and output .ics content
|
||||
var calendar = Calendar.builder()
|
||||
.withCalendarScale(CalendarScale.of("GREGORIAN"))
|
||||
.withMethod(Method.of("PUBLISH"))
|
||||
.withProductIdentifier(ProductIdentifier.of("-//OnixByte//Calendar Toolbox//EN"))
|
||||
.withVersion(Version.of("2.0"))
|
||||
.withComponents(event)
|
||||
.build();
|
||||
|
||||
System.out.println(calendar.formatted());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This produces an `.ics` string that can be saved to a file or sent via email.
|
||||
|
||||
## License
|
||||
|
||||
Calendar Toolbox is open-source software released under the MIT License.
|
||||
@@ -1,248 +0,0 @@
|
||||
---
|
||||
title: Captcha API
|
||||
---
|
||||
|
||||
## Package Overview
|
||||
|
||||
The library is organised under the base package `com.onixbyte.captcha` with the following structure:
|
||||
|
||||
| Package | Description |
|
||||
| -------------------------------------- | ------------------------------------- |
|
||||
| `com.onixbyte.captcha` | Core interfaces |
|
||||
| `com.onixbyte.captcha.impl` | Default implementations |
|
||||
| `com.onixbyte.captcha.text` | Text producer and renderer interfaces |
|
||||
| `com.onixbyte.captcha.text.impl` | Text producer and renderer impls |
|
||||
| `com.onixbyte.captcha.text.enums` | Text-related enums |
|
||||
| `com.onixbyte.captcha.background` | Background producer interface |
|
||||
| `com.onixbyte.captcha.background.impl` | Background producer implementations |
|
||||
| `com.onixbyte.captcha.noise` | Noise producer interface |
|
||||
| `com.onixbyte.captcha.noise.impl` | Noise producer implementations |
|
||||
| `com.onixbyte.captcha.gimpy` | Distortion engine interface |
|
||||
| `com.onixbyte.captcha.gimpy.impl` | Distortion engine implementations |
|
||||
|
||||
---
|
||||
|
||||
## Producer
|
||||
|
||||
The top-level interface responsible for creating CAPTCHA images with text drawn on them.
|
||||
|
||||
```java
|
||||
Producer captcha = DefaultCaptchaProducer.builder()
|
||||
.textProducer(textProducer)
|
||||
.wordRenderer(wordRenderer)
|
||||
.gimpyEngine(gimpyEngine)
|
||||
.backgroundProducer(backgroundProducer)
|
||||
.width(200)
|
||||
.height(50)
|
||||
.borderDrawn(true)
|
||||
.borderColour(Color.BLACK)
|
||||
.borderThickness(1)
|
||||
.build();
|
||||
|
||||
String text = captcha.createText();
|
||||
BufferedImage image = captcha.createImage(text);
|
||||
```
|
||||
|
||||
### DefaultCaptchaProducer Builder
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| -------------------- | --------------------------------------------- | ------------------------------ |
|
||||
| `textProducer` | `DefaultTextProducer.builder().build()` | The text producer to use |
|
||||
| `wordRenderer` | `DefaultWordRenderer.builder().build()` | The word renderer to use |
|
||||
| `gimpyEngine` | `WaterRipple.builder().build()` | The distortion engine to use |
|
||||
| `backgroundProducer` | `DefaultBackgroundProducer.builder().build()` | The background producer to use |
|
||||
| `width` | `200` | Width of the CAPTCHA image |
|
||||
| `height` | `50` | Height of the CAPTCHA image |
|
||||
| `borderDrawn` | `true` | Whether a border is drawn |
|
||||
| `borderColour` | `Color.BLACK` | The colour of the border |
|
||||
| `borderThickness` | `1` | The thickness of the border |
|
||||
|
||||
---
|
||||
|
||||
## Text
|
||||
|
||||
### TextProducer
|
||||
|
||||
Interface for creating CAPTCHA text strings.
|
||||
|
||||
```java
|
||||
public interface TextProducer {
|
||||
String getText();
|
||||
}
|
||||
```
|
||||
|
||||
#### DefaultTextProducer
|
||||
|
||||
Generates random text with configurable length and character set.
|
||||
|
||||
```java
|
||||
DefaultTextProducer textProducer = DefaultTextProducer.builder()
|
||||
.length(6) // default: 6
|
||||
.chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()) // default: a-z, A-Z, 0-9
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| -------- | ----------------------------------- | ------------------------------ |
|
||||
| `length` | `6` | Length of generated text |
|
||||
| `chars` | `a-z`, `A-Z`, `0-9` (62 characters) | Characters used for generation |
|
||||
|
||||
### WordRenderer
|
||||
|
||||
Interface for rendering text onto an image.
|
||||
|
||||
```java
|
||||
public interface WordRenderer {
|
||||
BufferedImage renderWord(String word, int width, int height);
|
||||
}
|
||||
```
|
||||
|
||||
#### DefaultWordRenderer
|
||||
|
||||
Renders text with configurable font, colour, and spacing.
|
||||
|
||||
```java
|
||||
DefaultWordRenderer wordRenderer = DefaultWordRenderer.builder()
|
||||
.fontSize(40) // default: 40
|
||||
.fonts("Arial", "Courier") // default: Arial, Courier
|
||||
.fontColour(Color.BLACK) // default: Color.BLACK
|
||||
.charSpace(2) // default: 2
|
||||
.fontStyle(FontStyle.BOLD) // default: FontStyle.BOLD
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| ------------ | ---------------------- | -------------------------------------- |
|
||||
| `fontSize` | `40` | Font size in pixels |
|
||||
| `fonts` | `["Arial", "Courier"]` | Font families to use (randomly chosen) |
|
||||
| `fontColour` | `Color.BLACK` | Font colour |
|
||||
| `charSpace` | `2` | Space between characters in pixels |
|
||||
| `fontStyle` | `FontStyle.BOLD` | Font style to apply |
|
||||
|
||||
### FontStyle
|
||||
|
||||
Defines the supported font styles for rendering CAPTCHA text.
|
||||
|
||||
| Value | Corresponding AWT Constant | Description |
|
||||
| ------------- | -------------------------- | ------------ | ------------------------ |
|
||||
| `PLAIN` | `java.awt.Font.PLAIN` | Plain style |
|
||||
| `BOLD` | `java.awt.Font.BOLD` | Bold style |
|
||||
| `ITALIC` | `java.awt.Font.ITALIC` | Italic style |
|
||||
| `BOLD_ITALIC` | `java.awt.Font.BOLD | Font.ITALIC` | Bold and italic combined |
|
||||
|
||||
---
|
||||
|
||||
## GimpyEngine
|
||||
|
||||
Interface for applying image distortion effects.
|
||||
|
||||
```java
|
||||
public interface GimpyEngine {
|
||||
BufferedImage getDistortedImage(BufferedImage baseImage);
|
||||
}
|
||||
```
|
||||
|
||||
### WaterRipple
|
||||
|
||||
Applies a water ripple distortion effect. Extends `AbstractGimpyEngine` and adds noise automatically.
|
||||
|
||||
```java
|
||||
WaterRipple gimpy = WaterRipple.builder()
|
||||
.noiseProducer(DefaultNoiseProducer.builder().build()) // default: DefaultNoiseProducer
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| --------------- | ---------------------------------------- | ------------------------- |
|
||||
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | The noise producer to use |
|
||||
|
||||
### FishEyeGimpy
|
||||
|
||||
Applies a fish-eye distortion effect with horizontal and vertical lines over the image.
|
||||
|
||||
```java
|
||||
FishEyeGimpy gimpy = FishEyeGimpy.builder()
|
||||
.build();
|
||||
```
|
||||
|
||||
### ShadowGimpy
|
||||
|
||||
Applies a shadow and ripple effect. Extends `AbstractGimpyEngine` and adds noise automatically.
|
||||
|
||||
```java
|
||||
ShadowGimpy gimpy = ShadowGimpy.builder()
|
||||
.noiseProducer(DefaultNoiseProducer.builder().build()) // default: DefaultNoiseProducer
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| --------------- | ---------------------------------------- | ------------------------- |
|
||||
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | The noise producer to use |
|
||||
|
||||
---
|
||||
|
||||
## Noise
|
||||
|
||||
### NoiseProducer
|
||||
|
||||
Interface for adding noise to an image.
|
||||
|
||||
```java
|
||||
public interface NoiseProducer {
|
||||
void makeNoise(BufferedImage image, float factorOne, float factorTwo,
|
||||
float factorThree, float factorFour);
|
||||
}
|
||||
```
|
||||
|
||||
#### DefaultNoiseProducer
|
||||
|
||||
Adds noise curves with configurable colour.
|
||||
|
||||
```java
|
||||
DefaultNoiseProducer noise = DefaultNoiseProducer.builder()
|
||||
.noiseColour(Color.BLACK) // default: Color.BLACK
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| ------------- | ------------- | ---------------- |
|
||||
| `noiseColour` | `Color.BLACK` | The noise colour |
|
||||
|
||||
#### NoNoiseProducer
|
||||
|
||||
A no-op implementation that does not add any noise to the image.
|
||||
|
||||
```java
|
||||
NoiseProducer noise = NoNoiseProducer.builder()
|
||||
.build();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
### BackgroundProducer
|
||||
|
||||
Interface for adding background to an image.
|
||||
|
||||
```java
|
||||
public interface BackgroundProducer {
|
||||
BufferedImage addBackground(BufferedImage image);
|
||||
}
|
||||
```
|
||||
|
||||
#### DefaultBackgroundProducer
|
||||
|
||||
Creates a gradient background with configurable start and end colours.
|
||||
|
||||
```java
|
||||
DefaultBackgroundProducer background = DefaultBackgroundProducer.builder()
|
||||
.colourFrom(Color.WHITE) // default: Color.LIGHT_GRAY
|
||||
.colourTo(Color.LIGHT_GRAY) // default: Color.WHITE
|
||||
.build();
|
||||
```
|
||||
|
||||
| Method | Default Value | Description |
|
||||
| ------------ | ------------------ | ------------------------------- |
|
||||
| `colourFrom` | `Color.LIGHT_GRAY` | The starting colour of gradient |
|
||||
| `colourTo` | `Color.WHITE` | The ending colour of gradient |
|
||||
@@ -1,86 +0,0 @@
|
||||
---
|
||||
title: Captcha
|
||||
---
|
||||
|
||||
import { Tabs, Tab } from "@rspress/core/theme"
|
||||
|
||||
## Introduction
|
||||
|
||||
Captcha is a Java library for generating CAPTCHA images. This project is a spiritual successor to Google's Kaptcha, modernised for contemporary development practices.
|
||||
|
||||
With Captcha, you can easily generate customisable CAPTCHA images with various distortion effects, noise, and background options to integrate into your Java applications.
|
||||
|
||||
## Features
|
||||
|
||||
- **Easy Integration** — Simple builder-based API for quick setup and configuration.
|
||||
- **Customisable CAPTCHA Generation** — Configure text length, character sets, font styles, and colours.
|
||||
- **Multiple Distortion Effects** — Choose from Water Ripple, Fish Eye, and Shadow effects.
|
||||
- **Configurable Noise** — Add noise lines with custom colours, or disable noise entirely.
|
||||
- **Gradient Backgrounds** — Customisable gradient background colours.
|
||||
- **Clean, Modern Codebase** — Well-documented, type-safe, and easy to extend.
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Gradle with Kotlin DSL">
|
||||
```kotlin title="build.gradle.kts" dependencies {implementation("com.onixbyte:captcha:1.0.0")}
|
||||
```
|
||||
</Tab>
|
||||
<Tab label="Maven">
|
||||
```xml title="pom.xml"
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.onixbyte</groupId>
|
||||
<artifactId>captcha</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Quick Start
|
||||
|
||||
```java
|
||||
import com.onixbyte.captcha.Producer;
|
||||
import com.onixbyte.captcha.background.BackgroundProducer;
|
||||
import com.onixbyte.captcha.background.impl.DefaultBackgroundProducer;
|
||||
import com.onixbyte.captcha.gimpy.GimpyEngine;
|
||||
import com.onixbyte.captcha.gimpy.impl.WaterRipple;
|
||||
import com.onixbyte.captcha.impl.DefaultCaptchaProducer;
|
||||
import com.onixbyte.captcha.noise.NoiseProducer;
|
||||
import com.onixbyte.captcha.noise.impl.DefaultNoiseProducer;
|
||||
import com.onixbyte.captcha.text.TextProducer;
|
||||
import com.onixbyte.captcha.text.WordRenderer;
|
||||
import com.onixbyte.captcha.text.impl.DefaultTextProducer;
|
||||
import com.onixbyte.captcha.text.impl.DefaultWordRenderer;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CaptchaExample {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
// Create a CAPTCHA producer with default configurations
|
||||
Producer captcha = DefaultCaptchaProducer.builder().build();
|
||||
|
||||
// Generate text and create the image
|
||||
String captchaText = captcha.createText();
|
||||
BufferedImage captchaImage = captcha.createImage(captchaText);
|
||||
|
||||
// Save the image to a file
|
||||
ImageIO.write(captchaImage, "png", new File("captcha.png"));
|
||||
|
||||
System.out.println("CAPTCHA text: " + captchaText);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This generates a CAPTCHA image with a 6-character random text and saves it as a PNG file.
|
||||
|
||||
## License
|
||||
|
||||
Captcha is open-source software released under the MIT License.
|
||||
@@ -1,217 +0,0 @@
|
||||
---
|
||||
title: Claude Code Skills Manager
|
||||
---
|
||||
|
||||
import { Tabs, Tab } from "@rspress/core/theme"
|
||||
|
||||
## Introduction
|
||||
|
||||
Claude Code Skills Manager (`ccsm`) is a CLI tool for discovering, installing, and managing Claude Code skills from Git-based marketplace repositories. It lets you register any public Git repository that exposes a `skills.json` manifest as a skill source, then search, install, update, and remove skills through a single command-line interface.
|
||||
|
||||
Skills are deployed directly into Claude Code's own skills directory (`~/.claude/skills/`), where Claude Code picks them up automatically at startup.
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Pre-built binaries">
|
||||
Download the latest binary for your platform from the [Releases page](https://onixbyte.dev/onixbyte/claude-code-skill-manager/releases).
|
||||
|
||||
| Platform | Binary |
|
||||
|----------|--------|
|
||||
| Linux (amd64) | `ccsm-linux-amd64` |
|
||||
| macOS (amd64) | `ccsm-darwin-amd64` |
|
||||
| Windows (amd64) | `ccsm-windows-amd64.exe` |
|
||||
|
||||
Place the binary somewhere on your `PATH` and rename it to `ccsm` (or `ccsm.exe` on Windows).
|
||||
|
||||
</Tab>
|
||||
<Tab label="Cargo install">
|
||||
```bash
|
||||
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
|
||||
cd claude-code-skill-manager
|
||||
cargo install --path .
|
||||
```
|
||||
Requires a Rust toolchain (install via [rustup.rs](https://rustup.rs)).
|
||||
</Tab>
|
||||
<Tab label="Build from source">
|
||||
```bash
|
||||
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
|
||||
cd claude-code-skill-manager
|
||||
cargo build --release
|
||||
```
|
||||
The binary will be at `target/release/ccsm` (or `ccsm.exe` on Windows).
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Registering a marketplace
|
||||
|
||||
A marketplace is any public Git repository with a `skills.json` manifest at its root. Register one with:
|
||||
|
||||
```bash
|
||||
ccsm marketplace add https://github.com/example/skills
|
||||
```
|
||||
|
||||
CCSM fetches the manifest and caches it locally. You can register as many marketplaces as you need.
|
||||
|
||||
### Browsing and installing skills
|
||||
|
||||
```bash
|
||||
# Search across all registered marketplaces
|
||||
ccsm search
|
||||
|
||||
# Search with a keyword filter
|
||||
ccsm search git
|
||||
|
||||
# Install a skill by name
|
||||
ccsm install my-awesome-skill
|
||||
|
||||
# If the same name exists in multiple marketplaces, disambiguate
|
||||
ccsm install my-skill --marketplace my-marketplace
|
||||
```
|
||||
|
||||
### Managing installed skills
|
||||
|
||||
```bash
|
||||
# List installed skills
|
||||
ccsm list
|
||||
|
||||
# Show details about an installed skill
|
||||
ccsm info my-awesome-skill
|
||||
|
||||
# Update all installed skills to their latest versions
|
||||
ccsm update
|
||||
|
||||
# Update a specific skill
|
||||
ccsm update my-awesome-skill
|
||||
|
||||
# Remove a skill
|
||||
ccsm remove my-awesome-skill
|
||||
```
|
||||
|
||||
### Marketplace management
|
||||
|
||||
```bash
|
||||
# List all registered marketplaces
|
||||
ccsm marketplace list
|
||||
|
||||
# Refresh cached manifests (all marketplaces)
|
||||
ccsm marketplace update
|
||||
|
||||
# Refresh a specific marketplace
|
||||
ccsm marketplace update my-marketplace
|
||||
|
||||
# Remove a marketplace
|
||||
ccsm marketplace remove my-marketplace
|
||||
```
|
||||
|
||||
### Install modes
|
||||
|
||||
CCSM supports two deploy modes:
|
||||
|
||||
| Mode | Behaviour | Use case |
|
||||
| ---------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |
|
||||
| `copy` (default) | Files are physically copied to `~/.claude/skills/<name>/` | Normal use — safe and self-contained |
|
||||
| `link` | A symlink is created pointing to the local store | Skill development — changes to source files are picked up immediately |
|
||||
|
||||
Set the default mode in your config, or override per install:
|
||||
|
||||
```bash
|
||||
ccsm install my-skill --link
|
||||
```
|
||||
|
||||
## Creating Your Own Skill Marketplace
|
||||
|
||||
A marketplace is simply a public Git repository containing a `skills.json` manifest. There is no server component, no authentication, and no API key — CCSM reads the manifest directly from the Git forge's raw file endpoint.
|
||||
|
||||
### Marketplace repository structure
|
||||
|
||||
```
|
||||
your-marketplace-repo/
|
||||
skills.json # Required: the manifest
|
||||
README.md # Optional
|
||||
```
|
||||
|
||||
### The `skills.json` manifest
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "My Marketplace",
|
||||
"version": "1.0.0",
|
||||
"skills": [
|
||||
{
|
||||
"name": "my-skill",
|
||||
"description": "A short description shown in search results",
|
||||
"repository": "https://github.com/user/skill-repo",
|
||||
"branch": "main"
|
||||
},
|
||||
{
|
||||
"name": "another-skill",
|
||||
"description": "A skill that lives in a monorepo subdirectory",
|
||||
"repository": "https://github.com/user/monorepo",
|
||||
"branch": "main",
|
||||
"path": "skills/another-skill"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Top-level fields:**
|
||||
|
||||
| Field | Required | Description |
|
||||
| --------- | -------- | ---------------------------------------------- |
|
||||
| `name` | Yes | Marketplace display name |
|
||||
| `version` | No | Arbitrary version string for your own tracking |
|
||||
| `skills` | No | Array of skill entries (defaults to `[]`) |
|
||||
|
||||
**Skill entry fields:**
|
||||
|
||||
| Field | Required | Description |
|
||||
| ------------- | -------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `name` | Yes | Unique skill identifier (used with `ccsm install <name>`) |
|
||||
| `description` | Yes | One-line summary shown in `ccsm search` output |
|
||||
| `repository` | Yes | Public Git repository URL for the skill |
|
||||
| `branch` | No | Git branch to install from (tries `main` first, falls back to `master`) |
|
||||
| `path` | No | Subdirectory within the repository where the skill files live — enables monorepos hosting multiple skills |
|
||||
|
||||
### How it works
|
||||
|
||||
1. **Manifest fetching** — CCSM constructs a raw-file URL from the marketplace repository URL and fetches `skills.json` from either the `main` or `master` branch. This works with GitHub, Gitea, GitLab, and any Git forge that exposes raw file endpoints.
|
||||
|
||||
2. **Skill downloading** — When a user installs a skill, CCSM downloads the skill repository archive (ZIP or tar.gz) from the Git forge, extracts it, and deploys the files to `~/.claude/skills/<name>/`.
|
||||
|
||||
3. **Monorepo support** — If a skill entry specifies a `path`, CCSM navigates into that subdirectory after extraction before deploying, so a single repository can host multiple skills.
|
||||
|
||||
### What goes in a skill repository
|
||||
|
||||
Each skill repository is a standard Claude Code skill directory. At minimum it should contain a `SKILL.md` manifest file:
|
||||
|
||||
```
|
||||
my-skill-repo/
|
||||
SKILL.md # Claude Code skill manifest
|
||||
script.py # Supporting files
|
||||
templates/
|
||||
template.txt
|
||||
```
|
||||
|
||||
### Publishing steps
|
||||
|
||||
1. Create a **public** Git repository with a `skills.json` at the root, following the format above.
|
||||
2. Push it.
|
||||
3. Anyone can register it with `ccsm marketplace add <url>`.
|
||||
|
||||
## How CCSM Stores Data
|
||||
|
||||
| Path | Purpose |
|
||||
| ------------------------------ | ---------------------------------------------------------- |
|
||||
| `~/.claude/skills/` | Claude Code's skills directory — where skills are deployed |
|
||||
| `~/.config/ccsm/config.json` | CCSM configuration (registered marketplaces, default mode) |
|
||||
| `~/.config/ccsm/marketplaces/` | Cached marketplace manifests |
|
||||
| `~/.config/ccsm/skills/` | Downloaded skill files (local store) |
|
||||
|
||||
On Windows, `~/.config/` resolves to `C:\Users\<user>\AppData\Roaming\`.
|
||||
|
||||
## Licence
|
||||
|
||||
Claude Code Skills Manager is open-source software released under the MIT Licence.
|
||||
@@ -1,91 +0,0 @@
|
||||
---
|
||||
title: Delta Force Guide API
|
||||
---
|
||||
|
||||
## Backend API Overview
|
||||
|
||||
The backend is a Spring Boot 3 application organised under the base package `com.onixbyte.deltaforceguide`:
|
||||
|
||||
| Package | Description |
|
||||
| ------------- | -------------------------------------------- |
|
||||
| `controller` | REST API endpoint controllers |
|
||||
| `service` | Business logic layer |
|
||||
| `repository` | MyBatis data access layer |
|
||||
| `domain` | JPA entity and domain objects |
|
||||
| `config` | Spring configuration (security, cache, etc.) |
|
||||
| `properties` | Configuration property classes |
|
||||
| `enumeration` | Enumerated types |
|
||||
| `shared` | Shared utilities |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Path | Description | Auth |
|
||||
| ------ | ------------------- | ------------------------ | ---- |
|
||||
| `POST` | `/api/auth/login` | Log in with credentials | No |
|
||||
| `POST` | `/api/auth/refresh` | Refresh access token | No |
|
||||
| `GET` | `/api/auth/profile` | Get current user profile | Yes |
|
||||
|
||||
### Firearms
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | ------------------- | --------------------------------------------- |
|
||||
| `GET` | `/api/firearms` | List firearms (paginated, filterable by type) |
|
||||
| `GET` | `/api/firearms/:id` | Get firearm by ID |
|
||||
| `POST` | `/api/firearms` | Create a new firearm |
|
||||
| `PUT` | `/api/firearms/:id` | Update an existing firearm |
|
||||
| `DELETE` | `/api/firearms/:id` | Remove a firearm |
|
||||
|
||||
### Modifications
|
||||
|
||||
| Method | Path | Description |
|
||||
| -------- | --------------------------------- | -------------------------------------------------------------- |
|
||||
| `GET` | `/api/modifications` | List modifications (paginated, filterable by firearm and tags) |
|
||||
| `GET` | `/api/modifications/:id` | Get modification by ID |
|
||||
| `POST` | `/api/modifications` | Create a modification |
|
||||
| `POST` | `/api/modifications/batch` | Batch create modifications |
|
||||
| `PUT` | `/api/modifications/:id` | Update a modification |
|
||||
| `DELETE` | `/api/modifications/:id` | Remove a modification |
|
||||
| `DELETE` | `/api/modifications/batch-delete` | Batch remove modifications |
|
||||
|
||||
### Tags
|
||||
|
||||
| Method | Path | Description |
|
||||
| ------ | ----------- | ------------- |
|
||||
| `GET` | `/api/tags` | List all tags |
|
||||
|
||||
## Frontend Routes
|
||||
|
||||
| Path | Layout | Page | Description |
|
||||
| ------------ | ------------ | ------------------ | ----------------------------- |
|
||||
| `/` | Hero Layout | Firearms Browser | Home page — firearm list |
|
||||
| `/firearms` | Hero Layout | Firearms Browser | Firearm detail and search |
|
||||
| `/mod-codes` | Hero Layout | Modification Codes | Modification code library |
|
||||
| `/legal` | Hero Layout | Legal | Legal and privacy information |
|
||||
| `/login` | Empty Layout | Login | User authentication page |
|
||||
|
||||
## Frontend State Management
|
||||
|
||||
Redux Toolkit with Redux Persist manages:
|
||||
|
||||
- **Auth state** — Access tokens, refresh tokens, user profile
|
||||
- **Settings state** — User preferences persisted to local storage
|
||||
|
||||
## API Client
|
||||
|
||||
The frontend uses a shared `WebClient` wrapper around Axios that handles:
|
||||
|
||||
- Base URL configuration (pointing to the backend API)
|
||||
- JWT access token injection via request interceptors
|
||||
- Automatic token refresh on 401 responses
|
||||
|
||||
## Infrastructure
|
||||
|
||||
| Component | Technology |
|
||||
| ---------------- | ----------------------------------- |
|
||||
| Database | PostgreSQL (with Flyway migrations) |
|
||||
| Cache | Redis (session/token storage) |
|
||||
| File Storage | AWS S3 (firearm images) |
|
||||
| Containerisation | Docker |
|
||||
| Deployment | Docker Compose behind Nginx |
|
||||
@@ -1,46 +0,0 @@
|
||||
---
|
||||
title: Delta Force Guide
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Delta Force Guide is a full-stack web application providing a searchable library of firearm modification codes for the game Delta Force. It consists of two components:
|
||||
|
||||
- **Web Frontend** — A React-based SPA for browsing, filtering, and copying modification codes.
|
||||
- **Backend Server** — A Spring Boot REST API handling data persistence, authentication, and content management.
|
||||
|
||||
The live site is available at **[dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)**.
|
||||
|
||||
## Features
|
||||
|
||||
- **Firearm Browser** — Browse a curated list of in-game firearms with detailed stats.
|
||||
- **Modification Code Library** — Search and filter modification codes by weapon, mode, and tags.
|
||||
- **One-Click Copy** — Copy modification codes directly from the interface.
|
||||
- **Efficient Rendering** — Large lists rendered smoothly with window virtualisation.
|
||||
- **Authentication** — User login with JWT-based auth and CAPTCHA verification.
|
||||
- **Admin Panel** — Full CRUD operations for firearms and modification data.
|
||||
- **Static-Friendly** — Frontend deployed as a static site with API proxy.
|
||||
|
||||
## Architecture
|
||||
|
||||
| Component | Tech Stack |
|
||||
| ------------ | --------------------------------------------------------------------------------------------------- |
|
||||
| **Frontend** | React 19, TypeScript, Tailwind CSS 4, Ant Design 6, React Router 7, Redux Toolkit, TanStack Virtual |
|
||||
| **Backend** | Spring Boot 3, Java 21, PostgreSQL, MyBatis, Flyway, Redis |
|
||||
| **Auth** | JWT (access + refresh tokens), CAPTCHA integration |
|
||||
| **Storage** | AWS S3 for image uploads |
|
||||
|
||||
The frontend is a static site served by Vite, communicating with the Spring Boot REST API backend. Authentication uses JWT access and refresh tokens with Redis-backed refresh token management.
|
||||
|
||||
## Repositories
|
||||
|
||||
- [delta-force-guide-server](https://github.com/onixbyte/delta-force-guide-server) — Spring Boot backend
|
||||
- [delta-force-guide-web](https://github.com/onixbyte/delta-force-guide-web) — React frontend
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **Live Site:** [dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)
|
||||
|
||||
## License
|
||||
|
||||
Delta Force Guide is open-source software released under the MIT Licence.
|
||||
@@ -1,73 +0,0 @@
|
||||
---
|
||||
title: Helix
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
Helix is a full-stack enterprise application template designed to accelerate the development of production-grade business applications. It provides a pre-built foundation with all the essential features an enterprise application needs, allowing teams to focus on business logic rather than infrastructure.
|
||||
|
||||
The project consists of two components:
|
||||
|
||||
- **Helix Server** — A Spring Boot backend providing REST APIs, authentication, authorisation, and data management.
|
||||
- **Helix Web** — A React-based SPA frontend with a modern UI, role-based routing, and enterprise identity integration.
|
||||
|
||||
Helix is intended to be used as a starting point. Fork or clone it and begin building your own domain-specific features on top of the ready-made foundation.
|
||||
|
||||
## Features
|
||||
|
||||
### Backend (Helix Server)
|
||||
|
||||
- **Authentication & Authorisation** — JWT-based access and refresh token flow with Spring Security. Supports Microsoft Entra ID (Azure AD) integration.
|
||||
- **Role-Based Access Control** — Fine-grained permissions via roles, authorities, and menu-level access control.
|
||||
- **User & Organisation Management** — Full CRUD for users, departments, positions, and roles.
|
||||
- **Menu Management** — Hierarchical menu tree with configurable visibility per role.
|
||||
- **Asset Management** — File uploads to AWS S3 with CDN-ready prefix handling.
|
||||
- **Settings Management** — Typed application settings persisted to database.
|
||||
- **Captcha Integration** — Image CAPTCHA verification for login and registration flows.
|
||||
- **Redis Caching** — High-performance caching with configurable TTL and custom serialisers.
|
||||
- **Data Access** — Hybrid persistence with Spring Data JPA and MyBatis.
|
||||
- **Validation** — Bean Validation groups with custom validation constraints.
|
||||
|
||||
### Frontend (Helix Web)
|
||||
|
||||
- **Enterprise Identity** — Microsoft Entra ID login via MSAL. Extensible social login provider architecture (Microsoft, Google, GitLab, Slack, Discord, DingTalk, Lark, WeCom, email/password).
|
||||
- **Dashboard Layout** — Responsive admin dashboard with collapsible sidebar and role-based menu rendering.
|
||||
- **User Management** — Data table with search, pagination, add/edit dialogues, role assignment, and phone number validation.
|
||||
- **Role & Menu Management** — Role CRUD with authority assignment. Tree-based menu configuration.
|
||||
- **Department & Position** — Organisation hierarchy management interfaces.
|
||||
- **Protected Routing** — Route guards checking authentication and authorisation before page load.
|
||||
- **State Management** — Redux Toolkit with Redux Persist for auth token persistence.
|
||||
- **HTTP Client** — Axios wrapper with automatic JWT token injection and 401 refresh handling.
|
||||
|
||||
## Architecture
|
||||
|
||||
| Layer | Technology |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------- |
|
||||
| **Frontend** | React 19, TypeScript, Tailwind CSS 4, Ant Design 6, React Router 7, Redux Toolkit, Axios, MSAL |
|
||||
| **Backend** | Spring Boot 3.5, Java 21, Spring Security, MyBatis, Spring Data JPA |
|
||||
| **Database** | PostgreSQL (with Flyway migrations) |
|
||||
| **Cache** | Redis (session/token storage) |
|
||||
| **Storage** | AWS S3 (asset uploads) |
|
||||
|
||||
The frontend communicates with the backend via a RESTful JSON API. Authentication flow uses JWT access tokens (short-lived) with refresh tokens stored in Redis. Microsoft Entra ID integration is handled client-side via MSAL and validated server-side.
|
||||
|
||||
## Repositories
|
||||
|
||||
Helix is a monorepo — both halves live in the same repository.
|
||||
|
||||
- [server/](https://github.com/onixbyte-opensource/helix/tree/main/server) — Spring Boot backend
|
||||
- [web/](https://github.com/onixbyte-opensource/helix/tree/main/web) — React frontend
|
||||
|
||||
## Getting Started
|
||||
|
||||
Helix is designed as a template project. To start a new project:
|
||||
|
||||
1. Fork or clone the [Helix repository](https://github.com/onixbyte-opensource/helix).
|
||||
2. Configure your database, Redis, and S3 settings in the backend.
|
||||
3. Configure your authentication providers and API base URL in the frontend.
|
||||
4. Run the backend: `./gradlew bootRun`
|
||||
5. Run the frontend: `pnpm install && pnpm dev`
|
||||
|
||||
## License
|
||||
|
||||
Helix is open-source software released under the MIT License.
|
||||
@@ -1,25 +0,0 @@
|
||||
---
|
||||
title: Open Source
|
||||
---
|
||||
|
||||
Open-source projects developed by OnixByte.
|
||||
|
||||
## Libraries & Toolkits
|
||||
|
||||
- **[OnixByte Toolbox](/opensource-projects/onixbyte-toolbox)** — A development kit for Java applications: AES, hashing, Base64, collection utilities, crypto key loaders, statistical calculators, identity generators, and generic tuples.
|
||||
- **[regions4j](/opensource-projects/regions4j)** — Type-safe ISO 3166-1 regional metadata as a Java enum with O(1) lookups, E.164 dialling codes, and locale tags.
|
||||
- **[Captcha](/opensource-projects/captcha)** — A modern Java CAPTCHA generation library with water ripple, fish-eye, and shadow distortion effects.
|
||||
- **[Calendar Toolbox](/opensource-projects/calendar-toolbox)** — A Java library for creating `.ics` files conforming to the iCalendar specification (RFC 5545).
|
||||
|
||||
## Full-Stack Applications
|
||||
|
||||
- **[Helix](/opensource-projects/helix)** — An enterprise application template with Spring Boot backend and React frontend, featuring JWT auth, RBAC, user and organisation management, and Microsoft Entra ID integration.
|
||||
- **[Delta Force Guide](/opensource-projects/delta-force-guide)** — A web app providing a searchable library of firearm modification codes for the game _Delta Force: Havvk Ops_, with a React SPA and Spring Boot REST API.
|
||||
|
||||
## Developer Tools
|
||||
|
||||
- **[Claude Code Skills Manager](/opensource-projects/claude-code-skill-manager)** — A CLI tool for discovering, installing, and managing Claude Code skills from Git-based marketplace repositories.
|
||||
|
||||
## Vite Plugins
|
||||
|
||||
- **[Port Checker](/opensource-projects/vite-plugins/port-checker)** — Warns when the Vite dev server listens on a browser-restricted port, preventing silent connection failures.
|
||||
@@ -1,209 +0,0 @@
|
||||
---
|
||||
title: OnixByte Toolbox API
|
||||
---
|
||||
|
||||
## Package Overview
|
||||
|
||||
All modules are published under the `com.onixbyte` group ID. Each module has its own base package:
|
||||
|
||||
| Module | Maven Artifact | Base Package |
|
||||
| ------------------ | -------------------- | -------------------------------- |
|
||||
| Common Toolbox | `common-toolbox` | `com.onixbyte.common` |
|
||||
| Crypto Toolbox | `crypto-toolbox` | `com.onixbyte.crypto` |
|
||||
| Math Toolbox | `math-toolbox` | `com.onixbyte.math` |
|
||||
| Tuple | `tuple` | `com.onixbyte.tuple` |
|
||||
| Identity Generator | `identity-generator` | `com.onixbyte.identitygenerator` |
|
||||
|
||||
---
|
||||
|
||||
## Common Toolbox
|
||||
|
||||
### Adapter
|
||||
|
||||
`ObjectMapAdapter` — Converts between objects and maps. Useful for serialisation or data transformation scenarios where key-value representations are preferred.
|
||||
|
||||
```java
|
||||
import com.onixbyte.common.adapter.ObjectMapAdapter;
|
||||
```
|
||||
|
||||
### Utility Classes
|
||||
|
||||
| Class | Description |
|
||||
| ---------------- | ---------------------------------------------------------- |
|
||||
| `AesUtil` | AES symmetric encryption/decryption with key generation |
|
||||
| `Base64Util` | Base64 encoding and decoding with multiple charset support |
|
||||
| `BoolUtil` | Boolean parsing and conversion utilities |
|
||||
| `BranchUtil` | Conditional branching helpers with functional-style chains |
|
||||
| `CollectionUtil` | Collection manipulation — grouping, partitioning, merging |
|
||||
| `HashUtil` | Hashing utilities supporting MD5, SHA-1, SHA-256, SHA-512 |
|
||||
| `MapUtil` | Map builder, merge, and transformation helpers |
|
||||
| `RangeUtil` | Numeric range operations with boundary handling |
|
||||
|
||||
**AesUtil Example:**
|
||||
|
||||
```java
|
||||
var key = AesUtil.generateKey();
|
||||
var encrypted = AesUtil.encrypt("hello world", key);
|
||||
var decrypted = AesUtil.decrypt(encrypted, key);
|
||||
```
|
||||
|
||||
**HashUtil Example:**
|
||||
|
||||
```java
|
||||
var md5 = HashUtil.md5("hello world");
|
||||
var sha256 = HashUtil.sha256("hello world");
|
||||
var fileSha = HashUtil.sha256(new File("data.bin"));
|
||||
```
|
||||
|
||||
**CollectionUtil Example:**
|
||||
|
||||
```java
|
||||
var partitioned = CollectionUtil.partition(list, 100);
|
||||
var grouped = CollectionUtil.groupBy(users, User::getDepartment);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Crypto Toolbox
|
||||
|
||||
Provides a clean abstraction for loading cryptographic keys in PEM format. Supports both RSA and ECDSA algorithms.
|
||||
|
||||
### Key Loaders
|
||||
|
||||
| Class | Description |
|
||||
| --------------------- | ---------------------------------------- |
|
||||
| `RSAPrivateKeyLoader` | Load RSA private keys from PEM strings |
|
||||
| `RSAPublicKeyLoader` | Load RSA public keys from PEM strings |
|
||||
| `ECPrivateKeyLoader` | Load ECDSA private keys from PEM strings |
|
||||
| `ECPublicKeyLoader` | Load ECDSA public keys from PEM strings |
|
||||
|
||||
All key loaders implement either `PrivateKeyLoader` or `PublicKeyLoader` interfaces.
|
||||
|
||||
**RSA Example:**
|
||||
|
||||
```java
|
||||
var privateKey = new RSAPrivateKeyLoader().load(pemString);
|
||||
var publicKey = new RSAPublicKeyLoader().load(pemString);
|
||||
```
|
||||
|
||||
**ECDSA Example:**
|
||||
|
||||
```java
|
||||
var ecPrivateKey = new ECPrivateKeyLoader().load(pemString);
|
||||
var ecPublicKey = new ECPublicKeyLoader().load(pemString);
|
||||
```
|
||||
|
||||
### Utility
|
||||
|
||||
`CryptoUtil` — Provides convenient methods for digital signature creation and verification, as well as general-purpose cryptographic helpers.
|
||||
|
||||
### Exceptions
|
||||
|
||||
| Class | Description |
|
||||
| --------------------- | ---------------------------------------------- |
|
||||
| `KeyLoadingException` | Thrown when a PEM-formatted key fails to parse |
|
||||
|
||||
---
|
||||
|
||||
## Math Toolbox
|
||||
|
||||
Statistical computation utilities for working with numeric datasets.
|
||||
|
||||
### Core Classes
|
||||
|
||||
| Class | Description |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------ |
|
||||
| `Calculator` | Statistical calculator for mean, median, variance, standard deviation, sum, min, max |
|
||||
| `PercentileCalculator` | Percentile computation with configurable interpolation strategies |
|
||||
|
||||
**Calculator Example:**
|
||||
|
||||
```java
|
||||
var stats = new Calculator(Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0));
|
||||
double mean = stats.getMean();
|
||||
double median = stats.getMedian();
|
||||
double stdDev = stats.getStandardDeviation();
|
||||
```
|
||||
|
||||
**PercentileCalculator Example:**
|
||||
|
||||
```java
|
||||
var calculator = new PercentileCalculator(data);
|
||||
double p95 = calculator.calculate(95);
|
||||
double p99 = calculator.calculate(99);
|
||||
```
|
||||
|
||||
### Model
|
||||
|
||||
| Class | Description |
|
||||
| ---------------- | ------------------------------------------------------------------ |
|
||||
| `QuartileBounds` | Holds Q1, median (Q2), Q3 values along with IQR and whisker bounds |
|
||||
|
||||
---
|
||||
|
||||
## Tuple
|
||||
|
||||
Lightweight generic tuple types for when you need to return multiple values without defining a dedicated class.
|
||||
|
||||
### Classes
|
||||
|
||||
| Class | Type Parameters | Mutability |
|
||||
| -------------------------- | --------------- | ---------- |
|
||||
| `Tuple<A, B>` | Two elements | Mutable |
|
||||
| `ImmutableTuple<A, B>` | Two elements | Immutable |
|
||||
| `Triple<A, B, C>` | Three elements | Mutable |
|
||||
| `ImmutableTriple<A, B, C>` | Three elements | Immutable |
|
||||
|
||||
**Example:**
|
||||
|
||||
```java
|
||||
var pair = Tuple.of("key", 42);
|
||||
var triple = Triple.of("id", "name", 100);
|
||||
var immutable = ImmutableTuple.of("constant", true);
|
||||
|
||||
// Accessors
|
||||
String first = pair.getFirst();
|
||||
Integer second = pair.getSecond();
|
||||
|
||||
// Triple accessors
|
||||
String a = triple.getFirst();
|
||||
String b = triple.getSecond();
|
||||
Integer c = triple.getThird();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Identity Generator
|
||||
|
||||
Generates unique identifiers suitable for use as database primary keys or distributed IDs.
|
||||
|
||||
### Interface
|
||||
|
||||
`IdentityGenerator<T>` — Generates identities of type `T`.
|
||||
|
||||
### Implementations
|
||||
|
||||
| Class | Description |
|
||||
| ---------------------------- | ------------------------------------------------------------------- |
|
||||
| `SequentialUuidGenerator` | Generates time-ordered, sequential UUIDs (optimised for DB indexes) |
|
||||
| `SnowflakeIdentityGenerator` | Snowflake-style distributed unique ID generation |
|
||||
|
||||
**Sequential UUID:**
|
||||
|
||||
```java
|
||||
var generator = new SequentialUuidGenerator();
|
||||
UUID id = generator.nextId(); // time-ordered UUID, B-tree friendly
|
||||
```
|
||||
|
||||
**Snowflake:**
|
||||
|
||||
```java
|
||||
var generator = new SnowflakeIdentityGenerator(workerId, datacenterId);
|
||||
long id = generator.nextId(); // 64-bit snowflake ID
|
||||
```
|
||||
|
||||
### Exceptions
|
||||
|
||||
| Class | Description |
|
||||
| ----------------- | ------------------------------------------------------------------------ |
|
||||
| `TimingException` | Thrown when the system clock moves backwards, compromising ID uniqueness |
|
||||
@@ -1,54 +0,0 @@
|
||||
---
|
||||
title: OnixByte Toolbox
|
||||
---
|
||||
|
||||
import { Tabs, Tab } from "@rspress/core/theme"
|
||||
|
||||
## Introduction
|
||||
|
||||
OnixByte Toolbox is a Development Kit for Java applications that provides a set of convenient, reusable tools for writing code efficiently. It is designed and maintained by OnixByte as a monorepo containing multiple purpose-built libraries, each addressing a specific development need — from common utilities and cryptography to mathematical computation and identity generation.
|
||||
|
||||
The toolkit is available on Maven Central under the `com.onixbyte` group ID. Each module is independently versioned and can be included in any Java 17+ project.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Artifact ID | Description |
|
||||
| ------------------ | -------------------- | ------------------------------------------------------------------------- |
|
||||
| Common Toolbox | `common-toolbox` | Everyday utilities — AES, hashing, Base64, collections, branching, ranges |
|
||||
| Crypto Toolbox | `crypto-toolbox` | Public/private key loading for RSA and ECDSA algorithms |
|
||||
| Math Toolbox | `math-toolbox` | Statistical calculation, percentile analysis, quartile computation |
|
||||
| Tuple | `tuple` | Generic 2-tuple and 3-tuple with immutable variants |
|
||||
| Identity Generator | `identity-generator` | Snowflake-style and sequential UUID identity generation |
|
||||
| Version Catalogue | `version-catalogue` | Centralised Gradle version catalogue for OnixByte projects |
|
||||
|
||||
## Installation
|
||||
|
||||
Add the desired module as a dependency in your build config:
|
||||
|
||||
<Tabs>
|
||||
<Tab label="Gradle with Kotlin DSL">
|
||||
```kotlin title="build.gradle.kts"
|
||||
dependencies {
|
||||
implementation("com.onixbyte:$artefactId:$version")
|
||||
}
|
||||
```
|
||||
</Tab>
|
||||
|
||||
<Tab label="Maven">
|
||||
```xml title="pom.xml"
|
||||
<dependency>
|
||||
<groupId>com.onixbyte</groupId>
|
||||
<artifactId>${artefactId}</artifactId>
|
||||
<version>${version}</version>
|
||||
</dependency>
|
||||
```
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 17 or later
|
||||
|
||||
## License
|
||||
|
||||
OnixByte Toolbox is open-source software released under the MIT Licence.
|
||||
@@ -1,98 +0,0 @@
|
||||
---
|
||||
title: Regions for Java
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
**regions4j** is a lightweight, type-safe Java library that provides comprehensive metadata for world regions based on the ISO 3166-1 standard. It is designed to simplify internationalisation (i18n), telecommunication routing, and regional data management in Java-based enterprise applications.
|
||||
|
||||
The library models every ISO 3166-1 country and territory as an enum constant, giving you compile-time safety and IDE autocompletion when referencing regions. With built-in O(1) lookups, zero external dependencies, and Java 8 compatibility, it is suitable for projects of any size — from microservices to monolithic enterprise systems.
|
||||
|
||||
## Features
|
||||
|
||||
- **ISO 3166-1 Compliant** — Full support for Alpha-2 (`abbreviation`) and Alpha-3 (`code`) formats across all assigned countries and territories.
|
||||
- **Telecommunication Metadata** — Includes E.164 international dialling prefixes for every region.
|
||||
- **Locality Support** — Default locale tags (`zh_HK`, `en_US`, `en_GB`, etc.) for each region to assist with i18n workflows.
|
||||
- **High Performance** — Thread-safe, immutable internal hash maps provide constant-time lookups by abbreviation or code.
|
||||
- **Zero Dependencies** — A pure Java library with no external overhead.
|
||||
- **Calendar Versioning** — Versions follow a `YYYY.MM` scheme (e.g. `2025.12`) to transparently indicate the freshness of the underlying ISO data, rather than semantic versioning.
|
||||
|
||||
## Core Schema
|
||||
|
||||
Each region exposes four attributes:
|
||||
|
||||
| Attribute | Type | Description | Example |
|
||||
| -------------- | ------ | ----------------------------------- | ---------------------------------- |
|
||||
| `abbreviation` | String | ISO 3166-1 alpha-2 abbreviation | `HK`, `US`, `GB`, `RU` |
|
||||
| `code` | String | ISO 3166-1 alpha-3 code | `HKG`, `USA`, `GBR`, `RUS` |
|
||||
| `callingCode` | String | E.164 international dialling prefix | `852`, `1`, `44`, `7` |
|
||||
| `locale` | String | Default language and region tag | `zh_HK`, `en_US`, `en_GB`, `ru_RU` |
|
||||
|
||||
## Installation
|
||||
|
||||
**Maven:**
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.onixbyte</groupId>
|
||||
<artifactId>regions4j</artifactId>
|
||||
<version>2025.12</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**Gradle (Kotlin DSL):**
|
||||
|
||||
```kotlin
|
||||
implementation("com.onixbyte:regions4j:2025.12")
|
||||
```
|
||||
|
||||
> **Versioning note:** This library uses Calendar Versioning (`YYYY.MM`) rather than Semantic Versioning. A version string like `2025.12` tells you immediately that the regional data reflects the ISO 3166-1 state as of December 2025.
|
||||
|
||||
## Usage
|
||||
|
||||
### Direct Access
|
||||
|
||||
Reference any region directly via its enum constant:
|
||||
|
||||
```java
|
||||
Region uk = Region.UNITED_KINGDOM;
|
||||
|
||||
uk.getAbbreviation(); // "GB"
|
||||
uk.getCode(); // "GBR"
|
||||
uk.getDialCode(); // "44"
|
||||
uk.getLocale(); // "en_GB"
|
||||
```
|
||||
|
||||
### Efficient Lookups
|
||||
|
||||
Look up regions from string inputs — ideal for processing web requests, API payloads, or database records:
|
||||
|
||||
```java
|
||||
// By Alpha-2 abbreviation (case-sensitive)
|
||||
Region region = Region.fromAbbreviation("HK");
|
||||
|
||||
// By Alpha-3 code (case-insensitive)
|
||||
Region region = Region.fromCode("HKG");
|
||||
```
|
||||
|
||||
### Java Locale Integration
|
||||
|
||||
Convert a region's locale string to a `java.util.Locale` for use with standard Java i18n APIs:
|
||||
|
||||
```java
|
||||
Region region = Region.CHINA;
|
||||
Locale javaLocale = Locale.forLanguageTag(region.getLocale().replace("_", "-"));
|
||||
javaLocale.getDisplayCountry(Locale.UK); // "China"
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
On first access, the library initialises all regions into immutable `HashMap` structures. Subsequent lookups via `fromAbbreviation()` and `fromCode()` are thread-safe and run in constant time — suitable for high-throughput request paths.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Java 8 or later
|
||||
|
||||
## License
|
||||
|
||||
regions4j is open-source software released under the MIT License.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
title: Vite Plugins
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
A collection of Vite plugins developed and maintained by OnixByte. Each plugin is designed to solve a specific problem in the Vite development workflow — lightweight, zero-dependency, and plug-and-play.
|
||||
|
||||
## Available Plugins
|
||||
|
||||
- **[Port Checker](/opensource-projects/vite-plugins/port-checker)** — Warns when the dev server listens on a browser-restricted port (per the Fetch specification), helping you avoid silent connection failures.
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
title: Port Checker
|
||||
---
|
||||
|
||||
import { Tabs, Tab } from "@rspress/core/theme"
|
||||
|
||||
## Introduction
|
||||
|
||||
**vite-plugin-port-checker** is a lightweight Vite plugin that warns you when the dev server is listening on a port blocked by common browsers. Browsers such as Chrome and Firefox maintain a [list of restricted ports](https://fetch.spec.whatwg.org/#port-blocking) — typically those associated with well-known protocols like SMTP, SSH, and DNS — and refuse to connect to them. Accidentally running your dev server on one of these ports leads to a confusing "connection refused" or "blocked" error with no clear explanation.
|
||||
|
||||
This plugin detects the situation at startup and prints a clear, colour-coded warning to the console, advising you to change the `server.port` setting in your Vite configuration.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Detection** — Checks the dev server port against the complete Fetch spec port-blocking list (68 restricted ports).
|
||||
- **Clear Warnings** — Yellow-highlighted console output with actionable guidance.
|
||||
- **Zero Configuration** — Works out of the box with no options to set.
|
||||
- **Lightweight** — Single-file plugin with no dependencies beyond Vite itself.
|
||||
|
||||
## Installation
|
||||
|
||||
<Tabs>
|
||||
<Tab label="npm">```bash npm install vite-plugin-port-checker -D ```</Tab>
|
||||
<Tab label="pnpm">```bash pnpm add vite-plugin-port-checker -D ```</Tab>
|
||||
</Tabs>
|
||||
|
||||
## Usage
|
||||
|
||||
Add the plugin to your `vite.config.ts`:
|
||||
|
||||
```ts
|
||||
// vite.config.ts
|
||||
import { defineConfig } from "vite"
|
||||
import checkRestrictedPort from "vite-plugin-port-checker"
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [checkRestrictedPort()],
|
||||
})
|
||||
```
|
||||
|
||||
When the dev server starts on a restricted port (e.g. port 25, 22, or 53), you will see:
|
||||
|
||||
```
|
||||
[Warning] The current listening port 25 is categorised as a restricted port
|
||||
by most browsers. This may prevent you from accessing the application.
|
||||
Please consider changing the port in your 'vite.config.ts' or 'vite.config.js'
|
||||
via 'server.port'.
|
||||
```
|
||||
|
||||
## How It Works
|
||||
|
||||
The plugin hooks into Vite's `configureServer` lifecycle. Once the HTTP server emits the `listening` event, the plugin inspects the bound port and checks it against the hardcoded set of 68 restricted ports defined in the [Fetch specification](https://fetch.spec.whatwg.org/#port-blocking). If there is a match, a yellow `console.warn` message alerts the developer.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Vite 3.0.0 or later
|
||||
|
||||
## License
|
||||
|
||||
vite-plugin-port-checker is open-source software released under the MIT License.
|
||||
Reference in new issue
Block a user