Compare commits

..
10 Commits
Author SHA1 Message Date
zihluwang 7a63074a65 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
2026-09-29 12:21:53 +08:00
zihluwang e149401346 feat(theme): add a capabilities section band
- Replace the techStack band with project-type capabilities
- Delete the TechStack component and its section type
- Give grid cards an optional status badge
- Pad --big buttons, which Rspress ships with none
- Revise the home, services and products copy
2026-09-29 12:21:25 +08:00
zihluwang f51407b98b fix(theme): restore article styles on doc pages
Overriding DocContent took the upstream module out of the bundle, and
with it the `doc.css` it imports as a side effect. Every page rendering
through DocLayout lost its article typography, table scroll container
and heading anchors — while the rest of the theme stayed intact, which
is why only some styles went missing.

The sole reference to the upstream component was a `typeof`, erased at
compile time, so the bundler saw a module it could drop.

- Delegate to the upstream component when a page has no hoisted bands,
  which keeps that module and its stylesheet in the dependency graph.
- Move the hoisting variant into its own component, so useScrollAfterNav
  is not called after a conditional return. DocContent survives
  client-side navigation, so its hook count has to stay constant.
2026-09-25 22:10:15 +08:00
zihluwang 9d095eba1b feat(theme): add top placement for sections
- Add `placement: top|bottom` to the section frontmatter
- Override `DocContent` to render hoisted bands under the page title
- Derive the zebra stripe from list position so split bands stay continuous
- Opt marketing bands out of doc typography with `rp-not-doc`
- Point the legal and privacy contact address at legal@onixbyte.com
- Condense the products pages' body copy into their section frontmatter
2026-09-25 18:04:49 +08:00
zihluwang d21b8f6b2f chore: drop the open licence and stop taking pull requests
The site exists to be read, not reused. It is no longer licensed for
anything, and external contributions have moved to issues only.

- Replace the MIT + CC-BY-4.0 dual licence with an all-rights-reserved
  notice. No permission is granted to copy, modify or distribute any part
  of the repository; reading and linking stay free.
- Carve out third-party components explicitly. Rspress, React, Rsbuild and
  Tailwind CSS keep their own licences, and an all-rights-reserved notice
  that did not say so would be wrong.
- Rewrite CONTRIBUTING around issues rather than pull requests, and say
  plainly that PRs will be closed without review.
- Mark the package UNLICENSED, the npm convention for proprietary code.

Note: this makes the repository source-available, not open source — a
licence that forbids all reuse cannot meet the OSI definition. The README
no longer calls it open source.

Closing PRs for real also needs the "Pull requests" feature switched off
in the repository's GitHub settings; that is not something this commit can
do.
2026-09-25 15:38:08 +08:00
zihluwang 164c319ac1 feat(i18n): default the cn build to Chinese
The .cn site exists for Chinese readers, so the unprefixed routes should
serve Chinese there. Rspress omits the prefix for the default locale, so
this also moves the routes:

              .com                      .cn
  English     /                         /en-gb/
  Chinese     /zh-hans/                 /

- Make the default locale a per-target value rather than a constant, and
  move the localePrefix helper inside createConfig so a prefix can no
  longer be hardcoded elsewhere.
- Build the sitemap priority map from localePrefix. The keys are full
  route paths, so the previous hardcoded "/zh-hans/..." entries would
  have silently stopped matching on .cn.
- Widen the nav Home activeMatch to cover either prefixed home, since the
  two targets put a different locale behind the prefix.

Note: README.md also carries the licence wording changed in the next
commit — a single file's hunks cannot be split non-interactively.
2026-09-25 15:38:03 +08:00
zihluwang 861852961f style: format the repository with prettier
These files were already out of step with the project's Prettier config
before this branch of work; running `pnpm format` finally swept them up.
No semantic changes.

Kept separate from the functional commits so the reformatting does not
bury them in diff noise.
2026-09-25 15:37:59 +08:00
zihluwang 6800f7251d docs(blogs): drop dead scws download links
Both URLs in the tip were dead. The GitHub one referenced an account that
does not exist, and the Gitea one is retired.

Point at the upstream SCWS project instead, and stop promising a
pre-built Windows binary — upstream publishes source only, and the 1.2.3
the dead link referenced was OnixByte's own build.
2026-09-25 14:49:03 +08:00
zihluwang c516341fae feat(content): add products, services and legal pages
- Add /products/, covering the one product actually in service. It is
  written to match reality rather than padded out to look like a matrix.
- Add /services/, describing the work taken on and, deliberately, the
  work that is not a good fit.
- Add the privacy policy and terms, linked from the footer. Both describe
  what the site actually does — no cookies, no analytics — and carry
  TODO notes for the parts needing legal review.
2026-09-25 14:48:59 +08:00
zihluwang 28f07677f3 feat(homepage): rebuild the homepage around the business
The old hero described a small open-source collective crafting tools for
developers, which no longer matches what the company sells.

- Replace the tagline with a value proposition and point the hero actions
  at Services and Products instead of the GitHub organisation.
- Add four content bands: why us, the technology we use, selected work
  and a contact band with an email address and WeChat QR.
- Point the feature cards at GitHub, and fix a dead link in the process:
  delta-force-guide is now ahsarah-guide.
2026-09-25 14:48:55 +08:00
123 changed files with 1196 additions and 8425 deletions

No files matched your search

+20 -25
View File
@@ -49,8 +49,8 @@ Minimal setup:
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import './index.css'; // optional import "./index.css" // optional
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
``` ```
**Critical import rule**: Inside `theme/` files, always import from `@rspress/core/theme-original`. The path `@rspress/core/theme` resolves to your own `theme/index.tsx`, which causes circular imports. (In `docs/` MDX files, `@rspress/core/theme` is fine — it correctly points to your custom theme.) **Critical import rule**: Inside `theme/` files, always import from `@rspress/core/theme-original`. The path `@rspress/core/theme` resolves to your own `theme/index.tsx`, which causes circular imports. (In `docs/` MDX files, `@rspress/core/theme` is fine — it correctly points to your custom theme.)
@@ -80,8 +80,8 @@ Override CSS custom properties for brand colors, backgrounds, text, code blocks,
```ts ```ts
// rspress.config.ts // rspress.config.ts
export default defineConfig({ export default defineConfig({
globalStyles: path.join(__dirname, 'styles/custom.css'), globalStyles: path.join(__dirname, "styles/custom.css"),
}); })
``` ```
> **Full variable list**: Read `references/css-variables.md` for all available CSS variables with light/dark defaults. > **Full variable list**: Read `references/css-variables.md` for all available CSS variables with light/dark defaults.
@@ -104,13 +104,11 @@ Inject content at specific positions in the layout without replacing built-in co
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original'; import { Layout as OriginalLayout } from "@rspress/core/theme-original"
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export function Layout() { export function Layout() {
return ( return <OriginalLayout beforeNavTitle={<MyLogo />} bottom={<CustomFooter />} />
<OriginalLayout beforeNavTitle={<MyLogo />} bottom={<CustomFooter />} />
);
} }
``` ```
@@ -132,8 +130,8 @@ rspress eject DocFooter # eject to theme/components/DocFooter/
Then re-export in `theme/index.tsx` (named export takes precedence over the wildcard): Then re-export in `theme/index.tsx` (named export takes precedence over the wildcard):
```tsx ```tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export { DocFooter } from './components/DocFooter'; export { DocFooter } from "./components/DocFooter"
``` ```
> **Component list & patterns**: Read `references/eject-components.md` for available components, workflow, and common patterns. > **Component list & patterns**: Read `references/eject-components.md` for available components, workflow, and common patterns.
@@ -147,38 +145,38 @@ Rspress has 27 built-in icons used across the UI. You can replace any of them by
**Icon type**: Each icon is a React component or a URL string: **Icon type**: Each icon is a React component or a URL string:
```ts ```ts
import type { FC, SVGProps } from 'react'; import type { FC, SVGProps } from "react"
type Icon = FC<SVGProps<SVGSVGElement>> | string; type Icon = FC<SVGProps<SVGSVGElement>> | string
``` ```
**Example 1** — Replace an icon with a custom SVG component: **Example 1** — Replace an icon with a custom SVG component:
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
// Named export overrides the wildcard — replaces the GitHub icon site-wide // Named export overrides the wildcard — replaces the GitHub icon site-wide
export const IconGithub = (props: React.SVGProps<SVGSVGElement>) => ( export const IconGithub = (props: React.SVGProps<SVGSVGElement>) => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" {...props}>
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 ..." fill="currentColor" /> <path d="M12 2C6.477 2 2 6.484 2 12.017c0 ..." fill="currentColor" />
</svg> </svg>
); )
``` ```
**Example 2** — Use an SVGR import: **Example 2** — Use an SVGR import:
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
import CustomGithubIcon from './icons/github.svg?react'; import CustomGithubIcon from "./icons/github.svg?react"
export const IconGithub = CustomGithubIcon; export const IconGithub = CustomGithubIcon
``` ```
**Using `SvgWrapper` in MDX or custom components**: **Using `SvgWrapper` in MDX or custom components**:
```mdx ```mdx
import { SvgWrapper, IconGithub } from '@rspress/core/theme'; import { SvgWrapper, IconGithub } from "@rspress/core/theme"
<SvgWrapper icon={IconGithub} width={24} height={24} /> <SvgWrapper icon={IconGithub} width={24} height={24} />
``` ```
@@ -197,13 +195,10 @@ For components that should render on every page without theme overrides:
// rspress.config.ts // rspress.config.ts
export default defineConfig({ export default defineConfig({
globalUIComponents: [ globalUIComponents: [
path.join(__dirname, 'components', 'BackToTop.tsx'), path.join(__dirname, "components", "BackToTop.tsx"),
[ [path.join(__dirname, "components", "Analytics.tsx"), { trackingId: "..." }],
path.join(__dirname, 'components', 'Analytics.tsx'),
{ trackingId: '...' },
],
], ],
}); })
``` ```
--- ---
@@ -50,8 +50,8 @@ Ejected source is placed in `theme/components/<ComponentName>/`.
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export { DocFooter } from './components/DocFooter'; export { DocFooter } from "./components/DocFooter"
``` ```
The named export takes precedence over the wildcard re-export, so Rspress uses your custom version. The named export takes precedence over the wildcard re-export, so Rspress uses your custom version.
@@ -64,21 +64,21 @@ The most common eject use case is wrapping the entire app in a context provider
```tsx ```tsx
// theme/components/Root/index.tsx // theme/components/Root/index.tsx
import type { RootProps } from '@rspress/core/theme'; import type { RootProps } from "@rspress/core/theme"
export function Root({ children }: RootProps) { export function Root({ children }: RootProps) {
return ( return (
<ThemeProvider> <ThemeProvider>
<AnalyticsProvider>{children}</AnalyticsProvider> <AnalyticsProvider>{children}</AnalyticsProvider>
</ThemeProvider> </ThemeProvider>
); )
} }
``` ```
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export { Root } from './components/Root'; export { Root } from "./components/Root"
``` ```
## Common Pattern: Custom Home Page (HomeLayout) ## Common Pattern: Custom Home Page (HomeLayout)
@@ -87,12 +87,12 @@ When the default home page structure (Hero + Features) doesn't meet the design r
```tsx ```tsx
// theme/components/HomeLayout/index.tsx // theme/components/HomeLayout/index.tsx
import { useSite, useLang } from '@rspress/core/runtime'; import { useSite, useLang } from "@rspress/core/runtime"
export function HomeLayout() { export function HomeLayout() {
const site = useSite(); const site = useSite()
const lang = useLang(); const lang = useLang()
const { title, description } = site.siteData; const { title, description } = site.siteData
return ( return (
<div className="custom-home"> <div className="custom-home">
@@ -100,10 +100,7 @@ export function HomeLayout() {
<h1>{title}</h1> <h1>{title}</h1>
<p>{description}</p> <p>{description}</p>
<div className="hero-actions"> <div className="hero-actions">
<a <a href={lang === "zh" ? "/zh/guide/start" : "/guide/start"} className="primary-btn">
href={lang === 'zh' ? '/zh/guide/start' : '/guide/start'}
className="primary-btn"
>
Get Started Get Started
</a> </a>
<a href="https://github.com/..." className="secondary-btn"> <a href="https://github.com/..." className="secondary-btn">
@@ -116,14 +113,14 @@ export function HomeLayout() {
{/* Custom content: testimonials, stats, demos, etc. */} {/* Custom content: testimonials, stats, demos, etc. */}
</section> </section>
</div> </div>
); )
} }
``` ```
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export { HomeLayout } from './components/HomeLayout'; export { HomeLayout } from "./components/HomeLayout"
``` ```
The named export overrides the built-in `HomeLayout` from the wildcard re-export — no need to eject first. The named export overrides the built-in `HomeLayout` from the wildcard re-export — no need to eject first.
@@ -134,16 +131,16 @@ If you only need to add content before/after the Hero or Features sections (with
```tsx ```tsx
// theme/components/DocFooter/index.tsx // theme/components/DocFooter/index.tsx
import { useFrontmatter } from '@rspress/core/runtime'; import { useFrontmatter } from "@rspress/core/runtime"
export function DocFooter() { export function DocFooter() {
const frontmatter = useFrontmatter(); const frontmatter = useFrontmatter()
return ( return (
<footer className="custom-doc-footer"> <footer className="custom-doc-footer">
{frontmatter.author && <span>Author: {frontmatter.author}</span>} {frontmatter.author && <span>Author: {frontmatter.author}</span>}
<a href="https://github.com/...">Edit this page</a> <a href="https://github.com/...">Edit this page</a>
</footer> </footer>
); )
} }
``` ```
@@ -71,11 +71,11 @@ All examples below follow the same structure in `theme/index.tsx`. The key parts
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original'; import { Layout as OriginalLayout } from "@rspress/core/theme-original"
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export function Layout() { export function Layout() {
return <OriginalLayout beforeNavTitle={<MyLogo />} />; return <OriginalLayout beforeNavTitle={<MyLogo />} />
} }
``` ```
@@ -83,8 +83,8 @@ export function Layout() {
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original'; import { Layout as OriginalLayout } from "@rspress/core/theme-original"
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
export function Layout() { export function Layout() {
return ( return (
@@ -93,7 +93,7 @@ export function Layout() {
bottom={<footer>© 2025 My Company</footer>} bottom={<footer>© 2025 My Company</footer>}
afterOutline={<div>Related resources</div>} afterOutline={<div>Related resources</div>}
/> />
); )
} }
``` ```
@@ -101,17 +101,17 @@ export function Layout() {
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original'; import { Layout as OriginalLayout } from "@rspress/core/theme-original"
import { useLang } from '@rspress/core/runtime'; import { useLang } from "@rspress/core/runtime"
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
function LocalizedBanner() { function LocalizedBanner() {
const lang = useLang(); const lang = useLang()
return <div>{lang === 'zh' ? '欢迎' : 'Welcome'}</div>; return <div>{lang === "zh" ? "欢迎" : "Welcome"}</div>
} }
export function Layout() { export function Layout() {
return <OriginalLayout top={<LocalizedBanner />} />; return <OriginalLayout top={<LocalizedBanner />} />
} }
``` ```
@@ -121,17 +121,15 @@ The `components` slot accepts a `Record<string, React.ComponentType>` to overrid
```tsx ```tsx
// theme/index.tsx // theme/index.tsx
import { Layout as OriginalLayout } from '@rspress/core/theme-original'; import { Layout as OriginalLayout } from "@rspress/core/theme-original"
export * from '@rspress/core/theme-original'; export * from "@rspress/core/theme-original"
function CustomH1({ children }: { children: React.ReactNode }) { function CustomH1({ children }: { children: React.ReactNode }) {
return ( return <h1 style={{ borderBottom: "2px solid var(--rp-c-brand)" }}>{children}</h1>
<h1 style={{ borderBottom: '2px solid var(--rp-c-brand)' }}>{children}</h1>
);
} }
export function Layout() { export function Layout() {
return <OriginalLayout components={{ h1: CustomH1 }} />; return <OriginalLayout components={{ h1: CustomH1 }} />
} }
``` ```
+8 -3
View File
@@ -19,9 +19,14 @@ pnpm format # Prettier — format all files
`rspress.config.ts` exports a `createConfig(target)` factory; `rspress.cn.config.ts` `rspress.config.ts` exports a `createConfig(target)` factory; `rspress.cn.config.ts`
is a three-line wrapper calling it with `"cn"`, passed via `--config`. The two is a three-line wrapper calling it with `"cn"`, passed via `--config`. The two
targets differ in the canonical origin used for `head`, the sitemap and targets differ in the default (unprefixed) language, the canonical origin used
`robots.txt`, and in which social links are rendered. Both write to `doc_build/`, for `head`, the sitemap and `robots.txt`, and in which social links are rendered.
so build and deploy one target at a time. Both write to `doc_build/`, so build and deploy one target at a time.
`.com` defaults to `en-gb` and `.cn` to `zh-hans`, so the same content is served
at different paths per target — Rspress omits the prefix for the default locale.
Anything referencing a locale prefix must go through the `localePrefix` helper
inside `createConfig`, never a hardcoded `/zh-hans`.
## Architecture ## Architecture
+46 -7
View File
@@ -1,8 +1,46 @@
# Contributing to OnixByte Homepage # Contributing to OnixByte Homepage
This repository holds the company website. It is published openly so that the
Rspress set-up can be read and learned from, but it is **not open for external
contributions**.
## We do not accept pull requests
External pull requests are not accepted and will be closed without review.
This is not a comment on the work — it is that a company website is edited by
the people accountable for what it says, and merging outside changes would put
content on the site that nobody here has signed off.
## Issues are welcome
Please do open an issue if you spot:
- a broken or dead link
- something factually wrong, in either language
- a typo, or a place where the British English or Chinese wording is off
- an accessibility problem
- a build or runtime bug you can reproduce
Issues are read, and genuine problems are usually fixed. If a report turns into
a change, we will make that change ourselves.
## Licence
Nothing in this repository is licensed for reuse. See [LICENCE](./LICENCE) —
copyright is retained and no permission is granted to copy, modify or
distribute any part of it. Reading it and linking to it are fine.
Third-party software this project depends on keeps its own licence; see
[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
---
The rest of this document is for people working on the site internally.
## Development Setup ## Development Setup
This project uses [Rspress](https://rspress.rs) as the static site framework and pnpm as the package manager. This project uses [Rspress](https://rspress.rs) as the static site framework and
pnpm as the package manager.
```bash ```bash
pnpm install pnpm install
@@ -23,8 +61,8 @@ theme/ # Custom theme overrides
- Write content in both English (British) and Simplified Chinese. British - Write content in both English (British) and Simplified Chinese. British
spelling is required throughout — interface text, documentation, code spelling is required throughout — interface text, documentation, code
comments and commit messages (`customised`, `centre`, `colour`, and so on). comments and commit messages (`customised`, `centre`, `colour`, and so on).
- Blog posts and documentation articles are licensed under CC-BY-4.0. - Address the reader as 您. This site speaks to buyers and decision-makers, not
- Code contributions are licensed under MIT. to engineers.
## Navigation and Sidebars ## Navigation and Sidebars
@@ -42,8 +80,9 @@ the theme:
Do not add a `_meta.json` next to `_nav.json` at the root of a locale Do not add a `_meta.json` next to `_nav.json` at the root of a locale
directory; doing so collapses the whole site into one flat sidebar. directory; doing so collapses the whole site into one flat sidebar.
## Pull Request Process ## Before Committing
1. Ensure the site builds cleanly: `pnpm build` 1. Ensure the site builds: `pnpm build:com` and `pnpm build:cn`
2. Run linting: `pnpm lint` 2. Type-check: `pnpm typecheck`
3. If adding a new page, include both `en-gb` and `zh-hans` versions. 3. Lint and format: `pnpm lint` and `pnpm format`
4. If adding a new page, include both `en-gb` and `zh-hans` versions.
+22 -50
View File
@@ -1,59 +1,31 @@
MIT Licence Copyright © 2026 Changsha OnixByte Technology Co., Ltd.
版权所有 © 2026 长沙曜珀科技有限公司
Copyright (c) 2026 OnixByte All rights reserved. 保留所有权利。
Permission is hereby granted, free of charge, to any person obtaining a copy This repository and its contents — source code, build scripts, theme
of this software and associated documentation files (the "Software"), to deal components, configuration, and all written content under `docs/` — are the
in the Software without restriction, including without limitation the rights property of Changsha OnixByte Technology Co., Ltd.
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all No licence is granted. No permission is given to use, copy, modify, merge,
copies or substantial portions of the Software. publish, distribute, sublicense or sell any part of this repository, in whole
or in part, for any purpose, whether commercial or otherwise.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR This repository is published for reading. You are welcome to read it, to learn
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, from it, and to link to it. Those things need no permission.
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
---
CC-BY-4.0
Copyright (c) 2026 OnixByte
You are free to:
- Share - copy and redistribute the material in any medium or format for any purpose,
even commercially.
- Adapt — remix, transform, and build upon the material for any purpose, even commercially.
The licensor cannot revoke these freedoms as long as you follow the license terms.
Under the following terms:
- Attribution — You must give appropriate credit, provide a link to the license, and indicate
if changes were made. You may do so in any reasonable manner, but not in any way that suggests
the licensor endorses you or your use.
No additional restrictions — You may not apply legal terms or technological measures that
legally restrict others from doing anything the license permits.
NOTICES
You do not have to comply with the license for elements of the material in the public domain or
where your use is permitted by an applicable exception or limitation.
No warranties are given. The license may not give you all of the permissions necessary for your
intended use. For example, other rights such as publicity, privacy, or moral rights may limit
how you use the material.
--- ---
This project applies a dual-licensing model: Third-party components
- Code (source code, build scripts, theme, configuration) is licensed under the MIT License (above). This project depends on third-party software, including Rspress, React,
- Content (blog posts, documentation articles, and other written content under docs/) is licensed under the Creative Commons Attribution 4.0 International License (CC-BY-4.0). Rsbuild and Tailwind CSS. Those components remain under their own licences,
and nothing in this notice changes or restricts the rights they grant you.
See THIRD_PARTY_NOTICES.md for the direct dependencies and their terms.
To view a copy of CC-BY-4.0, visit https://creativecommons.org/licenses/by/4.0/ ---
Enquiries
If you would like to use any part of this repository, write to
business@onixbyte.com.
+39 -12
View File
@@ -1,7 +1,8 @@
# OnixByte Homepage # OnixByte Homepage
The official OnixByte site — the company front door, alongside the technical The official OnixByte site — the company front door. The technical blog and the
blog, product documentation and development standards. open-source project documentation live in a separate repository, published at
`opensource.onixbyte.com` and `opensource.onixbyte.cn`.
## Tech Stack ## Tech Stack
@@ -42,14 +43,28 @@ pnpm format # Prettier — format all files
The site is published on two domains, and the two builds are not The site is published on two domains, and the two builds are not
interchangeable. `pnpm build:cn` runs `rspress build --config interchangeable. `pnpm build:cn` runs `rspress build --config
rspress.cn.config.ts`, which differs from the default in three ways: rspress.cn.config.ts`, which differs from the default in four ways:
| | `build:com` | `build:cn` | | | `build:com` | `build:cn` |
| ------------------------------- | ----------------- | -------------- | | ------------------------------- | ----------------- | -------------- |
| Default language (unprefixed) | `en-gb` | `zh-hans` |
| Canonical, `hreflang`, `og:url` | `onixbyte.com` | `onixbyte.cn` | | Canonical, `hreflang`, `og:url` | `onixbyte.com` | `onixbyte.cn` |
| `sitemap.xml`, `robots.txt` | `onixbyte.com` | `onixbyte.cn` | | `sitemap.xml`, `robots.txt` | `onixbyte.com` | `onixbyte.cn` |
| Social links | GitHub, Douyin, X | GitHub, Douyin | | Social links | GitHub, Douyin, X | GitHub, Douyin |
**The default language differs per target**, so the routes do too. Rspress
serves the default locale without a prefix:
| | `.com` | `.cn` |
| --------------- | ------------------------ | ----------------------------- |
| English home | `/` | `/en-gb/` |
| Chinese home | `/zh-hans/` | `/` |
| English privacy | `/legal/privacy` | `/en-gb/legal/privacy` |
| Chinese privacy | `/zh-hans/legal/privacy` | `/legal/privacy` |
Content paths under `docs/` are unaffected — only the prefixes change, and
Rspress rewrites internal links per locale automatically.
X is omitted from the `.cn` build because it is not reachable from mainland X is omitted from the `.cn` build because it is not reachable from mainland
China. Which links count as domestic is a single list in `rspress.config.ts` China. Which links count as domestic is a single list in `rspress.config.ts`
(`SOCIAL_LINKS`) — move an entry between the two arrays to change a build. (`SOCIAL_LINKS`) — move an entry between the two arrays to change a build.
@@ -73,14 +88,11 @@ second build overwrites the first. To preview the `.cn` variant in dev, run
docs/ docs/
├── public/ # Static assets served from the site root ├── public/ # Static assets served from the site root
│ └── onixbyte-*.svg │ └── onixbyte-*.svg
├── en-gb/ # English content (default locale, no URL prefix) ├── en-gb/ # English content
│ ├── _nav.json # Top navigation; labels are i18n keys │ ├── _nav.json # Top navigation; labels are i18n keys
│ ├── index.md # Marketing homepage │ ├── index.md # Marketing homepage
│ ├── blogs/ # Technical articles ("Insights")
│ ├── products/ # Products we run ourselves │ ├── products/ # Products we run ourselves
│ ├── services/ # Custom engineering services │ ├── services/ # Custom engineering services
│ ├── opensource-projects/ # Open-source project documentation
│ ├── notifications/ # Company announcements ("News")
│ └── legal/ # Privacy policy and terms │ └── legal/ # Privacy policy and terms
└── zh-hans/ # Simplified Chinese content (mirrors en-gb exactly) └── zh-hans/ # Simplified Chinese content (mirrors en-gb exactly)
@@ -89,6 +101,7 @@ theme/ # Custom Rspress theme
├── index.css # Brand colours and section styling ├── index.css # Brand colours and section styling
├── lib/ ├── lib/
│ ├── site.ts # Shared site constants │ ├── site.ts # Shared site constants
│ ├── i18n.ts # Types i18n.json for useI18n()
│ └── social-icons.ts # Inline SVG for social links with no preset icon │ └── social-icons.ts # Inline SVG for social links with no preset icon
└── components/ └── components/
├── Author.tsx # Byline, rendered from `author` frontmatter ├── Author.tsx # Byline, rendered from `author` frontmatter
@@ -118,7 +131,7 @@ sidebar and discards all the per-section ones.
The homepage, `/products/` and `/services/` render optional bands declared in The homepage, `/products/` and `/services/` render optional bands declared in
frontmatter under a `sections` key. Each band renders only when it has content, frontmatter under a `sections` key. Each band renders only when it has content,
so an unfilled page degrades to its heading and intro rather than looking so an unfilled page degrades to its heading and intro rather than looking
broken. Supported types are `advantages`, `contentGrid`, `techStack`, `cases` broken. Supported types are `capabilities`, `advantages`, `contentGrid`, `cases`
and `cta`; see `theme/components/sections/types.ts` for their shapes. and `cta`; see `theme/components/sections/types.ts` for their shapes.
`theme/components/sections/placeholder.ts` exposes a `SHOW_COPY_PLACEHOLDERS` `theme/components/sections/placeholder.ts` exposes a `SHOW_COPY_PLACEHOLDERS`
@@ -142,10 +155,24 @@ The server behind `onixbyte.cn` needs a `try_files` rule resolving extensionless
paths (`{path} {path}.html {path}/index.html`) — the sitemap and canonical URLs paths (`{path} {path}.html {path}/index.html`) — the sitemap and canonical URLs
are extensionless, and without it every one of them 404s on that host. are extensionless, and without it every one of them 404s on that host.
Redirects for the retired `/projects/` paths live in `vercel.json`. The `.cn` `vercel.json` still carries redirects for the retired `/projects/` paths, and
host needs the equivalent rule in its own web server config, which is not they now point at `/opensource-projects/`, which this site no longer serves —
tracked in this repository. they need repointing or removing. The removed `/blogs/`, `/opensource-projects/`
and `/notifications/` URLs are likewise unredirected and will 404; the new site
is reachable from the top navigation instead. The `.cn` host needs the
equivalent rules in its own web server config, which is not tracked in this
repository.
## Contributing
This repository is **not open for external contributions** — pull requests are
not accepted. Issues reporting broken links, factual errors, typos or
reproducible bugs are welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md).
## Licence ## Licence
MIT All rights reserved. Nothing here is licensed for reuse: no permission is
granted to copy, modify or distribute any part of this repository. Reading it
and linking to it are fine. See [LICENCE](./LICENCE) for the full notice, and
[THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for the dependencies, which
keep their own licences.
+1 -1
View File
@@ -5,7 +5,7 @@ This project incorporates the following third-party open source software.
## Direct Dependencies ## Direct Dependencies
| Package | License | | Package | License |
|-------------------|---------| | ----------------- | ------- |
| @rspress/core | MIT | | @rspress/core | MIT |
| react | MIT | | react | MIT |
| react-dom | MIT | | react-dom | MIT |
+2 -13
View File
@@ -2,7 +2,7 @@
{ {
"text": "nav.home", "text": "nav.home",
"link": "/", "link": "/",
"activeMatch": "^/$|^/zh-hans/$" "activeMatch": "^/(en-gb/|zh-hans/)?$"
}, },
{ {
"text": "nav.products", "text": "nav.products",
@@ -14,19 +14,8 @@
"link": "/services/", "link": "/services/",
"activeMatch": "/services/" "activeMatch": "/services/"
}, },
{
"text": "nav.insights",
"link": "/blogs/",
"activeMatch": "/blogs/"
},
{
"text": "nav.news",
"link": "/notifications/",
"activeMatch": "/notifications/"
},
{ {
"text": "nav.opensource", "text": "nav.opensource",
"link": "/opensource-projects/", "link": "https://opensource.onixbyte.com"
"activeMatch": "/opensource-projects/"
} }
] ]
-33
View File
@@ -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`
};
}
```
-43
View File
@@ -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.
-161
View File
@@ -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.
-60
View File
@@ -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.
-18
View File
@@ -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,281 +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,239 +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.
-200
View File
@@ -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.
-31
View File
@@ -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,212 +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
:::tip
Users compiling `xdb` binary dictionary files on Windows can visit OnixByte’s [GitHub](https://github.com/onixbyte/scws/releases/tag/1.2.3) or [GitLab](https://git.onixbyte.cn/onixbyte/scws/-/releases/1.2.3) pages to download the native scws command-line tool for Windows, pre-compiled using MingW.
:::
Convert the text dictionary to SCWS's efficient binary format (XDB):
1. **Compile the Binary Dictionary**:
```bash
# Use scws-gen-dict to generate an encrypted binary lexicon
/usr/local/scws/bin/scws-gen-dict -i custom_company.txt -o /usr/local/scws/etc/custom_company.xdb -c utf8
```
2. **File Distribution and Permissions**: Move the generated `.xdb` file to the tokenisation data directory and ensure the `postgres` user has read permission:
```bash
cp custom_company.xdb /usr/local/pgsql/share/tsearch_data/
chown postgres:postgres /usr/local/pgsql/share/tsearch_data/custom_company.xdb
```
### Database Parameter Configuration
Modify `postgresql.conf` to force-load `zhparser` and its custom extension lexicon:
```plain text
## Preload the extension library (requires restart to take effect)
shared_preload_libraries = 'zhparser'
## Load custom external dictionaries (use paths relative to tsearch_data)
zhparser.extra_dicts = 'custom_company.xdb'
```
### Hot Index Rebuild and Verification
Since tokenisation rules have changed, existing data must be semantically synchronised via index rebuild:
**Physically Restart the Service**:
```bash
su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart"
```
**Online Index Rebuild**: Use the `CONCURRENTLY` keyword to refresh the GIN index without blocking DML operations on 400K rows:
```bash
REINDEX INDEX CONCURRENTLY index_name;
```
**Tokenisation Effectiveness Verification**:
```sql
-- Expected part-of-speech should show as n (noun), not x (unknown)
SELECT * FROM ts_debug('chinese', '元一能源');
```
**Optimisation Notes:**
- **Explicit Weight Compensation**: This is the key technique that resolved the "元一" tokenisation failure (shown as `x`).
- **Distinguish Restart from Reload**: `shared_preload_libraries` must be activated via `restart`, not a simple reload.
-22
View File
@@ -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.
-140
View File
@@ -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.
-89
View File
@@ -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.
-85
View File
@@ -1,85 +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/).
+69 -20
View File
@@ -3,26 +3,75 @@ pageType: home
hero: hero:
name: OnixByte name: OnixByte
tagline: A small open-source collective, crafting tools for developers. tagline: Designing, building, and sustaining your core business systems for the long term.
actions: actions:
- theme: brand - theme: brand
text: Explore Projects text: Explore Custom Services
link: https://git.onixbyte.com/onixbyte link: /services/
features: - theme: alt
- title: OnixByte Toolbox text: View Products
details: A versatile Java library providing common utilities for modern development with seamless Spring integration. Published on Maven Central. link: /products/
icon: ☕ sections:
link: https://git.onixbyte.com/explore/repos?q=onixbyte-toolbox&topic=1 - type: capabilities
- title: Dev Lab id: capabilities
details: A collection of practical web tools built with Vite and React. Features a JSON Path viewer, BMI calculator, and JSON-to-table converter. title: What We Do
icon: 🛠️ subtitle: From initial business requirements to dependable deployment, we deliver end-to-end bespoke software engineering.
link: https://dev-lab.onixbyte.com items:
- title: Helix - icon: "🏢"
details: An evolving full-stack template combining Spring Boot with React, designed to help you ship applications faster. title: Bespoke Business Systems
icon: 🌀 details: Tailored precisely to your operational workflow, establishing a resilient digital foundation that drives efficiency.
link: https://git.onixbyte.com/explore/repos?q=helix&topic=1 subItems:
- title: Delta Force Guide - Custom Management Systems
details: A community-oriented guide for Delta Force, delivering game news, tips, and strategic insights for fellow operators. - Operational Automation Tools
icon: 🎯 - Legacy System Modernisation
link: https://git.onixbyte.com/explore/repos?q=delta-force-guide&topic=1 - icon: "🌐"
title: Official Portals & Marketing Sites
details: Crafting modern, high-converting web experiences that elevate your brand and clearly communicate value.
subItems:
- Corporate Web Portals
- Product Landing Pages
- Multilingual Global Sites
- icon: "📱"
title: Mobile & Cross-Platform Solutions
details: From lightweight mini-programmes to high-performance native applications, delivering fluid cross-device experiences.
subItems:
- WeChat Mini-Programmes
- Native Android & HarmonyOS Engineering
- Cross-Platform Applications
- type: advantages
id: advantages
title: Why OnixByte
subtitle: Rejecting piecemeal subcontracting and quick fixes — engineering reliable software with rigour.
items:
- icon: "🧩"
title: Single-Team Accountability
details: Handling interface design, backend architecture, databases, and infrastructure deployment in-house. A single point of ownership eliminates hand-off friction and ensures seamless delivery.
span: 6
- icon: "🔧"
title: Lifecycle Stewardship
details: Deployment is merely day one. We provide ongoing maintenance, performance optimisation, and agile iterations so your software remains an appreciating digital asset.
span: 6
- type: cases
id: work
title: Selected Work
subtitle: Rooted in real-world scenarios, demonstrating how engineering standards deliver consistent results.
items:
- company: Apparel Manufacturer in Guangdong
industry: Traditional Manufacturing / Global Export
details: Overhauled their responsive brand portal and digital product showcase with modern front-end architecture and automated static site generation, substantially reducing initial load times and boosting global availability.
tags:
- Corporate Portal
- Static Site Generation (SSG)
- Responsive Design
- type: cta
id: contact
title: Let's Discuss Your Next Initiative
subtitle: Tell us about your project or concept — we are always ready to collaborate.
actions:
- theme: brand
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to connect via WeCom
--- ---
+86
View File
@@ -0,0 +1,86 @@
---
title: Privacy Policy
description: How Changsha Onix Byte Technology Co., Ltd handles personal information on this website and in the tools we publish.
sidebar: false
outline: false
---
<!-- TODO(legal): reviewed for factual accuracy against the actual build (no
cookies, no analytics, two localStorage keys), but it has not been reviewed by
a lawyer. Have it checked before relying on it. -->
**Last updated: 25 September 2026.**
This policy covers this website and the tools we publish under it. It is issued
by Changsha Onix Byte Technology Co., Ltd ("we", "us"), the operator of
onixbyte.com and onixbyte.cn.
## What we collect
Almost nothing.
There is no account to create, no newsletter to sign up for and no form to
submit. We do not run analytics, advertising or tracking scripts of any kind,
and we do not build profiles of visitors.
We do not ask for your name, email address, phone number or any other personal
information, and there is nowhere on this site to give it to us.
## Tools that run in your browser
The tools we publish, including Dev Lab, run entirely in your browser. Data you
paste into them — configuration files, API responses, anything else — is
processed on your own machine and is never transmitted to us or to anyone else.
There is no upload step, and no code in those tools that could perform one.
This is a deliberate design decision. We use these tools on real data
ourselves, and a tool that sends your data somewhere is a tool we would not be
able to use.
## Local storage, and no cookies
We do not set cookies.
The site does store two small pieces of information in your browser's local
storage. Both stay on your device, and neither is readable by us:
| Key | What it holds |
| -------------------------- | ------------------------------------------------- |
| `rspress-theme-appearance` | Whether you chose the light or dark theme |
| `rspress-visited` | A flag so the language redirect only happens once |
Clearing your browser's site data removes both, and the site continues to work
without them.
## Server logs
Like any web server, the hosts that serve this site may record standard access
logs — IP address, browser user agent, and the page requested — for security and
troubleshooting. These are not used to identify or profile individuals, and we
do not combine them with any other information.
<!-- TODO(legal): confirm your actual log retention period on both hosts
(Vercel and the onixbyte.cn server) and state it here. -->
## Your rights
Because we do not hold personal information about you, there is generally
nothing for us to show, correct or delete. If you believe we hold something
about you, write to us and we will look into it.
If you are in a jurisdiction that grants you rights over your personal data,
those rights are not affected by anything in this policy.
## Contact
Questions about this policy, or about how we handle data:
**legal@onixbyte.com**
Changsha Onix Byte Technology Co., Ltd
Hunan Province, China
## Changes to this policy
If we start collecting something, this page will say so before we do it. The
date at the top shows when it was last changed.
+87
View File
@@ -0,0 +1,87 @@
---
title: Terms of Service
description: The terms governing use of the OnixByte website, our published tools and our open-source projects.
sidebar: false
outline: false
---
<!-- TODO(legal): written to describe how the site and tools actually behave,
but it has not been reviewed by a lawyer. Have it checked before relying on it —
particularly the liability section. -->
**Last updated: 25 September 2026.**
These terms govern your use of this website and of the free tools we publish.
They are issued by Changsha Onix Byte Technology Co., Ltd ("we", "us"), the
operator of onixbyte.com and onixbyte.cn. By using the site or the tools, you
agree to them.
## What these terms cover
They cover this website and the tools we host and offer free of charge, such as
Dev Lab.
They do **not** cover our open-source projects — see "Open-source projects"
below — and they do not govern any commercial engagement you have with us.
Work we do for a client is governed by the contract for that engagement.
## Use of the site and tools
You may use the site and the tools for any lawful purpose. You may not:
- attempt to disrupt or overload the service, or access it by automated means in
a way that degrades it for others;
- attempt to gain access to systems or data you are not authorised to access;
- use the tools to process material that is unlawful, or that you do not have
the right to process.
## Your data stays yours
Anything you paste into our browser-based tools remains yours and remains on
your machine. We claim no rights over it, and we never receive it. See the
[Privacy Policy](/legal/privacy) for the detail.
## Tools are provided as they are
The tools are offered free of charge and without warranty of any kind. They are
utilities, not professional advice:
- Results from the JSON tooling are a convenience for inspecting data. Verify
anything you intend to rely on against the source data.
- The BMI calculator produces a general-purpose figure. It is not medical
advice, and it is not a substitute for consulting a qualified professional.
## Open-source projects
Our open-source projects — including the libraries we publish on Maven Central
and GitHub — are released under the licence stated in each repository. That
licence governs your use of that code, and it takes precedence over these terms
in respect of it. If there is any conflict between these terms and an
open-source licence, the open-source licence wins.
## Intellectual property
The OnixByte name, logo and the design of this website belong to us. The
open-source code we publish is licensed to you under the terms described above.
## Limitation of liability
The site and the free tools are provided "as is" and "as available". To the
fullest extent permitted by law, we are not liable for any indirect or
consequential loss arising from your use of them, or from their being
unavailable.
Nothing in these terms excludes liability that cannot lawfully be excluded.
## Changes to these terms
We may update these terms as the site and tools change. The date at the top
shows when they were last revised. If you keep using the site after a change,
that means you accept the revised terms.
## Contact
**legal@onixbyte.com**
Changsha Onix Byte Technology Co., Ltd
Hunan Province, China
-18
View File
@@ -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.
-14
View File
@@ -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,102 +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,89 +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,216 +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.
-25
View File
@@ -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,68 +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.
+58
View File
@@ -0,0 +1,58 @@
---
title: Product Portfolio
description: >-
Explore OnixByte's suite of proprietary products and SaaS platforms, delivering highly reliable
digital services underpinned by solid engineering.
pageType: doc-wide
sidebar: false
outline: false
footer: false
head:
- - meta
- name: keywords
content: >-
OnixByte, Dev Lab, Ahsarah Guide, Delta Force community, SaaS products, developer tools,
JSONPath, data privacy
sections:
- type: contentGrid
id: products
title: In-House Products & Tools
subtitle: >-
Alongside bespoke client engineering, we continuously incubate proprietary SaaS solutions and
vertical products. Each tool originates from genuine demand and benefits from sustained
technical stewardship.
items:
- icon: "🧰"
title: Dev Lab
details: >-
A dedicated utility suite engineered for developers. Anchored by a structured data viewer
with robust JSONPath query support, all computations execute entirely within the local
browser sandbox — zero data transmission, guaranteeing absolute security for sensitive
code and operational credentials.
link: https://dev-lab.onixbyte.com
span: 6
- icon: "🧭"
title: Ahsarah Guide
badge: Coming Soon
details: >-
An unofficial player intelligence hub dedicated to Delta Force. Delivering real-time
dispatches, weapon registries, and practical tactical guides via a dependable architecture
tailored for a fluid, distraction-free reading experience.
span: 6
- type: cta
id: contact
title: Looking to Build a Dedicated Business System or SaaS Platform?
subtitle: >-
From internal productivity utilities to public-facing commercial platforms, share your vision
with us — we are always ready to collaborate.
actions:
- theme: brand
text: Explore Our Engineering Services
link: /services/
- theme: alt
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to connect via WeCom
---
+47
View File
@@ -0,0 +1,47 @@
---
title: Bespoke Services
description: >-
Custom web applications, system integration, and dedicated long-term maintenance — engineered
around your operational workflow, never forcing you to adapt to rigid software.
pageType: doc-wide
sidebar: false
outline: false
footer: false
head:
- - meta
- name: keywords
content: >-
Bespoke software engineering, custom web applications, system integration, single sign-on,
LDAP, legacy software maintenance, OnixByte
sections:
- type: cta
id: contact
title: Let's Discuss Your Next Initiative
subtitle: >-
Whether scoping a specific system requirement or exploring technical feasibility, we are
always ready to collaborate.
actions:
- theme: brand
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to connect via WeCom
---
We engineer bespoke business systems, internal productivity utilities, and robust system
integrations shaped precisely around your organisation's current workflows — continuing to support
and refine them long after go-live. Our engagements consistently evolve into long-term technology
partnerships rather than transactional, one-off deliveries.
## Who We Are Best Suited For
- **Complex business logic is the primary challenge**: You require a dedicated system tailored
around distinct, non-standard workflows, rather than generic templates that force your
organisation to compromise.
- **Requiring dependable full-stack technical capacity**: You have not yet established an in-house
engineering team, or existing technical resources are fully committed and require autonomous,
end-to-end delivery.
- **Valuing long-term stability and continuous evolution**: Deployment is merely day one. You prefer
an experienced technical team that understands your systems deeply and remains available for
sustained maintenance and agile iterations.
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

+2 -13
View File
@@ -2,7 +2,7 @@
{ {
"text": "nav.home", "text": "nav.home",
"link": "/", "link": "/",
"activeMatch": "^/$|^/zh-hans/$" "activeMatch": "^/(en-gb/|zh-hans/)?$"
}, },
{ {
"text": "nav.products", "text": "nav.products",
@@ -14,19 +14,8 @@
"link": "/services/", "link": "/services/",
"activeMatch": "/services/" "activeMatch": "/services/"
}, },
{
"text": "nav.insights",
"link": "/blogs/",
"activeMatch": "/blogs/"
},
{
"text": "nav.news",
"link": "/notifications/",
"activeMatch": "/notifications/"
},
{ {
"text": "nav.opensource", "text": "nav.opensource",
"link": "/opensource-projects/", "link": "https://opensource.onixbyte.com"
"activeMatch": "/opensource-projects/"
} }
] ]
-33
View File
@@ -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: 炒菜调料指南
tags:
- cooking
- cheatsheet
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 调料投放时机
| 调料类型 | 放调料时机 | 适用菜品 | 作用 |
|--------------|----------|----------------|---------------------|
| **盐** | 最后放 | 菜叶子 | 防止菜叶变软出水,保证口感干爽脆嫩 |
| | 中间放 | 土豆丝、豆角、蒜苔等 | 更好入味 |
| **味精、生抽、蚝油** | 偏后放 | 各类需要增鲜提香的菜品 | 增鲜提香,避免高温破坏鲜味和香味 |
| **料酒** | 和食材一起放 | 需要去腥的食材 | 焯水时去腥效果更好 |
| | 锅温最高的时候放 | 需要炒菜去腥的食材 | 炒菜时高温促进酒气挥发,炝锅去腥效果好 |
| **醋** | 提前放 | 酸辣土豆丝、酸辣大白菜 | 使土豆丝变脆更入味 |
| | 出锅前锅边醋 | 辣椒炒肉等需要猛火爆炒类菜品 | 大火炝锅增香 |
| **老抽** | 炒半熟后放 | 需要上色的菜品 | 达到最佳上色效果 |
## 快速参考
| 调料 | 投放时机 | 作用 | 示例 |
|-----------------|----------------|------------------------|------------------------|
| **盐** | 出锅前 10 - 15 秒放 | 避免提前放盐导致菜叶出水、变软,保持脆嫩口感 | 炒生菜、菠菜时,关火前撒盐翻炒均匀 |
| **蒜末 / 姜末** | 热油后先放 | 爆香提味,激发香味 | 油热后下蒜末炝锅,再放青菜翻炒 |
| **生抽 / 蚝油**(可选) | 盐之后放 | 增鲜提味,少量即可,避免盖过蔬菜原味 | 炒上海青时,加盐后滴 1 - 2 滴生抽翻炒 |
| **食用油** | 热锅热油 | 油热后快速炒,锁住水分 | 大火快炒时,油稍多一点,油温要高 |
@@ -1,16 +0,0 @@
---
title: Docker 部署规范
tags:
- docker
- deployment
- standards
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
- **Dockerfiles**: 为应用程序提供 `Dockerfile`,实现容器化部署。
- **轻量级镜像**: 通过使用适当的基础镜像和多阶段构建,力求实现轻量级 Docker 镜像。
- **配置**: 确保环境特定配置(例如,数据库连接字符串、外部服务 URL)通过注入到 Docker 容器中的环境变量进行管理。
- **日志记录**: 配置容器化日志记录,将输出发送到 `stdout` 和 `stderr`,以便日志聚合系统轻松收集。
@@ -1,55 +0,0 @@
---
title: ECMAScript 2025 语法糖大全
tags:
- javascript
- ecmascript
- pattern-matching
- frontend
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 模式匹配
### 传统写法
```javascript
function processResponse(response) {
if (response.status === 200 && response.data) {
return { success: true, data: response.data };
} else if (response.status === 404) {
return { success: false, error: 'Not found' };
} else if (response.status >= 500) {
return { success: false, error: 'Server error' };
} else {
return { success: false, error: 'Unknown error' };
}
}
```
### 模式匹配写法
```javascript
function processResponse(response) {
return match (response) {
when ({ status: 200, data }) -> ({ success: true, data })
when ({ status: 404 }) -> ({ success: false, error: 'Not found' })
when ({ status: status if status >= 500 }) -> ({ success: false, error: 'Server error' })
default -> ({ success: false, error: 'Unknown error' })
};
}
```
### 数组长度分支也能优雅处理
```javascript
function handleArray(arr) {
return match (arr) {
when ([]) -> "Empty array"
when ([first]) -> `Only one element: ${first}`
when ([first, second]) -> `Two elements: ${first}, ${second}`
when ([first, ...rest]) -> `First element: ${first}, others: ${rest.length} items`
};
}
```
-40
View File
@@ -1,40 +0,0 @@
---
title: 像老板一样写邮件
tags:
- communication
- email
- soft-skill
- career
- productivity
author:
name: Zihlu Wang
email: real@zihluwang.me
---
工作中的邮件沟通不仅仅是传递信息,更是建立信任、塑造专业形象的过程。同样一件事情,换一种表达方式,给人的印象可能是天壤之别。
以下整理了 9 个常见邮件场景中「低姿态表达」与「高阶表达」的对比,让你的邮件读起来更自信、更专业。
## 场景速查表
| 场景 | ❌ 别用(听起来……) | ✅ 改用(听起来……) | 为什么有效 |
|----------|--------------------------------------------------------------|----------------------------------------------------------|-----------------------------------------------|
| 回复晚了 | **Sorry for the delay**<br/>(抱歉回复晚了) | **Thanks for your patience**<br/>(感谢你的耐心等待) | 前者在道歉,后者在感谢——从「我做错了」转变为「你太好了」,既表达了歉意又让对方感到被尊重 |
| 约时间 | **What works best for you?**<br/>(你什么时候方便?) | **Could you do …?**<br/>(你可以在某某时间吗?) | 前者把决定权完全推给对方,后者主动给出选项,减少来回沟通的成本 |
| 帮助了别人之后 | **No problem / No worries**<br/>(没问题 / 别担心) | **Always happy to help**<br/>(乐于效劳) | 前者暗示这件事"本来可能是个问题",后者传递出你乐在其中、下次还愿意帮 |
| 提出建议 | **I think maybe we should …**<br/>(我觉得也许我们应该……) | **It'd be best if we …**<br/>(最好是……) | 前者透着犹豫和不自信,后者直接给出判断——像有经验的人在做决策 |
| 文字沟通费劲 | **_花 30 分钟反复修改一封邮件_** | **It'd be easier to discuss in person**<br/>(当面/电话聊会更轻松) | 意识到沟通渠道本身就是问题,果断切换方式反而是效率最高的选择 |
| 确认对方是否理解 | **Hopefully that makes sense?**<br/>(希望我说清楚了?) | **Let me know if you have questions**<br/>(有问题随时问我) | 前者在怀疑自己的表达能力,后者把责任合理地分担给对方,姿态更稳 |
| 催进度 | **Just wanted to check in**<br/>(只是想确认一下) | **When can I expect an update?**<br/>(我什么时候可以收到更新?) | 前者显得小心翼翼不敢打扰,后者直接问时间节点——清楚、礼貌、专业 |
| 犯了小错 | **Ahh sorry my bad totally missed that**<br/>(啊啊抱歉我的错,完全漏掉了) | **Thanks for letting me know**<br/>(感谢提醒) | 前者过度道歉反而让对方尴尬,后者承认了问题但把焦点放在了"解决"上 |
| 需要早退 | **Could I possibly leave early?**<br/>(我能提前走吗?) | **I will need to leave at …**<br/>(我需要在某某时间离开) | 前者在请求批准,后者在陈述安排——你是专业人士,不需要为合理需求道歉 |
## 核心原则
写好邮件的关键不在于词汇量,而在于**姿态**。时刻提醒自己三条规则:
1. **用陈述代替请求** —— 「我需要……」比「我能不能……」更有分量
2. **用感谢代替道歉** —— 把焦点从「我的过失」转移到「对方的支持」
3. **用明确代替模糊** —— 给出具体的时间、选项、行动项,而不是把球踢回给对方
下次打开邮箱,花 5 秒想一想:这句话能不能说得更像一个做决定的人?
@@ -1,58 +0,0 @@
---
title: 修复 IntelliJ IDEA 中 Spring Boot 的 application.yaml 智能提示消失问题
tags:
- intellij-idea
- spring-boot
- yaml
- schema-validation
- troubleshooting
author:
name: Zihlu Wang
email: real@zihluwang.me
---
import { Kbd } from "@rspress/core/theme"
最近在使用 IDEA 开发 Spring Boot 项目时,我发现 `application.yaml` 和 `application.yml` 的智能代码提示突然消失了。更糟糕的是,编辑器中充满了如下警告:
> `Schema validation: Missing required property 'kind' = 'Application'`
## 症状
打开 `src/main/resources/` 下的 `application.yaml` 或 `application.yml` 文件时,IDEA 会将其视为 Enonic XP 应用描述文件,而非 Spring Boot 配置文件。所有 Spring 特有的属性提示全部消失,取而代之的是一堆要求填写 `kind` 属性的 Schema 校验错误——这个字段与 Spring Boot 毫无关系。
重启 IDEA、清除缓存、重新导入项目都无济于事,问题依旧存在。
## 快速修复
经过一番排查,我找到了罪魁祸首。IDEA 会从远程源(具体来说是 [SchemaStore](https://www.schemastore.org/) 目录)下载 JSON Schema,并用它们来校验配置文件。最近的一次 Schema 更新引入了一个匹配范围过大的 `fileMatch` 模式。
要立即禁用此行为:
1. 打开 **Settings**(Windows:<Kbd>Ctrl</Kbd>+<Kbd>Alt</Kbd>+<Kbd>S</Kbd> / macOS:<Kbd mac="cmd" /> + <Kbd>,</Kbd>)
2. 导航至 **Languages & Frameworks → Schemas and DTDs → Remote JSON Schemas**
3. 取消勾选 **Allow downloading JSON Schemas from remote sources**
4. 点击 **OK**
Spring Boot 的配置提示会立刻恢复。
## 根本原因
事情的根源要追溯到 **2026 年 5 月 19 日**,一位来自 [Enonic](https://www.enonic.com/) 的开发者向 SchemaStore 仓库提交了一个 PR([PR #5704](https://github.com/SchemaStore/schemastore/pull/5704)),添加了 Enonic XP 应用描述文件的 Schema。该 Schema 的 `fileMatch` 属性被设置为:
```json
"fileMatch": [
"**/src/main/resources/application.yaml",
"**/src/main/resources/application.yml"
]
```
这个匹配模式过于宽泛——它匹配了 **所有** 位于 `src/main/resources/` 下的 `application.yaml` 和 `application.yml` 文件,而这正是 Spring Boot 项目存放配置文件的位置。一旦该 Schema 被合并到 SchemaStore 中,任何启用了远程 JSON Schema 下载功能的 IntelliJ IDEA 都会拉取它,并开始用 Enonic XP 的 Schema 来校验 Spring Boot 的配置。
该开发者已意识到问题,并提交了后续修复 PR([PR #5711](https://github.com/SchemaStore/schemastore/pull/5711))来收紧 `fileMatch` 匹配规则。截至本文撰写时,该修复尚未被 SchemaStore 合并。
## 为什么这很重要
SchemaStore 是一个由社区维护的 JSON Schema 目录,被许多编辑器(VS Code、IntelliJ IDEA、Neovim 等)用来为常见配置文件提供校验和自动补全功能。当该目录中的某个 Schema 声明了过于宽泛的匹配规则时,会影响到所有平台、所有编辑器的用户——不仅仅是 IDEA。
对于 Spring Boot 开发者来说,结论很简单:如果你的 `application.yaml` 提示一夜之间消失了,先检查一下你的 IDE 认为该文件属于哪个 Schema,它可能根本不是 Spring Boot。
@@ -1,26 +0,0 @@
---
title: 解决 macOS Monterey+ 设备休眠频繁自动唤醒的问题
tags:
- macos
- sleep
- power-management
- bug
---
> 本文由 **落格博客** 原创撰写:[落格博客](https://www.logcg.com/) » [升级 macOS Monterey 后设备休眠半夜频繁唤醒问题](https://www.logcg.com/archives/3528.html)
更新到了 macOS Monterey,半夜总会被屏幕照醒,就觉得很诡异,以前也有过,但都是有通知的时候才会点亮屏幕,现在是没有任何理由的自己点亮,硬件还是那个硬件,那就应该是软件的锅了。
在网上查了一圈,先是找到了[苹果官方的教程](https://support.apple.com/zh-cn/guide/mac-help/mchlp2995/mac)。写的很详细,但显然是没有任何用处的。
于是继续深挖,还真找到了原因,使用命令 **`pmset -g log | grep DarkWake`** 就可以看到,在你睡觉的过程中,你的 Mac 并没有歇着……
看看输出,这几个比较典型,大部分都是这样的,一个 DarkWake, 一个 Wake, 两个仅仅相连,那么问题就出来了, DarkWake 是给电脑在后台唤醒来更新数据的,但不知怎么的,外设被触发,从而导致了全局唤醒。
总之,我并不想要这个功能, 就那个 PowerNap,对我来说,我更希望它能给我多省点电,于是大概解决思路就这么多:关掉网络访问唤醒,关掉 PowerNap……不过问题来了,在 m1 设备上,其实是没有 PowerNap 选项的……(显然,苹果对自己的续航很有信心,但他们忽略了Bug的威力)
于是对于 PowerNap 这个功能来说,我们只能从命令行下手,首先使用 **`pmset -g`** 命令查看当前状态,找到 **`powernap`** 的值,如果不是 0 ,说明是启用的状态,使用命令 **`sudo pmset -a powernap 0`** 关掉它。
同时,还有另外一个 **`tcpkeepalive`** ,这个默认应该也不是 0 ,也要关掉,它决定了你的 rmbp 在休眠时是否要保持 tcp 连接。 **`sudo pmset -a tcpkeepalive 0`** ,执行这条命令会导致终端提示:***Warning: This option disables TCP Keep Alive mechanism when system is sleeping. This will result in some critical features like 'Find My Mac' not to function properly.***
大概是说关了的话某些功能会受到限制,其实就是系统功能不能休眠时联网了,我相信真有人偷了你 Mac,它也连不上网的。
@@ -1,30 +0,0 @@
---
title: 字号榜值转换表
tags:
- typography
- font
- cheatsheet
- design
author:
name: Zihlu Wang
email: real@zihluwang.me
---
| 中文字号 | 英文字号(磅)/pt | 毫米/mm | 像素/px |
|------|------------|-------|-------|
| 初号 | 42 | 14.82 | 56 |
| 小初 | 36 | 12.7 | 48 |
| 一号 | 26 | 9.17 | 34.7 |
| 小一 | 24 | 8.47 | 32 |
| 二号 | 22 | 7.76 | 29.3 |
| 小二 | 18 | 6.35 | 24 |
| 三号 | 16 | 5.64 | 21.3 |
| 小三 | 15 | 5.29 | 20 |
| 四号 | 14 | 4.94 | 18.7 |
| 小四 | 12 | 4.23 | 16 |
| 五号 | 10.5 | 3.7 | 14 |
| 小五 | 9 | 3.18 | 12 |
| 六号 | 7.5 | 2.56 | 10 |
| 小六 | 6.5 | 2.29 | 8.7 |
| 七号 | 5.5 | 1.94 | 7.3 |
| 八号 | 5 | 1.76 | 6.7 |
@@ -1,42 +0,0 @@
---
title: 前端开发规范
tags:
- frontend
- react
- standards
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 依赖管理 (pnpm)
- **严格性**: 利用 pnpm 的严格依赖管理,以确保更确定性的 `node_modules` 结构和高效的磁盘空间使用。
- **工作区**: 如果使用 monorepo 方法,配置 pnpm 工作区以简化多个前端包的依赖管理。
- **审计**: 定期使用 `pnpm audit` 审计前端依赖项是否存在已知漏洞。
## API 通信 (Axios)
- **Axios 实例**: 创建一个集中的 Axios 实例用于 API 调用,以应用通用配置(基本 URL、请求头、拦截器)。
- **拦截器**: 使用 Axios 拦截器进行以下操作:
- 向传出请求添加认证令牌。
- 处理全局错误响应(例如,显示 `401 Unauthorized` 的通知)。
- 在开发环境中记录请求/响应。
- **错误处理**: 在 Axios 拦截器或自定义工具函数中集中处理 API 错误,以提供一致的用户反馈。
## React 与组件标准
- **函数组件与 Hooks**: 对于新开发,优先使用带有 React Hooks 的函数组件,而非类组件。
- **Props**:
- 为组件 props 定义 `interface` 或 `type` 以确保类型安全。
- 在组件入口处解构 props 以提高清晰度。
- **状态管理 (Redux)**:
- 使用 Redux Toolkit 进行高效且减少样板代码的 Redux 开发。
- 使用 `createSlice` 将 Redux 逻辑组织成"切片"(特定功能的 reducer、action 和 selector)。
- 遵循"ducks"模式或"slices"方法来协同定位 Redux 逻辑。
- **组件组合**: 将复杂的 UI 分解为更小、可重用且职责单一的组件。
- **Ant Design**:
- 利用 Ant Design 组件以实现一致的 UI/UX。
- 如果需要,使用 CSS-in-JS 解决方案在整个应用程序中一致地定制 Ant Design 主题和样式。
- **可访问性**: 从一开始就设计和实现考虑到 Web 可访问性 (a11y) 的组件。
@@ -1,75 +0,0 @@
---
title: 前端开发技巧与方案速查手册
tags:
- frontend
- react
- ant-design
- tailwind
- best-practice
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 表单组件的解耦
将表单组件拆分为 UI 组件和逻辑组件,并在逻辑组件中使用 UI 组件渲染样式。
## React 入口组件顺序
```tsx
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}>
{/* 注意:StyleProvider 必须是 ConfigProvider 的父组件!!! */}
<StyleProvider layer>
<AntConfigProvider
locale={AntSimplifiedChinese}
button={{
autoInsertSpace: false,
}}>
<AntApp className="h-full w-full">
<RouterProvider router={router} />
</AntApp>
</AntConfigProvider>
</StyleProvider>
</PersistGate>
</ReduxProvider>
</StrictMode>
)
```
## React 中 Ant Design 与 Tailwind 整合
> 参考 [https://github.com/ant-design/ant-design/discussions/56152](https://github.com/ant-design/ant-design/discussions/56152)
```css
/* index.css */
@layer theme, base, antd, components, utilities;
@import "tailwindcss";
```
```tsx
<StyleProvider>
<ConfigProvider>
<App>
<RouterProvider/>
</App>
</ConfigProvider>
</StyleProvider>
```
@@ -1,23 +0,0 @@
---
title: 应用开发通用规范
tags:
- standards
- best-practice
- engineering
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 引言
本文件旨在概述应用程序开发的编码标准和最佳实践。遵守这些指导方针可确保所有项目的代码质量、可维护性和一致性。
## 通用原则
- **清晰性与可读性**: 代码必须易于阅读和理解。优先选择清晰、自文档化的代码,而非巧妙、简洁但晦涩的解决方案。
- **一致性**: 在整个项目中保持一致的编码风格、命名约定和架构模式。
- **模块化**: 设计组件时应实现松耦合和高内聚,以促进重用性和更简单的测试。
- **可测试性**: 编写本质上可测试的代码。
- **安全先行设计**: 在开发的每个阶段都将安全考虑融入其中。
- **性能意识**: 对关键代码段和 API 端点考虑其性能影响。
-161
View File
@@ -1,161 +0,0 @@
---
title: Git 使用小贴士
tags:
- git
- tips
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 忽略已跟踪文件的本地改动:`git update-index --skip-worktree`
当你需要在本机修改某个已被 Git 跟踪的文件,但又不希望这些改动出现在 `git status` 或提交里时(比如本地的 `application-local.yml`、`.env`、IDE 配置等),可以使用 `--skip-worktree` 让 Git 忽略该文件的本地改动。
### 标记与取消标记
```bash
# 让 Git 忽略该文件的本地改动
git update-index --skip-worktree <file>
# 取消忽略,恢复对该文件的跟踪
git update-index --no-skip-worktree <file>
```
### 查看所有被忽略的文件
被标记的文件会在 `git ls-files -v` 的输出中以 `S` 标识:
```bash
git ls-files -v | grep '^S'
```
### 提交对已标记文件的改动
如果需要向仓库提交该文件的新改动,需要先取消标记、提交、再重新标记:
```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` 与 `--assume-unchanged` 的区别
两个选项都能让 Git 不再关注已跟踪文件的改动,但用途不同:
| 选项 | 用途 |
| -------------------- | ---------------------------------------------------------------------------- |
| `--assume-unchanged` | 性能优化,适用于几乎不会改变的大型文件;Git 检测到文件变化后可能自动重新跟踪 |
| `--skip-worktree` | 有意忽略本地改动,适用于「保持本地修改但不上传」的场景,更持久 |
因此,「忽略本地改动」应该使用 `--skip-worktree`。
### 注意事项
- `--skip-worktree` 只影响工作区层面,并不是一个「永久忽略」。在执行 `git pull`、`git checkout`、`git stash` 等可能改写该文件的操作时,本地改动可能会被覆盖或引发冲突。如果该文件在远端也会变化,建议在拉取前先取消标记。
- 它只对**已跟踪**的文件生效;对于未跟踪的文件,请使用 `.gitignore`。
## 暂存未完成的改动:`git stash`
需要临时切换分支时,可以用 `git stash` 把工作区改动保存起来:
```bash
git stash push -m "work in progress" # 保存并附带备注
git stash -u # 连同未跟踪的文件一起保存
git stash list # 查看已保存的改动
git stash pop # 恢复并删除该条记录
git stash apply # 恢复但保留该条记录
git stash drop # 删除某条记录
```
## 修正最近一次提交:`git commit --amend`
当你提交后发现忘了加入某些文件、或想修改提交信息时:
```bash
git add <forgotten-file>
git commit --amend # 保持原提交信息,合并进新改动
git commit --amend -m "新提交信息" # 同时修改提交信息
```
注意:如果该提交已经被推送到远端,请改用新增一个提交的方式,避免改写公共历史。
## 撤销改动:`git restore` 与 `git reset`
```bash
git restore <file> # 丢弃工作区(未暂存)的改动
git restore --staged <file> # 取消暂存,保留改动(相当于 git reset HEAD <file>)
git restore --source=HEAD <file> # 将文件恢复到 HEAD 版本
```
想彻底丢弃未提交的全部改动(包括暂存区),可以:
```bash
git reset --hard HEAD
```
`--hard` 会连同工作区一并重置,使用前请确认没有需要保留的改动。
## 找回丢失的提交:`git reflog`
误用了 `git reset --hard` 或 `git rebase` 导致提交「消失」时,`reflog` 记录了 HEAD 的所有历史位置:
```bash
git reflog
git reset --hard HEAD@{2} # 回到 reflog 中记录的某个位置
```
只要提交还在对象库中,就能通过 reflog 找回。
## 二分查找回归:`git bisect`
当某个 bug 不知道从哪个提交引入时,`git bisect` 会通过二分法帮你定位:
```bash
git bisect start
git bisect bad # 当前提交是有问题的
git bisect good <sha> # 标记一个已知正常的提交
```
Git 会不断切换到一个中间提交,你只需要根据当前状态标记 `good` 或 `bad`,最终会定位到引入 bug 的那个提交:
```bash
git bisect good # 或 git bisect bad
git bisect reset # 结束并回到原分支
```
## 更友好的历史记录:`git log`
```bash
git log --oneline --graph --decorate --all # 以图形方式查看全部分支
git log -S "some-string" --all # 查找新增或删除某字符串的提交
git blame <file> # 查看每行代码的最后修改者
git blame -L 10,20 <file> # 只查看指定行范围
```
## 清理未跟踪文件:`git clean`
```bash
git clean -n -fd # 先预览会被删除的文件(安全起见务必先跑)
git clean -fd # 删除未跟踪的文件和目录
```
`-n` 表示只展示而不实际删除,建议先预览再执行。
## 精挑细选:`git cherry-pick`
把其他分支上的某个提交应用到当前分支:
```bash
git cherry-pick <sha>
```
## 清理已合并的本地分支
```bash
git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
```
该命令会删除所有已合并进当前分支的本地分支(当前分支除外)。执行前建议先用 `git branch --merged` 预览。
-56
View File
@@ -1,56 +0,0 @@
---
title: GitLab 运维
tags:
- gitlab
- devops
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## 在国际版 GitLab 中设置默认语言为中文
GitLab 在代码中通过 `SWITCHER_MINIMUM_TRANSLATION_LEVEL` 变量将翻译程度低于 90% 的语言过滤了,简体中文为 84%,繁体中文为 83%
可以前往 `/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb` 中将代码修改如下:
```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
```
随后通过指令 `gitlab-ctl restart` 重启 GitLab 即可。
## 在首页添加备案信息
前往 `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shared/_footer.html.haml` 中,添加如下代码:
```diff
+ = link_to _("你的备案号"), "https://beian.miit.gov.cn/", target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
```
## 升级 GitLab
### 下载 GitLab Global CE 版安装包
从 [GitLab Package 下载页](https://packages.gitlab.com/gitlab/gitlab-ce) 下载 deb 工具包,并使用 scp 等工具将其上传到服务器中。
### 停用内存大户
```shell
gitlab-ctl stop puma
gitlab-ctl stop sidekiq
```
### 执行备份
```shell
# 示例:跳过构建附件和容器镜像仓库,仅备份数据库和代码仓库
sudo gitlab-backup create SKIP=artifacts,registry
```
### 进行安装
```shell
sudo dpkg -i gitlab-package.deb
```
@@ -1,98 +0,0 @@
---
title: Google 代码审查标准
tags:
- code-review
- best-practice
- engineering
- google
author:
name: Zihlu Wang
email: real@zihluwang.me
---
代码审查是开发过程中的一个环节,顾名思义,代码审查需要一位或多位开发人员审查另一位开发人员(即代码的作者)编写的代码,以确保:
- 代码没有任何错误,没有 bug,也没有问题;
- 代码符合质量与样式指南的要求和标准;
- 代码完成了所有预期功能;
- 合并代码后,代码库仍然能够正常运行,且达到更好的状态。
这就是为什么代码审查是软件开发的重要环节的原因。代码审查者担当着把关者的职责,负责决定这些代码是否能够成为代码库的一部分并进入生产环境。
## 这些代码能够提升系统整体的运行状况
每次代码变更(拉取请求)都能够提升系统整体的运行状况。重点在于,即便是很小的改进,合并代码后都会提升软件或代码库的运行状况。
## 快速审查代码,并给出积极地响应和反馈
首先也是最重要的一点,不可延误代码的合并。世上没有完美的代码。如果代码可以提升系统的整体运行状况,则应该立即交付这些代码。
> 关键在于,世上没有完美的代码,只有更好的代码。—— Google 工程实践文档
如果手头没有紧急任务,那么请在代码提交上来后立即进行审查。响应拉取请求的时间最长不得超过一个工作日。一天之内,应针对一次拉取请求完成多轮的部分或完整的代码审查。
## 在代码审查的过程中开展教育和启发
在代码审查的过程中,应尽可能通过共享知识和经验提供指导。
## 审查代码应遵循标准
请始终牢记,样式指南、编程标准以及相关的文档应该作为代码审查的绝对权威。例如,制表符与空格的使用应保持一致,此时你可以引用编程约定。
> 如果你选用的是 Java,那么以下文章可能会有所帮助,文中总结了大型科技公司 Java 编程的最佳实践:《[Java 编程最佳实践摘要](https://rhamedy.medium.com/a-short-summary-of-java-coding-best-practices-31283d0167d3)》
## 解决代码审查冲突
解决代码审查冲突时,应遵循样式指南以及编程标准中商定的最佳实践,并征求其他拥有更多产品领域知识和经验的人的建议。
如果你的意见是可选或不怎么重要的,请在注释中说明,然后由作者来决定是解决还是略过。
作为代码审查者,在没有样式指南或编程标准的情况下,你至少可以建议此次代码变更与其余代码库保持一致。
## 演示 UI 变更是代码审查的一部分
如果代码变更涉及用户界面变化,则除了代码审查外,还需要提供演示,确保界面符合预期且与界面设计一致。
对于前端代码变更,你需要进行演示,或确保代码变更包括必要的UI自动化测试,以验证添加或更新的功能。
## 确保代码审查中包含了所有测试
除非遇到紧急情况,否则拉取请求应包含所有必要的测试,例如单元测试、集成测试以及端到端测试等。
这里所说的紧急情况指的是,某个需要尽快修复的 bug 或安全漏洞,而测试可以等到以后再添加。在这种情况下,请确保创建了适当的票证/问题,并确保有人负责在完成热修复或部署后立即完成测试。
我们绝对不可以跳过测试。如果时间有限,某些目标有无法实现的风险,那么解决方案不是跳过测试,而是限定可交付成果的范围。
## 不要为了代码审查打断手头的工作
如果你正在专心致志地工作,那么请不要打断自己,因为你需要花费很长时间才能重新投入工作。换句话说,打断专心工作的开发人员所付出的代价远远超过了让开发人员等待代码审查。你可以在休息(午餐或咖啡等)过后,进行代码审查。
大多数时候,整个代码审查以及代码的合并无法在一天内完成。重要的是迅速给作者一些反馈。例如,虽然可能无法完成完整的审查,但你可以快速指出一些有待探讨的地方。这可以极大地降低代码审查期间的挫败感。
## 审查所有代码,不要做任何假设
你需要审查提交上来的每一行代码。不要对人工编写的类和方法做任何假设,而且应该确保你理解代码在做什么。
确保你理解正在审核的代码。如果不理解,则请作者澄清或提供代码演示和解释。如果你不具备审核部分代码的资格,则请其他有资格的开发人员代为审查。
## 审查代码时需要保持大局观
从更广阔的视野来看待代码变更会更有帮助。例如,某个文件被修改,并添加了4行新代码。请不要只看这4行代码,你应该考虑审查整个文件,并检查新添加的内容。它们是否会降低现有代码的质量?它们是否会导致现有功能成为重构的候选对象?
如果不在函数/方法或类的背景下审查添加的代码,则随着时间的流逝,你将会得到一个面临无法维护、纠缠不清、不易于测试等问题的类,而且这个类很难扩展或重构。
请记住,即便是微不足道的改进,随着时间的推移,也可能导致产品出现缺陷,同样,即便是轻微的代码降级或技术负债也可能在日积月累下导致产品难以维护和扩展。
## 在代码审查期间认同和鼓励出色的工作
如果看到出色的代码变更,请别忘了大力表扬和鼓励作者。代码审查的目的不仅仅是发现错误,而且还应该鼓励和指导开发人员出色的工作。
## 在代码审查期间应保持谨慎、尊重、友善和思路清晰
在代码审查期间,你应该保持友善、思路清晰、有礼貌和尊重别人,这一点至关重要,同时也要给予作者清晰的反馈和积极的帮助。在审查代码时,你需要做到对事不对人,即对代码做出评论,而非开发人员。
## 详细解释代码审查的意见,并注意尺度
每当代码审查意见提出替代方案或指出某些问题时,重要的是你需要解释其中的原因,并根据个人的知识和经验提供示例,以帮助开发人员理解为何你的建议能够提升代码质量。
在建议修改或变更代码时,你需要在如何指导作者修改代码方面找到适当的平衡。例如,我更喜欢指导、解释、提示或建议,而不是整个解决方案。
-17
View File
@@ -1,17 +0,0 @@
---
title: 技术洞察
---
# 技术洞察
这里汇集了日常工程实践中沉淀的实用指南、速查手册和开发规范。
## 话题分类
- **前端** — React 模式、TailwindCSS 技巧、ECMAScript 语法指南、浏览器疑难杂症
- **后端与数据库** — Java 开发、MyBatis、PostgreSQL、MySQL JSON
- **DevOps 与基础设施** — Docker、GitLab CI/CD、LDAP、MinIO、macOS 故障排查
- **工程规范** — 代码评审、版本控制、通用与语言专项编码标准
- **工具与效率** — 邮件礼仪、字号换算、数据分析方法
侧边栏浏览或直接翻阅下方文章——每篇内容独立成文,即读即用。
@@ -1,276 +0,0 @@
---
title: Java 开发小技巧
tags:
- java
- tips
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## BigDecimal 对比
在 Java 中对 `BigDecimal` 进行比较需要格外注意,因为 `equals` 和 `compareTo` 的行为并不相同。
### `equals` 与 `compareTo` 的区别
`BigDecimal#equals` 会同时比较**数值**和**标度(scale)**,而 `BigDecimal#compareTo` 仅比较**数值**(忽略标度)。例如:
```java
var a = new BigDecimal("100"); // scale = 0
var b = new BigDecimal("100.00"); // scale = 2
a.equals(b); // false — 标度不同
a.compareTo(b); // 0 — 数学数值相同
var c = new BigDecimal("200");
a.compareTo(c); // -1(负数)— a 小于 c
c.compareTo(a); // 1(正数)— c 大于 a
```
### 为什么这很重要
当值来自不同来源时,标度不匹配的情况很常见——例如解析用户输入、从数据库(`DECIMAL(10,2)` 列)读取、或接收 JSON 数据。你可能会认为两个值相等,但 `equals` 却说它们不相等。
### 使用建议
- 使用 **`compareTo`** 进行数值相等判断:`a.compareTo(b) == 0`
- 仅在需要「完全相同的表示」(数值和标度都相同)时使用 **`equals`**
- 如果需要在 `equals` 前统一标度,可使用 **`stripTrailingZeros()`**:
```java
a.stripTrailingZeros().equals(b.stripTrailingZeros()); // true
```
### 与零比较
避免使用 `==` 或 `.equals(BigDecimal.ZERO)` 来判断是否为零——推荐使用 `compareTo`:
```java
if (value.compareTo(BigDecimal.ZERO) == 0) { ... }
```
## 如何从 BlockingQueue 中获得数据?
- 使用 `take()` 方法可以拿取数据,如果队列中没有数据会阻塞线程,一直等待;
- 使用 `poll()` 方法拿取数据,如果没有数据会返回 `null` ;
- 使用 `poll(long timeout, TimeUnit unit)` 方法拿去数据,将会在 `poll()` 的基础上等待特定的时间,如果时间到了还没有拿到数据则会返回 `null` ;
- 使用 `peek()` 拿取数据,但是这种方式不会将已经拿到的数据从队列中移除。
## Spring Cloud Alibaba 常见问题
### Nacos 在个人文件夹中创建 `nacos` 文件夹怎么办?
添加下面两个 Configuration properties 即可指定 nacos 的存储路径:
- `JM.LOG.PATH`
- `JM.SNAPSHOT.PATH`
### Sentinel 乱拉屎怎么办?
添加 Configuration property `csp.sentinel.log.dir` 即可修改 sentinel 的日志路径。
### 怎么在 JetBrains IntelliJ IDEA 中添加 Configuration Properties 配置?
在 JetBrains IntelliJ IDEA 中,在右上角的运行配置中,点击 **`Edit Configurations…`** ,即可看到配置页面。
点击页面中的 **`Modify options`** 按钮,并在下方附加上的 **`Override configuration properties`** 表格中添加需要重置的配置属性。
## Spring Data JPA 常见问题
### 使用 JPA 分页查询时提示 "Serializing `PageImpl` instances as-is not supported, meaning that there is no guarantee about the stability of the resulting JSON structure" 怎么办?
在应用程序主启动类上添加下面的代码:
```java
@EnableSpringDataWebSupport(pageSerializationMode = EnableSpringDataWebSupport.PageSerializationMode.VIA_DTO)
```
### 使用 JPA 分页查询时查询第一页都没数据是为什么?
JPA 的分页页码是从 0 开始的,请求中的页码数据 = 你实际想查询页码数 - 1。
### 如何避免 N+1 查询问题?
N+1 问题是指 JPA 先执行 1 条查询获取父实体,再为每条父实体的关联执行 N 条额外的子查询。
**检测方法** — 观察日志中是否有大量重复的 SQL 查询,或者配置 `spring.jpa.properties.hibernate.generate_statistics=true` 来发现。
**修复方案:**
| 方式 | 适用场景 |
|---------------------------|----------------------|
| `@EntityGraph` | 声明式,适合为特定实体定制抓取计划 |
| `@Query` 中使用 `JOIN FETCH` | 按查询精细控制 |
| `@BatchSize` | 将 N+1 降低为 N/k+1,批量加载 |
```java
// 方式一:EntityGraph
@EntityGraph(attributePaths = {"roles", "permissions"})
Optional<User> findById(long id);
// 方式二:JOIN FETCH
@Query("SELECT u FROM User u JOIN FETCH u.roles WHERE u.id = :id")
Optional<User> findByIdWithRoles(@Param("id") long id);
```
### `findById` 与 `getReferenceById` 该用哪个?
- **`findById`** — 立即查询数据库,返回实体或 `Optional.empty()`。需要实际数据时使用。
- **`getReferenceById`** — 返回一个惰性代理(proxy),**不查询数据库**。只有当访问不存在的代理属性时才会抛出 `EntityNotFoundException`。只需要 ID 来设置外键关系时使用。
```java
// 推荐:只需要引用 User 来设置外键
Post post = new Post();
post.setAuthor(userRepository.getReferenceById(userId));
```
### 如何解决 `LazyInitializationException`?
当你在持久化上下文之外(例如 Controller 或序列化器中,事务已关闭后)访问懒加载的关联属性时,就会抛出此异常。
**解决方案:**
1. **使用 `JOIN FETCH` 或 `@EntityGraph`** 提前加载所需的关联数据。
2. **使用 DTO 投影** — 只返回需要的字段,而不是整个实体:
```java
@Query("SELECT new com.example.UserDto(u.id, u.name) FROM User u WHERE u.id = :id")
UserDto findUserDtoById(@Param("id") long id);
```
3. **在 Service 方法上使用 `@Transactional(readOnly = true)`** — 使 Session 在整个方法作用域内保持打开。
### 什么时候该用 `@Transactional(readOnly = true)`?
在**只读**的 Service 方法上使用 `@Transactional(readOnly = true)` 有三个好处:
- Hibernate 会跳过脏检查(无需快照,占用内存更少)。
- JDBC 驱动可能会将请求路由到只读副本。
- 清晰地表达了方法的意图。
```java
@Service
public class UserService {
@Transactional(readOnly = true)
public UserDto getUser(long id) { ... }
@Transactional
public UserDto createUser(CreateUserRequest request) { ... }
}
```
### `save()` 与 `saveAll()` — 批量插入哪个更快?
`saveAll()` 在单个事务中执行,可以受益于 JDBC 批处理。需要配置批处理大小:
```yaml
spring:
jpa:
properties:
hibernate:
jdbc:
batch_size: 20
order_inserts: true
order_updates: true
```
对于大量数据的批量插入(数千行),建议改用 `JdbcTemplate` 的批处理操作——在这种规模下 Hibernate 的实体管理开销会非常大。
### 如何用 `Specification` 实现动态查询?
对于带有多个可选筛选条件的复杂搜索表单,使用 `JpaSpecificationExecutor`:
```java
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {
}
// 使用方式
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);
```
## 使用 Jackson 将对象或 Map 平铺到 JSON 中
在序列化 JSON 时,有时需要将嵌套对象的属性"平铺"到父级 JSON 中,而不是作为一个嵌套字段出现。Jackson 提供了两个注解来分别处理对象和 `Map` 的平铺场景。
### `@JsonUnwrapped` — 平铺已知结构的对象
`@JsonUnwrapped` 会将一个嵌套对象的属性直接展开到父级 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
}
```
序列化结果:
```json
{
"name": "陳大文",
"street": "彌敦道",
"city": "香港"
}
```
如果不使用 `@JsonUnwrapped`,`address` 会作为一个嵌套 JSON 对象出现:`"address": { "street": "...", "city": "..." }`。
### `@JsonAnyGetter` — 平铺键值不固定的 Map
`@JsonAnyGetter` 可以将 `Map` 中的键值对直接平铺到父级 JSON 中,适合 key 不固定或动态扩展的场景:
```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
}
```
当 `attributes` 包含 `{"colour": "red", "size": "XL"}` 时,序列化结果:
```json
{
"name": "Example Product",
"colour": "red",
"size": "XL"
}
```
注意 `@JsonAnyGetter` 需要加在 **getter 方法**上,而不是字段上;同时需要搭配 `@JsonAnySetter` 来支持反序列化。
### 如何选择
| 场景 | 注解 |
|----------------------------------------|--------------------|
| 平铺字段固定的嵌套对象(如地址、配置) | `@JsonUnwrapped` |
| 平铺 key 不固定的动态键值对 | `@JsonAnyGetter` |
@@ -1,238 +0,0 @@
---
title: Java 开发规范
tags:
- java
- spring-boot
- standards
- best-practice
- backend
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## Java 语言与编码风格
- **Java 版本**: 项目所使用的 JDK 应当在可能的情况下,使用最新的 LTS 版本。
- **命名约定**:
- 类: 帕斯卡命名法 (`PascalCase`) (例如,`UserService`, `OrderController`)。
- 方法: 驼峰命名法 (`camelCase`) (例如,`getUserById`, `saveOrder`)。
- 变量: 驼峰命名法 (`camelCase`) (例如,`username`, `statusCode`)。
- 常量: 全大写下划线命名法 (SCREAMING_SNAKE_CASE) (例如,`DEFAULT_PAGE_SIZE`)。
- **不变性**: 在可能的情况下,优先选择领域对象和 DTOs 的不变性,使用 `records` 或不可变类来减少副作用并提高线程安全性。
- **Optional**: 使用 `Optional<T>` 显式处理可能缺失的值,避免 `NullPointerException`。
- **Streams API**: 优先使用 Java Streams API 进行集合处理,提倡函数式和声明式编程。
- **异常处理**:
- 使用特定异常。避免捕获通用 `Exception`。
- 对于不可恢复的错误,抛出运行时异常。
- 对于可恢复的错误,如果业务逻辑需要,定义自定义受检异常。
- 利用 Spring 的 `@ControllerAdvice` 和 `@ExceptionHandler` 进行集中式全局异常处理和一致的 API 错误响应。
- **代码审查**: 所有后端代码在合并前必须经过彻底的人工代码审查,特别是遵循 GitFlow 原则。IntelliJ IDEA 的集成代码分析工具应作为初次审查使用。
## 文档与注释
- 后端 Java 代码中的所有**公共**类、方法和重要字段必须包含全面的 Javadoc 注释。
- Javadoc 应解释其目的、参数 (`@param`)、返回值 (`@return`) 和抛出的异常 (`@throws`)。
- Javadoc 的格式如下:
- Javadoc 需要遵循每行最多 100 个字符(包括用于调整格式的空白字符)。若内容超出了 100 字符,则应当在小于 100 字符的最后一个单词结尾处用换行符换行。但是如果在换行后,下一行句子的开头只有一个单词后就结束,则应当提前一个单词换行。
```java
/**
* Enables configuration properties for S3 file storage services. Individual service beans are
* created by their respective service classes to better support conditional configuration.
*/
```
- 每个段落之间使用一个 `<p>` 分割。
```java
/**
* This is the first paragraph of the Javadoc.
* <p>
* This is the second paragraph of the Javadoc.
*/
```
- 每个段落的内容必须是语法正确且含义完整的段落,遵循英文句子规范。
- 所有参数(`@param`)、返回值(`@return`)、异常抛出(`@throws`)及参考(`@see`)的解释需要遵循下面的规则:
- 无需使用大写字母开头
- 若描述的最后为陈述句,则无需使用标点符号结尾
```java
/**
* Returns the greater of two {@code int} values. That is, the
* result is the argument closer to the value of
* {@link Integer#MAX_VALUE}. If the arguments have the same value,
* the result is that same value.
*
* @param a an argument
* @param b another argument
* @return the larger of {@code a} and {@code b}
*/
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
```
## 依赖管理 (Gradle)
- **构建文件**: `build.gradle.kts` 必须组织良好,对插件、依赖项和任务有清晰的划分。
- **依赖版本**: 必须在 `gradle/libs.versions.toml` 文件中集中管理依赖项版本,以确保一致性。
- **插件管理**: 显式声明 Gradle 插件及其版本。
- **避免不必要的依赖项**: 仅包含项目实际使用的依赖项。定期审查并清理未使用的依赖项。
## API 设计 (RESTful)
- **RESTful 原则**: 遵循 RESTful 原则:
- **资源**: 将数据建模为可通过 URI 标识的资源。
- **HTTP 方法**: 适当地使用标准 HTTP 方法(GET 用于检索,POST 用于创建,PUT 用于完全更新,PATCH 用于部分更新,DELETE 用于删除)。
- **无状态性**: API 应是无状态的;客户端向服务器发送的每个请求都必须包含理解请求所需的所有信息。
- **URI**:
- 集合资源使用复数名词(例如,`/users`,`/products`)。
- URI 中使用连字符以提高可读性(例如,`/user-accounts`)。
- 避免在 URI 中使用动词(例如,使用 `/users` 而不是 `/getAllUsers`)。
- **状态码**: 使用适当的 HTTP 状态码来指示 API 请求的结果(例如,`200 OK`、`201 Created`、`204 No Content`、`400 Bad Request`、`401 Unauthorized`、`403 Forbidden`、`404 Not Found`、`500 Internal Server Error`)。
- **响应格式**: JSON 是首选的响应格式。
- **版本控制**: 建议在需要版本控制的接口中使用 Header 参数 `X-Endpoint-Version` 控制接口版本。
## Spring Boot 最佳实践与分层架构
- **分层架构 (MVC 配合 Manager 层)**: 我们的后端应用程序遵循严格的多层架构,确保职责清晰分离,并提高可维护性和可测试性。各层及其职责如下:
- **控制器层 (Controller Layer)**: 位于 `controller` 包中。负责暴露 RESTful API,处理 HTTP 请求,并将请求参数/主体映射到服务层调用。控制器应保持轻量,主要关注输入验证(使用 DTOs)和协调对 `Service` 层的调用。
- **服务层 (Service Layer)**: 位于 `service` 包中。此层封装了核心业务逻辑。服务层向 `Controller` 层提供可以直接使用的 API,抽象业务流程和事务管理。服务层协调对 `Manager` 层的调用以执行业务操作。
- **管理器层 (Manager Layer)**: 位于 `manager` 包中。此层提供原子化的业务操作,可由 `Service` 层组合使用。管理器通常处理更复杂的业务逻辑,可能涉及在更细粒度级别上与多个仓库或其他外部系统进行交互。
- **仓库层 (Repository Layer) (MyBatis)**: 位于 `repository` 包和 `src/main/resources/repository`(用于 XML 映射文件)中。此层负责提供原子化的数据库交互操作。
**层间通信策略**:
- **每一层内的组件仅可调用直接位于其下方的层中的组件。**
- **严格禁止跨层调用(例如,Controller 直接调用 Manager,Service 直接调用 Repository)。**
- **严格禁止向上调用(例如,Service 调用 Controller)。**
- **横向调用(例如,Service A 调用 Service B 处理不同领域)应仔细考虑,通常表明需要将共享逻辑重构到 `Manager` 或为该共享关注点重构一个专用 `Service` 中。**
- **配置**: 优先选择 `application.yml` 进行配置属性,而不是 `application.properties`,以获得更好的可读性和分层结构。使用 `@ConfigurationProperties` 进行类型安全配置。
- **依赖注入**: 所有依赖项都使用构造函数(强制依赖)或 Setter(非强制依赖)注入。避免在字段上使用 `@Autowired`,因为它会使测试更困难并隐藏依赖项。
- **服务**: 业务逻辑类使用 `@Service` 进行注解。服务应保持精简,并专注于协调领域操作,这些操作通常涉及通过 `Manager` 层组件进行业务逻辑处理,以及通过 `Manager` 或直接与 MyBatis 仓库进行交互(如果该特定操作不需要中间 Manager 逻辑,尽管对于所有仓库交互,Manager 层是首选,符合分层架构定义)。
- **仓库 (MyBatis 与 JPA)**:
- **项目使用 MyBatis 及 JPA 进行数据库操作**。`repository` 包及 `mapper` 包中的仓库接口定义了数据访问契约。
- 相应的 SQL 定义在位于 `src/main/resources/mapper` 的 XML 映射文件中管理。
- 数据操作约定:
- 对于简单数据库操作,使用 JPA。
- 对于复杂数据库操作,使用 MyBatis。
- 当进行分页查询时,页码应从 0 开始(*兼容 Spring Data JPA*)。
- **数据操作方法命名约定**:
- 对于**查询数据列表**:方法**必须**以 `selectListBy` 开头,紧随其后是筛选条件(例如,`selectListByUserId`、`selectListByDepartmentIdAndStatus`)。这些方法也必须包含一个 `PageRequest` 参数用于分页。
- 对于查询**单个数据记录**:方法**必须**以 `selectOne` 开头(例如,`selectOneById`、`selectOneByUsername`)。
- 对于**保存新数据**:方法**必须**命名为 `save`,Mapper 方法的返回值必须是 `int`(受影响的行数)。
- 对于**更新现有数据**:方法**必须**命名为 `update`,Mapper 方法的返回值必须是 `int`(受影响的行数)。
- 对于**删除数据**:方法**必须**以 `deleteBy` 开头,清晰地指明删除的依据(例如,`deleteById`)。Mapper 方法的返回值必须是 `int`(受影响的行数)。
- **控制器**:
- 使用 `@RestController` 注解 REST 控制器。
- 使用 `@GetMapping`、`@PostMapping` 等将 HTTP 方法(GET、POST、PUT、DELETE)映射到适当的控制器方法。
- 确保请求和响应负载定义明确(DTOs)并有文档说明。
- 控制器应主要处理 HTTP 请求/响应映射,并将实际业务逻辑委托给服务层。
- **DTOs (数据传输对象)**: 定义单独的 DTOs 用于请求和响应主体,以将内部领域模型与 API 契约解耦。在 DTOs 上使用验证注解(例如,`@Valid`、`@NotNull`、`@Size`)。
- **日志 (Slf4j & Logback)**:
- 应用程序中的所有日志记录都使用 `org.slf4j.Logger`。
- 配置 `application-dev.yml` 以设置日志级别、Appender 和输出格式。
- 日志消息应具有描述性并提供足够的上下文。避免记录敏感信息。
- 使用参数化日志记录以提高性能并防止字符串拼接开销(例如,`log.debug("Processing user: {}", userId);`)。
- 标准日志级别:`ERROR`、`WARN`、`INFO`、`DEBUG`、`TRACE`。
## 项目结构
后端应用程序遵循结构化的 Gradle 项目布局。核心应用程序应清晰地划分为各种子包。
```text
backend-application
├── build.gradle.kts // 项目的主 Gradle 构建脚本
├── config // 外部配置目录
│ ├── application-dev.yml // 开发环境的应用程序属性
│ └── application-prod.yml.example // 生产环境属性示例(待复制和配置)
├── database // 数据库相关文件
│ └── init.d // 数据库初始化脚本
│ └── init-en_GB.sql // 使用指定语言的数据库模式和初始数据的 SQL 脚本(英式英语区域设置)
├── gradle // Gradle Wrapper 和配置文件
│ ├── libs.versions.toml // 集中管理依赖版本(Gradle 版本目录)
│ └── wrapper // Gradle Wrapper 文件
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties // 项目特定的 Gradle 属性
├── gradlew // Gradle Wrapper 可执行文件 (Linux/macOS)
├── gradlew.bat // Gradle Wrapper 可执行文件 (Windows)
├── settings.gradle.kts // 多项目构建的 Gradle 设置(如果适用)
└── src
├── main
│ ├── java
│ │ └── com/onixbyte/application // 应用程序的根包
│ │ ├── Application.java // Spring Boot 应用程序主入口点
│ │ ├── config // Spring 配置类
│ │ ├── constant // 定义应用程序范围常量的类
│ │ ├── controller // REST API 端点
│ │ ├── domain // 核心领域模型和相关类型
│ │ │ ├── common // 通用领域对象/工具类
│ │ │ ├── entity // 表示数据库表的 JPA/MyBatis 实体
│ │ │ ├── model // 通用的实体类模型
│ │ │ ├── view // 专门用于只读操作的数据传输对象(例如,查询结果、报告结构)
│ │ │ └── web // 专门用于 Web 请求/响应体的数据传输对象
│ │ │ ├── request // 请求 DTO
│ │ │ └── response // 响应 DTO
│ │ ├── exception // 自定义应用程序特定异常
│ │ ├── extension // 扩展点或自定义功能
│ │ │ ├── jackson // Jackson 序列化/反序列化扩展
│ │ │ └── redis // Redis 相关扩展
│ │ │ └── serializer
│ │ ├── filter // Servlet 过滤器或 Spring Security 过滤器
│ │ ├── manager // 业务逻辑协调器,通常协调多个服务或仓库
│ │ ├── mapper // 数据访问层 (MyBatis 接口)
│ │ ├── processor // 通用处理组件或业务工作流
│ │ ├── properties // 用于类型安全配置属性的类 (`@ConfigurationProperties`)
│ │ ├── repository // 数据访问层 (Spring Data JPA 接口)
│ │ ├── security // Spring Security 特定组件
│ │ │ ├── authentication // 自定义认证机制
│ │ │ └── provider // 自定义认证提供者
│ │ ├── service // 核心业务逻辑(事务层)
│ │ ├── utils // 通用工具类
│ │ └── validation // 自定义验证逻辑
│ │ └── group // 针对不同上下文(例如,创建、更新)的验证分组
│ └── resources
│ ├── application.yml // 默认应用程序属性
│ └── mapper // MyBatis XML 映射文件
└── test
└── java
└── com/onixbyte/helix
└── HelixApplicationTests.java // Spring Boot 集成测试
```
**关键观察与具体指令:**
- **外部 `config` 目录**
- 环境配置 (`application-dev.yml`、`application-prod.yml.example`) 在顶层 `config` 目录中管理,与 `src/main/resources` 分开。这促进了特定于环境的属性管理,允许在部署时挂载或链接不同的配置。
- **禁止上传除 `src/main/resources/application.yml` 之外的任何配置文件到 Git 仓库。**
- **数据库初始化**: `database/init.d` 目录保留用于 SQL 脚本,特别是数据库模式初始化 (`init-en_GB.sql`),这对于环境设置和 CI/CD 流水线至关重要。这种结构暗示了"模式优先"或"代码驱动的模式演进"方法。
- **`client` 包**: 该包用于向应用提供所有中间件的服务,如 HTTP、S3 存储、Redis 调用等。对于需要添加到 Spring 上下文中的,自行编码实现的功能(如 JSON Web Token 生成与解析)也推荐放到该包中。
- **MyBatis 与 Spring Data JPA 集成**
- `src/main/java/.../mapper` 包包含 MyBatis mapper 接口,而实际的 SQL 定义位于 `src/main/resources/mapper/*.xml` 文件中。这种分离是保持代码整洁同时利用 MyBatis 强大 XML 映射能力的关键。
- `src/main/java/.../repository` 包包含 Spring Data JPA 接口。
- **`domain` 包的粒度**:
- `domain.entity`: 保留用于直接映射到数据库表的类(MyBatis 的 POJO)。
- `domain.model`: 用于不直接与单个表一对一映射的更通用领域对象或聚合根。
- `domain.view`: 专门用于在只读场景(例如,查询结果、报告结构)中呈现数据的数据传输对象 (DTO)。
- `domain.web.request` / `domain.web.response`: 明确分离的用于传入 API 请求和传出 API 响应的 DTO,严格遵守 API 契约并与内部领域实体解耦。
- **`manager` 和 `processor` 包**: 这些包暗示了一个分层架构,其中"管理器"协调涉及多个服务或仓库的操作,而"处理器"可能处理业务流程的特定方面。强制明确定义这些包中类的职责,以防止出现"贫血领域模型"或"上帝对象"等反模式。
- **`security` 包**: 此子包包含除初始配置之外的自定义 Spring Security 组件,例如自定义认证类型和提供者,表明了定制的安全实现。
- **`properties` 包**: 此包用于自定义 `@ConfigurationProperties` 类,促进对 YML 文件中定义的应用程序设置进行类型安全访问,位置得当。
- **`extension` 包**: 这是一个灵活的区域,用于应用程序特定的扩展,例如自定义 Jackson 序列化器或 Redis 定制。应谨慎使用,以防止成为"杂项"的堆放地。
## 安全性 (Spring Security)
- **强制使用**: Spring Security 在所有 Spring Boot Web 应用程序中都是强制的。
- **认证与授权**: 在 `SecurityConfig.java` 中配置认证机制(例如,OAuth 2.0、JWT、基于会话)和授权规则。
- **CSRF 保护**: 确保对修改状态的操作启用 CSRF 保护,除非有充分的理由禁用它(例如,已存在其他机制的无状态 API)。
- **CORS**: 根据前端部署要求正确配置跨域资源共享 (CORS)。
- **第三方身份提供者**: 在与第三方身份提供者(例如 Microsoft Entra ID)集成时,遵循安全令牌处理和用户配置的最佳实践。敏感凭据必须安全管理(例如,环境变量、Vault)。
- **输入验证**: 始终在服务器端验证所有用户输入,以防止常见的漏洞,如 SQL 注入、XSS 等。
- **内容安全策略 (CSP)**: 考虑为前端实施强大的 CSP 以减轻 XSS 攻击。
@@ -1,55 +0,0 @@
---
title: 修复 macOS 终端在私网 DNS 下 Host Name 显示为 IP 段的问题
tags:
- macos
- dns
- terminal
author:
name: Zihlu Wang
email: real@zihluwang.me
---
在部分企业或家庭私网环境中,DNS 反向解析会将设备的私网 IP 映射为一个以 `192`、`172` 或 `10` 开头的主机名。此时 macOS 终端提示符会从正常的 `user@MacBook-Pro` 变为 `user@192-168-1-100` 这样的形式,影响日常使用体验。
## 原因
macOS 在启动终端会话时会通过反向 DNS 查询当前 IP 对应的主机名。当私网 DNS 服务器返回了一个基于 IP 段拼凑的主机名(例如 `192-168-1-100.example.com`)时,系统便会采纳该名称作为 Host Name,终端提示符随之改变。
## 修复方法
使用 macOS 自带的 `scutil`(System Configuration Utility)即可将 Host Name 固定为你期望的值。
### 查看当前状态
```shell
# 查看当前 Host Name(可能为空或已被 DNS 改写)
scutil --get HostName
# 查看本地 Bonjour 名称
scutil --get LocalHostName
# 查看 Finder 中显示的计算机名
scutil --get ComputerName
```
### 设置 Host Name
```shell
sudo scutil --set HostName "MacBook-Pro"
```
建议使用不含空格和特殊字符的名称,例如 `MacBook-Pro`、`My-Mac` 或你的设备序列号。
### 验证修改
重新打开一个终端窗口,提示符中的 `@` 后面应恢复为你设置的主机名。
```shell
scutil --get HostName
# 输出: MacBook-Pro
```
## 补充说明
- `HostName` 仅影响网络层面的主机名标识,不会影响 `LocalHostName`(Bonjour 本地名称)和 `ComputerName`(Finder 显示名),三者独立管理。
- 若问题在重启后复现,可以检查 `/etc/hosts` 中是否有相关条目,或确认 DHCP/DNS 服务器是否持续下发不期望的主机名。
-200
View File
@@ -1,200 +0,0 @@
---
title: MinIO 新版本管理中心
tags:
- minio
- storage
- s3
- devops
- cheatsheet
author:
name: Zihlu Wang
email: real@zihluwang.me
---
MinIO 在新版本中将管理功能从 Web UI 中移除了。现在需要使用 **MinIO Client (mc)** 命令行工具来进行管理操作。
## 安装 MinIO Client (mc)
### Windows:
```powershell
# 下载 mc.exe
Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "mc.exe"
# 或使用 Chocolatey
choco install minio-client
```
### Linux/macOS:
```bash
# Linux
wget https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc
sudo mv mc /usr/local/bin/
# macOS
brew install minio/stable/mc
```
## 配置 MinIO Client
```bash
# 添加 MinIO 服务器别名
mc alias set myminio http://localhost:9000 minioadmin minioadmin
# 验证连接
mc admin info myminio
```
## 用户管理操作
### 创建用户
```bash
# 创建新用户
mc admin user add myminio newuser newpassword
# 查看所有用户
mc admin user list myminio
```
### 创建 Access Key 和 Secret Key
```bash
# 为用户创建服务账户(生成 AccessKey/SecretKey)
mc admin user svcacct add myminio newuser
# 或者指定自定义的 AccessKey
mc admin user svcacct add myminio newuser --access-key "MYACCESSKEY123" --secret-key "MYSECRETKEY456"
# 查看用户的服务账户
mc admin user svcacct list myminio newuser
```
## 权限管理
### 创建策略
```bash
# 创建策略文件 policy.json
cat > policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::mybucket/*"
]
}
]
}
EOF
# 添加策略
mc admin policy add myminio mypolicy policy.json
# 将策略分配给用户
mc admin policy set myminio mypolicy user=newuser
```
## 存储桶管理
```bash
# 创建存储桶
mc mb myminio/mybucket
# 列出存储桶
mc ls myminio
# 设置存储桶策略
mc policy set public myminio/mybucket
```
## 常用管理命令
```bash
# 查看服务器信息
mc admin info myminio
# 查看服务器配置
mc admin config get myminio
# 重启服务器
mc admin service restart myminio
# 查看日志
mc admin logs myminio
# 查看统计信息
mc admin prometheus metrics myminio
```
## 实用脚本示例
创建一个管理脚本 `setup-minio.sh`:
```bash
#!/bin/bash
MINIO_ALIAS="myminio"
MINIO_URL="http://localhost:9000"
ADMIN_USER="minioadmin"
ADMIN_PASS="minioadmin"
# 配置 MinIO 客户端
mc alias set $MINIO_ALIAS $MINIO_URL $ADMIN_USER $ADMIN_PASS
# 创建应用用户
APP_USER="appuser"
APP_PASS="apppassword"
mc admin user add $MINIO_ALIAS $APP_USER $APP_PASS
# 创建服务账户并获取 AccessKey/SecretKey
echo "Creating service account for $APP_USER..."
CREDENTIALS=$(mc admin user svcacct add $MINIO_ALIAS $APP_USER --json)
ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.accessKey')
SECRET_KEY=$(echo $CREDENTIALS | jq -r '.secretKey')
echo "Generated credentials:"
echo "Access Key: $ACCESS_KEY"
echo "Secret Key: $SECRET_KEY"
# 创建存储桶
mc mb $MINIO_ALIAS/app-bucket
# 设置只读策略
mc policy set download $MINIO_ALIAS/app-bucket
```
## Web Console 访问
虽然管理功能被移除,但您仍然可以通过以下方式访问 MinIO Console:
```bash
# 启动 MinIO Console(如果单独安装)
mc admin console myminio
```
或者在启动 MinIO 服务器时指定 Console 地址:
```bash
minio server /data --console-address ":9001"
```
## 总结
新版 MinIO 的管理完全依赖 `mc` 命令行工具:
1. **安装 mc 客户端**
2. **配置服务器别名**
3. **使用 `mc admin` 命令进行用户、权限、存储桶管理**
4. **通过 `mc admin user svcacct` 生成 AccessKey/SecretKey**
这种方式虽然需要命令行操作,但提供了更强大和灵活的管理能力,特别适合自动化部署和脚本化管理。
-31
View File
@@ -1,31 +0,0 @@
---
title: MyBatis Flex
tags:
- java
- mybatis
- spring-boot
- orm
- framework
author:
name: Zihlu Wang
email: real@zihluwang.me
---
> 本文仅针对在 Spring Boot 3 中使用 `MyBatis Flex`
## 安装与配置
### 安装
在 `gradle` 目录下的 `libs.versions.toml` 中添加如下代码:
```toml
[versions]
mybatisFlexVersion = "X.Y.Z"
hikariVersion = "X.Y.Z"
[libraries]
hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariVersion" }
mybatisFlex-processor = { group = "com.mybatis-flex", name = "mybatis-flex-processor", version.ref = "mybatisFlexVersion" }
mybatisFlex-starter = { group = "com.mybatis-flex", name = "mybatis-flex-spring-boot3-starter", version.ref = "mybatisFlexVersion" }
```
@@ -1,212 +0,0 @@
---
title: 使用分词在 PostgreSQL 中加速模糊查询
tags:
- postgresql
- zhparser
- full-text-search
- performance
author:
name: Siu Jam Oh
email: jamo.siu@gmail.com
---
## 背景与挑战
随着业务数据量突破 **200 万行**,传统的 `LIKE '%关键词%'` 模糊查询导致数据库 I/O 频繁告警,查询响应时间从毫秒级退化至秒级。为提升检索效率并支持中文语义,我们决定引入 `zhparser` 插件实现全文检索。
## 演进路线与环境适配
本次技术落地经历了四个关键阶段,每个阶段解决了不同的技术核心:
### CentOS 7.9 虚拟机(可行性验证)
- **目标**:验证 `SCWS` + `zhparser` 在旧版系统下的兼容性。
- **核心操作**:在 CentOS 7.9 环境下手动编译 `postgresql-16.2` 源码,完成插件初步跑通。
**结论**:确认了分词方案对中文检索性能的显著提升。
### 本地 Docker 容器(容器化探索)
- **目标**:在本地的完整系统进行初步测试。
- **核心操作**:通过 `docker cp` 注入二进制 `.so` 文件,解决 `ldconfig` 动态链接库路径可见性问题。
- **发现**:识别出 **"词典文件缺失"** 会导致分词退化为单字(助词)的故障点。
### EulerOS 2.0 测试服(自编译环境适配)
- **目标**:适配生产环境的 OS 架构(x86_64)与自编译安装的 PostgreSQL。
- **核心问题**:解决了 `libscws.so.1` 无法加载的报错。
- **关键方案**:
- 明确了 `postgres` 运行用户对 `/usr/local/scws/lib` 的访问权限需求。
- 通过修改 `systemd` 服务环境变量或创建 `/usr/lib64` 软链接强制刷新库搜索路径。
### 生产部署预备(最终调优)
- **目标**:确保 200w+ 数据量下的查询稳定性。
- **优化点**:针对"非语义片段(如:古唐合)"查询不到的问题,制定了"全文检索优先 + `pg_trgm` 索引辅助"的降级查询策略。
## 核心安装与配置步骤 (自编译环境)
### 安装 `SCWS` 分词引擎
SCWS 是 `zhparser` 依赖的底层核心,必须先行安装。
1. **下载并解压**:下载源码包(如 `scws-1.2.3`)。
2. **编译安装**:
```bash
./configure --prefix=/usr/local/scws
make && make install
```
3. **验证库文件**:确保 `/usr/local/scws/lib/libscws.so.1` 存在。
### 编译与安装 `zhparser`
这一步需要用到 PostgreSQL 自编译产生的 `pg_config`。
1. **获取源码**:从 GitHub 克隆 `zhparser` 项目。
2. **指定路径编译**:
```bash
# 确保 pg_config 在 PATH 中,或手动指定
make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config
make USE_PGXS=1 PG_CONFIG=/usr/local/pgsql/bin/pg_config install
```
*注:`install` 步骤会自动将 `zhparser.so` 放入 PG 的 `pkglibdir`,将脚本放入 `extension` 目录。*
### 解决动态链接库依赖
1. **刷新系统缓存**:
```bash
echo "/usr/local/scws/lib" > /etc/ld.so.conf.d/scws.conf
ldconfig
```
2. **权限检查**:确保运行 `postgres` 的 OS 用户对 `/usr/local/scws/lib` 有 `rx` 权限。
3. **强制软链 (备选)**:若 `ldconfig` 失效,可将库文件软链至 `/usr/lib64`。
### 重启数据库
修改完系统共享库配置后,必须重启 PostgreSQL 进程以重新加载环境变量和链接库。
```bash
## 使用 pg_ctl 重启(自编译路径需根据实际情况调整)
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart
## 或通过 systemd 重启(如果已注册服务)
systemctl restart postgresql
```
### 数据库内初始化
连接到 `psql`,执行逻辑配置:
```sql
-- 创建扩展
CREATE EXTENSION zhparser;
-- 创建全文检索配置并绑定分词器
CREATE TEXT SEARCH CONFIGURATION chinese (PARSER = zhparser);
-- 添加 Token 映射
ALTER TEXT SEARCH CONFIGURATION chinese ADD MAPPING FOR n,v,a,i,e,l,t,b WITH simple;
-- 【可选】指定自编译路径下的词典位置
-- ALTER DATABASE postgres SET zhparser.dict_path = '/usr/local/scws/etc/dict.utf8.xdb';
```
## 性能分析与避坑指南
### 索引性能瓶颈分析
在测试中发现,即使是精确查询,由于使用了不当的联合索引(`create_time` 在首列),导致产生 `Bitmap Index Scan`,耗时高达 **482ms**。
- **改进**:对检索高频列建立单列 **B-tree** 索引,利用 `Index Scan` 将响应降至 **10ms** 以内。
### GIN 索引与非语义匹配
- **跨词截断问题**:分词引擎基于语义,类似"古唐合"这种截断词可能因为分词边界导致 `@@` 无法命中。
- **应对方案**:采用"瀑布式搜索"。全文检索(FTS)优先;若结果为空,自动降级为 `LIKE` 模糊查询,并配合 `pg_trgm` 索引加速。
## 终极部署方案:双轨并行检索
经过对数据的分析,发现对于账户名称中出现了约 1.24% 的非标准简体中文的数据,并且数据库中也存在部分因为公司名称怪异而导致数据查询不出来的问题,决定采取"分步降级"策略:
- **第一步:全文检索(Fast Track):** 利用 GIN 索引进行 `@@` 匹配。
- **第二步:结果评估:** 若返回结果为空,且搜索词包含字母或疑似繁体字符。
- **第三步:模糊兜底(Safe Fallback):** 执行 `LIKE '%关键词%'`。虽然速度较慢,但由于其作为"补漏"逻辑,触发频率仅为 1% 左右,不会对数据库造成整体压力。
## 搜索优化:集成自定义业务词库
为了解决全文检索中公司品牌名被误切(如"元一"被切分为数词/量词)的问题,需构建一套从数据提取到索引重建的自动化维护链路。
### 词库提取与预处理
利用 **`companynameparser`** 的结构化解析能力剥离地名与行业后缀,并结合 **`jieba`** 进行语义复核,确保核心品牌词的完整性:
- **提取逻辑**:通过 Python 脚本遍历全量 `buyer_unique_name`,提取核心 `brand` 字段。
- **权重补偿**:针对包含"元"、"一"、"三"等易碎词条,手动将 TF(词频权重)提升至 **50.0 - 60.0**,确保其优先级高于内置量词规则。
- **输出规范**:输出符合 SCWS 标准的 4 字段 `UTF-8` 文本(WORD, TF, IDF, ATTR),建议使用制表符 `\t` 分隔以规避解析异常。
### 词库编译与部署
:::tip
对于使用 Windows 进行编译 `xdb` 二进制词典文件的用户,可以前往 OnixByte 的 [GitHub](https://github.com/onixbyte/scws/releases/tag/1.2.3) 或 [GitLab](https://git.onixbyte.cn/onixbyte/scws/-/releases/1.2.3) 页面下载使用 MingW 预编译好的,适用于 Windows 的原生 scws 命令行工具。
:::
将文本词典转换为 SCWS 高效二进制格式(XDB):
1. **编译二进制词典**:
```bash
# 使用 scws-gen-dict 工具生成加密二进制词库
/usr/local/scws/bin/scws-gen-dict -i custom_company.txt -o /usr/local/scws/etc/custom_company.xdb -c utf8
```
2. **文件分发与权限**:将生成的 `.xdb` 文件移动至分词数据目录,并确保 `postgres` 用户具备读取权限:
```bash
cp custom_company.xdb /usr/local/pgsql/share/tsearch_data/
chown postgres:postgres /usr/local/pgsql/share/tsearch_data/custom_company.xdb
```
### 数据库参数固化
修改 `postgresql.conf` 配置文件,强制加载 `zhparser` 及其自定义扩展词库:
```plain text
## 预加载插件库(必须重启生效)
shared_preload_libraries = 'zhparser'
## 加载自定义外部词典(需使用相对 tsearch_data 的路径)
zhparser.extra_dicts = 'custom_company.xdb'
```
### 索引热更新与验证
由于分词规则变动,存量数据必须通过重建索引实现语义同步:
**物理重启服务**:
```bash
su - postgres -c "/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data restart"
```
**在线重建索引**:利用 `CONCURRENTLY` 关键字,在不阻塞 40 万条数据 DML 操作的前提下刷新 GIN 索引:
```bash
REINDEX INDEX CONCURRENTLY index_name;
```
**分词效能验证**:
```sql
-- 预期结果词性应显示为 n (noun),而非 x (unknown)
SELECT * FROM ts_debug('chinese', '元一能源');
```
**优化建议:**
- **显式提及权重补偿**:这是解决"元一"分词失败(显示为 `x`)的关键技术点。
- **区分重启与重载**:明确 `shared_preload_libraries` 必须通过 `restart` 激活。
-22
View File
@@ -1,22 +0,0 @@
---
title: 四分位法
tags:
- statistics
- algorithm
- data-analysis
- math
author:
name: Zihlu Wang
email: real@zihluwang.me
---
四分位法是统计学中的一种常用方法,主要用于数据的分析和表示。这种方法涉及将一组数据分为四个等分,每份包含四分之一的数据量。主要的统计量包括第一四分位数(Q1)、第二四分位数(Q2),即中位数和第三四分位数(Q3)。
- 第一四分位数(`Q1`),也称为下四分位数,是指数据集中所有数值按大小排列后位于第 25% 位置的数值。
- 第二四分位数(`Q2`),即中位数,是指数据集中所有数值按大小排列后位于第 50% 位置的数值。
- 第三四分位数(`Q3`),也称为上四分位数,是指数据集中所有数值按大小排列后位于第 75% 位置的数值。
- 四分位距(`IQR`),即第三四分位数与第一四分位数之间的差值,用于表示数据集中间50%数据的离散程度。四分位距的计算公式为 IQR = Q3 - Q1。四分位距常用于构建箱形图,是一种描述数据分布的有效方式,特别适用于识别数据中的异常值。
上界值 = Q3 + 1.5 IQR。
下界值 = Q1 - 1.5 IQR。
-140
View File
@@ -1,140 +0,0 @@
---
title: 搭建 LDAP 服务
tags:
- ldap
- openldap
- authentication
- linux
- devops
author:
name: Zihlu Wang
email: real@zihluwang.me
---
搭建 LDAP(轻型目录访问协议)服务器是实现企业级**集中身份认证**和**权限管理**的核心步骤。最常用的开源实现是 **OpenLDAP**。
以下是在基于 Debian/Ubuntu 的 Linux 系统上搭建 OpenLDAP 服务器的详细步骤。
## 环境准备与安装
在开始之前,请确保你的系统软件包是最新的,并设置好主机名。
```bash
## 更新系统
sudo apt update && sudo apt upgrade -y
## 安装 OpenLDAP 及管理工具
## slapd 是守护进程,ldap-utils 是命令行客户端工具
sudo apt install slapd ldap-utils -y
```
> 在安装过程中,系统会提示你设置 **LDAP 管理员密码**。请务必记住这个密码,稍后配置时会频繁使用。
## 配置 OpenLDAP 服务器
虽然安装时已经初始化了部分设置,但通常我们需要根据具体的域名(如 `example.com`)进行自定义配置。
### 重新配置 slapd
执行以下命令进入交互式配置界面:
```bash
sudo dpkg-reconfigure slapd
```
**配置建议**:
1. **Omit OpenLDAP server configuration?** 选择 **No**。
2. **DNS domain name:** 输入你的域名(例如 `centre.example.com`)。这将决定你的基准识别名(Base DN),如 `dc=centre,dc=example,dc=com`。
3. **Organization name:** 输入你的组织名称。
4. **Administrator password:** 输入之前设置的管理员密码。
5. **Database backend:** 建议选择 **MDB**。
6. **Remove database when slapd is purged?** 选择 **No**。
7. **Move old database?** 选择 **Yes**。
## 理解 LDAP 层级结构
LDAP 的数据是以**树状结构**存储的。为了方便管理,我们通常会创建两个"组织单元"(Organizational Units, OU):一个存放用户(Users),一个存放组(Groups)。
## 创建组织单元 (OU)
在 LDAP 中,我们使用 **LDIF** (LDAP Data Interchange Format) 文件来添加或修改目录。
创建一个名为 `base.ldif` 的文件:
```text
## 创建用户组 OU
dn: ou=people,dc=centre,dc=example,dc=com
objectClass: organizationalUnit
ou: people
## 创建用户组别 OU
dn: ou=groups,dc=centre,dc=example,dc=com
objectClass: organizationalUnit
ou: groups
```
**导入数据:**
```bash
## 使用管理员身份将 LDIF 文件导入数据库
ldapadd -x -D "cn=admin,dc=centre,dc=example,dc=com" -W -f base.ldif
```
## 添加用户和组
创建一个名为 `groups.ldif` 的文件来定义一个新组:
```text
## 定义一个新组
dn: cn=developers,ou=groups,dc=centre,dc=example,dc=com
objectClass: inetOrgGroup
cn: developers
```
创建一个名为 `users.ldif` 的文件来定义一个新用户:
```text
## 定义一个新用户
dn: uid=jbloggs,ou=people,dc=centre,dc=example,dc=com # 该条目的身份证号,即 Distinguished Name (DN)。
objectClass: inetOrgPerson # 对象类,inetOrgPerson 代表这是一个互联网机构人员。它允许你使用 email、displayName、telephoneNumber 等常见的联系人属性
objectClass: posixAccount # 它使该条目兼容 Unix/Linux 系统账号。有了它,用户才能登录 Linux 服务器,并拥有 UID、GID 和家目录。
objectClass: shadowAccount # 用于管理密码老化(如过期、更改警告等),对应 Linux 中的 /etc/shadow 文件功能。
uid: jbloggs # 用户的登录名(User ID)。在 Linux 登录界面输入的通常就是这个。
sn: Bloggs # 姓(Surname)。inetOrgPerson 类要求必须填写。
givenName: Joe # 名(First Name)。
cn: Joe Bloggs # 全名(Common Name)。这是 LDAP 条目的标准显示名称。
displayName: Joe Bloggs # 在图形界面或邮件客户端显示的友好名称。
uidNumber: 10000 # Linux 系统中的用户 ID 数字
gidNumber: 5000 # 用户主组的 ID 数字。
userPassword: {SSHA}密码哈希值或明文 # 存储用户的加密密码。
homeDirectory: /home/jbloggs # 用户登录 Linux 后的家目录路径。
loginShell: /bin/bash # 用户登录后使用的 Shell 环境。
```
> 如果您将来要集成 **GitLab**、**Jenkins** 或 **VPN**,它们通常会搜索 `uid` 属性来验证登录名。
### FAQ
#### 如果不需要为该用户授予服务器的登录权限,是不是可以移除 objectClass: posixAccount?
**是的,完全可以移除**,但你需要注意属性之间的"绑定关系"。
如果你只需要这个用户用于 Web 应用登录(如 GitLab, Jenkins, Wiki)或作为电子邮件联系人,而不需要他通过 SSH 或控制台登录 Linux 服务器,移除 `posixAccount` 是更规范的做法。
当你移除 `objectClass: posixAccount` 时,以下属性**必须一并删除**,因为它们属于该类定义的"强制或可选属性":
- `uidNumber`
- `gidNumber`
- `homeDirectory`
- `loginShell`
此外, `shadowAccount` 通常也与系统登录挂钩,如果不需要管理 Linux 密码过期策略,也可以一并移除。
## 后续建议与管理工具
命令行管理 LDAP 较为繁琐,建议使用以下工具进行可视化操作:
- **phpLDAPAdmin**: 基于 Web 的管理界面,适合快速上手;
- **Apache Directory Studio**: 强大的跨平台桌面客户端,适合复杂的架构设计。
- **安全加固**: 默认情况下 LDAP 是明文传输的,建议配置 **LDAPS (LDAP over SSL/TLS)** 来加密 636 端口的通信。
-89
View File
@@ -1,89 +0,0 @@
---
title: 在 MySQL 中使用 JSON
tags:
- mysql
- json
- database
author:
name: Zihlu Wang
email: real@zihluwang.me
---
MySQL (自 5.7 版本开始) **不直接支持名为 `jsonb` 的数据类型**。`jsonb` 是 PostgreSQL 数据库特有的数据类型,它以二进制格式存储 JSON 数据,并对其进行预解析,以便在查询时更快地访问和操作。
然而,MySQL 提供的 `JSON` 数据类型在功能和内部实现上与 PostgreSQL 的 `jsonb` 有很多相似之处,尤其是在查询数据方面。
MySQL 的 `JSON` 数据类型于 MySQL 5.7 版本引入,其特点如下:
1. **二进制存储**:与 PostgreSQL 的 `jsonb` 类似,MySQL 的 `JSON` 类型数据也是以**内部二进制格式**存储的,而不是简单的文本字符串。这使得 JSON 数据的读取和操作效率更高,因为数据库不必在每次查询时都解析文本格式的 JSON 字符串。
2. **自动验证**:当您插入或更新 `JSON` 列时,MySQL 会自动验证其内容是否是有效的 JSON 文档。如果不是,则会抛出错误。
3. **优化存储**:二进制格式的存储也对空间进行了优化,通常比存储原始文本格式的 JSON 更加紧凑。
MySQL 提供了一系列强大的函数和操作符来查询和操作 `JSON` 数据,这些功能与您对 `jsonb` 的期望非常相似:
1. **`->` (JSON Extract Operator)**:用于从 JSON 文档中提取值。它返回一个 JSON 值。
```sql
SELECT my_json_column->'$.key' FROM my_table;
-- 示例:提取 user 对象的 name 属性
-- 假设 my_json_column 存储 {'user': {'name': 'Alice'}}
SELECT json_data->'$.user.name' FROM my_table;
```
2. **`->>` (JSON Unquote Operator)**:用于从 JSON 文档中提取值并**自动取消引用 (unquote)**,通常返回一个标量值(如字符串、数字)。这等同于 `JSON_UNQUOTE(JSON_EXTRACT(...))`。
```sql
SELECT my_json_column->>'$.key' FROM my_table;
-- 示例:提取 user 对象的 name 属性(直接返回字符串 'Alice')
SELECT json_data->>'$.user.name' FROM my_table;
```
3. **`JSON_EXTRACT(json_doc, path, ...)`**:显式地从 JSON 文档中提取数据。
```sql
SELECT JSON_EXTRACT(my_json_column, '$.key') FROM my_table;
```
4. **`JSON_CONTAINS(json_doc, candidate, path)`**:检查 JSON 文档中是否包含指定的值。
```sql
-- 检查tags数组是否包含 'backend'
-- 假设 my_json_column 存储 {'tags': ['frontend', 'backend']}
SELECT * FROM my_table WHERE JSON_CONTAINS(json_data->'$.tags', '"backend"');
```
5. **`JSON_SEARCH(json_doc, one_or_all, search_str, escape_char, path, ...)`**:返回指定字符串在 JSON 文档中的路径。
```sql
-- 查找值为 'test' 的路径
SELECT JSON_SEARCH(my_json_column, 'one', 'test') FROM my_table;
```
6. **`JSON_TABLE(json_doc, path COLUMNS ...) ` (MySQL 8.0 及更高版本)**:这是一个非常强大的函数,可以将 JSON 数据 "展开" 成关系型的行和列,非常适合进行复杂查询和报表。
```sql
-- 假设 json_data 存储 {'items': [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]}
SELECT *
FROM my_table,
JSON_TABLE(json_data, '$.items[*]' COLUMNS(
itemId INT PATH '$.id',
itemName VARCHAR(50) PATH '$.name'
)) AS jt;
```
像 PostgreSQL 的 `jsonb` 一样,为了对 JSON 字段进行高效查询,您通常需要创建索引。由于 JSON 字段的内容是动态的,MySQL 不直接支持在 JSON 字段的某个内部路径上直接创建传统 B-tree 索引。但是,可以通过 **虚拟列 (Virtual Generated Columns)** 来实现:
1. **创建虚拟列**:定义一个虚拟列,其值是从 JSON 字段中提取的特定路径。
```sql
ALTER TABLE my_table
ADD COLUMN user_name VARCHAR(255) AS (json_data->>'$.user.name') VIRTUAL;
```
2. **在虚拟列上创建索引**:这样,当您查询 `WHERE json_data->>'$.user.name' = 'Alice'` 时,MySQL 优化器可以选择使用 `idx_user_name` 索引,从而大大提高查询性能。
```sql
CREATE INDEX idx_user_name ON my_table (user_name);
```
尽管 MySQL 没有 `jsonb` 这个名字,但其 `JSON` 数据类型提供了高度相似的功能:二进制存储优化、自动验证和丰富的查询操作符及函数。通过结合虚拟列和索引,MySQL 可以在处理 JSON 数据时提供与 PostgreSQL 的 `jsonb` 相似的查询性能和灵活性。
-83
View File
@@ -1,83 +0,0 @@
---
title: 在第一方系统中使用 Gravatar
tags:
- gravatar
- avatar
author:
name: Zihlu Wang
email: real@zihluwang.me
---
[Gravatar](https://gravatar.com)(Globally Recognised Avatar)是一项将头像图片与邮箱地址关联的服务。当用户使用邮箱在你的平台上注册时,你可以直接展示其
Gravatar 作为默认头像,而无需自行搭建图片上传和存储服务。
## 工作原理
Gravatar 提供了一个简单的 HTTP 端点。你只需计算用户**小写并去除首尾空格后**的邮箱地址的 SHA256 哈希值,然后将其嵌入图片
URL:
```
https://gravatar.com/avatar/<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 参数
`/avatar/` 端点接受以下查询参数来定制结果:
| 参数 | 描述 | 示例 |
|-----|------------------------|----------------|
| `s` | 图片尺寸(像素,默认 80) | `?s=200` |
| `d` | 未找到 Gravatar 时的默认图片 | `?d=identicon` |
| `r` | 内容分级(`g`、`pg`、`r`、`x`) | `?r=g` |
### 默认图片选项(`d`)
- `identicon` — 基于哈希值的几何图案
- `robohash` — 自动生成的机器人图片
- `retro` — 8-bit 风格的像素化人脸
- `monsterid` — 自动生成的怪物卡通
- `wavatar` — 自动生成的人脸
- `mp` — 通用剪影(Mystery Person)
- `blank` — 透明 PNG
- 自定义 URL(需经过 URL 编码)
### 完整示例
```html
<img
src="https://gravatar.com/avatar/a3b4c5d6e7f8?s=160&d=robohash&r=g"
alt="用户头像"
width="160"
height="160"
/>
```
始终传入 `d` 参数以避免未设置 Gravatar 的用户出现裂图。`identicon` 和 `robohash` 是常用的选择,因为它们能为每个哈希值生成唯一且易于辨识的图片。
@@ -1,36 +0,0 @@
---
title: 版本控制与代码审查
tags:
- git
- code-review
- best-practice
- workflow
author:
name: Zihlu Wang
email: real@zihluwang.me
---
## GitFlow 工作流
版本控制将使用 GitFlow 分支模型,包括 `main`、`develop`、`feature`、`release` 和 `hotfix` 分支。
- `main`: 生产就绪代码。只有 `release` 和 `hotfix` 分支会合并到 `main`。
- `develop`: 即将开发的功能集成分支。
- `feature/*`: 用于新功能的分支,从 `develop` 分支出来。
- `release/*`: 用于准备新生产版本的分支,从 `develop` 分支出来。
- `hotfix/*`: 用于紧急生产错误修复的分支,从 `main` 分支出来。
## 拉取请求 (PRs)
所有代码更改(直接推送到功能分支除外)都必须通过拉取请求提交。
## 代码审查
- 每个拉取请求必须由至少一名其他开发人员进行审查。
- 审查者负责检查是否符合本代码标准、代码质量、逻辑正确性和测试覆盖率。
- 在创建 PR 之前,应在本地运行 IntelliJ IDEA 的集成代码分析工具。
## 提交消息
编写清晰、简洁、描述性的提交消息,解释更改了什么以及为什么进行更改。如果可能,遵循约定式提交格式(例如,
`feat: add user registration endpoint`)。
@@ -1,85 +0,0 @@
---
title: 通过备份驱动的方式重装系统后一键恢复驱动
tags:
- windows
- driver
- backup
- system-reinstall
- dism
author:
name: Zihlu Wang
email: real@zihluwang.me
---
import { Kbd } from "@rspress/core/theme"
重装 Windows 系统后,最让人头疼的环节之一就是安装驱动程序——显卡、网卡、声卡、蓝牙、触摸板……缺少任何一个都会影响正常使用。Windows Update 能自动拉取一部分,但往往版本老旧或根本找不到某些小众硬件的驱动。
好在 Windows 内置的 DISM 工具提供了驱动导出和恢复功能,只需在重装前花一分钟备份,重装后就能一键恢复所有驱动。
## 重装前:备份所有驱动
以**管理员身份**打开终端(PowerShell 或命令提示符均可),执行以下命令:
```powershell
dism /online /export-driver /destination:D:\DriverBackup
```
将 `D:\DriverBackup` 替换为你的备份目标路径。建议放到外置硬盘、U 盘或非系统盘,确保重装时不会被格式化。
命令执行后会遍历系统中所有已安装的第三方驱动,逐个导出到目标目录。整个过程通常在一分钟左右完成。导出完成后,你会看到类似如下的输出:
```
The operation completed successfully.
```
将该备份目录妥善保存即可进行系统重装。
### 备份了什么?
该命令导出的是**第三方驱动程序**(即非 Windows 内置驱动),包括显卡、声卡、网卡、芯片组、蓝牙、读卡器等所有你在系统使用过程中安装过的驱动。Windows 自带的通用驱动不会被导出,这部分系统重装后会自动回归。
## 重装后:一键恢复所有驱动
### 方法一:命令行一键恢复(推荐)
这是最省心的方式。同样以**管理员身份**打开终端,执行:
```powershell
dism /online /add-driver /driver:D:\DriverBackup /recurse
```
`/recurse` 参数会递归搜索备份目录下的所有子文件夹,将每一个驱动都安装到新系统中。执行完成后**必须重启电脑**使其生效。
### 方法二:通过设备管理器逐个安装
如果你更偏好图形界面,或者只需要恢复特定驱动:
1. 按 <Kbd>Win</Kbd> + <Kbd>X</Kbd>,选择**设备管理器**
2. 找到带有黄色感叹号的设备(即缺失驱动的设备)
3. 右键点击该设备,选择**更新驱动程序**
4. 选择**浏览我的电脑以查找驱动程序**
5. 将路径指向你的驱动备份目录,勾选**包括子文件夹**
6. 点击下一步,系统会自动搜索并安装匹配的驱动
重复以上步骤直到所有设备都恢复正常。
## 常见问题
### 显示 "The driver package could not be installed"
少数情况下,某些驱动可能因为签名问题或版本不兼容而无法通过 DISM 安装。这时可以尝试手动安装:在备份目录中找到对应的 `.inf` 文件,右键选择**安装**。
### 恢复后仍有设备缺失驱动
DISM 备份的仅是当前系统上已安装的驱动。如果重装前就有设备未被正确驱动,这些设备的驱动自然不会存在于备份中。建议在备份前先用设备管理器确认所有设备都已正常工作。
### 能否跨 Windows 版本恢复?
同一台电脑从 Windows 10 升级到 Windows 11 的驱动恢复通常是可行的,但不建议在不同硬件配置的电脑间迁移驱动备份。每台机器的驱动应独立备份。
## 小结
这套流程的核心优势在于**零第三方工具依赖**——DISM 是 Windows 内建组件,无需安装任何额外软件。重装前花一分钟导出,重装后一条命令恢复,比手动逐个下载安装驱动高效得多。养成重装前先备份驱动的习惯,能让每次系统迁移都轻松不少。
> 本文内容参考自抖音用户 [@科技碎碎念](https://v.douyin.com/Cbf0ruVdbUM/) 的视频教程。
+70 -20
View File
@@ -3,26 +3,76 @@ pageType: home
hero: hero:
name: OnixByte name: OnixByte
tagline: 一个小型开源开发者组织,为开发者打造实用工具。 tagline: 设计、构建并长期维护您的核心业务系统。
actions: actions:
- theme: brand - theme: brand
text: 探索项目 text: 了解定制服务
link: https://git.onixbyte.com/onixbyte link: /services/
features: - theme: alt
- title: OnixByte Toolbox text: 查看产品
details: 一个多功能 Java 类库,为现代 Java 开发提供通用工具,无缝集成 Spring 生态。已上架 Maven Central。 link: /products/
icon: ☕ sections:
link: https://git.onixbyte.com/explore/repos?q=onixbyte-toolbox&topic=1 - type: capabilities
- title: Dev Lab id: capabilities
details: 基于 Vite 和 React 构建的实用在线工具集。包含 JSON Path 查看器、BMI 计算器以及 JSON 转表格工具。 title: 我们能为您做什么
icon: 🛠️ subtitle: 从业务诉求到稳定投产,提供全周期、定制化的软件研发交付。
link: https://dev-lab.onixbyte.com items:
- title: Helix - icon: "🏢"
details: 一个不断演进的前后端模版项目,结合 Spring Boot 与 React,助你快速构建应用。 title: 定制化业务系统
icon: 🌀 details: 围绕实际运作流程构建,让系统成为推动效率提升的数字基座。
link: https://git.onixbyte.com/explore/repos?q=helix&topic=1 subItems:
- title: Delta Force Guide - 定制业务系统
details: 社区向的《三角洲行动》游戏指南,提供最新游戏资讯、实用技巧和战术策略。 - 自动化运营工具
icon: 🎯 - 现有系统升级改造
link: https://git.onixbyte.com/explore/repos?q=delta-force-guide&topic=1 - icon: "🌐"
title: 官方门户与营销站点
details: 打造现代、专业且兼顾转化率的数字门面,全面提升品牌认知。
subItems:
- 企业门户
- 产品落地页 (Landing Page)
- 国际化多语言站点
- icon: "📱"
title: 移动端应用全案
details: 从轻量化的小程序到全平台原生 App,打造顺畅无阻的移动体验。
subItems:
- 微信小程序
- Android / HarmonyOS 原生开发
- 多端跨平台解决方案
- type: advantages
id: advantages
title: 核心优势
subtitle: 摒弃外包拼凑与短期交付,用工程化思维构建可靠软件。
items:
- icon: "🧩"
title: 端到端自主研发
details: 涵盖前端、服务端、数据库与基础设施运维,单一责任主体贯穿全生命周期,沟通无缝隙,交付高效率。
span: 6
- icon: "🔧"
title: 全生命周期守护
details: 交付上线只是起点。我们提供长期可靠的系统运维与敏捷迭代支持,保障系统平稳演进与持续创造价值。
span: 6
- type: cases
id: work
title: 典型案例
subtitle: 深入真实业务场景,看我们如何用工程标准完成稳定交付。
items:
# 待客户授权后,可在此补充 quote, author, role 等评价字段
- company: 广东某服饰制造企业
industry: 传统制造 / 外贸出海
details: 为其重塑多端适配的品牌门户与产品数字展厅,采用现代前端架构与自动化静态构建,大幅缩短首屏加载时间并提升海外访问稳定性。
tags:
- 品牌官网
- 静态化构建 (SSG)
- 响应式设计
- type: cta
id: contact
title: 聊聊您要做的事
subtitle: 聊聊您的项目或想法,我们随时在线。
actions:
- theme: brand
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: 或者扫码加企业微信
--- ---
+66
View File
@@ -0,0 +1,66 @@
---
title: 隐私政策
description: 长沙曜珀科技有限公司在本网站及我们所发布工具中如何处理个人信息。
sidebar: false
outline: false
---
<!-- TODO(legal): 已对照实际构建核实过事实(不使用 cookie、无统计脚本、仅两个
localStorage 键),但未经律师审阅。正式依赖前请交由法务检查。 -->
**最后更新:2026 年 9 月 25 日。**
本政策适用于本网站及我们在其下发布的工具。发布方为长沙曜珀科技有限公司(下称"我们"),即 onixbyte.com 与 onixbyte.cn 的运营方。
## 我们收集什么
几乎不收集任何信息。
本网站没有账号注册,没有邮件订阅,也没有任何表单需要提交。我们不使用统计分析、广告或任何形式的追踪脚本,也不会为访客建立画像。
我们不会向您索取姓名、邮箱、电话号码或其他个人信息,本网站上也没有任何可以留下这些信息的地方。
## 在您浏览器本地运行的工具
我们发布的工具(包括 Dev Lab)完全在您的浏览器中运行。您粘贴进去的内容 —— 配置文件、接口返回、或其他任何数据 —— 都在您自己的机器上处理,不会传输给我们或任何第三方。这些工具没有上传步骤,也不存在能执行上传的代码。
这是刻意的设计选择。我们自己在真实数据上使用这些工具,而一个会把数据发送出去的工具,我们自己就不会用。
## 本地存储与 cookie
我们不使用 cookie。
网站会在您浏览器的本地存储中保存两项很小的信息。两者都留在您的设备上,我们也无法读取:
| 键 | 内容 |
| -------------------------- | ---------------------------------- |
| `rspress-theme-appearance` | 您选择的浅色或深色主题 |
| `rspress-visited` | 一个标记,用于让语言跳转只发生一次 |
清除浏览器站点数据即可删除这两项,网站不会因此无法使用。
## 服务器日志
与所有网站一样,为本站提供服务的主机可能会记录标准访问日志 —— IP 地址、浏览器 User-Agent、所请求的页面 —— 用于安全防护与故障排查。这些日志不用于识别或分析个人,我们也不会将其与其他信息关联。
<!-- TODO(legal): 请确认两个托管方(Vercel 与 onixbyte.cn 服务器)的实际日志
保留期限,并在此写明。 -->
## 您的权利
由于我们不持有您的个人信息,通常情况下没有可供查询、更正或删除的内容。如果您认为我们持有您的某些信息,请来信告知,我们会核实处理。
如果您所在司法辖区赋予您对个人数据的相关权利,这些权利不受本政策任何内容的影响。
## 联系方式
关于本政策,或关于我们如何处理数据:
**legal@onixbyte.com**
长沙曜珀科技有限公司
中国湖南省
## 政策变更
如果我们开始收集任何信息,会在收集之前先在本页说明。页面顶部的日期为最近一次修改时间。
+63
View File
@@ -0,0 +1,63 @@
---
title: 服务条款
description: 使用曜珀科技网站、所发布工具及开源项目所适用的条款。
sidebar: false
outline: false
---
<!-- TODO(legal): 已按网站与工具的实际行为撰写,但未经律师审阅。正式依赖前请交由
法务检查 —— 尤其是责任限制部分。 -->
**最后更新:2026 年 9 月 25 日。**
本条款适用于您对本网站及我们所发布的免费工具的使用。发布方为长沙曜珀科技有限公司(下称"我们"),即 onixbyte.com 与 onixbyte.cn 的运营方。使用本网站或上述工具,即表示您同意本条款。
## 本条款的适用范围
本条款适用于本网站,以及我们托管并免费提供的工具(例如 Dev Lab)。
本条款**不**适用于我们的开源项目 —— 详见下文"开源项目"一节 —— 也不适用于您与我们之间的任何商业合作。我们为客户所做的工作,以该项目合同为准。
## 网站与工具的使用
您可以出于任何合法目的使用本网站与上述工具。但您不得:
- 干扰或过载服务,或以影响他人使用的方式通过自动化手段访问;
- 试图访问您未获授权的系统或数据;
- 使用上述工具处理违法内容,或处理您无权处理的内容。
## 您的数据始终属于您
您粘贴进浏览器本地工具的任何内容都归您所有,并且始终留在您的机器上。我们不主张任何权利,也从未接收到这些内容。详见[隐私政策](/legal/privacy)。
## 工具按现状提供
上述工具免费提供,不附带任何形式的保证。它们是实用工具,而非专业意见:
- JSON 相关工具的输出只是查看数据的便利手段。任何您打算据以决策的结果,请与原始数据核对。
- BMI 计算器给出的是通用参考数值,不构成医疗建议,也不能替代咨询专业人士。
## 开源项目
我们的开源项目 —— 包括发布在 Maven Central 与 GitHub 上的类库 —— 均按各自仓库中声明的许可证发布。您的使用受该许可证约束,且就该项目而言,许可证的效力优先于本条款。若本条款与开源许可证存在冲突,以开源许可证为准。
## 知识产权
OnixByte 名称、标识及本网站的设计归我们所有。我们发布的开源代码按上述条款授权给您使用。
## 责任限制
本网站与免费工具按"现状"及"现有"提供。在法律允许的最大范围内,对于因使用或无法使用它们而产生的任何间接或后果性损失,我们不承担责任。
本条款不排除依法不得排除的责任。
## 条款变更
随着网站与工具的调整,我们可能会更新本条款。页面顶部的日期为最近一次修订时间。若变更后您继续使用本网站,即视为接受修订后的条款。
## 联系方式
**legal@onixbyte.com**
长沙曜珀科技有限公司
中国湖南省
-18
View File
@@ -1,18 +0,0 @@
---
title: 企业动态
---
## Microsoft 邮件投递问题(@outlook、@hotmail、@live、@msn 以及托管在 Microsoft 的自定义域名邮箱)
> 更新于 _2026 年 5 月 19 日_
**使用 Microsoft 托管邮箱的用户需立即采取行动。**
特此通知,我们的自动邮件服务目前向所有由 Microsoft 托管的邮箱地址投递时遇到严重问题。由于 Microsoft Exchange Online Protection (EOP) 近期采取了激进的过滤策略,我们的邮件投递服务器已被 Microsoft 屏蔽。因此,我们的系统**无法向以下邮箱地址发送任何通知、密码重置或激活邮件**:**@outlook.com**、**@hotmail.com**、**@live.com**、**@msn.com**,以及**任何由 Microsoft 托管自定义域名的邮箱**。
为确保服务不受影响,请根据您的账户状态参考以下指引:
- **新用户(注册前)**:请**不要**使用 Microsoft 托管的邮箱地址注册账户。您将无法收到必要的激活链接。请使用其他邮件服务商,例如您的企业邮箱、QQ 邮箱或网易 163 邮箱完成注册。
- **已注册用户**:如果您的账户当前绑定了 Outlook 或 Hotmail 邮箱地址,请立即登录并更新您的主邮箱。您可以通过 **[用户设置 -> 偏好设置 -> 邮箱](https://github.com/settings/emails)** 页面进行更改。未能及时更新将导致您无法收到重要的仓库更新、CI/CD 流水线失败日志以及合并请求通知。
我们正在积极与 Microsoft Postmaster Centre 沟通,以申请解除此屏蔽。对于由此带来的任何不便,我们深表歉意,并感谢您及时配合更新联系信息。
@@ -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
---
## 包结构概览
库的代码组织在基础包 `com.onixbyte.calendar` 下,结构如下:
| 包 | 描述 |
|---|---|
| `com.onixbyte.calendar` | 根日历对象 |
| `com.onixbyte.calendar.component` | iCalendar 组件(VEVENT、VTODO 等) |
| `com.onixbyte.calendar.component.property` | 组件级属性 |
| `com.onixbyte.calendar.property` | 日历级属性 |
| `com.onixbyte.calendar.parameter` | iCalendar 属性参数 |
| `com.onixbyte.calendar.recurrence` | 重复规则类型 |
| `com.onixbyte.calendar.value` | 值类型 |
| `com.onixbyte.calendar.util` | 格式化工具类 |
---
## Calendar
表示 iCalendar(RFC 5545)VCALENDAR 容器的顶层对象。
```java
Calendar calendar = Calendar.builder()
.withCalendarScale(...)
.withMethod(...)
.withProductIdentifier(...)
.withVersion(...)
.withOwner(...)
.withPrimaryCalendar(...)
.withPublishedTTL(...)
.withCalendarDescription(...)
.withCalendarName(...)
.withCalendarId(...)
.withCustomProperties(...)
.withComponents(event, todo, ...)
.build();
String icsContent = calendar.formatted();
```
### 日历属性
所有日历级属性位于 `com.onixbyte.calendar.property`,实现 `CalendarProperty` 接口。
| 类 | iCalendar 属性 | 描述 |
|---|---|---|
| `CalendarScale` | `CALSCALE` | 日历系统(例如 GREGORIAN) |
| `Method` | `METHOD` | iCalendar 方法(例如 PUBLISH、REQUEST) |
| `ProductIdentifier` | `PRODID` | 日历的产品标识符 |
| `Version` | `VERSION` | iCalendar 版本(例如 2.0) |
| `Owner` | `X-OWNER` | 日历所有者 |
| `PrimaryCalendar` | `X-PRIMARY-CALENDAR` | 是否为主日历 |
| `PublishedTTL` | `X-PUBLISHED-TTL` | 已发布日历的生存时间 |
| `CalendarDescription` | `X-CALENDAR-DESC` | 日历描述 |
| `CalendarName` | `X-CALENDAR-NAME` | 日历名称 |
| `CalendarId` | `X-CALENDAR-ID` | 日历标识符 |
| `CustomCalendarProperty` | `X-*` | 自定义日历属性 |
自定义属性使用 Builder 创建:
```java
var customProp = CustomCalendarProperty.builder()
.withParameters(...)
.build("X-MY-PROPERTY", "my-value");
```
> 注意:自定义属性名称**必须**以 `X-` 开头。
---
## 组件
组件是 iCalendar 对象的核心实体。每个组件实现 `CalendarComponent` 接口,并在 `BEGIN:TYPE` 和 `END:TYPE` 定界符之间渲染。
### Event(VEVENT)
表示一个预定的活动/事件。
```java
Event event = Event.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend) // 与 duration 互斥
.withDuration(duration) // 与 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)
表示待办事项或任务。
```java
Todo todo = Todo.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withSummary(summary)
.withDateTimeStart(dtstart)
.withDateTimeDue(due) // 与 duration 互斥
.withDuration(duration) // 与 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)
表示日记条目或笔记。
```java
Journal journal = Journal.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.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)
表示空闲/忙碌时间信息。
```java
FreeBusy freeBusy = FreeBusy.builder()
.withDateTimeStamp(dtstamp) // 必需
.withUniqueIdentifier(uid) // 必需
.withContact(contact)
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend)
.withOrganiser(organiser)
.withUniformResourceLocator(url)
.withAttendees(attendees)
.withComments(comments)
.withFreeBusyTimes(freeBusyTimes)
.withRequestStatuses(statuses)
.build();
```
### TimeZone(VTIMEZONE)
定义时区规则,包括标准时间和夏令时转换。
```java
TimeZone timezone = TimeZone.builder()
.withTimeZoneIdentifier(tzId)
.withLastModified(lastModified)
.withTimeZoneUrl(tzUrl)
.withTimeZoneProperties(standardProp, daylightProp)
.build();
```
时区属性定义实际的转换规则:
```java
TimeZoneProperty standard = TimeZoneProperty.builder()
.withDateTimeStart(dtstart)
.withTimeZoneOffsetTo(offsetTo)
.withTimeZoneOffsetFrom(offsetFrom)
.withRecurrenceRule(rrule)
.withTimeZoneNames(tzName)
.buildStandard(); // 或 buildDaylight()
TimeZoneProperty daylight = TimeZoneProperty.builder()
.withDateTimeStart(dtstart)
.withTimeZoneOffsetTo(offsetTo)
.withTimeZoneOffsetFrom(offsetFrom)
.buildDaylight();
```
### Alarm(VALARM)
定义闹钟/提醒通知。支持三种闹钟类型:
```java
// 音频闹钟
Alarm audioAlarm = Alarm.builder()
.withTrigger(trigger)
.withDuration(duration)
.withRepeatCount(repeatCount)
.withAttachments(audioAttachment)
.buildAudio();
// 显示闹钟
Alarm displayAlarm = Alarm.builder()
.withTrigger(trigger)
.withDescription(description) // 必需
.withDuration(duration)
.withRepeatCount(repeatCount)
.buildDisplay();
// 邮件闹钟
Alarm emailAlarm = Alarm.builder()
.withTrigger(trigger)
.withDescription(description) // 必需
.withSummary(summary) // 必需
.withAttendees(attendees) // 必需
.withDuration(duration)
.withRepeatCount(repeatCount)
.buildEmail();
```
---
## 组件属性
组件属性位于 `com.onixbyte.calendar.component.property`,实现 `ComponentProperty` 接口。
| 类 | iCalendar 属性 | 描述 |
|---|---|---|
| `Action` | `ACTION` | 闹钟操作类型(AUDIO、DISPLAY、EMAIL) |
| `Attachment` | `ATTACH` | 文档附件 |
| `Attendee` | `ATTENDEE` | 事件/任务参与者 |
| `Categories` | `CATEGORIES` | 类别或标签 |
| `Classification` | `CLASS` | 访问分类(PUBLIC、PRIVATE、CONFIDENTIAL) |
| `Comment` | `COMMENT` | 评论 |
| `Contact` | `CONTACT` | 联系信息 |
| `DateTimeCompleted` | `COMPLETED` | 完成日期时间 |
| `DateTimeCreated` | `CREATED` | 创建日期时间 |
| `DateTimeDue` | `DUE` | 截止日期时间 |
| `DateTimeEnd` | `DTEND` | 结束日期时间 |
| `DateTimeStamp` | `DTSTAMP` | 日期时间戳 |
| `DateTimeStart` | `DTSTART` | 开始日期时间 |
| `Description` | `DESCRIPTION` | 描述 |
| `ExceptionDateTimes` | `EXDATE` | 例外日期时间 |
| `FreeBusyTime` | `FREEBUSY` | 空闲/忙碌时间段 |
| `GeographicPosition` | `GEO` | 地理位置(纬度/经度) |
| `LastModified` | `LAST-MODIFIED` | 最后修改日期时间 |
| `Location` | `LOCATION` | 地点 |
| `Organiser` | `ORGANIZER` | 组织者 |
| `PercentComplete` | `PERCENT-COMPLETE` | 完成百分比 |
| `Priority` | `PRIORITY` | 优先级 |
| `RecurrenceDateTimes` | `RDATE` | 重复日期时间 |
| `RecurrenceId` | `RECURRENCE-ID` | 重复实例标识符 |
| `RecurrenceRule` | `RRULE` | 重复规则 |
| `RelatedTo` | `RELATED-TO` | 相关组件引用 |
| `RepeatCount` | `REPEAT` | 闹钟重复次数 |
| `RequestStatus` | `REQUEST-STATUS` | 请求状态 |
| `Resources` | `RESOURCES` | 资源 |
| `SequenceNumber` | `SEQUENCE` | 序列号(修订版本) |
| `Status` | `STATUS` | 组件状态(TENTATIVE、CONFIRMED、CANCELLED 等) |
| `Summary` | `SUMMARY` | 标题或摘要 |
| `TimeTransparency` | `TRANSP` | 时间透明度(OPAQUE、TRANSPARENT) |
| `TimeZoneIdentifier` | `TZID` | 时区标识符 |
| `TimeZoneName` | `TZNAME` | 时区名称 |
| `TimeZoneOffsetFrom` | `TZOFFSETFROM` | 相对于 UTC 的时区偏移(原) |
| `TimeZoneOffsetTo` | `TZOFFSETTO` | 相对于 UTC 的时区偏移(目标) |
| `TimeZoneUrl` | `TZURL` | 时区 URL |
| `Trigger` | `TRIGGER` | 闹钟触发器 |
| `UniformResourceLocator` | `URL` | 关联 URL |
| `UniqueIdentifier` | `UID` | 唯一标识符 |
| `CustomComponentProperty` | `X-*` | 自定义组件属性 |
---
## 参数
参数位于 `com.onixbyte.calendar.parameter`,为组件属性提供额外限定。
| 类 | iCalendar 参数 | 描述 |
|---|---|---|
| `AlarmTriggerRelationship` | `RELATED` | 触发器关联(START、END) |
| `AlternateTextRepresentation` | `ALTREP` | 替代文本 URI |
| `CalendarUserType` | `CUTYPE` | 日历用户类型(INDIVIDUAL、GROUP、RESOURCE 等) |
| `CommonName` | `CN` | 通用名称 |
| `Delegatees` | `DELEGATED-TO` | 受托人 |
| `Delegators` | `DELEGATED-FROM` | 委托人 |
| `DirectoryEntryReference` | `DIR` | 目录条目 URI |
| `FormatType` | `FMTTYPE` | 格式类型(MIME 类型) |
| `FreeBusyTimeType` | `FBTYPE` | 空闲/忙碌时间类型 |
| `InlineEncoding` | `ENCODING` | 内联编码(BASE64) |
| `Language` | `LANGUAGE` | 语言 |
| `Membership` | `MEMBER` | 组成员 |
| `ParticipationRole` | `ROLE` | 参与角色(CHAIR、REQ-PARTICIPANT 等) |
| `ParticipationStatus` | `PARTSTAT` | 参与状态(ACCEPTED、DECLINED 等) |
| `RecurrenceIdentifierRange` | `RANGE` | 重复范围(THISANDPRIOR、THISANDFUTURE) |
| `RelationshipType` | `RELTYPE` | 关系类型(PARENT、CHILD、SIBLING) |
| `RsvpExpectation` | `RSVP` | RSVP 期望(TRUE、FALSE) |
| `SentBy` | `SENT-BY` | 发送者 |
| `TimeZoneIdentifier` | `TZID` | 时区标识符 |
| `ValueDataType` | `VALUE` | 值数据类型(DATE、DATE-TIME 等) |
---
## 递归类型
位于 `com.onixbyte.calendar.recurrence`。
| 类 | 描述 |
|---|---|
| `Frequency` | 重复频率常量(DAILY、WEEKLY、MONTHLY、YEARLY),用于重复规则 |
| `WeekdayNum` | 重复规则的星期序号(例如第 2 个星期一) |
---
## 值类型
位于 `com.onixbyte.calendar.value`。
| 类 | 描述 |
|---|---|
| `FreeBusyTimeValue` | 表示空闲/忙碌时间值 |
| `PeriodOfTime` | 表示一个时间段,包含开始和结束 |
| `PropertyValue` | 属性的基础值类型 |
| `UtcOffset` | UTC 偏移值(例如 `-05:00`、`+01:00`) |
---
## 格式化输出
所有组件和属性都提供 `formatted()` 方法,返回符合 iCalendar 规范的字符串。`Calendar.formatted()` 方法生成完整的 `.ics` 输出,包含 `BEGIN:VCALENDAR`/`END:VCALENDAR`。
```java
String ics = calendar.formatted();
// BEGIN:VCALENDAR
// CALSCALE:GREGORIAN
// METHOD:PUBLISH
// ...
// END:VCALENDAR
```
@@ -1,102 +0,0 @@
---
title: Calendar Toolbox
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Calendar Toolbox 是一个 Java 库,旨在简化遵循 iCalendar 规范(RFC 5545)的 `.ics` 文件的创建。该库为开发者提供了简洁的 API,可生成能够导入到 Microsoft Outlook、Google Calendar 和 Apple Calendar 等主流日历应用程序中的日历事件。
通过 Calendar Toolbox,您可以以编程方式轻松创建、定制和导出日历事件文件,从而以最少的代价将日历功能集成到您的 Java 应用中。
## 特性
- **完整 RFC 5545 兼容** — 支持 VEVENT、VTODO、VJOURNAL、VFREEBUSY、VTIMEZONE 和 VALARM 等标准 iCalendar 组件。
- **流式 Builder 模式** — 所有组件和属性均使用 Builder 模式,构建过程可读性强且直观。
- **类型安全属性** — 强类型的属性和参数确保编译期正确性。
- **自定义扩展** — 支持日历级和组件级的自定义 `X-` 属性。
- **轻量级** — 核心功能零外部依赖。
## 安装
<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>
## 快速开始
```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) {
// 创建所需的日期时间属性
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.");
// 构建事件
var event = Event.builder()
.withDateTimeStamp(dtstamp)
.withUniqueIdentifier(uid)
.withDateTimeStart(dtstart)
.withDateTimeEnd(dtend)
.withSummary(summary)
.withDescription(description)
.build();
// 构建日历并输出 .ics 内容
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());
}
}
```
上述代码会生成一个 `.ics` 格式的字符串,可以保存为文件或通过邮件发送。
## 许可证
Calendar Toolbox 是采用 MIT 许可证发布的开源软件。
@@ -1,248 +0,0 @@
---
title: Captcha API
---
## 包结构概览
库的代码组织在基础包 `com.onixbyte.captcha` 下,结构如下:
| 包 | 描述 |
|---|---|
| `com.onixbyte.captcha` | 核心接口 |
| `com.onixbyte.captcha.impl` | 默认实现 |
| `com.onixbyte.captcha.text` | 文本生成器和渲染器接口 |
| `com.onixbyte.captcha.text.impl` | 文本生成器和渲染器实现 |
| `com.onixbyte.captcha.text.enums` | 文本相关枚举 |
| `com.onixbyte.captcha.background` | 背景生成器接口 |
| `com.onixbyte.captcha.background.impl` | 背景生成器实现 |
| `com.onixbyte.captcha.noise` | 噪点生成器接口 |
| `com.onixbyte.captcha.noise.impl` | 噪点生成器实现 |
| `com.onixbyte.captcha.gimpy` | 扭曲引擎接口 |
| `com.onixbyte.captcha.gimpy.impl` | 扭曲引擎实现 |
---
## Producer
负责创建 CAPTCHA 图片并在其上绘制文字的顶层接口。
```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
| 方法 | 默认值 | 描述 |
|---|---|---|
| `textProducer` | `DefaultTextProducer.builder().build()` | 文本生成器 |
| `wordRenderer` | `DefaultWordRenderer.builder().build()` | 文字渲染器 |
| `gimpyEngine` | `WaterRipple.builder().build()` | 扭曲引擎 |
| `backgroundProducer` | `DefaultBackgroundProducer.builder().build()` | 背景生成器 |
| `width` | `200` | 验证码图片宽度 |
| `height` | `50` | 验证码图片高度 |
| `borderDrawn` | `true` | 是否绘制边框 |
| `borderColour` | `Color.BLACK` | 边框颜色 |
| `borderThickness` | `1` | 边框厚度 |
---
## Text
### TextProducer
用于创建 CAPTCHA 文本字符串的接口。
```java
public interface TextProducer {
String getText();
}
```
#### DefaultTextProducer
生成可配置长度和字符集的随机文本。
```java
DefaultTextProducer textProducer = DefaultTextProducer.builder()
.length(6) // 默认: 6
.chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()) // 默认: a-z, A-Z, 0-9
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `length` | `6` | 生成文本的长度 |
| `chars` | `a-z`、`A-Z`、`0-9`(共 62 个字符) | 生成文本使用的字符集 |
### WordRenderer
将文字渲染到图片上的接口。
```java
public interface WordRenderer {
BufferedImage renderWord(String word, int width, int height);
}
```
#### DefaultWordRenderer
使用可配置的字体、颜色和间距渲染文字。
```java
DefaultWordRenderer wordRenderer = DefaultWordRenderer.builder()
.fontSize(40) // 默认: 40
.fonts("Arial", "Courier") // 默认: Arial, Courier
.fontColour(Color.BLACK) // 默认: Color.BLACK
.charSpace(2) // 默认: 2
.fontStyle(FontStyle.BOLD) // 默认: FontStyle.BOLD
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `fontSize` | `40` | 字号(像素) |
| `fonts` | `["Arial", "Courier"]` | 字体族(随机选择) |
| `fontColour` | `Color.BLACK` | 字体颜色 |
| `charSpace` | `2` | 字符间距(像素) |
| `fontStyle` | `FontStyle.BOLD` | 字体样式 |
### FontStyle
定义 CAPTCHA 文字渲染支持的字体样式。
| 值 | 对应的 AWT 常量 | 描述 |
|---|---|---|
| `PLAIN` | `java.awt.Font.PLAIN` | 普通样式 |
| `BOLD` | `java.awt.Font.BOLD` | 粗体 |
| `ITALIC` | `java.awt.Font.ITALIC` | 斜体 |
| `BOLD_ITALIC` | `java.awt.Font.BOLD | Font.ITALIC` | 粗斜体 |
---
## GimpyEngine
用于对图片应用扭曲效果的接口。
```java
public interface GimpyEngine {
BufferedImage getDistortedImage(BufferedImage baseImage);
}
```
### WaterRipple
应用水波纹扭曲效果。继承 `AbstractGimpyEngine`,自动添加噪点。
```java
WaterRipple gimpy = WaterRipple.builder()
.noiseProducer(DefaultNoiseProducer.builder().build()) // 默认: DefaultNoiseProducer
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | 噪点生成器 |
### FishEyeGimpy
应用鱼眼扭曲效果,在图片上绘制水平和垂直线条。
```java
FishEyeGimpy gimpy = FishEyeGimpy.builder()
.build();
```
### ShadowGimpy
应用阴影和波纹效果。继承 `AbstractGimpyEngine`,自动添加噪点。
```java
ShadowGimpy gimpy = ShadowGimpy.builder()
.noiseProducer(DefaultNoiseProducer.builder().build()) // 默认: DefaultNoiseProducer
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `noiseProducer` | `DefaultNoiseProducer.builder().build()` | 噪点生成器 |
---
## Noise
### NoiseProducer
向图片添加噪点的接口。
```java
public interface NoiseProducer {
void makeNoise(BufferedImage image, float factorOne, float factorTwo,
float factorThree, float factorFour);
}
```
#### DefaultNoiseProducer
使用可配置的颜色添加噪点曲线。
```java
DefaultNoiseProducer noise = DefaultNoiseProducer.builder()
.noiseColour(Color.BLACK) // 默认: Color.BLACK
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `noiseColour` | `Color.BLACK` | 噪点颜色 |
#### NoNoiseProducer
不向图片添加任何噪点的空实现。
```java
NoiseProducer noise = NoNoiseProducer.builder()
.build();
```
---
## Background
### BackgroundProducer
向图片添加背景的接口。
```java
public interface BackgroundProducer {
BufferedImage addBackground(BufferedImage image);
}
```
#### DefaultBackgroundProducer
创建具有可配置起始和结束颜色的渐变背景。
```java
DefaultBackgroundProducer background = DefaultBackgroundProducer.builder()
.colourFrom(Color.WHITE) // 默认: Color.LIGHT_GRAY
.colourTo(Color.LIGHT_GRAY) // 默认: Color.WHITE
.build();
```
| 方法 | 默认值 | 描述 |
|---|---|---|
| `colourFrom` | `Color.LIGHT_GRAY` | 渐变起始颜色 |
| `colourTo` | `Color.WHITE` | 渐变结束颜色 |
@@ -1,89 +0,0 @@
---
title: Captcha
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Captcha 是一个用于生成 CAPTCHA 验证码图片的 Java 库。本项目是 Google Kaptcha 的精神继承者,按照现代开发实践进行了现代化改造。
通过 Captcha,您可以轻松生成可定制的 CAPTCHA 图片,支持多种扭曲效果、噪点和背景选项,轻松集成到您的 Java 应用中。
## 特性
- **易于集成** — 基于 Builder 的简洁 API,快速完成配置和集成。
- **可定制的验证码生成** — 配置文本长度、字符集、字体样式和颜色。
- **多种扭曲效果** — 支持水波纹、鱼眼和阴影效果。
- **可配置噪点** — 使用自定义颜色添加噪点线条,或完全禁用噪点。
- **渐变背景** — 可自定义的渐变背景颜色。
- **简洁现代的代码** — 文档完善、类型安全且易于扩展。
## 安装
<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>
## 快速开始
```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 {
// 使用默认配置创建 CAPTCHA 生产者
Producer captcha = DefaultCaptchaProducer.builder().build();
// 生成文本并创建图片
String captchaText = captcha.createText();
BufferedImage captchaImage = captcha.createImage(captchaText);
// 保存图片到文件
ImageIO.write(captchaImage, "png", new File("captcha.png"));
System.out.println("CAPTCHA 文本: " + captchaText);
}
}
```
以上代码生成一张包含 6 位随机字符的 CAPTCHA 图片,并将其保存为 PNG 文件。
## 许可证
Captcha 是采用 MIT 许可证发布的开源软件。
@@ -1,216 +0,0 @@
---
title: Claude Code Skills Manager
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Claude Code Skills Manager(`ccsm`)是一个 CLI 工具,用于从基于 Git 的 Marketplace 仓库中发现、安装和管理 Claude Code Skills。你可以将任何公开的、包含 `skills.json` 清单的 Git 仓库注册为 Skill 来源,然后通过统一的命令行界面搜索、安装、更新和移除 Skills。
Skills 会被直接部署到 Claude Code 自身的 Skills 目录(`~/.claude/skills/`)中,Claude Code 启动时会自动加载。
## 安装
<Tabs>
<Tab label="预编译二进制">
从 [Releases 页面](https://onixbyte.dev/onixbyte/claude-code-skill-manager/releases) 下载对应平台的最新二进制文件。
| 平台 | 二进制文件 |
|------|-----------|
| Linux (amd64) | `ccsm-linux-amd64` |
| macOS (amd64) | `ccsm-darwin-amd64` |
| Windows (amd64) | `ccsm-windows-amd64.exe` |
将二进制文件放到 `PATH` 中的某个目录,并重命名为 `ccsm`(Windows 上为 `ccsm.exe`)。
</Tab>
<Tab label="Cargo 安装">
```bash
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
cd claude-code-skill-manager
cargo install --path .
```
需要 Rust 工具链(通过 [rustup.rs](https://rustup.rs) 安装)。
</Tab>
<Tab label="从源码构建">
```bash
git clone https://onixbyte.dev/onixbyte/claude-code-skill-manager.git
cd claude-code-skill-manager
cargo build --release
```
二进制文件位于 `target/release/ccsm`(Windows 上为 `ccsm.exe`)。
</Tab>
</Tabs>
## 快速开始
### 注册 Marketplace
Marketplace 可以是任何在根目录包含 `skills.json` 清单的公开 Git 仓库。通过以下命令注册:
```bash
ccsm marketplace add https://github.com/example/skills
```
CCSM 会拉取清单并缓存到本地。你可以根据需要注册任意数量的 Marketplace。
### 浏览和安装 Skills
```bash
# 搜索所有已注册 Marketplace 中的 Skills
ccsm search
# 通过关键词过滤
ccsm search git
# 按名称安装 Skill
ccsm install my-awesome-skill
# 如果多个 Marketplace 中存在同名 Skill,可指定 Marketplace
ccsm install my-skill --marketplace my-marketplace
```
### 管理已安装的 Skills
```bash
# 列出已安装的 Skills
ccsm list
# 查看已安装 Skill 的详细信息
ccsm info my-awesome-skill
# 更新所有已安装的 Skills 到最新版本
ccsm update
# 更新指定 Skill
ccsm update my-awesome-skill
# 移除 Skill
ccsm remove my-awesome-skill
```
### Marketplace 管理
```bash
# 列出所有已注册的 Marketplace
ccsm marketplace list
# 刷新缓存的清单(所有 Marketplace)
ccsm marketplace update
# 刷新指定 Marketplace
ccsm marketplace update my-marketplace
# 移除 Marketplace
ccsm marketplace remove my-marketplace
```
### 安装模式
CCSM 支持两种部署模式:
| 模式 | 行为 | 适用场景 |
|------|------|----------|
| `copy`(默认) | 将文件物理复制到 `~/.claude/skills/<name>/` | 日常使用 — 安全且自包含 |
| `link` | 创建指向本地存储的符号链接 | Skill 开发 — 对源文件的修改会立即生效 |
可在配置中设置默认模式,也可在安装时单独指定:
```bash
ccsm install my-skill --link
```
## 自建 Skill Marketplace
Marketplace 本质上就是一个包含 `skills.json` 清单的公开 Git 仓库。不需要服务器组件,不需要认证,也不需要 API 密钥 —— CCSM 会直接从 Git 托管平台的 raw 文件端点读取清单。
### Marketplace 仓库结构
```
your-marketplace-repo/
skills.json # 必需:清单文件
README.md # 可选
```
### `skills.json` 清单格式
```json
{
"name": "My Marketplace",
"version": "1.0.0",
"skills": [
{
"name": "my-skill",
"description": "在搜索结果中显示的简短描述",
"repository": "https://github.com/user/skill-repo",
"branch": "main"
},
{
"name": "another-skill",
"description": "位于 monorepo 子目录中的 Skill",
"repository": "https://github.com/user/monorepo",
"branch": "main",
"path": "skills/another-skill"
}
]
}
```
**顶层字段:**
| 字段 | 必需 | 说明 |
|------|------|------|
| `name` | 是 | Marketplace 的显示名称 |
| `version` | 否 | 用于自行追踪的任意版本号 |
| `skills` | 否 | Skill 条目数组(默认为 `[]`) |
**Skill 条目字段:**
| 字段 | 必需 | 说明 |
|------|------|------|
| `name` | 是 | Skill 的唯一标识符(用于 `ccsm install <name>`) |
| `description` | 是 | 在 `ccsm search` 输出中显示的一行摘要 |
| `repository` | 是 | Skill 的公开 Git 仓库 URL |
| `branch` | 否 | 安装时使用的 Git 分支(先尝试 `main`,再回退到 `master`) |
| `path` | 否 | 仓库中 Skill 文件所在的子目录 — 支持单个仓库托管多个 Skills |
### 工作原理
1. **清单拉取** — CCSM 根据 Marketplace 仓库 URL 构建 raw 文件 URL,从 `main` 或 `master` 分支获取 `skills.json`。兼容 GitHub、Gitea、GitLab 以及任何提供 raw 文件端点的 Git 托管平台。
2. **Skill 下载** — 当用户安装 Skill 时,CCSM 从 Git 托管平台下载 Skill 仓库的归档文件(ZIP 或 tar.gz),解压后将文件部署到 `~/.claude/skills/<name>/`。
3. **Monorepo 支持** — 如果 Skill 条目指定了 `path`,CCSM 会在解压后进入该子目录再进行部署,从而支持单个仓库托管多个 Skills。
### Skill 仓库的内容
每个 Skill 仓库都是一个标准的 Claude Code Skill 目录。至少需要包含一个 `SKILL.md` 清单文件:
```
my-skill-repo/
SKILL.md # Claude Code Skill 清单
script.py # 辅助文件
templates/
template.txt
```
### 发布步骤
1. 创建一个**公开**的 Git 仓库,在根目录放置符合上述格式的 `skills.json`。
2. 推送上去。
3. 任何人都可以通过 `ccsm marketplace add <url>` 注册使用。
## 数据存储位置
| 路径 | 用途 |
|------|------|
| `~/.claude/skills/` | Claude Code 的 Skills 目录 —— Skills 的部署目标 |
| `~/.config/ccsm/config.json` | CCSM 配置文件(已注册的 Marketplace、默认模式) |
| `~/.config/ccsm/marketplaces/` | 缓存的 Marketplace 清单 |
| `~/.config/ccsm/skills/` | 下载的 Skill 文件(本地存储) |
在 Windows 上,`~/.config/` 对应 `C:\Users\<user>\AppData\Roaming\`。
## 许可证
Claude Code Skills Manager 是采用 MIT 许可证发布的开源软件。
@@ -1,91 +0,0 @@
---
title: Delta Force Guide API
---
## 后端 API 概览
后端是基于 Spring Boot 3 的应用程序,代码组织在基础包 `com.onixbyte.deltaforceguide` 下:
| 包 | 描述 |
|---|---|
| `controller` | REST API 端点控制器 |
| `service` | 业务逻辑层 |
| `repository` | MyBatis 数据访问层 |
| `domain` | JPA 实体与领域对象 |
| `config` | Spring 配置(安全、缓存等) |
| `properties` | 配置属性类 |
| `enumeration` | 枚举类型 |
| `shared` | 共享工具类 |
## API 端点
### 认证
| 方法 | 路径 | 描述 | 认证 |
|---|---|---|---|
| `POST` | `/api/auth/login` | 使用凭证登录 | 否 |
| `POST` | `/api/auth/refresh` | 刷新 access token | 否 |
| `GET` | `/api/auth/profile` | 获取当前用户信息 | 是 |
### 枪械
| 方法 | 路径 | 描述 |
|---|---|---|
| `GET` | `/api/firearms` | 枪械列表(分页,可按类型筛选) |
| `GET` | `/api/firearms/:id` | 按 ID 获取枪械详情 |
| `POST` | `/api/firearms` | 创建新枪械 |
| `PUT` | `/api/firearms/:id` | 更新枪械信息 |
| `DELETE` | `/api/firearms/:id` | 删除枪械 |
### 改装
| 方法 | 路径 | 描述 |
|---|---|---|
| `GET` | `/api/modifications` | 改装列表(分页,可按枪械和标签筛选) |
| `GET` | `/api/modifications/:id` | 按 ID 获取改装详情 |
| `POST` | `/api/modifications` | 创建改装 |
| `POST` | `/api/modifications/batch` | 批量创建改装 |
| `PUT` | `/api/modifications/:id` | 更新改装 |
| `DELETE` | `/api/modifications/:id` | 删除改装 |
| `DELETE` | `/api/modifications/batch-delete` | 批量删除改装 |
### 标签
| 方法 | 路径 | 描述 |
|---|---|---|
| `GET` | `/api/tags` | 获取所有标签 |
## 前端路由
| 路径 | 布局 | 页面 | 描述 |
|---|---|---|---|
| `/` | 主布局 | 枪械浏览 | 首页 — 枪械列表 |
| `/firearms` | 主布局 | 枪械浏览 | 枪械详情与搜索 |
| `/mod-codes` | 主布局 | 改装代码 | 改装代码库 |
| `/legal` | 主布局 | 法律信息 | 法律与隐私信息 |
| `/login` | 空白布局 | 登录 | 用户认证页面 |
## 前端状态管理
Redux Toolkit 结合 Redux Persist 管理以下状态:
- **认证状态** — Access token、refresh token、用户信息
- **设置状态** — 用户偏好,持久化至本地存储
## API 客户端
前端使用基于 Axios 封装的共享 `WebClient`,提供以下能力:
- 基础 URL 配置(指向后端 API)
- 通过请求拦截器自动注入 JWT access token
- 在收到 401 响应时自动刷新 token
## 基础设施
| 组件 | 技术 |
|---|---|
| 数据库 | PostgreSQL(使用 Flyway 进行数据库迁移) |
| 缓存 | Redis(会话和 token 存储) |
| 文件存储 | AWS S3(枪械图片) |
| 容器化 | Docker |
| 部署 | Docker Compose + Nginx |
@@ -1,48 +0,0 @@
---
title: Delta Force Guide
---
import { Tabs, Tab } from "@rspress/core/theme"
## 介绍
Delta Force Guide 是一个全栈 Web 应用,为游戏 Delta Force(三角洲部队)提供可搜索的枪械改装代码库。项目包含两个组件:
- **Web 前端** — 基于 React 的单页应用,用于浏览、筛选和复制改装代码。
- **后端服务** — 基于 Spring Boot 的 REST API,处理数据持久化、身份认证和内容管理。
线上站点位于 **[dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)**。
## 特性
- **枪械浏览** — 浏览精选的游戏内枪械列表及详细属性。
- **改装代码库** — 按武器、模式和标签搜索筛选改装代码。
- **一键复制** — 直接从界面复制改装代码。
- **高效渲染** — 通过窗口虚拟化实现大列表的流畅渲染。
- **身份认证** — 支持用户登录,采用 JWT 认证和验证码校验。
- **后台管理** — 枪械与改装数据完整的增删改查功能。
- **静态友好** — 前端以静态站点部署,通过 API 代理与后端通信。
## 架构
| 组件 | 技术栈 |
|---|---|
| **前端** | React 19、TypeScript、Tailwind CSS 4、Ant Design 6、React Router 7、Redux Toolkit、TanStack Virtual |
| **后端** | Spring Boot 3、Java 21、PostgreSQL、MyBatis、Flyway、Redis |
| **认证** | JWT(access 和 refresh token)、验证码集成 |
| **存储** | AWS S3(图片上传) |
前端是由 Vite 构建的静态站点,通过 API 与 Spring Boot 后端通信。认证采用 JWT access 和 refresh token 机制,refresh token 通过 Redis 管理。
## 代码仓库
- [delta-force-guide-server](https://github.com/onixbyte/delta-force-guide-server) — Spring Boot 后端
- [delta-force-guide-web](https://github.com/onixbyte/delta-force-guide-web) — React 前端
## 快速链接
- **线上站点:** [dfguide.onixbyte.cn](https://dfguide.onixbyte.cn)
## 许可证
Delta Force Guide 是采用 MIT 许可证发布的开源软件。
@@ -1,73 +0,0 @@
---
title: Helix
---
## 介绍
Helix 是一个全栈企业级应用模板,旨在加速生产级业务应用的开发。它提供了预构建的基础,包含企业应用所需的全部基本功能,使团队能够专注于业务逻辑而非基础设施。
项目由两个组件构成:
- **Helix Server** — 基于 Spring Boot 的后端,提供 REST API、身份认证、权限管理和数据管理。
- **Helix Web** — 基于 React 的单页应用前端,具备现代化的 UI、基于角色的路由和企业级身份集成。
Helix 旨在作为项目的起点使用。Fork 或克隆它,然后在现成的基础上开始构建您自己的业务功能。
## 特性
### 后端(Helix Server)
- **身份认证与授权** — 基于 JWT 的 access token 和 refresh token 流程,配合 Spring Security。支持 Microsoft Entra ID(Azure AD)集成。
- **基于角色的访问控制** — 通过角色、权限和菜单级访问控制实现细粒度权限管理。
- **用户与组织管理** — 用户、部门、职位和角色的完整增删改查。
- **菜单管理** — 层级菜单树,支持按角色配置可见性。
- **资产管理** — 文件上传至 AWS S3,支持 CDN 前缀处理。
- **设置管理** — 类型化的应用配置项持久化至数据库。
- **验证码集成** — 登录和注册流程中的图形验证码校验。
- **Redis 缓存** — 高性能缓存,支持可配置 TTL 和自定义序列化器。
- **数据访问** — Spring Data JPA 与 MyBatis 混合持久化方案。
- **校验** — Bean Validation 分组校验与自定义校验约束。
### 前端(Helix Web)
- **企业级身份认证** — 通过 MSAL 实现 Microsoft Entra ID 登录。可扩展的社交登录提供商架构(Microsoft、Google、GitLab、Slack、Discord、钉钉、飞书、企业微信、邮箱/密码)。
- **仪表盘布局** — 响应式管理后台,可折叠侧边栏,基于角色的菜单渲染。
- **用户管理** — 数据表格支持搜索、分页,添加/编辑对话框,角色分配和手机号校验。
- **角色与菜单管理** — 角色增删改查与权限分配。基于树形的菜单配置。
- **部门与职位** — 组织层级管理界面。
- **路由保护** — 在页面加载前检查认证和授权状态。
- **状态管理** — Redux Toolkit 配合 Redux Persist 实现认证 token 的持久化。
- **HTTP 客户端** — 基于 Axios 封装的请求客户端,自动注入 JWT token 并在 401 时自动刷新。
## 架构
| 层 | 技术栈 |
| ---------- | ---------------------------------------------------------------------------------------------- |
| **前端** | React 19、TypeScript、Tailwind CSS 4、Ant Design 6、React Router 7、Redux Toolkit、Axios、MSAL |
| **后端** | Spring Boot 3.5、Java 21、Spring Security、MyBatis、Spring Data JPA |
| **数据库** | PostgreSQL(使用 Flyway 迁移) |
| **缓存** | Redis(会话和 token 存储) |
| **存储** | AWS S3(资源上传) |
前端通过 RESTful JSON API 与后端通信。认证流程使用短有效期的 JWT access token,refresh token 存储在 Redis 中。Microsoft Entra ID 集成在前端通过 MSAL 处理,并在后端进行校验。
## 代码仓库
Helix 采用单仓库(monorepo)结构,前后端都在同一个仓库中。
- [server/](https://github.com/onixbyte-opensource/helix/tree/main/server) — Spring Boot 后端
- [web/](https://github.com/onixbyte-opensource/helix/tree/main/web) — React 前端
## 快速开始
Helix 是一个模板项目。启动新项目的步骤:
1. Fork 或克隆 [Helix 仓库](https://github.com/onixbyte-opensource/helix)。
2. 在后端配置您的数据库、Redis 和 S3 设置。
3. 在前端配置您的认证提供商和 API 基础地址。
4. 启动后端:`./gradlew bootRun`
5. 启动前端:`pnpm install && pnpm dev`
## 许可证
Helix 是采用 MIT 许可证发布的开源软件。
-25
View File
@@ -1,25 +0,0 @@
---
title: 开源生态
---
由 OnixByte 开发的开源项目。
## 库和工具包
- **[OnixByte Toolbox](/opensource-projects/onixbyte-toolbox)** — 面向 Java 应用的开发工具包:AES、哈希、Base64、集合工具、密钥加载器、统计计算器、标识符生成器和泛型元组。
- **[regions4j](/opensource-projects/regions4j)** — 基于 ISO 3166-1 的类型安全 Java 枚举地区元数据,支持 O(1) 查找、E.164 拨号前缀和区域标签。
- **[Captcha](/opensource-projects/captcha)** — 现代化的 Java 验证码生成库,支持水波纹、鱼眼和阴影扭曲效果。
- **[Calendar Toolbox](/opensource-projects/calendar-toolbox)** — 遵循 iCalendar 规范(RFC 5545)创建 `.ics` 文件的 Java 库。
## 全栈应用
- **[Helix](/opensource-projects/helix)** — 企业级应用模板,后端使用 Spring Boot,前端使用 React,集成 JWT 认证、RBAC 权限管理、用户与组织管理以及 Microsoft Entra ID 集成。
- **[Delta Force Guide](/opensource-projects/delta-force-guide)** — 为游戏《三角洲行动》提供可搜索枪械改装代码库的 Web 应用,包含 React 单页应用和 Spring Boot REST API。
## 开发者工具
- **[Claude Code Skills Manager](/opensource-projects/claude-code-skill-manager)** — 用于从基于 Git 的 Marketplace 仓库中发现、安装和管理 Claude Code Skills 的 CLI 工具。
## Vite 插件
- **[Port Checker](/opensource-projects/vite-plugins/port-checker)** — 当 Vite 开发服务器监听浏览器限制的端口时发出警告,避免无响应的连接失败。
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more