146 lines
4.9 KiB
Markdown
146 lines
4.9 KiB
Markdown
# CLAUDE.md
|
|
|
|
> **Cross-repo rules** — see `/Users/wen/project/rag/CLAUDE.md` for full workspace conventions.
|
|
> - .NET backends share JWT key: `RagJwtSecretKey2026MustBeAtLeast32CharsLong!`
|
|
> - gRPC auth: call `rag-backend:50051` for token validation + permission checks
|
|
> - Other repos: `rag-backend` (5211), `im-system` (5212), `work-flow`, `rag-frontend` (5666 Vue)
|
|
|
|
## Build & Run
|
|
|
|
```bash
|
|
go run ./cmd/server -conf configs/config.yaml # HTTP :8080, gRPC :9000
|
|
make api # Regenerate proto code
|
|
make wire # Regenerate Wire DI
|
|
go test ./... # All tests
|
|
buf generate # Regenerate proto
|
|
docker compose up -d # Via local build
|
|
```
|
|
|
|
## Architecture
|
|
|
|
Go 1.25 microservice. Kratos framework + DDD four-layer + Watermill CQRS.
|
|
|
|
```
|
|
cmd/server/main.go → Entry point, load config
|
|
cmd/server/wire.go → Wire DI declarations
|
|
cmd/server/wire_gen.go → Wire generated code
|
|
api/file/v1/ → Proto definitions (HTTP+gRPC dual protocol)
|
|
internal/conf/ → Config structs (proto-defined)
|
|
internal/biz/ → Business logic layer (repo interface definitions + usecases)
|
|
internal/data/ → Data access layer (GORM + S3, implements biz repo interfaces)
|
|
internal/service/ → Service implementation (implements proto Service interface)
|
|
internal/server/ → HTTP/gRPC server creation and middleware
|
|
internal/watermark/ → Watermill CQRS (CommandBus + EventBus)
|
|
internal/pkg/sanitize/ → Input sanitization utilities
|
|
internal/pkg/s3errors/ → S3 error mapping
|
|
configs/config.yaml → Local dev config
|
|
```
|
|
|
|
## Layered Call Chain
|
|
|
|
```
|
|
service (DTO conversion) → biz (business logic) → data (data access)
|
|
↕
|
|
watermark (CQRS commands/events)
|
|
```
|
|
|
|
## Tech Stack
|
|
|
|
- **Kratos** — HTTP + gRPC framework, proto-first API definition
|
|
- **Wire** — Compile-time dependency injection
|
|
- **Watermill** — CQRS (CommandBus + EventBus), PGSQL as message store
|
|
- **GORM** — Business data ORM (folders, files, share_links)
|
|
- **AWS SDK v2** — S3 interface to RustFS/MinIO
|
|
- **PostgreSQL** — Business data + Watermill message queue
|
|
|
|
## Code Patterns
|
|
|
|
### Wire ProviderSet Pattern
|
|
|
|
Each layer defines a ProviderSet:
|
|
```go
|
|
// internal/data/data.go
|
|
var ProviderSet = wire.NewSet(NewData, NewFileRepo, NewFolderRepo, NewFileMetaRepo, NewShareRepo)
|
|
|
|
// internal/biz/biz.go
|
|
var ProviderSet = wire.NewSet(NewFileUsecase, NewBucketUsecase, NewFolderUsecase, NewShareUsecase)
|
|
|
|
// internal/service/service.go
|
|
var ProviderSet = wire.NewSet(NewFileService)
|
|
|
|
// internal/server/server.go
|
|
var ProviderSet = wire.NewSet(NewHTTPServer, NewGRPCServer)
|
|
```
|
|
|
|
Wire bindings (in cmd/server/wire.go):
|
|
- `biz.FileRepo` → `*data.FileRepo`
|
|
- `biz.FolderRepo` → `*data.FolderRepo`
|
|
- `biz.FileMetaRepo` → `*data.FileMetaRepo`
|
|
- `biz.ShareRepo` → `*data.ShareRepo`
|
|
|
|
### GORM Transaction Management
|
|
|
|
```go
|
|
// biz layer defines interface
|
|
type Transaction interface { InTx(ctx context.Context, fn func(ctx context.Context) error) error }
|
|
|
|
// data layer implements
|
|
func (d *Data) DB(ctx context.Context) *gorm.DB {
|
|
tx, ok := ctx.Value(contextTxKey{}).(*gorm.DB)
|
|
if ok { return tx }
|
|
return d.db.WithContext(ctx)
|
|
}
|
|
```
|
|
|
|
### Proto Error Definition
|
|
|
|
Errors defined in `api/file/v1/error_reason.proto`:
|
|
```protobuf
|
|
enum ErrorReason {
|
|
FILE_NOT_FOUND = 0 [(errors.code) = 404];
|
|
INVALID_PARAMETER = 1 [(errors.code) = 400];
|
|
}
|
|
```
|
|
|
|
Usage: `return nil, api.file.v1.ErrorFileNotFound("file %s not found", key)`
|
|
|
|
### GORM Model Naming
|
|
|
|
- Models suffixed with `PO`: `FolderPO`, `FileMetaPO`, `ShareLinkPO`
|
|
- Table names via `TableName()` method with snake_case
|
|
|
|
### Service → Biz → Data Pattern
|
|
|
|
```go
|
|
// service: proto request → usecase call → proto response
|
|
func (s *FileService) UploadFile(ctx context.Context, req *pb.UploadFileRequest) (*pb.UploadFileResponse, error) {
|
|
err := s.fileUC.UploadFile(ctx, req.BucketName, req.ObjectKey, bytes.NewReader(req.Data))
|
|
return &pb.UploadFileResponse{Message: "uploaded"}, err
|
|
}
|
|
|
|
// biz: business logic, repo interface calls
|
|
func (uc *FileUsecase) UploadFile(ctx context.Context, bucket, key string, data io.Reader) error {
|
|
return uc.repo.UploadFile(ctx, bucket, key, data)
|
|
}
|
|
|
|
// data: concrete implementation
|
|
func (r *FileRepo) UploadFile(ctx context.Context, bucket, key string, data io.Reader) error {
|
|
_, err := r.client.PutObject(ctx, &s3.PutObjectInput{...})
|
|
return s3errors.Wrap(err)
|
|
}
|
|
```
|
|
|
|
### Configuration
|
|
|
|
`configs/config.yaml` for local development. Environment variables via `${ENV_VAR}` placeholders.
|
|
Loaded via Kratos config component with file source.
|
|
|
|
### Middleware Chain (Kratos)
|
|
|
|
HTTP and gRPC share the same middleware:
|
|
```
|
|
recovery → tracing → logging
|
|
```
|
|
|
|
Configured in `internal/server/http.go` and `internal/server/grpc.go`.
|