Compare commits
25
Commits
776ddd28c1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06fd31576a | ||
|
|
965b92b47d | ||
|
|
112d0f3be4 | ||
|
|
c732624580 | ||
|
|
21f14f8c05 | ||
|
|
1c675c9f53 | ||
|
|
dedf95865e | ||
|
|
9d2fc024ea | ||
|
|
6f29904349 | ||
|
|
08c18fea90 | ||
|
|
83f3120fdc | ||
|
|
9b642c85d0
|
||
|
|
d5074a92a2 | ||
|
|
d5109e1333 | ||
|
|
0d3b0bd28d | ||
|
|
a00488be5d | ||
|
|
29ce5e45f1 | ||
|
|
d3723ba485 | ||
|
|
944d176240 | ||
|
|
1ae17823ef | ||
|
|
b0ccd8f832 | ||
|
|
3b0bd56001 | ||
|
|
8aaad677b6 | ||
|
|
9139d0c451 | ||
|
|
faff32475f |
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# Docker Compose configuration for Helix.
|
||||
# Copy to `.env` and adjust: cp .env.example .env
|
||||
|
||||
# PostgreSQL
|
||||
PG_DATABASE=helix
|
||||
PG_USER=helix
|
||||
PG_PASSWORD=helix
|
||||
|
||||
# Redis (keep empty for a local no-password Redis)
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# JWT signing secret — change this in any non-local deployment
|
||||
TOKEN_SECRET=please-change-this-secret
|
||||
|
||||
# Microsoft Entra ID single sign-on (optional for local runs)
|
||||
MSAL_CLIENT_ID=
|
||||
MSAL_TENANT_ID=
|
||||
|
||||
# S3-compatible object storage (optional — requires enabling `app.asset.enabled`
|
||||
# in docker/application-prod.yml and providing the credentials below)
|
||||
S3_ACCESS_KEY_ID=
|
||||
S3_SECRET_ACCESS_KEY=
|
||||
|
||||
# Host ports
|
||||
SERVER_PORT=8080
|
||||
WEB_PORT=80
|
||||
+25
-7
@@ -124,9 +124,9 @@ hs_err_pid*
|
||||
replay_pid*
|
||||
|
||||
# Config
|
||||
config/*.yml
|
||||
config/*.yaml
|
||||
config/*.properties
|
||||
**/config/*.yml
|
||||
**/config/*.yaml
|
||||
**/config/*.properties
|
||||
|
||||
# Tests
|
||||
test/
|
||||
@@ -136,10 +136,11 @@ test/
|
||||
**/build/
|
||||
!**/src/**/build/
|
||||
|
||||
!gradle/
|
||||
!gradle/wrapper
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!gradle/wrapper/gradle-wrapper.properties
|
||||
# Keep the Gradle wrapper (checked in per Gradle convention) tracked at any depth
|
||||
!**/gradle/
|
||||
!**/gradle/wrapper
|
||||
!**/gradle/wrapper/gradle-wrapper.jar
|
||||
!**/gradle/wrapper/gradle-wrapper.properties
|
||||
gradle-app.setting
|
||||
.gradletasknamecache
|
||||
|
||||
@@ -148,3 +149,20 @@ gradle-app.setting
|
||||
.project
|
||||
# JDT-specific (Eclipse Java Development Tools)
|
||||
.classpath
|
||||
|
||||
### Frontend
|
||||
|
||||
# Node libraries
|
||||
node_modules/
|
||||
|
||||
# Config
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
|
||||
# Claude Code local tooling
|
||||
.claude/
|
||||
|
||||
### Docker Compose
|
||||
# Root .env holds Compose secrets; use .env.example as a template
|
||||
/.env
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"java.compile.nullAnalysis.mode": "disabled",
|
||||
"java.configuration.updateBuildConfiguration": "automatic"
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# AGENTS.md
|
||||
|
||||
Guidance for AI coding agents and contributors working in this repository.
|
||||
It complements [README.md](README.md); read that first for the project overview.
|
||||
|
||||
> **Scope — this is a quick-start boilerplate.** Helix is a starting point for
|
||||
> custom development, not a finished product. The baseline modules
|
||||
> (authentication, RBAC, user/role/department/position/menu management) are
|
||||
> intentionally kept minimal and exist to be extended. When adding features,
|
||||
> build on the existing patterns below; do not introduce parallel
|
||||
> conventions.
|
||||
|
||||
## Repository overview
|
||||
|
||||
Helix is a monorepo containing a backend API and a frontend SPA:
|
||||
|
||||
- **`server/`** — Spring Boot 3.5 REST API. Java 21, Gradle, Spring Security,
|
||||
Spring Data JPA + MyBatis, Flyway, PostgreSQL, Redis, Auth0 java-jwt,
|
||||
AWS SDK (S3). Package root: `com.onixbyte.helix`.
|
||||
- **`web/`** — React 19 SPA. TypeScript, Vite 7, Ant Design 6, Tailwind CSS 4,
|
||||
Redux Toolkit, React Router 7, Axios, MSAL.
|
||||
|
||||
## Build and test
|
||||
|
||||
Server (requires JDK 21):
|
||||
|
||||
```bash
|
||||
cd server
|
||||
./gradlew build # compiles and runs the JUnit 5 test suite
|
||||
./gradlew bootRun # starts the API on port 8080
|
||||
```
|
||||
|
||||
Web (requires Node 22+ and pnpm):
|
||||
|
||||
```bash
|
||||
cd web
|
||||
pnpm install
|
||||
pnpm dev # Vite dev server
|
||||
pnpm build # tsc -b && vite build
|
||||
pnpm lint # ESLint
|
||||
```
|
||||
|
||||
## Server conventions
|
||||
|
||||
- **Layering.** Follow the strict call chain
|
||||
`Controller → Service → Manager → Repository` (MyBatis Mappers sit alongside
|
||||
JPA repositories for query-heavy reads). Controllers stay thin: parse and
|
||||
validate input, delegate to a service, return a response DTO. Business rules
|
||||
live in services; transactional use-case orchestration lives in managers.
|
||||
- **Validation.** Use the custom validation framework in
|
||||
`com.onixbyte.helix.validation` (`annotation` / `validator` / `executor` /
|
||||
`group`) instead of ad-hoc checks where possible.
|
||||
- **Errors and i18n.** Throw `BizException(HttpStatus, MessageName.X, args...)`.
|
||||
Message keys are constants declared in `com.onixbyte.helix.shared.MessageName`
|
||||
and resolved from the bundles in `src/main/resources/i18n`
|
||||
(`messages.properties`, `messages_en_GB.properties`, `messages_zh_CN.properties`).
|
||||
Always add a new key to **all** bundles, in the default language and in Chinese.
|
||||
- **Authentication.** Spring Security with a JWT stored in a cookie.
|
||||
`TokenAuthenticationFilter` (under `com.onixbyte.helix.filter`) validates the
|
||||
token; the `X-Authorisation` header carries the exchanged token. Sign-in
|
||||
providers (Entra ID/MSAL, WeCom) live under `service/auth`. MFA is supported.
|
||||
- **Persistence.** The schema is owned by Flyway — add migrations under
|
||||
`src/main/resources/db/migrations/`; `ddl-auto` is disabled. MyBatis XML
|
||||
mappers live in `src/main/resources/mapper/`. JPA entities go in
|
||||
`com.onixbyte.helix.domain.entity`.
|
||||
- **Shared utilities.** Prefer the Onixbyte libraries already on the classpath
|
||||
(`identity-generator`, `tuple`, `common-toolbox`, `math-toolbox`) over
|
||||
reimplementing them.
|
||||
- **Style.** UTF-8 source, Javadoc on public APIs, constructor injection with
|
||||
`final` fields, builder/record patterns for DTOs. Match the surrounding code.
|
||||
- **Configuration.** Runtime config is externalised via environment variables
|
||||
(reference: `config/application-prod.yml.example`). Never hardcode secrets.
|
||||
Local `config/*.yml` files are gitignored.
|
||||
|
||||
## Web conventions
|
||||
|
||||
- **State.** Redux Toolkit slices in `src/store` (e.g. `auth-slice`);
|
||||
consume via `react-redux` hooks.
|
||||
- **API calls.** Typed per-domain clients in `src/api` (`auth`, `user`, `role`,
|
||||
`department`, `position`, `menu`). They use the shared Axios instance in
|
||||
`src/client/web-client/index.ts`, whose `baseURL` comes from
|
||||
`VITE_API_BASE_URL` and which sends credentials (`withCredentials`).
|
||||
- **Types.** Request/response types live in `src/types`.
|
||||
- **Routing.** `react-router` 7 routes are declared in `src/router/index.tsx`.
|
||||
- **UI.** Ant Design 6 components styled with Tailwind utility classes.
|
||||
- **Environment.** `VITE_*` variables (see `.env.example`). The Vite dev server
|
||||
has no proxy — for local development point `VITE_API_BASE_URL` at the backend
|
||||
directly (e.g. `http://localhost:8080`).
|
||||
|
||||
## General guidelines
|
||||
|
||||
- **Custom development.** When extending the boilerplate, use the existing
|
||||
extension points rather than parallel patterns: add Flyway migrations for
|
||||
schema changes, register message keys in every i18n bundle, implement features
|
||||
through the standard `Controller → Service → Manager → Repository` chain, and
|
||||
expose typed API clients in `web/src/api`. Customise the baseline — do not
|
||||
re-architect it.
|
||||
- **Commits.** Use Conventional Commits (`feat:`, `fix:`, `refactor:`, `chore:`,
|
||||
`docs:`, `perf:`, `style:`, `test:`). Match the language of the surrounding
|
||||
history (the log is a mix of English and Chinese).
|
||||
- **Secrets.** Never commit `.env` files, local config, or credentials.
|
||||
`web/.dockerignore` excludes local env files from the build context — keep it
|
||||
that way.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Contributing to Helix
|
||||
|
||||
Thanks for your interest in contributing! Helix is a quick-start boilerplate for
|
||||
custom development, open source under the [MIT licence](LICENCE). This guide
|
||||
explains how to set up your environment, make changes and get them merged.
|
||||
|
||||
## What to contribute
|
||||
|
||||
Contributions should make the **boilerplate itself** more useful to everyone who
|
||||
forks it:
|
||||
|
||||
- New or improved baseline capabilities (reusable, not business-specific)
|
||||
- Bug fixes and hardening
|
||||
- Documentation and tests
|
||||
|
||||
Changes that encode one particular organisation's custom rules are usually
|
||||
better kept in your own fork. If you are not sure whether something belongs in
|
||||
the boilerplate, open an issue first and ask.
|
||||
|
||||
## Getting started
|
||||
|
||||
1. Fork the repository and clone your fork.
|
||||
2. Install the prerequisites:
|
||||
- Backend: JDK 21 and the Gradle wrapper (bundled in `server/`)
|
||||
- Frontend: Node.js 22+ and [pnpm](https://pnpm.io/)
|
||||
- Optional: Docker + Docker Compose for the full-stack run
|
||||
3. Follow the [Quick start](README.md#quick-start) in the README to run the app
|
||||
locally.
|
||||
|
||||
## Development workflow
|
||||
|
||||
- Create a feature branch from `main`, using a short prefix for clarity:
|
||||
`feat/`, `fix/`, `refactor/`, `chore/` or `docs/` (e.g. `feat/role-audit-log`).
|
||||
- Keep pull requests small and focused on a single concern.
|
||||
- Write or update tests for the code you touch.
|
||||
- Open a pull request against `main` and describe what changed and why.
|
||||
|
||||
## Code style
|
||||
|
||||
The full conventions are documented in [AGENTS.md](AGENTS.md). The essentials:
|
||||
|
||||
**Backend (`server/`)**
|
||||
|
||||
- Follow the `Controller → Service → Manager → Repository` layering.
|
||||
- Use the custom validation framework for input validation.
|
||||
- Throw `BizException` with a `MessageName` constant and add the message key to
|
||||
every bundle under `src/main/resources/i18n`.
|
||||
- Add Flyway migrations for schema changes under `src/main/resources/db/migrations/`.
|
||||
- Run `./gradlew build` before opening a PR.
|
||||
|
||||
**Frontend (`web/`)**
|
||||
|
||||
- Use ESLint and Prettier formatting; run `pnpm lint`.
|
||||
- Keep API clients in `src/api` and state in Redux Toolkit slices.
|
||||
- Never commit local `.env` files.
|
||||
|
||||
## Commit messages
|
||||
|
||||
Use [Conventional Commits](https://www.conventionalcommits.org/):
|
||||
|
||||
```
|
||||
feat: support TOTP multi-factor authentication
|
||||
fix: resolve department name uniqueness check
|
||||
refactor: extract build cookie
|
||||
docs: update deployment guide
|
||||
chore(deps): upgrade spring boot to 3.5.4
|
||||
```
|
||||
|
||||
## Reporting issues
|
||||
|
||||
When opening an issue, include:
|
||||
|
||||
- What you expected to happen and what actually happened
|
||||
- Steps to reproduce
|
||||
- Environment details (OS, Java/Node versions, deployment mode)
|
||||
|
||||
Security issues: please report them privately to the maintainers rather than
|
||||
opening a public issue.
|
||||
|
||||
## License
|
||||
|
||||
By contributing, you agree that your contributions are licensed under the
|
||||
[MIT licence](LICENCE).
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2026 OnixByte
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
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
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
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.
|
||||
@@ -0,0 +1,176 @@
|
||||
# Helix
|
||||
|
||||
Helix is a **quick-start boilerplate** for building custom identity and organisation
|
||||
management systems. It ships with a working full-stack baseline — authentication,
|
||||
role-based access control, and user/role/department/position/menu management — so
|
||||
you can start from a runnable application and customise it to your own
|
||||
requirements instead of starting from zero.
|
||||
|
||||
The boilerplate is organised as a monorepo:
|
||||
|
||||
- **`server/`** — Spring Boot 3 REST API (Java 21, PostgreSQL, Redis, Flyway)
|
||||
- **`web/`** — React 19 single-page application (Vite, Ant Design, Tailwind CSS, Redux Toolkit)
|
||||
- **`docker/`** — deployment support files used by Docker Compose
|
||||
|
||||
> Helix is a starting point, not a finished product. Every baseline module is
|
||||
> meant to be extended or replaced as part of your custom development.
|
||||
|
||||
## Baseline capabilities
|
||||
|
||||
Out of the box the boilerplate provides:
|
||||
|
||||
- **Authentication & single sign-on** — username/password login with captcha,
|
||||
Microsoft Entra ID (MSAL) sign-in, WeCom (WeChat Work) login and MFA
|
||||
- **User management** — create, edit and deactivate users; assign roles, departments and positions
|
||||
- **Role-based access control** — roles with fine-grained authorities, enforced across all endpoints
|
||||
- **Organisation management** — departments, positions and custom navigation menus
|
||||
- **Asset storage** — optional S3-compatible object storage for uploaded files
|
||||
- **Internationalisation** — English (`en-GB`) and Chinese (`zh-CN`) message bundles
|
||||
- **Responsive admin UI** — Ant Design + Tailwind CSS front end
|
||||
|
||||
## Tech stack
|
||||
|
||||
| Layer | Technologies |
|
||||
| ----------- | ------------ |
|
||||
| Backend | Java 21 · Spring Boot 3.5 · Spring Security · Spring Data JPA · MyBatis · Flyway · PostgreSQL · Redis · Auth0 java-jwt · AWS SDK for Java (S3) |
|
||||
| Frontend | React 19 · TypeScript · Vite 7 · Ant Design 6 · Tailwind CSS 4 · Redux Toolkit · React Router 7 · Axios · MSAL |
|
||||
| Infra | Docker · Docker Compose · Caddy |
|
||||
|
||||
## Repository layout
|
||||
|
||||
```
|
||||
.
|
||||
├── server/ # Spring Boot REST API
|
||||
│ ├── src/main/java/com/onixbyte/helix/
|
||||
│ │ ├── controller/ # HTTP entry points
|
||||
│ │ ├── service/ # business logic
|
||||
│ │ ├── manager/ # use-case orchestration
|
||||
│ │ ├── repository/ # JPA repositories
|
||||
│ │ ├── mapper/ # MyBatis mappers
|
||||
│ │ ├── validation/ # custom validation framework
|
||||
│ │ └── security/ # JWT filter, security configuration
|
||||
│ ├── src/main/resources/
|
||||
│ │ ├── db/migrations/ # Flyway SQL migrations
|
||||
│ │ ├── i18n/ # message bundles (en-GB, zh-CN)
|
||||
│ │ └── mapper/ # MyBatis XML mappers
|
||||
│ ├── config/ # runtime config (see application-prod.yml.example)
|
||||
│ └── Dockerfile
|
||||
├── web/ # React SPA
|
||||
│ ├── src/api/ # typed API clients (auth, user, role, …)
|
||||
│ ├── src/page/ # page components
|
||||
│ ├── src/store/ # Redux slices
|
||||
│ ├── src/router/ # react-router routes
|
||||
│ ├── Dockerfile # builds the SPA and serves it with Caddy
|
||||
│ └── Caddyfile # static files + /api reverse proxy
|
||||
├── docker/ # Compose support files
|
||||
│ └── application-prod.yml # server config mounted into the container
|
||||
├── docker-compose.yaml # full-stack: postgres + redis + server + web
|
||||
└── .env.example # Compose environment template
|
||||
```
|
||||
|
||||
## Customising the boilerplate
|
||||
|
||||
Helix is designed to be forked and extended. The recommended approach:
|
||||
|
||||
1. **Fork (or copy) the repository** and rename the project to your own.
|
||||
2. **Change the branding** — application title (`VITE_APP_TITLE`), regions
|
||||
(`VITE_DEFAULT_REGION_ABBREVIATION`), JWT issuer, S3 buckets and so on.
|
||||
3. **Build on the baseline modules.** The existing patterns — custom validation
|
||||
framework, `BizException` + `MessageName` error handling, JWT authentication,
|
||||
Redux slices and typed API clients — are the extension points for your own
|
||||
features. Follow the conventions in [AGENTS.md](AGENTS.md).
|
||||
4. **Add schema changes as Flyway migrations** under
|
||||
`server/src/main/resources/db/migrations/` and add new message keys to
|
||||
**all** i18n bundles.
|
||||
|
||||
## Quick start
|
||||
|
||||
### Full stack with Docker Compose
|
||||
|
||||
Requires [Docker](https://www.docker.com/) with the Compose plugin.
|
||||
|
||||
```bash
|
||||
cp .env.example .env # optional: adjust ports, secrets and integrations
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Once the containers are healthy:
|
||||
|
||||
| Service | URL |
|
||||
| ------------------ | ---------------------------------- |
|
||||
| Web UI | <http://localhost> |
|
||||
| REST API | <http://localhost:8080> |
|
||||
| Health check | <http://localhost:8080/actuator/health> |
|
||||
|
||||
Login works out of the box with username/password + captcha. Microsoft Entra ID
|
||||
and WeCom sign-in require configuring their credentials (see below).
|
||||
|
||||
### Manual development
|
||||
|
||||
#### Server (JDK 21)
|
||||
|
||||
```bash
|
||||
cd server
|
||||
cp config/application-prod.yml.example config/application-prod.yml # edit to match your environment
|
||||
SPRING_PROFILES_ACTIVE=prod ./gradlew bootRun
|
||||
```
|
||||
|
||||
The API starts on <http://localhost:8080>.
|
||||
|
||||
#### Web (Node 22+ with pnpm)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
pnpm install
|
||||
cp .env.example .env.development.local # point the API base URL at your dev server
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Set `VITE_API_BASE_URL=http://localhost:8080` in `.env.development.local` to call
|
||||
the locally running API directly. The Vite dev server listens on
|
||||
<http://localhost:5173>.
|
||||
|
||||
## Configuration
|
||||
|
||||
Runtime configuration is externalised through environment variables (see
|
||||
`server/config/application-prod.yml.example` for the complete reference).
|
||||
|
||||
### Docker Compose (`.env`)
|
||||
|
||||
| Variable | Default | Description |
|
||||
| -------------------------- | ------------------------- | ---------------------------------------------- |
|
||||
| `PG_DATABASE` | `helix` | PostgreSQL database name |
|
||||
| `PG_USER` | `helix` | PostgreSQL user |
|
||||
| `PG_PASSWORD` | `helix` | PostgreSQL password |
|
||||
| `REDIS_PASSWORD` | *(empty)* | Redis password (leave empty for local Redis) |
|
||||
| `TOKEN_SECRET` | *(see example)* | JWT signing secret — change in production |
|
||||
| `MSAL_CLIENT_ID` | *(empty)* | Microsoft Entra ID application (client) ID |
|
||||
| `MSAL_TENANT_ID` | *(empty)* | Microsoft Entra ID tenant ID |
|
||||
| `S3_ACCESS_KEY_ID` | *(empty)* | S3 access key (requires enabling asset storage)|
|
||||
| `S3_SECRET_ACCESS_KEY` | *(empty)* | S3 secret key |
|
||||
| `SERVER_PORT` | `8080` | Host port mapped to the server container |
|
||||
| `WEB_PORT` | `80` | Host port mapped to the Caddy container |
|
||||
|
||||
Asset storage is disabled in `docker/application-prod.yml` by default. To enable
|
||||
it, set `app.asset.enabled: true` there and provide the `S3_*` variables.
|
||||
|
||||
### Web (`.env`)
|
||||
|
||||
| Variable | Default | Description |
|
||||
| ------------------------------- | ---------------------------------- | ---------------------------------- |
|
||||
| `VITE_API_BASE_URL` | `/api` | Backend base URL |
|
||||
| `VITE_MSAL_CLIENT_ID` | *(empty)* | Microsoft Entra ID client ID |
|
||||
| `VITE_MSAL_TENANT_ID` | *(empty)* | Microsoft Entra ID tenant ID |
|
||||
| `VITE_DEFAULT_REGION_ABBREVIATION` | `GB` | Default user region (ISO 3166-1 alpha-2) |
|
||||
| `VITE_APP_TITLE` | `Onixbyte Technology Co., Ltd` | Application title shown in the UI |
|
||||
|
||||
## Development
|
||||
|
||||
- Architecture and conventions are documented in [AGENTS.md](AGENTS.md).
|
||||
- See [CONTRIBUTING.md](CONTRIBUTING.md) for how to contribute.
|
||||
- Database schema is managed with Flyway — add migrations under
|
||||
`server/src/main/resources/db/migrations/` rather than relying on JPA `ddl-auto`.
|
||||
|
||||
## Licence
|
||||
|
||||
[MIT](LICENCE) © OnixByte
|
||||
@@ -0,0 +1,81 @@
|
||||
name: helix
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_DB: ${PG_DATABASE:-helix}
|
||||
POSTGRES_USER: ${PG_USER:-helix}
|
||||
POSTGRES_PASSWORD: ${PG_PASSWORD:-helix}
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${PG_USER:-helix} -d ${PG_DATABASE:-helix}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
command: ["redis-server", "--appendonly", "yes"]
|
||||
volumes:
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
server:
|
||||
build:
|
||||
context: ./server
|
||||
args:
|
||||
ARTEFACT_VERSION: ${ARTEFACT_VERSION:-0.0.1}
|
||||
environment:
|
||||
SPRING_PROFILES_ACTIVE: prod
|
||||
PG_HOST: postgres
|
||||
PG_PORT: "5432"
|
||||
PG_DATABASE: ${PG_DATABASE:-helix}
|
||||
PG_USER: ${PG_USER:-helix}
|
||||
PG_PASSWORD: ${PG_PASSWORD:-helix}
|
||||
REDIS_HOST: redis
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_PASSWORD: ${REDIS_PASSWORD:-}
|
||||
TOKEN_SECRET: ${TOKEN_SECRET:-please-change-this-secret}
|
||||
MSAL_CLIENT_ID: ${MSAL_CLIENT_ID:-}
|
||||
MSAL_TENANT_ID: ${MSAL_TENANT_ID:-}
|
||||
S3_ACCESS_KEY_ID: ${S3_ACCESS_KEY_ID:-}
|
||||
S3_SECRET_ACCESS_KEY: ${S3_SECRET_ACCESS_KEY:-}
|
||||
volumes:
|
||||
# Mounts the Compose-specific server configuration (Spring Boot picks up
|
||||
# config/application-{profile}.yml next to the working directory).
|
||||
- ./docker/application-prod.yml:/app/config/application-prod.yml:ro
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "${SERVER_PORT:-8080}:8080"
|
||||
restart: unless-stopped
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./web
|
||||
args:
|
||||
VITE_API_BASE_URL: ${VITE_API_BASE_URL:-/api}
|
||||
VITE_MSAL_CLIENT_ID: ${VITE_MSAL_CLIENT_ID:-}
|
||||
VITE_MSAL_TENANT_ID: ${VITE_MSAL_TENANT_ID:-}
|
||||
VITE_APP_TITLE: ${VITE_APP_TITLE:-Helix}
|
||||
VITE_DEFAULT_REGION_ABBREVIATION: ${VITE_DEFAULT_REGION_ABBREVIATION:-GB}
|
||||
depends_on:
|
||||
- server
|
||||
ports:
|
||||
- "${WEB_PORT:-80}:80"
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
@@ -0,0 +1,77 @@
|
||||
# Server configuration used by docker-compose.yaml (mounted into the server
|
||||
# container at /app/config/application-prod.yml).
|
||||
#
|
||||
# All values resolve from environment variables set by the Compose services.
|
||||
# For a standalone deployment, base your own config on
|
||||
# server/config/application-prod.yml.example instead.
|
||||
|
||||
spring:
|
||||
data:
|
||||
redis:
|
||||
# Redis 主机
|
||||
host: ${REDIS_HOST:-localhost}
|
||||
# Redis 端口
|
||||
port: ${REDIS_PORT:-6379}
|
||||
# Redis 数据库索引
|
||||
database: ${REDIS_DATABASE_INDEX:-0}
|
||||
# Redis 数据库密码
|
||||
password: ${REDIS_PASSWORD:-}
|
||||
datasource:
|
||||
# 数据库连接池类型
|
||||
type: com.zaxxer.hikari.HikariDataSource
|
||||
# 数据库驱动类
|
||||
driver-class-name: org.postgresql.Driver
|
||||
# 数据库 URL
|
||||
url: jdbc:postgresql://${PG_HOST:-localhost}:${PG_PORT:-5432}/${PG_DATABASE:-helix}
|
||||
# 数据库用户
|
||||
username: ${PG_USER:-helix}
|
||||
# 数据库密码
|
||||
password: ${PG_PASSWORD:-helix}
|
||||
|
||||
app:
|
||||
asset:
|
||||
# Disabled by default for a local full-stack run.
|
||||
# Set `enabled: true` and provide S3_* environment variables to enable
|
||||
# S3-compatible object storage.
|
||||
enabled: false
|
||||
# 鉴权配置
|
||||
authentication:
|
||||
# Microsoft Entra ID 鉴权配置
|
||||
msal:
|
||||
# Microsoft Entra ID 提供的客户端 ID
|
||||
client-id: ${MSAL_CLIENT_ID:-}
|
||||
# Microsoft Entra ID 提供的租户 ID
|
||||
tenant-id: ${MSAL_TENANT_ID:-}
|
||||
# 令牌配置
|
||||
jwt:
|
||||
# 令牌签发人
|
||||
issuer: Helix Server
|
||||
# 令牌机密
|
||||
secret: ${TOKEN_SECRET:-please-change-this-secret}
|
||||
# 令牌有效期(Ref java.time.Duration)
|
||||
valid-time: PT2H
|
||||
# 跨域配置
|
||||
cors:
|
||||
# 是否允许身份验证
|
||||
allow-credentials: true
|
||||
# 是否允许私有网络
|
||||
allow-private-network: true
|
||||
# 允许的请求头列表
|
||||
allowed-headers: Content-Type
|
||||
# 允许的请求方法列表(Ref org.springframework.http.HttpMethod)
|
||||
allowed-methods:
|
||||
- GET
|
||||
- POST
|
||||
- PUT
|
||||
- PATCH
|
||||
- DELETE
|
||||
# 允许的来源域名
|
||||
allowed-origins: '*'
|
||||
# 要对外暴露的响应头列表
|
||||
exposed-headers: X-Authorisation
|
||||
# 跨域缓存时长
|
||||
max-age: PT2H
|
||||
# Captcha 配置
|
||||
captcha:
|
||||
# Captcha 长度
|
||||
length: 6
|
||||
File renamed without changes.
@@ -5,6 +5,7 @@ WORKDIR /home/app
|
||||
|
||||
COPY gradlew .
|
||||
COPY gradle gradle
|
||||
COPY gradle.properties .
|
||||
COPY build.gradle.kts .
|
||||
COPY settings.gradle.kts .
|
||||
COPY gradle/libs.versions.toml gradle/libs.versions.toml
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Vendored
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+7
-10
@@ -4,8 +4,7 @@ import com.onixbyte.helix.shared.AssetPrefix;
|
||||
import com.onixbyte.helix.domain.web.response.FileUploadResponse;
|
||||
import com.onixbyte.helix.exception.BizException;
|
||||
import com.onixbyte.helix.service.AssetService;
|
||||
import com.onixbyte.helix.shared.Message;
|
||||
import com.onixbyte.helix.utils.MessageUtil;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -27,15 +26,10 @@ public class AssetController {
|
||||
private static final Logger log = LoggerFactory.getLogger(AssetController.class);
|
||||
|
||||
private final AssetService assetService;
|
||||
private final MessageUtil messageUtil;
|
||||
|
||||
@Autowired
|
||||
public AssetController(
|
||||
AssetService assetService,
|
||||
MessageUtil messageUtil
|
||||
) {
|
||||
public AssetController(AssetService assetService) {
|
||||
this.assetService = assetService;
|
||||
this.messageUtil = messageUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +44,7 @@ public class AssetController {
|
||||
) {
|
||||
try {
|
||||
if (file.isEmpty()) {
|
||||
throw new BizException(HttpStatus.BAD_REQUEST, messageUtil.getMessage(Message.ASSET_NOT_EMPTY));
|
||||
throw new BizException(HttpStatus.BAD_REQUEST, MessageName.ASSET_NOT_EMPTY);
|
||||
}
|
||||
|
||||
var fileUrl = assetService.uploadFile(AssetPrefix.UPLOADS, file);
|
||||
@@ -63,10 +57,13 @@ public class AssetController {
|
||||
file.getSize(),
|
||||
fileUrl
|
||||
));
|
||||
} catch (BizException ex) {
|
||||
throw ex;
|
||||
} catch (Exception e) {
|
||||
log.error("File upload failed: {}", e.getMessage(), e);
|
||||
throw new BizException(HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
"Failed upload file: " + e.getMessage());
|
||||
MessageName.ASSET_UPLOAD_FAILED,
|
||||
e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
File renamed without changes.
+2
-2
@@ -5,7 +5,7 @@ import com.onixbyte.helix.domain.web.request.AuthorityRequest;
|
||||
import com.onixbyte.helix.domain.web.request.QueryAuthorityRequest;
|
||||
import com.onixbyte.helix.domain.web.response.ActionResponse;
|
||||
import com.onixbyte.helix.service.AuthorityService;
|
||||
import com.onixbyte.helix.shared.Message;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import com.onixbyte.helix.utils.MessageUtil;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
@@ -79,6 +79,6 @@ public class AuthorityController {
|
||||
@DeleteMapping("/{authorityId:\\d+}")
|
||||
public ActionResponse deleteAuthority(@PathVariable Long authorityId) {
|
||||
var name = authorityService.deleteAuthority(authorityId);
|
||||
return ActionResponse.success(messageUtil.getMessage(Message.AUTHORITY_DELETED, name));
|
||||
return ActionResponse.success(messageUtil.getMessage(MessageName.AUTHORITY_DELETED, name));
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+12
-1
@@ -2,6 +2,7 @@ package com.onixbyte.helix.controller;
|
||||
|
||||
import com.onixbyte.helix.domain.web.response.BizExceptionResponse;
|
||||
import com.onixbyte.helix.exception.BizException;
|
||||
import com.onixbyte.helix.utils.MessageUtil;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import org.springframework.context.support.DefaultMessageSourceResolvable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -27,12 +28,22 @@ import java.util.stream.Collectors;
|
||||
@RestControllerAdvice
|
||||
public class ExceptionController {
|
||||
|
||||
private final MessageUtil messageUtil;
|
||||
|
||||
public ExceptionController(MessageUtil messageUtil) {
|
||||
this.messageUtil = messageUtil;
|
||||
}
|
||||
|
||||
@ExceptionHandler(BizException.class)
|
||||
public ResponseEntity<BizExceptionResponse> handleBizException(BizException ex) {
|
||||
var message = ex.getMessageCode() == null
|
||||
? ex.getMessage()
|
||||
: messageUtil.getMessage(ex.getMessageCode(), ex.getMessageArgs());
|
||||
|
||||
return ResponseEntity.status(ex.getStatus())
|
||||
.body(new BizExceptionResponse(
|
||||
LocalDateTime.now(),
|
||||
ex.getMessage())
|
||||
message)
|
||||
);
|
||||
}
|
||||
|
||||
File renamed without changes.
File renamed without changes.
+18
-14
@@ -1,15 +1,16 @@
|
||||
package com.onixbyte.helix.controller;
|
||||
|
||||
import com.onixbyte.helix.domain.entity.Role;
|
||||
import com.onixbyte.helix.domain.web.request.AddRoleRequest;
|
||||
import com.onixbyte.helix.domain.web.request.EditRoleRequest;
|
||||
import com.onixbyte.helix.domain.web.request.QueryRoleRequest;
|
||||
import com.onixbyte.helix.domain.web.request.RoleRequest;
|
||||
import com.onixbyte.helix.domain.web.response.ActionResponse;
|
||||
import com.onixbyte.helix.service.RoleService;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import com.onixbyte.helix.utils.MessageUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -24,10 +25,12 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class RoleController {
|
||||
|
||||
private final RoleService roleService;
|
||||
private final MessageUtil messageUtil;
|
||||
|
||||
@Autowired
|
||||
public RoleController(RoleService roleService) {
|
||||
public RoleController(RoleService roleService, MessageUtil messageUtil) {
|
||||
this.roleService = roleService;
|
||||
this.messageUtil = messageUtil;
|
||||
}
|
||||
|
||||
@GetMapping
|
||||
@@ -41,20 +44,21 @@ public class RoleController {
|
||||
}
|
||||
|
||||
@PostMapping
|
||||
public ResponseEntity<Void> addRole(@Validated @RequestBody AddRoleRequest request) {
|
||||
roleService.addRole(request);
|
||||
return ResponseEntity.ok(null);
|
||||
public Role addRole(@Validated @RequestBody RoleRequest request) {
|
||||
return roleService.addRole(request);
|
||||
}
|
||||
|
||||
@PutMapping
|
||||
public ResponseEntity<Void> editRole(@Validated @RequestBody EditRoleRequest request) {
|
||||
roleService.editRole(request);
|
||||
return ResponseEntity.ok(null);
|
||||
@PutMapping("/{id:\\d+}")
|
||||
public Role editRole(
|
||||
@PathVariable Long id,
|
||||
@Validated @RequestBody RoleRequest request
|
||||
) {
|
||||
return roleService.editRole(id, request);
|
||||
}
|
||||
|
||||
@DeleteMapping("/{id:\\d+}")
|
||||
public ResponseEntity<Void> deleteRole(@PathVariable Long id) {
|
||||
roleService.deleteRole(id);
|
||||
return ResponseEntity.ok(null);
|
||||
public ActionResponse deleteRole(@PathVariable Long id) {
|
||||
var name = roleService.deleteRole(id);
|
||||
return ActionResponse.success(messageUtil.getMessage(MessageName.ROLE_DELETED, name));
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+10
-8
@@ -7,13 +7,12 @@ import com.onixbyte.helix.domain.web.request.ResetPasswordRequest;
|
||||
import com.onixbyte.helix.domain.web.response.ActionResponse;
|
||||
import com.onixbyte.helix.domain.web.response.UserDetailResponse;
|
||||
import com.onixbyte.helix.service.UserService;
|
||||
import com.onixbyte.helix.shared.Message;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import com.onixbyte.helix.utils.MessageUtil;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -82,13 +81,16 @@ public class UserController {
|
||||
/**
|
||||
* Edit a user.
|
||||
*
|
||||
* @param id user ID
|
||||
* @param request user to be edited
|
||||
* @return edited user
|
||||
*/
|
||||
@PutMapping
|
||||
public ResponseEntity<Void> editUser(@Validated @RequestBody EditUserRequest request) {
|
||||
userService.updateUser(request);
|
||||
return ResponseEntity.ok(null);
|
||||
@PutMapping("/{id:\\d+}")
|
||||
public UserDetailResponse editUser(
|
||||
@PathVariable Long id,
|
||||
@Validated @RequestBody EditUserRequest request
|
||||
) {
|
||||
return userService.updateUser(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +106,7 @@ public class UserController {
|
||||
@Validated @RequestBody ResetPasswordRequest request
|
||||
) {
|
||||
userService.resetPassword(id, request);
|
||||
return ActionResponse.success(messageUtil.getMessage(Message.USER_PASSWORD_RESET_SUCCESS));
|
||||
return ActionResponse.success(messageUtil.getMessage(MessageName.USER_PASSWORD_RESET_SUCCESS));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,6 +119,6 @@ public class UserController {
|
||||
@DeleteMapping("/{userId:\\d+}")
|
||||
public ActionResponse deleteUser(@PathVariable Long userId) {
|
||||
userService.deleteUser(userId);
|
||||
return ActionResponse.success(messageUtil.getMessage(Message.USER_DELETED));
|
||||
return ActionResponse.success(messageUtil.getMessage(MessageName.USER_DELETED, userId));
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-3
@@ -1,16 +1,17 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.enumeration.UserStatus;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record AddUserRequest(
|
||||
@NotBlank(message = "Username cannot be empty.")
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_USERNAME_NOT_EMPTY + "}")
|
||||
String username,
|
||||
@NotBlank(message = "Password cannot be empty.")
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_PASSWORD_NOT_EMPTY + "}")
|
||||
String password,
|
||||
@NotBlank(message = "Full name cannot be empty.")
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_ADD_USER_FULL_NAME_NOT_EMPTY + "}")
|
||||
String fullName,
|
||||
String email,
|
||||
String regionAbbreviation,
|
||||
+5
-4
@@ -1,6 +1,7 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.enumeration.Status;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import com.onixbyte.helix.validation.group.OnCreate;
|
||||
import com.onixbyte.helix.validation.group.OnUpdate;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
@@ -8,11 +9,11 @@ import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Null;
|
||||
|
||||
public record AuthorityRequest(
|
||||
@Null(groups = {OnUpdate.class}, message = "Code cannot be edited.")
|
||||
@NotNull(groups = {OnCreate.class}, message = "Code cannot be null.")
|
||||
@Null(groups = {OnUpdate.class}, message = "{" + MessageName.REQUEST_AUTHORITY_CODE_NOT_EDITABLE + "}")
|
||||
@NotNull(groups = {OnCreate.class}, message = "{" + MessageName.REQUEST_AUTHORITY_CODE_NOT_NULL + "}")
|
||||
String code,
|
||||
@NotNull(message = "Name of the authority cannot be null")
|
||||
@NotBlank(message = "Name of the authority cannot be null")
|
||||
@NotNull(message = "{" + MessageName.REQUEST_AUTHORITY_NAME_NOT_NULL + "}")
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_AUTHORITY_NAME_NOT_NULL + "}")
|
||||
String name,
|
||||
String description,
|
||||
Status status
|
||||
+3
-2
@@ -23,12 +23,13 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.enumeration.Status;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record DepartmentRequest(
|
||||
@NotNull(message = "Name of the department should not be null")
|
||||
@NotBlank(message = "Name of the department should not be null")
|
||||
@NotNull(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_DEPARTMENT_NAME_NOT_NULL + "}")
|
||||
String name,
|
||||
Long parentId,
|
||||
Integer sort,
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public record EditRoleRequest(
|
||||
@NotNull(message = "角色 ID 不能为空")
|
||||
@NotNull(message = "{" + MessageName.REQUEST_EDIT_ROLE_ID_NOT_NULL + "}")
|
||||
Long id,
|
||||
String name,
|
||||
String code,
|
||||
@@ -13,7 +14,7 @@ public record EditRoleRequest(
|
||||
String description,
|
||||
@Pattern(
|
||||
regexp = "^(ACTIVE|INACTIVE)?$",
|
||||
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
|
||||
message = "{" + MessageName.REQUEST_EDIT_ROLE_STATUS_INVALID + "}")
|
||||
String status
|
||||
) {
|
||||
}
|
||||
+3
-2
@@ -1,12 +1,13 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.enumeration.UserStatus;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Positive;
|
||||
|
||||
public record EditUserRequest(
|
||||
@NotNull(message = "User ID cannot be null")
|
||||
@Positive(message = "User ID must be positive")
|
||||
@NotNull(message = "{" + MessageName.REQUEST_EDIT_USER_ID_NOT_NULL + "}")
|
||||
@Positive(message = "{" + MessageName.REQUEST_EDIT_USER_ID_POSITIVE + "}")
|
||||
Long id,
|
||||
String fullName,
|
||||
String email,
|
||||
File renamed without changes.
File renamed without changes.
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public record QueryRoleRequest(
|
||||
@@ -7,7 +8,7 @@ public record QueryRoleRequest(
|
||||
String code,
|
||||
@Pattern(
|
||||
regexp = "^(ACTIVE|INACTIVE)?$",
|
||||
message = "状态仅可以是 ACTIVE、INACTIVE 其中之一")
|
||||
message = "{" + MessageName.REQUEST_QUERY_ROLE_STATUS_INVALID + "}")
|
||||
String status
|
||||
) {
|
||||
}
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
@@ -11,7 +12,7 @@ public record QueryUserRequest(
|
||||
String phoneNumber,
|
||||
@Pattern(
|
||||
regexp = "^(ACTIVE|INACTIVE|LOCKED)?$",
|
||||
message = "状态仅可以是 ACTIVE、INACTIVE 或 LOCKED 其中之一")
|
||||
message = "{" + MessageName.REQUEST_QUERY_USER_STATUS_INVALID + "}")
|
||||
String status,
|
||||
LocalDateTime createdAtStart,
|
||||
LocalDateTime createdAtEnd
|
||||
+2
-1
@@ -1,9 +1,10 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
|
||||
public record ResetPasswordRequest(
|
||||
@NotBlank(message = "密码不能为空") String password
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_RESET_PASSWORD_NOT_EMPTY + "}") String password
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.onixbyte.helix.domain.web.request;
|
||||
|
||||
import com.onixbyte.helix.enumeration.Status;
|
||||
import com.onixbyte.helix.shared.MessageName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
|
||||
public record RoleRequest(
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_ROLE_NAME_NOT_EMPTY + "}")
|
||||
String name,
|
||||
@NotBlank(message = "{" + MessageName.REQUEST_ROLE_CODE_NOT_EMPTY + "}")
|
||||
String code,
|
||||
@NotNull(message = "{" + MessageName.REQUEST_ROLE_SORT_NOT_NULL + "}")
|
||||
Integer sort,
|
||||
Boolean defaultValue,
|
||||
String description,
|
||||
Status status
|
||||
) {
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+16
-15
@@ -41,26 +41,19 @@ public class BizException extends RuntimeException {
|
||||
* REST API endpoints.
|
||||
*/
|
||||
private final HttpStatus status;
|
||||
private final String messageCode;
|
||||
private final Object[] messageArgs;
|
||||
|
||||
/**
|
||||
* Constructs a new business exception with the specified HTTP status and message.
|
||||
*
|
||||
* @param message the detailed error message explaining the business logic violation
|
||||
*/
|
||||
public BizException(String message) {
|
||||
super(message);
|
||||
public BizException(String messageCode, Object... messageArgs) {
|
||||
this.status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
this.messageCode = messageCode;
|
||||
this.messageArgs = messageArgs == null ? new Object[0] : messageArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new business exception with the specified HTTP status and message.
|
||||
*
|
||||
* @param status the HTTP status code to associate with this exception
|
||||
* @param message the detailed error message explaining the business logic violation
|
||||
*/
|
||||
public BizException(HttpStatus status, String message) {
|
||||
super(message);
|
||||
public BizException(HttpStatus status, String messageCode, Object... messageArgs) {
|
||||
this.status = status;
|
||||
this.messageCode = messageCode;
|
||||
this.messageArgs = messageArgs == null ? new Object[0] : messageArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,4 +64,12 @@ public class BizException extends RuntimeException {
|
||||
public HttpStatus getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
public String getMessageCode() {
|
||||
return messageCode;
|
||||
}
|
||||
|
||||
public Object[] getMessageArgs() {
|
||||
return messageArgs;
|
||||
}
|
||||
}
|
||||
File renamed without changes.
Loaded 100 of 281 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user