package crud import ( "context" "fmt" "sync" "time" "aijianzhan/platform/internal/meta" "github.com/google/uuid" ) // MemoryEngine 骨架默认引擎:不依赖外部 DB,方便本地验证动态 CRUD。 type MemoryEngine struct { mu sync.RWMutex rows map[string][]map[string]any // schema.table } func NewMemoryEngine() *MemoryEngine { return &MemoryEngine{rows: map[string][]map[string]any{}} } func tableKey(ref *meta.ResourceRef) string { return ref.App.SchemaName + "." + ref.Entity.Table } func (e *MemoryEngine) List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) ([]map[string]any, int, error) { if err := ensureOp(ref, "list"); err != nil { return nil, 0, err } if err := validateFilters(ref, filters); err != nil { return nil, 0, err } if err := validateSort(ref, sortBy); err != nil { return nil, 0, err } page, pageSize = pageBounds(ref, page, pageSize) e.mu.RLock() defer e.mu.RUnlock() all := e.rows[tableKey(ref)] filtered := make([]map[string]any, 0, len(all)) for _, row := range all { if toInt64(row["tenant_id"]) != tenantID { continue } if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) { continue } if matchFilters(row, filters) { filtered = append(filtered, cloneRow(row)) } } sortRows(filtered, sortBy) total := len(filtered) start := (page - 1) * pageSize if start >= total { return []map[string]any{}, total, nil } end := start + pageSize if end > total { end = total } return filtered[start:end], total, nil } func (e *MemoryEngine) Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) { if err := ensureOp(ref, "get"); err != nil { return nil, err } e.mu.RLock() defer e.mu.RUnlock() pk := ref.Entity.PrimaryKey for _, row := range e.rows[tableKey(ref)] { if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) { break } return cloneRow(row), nil } } return nil, fmt.Errorf("not found") } func (e *MemoryEngine) Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) { if err := ensureOp(ref, "create"); err != nil { return nil, err } row, err := sanitizeBody(ref.Entity, body, false) if err != nil { return nil, err } pk := ref.Entity.PrimaryKey if _, ok := row[pk]; !ok { if isAutoPK(ref.Entity) { row[pk] = time.Now().UnixNano() } else { row[pk] = uuid.NewString() } } now := time.Now().UTC().Format(time.RFC3339) row["tenant_id"] = tenantID row["created_by"] = userID row["created_at"] = now row["updated_at"] = now if scope := RowScopeFrom(ctx); scope.WriteOrgUnit > 0 { row["org_unit_id"] = scope.WriteOrgUnit } e.mu.Lock() defer e.mu.Unlock() k := tableKey(ref) e.rows[k] = append(e.rows[k], row) return cloneRow(row), nil } func (e *MemoryEngine) Update(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) { if err := ensureOp(ref, "update"); err != nil { return nil, err } patch, err := sanitizeBody(ref.Entity, body, true) if err != nil { return nil, err } e.mu.Lock() defer e.mu.Unlock() pk := ref.Entity.PrimaryKey rows := e.rows[tableKey(ref)] for i, row := range rows { if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { for k, v := range patch { if k == pk || k == "tenant_id" { continue } row[k] = v } row["updated_at"] = time.Now().UTC().Format(time.RFC3339) rows[i] = row return cloneRow(row), nil } } return nil, fmt.Errorf("not found") } func (e *MemoryEngine) Delete(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string) error { if err := ensureOp(ref, "delete"); err != nil { return err } e.mu.Lock() defer e.mu.Unlock() pk := ref.Entity.PrimaryKey k := tableKey(ref) rows := e.rows[k] out := rows[:0] found := false for _, row := range rows { if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id { found = true continue } out = append(out, row) } if !found { return fmt.Errorf("not found") } e.rows[k] = out return nil }