Skip to content

Commit 56f9301

Browse files
committed
feat(multi-tenant): add tenant-aware platform governance
1 parent 230014b commit 56f9301

730 files changed

Lines changed: 34031 additions & 2655 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ pnpm report # 查看 HTML 报告
132132
5. 用户确认本次迭代功能已完成没有问题后,则执行`/opsx:archive`斜杠指令`.agents/prompts/opsx/archive.md`将本次变更归档。归档前需要调用`/lina-review`技能进行全面的变更审查,确保代码质量和规范遵循。
133133

134134
**关键规则**
135+
- 只有在`openspec`工具安装时才启用`openspec`执行流程,包括`/opsx:explore``/opsx:propose``/opsx:apply``/opsx:archive`等斜杠指令,以及相关的技能调用和文档生成;如果未安装`openspec`工具,则不启用这些功能,用户需要手动维护变更文档和执行流程。
135136
- **活跃`OpenSpec`变更的判定以是否归档为准**:凡是仍位于`openspec/changes/`根目录下、且**未移动到**`openspec/changes/archive/`中的变更目录,都属于活跃变更;**即便该变更已经完成了全部任务、`openspec list --json`中显示为`status: complete`,只要尚未执行归档,仍然必须视为活跃变更**
136137
- 当用户报告问题缺陷/改进建议时(无论中文或英文),如果当前项目存在活跃的`OpenSpec`变更,那么必须调用`lina-feedback`技能。**在用户未明确要求新建变更的前提下,无论反馈内容是否与当前活跃迭代的主要功能相关,都必须追加到当前活跃迭代中**,便于统一管理和归档。
137138
- 审查技能`/lina-review`自动在以下节点触发:`/opsx:apply`任务完成后、`/opsx:feedback`任务完成后、`/opsx:archive`归档前。
@@ -221,7 +222,7 @@ pnpm report # 查看 HTML 报告
221222
- **宿主通用组件分层规范**`apps/lina-core/pkg/`只用于承载宿主与插件、构建工具链或跨组件复用的稳定公共组件;宿主私有共享代码应放在`internal`下具有明确职责名的包中。禁止新增`internal/util``internal/common``internal/helper`这类语义模糊的兜底目录;仅服务单一业务组件的辅助逻辑应优先放回该组件目录,需要被`internal`目录外复用时再提升到`pkg/<component>`,并补齐包注释、文件注释、公开方法注释与必要的关键逻辑说明
222223
- **禁止为已导入包的导出常量或变量创建包内别名**:当需要使用其他包导出的常量或变量时,必须直接通过`pkg.ExportedConst`方式引用,禁止在本包内通过`const localName = pkg.ExportedConst``var localName = pkg.ExportedVar`创建无意义的别名。这种别名增加了间接层且不提供任何类型安全或语义收益,只会降低可读性和可维护性
223224
- **禁止使用`_ = var`这类单独赋值语句掩盖未使用的参数或局部变量**:这类占位写法没有业务语义,只会制造“变量是否本应参与逻辑”的误导。应优先删除无用变量;若为满足接口签名或回调约束必须保留参数,应直接在函数签名中使用空白标识符(如`func(ctx context.Context, _ gdb.TX) error`)或省略不需要的接收者名称,而不是在函数体内追加`_ = tx``_ = req``_ = ctx`之类的单行语句
224-
- **单元测试必须自包含且顺序无关**:每个单元测试方法(如 Go`TestXxx`)必须在自身函数内部闭环完成测试场景、数据、依赖替身和清理逻辑的构造与注册,可以复用 helper/fixture 函数,但必须由当前测试显式调用;禁止把其他测试方法的执行结果、数据库残留、全局状态、副作用或执行顺序作为当前测试通过的前提。测试清理必须使用当前测试注册的 `defer`/`t.Cleanup` 或等价机制完成,避免因测试顺序、单独运行、并行运行或 `-run` 精确筛选导致结果不符合预期。
225+
- **单元测试必须自包含且顺序无关**:每个单元测试方法(如 `Go``TestXxx`)必须在自身函数内部闭环完成测试场景、数据、依赖替身和清理逻辑的构造与注册,可以复用 helper/fixture 函数,但必须由当前测试显式调用;禁止把其他测试方法的执行结果、数据库残留、全局状态、副作用或执行顺序作为当前测试通过的前提。测试清理必须使用当前测试注册的 `defer`/`t.Cleanup` 或等价机制完成,避免因测试顺序、单独运行、并行运行或 `-run` 精确筛选导致结果不符合预期。
225226
- **文件顶部注释规范**
226227
- 所有`Go`源码文件都必须在文件顶部增加文件用途注释说明。组件说明应写在该组件的主文件中,即与组件同名的主文件(如`plugin.go``config.go``file.go`)。
227228
- 主文件中的组件注释必须紧贴`package xxx`声明,中间不得有空行。例如:

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@ Plugins are the primary extension point in `LinaPro`. Each plugin is a self-cont
151151
- Built-in distributed locking and key-value caching, with core components that are natively cluster-aware
152152
- The job scheduling subsystem is distribution-aware, automatically preventing duplicate execution across cluster nodes
153153

154+
## Multi-Tenant Foundation
155+
156+
`LinaPro` is being extended with a pool-based multi-tenant model that keeps the single-tenant experience available by default. When the `multi-tenant` plugin is not installed or enabled, host and plugin data use `tenant_id = 0`, which represents the `PLATFORM` tenant.
157+
158+
When the `multi-tenant` plugin is enabled:
159+
160+
- Tenant identity is resolved by the built-in chain: `override`, `jwt`, `session`, `header`, `subdomain`, and `default`; supported runtime policy changes are stored by the plugin, not by the host config template.
161+
- The isolation model is code-owned and currently fixed to `pool`.
162+
- User-to-tenant cardinality is code-owned and defaults to `multi`, allowing one user to belong to multiple tenants.
163+
- Tenant-aware plugins declare `scope_nature`, `default_install_mode`, and `default_for_new_tenants` in `plugin.yaml`.
164+
- `platform_only` plugins are governed globally, while `tenant_aware` plugins can be enabled globally or per tenant.
165+
- LifecycleGuard hooks may veto plugin disable or uninstall operations, and `plugin.allowForceUninstall` controls whether platform administrators can force an audited override.
166+
167+
Typical internal `BU` usage starts with the built-in `multi` cardinality, `prompt` ambiguity handling, and tenant-scoped enablement for audit or content plugins. This iteration uses the pool model only; `rootDomain` is reserved for a later settings release and is not configurable yet. Schema-per-tenant, database-per-tenant, quotas, billing, and branding customization are reserved for future work.
168+
154169

155170
# Tech Stack
156171

README.zh-CN.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@ graph TB
151151
- 底层内置支持分布式锁与键值缓存机制,核心组件支持集群自动感知
152152
- 定时任务调度子系统具备分布式感知能力,集群环境下自动避免重复执行
153153

154+
## 多租户基础能力
155+
156+
`LinaPro`正在扩展基于`Pool`模型的多租户能力,同时保留默认单租户开箱体验。未安装或未启用`multi-tenant`插件时,宿主与插件数据统一使用`tenant_id = 0`,表示`PLATFORM`平台租户。
157+
158+
启用`multi-tenant`插件后:
159+
160+
- 租户身份通过内置责任链解析:`override``jwt``session``header``subdomain``default`;支持的运行时策略变更由插件持久化,不再通过宿主配置模板维护。
161+
- 隔离模型由代码默认值维护,当前固定为`pool`
162+
- 用户与租户基数由代码默认值维护,默认`multi`,允许一个用户加入多个租户。
163+
- 租户感知插件需要在`plugin.yaml`中声明`scope_nature``default_install_mode``default_for_new_tenants`
164+
- `platform_only`插件按平台全局治理,`tenant_aware`插件可选择全局启用或按租户独立启用。
165+
- `LifecycleGuard`钩子可否决插件禁用或卸载,`plugin.allowForceUninstall`控制平台管理员是否允许执行带审计的强制覆盖。
166+
167+
典型内部`BU`场景使用内置`multi`基数、`prompt`歧义处理,并对审计或内容类插件采用租户级启用。当前迭代仅支持`Pool`模型;`rootDomain`预留给后续设置入口,当前暂不支持配置。schema-per-tenant、database-per-tenant、配额、计费与品牌定制留作后续演进。
168+
154169

155170

156171
# 主要技术栈

apps/lina-core/api/auth/v1/auth_login.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,17 @@ type LoginReq struct {
1313
Password string `json:"password" v:"required#validation.auth.login.password.required" dc:"Password" eg:"admin123"`
1414
}
1515

16+
// LoginTenantEntity is one tenant candidate returned during two-stage login.
17+
type LoginTenantEntity struct {
18+
Id int `json:"id" dc:"Tenant ID" eg:"1"`
19+
Code string `json:"code" dc:"Tenant code" eg:"acme"`
20+
Name string `json:"name" dc:"Tenant display name" eg:"Acme"`
21+
Status string `json:"status" dc:"Tenant status" eg:"active"`
22+
}
23+
1624
// LoginRes is the login response.
1725
type LoginRes struct {
18-
AccessToken string `json:"accessToken" dc:"JWT token" eg:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
26+
AccessToken string `json:"accessToken" dc:"JWT token. Empty when tenant selection is required." eg:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
27+
PreToken string `json:"preToken" dc:"Short-lived pre-login token when tenant selection is required." eg:"pre_8f4f..."`
28+
Tenants []*LoginTenantEntity `json:"tenants" dc:"Tenant candidates when tenant selection is required." eg:"[]"`
1929
}
Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package v1
1+
// This file defines parameter setting import DTOs.
22

3-
import (
4-
"github.com/gogf/gf/v2/frame/g"
5-
)
3+
package v1
64

7-
// Config Import API
5+
import "github.com/gogf/gf/v2/frame/g"
86

97
// ConfigImportReq defines the request for importing configs.
108
type ConfigImportReq struct {
@@ -23,11 +21,3 @@ type ConfigImportFailItem struct {
2321
Row int `json:"row" dc:"Line number" eg:"3"`
2422
Reason string `json:"reason" dc:"Reason for failure" eg:"Parameter key name already exists"`
2523
}
26-
27-
// ConfigImportTemplateReq defines the request for downloading import template.
28-
type ConfigImportTemplateReq struct {
29-
g.Meta `path:"/config/import-template" method:"get" tags:"Parameter Settings" summary:"Download parameter setting import template" dc:"Download the parameter settings and import the Excel template file, including required fields and data format instructions" permission:"system:config:add"`
30-
}
31-
32-
// ConfigImportTemplateRes is the response for template download.
33-
type ConfigImportTemplateRes struct{}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file defines parameter setting import template DTOs.
2+
3+
package v1
4+
5+
import "github.com/gogf/gf/v2/frame/g"
6+
7+
// ConfigImportTemplateReq defines the request for downloading import template.
8+
type ConfigImportTemplateReq struct {
9+
g.Meta `path:"/config/import-template" method:"get" tags:"Parameter Settings" summary:"Download parameter setting import template" dc:"Download the parameter settings and import the Excel template file, including required fields and data format instructions" permission:"system:config:add"`
10+
}
11+
12+
// ConfigImportTemplateRes is the response for template download.
13+
type ConfigImportTemplateRes struct{}
Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package v1
1+
// This file defines dictionary data import DTOs.
22

3-
import (
4-
"github.com/gogf/gf/v2/frame/g"
5-
)
3+
package v1
64

7-
// DictData Import API
5+
import "github.com/gogf/gf/v2/frame/g"
86

97
// DataImportReq defines the request for importing dictionary data.
108
type DataImportReq struct {
@@ -23,11 +21,3 @@ type DataImportFailItem struct {
2321
Row int `json:"row" dc:"Line number" eg:"3"`
2422
Reason string `json:"reason" dc:"Reason for failure" eg:"Dictionary type does not exist"`
2523
}
26-
27-
// DataImportTemplateReq defines the request for downloading import template.
28-
type DataImportTemplateReq struct {
29-
g.Meta `path:"/dict/data/import-template" method:"get" tags:"Dictionary Management" summary:"Download dictionary data import template" dc:"Download the dictionary data import Excel template file, including required fields and data format instructions" permission:"system:dict:add"`
30-
}
31-
32-
// DataImportTemplateRes is the response for template download.
33-
type DataImportTemplateRes struct{}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file defines dictionary data import template DTOs.
2+
3+
package v1
4+
5+
import "github.com/gogf/gf/v2/frame/g"
6+
7+
// DataImportTemplateReq defines the request for downloading import template.
8+
type DataImportTemplateReq struct {
9+
g.Meta `path:"/dict/data/import-template" method:"get" tags:"Dictionary Management" summary:"Download dictionary data import template" dc:"Download the dictionary data import Excel template file, including required fields and data format instructions" permission:"system:dict:add"`
10+
}
11+
12+
// DataImportTemplateRes is the response for template download.
13+
type DataImportTemplateRes struct{}

apps/lina-core/api/dict/v1/dict_import.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
package v1
1+
// This file defines combined dictionary import DTOs.
22

3-
import (
4-
"github.com/gogf/gf/v2/frame/g"
5-
)
3+
package v1
64

7-
// Dict Combined Import API
5+
import "github.com/gogf/gf/v2/frame/g"
86

97
// ImportReq defines the request for importing dictionary types and data together.
108
type ImportReq struct {
@@ -26,11 +24,3 @@ type ImportFailItem struct {
2624
Row int `json:"row" dc:"Line number" eg:"3"`
2725
Reason string `json:"reason" dc:"Reason for failure" eg:"Dictionary type already exists"`
2826
}
29-
30-
// ImportTemplateReq defines the request for downloading combined import template.
31-
type ImportTemplateReq struct {
32-
g.Meta `path:"/dict/import-template" method:"get" tags:"Dictionary Management" summary:"Download Dictionary Management Import Template" dc:"Download the dictionary management and import Excel template file, which contains two Sheets of dictionary type and dictionary data. Each Sheet contains sample data and field descriptions." permission:"system:dict:add"`
33-
}
34-
35-
// ImportTemplateRes is the response for template download.
36-
type ImportTemplateRes struct{}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This file defines combined dictionary import template DTOs.
2+
3+
package v1
4+
5+
import "github.com/gogf/gf/v2/frame/g"
6+
7+
// ImportTemplateReq defines the request for downloading combined import template.
8+
type ImportTemplateReq struct {
9+
g.Meta `path:"/dict/import-template" method:"get" tags:"Dictionary Management" summary:"Download Dictionary Management Import Template" dc:"Download the dictionary management and import Excel template file, which contains two Sheets of dictionary type and dictionary data. Each Sheet contains sample data and field descriptions." permission:"system:dict:add"`
10+
}
11+
12+
// ImportTemplateRes is the response for template download.
13+
type ImportTemplateRes struct{}

0 commit comments

Comments
 (0)