# CLAUDE.md > **Cross-repo rules** — see `/Users/wen/project/rag/CLAUDE.md` for full workspace conventions. > - .NET backends share JWT key: `RagJwtSecretKey2026MustBeAtLeast32CharsLong!` > - 鉴权:本服务用 OIDC Authority(信 SSO 签发的 RS256 JWT,框架自动发现 JWKS),不调 rag-backend gRPC > - 其他仓库:`rag-backend`(5211)、`im-system`(5212)、`work-flow`、`order-service`、`rag-frontend`(5666 Vue) ## Build & Run ```bash dotnet build # 全量构建(TreatWarningsAsErrors) cd src/FileSystem.Api && dotnet run # HTTP :8080, gRPC :9090 cd src/FileSystem.Infrastructure && dotnet ef migrations add --startup-project ../FileSystem.Api cd src/FileSystem.Infrastructure && dotnet ef database update --startup-project ../FileSystem.Api docker compose up -d # PostgreSQL + 服务 ``` ## Architecture .NET 10, ABP modular. Four projects: `Api → Application → Infrastructure → Domain`。 ### ABP Module Chain ``` FileSystemApiModule [DependsOn(FileSystemApplicationModule, FileSystemInfrastructureModule)] → FileSystemApplicationModule [DependsOn(FileSystemInfrastructureModule)] → FileSystemInfrastructureModule [DependsOn(FileSystemDomainModule)] ``` DI 在 `ConfigureServices()`,中间件在 `OnApplicationInitialization()`。`Program.cs` 经 `builder.AddApplicationAsync()` 引导。 ### Middleware Order(不可变) ``` Cors → GlobalExceptionMiddleware → MapGrpcService → ApiResponseMiddleware → Authentication → Authorization → FastEndpoints(RoutePrefix="api") → SwaggerGen ``` **`MapGrpcService` 必须在 `ApiResponseMiddleware` 之前**(否则 MemoryStream 缓冲破坏 gRPC HTTP/2 帧)。 ### 鉴权 OIDC Authority 模式:`AddAuthentication("Bearer").AddJwtBearer(Authority = Jwt:Authority, MapInboundClaims=false, ValidateAudience=false, ValidateIssuer=false)`。框架自动从 SSO 发现 JWKS 验签 RS256。 全局 fallback policy(`RequireAuthenticatedUser`):所有未声明 `[AllowAnonymous]` 的 endpoint 默认要求认证。公开接口(`GetShareInfoEndpoint` / `DownloadShareEndpoint`)在 endpoint 类标注 `[AllowAnonymous]`。 gRPC 鉴权:`FileGrpcService` 类级 `[Authorize]`,`GetShareInfo`/`DownloadShare` 方法级 `[AllowAnonymous]`。 ## Tech Stack - ABP 10.3(模块化)、FastEndpoints 8.1(HTTP REPR)、MediatR 14(CQRS) - EF Core 10 + Npgsql(PostgreSQL)、FluentValidation 12 - AWSSDK.S3(ForcePathStyle,RustFS/MinIO 兼容) - Grpc.AspNetCore(原生 proto code-gen) ## Code Patterns ### Entity(硬删除,不软删) file-system 的三实体(`Folder`/`FileMeta`/`ShareLink`)实现 `IAuditable + IHasOperatorIP`,**不**实现 `ISoftDelete`——忠实 Go 版硬删除语义,`Remove()` 走真实物理删除。 ```csharp public class Folder : BaseEntity, IAuditable, IHasOperatorIP { public Guid? ParentId { get; set; } public string Name { get; set; } = default!; public string OwnerId { get; set; } = default!; // IAuditable + IHasOperatorIP 字段(= default!) } ``` ### EF Configuration(snake_case + ValueGeneratedNever) ```csharp public class FolderConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("folders"); builder.HasKey(e => e.Id); builder.Property(e => e.Id).ValueGeneratedNever(); builder.HasIndex(e => e.OwnerId); } } ``` 自动发现(`ApplyConfigurationsFromAssembly`)。 ### Command + Handler(同文件,主构造注入) ```csharp public record CreateFolderCommand(Guid? ParentId, string Name) : IRequest; public class CreateFolderCommandHandler(FileSystemDbContext db, ICurrentUserContext currentUser, IMediator mediator) : IRequestHandler { ... } ``` `ValidationBehavior` 管线自动跑 FluentValidation。 ### Endpoint(FastEndpoints REPR) ```csharp public class CreateFolderEndpoint(IMediator mediator) : Endpoint { public override void Configure() => Post("/folders"); public override async Task HandleAsync(CreateFolderRequest req, CancellationToken ct) { var result = await mediator.Send(new CreateFolderCommand(...), ct); await Send.OkAsync(result, ct); } } ``` - 受保护 endpoint:全局 fallback policy 兜底认证 - 公开 endpoint:endpoint 类 `[AllowAnonymous]` - 路由前缀 `/api/`(Module 配置) - 文件上传:`AllowFileUploads()` + Request 的 `IFormFile File` ### gRPC(双协议共享 MediatR) `FileGrpcService : FileService.FileServiceBase`(proto 生成),24 RPC 全部经 `IMediator.Send` 复用 Application 层 Command/Query。HTTP endpoint 与 gRPC 共享业务逻辑。proto 见 `src/FileSystem.Api/Protos/file.proto`。 ### 递归 CTE 删文件夹(DeleteFolderCommand) PostgreSQL 递归 CTE(EF Core 不原生支持,用原生 SQL): 1. 事务外 `SqlQuery` 查后代 file 的 S3 引用; 2. 单事务内 `ExecuteSqlInterpolated` 递归删 files + 删 folder 子树; 3. 事务提交后 best-effort 循环删 S3(单失败只 log); 4. 发布 `FolderDeletedEvent`。 ### S3 存储(S3StorageService) AWSSDK.S3,`ForcePathStyle=true`。`CompleteMultipartUpload` 内部按 PartNumber 升序排序。NoSuchKey/NotFound → `NotFoundException`(404)。 ## Conventions - file-scoped namespaces、primary constructor DI、`record` for DTO/Command/Query - `class` for Entity/Handler/Endpoint/Middleware - NRT 开启 + `= default!`、`TreatWarningsAsErrors` ON - 响应:`{ code: 0, data, message: "ok" }`(ApiResponseMiddleware 自动包裹) - 路由前缀 `/api/` - 错误码:401/403/400/404/409/410/413(GlobalExceptionMiddleware 映射) ## Feature Folder ``` Application/{Feature}/Commands/ Query+Handler 同文件 Application/{Feature}/Queries/ Application/{Feature}/DTOs/ Application/{Feature}/Validators/ Api/Endpoints/{Feature}/ FastEndpoint ``` Features:`Files`、`Multipart`、`Buckets`、`Folders`、`Shares`。 ## Port & Config - HTTP `:8080`(Kestrel Http1,前端 `/file-api` 代理) - gRPC `:9090`(Kestrel Http2) - PostgreSQL 库 `file_system` - `Kestrel:Limits:MaxRequestBodySize` = 100 MiB;gRPC `MaxReceiveMessageSize` = 100 MiB