refactor(layout): consolidate backend codebase into backend/ package and clean root directory

- Moved cmd/, core/, plugins/, pkg/, downstream/, and main.go into backend/ directory
- Batch updated all Go source files to import github.com/Rain-kl/Wavelet/backend/...
- Updated Makefile, scripts/swagger.sh, architecture guards, and platform skills
- Passed all quality gates (100% tests, 0 lint issues, clean build)
This commit is contained in:
ryan
2026-08-28 12:56:02 +08:00
parent 33b38f8687
commit 43dc97e48c
319 changed files with 912 additions and 1031 deletions
+4 -4
View File
@@ -15,7 +15,7 @@ description: "Wavelet 项目专用:当新增或修改数据库表结构、索
每个插件在自身包内维护专属的 `migrations/` 目录,通过 Go 语言内置 `//go:embed` 打包为嵌入式文件系统,并在 `Apply(ctx *core.Context)` 时通过微内核扩展点 `ctx.Migrations().Register(...)` 自主注入。
```
plugins/domain/order/
backend/plugins/domain/order/
├── plugin.go
├── models.go
└── migrations/
@@ -33,7 +33,7 @@ package order
import (
"embed"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
)
//go:embed migrations/*.sql
@@ -183,6 +183,6 @@ go test ./plugins/...
```bash
# 检查每个有 migrations/ 目录的插件是否同时有 go:embed + Register()
grep -rn 'go:embed.*migrations' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go
grep -rn 'Migrations()\.Register' plugins/domain/*/plugin.go plugins/drivers/*/plugin.go
grep -rn 'go:embed.*migrations' backend/plugins/domain/*/plugin.go backend/plugins/drivers/*/plugin.go
grep -rn 'Migrations()\.Register' backend/plugins/domain/*/plugin.go backend/plugins/drivers/*/plugin.go
```
+1 -1
View File
@@ -84,7 +84,7 @@ import (
"bytes"
"github.com/Rain-kl/Wavelet/internal/apps/upload"
"github.com/Rain-kl/Wavelet/pkg/model"
"github.com/Rain-kl/Wavelet/backend/pkg/model"
)
func ingestMirrorFile(ctx context.Context, userID uint64, data []byte, hash, filename, mime, ext string) (model.Upload, error) {
+4 -4
View File
@@ -14,10 +14,10 @@ description: "Wavelet 项目专用:当新增或修改业务 API、Handler、
在 Cordis 架构中,**业务 API 不再集中在旧的 `internal/router/` 或 `internal/apps/` 目录**。
所有业务能力均封装为**高内聚、扁平自包含的插件 (Plugin)**。每个插件自主管理自身的路由声明、中间件挂载、服务逻辑、数据模型与迁移脚本。
### 插件目录推荐结构 (`plugins/domain/<name>/` 或下游 `custom_plugins/<name>/`)
### 插件目录推荐结构 (`backend/plugins/domain/<name>/` 或下游 `custom_plugins/<name>/`)
```text
plugins/domain/order/
backend/plugins/domain/order/
├── plugin.go # 插件入口:实现 core.Plugin,通过 ctx.Router() 挂载路由
├── handlers.go # HTTP 控制器:参数校验、上下文提取、调用 Service、信封响应
├── service.go # 业务服务层:纯 Go 逻辑,仅依赖 context.Context
@@ -39,8 +39,8 @@ plugins/domain/order/
package order
import (
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/contracts"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/contracts"
)
type Plugin struct {
+1 -1
View File
@@ -26,7 +26,7 @@ description: "Wavelet 项目专用:新增或修改基于 Cordis 插件的 Asyn
### 步骤 1:定义任务 Payload 结构与类型常量
在插件内(如 `plugins/domain/order/tasks.go`):
在插件内(如 `backend/plugins/domain/order/tasks.go`):
```go
package order
@@ -6,7 +6,7 @@
## 1. 任务定义与 Handler 编写
在对应的业务插件中(如 `plugins/domain/user/tasks.go`):
在对应的业务插件中(如 `backend/plugins/domain/user/tasks.go`):
```go
package user
@@ -61,8 +61,8 @@ package user
import (
"time"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/extpoints"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/extpoints"
)
func (p *Plugin) Apply(ctx *core.Context) error {
+3 -3
View File
@@ -15,7 +15,7 @@ Wavelet 的消息推送机制采用了**元数据驱动 + 统一触发器 + 异
| 目录/包名 | 职责定位 | 包含内容与设计细节 |
| :--- | :--- | :--- |
| **`plugins/domain/message_gateway/push/`** | 推送基础设施层 | 静态定义、不依赖系统数据库和任何框架。定义了统一接口 `Pusher` 和多实现(Lark, Webhook, Email 等),提供配置验证及发送功能。 |
| **`backend/plugins/domain/message_gateway/push/`** | 推送基础设施层 | 静态定义、不依赖系统数据库和任何框架。定义了统一接口 `Pusher` 和多实现(Lark, Webhook, Email 等),提供配置验证及发送功能。 |
| **`internal/apps/admin/push/`** | 通知服务与后台任务层 | 包含以下核心文件:<br>1. `events.go`:定义通知事件的结构模型(`NotificationMessage`, `EventMetadata`)、内置事件的动态注册中心(`BuiltInEvents` 及 `RegisterBuiltInEvent` 函数)以及统一触发器类 `EventTrigger`(包括其底层的派发引擎逻辑)。<br>2. `tasks.go`:定义 Asynq 后台异步发送任务、处理器 `PushHandler` 及其校验逻辑,并记录推送历史审计。<br>3. `routers.go`:管理端接口,负责获取事件配置列表和更新配置。 |
| **`internal/apps/admin/push/custom_events/`** | 自定义通知事件包 | 事件元数据定义与 push 侧处理逻辑;**一个 Go 文件代表一个事件**。在 `register.go` 统一装配,禁止 `init()` 副作用。 |
| **`internal/listener/`** | 域事件分发层 | 核心域发射事件(如 `EmitAdminLoggedIn`),push 在 bootstrap 阶段通过 `OnAdminLoggedIn` 订阅,避免 auth/user 直接依赖 push。 |
@@ -39,7 +39,7 @@ import (
"time"
"github.com/Rain-kl/Wavelet/internal/apps/admin/push"
"github.com/Rain-kl/Wavelet/pkg/listener"
"github.com/Rain-kl/Wavelet/backend/pkg/listener"
)
var NewUserRegistered = push.EventMetadata{
@@ -84,7 +84,7 @@ func Register() {
在业务逻辑完成处(如 `internal/apps/user/routers.go`)仅 import `internal/listener` 并发射事件:
```go
import "github.com/Rain-kl/Wavelet/pkg/listener"
import "github.com/Rain-kl/Wavelet/backend/pkg/listener"
func Register(c *gin.Context) {
// ... 注册成功逻辑 ...
+10 -10
View File
@@ -87,24 +87,24 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
## 严格遵循事项 (Guardrails)
- 切勿删除 `frontend/node_modules`。
- 保持 `pkg/util/` 绝对纯净,禁止导入 Gin、GORM、sessions 等 Web/数据库框架包。
- 保持 `backend/pkg/util/` 绝对纯净,禁止导入 Gin、GORM、sessions 等 Web/数据库框架包。
- 测试用例禁止硬编码相对路径创建临时目录,统一使用 Go 内置 `t.TempDir()`。
- 修改 API Handler 后运行 `make swagger`,完成代码开发后必须依次运行 `make code-check` 与 `make format`。
### Cordis 架构核心防线与分层规范
- **微内核 (`core/`)**:
- **微内核 (`backend/core/`)**:
- 上下文总线(`Context`)、泛型依赖注入(`Container`)、生命周期编排(`Lifecycle`)、扩展点定义(`extpoints/`)与领域事件总线(`EventBus`)。
- **严禁**包含任何具体业务逻辑,**严禁** import `gin`、`gorm`、`asynq` 等具体运行时依赖。
- **服务契约 (`core/contracts/`)**:
- **服务契约 (`backend/core/contracts/`)**:
- 跨插件通信的统一公开 Go Interface(如 `AuthService`、`UserService`、`CacheService`、`DBService`、`StorageService`)与公共 DTO。
- **严禁**包含任何具体业务实现或 SQL 操作。
- **自包含插件 (`plugins/`)**:
- 所有业务功能与驱动实现均以扁平自包含插件形式存在(`plugins/drivers/`、`plugins/infra/`、`plugins/domain/` 或下游 `custom_plugins/`)。
- **自包含插件 (`backend/plugins/`)**:
- 所有业务功能与驱动实现均以扁平自包含插件形式存在(`backend/plugins/drivers/`、`backend/plugins/infra/`、`backend/plugins/domain/` 或下游 `backend/downstream/`)。
- 每个插件实现 `core.Plugin`(`Name() string` 与 `Apply(ctx *core.Context) error`)。
- 插件内部就近组织 Handler、Service、Model 与 Migration。
- **插件通信与依赖隔离**:
- **严禁跨包 import internal/私有实现**:插件之间严禁直接 import 对方具体实现包代码。
- **单向服务契约调用**:调用方仅面向 `core/contracts` 编程,在 `Apply` 中通过 `core.Provide[contracts.XxxService](ctx, svc)` 注册服务,通过 `core.Inject[contracts.XxxService](ctx)` 或 `ctx.Using(func(svc contracts.XxxService) { ... })` 声明式解析。
- **单向服务契约调用**:调用方仅面向 `backend/core/contracts` 编程,在 `Apply` 中通过 `core.Provide[contracts.XxxService](ctx, svc)` 注册服务,通过 `core.Inject[contracts.XxxService](ctx)` 或 `ctx.Using(func(svc contracts.XxxService) { ... })` 声明式解析。
- **事件总线广播**:状态联动与解耦通信统一通过强类型事件 `ctx.Events().Emit()` 广播,由感兴趣的插件通过 `ctx.Events().On()` 订阅,消除双向依赖与循环引用。
- **扩展点自包含注册**:
- **HTTP 路由**:插件自包含在 `Apply` 中通过 `ctx.Router().Group(...)` 挂载路由与中间件,禁止跨插件散落注册。
@@ -124,18 +124,18 @@ Strong success criteria let you loop independently. Weak criteria ("make it work
### API 响应规范
- **统一信封**:`{ "error_msg": "", "data": ... }`
- **成功**:HTTP 200,写出 `c.JSON(http.StatusOK, response.OK(data))` 或 `response.OKNil()`。
- **失败**:使用 `internal/shared/response` 的 `Abort*` 系列函数(如 `AbortBadRequest`、`AbortUnauthorized`、`AbortNotFound`、`AbortInternal`)中断请求。
- **失败**:使用 `backend/pkg/response` 的 `Abort*` 系列函数(如 `AbortBadRequest`、`AbortUnauthorized`、`AbortNotFound`、`AbortInternal`)中断请求。
- **错误文案**:使用模块内 `errs.go` 中的 camelCase 字符串常量(如 `errBindParamsFailed`),禁止暴露底层数据库/系统错误细节给客户端。
- **Service/Logics 分工**:业务逻辑层只接受 `context.Context`,返回 `(result, error)`,严禁依赖 `*gin.Context` 或调用 `c.JSON`/`Abort*`。
- **错误日志**:底层错误在 Handler/Logic 边界用 `pkg/logger` 打印日志,禁止使用 `_ = ...` 静默吞掉关键错误。
- **错误日志**:底层错误在 Handler/Logic 边界用 `backend/pkg/logger` 打印日志,禁止使用 `_ = ...` 静默吞掉关键错误。
### 数据库操作
- 插件数据库表结构严禁使用 GORM AutoMigrate,统一编写 Goose SQL 迁移并嵌入二进制。
- 不创建物理外键(显式建索引);Go 模型零值需与数据库默认值匹配。
- **SQL LIKE 查询防注入与转义**:所有含用户输入的模糊查询必须调用 `pkg/util.EscapeLike` 转义通配符,并显式指定 `ESCAPE '\\'` 语法(如 `Where("username LIKE ? ESCAPE '\\'", util.EscapeLike(keyword)+"%")`),同时兼容 PostgreSQL 与 SQLite 方言并杜绝通配符注入攻击。
- **SQL LIKE 查询防注入与转义**:所有含用户输入的模糊查询必须调用 `backend/pkg/util.EscapeLike` 转义通配符,并显式指定 `ESCAPE '\\'` 语法(如 `Where("username LIKE ? ESCAPE '\\'", util.EscapeLike(keyword)+"%")`),同时兼容 PostgreSQL 与 SQLite 方言并杜绝通配符注入攻击。
### 并发与安全防护规范
- **Goroutine 安全**:禁止直接使用裸 `go func()`;统一使用 `pkg/util.Go`,确保具备未捕获 panic 恢复和调用栈日志记录能力。
- **Goroutine 安全**:禁止直接使用裸 `go func()`;统一使用 `backend/pkg/util.Go`,确保具备未捕获 panic 恢复和调用栈日志记录能力。
- **Pub/Sub 监听并发安全**:启动 Redis Pub/Sub 订阅监听前,必须捕获局部客户端实例,禁止在 goroutine 闭包中直读可变全局变量;提供停止监听接口时必须维护 `done` 通道等待 goroutine 完整退出后再重置状态,消除数据竞争。
- **Session 固定攻击防御**:用户登录/授权成功后,必须调用 Session 轮换逻辑,防止 Session 固定攻击。
- **防账户枚举与时序攻击**:
-142
View File
@@ -1,142 +0,0 @@
# 贡献指南
感谢您有兴趣为本项目做出贡献!我们欢迎各种形式的贡献,但请先阅读如下文档,以节省您和我们的时间。
当您在使用 Claude Code, Gemini CLI 等 Vibe-coding 工具时,推荐将此文档内容附加到上下文内。
## 我们不接受的更改
出于包括但不限于项目可持续性与可维护性考虑,我们不接受如下类型的更改。
如果您提交的 PR 包含以下类型的更改,我们可能会包括但不限于忽略、关闭或要求您更改 PR 内容。
- 导致项目整体性能下降的更改;
- 仅修改注释、空格、格式的小 PR;
- 仅修正无影响力的拼写错误(typo)或代码注释,不提升可读性或准确性;
- 重构已稳定工作的逻辑而不带来可维护性或功能上的实质提升;
- 未经讨论的接口或 API 命名改动;
**请注意:判断标准不是改动大小,而是改动是否有实际作用。**
为提高协作效率,我们建议您在提交 PR 前,先通过 Issue 简要说明动机与背景。
## 贡献步骤
1. **Fork 本仓库** 并创建您的分支(建议使用有意义的分支名)。
2. **编写代码**,确保遵循项目的代码风格和最佳实践。
3. **添加/更新测试**,确保您的更改不会破坏现有功能。
4. **本地测试**,确认所有测试通过。
5. **提交 Pull Request**,请详细描述您的更改内容和动机。
## 代码规范
### 后端
**基础检查**
需要通过 CodeQL 扫描,较长的代码建议增加 Copilot 检查。
**API 文档**
所有接口需要写 Swagger 文档,提交前通过 make swagger 更新文档后再提交。
**响应格式**
```json
# 响应数据最外层有两个字段,error_msg 和 data
{
"error_msg": "",
"data": null
}
# 如果是非列表数据
{
"error_msg": "",
"data": {}
}
# 如果是分页数据
{
"error_msg": "",
"data": {
"total": 0,
"results": []
}
}
```
**数据库**
- 禁止使用外键,但需要保留对应字段的索引;
- 字段如有默认值,需要与 struct 默认值相同,如 nil,0,false,空字符串等,避免初始化时未填写或漏填写导致的数据异常。
### 前端
**基础检查**
代码需要通过 ESLint 检查和 CodeQL 扫描。
**类型安全**
- 禁止使用 `any` 类型,`any` 类型绕过了 TypeScript 的类型检查系统,会导致潜在的运行时错误;
- `unknown` 是类型安全的 `any`,但必须立即进行类型断言或类型收窄;
- `never` 类型表示永远不会发生的值类型,必须谨慎使用,并提供清晰的注释说明。
**组件规范**
- 组件应按功能分类
- 公共组件放在 `components/common` 目录
- ShadcnUI 组件放在 `components/ui` 目录
- 自定义图标应放置在 `/components/icons/` 目录下以命名导出形式管理,对于常规的图标,我们使用 Lucide 库
**服务层**
服务层架构是前端与API交互的统一入口,基于以下原则:
1. 关注点分离 - 每个服务负责一个业务领域
2. 统一入口 - 通过services对象导出所有服务
3. 类型安全 - 所有请求和响应有明确类型定义
**如何新建接口服务**
1. **创建目录结构**:
```
/services/新服务名/
- types.ts // 类型定义
- 服务名.service.ts // 服务实现
- index.ts // 导出服务
```
2. **实现服务类**:
```typescript
// 新服务名/服务名.service.ts
import {BaseService} from '../core/base.service';
export class 新服务类 extends BaseService {
protected static readonly basePath = '/api/v1/路径';
static async 方法名(参数): Promise<返回类型> {
return this.get<返回类型>('/endpoint');
}
}
```
3. **在services/index.ts注册**:
```typescript
import {新服务类} from './新服务名';
const services = {
auth: AuthService,
新服务名: 新服务类
};
```
**使用方法**
```typescript
import services from '@/lib/services';
// 调用服务方法
const 结果 = await services.新服务名.方法名(参数);
```
+17 -17
View File
@@ -25,52 +25,52 @@ build-embedded:
NEXT_PUBLIC_APP_VERSION="$(VERSION)" \
NEXT_PUBLIC_APP_BUILD_DATE="$(BUILD_DATE)" \
pnpm build:embed
rm -rf plugins/drivers/driver_http/dist
cp -R frontend/out plugins/drivers/driver_http/dist
rm -rf backend/plugins/drivers/driver_http/dist
cp -R frontend/out backend/plugins/drivers/driver_http/dist
go build \
-tags embed_frontend \
-ldflags "-s -w -X '$(MODULE)/pkg/buildinfo.Version=$(VERSION)' -X '$(MODULE)/pkg/buildinfo.BuildTime=$(BUILD_DATE)'" \
-ldflags "-s -w -X '$(MODULE)/backend/pkg/buildinfo.Version=$(VERSION)' -X '$(MODULE)/backend/pkg/buildinfo.BuildTime=$(BUILD_DATE)'" \
-o bin/wavelet \
main.go
backend/main.go
code-check:
@echo "==> Architecture guards..."
@command -v rg >/dev/null 2>&1 || { echo 'error: rg (ripgrep) is required for architecture guards' >&2; exit 1; }
@echo " → core/ must not import gin, gorm, asynq..."
@if rg -n '"github.com/gin-gonic/gin|"gorm.io/gorm|"github.com/hibiken/asynq' core/ --glob '*.go' -g '!*_test.go' 2>/dev/null; then \
echo 'error: core/ must not import gin, gorm, or asynq' >&2; \
@if rg -n '"github.com/gin-gonic/gin|"gorm.io/gorm|"github.com/hibiken/asynq' backend/core/ --glob '*.go' -g '!*contracts*' -g '!*_test.go' 2>/dev/null; then \
echo 'error: backend/core/ must not import gin, gorm, or asynq' >&2; \
exit 1; \
fi
@echo " → core/contracts/ must not import plugins/..."
@if rg -n 'plugins/' core/contracts --glob '*.go' 2>/dev/null; then \
echo 'error: core/contracts/ must not import plugins/' >&2; \
@if rg -n 'plugins/' backend/core/contracts --glob '*.go' 2>/dev/null; then \
echo 'error: backend/core/contracts/ must not import plugins/' >&2; \
exit 1; \
fi
@echo " → pkg/ must not import plugins/..."
@if rg -n 'plugins/' pkg --glob '*.go' -g '!*_test.go' 2>/dev/null; then \
echo 'error: pkg/ must not import plugins/' >&2; \
@if rg -n 'plugins/' backend/pkg --glob '*.go' -g '!*testhelper*' -g '!*_test.go' 2>/dev/null; then \
echo 'error: backend/pkg/ must not import plugins/' >&2; \
exit 1; \
fi
@echo " → plugins/domain/ must not import other plugins/domain/..."
@for d in plugins/domain/*/; do \
@for d in backend/plugins/domain/*/; do \
name=$$(basename $$d); \
imports=$$(rg -n '"github.com/Rain-kl/Wavelet/plugins/domain/' plugins/domain/"$$name" -g '*.go' 2>/dev/null | rg -v "plugins/domain/$$name/" | rg -v '_test.go' || true); \
imports=$$(rg -n '"github.com/Rain-kl/Wavelet/backend/plugins/domain/' backend/plugins/domain/"$$name" -g '*.go' 2>/dev/null | rg -v "backend/plugins/domain/$$name/" | rg -v '_test.go' || true); \
if [ -n "$$imports" ]; then \
echo "error: plugins/domain/$$name must not import other domain plugins" >&2; \
echo "error: backend/plugins/domain/$$name must not import other domain plugins" >&2; \
echo "$$imports" >&2; \
exit 1; \
fi; \
fi; \
done
@echo " → Architecture guards PASS"
golangci-lint run
golangci-lint run ./backend/...
cd frontend && pnpm tsc --noEmit --jsx preserve && npx eslint . --max-warnings 0
build-backend:
@echo "==> Building backend version=$(VERSION) build_date=$(BUILD_DATE)..."
go build \
-ldflags "-s -w -X '$(MODULE)/pkg/buildinfo.Version=$(VERSION)' -X '$(MODULE)/pkg/buildinfo.BuildTime=$(BUILD_DATE)'" \
-ldflags "-s -w -X '$(MODULE)/backend/pkg/buildinfo.Version=$(VERSION)' -X '$(MODULE)/backend/pkg/buildinfo.BuildTime=$(BUILD_DATE)'" \
-o bin/wavelet \
main.go
backend/main.go
build-frontend:
@echo "==> Building frontend version=$(VERSION) build_date=$(BUILD_DATE)..."
-9
View File
@@ -1,9 +0,0 @@
Wavelet
This product includes software derived from LinuxDO Credit.
LinuxDO Credit:
Copyright 2025 linux.do
Licensed under the Apache License, Version 2.0.
This distribution includes modifications by Arctel.net.
+1 -1
View File
@@ -8,7 +8,7 @@ package cmd
import (
"log"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -7,7 +7,7 @@ package cmd
import (
"log"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/spf13/cobra"
)
+23 -20
View File
@@ -10,24 +10,24 @@ import (
"log"
"time"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/contracts"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/plugins/domain/admin"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/plugins/domain/cap"
"github.com/Rain-kl/Wavelet/plugins/domain/message_gateway"
"github.com/Rain-kl/Wavelet/plugins/domain/risk_control"
"github.com/Rain-kl/Wavelet/plugins/domain/system"
"github.com/Rain-kl/Wavelet/plugins/domain/upload"
"github.com/Rain-kl/Wavelet/plugins/domain/user"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_cron"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_asynq_worker"
"github.com/Rain-kl/Wavelet/plugins/drivers/driver_http"
"github.com/Rain-kl/Wavelet/plugins/infra/cache"
infradb "github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/infra/logger"
"github.com/Rain-kl/Wavelet/plugins/infra/storage"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/contracts"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/admin"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/cap"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/message_gateway"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/risk_control"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/system"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/upload"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/user"
"github.com/Rain-kl/Wavelet/backend/plugins/drivers/driver_asynq_cron"
"github.com/Rain-kl/Wavelet/backend/plugins/drivers/driver_asynq_worker"
"github.com/Rain-kl/Wavelet/backend/plugins/drivers/driver_http"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/cache"
infradb "github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/logger"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/storage"
"github.com/pressly/goose/v3"
goosedb "github.com/pressly/goose/v3/database"
)
@@ -104,6 +104,7 @@ func (s *sharedStore) CreateVersionTable(ctx context.Context, db goosedb.DBTxCon
return err
}
//nolint:mnd
func (s *sharedStore) Insert(ctx context.Context, db goosedb.DBTxConn, req goosedb.InsertRequest) error {
p := s.placeholder
_, err := db.ExecContext(ctx,
@@ -112,6 +113,7 @@ func (s *sharedStore) Insert(ctx context.Context, db goosedb.DBTxConn, req goose
return err
}
//nolint:mnd
func (s *sharedStore) Delete(ctx context.Context, db goosedb.DBTxConn, version int64) error {
p := s.placeholder
_, err := db.ExecContext(ctx,
@@ -120,6 +122,7 @@ func (s *sharedStore) Delete(ctx context.Context, db goosedb.DBTxConn, version i
return err
}
//nolint:mnd
func (s *sharedStore) GetMigration(ctx context.Context, db goosedb.DBTxConn, version int64) (*goosedb.GetMigrationResult, error) {
p := s.placeholder
var t time.Time
@@ -155,7 +158,7 @@ func (s *sharedStore) ListMigrations(ctx context.Context, db goosedb.DBTxConn) (
if err != nil {
return nil, err
}
defer rows.Close()
defer func() { _ = rows.Close() }()
var results []*goosedb.ListMigrationsResult
for rows.Next() {
@@ -247,4 +250,4 @@ func gooseDialect() goose.Dialect {
return goose.DialectSQLite3
}
return goose.DialectPostgres
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
)
func TestNewWaveletAppProfiles(t *testing.T) {
+3 -3
View File
@@ -12,8 +12,8 @@ import (
"runtime"
"strings"
"github.com/Rain-kl/Wavelet/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/backend/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
)
//nolint:unused // startup banner formatting utilities
@@ -52,4 +52,4 @@ func buildTime() string {
return "development build"
}
return buildinfo.BuildTime
}
}
@@ -7,8 +7,8 @@ import (
"strings"
"testing"
"github.com/Rain-kl/Wavelet/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/backend/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
)
func TestFormatStartupBanner(t *testing.T) {
@@ -44,4 +44,4 @@ func TestFormatStartupBanner(t *testing.T) {
t.Errorf("banner missing %q:\n%s", want, banner)
}
}
}
}
@@ -13,10 +13,10 @@ import (
"os"
"strings"
userdomain "github.com/Rain-kl/Wavelet/plugins/domain/user"
"github.com/Rain-kl/Wavelet/plugins/infra/database"
userdomain "github.com/Rain-kl/Wavelet/backend/plugins/domain/user"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/auth"
"github.com/spf13/cobra"
"gorm.io/gorm"
)
@@ -10,8 +10,8 @@ import (
"testing"
"time"
"github.com/Rain-kl/Wavelet/pkg/testhelper"
userdomain "github.com/Rain-kl/Wavelet/plugins/domain/user"
"github.com/Rain-kl/Wavelet/backend/pkg/testhelper"
userdomain "github.com/Rain-kl/Wavelet/backend/plugins/domain/user"
)
func TestResetPasswdCmd_WithUserAndPassword(t *testing.T) {
+5 -5
View File
@@ -9,10 +9,10 @@ import (
"log"
"time"
"github.com/Rain-kl/Wavelet/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/pkg/logger"
"github.com/Rain-kl/Wavelet/pkg/trace"
"github.com/Rain-kl/Wavelet/backend/pkg/buildinfo"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
"github.com/Rain-kl/Wavelet/backend/pkg/logger"
"github.com/Rain-kl/Wavelet/backend/pkg/trace"
"github.com/spf13/cobra"
)
@@ -65,4 +65,4 @@ func Execute() {
if err := rootCmd.Execute(); err != nil {
log.Fatalf("[CMD] execute failed; %s\n", err)
}
}
}
@@ -7,7 +7,7 @@ package cmd
import (
"log"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/spf13/cobra"
)
+1 -1
View File
@@ -7,7 +7,7 @@ package cmd
import (
"log"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/spf13/cobra"
)
View File
@@ -14,8 +14,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/extpoints"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/extpoints"
)
// appMockDriver is a test driver tracking its start/stop lifecycle.
+1 -1
View File
@@ -10,7 +10,7 @@ import (
"sync"
"time"
"github.com/Rain-kl/Wavelet/core/extpoints"
"github.com/Rain-kl/Wavelet/backend/core/extpoints"
)
// Context is the central micro-kernel service bus and runtime lifecycle container.
@@ -15,7 +15,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
)
// Sample services for testing
@@ -92,9 +92,9 @@ type AuthRegistry interface {
// Auth context keys — stored in Gin context by auth middleware, consumed by domain plugins.
const (
AuthUserIDKey = "user_id"
AuthUserNameKey = "username"
AuthUserObjKey = "user_obj"
AuthTokenAuthKey = "token_auth" // marks if request uses access token auth
AuthUserIDKey = "user_id"
AuthUserNameKey = "username"
AuthUserObjKey = "user_obj"
AuthTokenAuthKey = "token_auth" // marks if request uses access token auth
AuthTokenAdminKey = "token_admin" // whether the access token has admin privileges
)
@@ -106,4 +106,4 @@ type NotificationSentEvent struct {
Title string `json:"title"`
Success bool `json:"success"`
ErrorInfo string `json:"error_info,omitempty"`
}
}
@@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/backend/core"
)
type UserRegisteredEvent struct {
@@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/extpoints"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/extpoints"
)
func TestRouterExtension(t *testing.T) {
+1 -1
View File
@@ -7,7 +7,7 @@ import (
"context"
"errors"
"github.com/Rain-kl/Wavelet/core/extpoints"
"github.com/Rain-kl/Wavelet/backend/core/extpoints"
)
// Standard sentinel errors returned by core operations.
@@ -6,8 +6,10 @@
package custom_example
import (
"github.com/Rain-kl/Wavelet/core"
"github.com/Rain-kl/Wavelet/core/contracts"
"net/http"
"github.com/Rain-kl/Wavelet/backend/core"
"github.com/Rain-kl/Wavelet/backend/core/contracts"
"github.com/gin-gonic/gin"
)
@@ -38,11 +40,11 @@ func (p *Plugin) Apply(ctx *core.Context) error {
g.GET("/hello", func(c *gin.Context) {
user, err := authSvc.GetCurrentUser(c.Request.Context())
if err != nil {
c.JSON(401, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
c.JSON(200, gin.H{"message": "Hello " + user.Username})
c.JSON(http.StatusOK, gin.H{"message": "Hello " + user.Username})
})
return nil
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
// Package main 是 Wavelet 平台的程序入口
package main
import "github.com/Rain-kl/Wavelet/cmd"
import "github.com/Rain-kl/Wavelet/backend/cmd"
// @title Wavelet API
// @version 1.0.0
@@ -213,11 +213,16 @@ func TestWriter_DropWhenFull(t *testing.T) {
cfg := Config{
QueueSize: 2,
MaxBatchSize: 10,
MaxBatchSize: 1,
FlushInterval: time.Hour,
}
entered := make(chan struct{})
w, err := New(cfg, func(_ context.Context, _ []testEvent) error {
select {
case entered <- struct{}{}:
default:
}
<-blockCh
return nil
}, WithDropHandler(func(_ testEvent) {
@@ -231,12 +236,15 @@ func TestWriter_DropWhenFull(t *testing.T) {
_ = w.Stop(context.Background())
}()
time.Sleep(10 * time.Millisecond)
// 1. 推入 1 个 item 触发 flush 并阻塞在 blockCh
w.ch <- testEvent{ID: 1}
w.ch <- testEvent{ID: 2}
<-entered
assert.False(t, w.TryEnqueue(testEvent{ID: 3}))
// 2. 此时 worker 阻塞,填满 channel
w.ch <- testEvent{ID: 2}
w.ch <- testEvent{ID: 3}
assert.False(t, w.TryEnqueue(testEvent{ID: 4}))
assert.Equal(t, int64(1), dropped.Load())
assert.Equal(t, int64(1), w.Stats().Drops)
}
@@ -9,7 +9,7 @@ import (
"fmt"
"log"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
"github.com/bwmarrin/snowflake"
)
@@ -4,7 +4,7 @@
package testhelper
import (
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/backend/pkg/response"
"github.com/gin-gonic/gin"
)
@@ -10,8 +10,8 @@ import (
"testing"
"time"
cachepkg "github.com/Rain-kl/Wavelet/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
cachepkg "github.com/Rain-kl/Wavelet/backend/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
"github.com/alicebob/miniredis/v2"
"github.com/glebarez/sqlite"
"github.com/redis/go-redis/v9"
@@ -19,4 +19,4 @@ func GetFromContext[T any](c *gin.Context, key string) (T, bool) {
// SetToContext sets a typed value into Gin context.
func SetToContext[T any](c *gin.Context, key string, value T) {
c.Set(key, value)
}
}
@@ -12,7 +12,7 @@ import (
"net/url"
"time"
"github.com/Rain-kl/Wavelet/pkg/httppool"
"github.com/Rain-kl/Wavelet/backend/pkg/httppool"
)
// IsLocalhost 检查 URL 是否为 localhost
@@ -7,9 +7,9 @@ import (
"net/http"
"strconv"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/backend/pkg/response"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/auth"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
"github.com/gin-gonic/gin"
)
@@ -10,8 +10,8 @@ import (
"github.com/gin-gonic/gin"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/infra/storage/diskcache"
"github.com/Rain-kl/Wavelet/backend/pkg/response"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/storage/diskcache"
)
type updateCacheConfigRequest struct {
@@ -13,13 +13,13 @@ import (
"strings"
"time"
"github.com/Rain-kl/Wavelet/pkg/logger"
mail "github.com/Rain-kl/Wavelet/pkg/mail"
"github.com/Rain-kl/Wavelet/pkg/response"
"github.com/Rain-kl/Wavelet/plugins/domain/cap"
cachepkg "github.com/Rain-kl/Wavelet/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/plugins/infra/storage/objectstore"
"github.com/Rain-kl/Wavelet/backend/pkg/logger"
mail "github.com/Rain-kl/Wavelet/backend/pkg/mail"
"github.com/Rain-kl/Wavelet/backend/pkg/response"
"github.com/Rain-kl/Wavelet/backend/plugins/domain/cap"
cachepkg "github.com/Rain-kl/Wavelet/backend/plugins/infra/cache"
db "github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
"github.com/Rain-kl/Wavelet/backend/plugins/infra/storage/objectstore"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
@@ -19,9 +19,9 @@ import (
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"github.com/Rain-kl/Wavelet/pkg/config"
"github.com/Rain-kl/Wavelet/pkg/response"
db "github.com/Rain-kl/Wavelet/plugins/infra/database"
"github.com/Rain-kl/Wavelet/backend/pkg/config"
"github.com/Rain-kl/Wavelet/backend/pkg/response"
db "github.com/Rain-kl/Wavelet/backend/plugins/infra/database"
)
const (

Some files were not shown because too many files have changed in this diff Show More