Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a63074a65
|
||
|
|
e149401346
|
||
|
|
f51407b98b
|
||
|
|
9d095eba1b
|
||
|
|
d21b8f6b2f
|
||
|
|
164c319ac1
|
||
|
|
861852961f
|
||
|
|
6800f7251d
|
||
|
|
c516341fae
|
||
|
|
28f07677f3
|
No files matched your search
@@ -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 }} />
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -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/"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
[
|
|
||||||
"index",
|
|
||||||
{ "type": "section-header", "label": "insights.group.frontend" },
|
|
||||||
{ "type": "file", "name": "ecmascript-2025-syntax-sugar" },
|
|
||||||
{ "type": "file", "name": "frontend-development-standards" },
|
|
||||||
{ "type": "file", "name": "frontend-tips-and-solutions" },
|
|
||||||
{ "type": "file", "name": "font-size-conversion-table" },
|
|
||||||
{ "type": "section-header", "label": "insights.group.backend" },
|
|
||||||
{ "type": "file", "name": "java-development-standards" },
|
|
||||||
{ "type": "file", "name": "java-development-cheatsheet" },
|
|
||||||
{ "type": "file", "name": "mybatis-flex" },
|
|
||||||
{ "type": "file", "name": "postgresql-zhparser-fuzzy-search" },
|
|
||||||
{ "type": "file", "name": "use-json-in-mysql" },
|
|
||||||
{ "type": "section-header", "label": "insights.group.devops" },
|
|
||||||
{ "type": "file", "name": "docker-deployment-standards" },
|
|
||||||
{ "type": "file", "name": "gitlab-ops" },
|
|
||||||
{ "type": "file", "name": "setup-ldap-service" },
|
|
||||||
{ "type": "file", "name": "minio-admin-guide" },
|
|
||||||
{ "type": "section-header", "label": "insights.group.practice" },
|
|
||||||
{ "type": "file", "name": "general-development-standards" },
|
|
||||||
{ "type": "file", "name": "google-code-review-standards" },
|
|
||||||
{ "type": "file", "name": "version-control-and-code-review" },
|
|
||||||
{ "type": "file", "name": "git-cheatsheet" },
|
|
||||||
{ "type": "section-header", "label": "insights.group.tooling" },
|
|
||||||
{ "type": "file", "name": "fix-idea-application-yaml-schema-validation" },
|
|
||||||
{ "type": "file", "name": "windows-driver-backup-restore" },
|
|
||||||
{ "type": "file", "name": "fix-macos-monterey-sleep-wake" },
|
|
||||||
{ "type": "file", "name": "macos-private-dns-hostname" },
|
|
||||||
{ "type": "file", "name": "quartile-method" },
|
|
||||||
{ "type": "file", "name": "use-of-gravatar" },
|
|
||||||
{ "type": "file", "name": "email-like-a-boss" },
|
|
||||||
{ "type": "file", "name": "cooking-seasoning-guide" }
|
|
||||||
]
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: Cooking Seasoning Guide
|
|
||||||
tags:
|
|
||||||
- cooking
|
|
||||||
- cheatsheet
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## When to Add Seasonings
|
|
||||||
|
|
||||||
| Seasoning Type | Timing | Suitable Dishes | Effect |
|
|
||||||
|----------------------------------------|----------------------------------------|------------------------------------------------------|-------------------------------------------------------------------------------|
|
|
||||||
| **Salt** | Last | Leafy greens | Prevents leaves from wilting and releasing water, keeps them crisp and tender |
|
|
||||||
| | Mid-way | Shredded potatoes, green beans, garlic shoots | Better flavour absorption |
|
|
||||||
| **MSG, Light Soy Sauce, Oyster Sauce** | Towards the end | Dishes needing umami and aroma enhancement | Adds umami and aroma; high heat destroys freshness and fragrance |
|
|
||||||
| **Cooking Wine** | Together with ingredients | Ingredients needing deodorisation | Better deodorisation when blanching |
|
|
||||||
| | At highest wok heat | Stir-fried ingredients needing deodorisation | High heat accelerates alcohol evaporation, effective aroma searing |
|
|
||||||
| **Vinegar** | Early | Hot and sour shredded potatoes, hot and sour cabbage | Makes shredded potatoes crisper and more flavourful |
|
|
||||||
| | Drizzle around wok edge before serving | Stir-fried chilli pork and similar high-heat dishes | High-heat searing enhances aroma |
|
|
||||||
| **Dark Soy Sauce** | After half-cooked | Dishes needing colour | Achieves optimal colouring effect |
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
| Seasoning | Timing | Effect | Example |
|
|
||||||
|-----------------------------------------------|------------------------------|-----------------------------------------------------------------------------|---------------------------------------------------------------------------|
|
|
||||||
| **Salt** | 10–15 seconds before serving | Prevents premature water loss and wilting of leafy greens; keeps them crisp | Sprinkle salt and toss before turning off the heat for lettuce or spinach |
|
|
||||||
| **Minced Garlic / Ginger** | First, after oil is hot | Releases fragrance and enhances flavour | Sauté minced garlic in hot oil, then add greens |
|
|
||||||
| **Light Soy Sauce / Oyster Sauce** (optional) | After salt | Adds umami; use sparingly to avoid overpowering the vegetables | Add 1–2 drops of light soy sauce after salting Shanghai greens |
|
|
||||||
| **Cooking Oil** | Hot wok, hot oil | Quick stir-fry at high heat locks in moisture | Use slightly more oil and high heat for fast stir-frying |
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: Docker Deployment Standards
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- deployment
|
|
||||||
- standards
|
|
||||||
- best-practice
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
- **Dockerfiles**: Provide a `Dockerfile` for the application to enable containerised deployment.
|
|
||||||
- **Lightweight Images**: Strive for lightweight Docker images by using appropriate base images and multi-stage builds.
|
|
||||||
- **Configuration**: Ensure environment-specific configuration (e.g., database connection strings, external service
|
|
||||||
URLs) is managed through environment variables injected into Docker containers.
|
|
||||||
- **Logging**: Configure containerised logging to output to `stdout` and `stderr` so that log aggregation systems can
|
|
||||||
collect logs easily.
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
---
|
|
||||||
title: ECMAScript 2025 Syntax Sugar Guide
|
|
||||||
tags:
|
|
||||||
- javascript
|
|
||||||
- ecmascript
|
|
||||||
- pattern-matching
|
|
||||||
- frontend
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pattern Matching
|
|
||||||
|
|
||||||
### Traditional Approach
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function processResponse(response) {
|
|
||||||
if (response.status === 200 && response.data) {
|
|
||||||
return { success: true, data: response.data };
|
|
||||||
} else if (response.status === 404) {
|
|
||||||
return { success: false, error: 'Not found' };
|
|
||||||
} else if (response.status >= 500) {
|
|
||||||
return { success: false, error: 'Server error' };
|
|
||||||
} else {
|
|
||||||
return { success: false, error: 'Unknown error' };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pattern Matching Approach
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function processResponse(response) {
|
|
||||||
return match (response) {
|
|
||||||
when ({ status: 200, data }) -> ({ success: true, data })
|
|
||||||
when ({ status: 404 }) -> ({ success: false, error: 'Not found' })
|
|
||||||
when ({ status: status if status >= 500 }) -> ({ success: false, error: 'Server error' })
|
|
||||||
default -> ({ success: false, error: 'Unknown error' })
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Handling Array Length Branches Gracefully
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function handleArray(arr) {
|
|
||||||
return match (arr) {
|
|
||||||
when ([]) -> "Empty array"
|
|
||||||
when ([first]) -> `Only one element: ${first}`
|
|
||||||
when ([first, second]) -> `Two elements: ${first}, ${second}`
|
|
||||||
when ([first, ...rest]) -> `First element: ${first}, others: ${rest.length} items`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
---
|
|
||||||
title: Email Like a Boss
|
|
||||||
tags:
|
|
||||||
- communication
|
|
||||||
- email
|
|
||||||
- soft-skill
|
|
||||||
- career
|
|
||||||
- productivity
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
Email at work is about more than sharing information — it's about building trust and shaping your professional image.
|
|
||||||
The same message, phrased differently, can leave an entirely different impression.
|
|
||||||
|
|
||||||
Below are 9 common email scenarios, contrasting "low-power" expressions with "high-power" alternatives that make you
|
|
||||||
sound more confident and professional.
|
|
||||||
|
|
||||||
## Scenario Cheatsheet
|
|
||||||
|
|
||||||
| Scenario | ❌ Don't Use (sounds…) | ✅ Use Instead (sounds…) | Why It Works |
|
|
||||||
|----------------------------|----------------------------------------------|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| Late reply | **Sorry for the delay** | **Thanks for your patience** | Swaps an apology for gratitude — the focus shifts from "I was wrong" to "you were generous," acknowledging the delay while making the recipient feel respected |
|
|
||||||
| Scheduling | **What works best for you?** | **Could you do …?** | The former throws the decision entirely back to the other person; the latter offers a concrete option, cutting down on back-and-forth |
|
|
||||||
| After helping someone | **No problem / No worries** | **Always happy to help** | The former implies the task *could* have been a problem; the latter signals you enjoyed it and would happily do it again |
|
|
||||||
| Making a suggestion | **I think maybe we should …** | **It'd be best if we …** | The former oozes hesitation and self-doubt; the latter delivers a clear judgment — like someone with experience making a decision |
|
|
||||||
| Text isn't working | **_Spending 30 minutes rewriting an email_** | **It'd be easier to discuss in person** | Recognising the medium itself is the bottleneck and switching channels can be the most efficient move |
|
|
||||||
| Checking for understanding | **Hopefully that makes sense?** | **Let me know if you have questions** | The former betrays doubt about your own clarity; the latter calmly shares responsibility — the reader now has an action item too |
|
|
||||||
| Following up on progress | **Just wanted to check in** | **When can I expect an update?** | The former tiptoes around the ask; the latter names the time frame directly — clear, polite, and professional |
|
|
||||||
| Owned a small mistake | **Ahh sorry my bad totally missed that** | **Thanks for letting me know** | Over-apologising makes things awkward; this acknowledges the catch while keeping the focus on moving forward |
|
|
||||||
| Need to leave early | **Could I possibly leave early?** | **I will need to leave at …** | The former asks for permission; the latter states a plan — you're a professional and don't need to apologise for reasonable needs |
|
|
||||||
|
|
||||||
## Core Principles
|
|
||||||
|
|
||||||
Writing great emails is less about vocabulary and more about **stance**. Keep three rules in mind:
|
|
||||||
|
|
||||||
1. **State instead of ask** — "I need…" carries more weight than "Could I possibly…"
|
|
||||||
2. **Thank instead of apologise** — Shift the focus from "my shortcoming" to "their support"
|
|
||||||
3. **Be specific instead of vague** — Offer exact times, options, and action items rather than lobbing the ball back
|
|
||||||
into their court
|
|
||||||
|
|
||||||
Next time you open your inbox, take five seconds to ask: can I phrase this more like someone who makes decisions?
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
---
|
|
||||||
title: Fix IntelliJ IDEA Missing Spring Boot application.yaml Code Completion
|
|
||||||
tags:
|
|
||||||
- intellij-idea
|
|
||||||
- spring-boot
|
|
||||||
- yaml
|
|
||||||
- schema-validation
|
|
||||||
- troubleshooting
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
import { Kbd } from "@rspress/core/theme"
|
|
||||||
|
|
||||||
Recently, while developing a Spring Boot project in IntelliJ IDEA, I noticed that the smart code completion for `application.yaml` and `application.yml` had suddenly vanished. Worse, the editor was flooded with warnings reading:
|
|
||||||
|
|
||||||
> Schema validation: Missing required property 'kind' = 'Application'
|
|
||||||
|
|
||||||
## The Symptom
|
|
||||||
|
|
||||||
When opening any `application.yaml` or `application.yml` under `src/main/resources/`, IDEA would treat it as an Enonic XP application descriptor rather than a Spring Boot configuration file. All Spring-specific property hints were gone, replaced by schema validation errors complaining about a missing `kind` property — a field that has nothing to do with Spring Boot.
|
|
||||||
|
|
||||||
Restarting IDEA, invalidating caches, and re-importing the project made no difference. The problem persisted across restarts.
|
|
||||||
|
|
||||||
## The Quick Fix
|
|
||||||
|
|
||||||
After some digging, I found the culprit. IDEA downloads JSON Schemas from remote sources (specifically, the [SchemaStore](https://www.schemastore.org/) catalogue) and uses them to validate configuration files. A recent schema update had introduced a `fileMatch` pattern that was too aggressive.
|
|
||||||
|
|
||||||
To disable this behaviour immediately:
|
|
||||||
|
|
||||||
1. Open **Settings** (<Kbd>Ctrl</Kbd>+<Kbd>Alt</Kbd>+<Kbd>S</Kbd> for Windows / <Kbd mac="cmd" /> + <Kbd>,</Kbd> for macOS)
|
|
||||||
2. Navigate to **Languages & Frameworks → Schemas and DTDs → Remote JSON Schemas**
|
|
||||||
3. Uncheck **Allow downloading JSON Schemas from remote sources**
|
|
||||||
4. Click **OK**
|
|
||||||
|
|
||||||
Your Spring Boot configuration hints will return instantly.
|
|
||||||
|
|
||||||
## Root Cause
|
|
||||||
|
|
||||||
The real culprit traces back to **19 May 2026**, when a developer from [Enonic](https://www.enonic.com/) submitted a pull request to the SchemaStore repository ([PR #5704](https://github.com/SchemaStore/schemastore/pull/5704)) that added the Enonic XP application descriptor schema. The schema's `fileMatch` property was set to:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"fileMatch": [
|
|
||||||
"**/src/main/resources/application.yaml",
|
|
||||||
"**/src/main/resources/application.yml"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
This pattern is far too broad — it matches **every** `application.yaml` and `application.yml` placed under `src/main/resources/`, which happens to be exactly where Spring Boot projects store their configuration files. Once this schema was merged into SchemaStore, any IntelliJ IDEA instance with remote JSON schema downloading enabled would fetch it and begin validating Spring Boot configs against the Enonic XP schema.
|
|
||||||
|
|
||||||
The developer has since acknowledged the issue and submitted a follow-up fix ([PR #5711](https://github.com/SchemaStore/schemastore/pull/5711)) to tighten the `fileMatch` patterns. As of writing, the fix has not yet been merged into SchemaStore.
|
|
||||||
|
|
||||||
## Why This Matters
|
|
||||||
|
|
||||||
SchemaStore is a community-maintained catalogue of JSON Schemas used by many editors (VS Code, IntelliJ IDEA, Neovim, etc.) to provide validation and autocompletion for common configuration files. When a schema in this catalogue declares overly broad matching rules, it affects users across all platforms and editors — not just IDEA.
|
|
||||||
|
|
||||||
For Spring Boot developers, the takeaway is simple: if your `application.yaml` hints disappear overnight, check what schema your IDE thinks the file belongs to. It might not be Spring Boot at all.
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
---
|
|
||||||
title: Fix macOS Monterey+ Devices Waking Frequently from Sleep
|
|
||||||
tags:
|
|
||||||
- macos
|
|
||||||
- sleep
|
|
||||||
- power-management
|
|
||||||
- bug
|
|
||||||
---
|
|
||||||
|
|
||||||
> This article was originally written by **落格博客
|
|
||||||
**: [落格博客](https://www.logcg.com/) » [Frequent Wake-from-Sleep Issues After Upgrading to macOS Monterey](https://www.logcg.com/archives/3528.html)
|
|
||||||
|
|
||||||
After upgrading to macOS Monterey, my screen kept lighting up in the middle of the night for no apparent reason. It had
|
|
||||||
happened before, but only when notifications came in. Now the screen lights up on its own with no trigger — same
|
|
||||||
hardware, so it must be a software issue.
|
|
||||||
|
|
||||||
After searching online, I first
|
|
||||||
found [Apple's official guide](https://support.apple.com/en-gb/guide/mac-help/mchlp2995/mac). It's very detailed, but
|
|
||||||
clearly of no help whatsoever.
|
|
||||||
|
|
||||||
Digging deeper, I found the root cause. Run **`pmset -g log | grep DarkWake`** and you'll see your Mac hasn't been
|
|
||||||
resting while you slept...
|
|
||||||
|
|
||||||
Several typical patterns appear, most with a DarkWake immediately followed by a Wake. The issue: DarkWake is meant to
|
|
||||||
wake the computer in the background to update data, but somehow a peripheral gets triggered, causing a full system wake.
|
|
||||||
|
|
||||||
In any case, I don't want this feature — Power Nap. For me, I'd rather it save as much power as possible. The fix path:
|
|
||||||
disable network access wake, disable Power Nap... But here's the catch — on M1 devices, there is no Power Nap option in
|
|
||||||
settings. (Clearly, Apple is confident in their battery life but overlooked the power of bugs.)
|
|
||||||
|
|
||||||
So for Power Nap, we have to go through the command line. First, check the current status with **`pmset -g`**. Find the
|
|
||||||
**`powernap`** value — if it isn't 0, it's enabled. Disable it with **`sudo pmset -a powernap 0`**.
|
|
||||||
|
|
||||||
Also check **`tcpkeepalive`** — it likely isn't 0 either and should also be turned off. It controls whether your Mac
|
|
||||||
maintains TCP connections while sleeping. Run **`sudo pmset -a tcpkeepalive 0`** — you'll see a terminal warning:
|
|
||||||
***Warning: This option disables TCP Keep Alive mechanism when system is sleeping. This will result in some critical
|
|
||||||
features like 'Find My Mac' not to function properly.***
|
|
||||||
|
|
||||||
Essentially, turning it off limits some features — the system simply won't connect to the network while asleep. I'm
|
|
||||||
fairly confident that if someone actually steals your Mac, they won't be getting it online anyway.
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
---
|
|
||||||
title: Font Size Conversion Table
|
|
||||||
tags:
|
|
||||||
- typography
|
|
||||||
- font
|
|
||||||
- cheatsheet
|
|
||||||
- design
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
| Chinese Size Name | English Point Size (pt) | mm | px |
|
|
||||||
|--------------------|-------------------------|-------|------|
|
|
||||||
| 初号 (Primary) | 42 | 14.82 | 56 |
|
|
||||||
| 小初 (Small Primary) | 36 | 12.7 | 48 |
|
|
||||||
| 一号 (No. 1) | 26 | 9.17 | 34.7 |
|
|
||||||
| 小一 (Small No. 1) | 24 | 8.47 | 32 |
|
|
||||||
| 二号 (No. 2) | 22 | 7.76 | 29.3 |
|
|
||||||
| 小二 (Small No. 2) | 18 | 6.35 | 24 |
|
|
||||||
| 三号 (No. 3) | 16 | 5.64 | 21.3 |
|
|
||||||
| 小三 (Small No. 3) | 15 | 5.29 | 20 |
|
|
||||||
| 四号 (No. 4) | 14 | 4.94 | 18.7 |
|
|
||||||
| 小四 (Small No. 4) | 12 | 4.23 | 16 |
|
|
||||||
| 五号 (No. 5) | 10.5 | 3.7 | 14 |
|
|
||||||
| 小五 (Small No. 5) | 9 | 3.18 | 12 |
|
|
||||||
| 六号 (No. 6) | 7.5 | 2.56 | 10 |
|
|
||||||
| 小六 (Small No. 6) | 6.5 | 2.29 | 8.7 |
|
|
||||||
| 七号 (No. 7) | 5.5 | 1.94 | 7.3 |
|
|
||||||
| 八号 (No. 8) | 5 | 1.76 | 6.7 |
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
---
|
|
||||||
title: Frontend Development Standards
|
|
||||||
tags:
|
|
||||||
- frontend
|
|
||||||
- react
|
|
||||||
- standards
|
|
||||||
- best-practice
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Dependency Management (pnpm)
|
|
||||||
|
|
||||||
- **Strictness**: Leverage pnpm's strict dependency management to ensure a more deterministic `node_modules` structure
|
|
||||||
and efficient disk space usage.
|
|
||||||
- **Workspaces**: When using a monorepo approach, configure pnpm workspaces to streamline dependency management across
|
|
||||||
multiple frontend packages.
|
|
||||||
- **Auditing**: Regularly audit frontend dependencies for known vulnerabilities using `pnpm audit`.
|
|
||||||
|
|
||||||
## API Communication (Axios)
|
|
||||||
|
|
||||||
- **Axios Instance**: Create a centralised Axios instance for API calls to apply common configuration (base URL,
|
|
||||||
headers, interceptors).
|
|
||||||
- **Interceptors**: Use Axios interceptors to:
|
|
||||||
- Add authentication tokens to outgoing requests.
|
|
||||||
- Handle global error responses (e.g., display a notification for `401 Unauthorized`).
|
|
||||||
- Log requests/responses in development environments.
|
|
||||||
- **Error Handling**: Centralise API error handling in Axios interceptors or custom utility functions to provide
|
|
||||||
consistent user feedback.
|
|
||||||
|
|
||||||
## React and Component Standards
|
|
||||||
|
|
||||||
- **Function Components & Hooks**: Prefer function components with React Hooks over class components for new
|
|
||||||
development.
|
|
||||||
- **Props**:
|
|
||||||
- Define `interface` or `type` for component props to ensure type safety.
|
|
||||||
- Destructure props at the component entry point for clarity.
|
|
||||||
- **State Management (Redux)**:
|
|
||||||
- Use Redux Toolkit for efficient, boilerplate-reduced Redux development.
|
|
||||||
- Use `createSlice` to organise Redux logic into "slices" (feature-specific reducers, actions, and selectors).
|
|
||||||
- Follow the "ducks" pattern or "slices" approach to co-locate Redux logic.
|
|
||||||
- **Component Composition**: Break down complex UIs into smaller, reusable, single-responsibility components.
|
|
||||||
- **Ant Design**:
|
|
||||||
- Leverage Ant Design components for consistent UI/UX.
|
|
||||||
- Use CSS-in-JS solutions to consistently customise Ant Design themes and styles across the application if needed.
|
|
||||||
- **Accessibility**: Design and implement components with web accessibility (a11y) in mind from the start.
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
---
|
|
||||||
title: Frontend Tips and Solutions Cheatsheet
|
|
||||||
tags:
|
|
||||||
- frontend
|
|
||||||
- react
|
|
||||||
- ant-design
|
|
||||||
- tailwind
|
|
||||||
- best-practice
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decoupling Form Components
|
|
||||||
|
|
||||||
Split form components into UI components and logic components. Use the UI components inside the logic components for rendering styles.
|
|
||||||
|
|
||||||
## React Entry Component Order
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import { StrictMode } from "react"
|
|
||||||
import { createRoot } from "react-dom/client"
|
|
||||||
import { Provider as ReduxProvider } from "react-redux"
|
|
||||||
import { PersistGate } from "redux-persist/integration/react"
|
|
||||||
import { RouterProvider } from "react-router"
|
|
||||||
import { App as AntApp, ConfigProvider as AntConfigProvider } from "antd"
|
|
||||||
import { StyleProvider } from "@ant-design/cssinjs"
|
|
||||||
import AntSimplifiedChinese from "antd/locale/zh_CN"
|
|
||||||
import "./index.css"
|
|
||||||
import "@/config/dayjs-config"
|
|
||||||
import store, { persistor } from "@/store"
|
|
||||||
import router from "@/router"
|
|
||||||
|
|
||||||
createRoot(document.getElementById("root")!).render(
|
|
||||||
<StrictMode>
|
|
||||||
<ReduxProvider store={store}>
|
|
||||||
<PersistGate loading={null} persistor={persistor}>
|
|
||||||
{/* Note: StyleProvider must be the parent of ConfigProvider!!! */}
|
|
||||||
<StyleProvider layer>
|
|
||||||
<AntConfigProvider
|
|
||||||
locale={AntSimplifiedChinese}
|
|
||||||
button={{
|
|
||||||
autoInsertSpace: false,
|
|
||||||
}}>
|
|
||||||
<AntApp className="h-full w-full">
|
|
||||||
<RouterProvider router={router} />
|
|
||||||
</AntApp>
|
|
||||||
</AntConfigProvider>
|
|
||||||
</StyleProvider>
|
|
||||||
</PersistGate>
|
|
||||||
</ReduxProvider>
|
|
||||||
</StrictMode>
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integrating Ant Design with Tailwind CSS in React
|
|
||||||
|
|
||||||
> Reference: [https://github.com/ant-design/ant-design/discussions/56152](https://github.com/ant-design/ant-design/discussions/56152)
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* index.css */
|
|
||||||
|
|
||||||
@layer theme, base, antd, components, utilities;
|
|
||||||
@import "../../../node_modules/.pnpm/tailwindcss@4.3.0/node_modules/tailwindcss/dist/lib.d.mts";
|
|
||||||
```
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<StyleProvider>
|
|
||||||
<ConfigProvider>
|
|
||||||
<App>
|
|
||||||
<RouterProvider/>
|
|
||||||
</App>
|
|
||||||
</ConfigProvider>
|
|
||||||
</StyleProvider>
|
|
||||||
```
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
---
|
|
||||||
title: General Application Development Standards
|
|
||||||
tags:
|
|
||||||
- standards
|
|
||||||
- best-practice
|
|
||||||
- engineering
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Introduction
|
|
||||||
|
|
||||||
This document outlines coding standards and best practices for application development. Adhering to these guidelines
|
|
||||||
ensures code quality, maintainability, and consistency across all projects.
|
|
||||||
|
|
||||||
## General Principles
|
|
||||||
|
|
||||||
- **Clarity & Readability**: Code must be easy to read and understand. Prefer clear, self-documenting code over clever,
|
|
||||||
terse, but obscure solutions.
|
|
||||||
- **Consistency**: Maintain a consistent coding style, naming conventions, and architectural patterns across the
|
|
||||||
project.
|
|
||||||
- **Modularity**: Design components with loose coupling and high cohesion to promote reusability and simpler testing.
|
|
||||||
- **Testability**: Write code that is inherently testable.
|
|
||||||
- **Security-First Design**: Incorporate security considerations at every stage of development.
|
|
||||||
- **Performance Awareness**: Be mindful of performance implications for critical code paths and API endpoints.
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
---
|
|
||||||
title: Git Cheatsheet
|
|
||||||
tags:
|
|
||||||
- git
|
|
||||||
- tips
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Ignoring Local Modifications to Tracked Files: `git update-index --skip-worktree`
|
|
||||||
|
|
||||||
When you need to modify a Git-tracked file locally but don't want those changes to show up in `git status` or in commits (e.g. a local `application-local.yml`, `.env`, or IDE configuration), you can use `--skip-worktree` to make Git ignore local modifications to that file.
|
|
||||||
|
|
||||||
### Marking and Unmarking
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Make Git ignore local modifications to the file
|
|
||||||
git update-index --skip-worktree <file>
|
|
||||||
|
|
||||||
# Unmark the file and track it again
|
|
||||||
git update-index --no-skip-worktree <file>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Listing Ignored Files
|
|
||||||
|
|
||||||
Marked files are shown with an `S` flag in `git ls-files -v`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git ls-files -v | grep '^S'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Committing Changes to a Marked File
|
|
||||||
|
|
||||||
To commit new changes to the file, unmark it first, commit, then mark it again:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git update-index --no-skip-worktree <file>
|
|
||||||
git add <file>
|
|
||||||
git commit -m "chore: update config"
|
|
||||||
git update-index --skip-worktree <file>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `--skip-worktree` vs `--assume-unchanged`
|
|
||||||
|
|
||||||
Both options stop Git from reporting modifications to tracked files, but they serve different purposes:
|
|
||||||
|
|
||||||
| Option | Purpose |
|
|
||||||
| -------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
||||||
| `--assume-unchanged` | A performance optimisation for large files that rarely change; Git may re-track the file once it notices a change |
|
|
||||||
| `--skip-worktree` | Deliberately ignore local changes for "keep local modifications, don't commit" scenarios; more persistent |
|
|
||||||
|
|
||||||
So for "ignoring local changes", use `--skip-worktree`.
|
|
||||||
|
|
||||||
### Caveats
|
|
||||||
|
|
||||||
- `--skip-worktree` only affects the working tree — it is not a permanent ignore. Operations that rewrite the file, such as `git pull`, `git checkout`, or `git stash`, may overwrite your local changes or cause a conflict. If the file is also likely to change upstream, unmark it before pulling.
|
|
||||||
- It only applies to **tracked** files; for untracked files, use `.gitignore`.
|
|
||||||
|
|
||||||
## Stashing Unfinished Work: `git stash`
|
|
||||||
|
|
||||||
When you need to switch branches mid-task, use `git stash` to set your working-tree changes aside:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git stash push -m "work in progress" # save with a message
|
|
||||||
git stash -u # include untracked files
|
|
||||||
git stash list # list saved stashes
|
|
||||||
git stash pop # restore and drop the stash
|
|
||||||
git stash apply # restore but keep the stash
|
|
||||||
git stash drop # delete a stash
|
|
||||||
```
|
|
||||||
|
|
||||||
## Amending the Latest Commit: `git commit --amend`
|
|
||||||
|
|
||||||
When you commit and then realise you forgot a file or want to change the message:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add <forgotten-file>
|
|
||||||
git commit --amend # keep the same message, fold in new changes
|
|
||||||
git commit --amend -m "new message" # also change the message
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: if the commit has already been pushed, prefer adding a new commit instead of rewriting shared history.
|
|
||||||
|
|
||||||
## Undoing Changes: `git restore` and `git reset`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git restore <file> # discard unstaged working-tree changes
|
|
||||||
git restore --staged <file> # unstage but keep the changes (like git reset HEAD <file>)
|
|
||||||
git restore --source=HEAD <file> # restore the file to its HEAD version
|
|
||||||
```
|
|
||||||
|
|
||||||
To discard all uncommitted changes (including the staging area):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git reset --hard HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
`--hard` also resets the working tree, so double-check that nothing you want to keep would be lost.
|
|
||||||
|
|
||||||
## Recovering Lost Commits: `git reflog`
|
|
||||||
|
|
||||||
When a commit seems to have vanished after a bad `git reset --hard` or `git rebase`, `reflog` records every place HEAD has been:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git reflog
|
|
||||||
git reset --hard HEAD@{2} # go back to an earlier recorded position
|
|
||||||
```
|
|
||||||
|
|
||||||
As long as the commit still exists in the object database, reflog can get you back to it.
|
|
||||||
|
|
||||||
## Bisecting a Regression: `git bisect`
|
|
||||||
|
|
||||||
When you don't know which commit introduced a bug, `git bisect` uses binary search to locate it:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git bisect start
|
|
||||||
git bisect bad # the current commit is bad
|
|
||||||
git bisect good <sha> # mark a known-good commit
|
|
||||||
```
|
|
||||||
|
|
||||||
Git checks out a midpoint commit; you just mark each as `good` or `bad` until it narrows down the offending commit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git bisect good # or git bisect bad
|
|
||||||
git bisect reset # finish and return to your branch
|
|
||||||
```
|
|
||||||
|
|
||||||
## Friendlier History: `git log`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --graph --decorate --all # graph view across all branches
|
|
||||||
git log -S "some-string" --all # find commits that added/removed a string
|
|
||||||
git blame <file> # show the last modifier of each line
|
|
||||||
git blame -L 10,20 <file> # restrict to a line range
|
|
||||||
```
|
|
||||||
|
|
||||||
## Cleaning Up Untracked Files: `git clean`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clean -n -fd # preview which files would be removed (always run this first)
|
|
||||||
git clean -fd # delete untracked files and directories
|
|
||||||
```
|
|
||||||
|
|
||||||
`-n` only shows what would be deleted without deleting it — preview before you execute.
|
|
||||||
|
|
||||||
## Picking Commits: `git cherry-pick`
|
|
||||||
|
|
||||||
Apply a specific commit from another branch onto your current branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git cherry-pick <sha>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deleting Merged Local Branches
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git branch --merged | grep -v '\*' | xargs -n 1 git branch -d
|
|
||||||
```
|
|
||||||
|
|
||||||
This deletes all local branches merged into the current branch (except the current one). Preview with `git branch --merged` first.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
---
|
|
||||||
title: GitLab Operations
|
|
||||||
tags:
|
|
||||||
- gitlab
|
|
||||||
- devops
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
## Setting the Default Language to Chinese in GitLab
|
|
||||||
|
|
||||||
GitLab filters out languages with less than 90% translation coverage via the `SWITCHER_MINIMUM_TRANSLATION_LEVEL`
|
|
||||||
variable in the code. Simplified Chinese sits at 84% and Traditional Chinese at 83%.
|
|
||||||
|
|
||||||
Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb` and modify
|
|
||||||
the code as follows:
|
|
||||||
|
|
||||||
```diff title="/path/to/gitlab/embedded/service/gitlab-rails/app/helpers/preferred_language_switcher_helper.rb"
|
|
||||||
- SWITCHER_MINIMUM_TRANSLATION_LEVEL = 90
|
|
||||||
+ SWITCHER_MINIMUM_TRANSLATION_LEVEL = 80
|
|
||||||
```
|
|
||||||
|
|
||||||
Then restart GitLab with `gitlab-ctl restart`.
|
|
||||||
|
|
||||||
## Adding ICP Filing Information to the Homepage
|
|
||||||
|
|
||||||
Navigate to `/path/to/gitlab/embedded/service/gitlab-rails/app/views/devise/shared/_footer.html.haml` and add the
|
|
||||||
following:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
+ = link_to _("Your ICP Filing Number"), "https://beian.miit.gov.cn/", target: '_blank', class: 'text-nowrap', rel: 'noopener noreferrer'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Upgrading GitLab
|
|
||||||
|
|
||||||
### Downloading the GitLab CE Package
|
|
||||||
|
|
||||||
Download the deb package from the [GitLab Package page](https://packages.gitlab.com/gitlab/gitlab-ce) and transfer it to
|
|
||||||
the server using `scp` or a similar tool.
|
|
||||||
|
|
||||||
### Stopping Memory-Intensive Services
|
|
||||||
|
|
||||||
```shell
|
|
||||||
gitlab-ctl stop puma
|
|
||||||
gitlab-ctl stop sidekiq
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running a Backup
|
|
||||||
|
|
||||||
```shell
|
|
||||||
# Example: skip build artefacts and container registry, backing up only the database and repositories
|
|
||||||
sudo gitlab-backup create SKIP=artifacts,registry
|
|
||||||
```
|
|
||||||
|
|
||||||
### Installing
|
|
||||||
|
|
||||||
```shell
|
|
||||||
sudo dpkg -i gitlab-package.deb
|
|
||||||
```
|
|
||||||
@@ -1,131 +0,0 @@
|
|||||||
---
|
|
||||||
title: Google Code Review Standards
|
|
||||||
tags:
|
|
||||||
- code-review
|
|
||||||
- best-practice
|
|
||||||
- engineering
|
|
||||||
- google
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
Code review is a step in the development process where one or more developers review code written by another developer (
|
|
||||||
the author) to ensure that:
|
|
||||||
|
|
||||||
- The code is free of errors, bugs, and issues;
|
|
||||||
- The code conforms to quality and style guide requirements and standards;
|
|
||||||
- The code fulfils all intended functionality;
|
|
||||||
- Once merged, the codebase continues to function properly and is left in a better state.
|
|
||||||
|
|
||||||
This is why code review is a vital part of software development. Code reviewers act as gatekeepers, responsible for
|
|
||||||
deciding whether the code is ready to become part of the codebase and enter production.
|
|
||||||
|
|
||||||
## The Code Should Improve the Overall Health of the System
|
|
||||||
|
|
||||||
Every code change (pull request) should improve the overall health of the system. The key point is that even small
|
|
||||||
improvements, once merged, enhance the health of the software or codebase.
|
|
||||||
|
|
||||||
## Review Code Quickly and Provide Active Responses and Feedback
|
|
||||||
|
|
||||||
First and foremost, do not delay the merging of code. There is no such thing as perfect code. If the code improves the
|
|
||||||
overall health of the system, it should be delivered promptly.
|
|
||||||
|
|
||||||
> The key insight is that there is no perfect code — only better code. — Google Engineering Practices Documentation
|
|
||||||
|
|
||||||
If there are no urgent tasks at hand, review code as soon as it is submitted. The maximum response time for a pull
|
|
||||||
request should not exceed one working day. Multiple rounds of partial or complete code review should be completed for a
|
|
||||||
single pull request within a day.
|
|
||||||
|
|
||||||
## Educate and Inspire During Code Review
|
|
||||||
|
|
||||||
During code review, provide guidance by sharing knowledge and experience wherever possible.
|
|
||||||
|
|
||||||
## Review Code Against Standards
|
|
||||||
|
|
||||||
Always remember that style guides, coding standards, and relevant documentation should serve as the absolute authority
|
|
||||||
in code review. For example, when consistency of tabs versus spaces is in question, cite the coding conventions.
|
|
||||||
|
|
||||||
> If you use Java, the following article may be helpful — it summarises Java coding best practices from major tech
|
|
||||||
> companies: [A Short Summary of Java Coding Best Practices](https://rhamedy.medium.com/a-short-summary-of-java-coding-best-practices-31283d0167d3)
|
|
||||||
|
|
||||||
## Resolving Code Review Conflicts
|
|
||||||
|
|
||||||
When resolving conflicts during code review, follow the agreed best practices in the style guide and coding standards,
|
|
||||||
and seek advice from others with more domain knowledge and experience.
|
|
||||||
|
|
||||||
If your comment is optional or relatively unimportant, indicate so in the comment and let the author decide whether to
|
|
||||||
address or skip it.
|
|
||||||
|
|
||||||
As a reviewer, when there is no style guide or coding standard to reference, you can at minimum suggest that the code
|
|
||||||
change remain consistent with the rest of the codebase.
|
|
||||||
|
|
||||||
## UI Change Demonstrations Are Part of Code Review
|
|
||||||
|
|
||||||
If a code change involves user interface modifications, a demonstration is required in addition to the code review to
|
|
||||||
ensure the UI meets expectations and aligns with the interface design.
|
|
||||||
|
|
||||||
For frontend code changes, you must provide a demonstration or ensure that the code change includes the necessary UI
|
|
||||||
automation tests to verify the added or updated functionality.
|
|
||||||
|
|
||||||
## Ensure All Tests Are Included in the Code Review
|
|
||||||
|
|
||||||
Unless in an emergency, pull requests should include all necessary tests, such as unit tests, integration tests, and
|
|
||||||
end-to-end tests.
|
|
||||||
|
|
||||||
An emergency here means a bug or security vulnerability that needs fixing as soon as possible, and tests can be added
|
|
||||||
later. In such cases, ensure that appropriate tickets/issues are created and someone is responsible for completing the
|
|
||||||
tests immediately after the hotfix or deployment.
|
|
||||||
|
|
||||||
Skipping tests must never be accepted. If time is short and certain goals risk not being met, the solution is not to
|
|
||||||
skip tests but to scope down the deliverables.
|
|
||||||
|
|
||||||
## Don't Interrupt Your Own Work for Code Review
|
|
||||||
|
|
||||||
If you are deeply focused on your work, don't interrupt yourself — it takes a long time to get back into the flow. In
|
|
||||||
other words, the cost of interrupting a developer in flow far exceeds the cost of making them wait for a code review. Do
|
|
||||||
your code reviews after a break (lunch, coffee, etc.).
|
|
||||||
|
|
||||||
Most of the time, an entire code review and merge cannot be completed in a single day. What matters is giving the author
|
|
||||||
prompt feedback. For example, even if you cannot complete a full review, you can quickly point out a few areas worth
|
|
||||||
discussing. This significantly reduces frustration during the review process.
|
|
||||||
|
|
||||||
## Review All Code — Make No Assumptions
|
|
||||||
|
|
||||||
Review every line of code that is submitted. Do not make assumptions about manually written classes and methods, and
|
|
||||||
make sure you understand what the code is doing.
|
|
||||||
|
|
||||||
Make sure you understand the code you are reviewing. If you don't, ask the author for clarification or a code
|
|
||||||
walkthrough and explanation. If you are not qualified to review part of the code, ask another qualified developer to
|
|
||||||
review it in your place.
|
|
||||||
|
|
||||||
## Keep the Big Picture in Mind During Code Review
|
|
||||||
|
|
||||||
It helps to look at code changes from a broader perspective. For example, a file is modified and four new lines of code
|
|
||||||
are added. Don't just look at those four lines — consider reviewing the entire file and examining what was added. Do the
|
|
||||||
additions degrade the quality of existing code? Do they make existing functionality a candidate for refactoring?
|
|
||||||
|
|
||||||
Reviewing added code outside the context of the function/method or class will, over time, lead to classes that are
|
|
||||||
unmaintainable, tangled, difficult to test, and hard to extend or refactor.
|
|
||||||
|
|
||||||
Remember that just as trivial improvements can compound into a better product over time, even minor code degradation or
|
|
||||||
technical debt can accumulate to the point where the product becomes difficult to maintain and extend.
|
|
||||||
|
|
||||||
## Recognise and Encourage Great Work During Code Review
|
|
||||||
|
|
||||||
If you see an excellent code change, don't forget to acknowledge and encourage the author generously. The purpose of
|
|
||||||
code review is not only to find errors but also to encourage and guide developers towards great work.
|
|
||||||
|
|
||||||
## Be Considerate, Respectful, Kind, and Clear During Code Review
|
|
||||||
|
|
||||||
It is critical to remain kind, clear, polite, and respectful during code review, while also providing the author with
|
|
||||||
clear feedback and positive assistance. When reviewing code, comment on the code — not the developer.
|
|
||||||
|
|
||||||
## Explain Code Review Comments Thoroughly, with a Sense of Proportion
|
|
||||||
|
|
||||||
Whenever a code review comment proposes an alternative or points out a problem, it's important to explain the reasoning
|
|
||||||
and provide examples based on your knowledge and experience to help the developer understand why your suggestion
|
|
||||||
improves the code quality.
|
|
||||||
|
|
||||||
When suggesting modifications or changes, find the right balance in how you guide the author. For example, I prefer
|
|
||||||
guidance, explanation, hints, or suggestions over providing the entire solution.
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: Insights
|
|
||||||
---
|
|
||||||
|
|
||||||
# Insights
|
|
||||||
|
|
||||||
Practical guides, cheatsheets, and standards distilled from daily engineering
|
|
||||||
work.
|
|
||||||
|
|
||||||
## Topics
|
|
||||||
|
|
||||||
- **Frontend** — React patterns, TailwindCSS tips, ECMAScript syntax guides, browser quirks
|
|
||||||
- **Backend & Database** — Java development, MyBatis, PostgreSQL, MySQL JSON
|
|
||||||
- **DevOps & Infrastructure** — Docker, GitLab CI/CD, LDAP, MinIO, macOS troubleshooting
|
|
||||||
- **Engineering Standards** — code review, version control, general and language-specific conventions
|
|
||||||
- **Tools & Productivity** — email etiquette, font sizing, data analysis methods
|
|
||||||
|
|
||||||
Browse the sidebar or scroll through the posts below — each article is self-contained and written to be immediately useful.
|
|
||||||
@@ -1,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.
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
---
|
|
||||||
title: MinIO Administration Guide for New Versions
|
|
||||||
tags:
|
|
||||||
- minio
|
|
||||||
- storage
|
|
||||||
- s3
|
|
||||||
- devops
|
|
||||||
- cheatsheet
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
In newer versions, MinIO has removed administrative functionality from the Web UI. You now need to use the **MinIO Client (mc)** command-line tool for all management operations.
|
|
||||||
|
|
||||||
## Installing MinIO Client (mc)
|
|
||||||
|
|
||||||
### Windows:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Download mc.exe
|
|
||||||
Invoke-WebRequest -Uri "https://dl.min.io/client/mc/release/windows-amd64/mc.exe" -OutFile "mc.exe"
|
|
||||||
|
|
||||||
# Or using Chocolatey
|
|
||||||
choco install minio-client
|
|
||||||
```
|
|
||||||
|
|
||||||
### Linux/macOS:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Linux
|
|
||||||
wget https://dl.min.io/client/mc/release/linux-amd64/mc
|
|
||||||
chmod +x mc
|
|
||||||
sudo mv mc /usr/local/bin/
|
|
||||||
|
|
||||||
# macOS
|
|
||||||
brew install minio/stable/mc
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuring the MinIO Client
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Add a MinIO server alias
|
|
||||||
mc alias set myminio http://localhost:9000 minioadmin minioadmin
|
|
||||||
|
|
||||||
# Verify the connection
|
|
||||||
mc admin info myminio
|
|
||||||
```
|
|
||||||
|
|
||||||
## User Management
|
|
||||||
|
|
||||||
### Creating Users
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a new user
|
|
||||||
mc admin user add myminio newuser newpassword
|
|
||||||
|
|
||||||
# List all users
|
|
||||||
mc admin user list myminio
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating Access Keys and Secret Keys
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a service account for a user (generates AccessKey/SecretKey)
|
|
||||||
mc admin user svcacct add myminio newuser
|
|
||||||
|
|
||||||
# Or specify custom AccessKey and SecretKey
|
|
||||||
mc admin user svcacct add myminio newuser --access-key "MYACCESSKEY123" --secret-key "MYSECRETKEY456"
|
|
||||||
|
|
||||||
# View a user's service accounts
|
|
||||||
mc admin user svcacct list myminio newuser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Permission Management
|
|
||||||
|
|
||||||
### Creating Policies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a policy file policy.json
|
|
||||||
cat > policy.json << EOF
|
|
||||||
{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [
|
|
||||||
{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Action": [
|
|
||||||
"s3:GetObject",
|
|
||||||
"s3:PutObject",
|
|
||||||
"s3:DeleteObject"
|
|
||||||
],
|
|
||||||
"Resource": [
|
|
||||||
"arn:aws:s3:::mybucket/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# Add the policy
|
|
||||||
mc admin policy add myminio mypolicy policy.json
|
|
||||||
|
|
||||||
# Assign the policy to a user
|
|
||||||
mc admin policy set myminio mypolicy user=newuser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Bucket Management
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create a bucket
|
|
||||||
mc mb myminio/mybucket
|
|
||||||
|
|
||||||
# List buckets
|
|
||||||
mc ls myminio
|
|
||||||
|
|
||||||
# Set bucket policy
|
|
||||||
mc policy set public myminio/mybucket
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Administration Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View server information
|
|
||||||
mc admin info myminio
|
|
||||||
|
|
||||||
# View server configuration
|
|
||||||
mc admin config get myminio
|
|
||||||
|
|
||||||
# Restart the server
|
|
||||||
mc admin service restart myminio
|
|
||||||
|
|
||||||
# View logs
|
|
||||||
mc admin logs myminio
|
|
||||||
|
|
||||||
# View statistics
|
|
||||||
mc admin prometheus metrics myminio
|
|
||||||
```
|
|
||||||
|
|
||||||
## Practical Script Example
|
|
||||||
|
|
||||||
Create an administration script `setup-minio.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
MINIO_ALIAS="myminio"
|
|
||||||
MINIO_URL="http://localhost:9000"
|
|
||||||
ADMIN_USER="minioadmin"
|
|
||||||
ADMIN_PASS="minioadmin"
|
|
||||||
|
|
||||||
# Configure the MinIO client
|
|
||||||
mc alias set $MINIO_ALIAS $MINIO_URL $ADMIN_USER $ADMIN_PASS
|
|
||||||
|
|
||||||
# Create an application user
|
|
||||||
APP_USER="appuser"
|
|
||||||
APP_PASS="apppassword"
|
|
||||||
mc admin user add $MINIO_ALIAS $APP_USER $APP_PASS
|
|
||||||
|
|
||||||
# Create a service account and retrieve AccessKey/SecretKey
|
|
||||||
echo "Creating service account for $APP_USER..."
|
|
||||||
CREDENTIALS=$(mc admin user svcacct add $MINIO_ALIAS $APP_USER --json)
|
|
||||||
ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.accessKey')
|
|
||||||
SECRET_KEY=$(echo $CREDENTIALS | jq -r '.secretKey')
|
|
||||||
|
|
||||||
echo "Generated credentials:"
|
|
||||||
echo "Access Key: $ACCESS_KEY"
|
|
||||||
echo "Secret Key: $SECRET_KEY"
|
|
||||||
|
|
||||||
# Create a bucket
|
|
||||||
mc mb $MINIO_ALIAS/app-bucket
|
|
||||||
|
|
||||||
# Set a read-only policy
|
|
||||||
mc policy set download $MINIO_ALIAS/app-bucket
|
|
||||||
```
|
|
||||||
|
|
||||||
## Web Console Access
|
|
||||||
|
|
||||||
Although administrative functionality has been removed, you can still access the MinIO Console via:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Launch the MinIO Console (if installed separately)
|
|
||||||
mc admin console myminio
|
|
||||||
```
|
|
||||||
|
|
||||||
Alternatively, specify the console address when starting the MinIO server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
minio server /data --console-address ":9001"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
Managing the new MinIO relies entirely on the `mc` command-line tool:
|
|
||||||
|
|
||||||
1. **Install the mc client**
|
|
||||||
2. **Configure the server alias**
|
|
||||||
3. **Use `mc admin` commands for user, permission, and bucket management**
|
|
||||||
4. **Generate AccessKeys/SecretKeys via `mc admin user svcacct`**
|
|
||||||
|
|
||||||
While this approach requires command-line operations, it provides more powerful and flexible management capabilities, particularly suited for automated deployment and script-based management.
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: MyBatis Flex
|
|
||||||
tags:
|
|
||||||
- java
|
|
||||||
- mybatis
|
|
||||||
- spring-boot
|
|
||||||
- orm
|
|
||||||
- framework
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
> This article only aims at using `MyBatis Flex` in Spring Boot 3
|
|
||||||
|
|
||||||
## Installation and Configuration
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Add the following codes to `libs.versions.toml` in `gradle` :
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[versions]
|
|
||||||
mybatisFlexVersion = "X.Y.Z"
|
|
||||||
hikariVersion = "X.Y.Z"
|
|
||||||
|
|
||||||
[libraries]
|
|
||||||
hikari = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikariVersion" }
|
|
||||||
mybatisFlex-processor = { group = "com.mybatis-flex", name = "mybatis-flex-processor", version.ref = "mybatisFlexVersion" }
|
|
||||||
mybatisFlex-starter = { group = "com.mybatis-flex", name = "mybatis-flex-spring-boot3-starter", version.ref = "mybatisFlexVersion" }
|
|
||||||
```
|
|
||||||
@@ -1,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.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
---
|
|
||||||
title: The Quartile Method
|
|
||||||
tags:
|
|
||||||
- statistics
|
|
||||||
- algorithm
|
|
||||||
- data-analysis
|
|
||||||
- math
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
The quartile method is a commonly used statistical technique primarily employed for data analysis and presentation. The method divides a data set into four equal parts, each containing one quarter of the data. The key statistics include the first quartile (Q1), second quartile (Q2, the median), and third quartile (Q3).
|
|
||||||
|
|
||||||
- The first quartile (`Q1`), also known as the lower quartile, is the value at the 25th percentile of a data set sorted in ascending order.
|
|
||||||
- The second quartile (`Q2`), also known as the median, is the value at the 50th percentile of a data set sorted in ascending order.
|
|
||||||
- The third quartile (`Q3`), also known as the upper quartile, is the value at the 75th percentile of a data set sorted in ascending order.
|
|
||||||
- The interquartile range (`IQR`) is the difference between the third quartile and the first quartile, used to measure the dispersion of the middle 50% of data. The formula is IQR = Q3 - Q1. The IQR is commonly used in constructing box plots, an effective way to describe data distribution, particularly useful for identifying outliers.
|
|
||||||
|
|
||||||
Upper bound = Q3 + 1.5 × IQR.
|
|
||||||
|
|
||||||
Lower bound = Q1 - 1.5 × IQR.
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
---
|
|
||||||
title: Setting Up an LDAP Service
|
|
||||||
tags:
|
|
||||||
- ldap
|
|
||||||
- openldap
|
|
||||||
- authentication
|
|
||||||
- linux
|
|
||||||
- devops
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
Setting up an LDAP (Lightweight Directory Access Protocol) server is a core step in implementing enterprise-level **centralised identity authentication** and **permission management**. The most commonly used open-source implementation is **OpenLDAP**.
|
|
||||||
|
|
||||||
Below are detailed steps for setting up an OpenLDAP server on Debian/Ubuntu-based Linux systems.
|
|
||||||
|
|
||||||
## Environment Preparation and Installation
|
|
||||||
|
|
||||||
Before starting, ensure your system packages are up to date and the hostname is properly configured.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
## Update the system
|
|
||||||
sudo apt update && sudo apt upgrade -y
|
|
||||||
|
|
||||||
## Install OpenLDAP and management tools
|
|
||||||
## slapd is the daemon, ldap-utils is the command-line client tool
|
|
||||||
sudo apt install slapd ldap-utils -y
|
|
||||||
```
|
|
||||||
|
|
||||||
> During installation, you will be prompted to set the **LDAP administrator password**. Be sure to remember this password — it will be used frequently during configuration.
|
|
||||||
|
|
||||||
## Configuring the OpenLDAP Server
|
|
||||||
|
|
||||||
Although the installation performs some initial setup, we typically need to customise the configuration for a specific domain (e.g., `example.com`).
|
|
||||||
|
|
||||||
### Reconfigure slapd
|
|
||||||
|
|
||||||
Run the following command to enter the interactive configuration interface:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo dpkg-reconfigure slapd
|
|
||||||
```
|
|
||||||
|
|
||||||
**Configuration recommendations**:
|
|
||||||
|
|
||||||
1. **Omit OpenLDAP server configuration?** Choose **No**.
|
|
||||||
2. **DNS domain name:** Enter your domain (e.g., `centre.example.com`). This determines your Base DN, such as `dc=centre,dc=example,dc=com`.
|
|
||||||
3. **Organization name:** Enter your organisation name.
|
|
||||||
4. **Administrator password:** Enter the administrator password you set earlier.
|
|
||||||
5. **Database backend:** **MDB** is recommended.
|
|
||||||
6. **Remove database when slapd is purged?** Choose **No**.
|
|
||||||
7. **Move old database?** Choose **Yes**.
|
|
||||||
|
|
||||||
## Understanding the LDAP Hierarchy
|
|
||||||
|
|
||||||
LDAP data is stored in a **tree structure**. To facilitate management, we typically create two "Organisational Units" (OUs): one for users (People) and one for groups (Groups).
|
|
||||||
|
|
||||||
## Creating Organisational Units (OUs)
|
|
||||||
|
|
||||||
In LDAP, we use **LDIF** (LDAP Data Interchange Format) files to add or modify directory entries.
|
|
||||||
|
|
||||||
Create a file named `base.ldif`:
|
|
||||||
|
|
||||||
```text
|
|
||||||
## Create the users organisational unit
|
|
||||||
dn: ou=people,dc=centre,dc=example,dc=com
|
|
||||||
objectClass: organizationalUnit
|
|
||||||
ou: people
|
|
||||||
|
|
||||||
## Create the groups organisational unit
|
|
||||||
dn: ou=groups,dc=centre,dc=example,dc=com
|
|
||||||
objectClass: organizationalUnit
|
|
||||||
ou: groups
|
|
||||||
```
|
|
||||||
|
|
||||||
**Import the data:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
## Import the LDIF file into the database as the administrator
|
|
||||||
ldapadd -x -D "cn=admin,dc=centre,dc=example,dc=com" -W -f base.ldif
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding Users and Groups
|
|
||||||
|
|
||||||
Create a file named `groups.ldif` to define a new group:
|
|
||||||
|
|
||||||
```text
|
|
||||||
## Define a new group
|
|
||||||
dn: cn=developers,ou=groups,dc=centre,dc=example,dc=com
|
|
||||||
objectClass: inetOrgGroup
|
|
||||||
cn: developers
|
|
||||||
```
|
|
||||||
|
|
||||||
Create a file named `users.ldif` to define a new user:
|
|
||||||
|
|
||||||
```text
|
|
||||||
## Define a new user
|
|
||||||
dn: uid=jbloggs,ou=people,dc=centre,dc=example,dc=com # The Distinguished Name (DN) of this entry.
|
|
||||||
objectClass: inetOrgPerson # Object class — inetOrgPerson represents an internet organisation person. It enables common contact attributes such as email, displayName, telephoneNumber, etc.
|
|
||||||
objectClass: posixAccount # Makes this entry compatible with Unix/Linux system accounts. With it, the user can log into Linux servers and has a UID, GID, and home directory.
|
|
||||||
objectClass: shadowAccount # Used to manage password ageing (expiry, change warnings, etc.), corresponding to /etc/shadow functionality in Linux.
|
|
||||||
uid: jbloggs # The user's login name (User ID). This is typically what you enter on the Linux login screen.
|
|
||||||
sn: Bloggs # Surname. Required by the inetOrgPerson class.
|
|
||||||
givenName: Joe # First name.
|
|
||||||
cn: Joe Bloggs # Common Name. The standard display name for an LDAP entry.
|
|
||||||
displayName: Joe Bloggs # The friendly name displayed in graphical interfaces or email clients.
|
|
||||||
uidNumber: 10000 # The user's numeric ID in the Linux system.
|
|
||||||
gidNumber: 5000 # The numeric ID of the user's primary group.
|
|
||||||
userPassword: {SSHA}password-hash-or-plaintext # The user's encrypted password.
|
|
||||||
homeDirectory: /home/jbloggs # The path to the user's home directory after logging into Linux.
|
|
||||||
loginShell: /bin/bash # The shell environment the user gets after logging in.
|
|
||||||
```
|
|
||||||
|
|
||||||
> If you plan to integrate **GitLab**, **Jenkins**, or a **VPN** later, they typically search the `uid` attribute to verify login names.
|
|
||||||
|
|
||||||
### FAQ
|
|
||||||
|
|
||||||
#### If the user doesn't need server login permissions, can the `objectClass: posixAccount` be removed?
|
|
||||||
|
|
||||||
**Yes, it can be completely removed**, but you need to be aware of the "binding relationships" between attributes.
|
|
||||||
|
|
||||||
If you only need this user for web application logins (e.g., GitLab, Jenkins, Wiki) or as an email contact, and they don't need to log into a Linux server via SSH or console, removing `posixAccount` is the more standard approach.
|
|
||||||
|
|
||||||
When you remove `objectClass: posixAccount`, the following attributes **must also be deleted**, as they belong to that class's mandatory or optional attributes:
|
|
||||||
|
|
||||||
- `uidNumber`
|
|
||||||
- `gidNumber`
|
|
||||||
- `homeDirectory`
|
|
||||||
- `loginShell`
|
|
||||||
|
|
||||||
Additionally, `shadowAccount` is also typically associated with system logins — if you don't need to manage Linux password expiry policies, it can also be removed.
|
|
||||||
|
|
||||||
## Follow-up Suggestions and Management Tools
|
|
||||||
|
|
||||||
Command-line LDAP management can be cumbersome. The following tools are recommended for visual administration:
|
|
||||||
|
|
||||||
- **phpLDAPAdmin**: A web-based management interface, ideal for quick onboarding.
|
|
||||||
- **Apache Directory Studio**: A powerful cross-platform desktop client, suitable for complex architecture design.
|
|
||||||
- **Security Hardening**: By default, LDAP transmits data in plain text. It is recommended to configure **LDAPS (LDAP over SSL/TLS)** to encrypt communication on port 636.
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
---
|
|
||||||
title: Using JSON in MySQL
|
|
||||||
tags:
|
|
||||||
- mysql
|
|
||||||
- json
|
|
||||||
- database
|
|
||||||
author:
|
|
||||||
name: Zihlu Wang
|
|
||||||
email: real@zihluwang.me
|
|
||||||
---
|
|
||||||
|
|
||||||
MySQL (since version 5.7) **does not directly support a data type called `jsonb`**. `jsonb` is a data type specific to PostgreSQL, which stores JSON data in a binary format with pre-parsing for faster access and manipulation during queries.
|
|
||||||
|
|
||||||
However, MySQL's `JSON` data type is functionally and internally similar to PostgreSQL's `jsonb` in many respects, particularly when it comes to querying data.
|
|
||||||
|
|
||||||
The `JSON` data type in MySQL was introduced in MySQL 5.7 and has the following characteristics:
|
|
||||||
|
|
||||||
1. **Binary Storage**: Like PostgreSQL's `jsonb`, MySQL's `JSON` type data is stored in an **internal binary format** rather than as a plain text string. This makes reading and manipulating JSON data more efficient, as the database does not need to parse text-format JSON strings on every query.
|
|
||||||
2. **Automatic Validation**: When you insert or update a `JSON` column, MySQL automatically validates that its content is a valid JSON document. If not, it throws an error.
|
|
||||||
3. **Optimised Storage**: The binary format is also space-optimised, typically more compact than storing JSON in raw text format.
|
|
||||||
|
|
||||||
MySQL provides a powerful set of functions and operators for querying and manipulating `JSON` data, very similar to what you'd expect from `jsonb`:
|
|
||||||
|
|
||||||
1. **`->` (JSON Extract Operator)**: Extracts a value from a JSON document. It returns a JSON value.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT my_json_column->'$.key' FROM my_table;
|
|
||||||
-- Example: extract the name property of a user object
|
|
||||||
-- Assuming my_json_column stores {'user': {'name': 'Alice'}}
|
|
||||||
SELECT json_data->'$.user.name' FROM my_table;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **`->>` (JSON Unquote Operator)**: Extracts a value from a JSON document and **automatically unquotes it**, typically returning a scalar value (e.g., string, number). This is equivalent to `JSON_UNQUOTE(JSON_EXTRACT(...))`.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT my_json_column->>'$.key' FROM my_table;
|
|
||||||
-- Example: extract the name property of a user object (returns the string 'Alice' directly)
|
|
||||||
SELECT json_data->>'$.user.name' FROM my_table;
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **`JSON_EXTRACT(json_doc, path, ...)`**: Explicitly extracts data from a JSON document.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
SELECT JSON_EXTRACT(my_json_column, '$.key') FROM my_table;
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **`JSON_CONTAINS(json_doc, candidate, path)`**: Checks whether a JSON document contains a specified value.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Check whether the tags array contains 'backend'
|
|
||||||
-- Assuming my_json_column stores {'tags': ['frontend', 'backend']}
|
|
||||||
SELECT * FROM my_table WHERE JSON_CONTAINS(json_data->'$.tags', '"backend"');
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **`JSON_SEARCH(json_doc, one_or_all, search_str, escape_char, path, ...)`**: Returns the path to a specified string within a JSON document.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Find the path to a value of 'test'
|
|
||||||
SELECT JSON_SEARCH(my_json_column, 'one', 'test') FROM my_table;
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **`JSON_TABLE(json_doc, path COLUMNS ... )` (MySQL 8.0 and later)**: A very powerful function that "expands" JSON data into relational rows and columns, ideal for complex queries and reporting.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
-- Assuming json_data stores {'items': [{'id': 1, 'name': 'A'}, {'id': 2, 'name': 'B'}]}
|
|
||||||
SELECT *
|
|
||||||
FROM my_table,
|
|
||||||
JSON_TABLE(json_data, '$.items[*]' COLUMNS(
|
|
||||||
itemId INT PATH '$.id',
|
|
||||||
itemName VARCHAR(50) PATH '$.name'
|
|
||||||
)) AS jt;
|
|
||||||
```
|
|
||||||
|
|
||||||
Like PostgreSQL's `jsonb`, efficient querying on JSON fields typically requires indexing. Since the content of JSON fields is dynamic, MySQL does not directly support creating traditional B-tree indexes on a specific internal path of a JSON field. However, you can achieve this through **Virtual Generated Columns**:
|
|
||||||
|
|
||||||
1. **Create a Virtual Column**: Define a virtual column whose value is extracted from a specific path in the JSON field.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
ALTER TABLE my_table
|
|
||||||
ADD COLUMN user_name VARCHAR(255) AS (json_data->>'$.user.name') VIRTUAL;
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create an Index on the Virtual Column**: This way, when you query `WHERE json_data->>'$.user.name' = 'Alice'`, the MySQL optimiser can use the `idx_user_name` index, significantly improving query performance.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
CREATE INDEX idx_user_name ON my_table (user_name);
|
|
||||||
```
|
|
||||||
|
|
||||||
Although MySQL does not have the exact name `jsonb`, its `JSON` data type provides highly similar functionality: binary storage optimisation, automatic validation, and rich query operators and functions. By combining virtual columns with indexes, MySQL can deliver query performance and flexibility comparable to PostgreSQL's `jsonb` when working with JSON data.
|
|
||||||
@@ -1,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
@@ -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
|
||||||
---
|
---
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
title: News
|
|
||||||
---
|
|
||||||
|
|
||||||
## Microsoft Email Delivery Issues (@outlook, @hotmail, @live, @msn and custom domain emails hosted on Microsoft)
|
|
||||||
|
|
||||||
> Updated at _19 May 2026_.
|
|
||||||
|
|
||||||
**Urgent Action Required for Users with Microsoft-Hosted Emails.**
|
|
||||||
|
|
||||||
Please be advised that our automated email service is currently experiencing severe delivery issues to all email addresses hosted by Microsoft. Due to recent aggressive filtering policies by their Exchange Online Protection (EOP), our mail delivery server has been blocked by Microsoft. As a consequence, our system is **unable to send any notifications, password resets, or activation emails** to accounts ending in **@outlook.com**, **@hotmail.com**, **@live.com**, **@msn.com**, or **indeed any custom domains hosted by Microsoft**.
|
|
||||||
|
|
||||||
To ensure uninterrupted service, please follow the guidance below based on your account status:
|
|
||||||
|
|
||||||
- For Prospective Users (Before Registration): Please **do not** use a Microsoft-hosted email address to register your account. You will not receive the essential activation link. Kindly use an alternative email service provider, such as your corporate email, Gmail, etc. to complete your registration.
|
|
||||||
- For Existing Registered Users: If your profile is currently linked to an Outlook or Hotmail address, please log in immediately and update your primary email address. You can change this by navigating to your **[User Settings -> Preferences -> Emails](https://github.com/settings/emails)** page. Failing to do so will result in missing critical repository updates, CI/CD pipeline failure logs, and Merge Request notifications.
|
|
||||||
|
|
||||||
We are actively working to appeal this block with the Microsoft Postmaster Centre. We sincerely apologise for any inconvenience caused and appreciate your prompt cooperation in updating your contact details.
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
[
|
|
||||||
"index",
|
|
||||||
{ "type": "section-header", "label": "opensource.group.libraries" },
|
|
||||||
{ "type": "dir", "name": "onixbyte-toolbox" },
|
|
||||||
{ "type": "dir", "name": "regions4j" },
|
|
||||||
{ "type": "dir", "name": "captcha" },
|
|
||||||
{ "type": "dir", "name": "calendar-toolbox" },
|
|
||||||
{ "type": "section-header", "label": "opensource.group.applications" },
|
|
||||||
{ "type": "dir", "name": "helix" },
|
|
||||||
{ "type": "dir", "name": "delta-force-guide" },
|
|
||||||
{ "type": "section-header", "label": "opensource.group.tools" },
|
|
||||||
{ "type": "dir", "name": "claude-code-skill-manager" },
|
|
||||||
{ "type": "dir", "name": "vite-plugins" }
|
|
||||||
]
|
|
||||||
@@ -1,390 +0,0 @@
|
|||||||
---
|
|
||||||
title: Calendar Toolbox API
|
|
||||||
---
|
|
||||||
|
|
||||||
## Package Overview
|
|
||||||
|
|
||||||
The library is organised under the base package `com.onixbyte.calendar` with the following structure:
|
|
||||||
|
|
||||||
| Package | Description |
|
|
||||||
|--------------------------------------------|--------------------------------------------|
|
|
||||||
| `com.onixbyte.calendar` | Root calendar object |
|
|
||||||
| `com.onixbyte.calendar.component` | iCalendar components (VEVENT, VTODO, etc.) |
|
|
||||||
| `com.onixbyte.calendar.component.property` | Component-level properties |
|
|
||||||
| `com.onixbyte.calendar.property` | Calendar-level properties |
|
|
||||||
| `com.onixbyte.calendar.parameter` | iCalendar property parameters |
|
|
||||||
| `com.onixbyte.calendar.recurrence` | Recurrence rule types |
|
|
||||||
| `com.onixbyte.calendar.value` | Value types |
|
|
||||||
| `com.onixbyte.calendar.util` | Formatting utilities |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Calendar
|
|
||||||
|
|
||||||
The top-level object representing an iCalendar (RFC 5545) VCALENDAR container.
|
|
||||||
|
|
||||||
```java
|
|
||||||
Calendar calendar = Calendar.builder()
|
|
||||||
.withCalendarScale(...)
|
|
||||||
.withMethod(...)
|
|
||||||
.withProductIdentifier(...)
|
|
||||||
.withVersion(...)
|
|
||||||
.withOwner(...)
|
|
||||||
.withPrimaryCalendar(...)
|
|
||||||
.withPublishedTTL(...)
|
|
||||||
.withCalendarDescription(...)
|
|
||||||
.withCalendarName(...)
|
|
||||||
.withCalendarId(...)
|
|
||||||
.withCustomProperties(...)
|
|
||||||
.withComponents(event, todo, ...)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
String icsContent = calendar.formatted();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Calendar Properties
|
|
||||||
|
|
||||||
All calendar-level properties live in `com.onixbyte.calendar.property` and implement `CalendarProperty`.
|
|
||||||
|
|
||||||
| Class | iCalendar Property | Description |
|
|
||||||
|--------------------------|----------------------|-------------------------------------------|
|
|
||||||
| `CalendarScale` | `CALSCALE` | Calendar system (e.g., GREGORIAN) |
|
|
||||||
| `Method` | `METHOD` | iCalendar method (e.g., PUBLISH, REQUEST) |
|
|
||||||
| `ProductIdentifier` | `PRODID` | Product identifier of the calendar |
|
|
||||||
| `Version` | `VERSION` | iCalendar version (e.g., 2.0) |
|
|
||||||
| `Owner` | `X-OWNER` | Owner of the calendar |
|
|
||||||
| `PrimaryCalendar` | `X-PRIMARY-CALENDAR` | Whether this is a primary calendar |
|
|
||||||
| `PublishedTTL` | `X-PUBLISHED-TTL` | Time-to-live for published calendar |
|
|
||||||
| `CalendarDescription` | `X-CALENDAR-DESC` | Calendar description |
|
|
||||||
| `CalendarName` | `X-CALENDAR-NAME` | Calendar name |
|
|
||||||
| `CalendarId` | `X-CALENDAR-ID` | Calendar identifier |
|
|
||||||
| `CustomCalendarProperty` | `X-*` | Custom calendar properties |
|
|
||||||
|
|
||||||
Custom properties are created using a builder:
|
|
||||||
|
|
||||||
```java
|
|
||||||
var customProp = CustomCalendarProperty.builder()
|
|
||||||
.withParameters(...)
|
|
||||||
.build("X-MY-PROPERTY", "my-value");
|
|
||||||
```
|
|
||||||
|
|
||||||
> Note: Custom property names **must** start with `X-`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Components
|
|
||||||
|
|
||||||
Components represent the core entities in an iCalendar object. Each component implements `CalendarComponent` and is rendered between `BEGIN:TYPE` and `END:TYPE` delimiters.
|
|
||||||
|
|
||||||
### Event (VEVENT)
|
|
||||||
|
|
||||||
Represents a scheduled event.
|
|
||||||
|
|
||||||
```java
|
|
||||||
Event event = Event.builder()
|
|
||||||
.withDateTimeStamp(dtstamp) // required
|
|
||||||
.withUniqueIdentifier(uid) // required
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withDateTimeEnd(dtend) // mutually exclusive with duration
|
|
||||||
.withDuration(duration) // mutually exclusive with dtend
|
|
||||||
.withSummary(summary)
|
|
||||||
.withDescription(description)
|
|
||||||
.withClassification(classification)
|
|
||||||
.withDateTimeCreated(dateTimeCreated)
|
|
||||||
.withGeographicPosition(geoPos)
|
|
||||||
.withLastModified(lastModified)
|
|
||||||
.withLocation(location)
|
|
||||||
.withOrganiser(organiser)
|
|
||||||
.withPriority(priority)
|
|
||||||
.withSequenceNumber(seqNumber)
|
|
||||||
.withStatus(status)
|
|
||||||
.withTimeTransparency(transparency)
|
|
||||||
.withUniformResourceLocator(url)
|
|
||||||
.withRecurrenceId(recurrenceId)
|
|
||||||
.withRecurrenceRule(rrule)
|
|
||||||
.withAttachments(attachments)
|
|
||||||
.withAttendees(attendees)
|
|
||||||
.withCategories(categories)
|
|
||||||
.withComments(comments)
|
|
||||||
.withContacts(contacts)
|
|
||||||
.withExceptionDateTimes(exDates)
|
|
||||||
.withRequestStatuses(statuses)
|
|
||||||
.withRelated(related)
|
|
||||||
.withResources(resources)
|
|
||||||
.withRecurrenceDateTimes(rDates)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Todo (VTODO)
|
|
||||||
|
|
||||||
Represents a to-do item or task.
|
|
||||||
|
|
||||||
```java
|
|
||||||
Todo todo = Todo.builder()
|
|
||||||
.withDateTimeStamp(dtstamp) // required
|
|
||||||
.withUniqueIdentifier(uid) // required
|
|
||||||
.withSummary(summary)
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withDateTimeDue(due) // mutually exclusive with duration
|
|
||||||
.withDuration(duration) // mutually exclusive with due
|
|
||||||
.withDateTimeCompleted(completed)
|
|
||||||
.withPercentComplete(percent)
|
|
||||||
.withClassification(classification)
|
|
||||||
.withDateTimeCreated(created)
|
|
||||||
.withDescription(description)
|
|
||||||
.withGeographicPosition(geoPos)
|
|
||||||
.withLastModified(lastModified)
|
|
||||||
.withLocation(location)
|
|
||||||
.withOrganiser(organiser)
|
|
||||||
.withPriority(priority)
|
|
||||||
.withSequenceNumber(seq)
|
|
||||||
.withStatus(status)
|
|
||||||
.withRecurrenceId(recurId)
|
|
||||||
.withRecurrenceRule(rrule)
|
|
||||||
.withUniformResourceLocator(url)
|
|
||||||
.withAttachments(attachments)
|
|
||||||
.withAttendees(attendees)
|
|
||||||
.withCategories(categories)
|
|
||||||
.withComments(comments)
|
|
||||||
.withContacts(contacts)
|
|
||||||
.withExceptionDateTimes(exDates)
|
|
||||||
.withRequestStatuses(statuses)
|
|
||||||
.withRelatedToList(related)
|
|
||||||
.withResources(resources)
|
|
||||||
.withRecurrenceDateTimes(rDates)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Journal (VJOURNAL)
|
|
||||||
|
|
||||||
Represents a journal entry or diary note.
|
|
||||||
|
|
||||||
```java
|
|
||||||
Journal journal = Journal.builder()
|
|
||||||
.withDateTimeStamp(dtstamp) // required
|
|
||||||
.withUniqueIdentifier(uid) // required
|
|
||||||
.withSummary(summary)
|
|
||||||
.withClassification(classification)
|
|
||||||
.withDateTimeCreated(created)
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withLastModified(lastModified)
|
|
||||||
.withOrganiser(organiser)
|
|
||||||
.withRecurrenceId(recurId)
|
|
||||||
.withSequenceNumber(seq)
|
|
||||||
.withStatus(status)
|
|
||||||
.withUniformResourceLocator(url)
|
|
||||||
.withRecurrenceRule(rrule)
|
|
||||||
.withAttachments(attachments)
|
|
||||||
.withAttendees(attendees)
|
|
||||||
.withCategories(categories)
|
|
||||||
.withComments(comments)
|
|
||||||
.withContacts(contacts)
|
|
||||||
.withDescriptions(descriptions)
|
|
||||||
.withExceptionDateTimes(exDates)
|
|
||||||
.withRelatedToList(related)
|
|
||||||
.withRecurrenceDate(rDates)
|
|
||||||
.withRequestStatuses(statuses)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
### FreeBusy (VFREEBUSY)
|
|
||||||
|
|
||||||
Represents free/busy time information.
|
|
||||||
|
|
||||||
```java
|
|
||||||
FreeBusy freeBusy = FreeBusy.builder()
|
|
||||||
.withDateTimeStamp(dtstamp) // required
|
|
||||||
.withUniqueIdentifier(uid) // required
|
|
||||||
.withContact(contact)
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withDateTimeEnd(dtend)
|
|
||||||
.withOrganiser(organiser)
|
|
||||||
.withUniformResourceLocator(url)
|
|
||||||
.withAttendees(attendees)
|
|
||||||
.withComments(comments)
|
|
||||||
.withFreeBusyTimes(freeBusyTimes)
|
|
||||||
.withRequestStatuses(statuses)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
### TimeZone (VTIMEZONE)
|
|
||||||
|
|
||||||
Defines time zone rules including standard and daylight saving time transitions.
|
|
||||||
|
|
||||||
```java
|
|
||||||
TimeZone timezone = TimeZone.builder()
|
|
||||||
.withTimeZoneIdentifier(tzId)
|
|
||||||
.withLastModified(lastModified)
|
|
||||||
.withTimeZoneUrl(tzUrl)
|
|
||||||
.withTimeZoneProperties(standardProp, daylightProp)
|
|
||||||
.build();
|
|
||||||
```
|
|
||||||
|
|
||||||
Time zone properties define the actual transition rules:
|
|
||||||
|
|
||||||
```java
|
|
||||||
TimeZoneProperty standard = TimeZoneProperty.builder()
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withTimeZoneOffsetTo(offsetTo)
|
|
||||||
.withTimeZoneOffsetFrom(offsetFrom)
|
|
||||||
.withRecurrenceRule(rrule)
|
|
||||||
.withTimeZoneNames(tzName)
|
|
||||||
.buildStandard(); // or buildDaylight()
|
|
||||||
|
|
||||||
TimeZoneProperty daylight = TimeZoneProperty.builder()
|
|
||||||
.withDateTimeStart(dtstart)
|
|
||||||
.withTimeZoneOffsetTo(offsetTo)
|
|
||||||
.withTimeZoneOffsetFrom(offsetFrom)
|
|
||||||
.buildDaylight();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alarm (VALARM)
|
|
||||||
|
|
||||||
Defines alarm/reminder notifications. Three alarm types are supported:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// Audio alarm
|
|
||||||
Alarm audioAlarm = Alarm.builder()
|
|
||||||
.withTrigger(trigger)
|
|
||||||
.withDuration(duration)
|
|
||||||
.withRepeatCount(repeatCount)
|
|
||||||
.withAttachments(audioAttachment)
|
|
||||||
.buildAudio();
|
|
||||||
|
|
||||||
// Display alarm
|
|
||||||
Alarm displayAlarm = Alarm.builder()
|
|
||||||
.withTrigger(trigger)
|
|
||||||
.withDescription(description) // required
|
|
||||||
.withDuration(duration)
|
|
||||||
.withRepeatCount(repeatCount)
|
|
||||||
.buildDisplay();
|
|
||||||
|
|
||||||
// Email alarm
|
|
||||||
Alarm emailAlarm = Alarm.builder()
|
|
||||||
.withTrigger(trigger)
|
|
||||||
.withDescription(description) // required
|
|
||||||
.withSummary(summary) // required
|
|
||||||
.withAttendees(attendees) // required
|
|
||||||
.withDuration(duration)
|
|
||||||
.withRepeatCount(repeatCount)
|
|
||||||
.buildEmail();
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Component Properties
|
|
||||||
|
|
||||||
Component properties live in `com.onixbyte.calendar.component.property` and implement `ComponentProperty`.
|
|
||||||
|
|
||||||
| Class | iCalendar Property | Description |
|
|
||||||
|---------------------------|--------------------|----------------------------------------------------------|
|
|
||||||
| `Action` | `ACTION` | Alarm action type (AUDIO, DISPLAY, EMAIL) |
|
|
||||||
| `Attachment` | `ATTACH` | Document attachment |
|
|
||||||
| `Attendee` | `ATTENDEE` | Event/task attendee |
|
|
||||||
| `Categories` | `CATEGORIES` | Categories or tags |
|
|
||||||
| `Classification` | `CLASS` | Access classification (PUBLIC, PRIVATE, CONFIDENTIAL) |
|
|
||||||
| `Comment` | `COMMENT` | Comment |
|
|
||||||
| `Contact` | `CONTACT` | Contact information |
|
|
||||||
| `DateTimeCompleted` | `COMPLETED` | Completion date-time |
|
|
||||||
| `DateTimeCreated` | `CREATED` | Creation date-time |
|
|
||||||
| `DateTimeDue` | `DUE` | Due date-time |
|
|
||||||
| `DateTimeEnd` | `DTEND` | End date-time |
|
|
||||||
| `DateTimeStamp` | `DTSTAMP` | Date-time stamp |
|
|
||||||
| `DateTimeStart` | `DTSTART` | Start date-time |
|
|
||||||
| `Description` | `DESCRIPTION` | Description |
|
|
||||||
| `ExceptionDateTimes` | `EXDATE` | Exception date-times |
|
|
||||||
| `FreeBusyTime` | `FREEBUSY` | Free/busy time periods |
|
|
||||||
| `GeographicPosition` | `GEO` | Geographic position (lat/long) |
|
|
||||||
| `LastModified` | `LAST-MODIFIED` | Last modified date-time |
|
|
||||||
| `Location` | `LOCATION` | Location |
|
|
||||||
| `Organiser` | `ORGANIZER` | Event organiser |
|
|
||||||
| `PercentComplete` | `PERCENT-COMPLETE` | Completion percentage |
|
|
||||||
| `Priority` | `PRIORITY` | Priority level |
|
|
||||||
| `RecurrenceDateTimes` | `RDATE` | Recurrence date-times |
|
|
||||||
| `RecurrenceId` | `RECURRENCE-ID` | Recurrence instance identifier |
|
|
||||||
| `RecurrenceRule` | `RRULE` | Recurrence rule |
|
|
||||||
| `RelatedTo` | `RELATED-TO` | Related component reference |
|
|
||||||
| `RepeatCount` | `REPEAT` | Alarm repeat count |
|
|
||||||
| `RequestStatus` | `REQUEST-STATUS` | Request status |
|
|
||||||
| `Resources` | `RESOURCES` | Resources |
|
|
||||||
| `SequenceNumber` | `SEQUENCE` | Sequence number (revision) |
|
|
||||||
| `Status` | `STATUS` | Component status (TENTATIVE, CONFIRMED, CANCELLED, etc.) |
|
|
||||||
| `Summary` | `SUMMARY` | Title or summary |
|
|
||||||
| `TimeTransparency` | `TRANSP` | Time transparency (OPAQUE, TRANSPARENT) |
|
|
||||||
| `TimeZoneIdentifier` | `TZID` | Time zone identifier |
|
|
||||||
| `TimeZoneName` | `TZNAME` | Time zone name |
|
|
||||||
| `TimeZoneOffsetFrom` | `TZOFFSETFROM` | Time zone offset from UTC |
|
|
||||||
| `TimeZoneOffsetTo` | `TZOFFSETTO` | Time zone offset to UTC |
|
|
||||||
| `TimeZoneUrl` | `TZURL` | Time zone URL |
|
|
||||||
| `Trigger` | `TRIGGER` | Alarm trigger |
|
|
||||||
| `UniformResourceLocator` | `URL` | Associated URL |
|
|
||||||
| `UniqueIdentifier` | `UID` | Unique identifier |
|
|
||||||
| `CustomComponentProperty` | `X-*` | Custom component properties |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Parameters
|
|
||||||
|
|
||||||
Parameters in `com.onixbyte.calendar.parameter` provide additional qualifiers on component properties.
|
|
||||||
|
|
||||||
| Class | iCalendar Parameter | Description |
|
|
||||||
|-------------------------------|---------------------|--------------------------------------------------------|
|
|
||||||
| `AlarmTriggerRelationship` | `RELATED` | Trigger relationship (START, END) |
|
|
||||||
| `AlternateTextRepresentation` | `ALTREP` | Alternate text URI |
|
|
||||||
| `CalendarUserType` | `CUTYPE` | Calendar user type (INDIVIDUAL, GROUP, RESOURCE, etc.) |
|
|
||||||
| `CommonName` | `CN` | Common name |
|
|
||||||
| `Delegatees` | `DELEGATED-TO` | Delegatees |
|
|
||||||
| `Delegators` | `DELEGATED-FROM` | Delegators |
|
|
||||||
| `DirectoryEntryReference` | `DIR` | Directory entry URI |
|
|
||||||
| `FormatType` | `FMTTYPE` | Format type (MIME type) |
|
|
||||||
| `FreeBusyTimeType` | `FBTYPE` | Free/busy time type |
|
|
||||||
| `InlineEncoding` | `ENCODING` | Inline encoding (BASE64) |
|
|
||||||
| `Language` | `LANGUAGE` | Language |
|
|
||||||
| `Membership` | `MEMBER` | Group membership |
|
|
||||||
| `ParticipationRole` | `ROLE` | Participation role (CHAIR, REQ-PARTICIPANT, etc.) |
|
|
||||||
| `ParticipationStatus` | `PARTSTAT` | Participation status (ACCEPTED, DECLINED, etc.) |
|
|
||||||
| `RecurrenceIdentifierRange` | `RANGE` | Recurrence range (THISANDPRIOR, THISANDFUTURE) |
|
|
||||||
| `RelationshipType` | `RELTYPE` | Relationship type (PARENT, CHILD, SIBLING) |
|
|
||||||
| `RsvpExpectation` | `RSVP` | RSVP expectation (TRUE, FALSE) |
|
|
||||||
| `SentBy` | `SENT-BY` | Sent by |
|
|
||||||
| `TimeZoneIdentifier` | `TZID` | Time zone identifier |
|
|
||||||
| `ValueDataType` | `VALUE` | Value data type (DATE, DATE-TIME, etc.) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recurrence Types
|
|
||||||
|
|
||||||
Located in `com.onixbyte.calendar.recurrence`.
|
|
||||||
|
|
||||||
| Class | Description |
|
|
||||||
|--------------|---------------------------------------------------------------------------|
|
|
||||||
| `Frequency` | Frequency constants (DAILY, WEEKLY, MONTHLY, YEARLY) for recurrence rules |
|
|
||||||
| `WeekdayNum` | Weekday number for recurrence rules (e.g., 2nd Monday) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Value Types
|
|
||||||
|
|
||||||
Located in `com.onixbyte.calendar.value`.
|
|
||||||
|
|
||||||
| Class | Description |
|
|
||||||
|---------------------|------------------------------------------------|
|
|
||||||
| `FreeBusyTimeValue` | Represents free/busy time value |
|
|
||||||
| `PeriodOfTime` | Represents a period of time with start and end |
|
|
||||||
| `PropertyValue` | Base value type for properties |
|
|
||||||
| `UtcOffset` | UTC offset value (e.g., `-05:00`, `+01:00`) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Formatted Output
|
|
||||||
|
|
||||||
All components and properties provide a `formatted()` method that returns an iCalendar-compliant string representation. The `Calendar.formatted()` method produces a complete `.ics` output wrapped in `BEGIN:VCALENDAR`/`END:VCALENDAR`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
String ics = calendar.formatted();
|
|
||||||
// BEGIN:VCALENDAR
|
|
||||||
// CALSCALE:GREGORIAN
|
|
||||||
// METHOD:PUBLISH
|
|
||||||
// ...
|
|
||||||
// END:VCALENDAR
|
|
||||||
```
|
|
||||||
@@ -1,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.
|
|
||||||
@@ -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.
|
|
||||||
@@ -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
|
||||||
|
---
|
||||||
@@ -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
@@ -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/"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -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`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -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 端点考虑其性能影响。
|
|
||||||
@@ -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` 预览。
|
|
||||||
@@ -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行代码,你应该考虑审查整个文件,并检查新添加的内容。它们是否会降低现有代码的质量?它们是否会导致现有功能成为重构的候选对象?
|
|
||||||
|
|
||||||
如果不在函数/方法或类的背景下审查添加的代码,则随着时间的流逝,你将会得到一个面临无法维护、纠缠不清、不易于测试等问题的类,而且这个类很难扩展或重构。
|
|
||||||
|
|
||||||
请记住,即便是微不足道的改进,随着时间的推移,也可能导致产品出现缺陷,同样,即便是轻微的代码降级或技术负债也可能在日积月累下导致产品难以维护和扩展。
|
|
||||||
|
|
||||||
## 在代码审查期间认同和鼓励出色的工作
|
|
||||||
|
|
||||||
如果看到出色的代码变更,请别忘了大力表扬和鼓励作者。代码审查的目的不仅仅是发现错误,而且还应该鼓励和指导开发人员出色的工作。
|
|
||||||
|
|
||||||
## 在代码审查期间应保持谨慎、尊重、友善和思路清晰
|
|
||||||
|
|
||||||
在代码审查期间,你应该保持友善、思路清晰、有礼貌和尊重别人,这一点至关重要,同时也要给予作者清晰的反馈和积极的帮助。在审查代码时,你需要做到对事不对人,即对代码做出评论,而非开发人员。
|
|
||||||
|
|
||||||
## 详细解释代码审查的意见,并注意尺度
|
|
||||||
|
|
||||||
每当代码审查意见提出替代方案或指出某些问题时,重要的是你需要解释其中的原因,并根据个人的知识和经验提供示例,以帮助开发人员理解为何你的建议能够提升代码质量。
|
|
||||||
|
|
||||||
在建议修改或变更代码时,你需要在如何指导作者修改代码方面找到适当的平衡。例如,我更喜欢指导、解释、提示或建议,而不是整个解决方案。
|
|
||||||
@@ -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 服务器是否持续下发不期望的主机名。
|
|
||||||
@@ -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**
|
|
||||||
|
|
||||||
这种方式虽然需要命令行操作,但提供了更强大和灵活的管理能力,特别适合自动化部署和脚本化管理。
|
|
||||||
@@ -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` 激活。
|
|
||||||
@@ -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。
|
|
||||||
@@ -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 端口的通信。
|
|
||||||
@@ -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` 相似的查询性能和灵活性。
|
|
||||||
@@ -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
@@ -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: 或者扫码加企业微信
|
||||||
---
|
---
|
||||||
@@ -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**
|
||||||
|
|
||||||
|
长沙曜珀科技有限公司
|
||||||
|
中国湖南省
|
||||||
|
|
||||||
|
## 政策变更
|
||||||
|
|
||||||
|
如果我们开始收集任何信息,会在收集之前先在本页说明。页面顶部的日期为最近一次修改时间。
|
||||||
@@ -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**
|
||||||
|
|
||||||
|
长沙曜珀科技有限公司
|
||||||
|
中国湖南省
|
||||||
@@ -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 许可证发布的开源软件。
|
|
||||||
@@ -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
Reference in new issue
Block a user