mirror of
https://github.com/oarkflow/mq.git
synced 2025-09-27 20:32:15 +08:00
443 lines
13 KiB
Go
443 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/oarkflow/mq"
|
|
"github.com/oarkflow/mq/dag"
|
|
)
|
|
|
|
// User represents a user with roles
|
|
type User struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Roles []string `json:"roles"`
|
|
}
|
|
|
|
// HasRole checks if user has a specific role
|
|
func (u *User) HasRole(role string) bool {
|
|
for _, r := range u.Roles {
|
|
if r == role {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// HasAnyRole checks if user has any of the specified roles
|
|
func (u *User) HasAnyRole(roles ...string) bool {
|
|
for _, requiredRole := range roles {
|
|
if u.HasRole(requiredRole) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LoggingMiddleware logs the start and end of task processing
|
|
func LoggingMiddleware(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("Middleware: Starting processing for node %s, task %s", task.Topic, task.ID)
|
|
start := time.Now()
|
|
|
|
// For middleware, we return a successful result to continue to next middleware/processor
|
|
// The actual processing will happen after all middlewares
|
|
result := mq.Result{
|
|
Status: mq.Completed,
|
|
Ctx: ctx,
|
|
Payload: task.Payload, // Pass through the payload
|
|
}
|
|
|
|
log.Printf("Middleware: Completed in %v", time.Since(start))
|
|
return result
|
|
}
|
|
|
|
// ValidationMiddleware validates the task payload
|
|
func ValidationMiddleware(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("ValidationMiddleware: Validating payload for node %s", task.Topic)
|
|
|
|
// Check if payload is empty
|
|
if len(task.Payload) == 0 {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("empty payload not allowed"),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
log.Printf("ValidationMiddleware: Payload validation passed")
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Ctx: ctx,
|
|
Payload: task.Payload,
|
|
}
|
|
}
|
|
|
|
// RoleCheckMiddleware checks if the user has required roles for accessing a sub-DAG
|
|
func RoleCheckMiddleware(requiredRoles ...string) mq.Handler {
|
|
return func(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("RoleCheckMiddleware: Checking roles %v for node %s", requiredRoles, task.Topic)
|
|
|
|
// Extract user from payload
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(task.Payload, &payload); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid payload format: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
userData, exists := payload["user"]
|
|
if !exists {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("user information not found in payload"),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
userBytes, err := json.Marshal(userData)
|
|
if err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid user data: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
var user User
|
|
if err := json.Unmarshal(userBytes, &user); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid user format: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
if !user.HasAnyRole(requiredRoles...) {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("user %s does not have required roles %v. User roles: %v", user.Name, requiredRoles, user.Roles),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
log.Printf("RoleCheckMiddleware: User %s authorized for roles %v", user.Name, requiredRoles)
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Ctx: ctx,
|
|
Payload: task.Payload,
|
|
}
|
|
}
|
|
}
|
|
|
|
// TimingMiddleware measures execution time
|
|
func TimingMiddleware(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("TimingMiddleware: Starting timing for node %s", task.Topic)
|
|
|
|
// Add timing info to context
|
|
ctx = context.WithValue(ctx, "start_time", time.Now())
|
|
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Ctx: ctx,
|
|
Payload: task.Payload,
|
|
}
|
|
}
|
|
|
|
// Example processor that simulates some work
|
|
type ExampleProcessor struct {
|
|
dag.Operation
|
|
}
|
|
|
|
func (p *ExampleProcessor) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("Processor: Processing task %s on node %s", task.ID, task.Topic)
|
|
|
|
// Simulate some processing time
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Parse the payload as JSON
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(task.Payload, &payload); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid payload: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// Add processing information
|
|
payload["processed_by"] = "ExampleProcessor"
|
|
payload["processing_time"] = time.Now().Format(time.RFC3339)
|
|
|
|
resultPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("failed to marshal result: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Payload: resultPayload,
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// AdminProcessor handles admin-specific tasks
|
|
type AdminProcessor struct {
|
|
dag.Operation
|
|
}
|
|
|
|
func (p *AdminProcessor) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("AdminProcessor: Processing admin task %s on node %s", task.ID, task.Topic)
|
|
|
|
// Simulate admin processing
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
// Parse the payload as JSON
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(task.Payload, &payload); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid payload: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// Add admin-specific processing information
|
|
payload["processed_by"] = "AdminProcessor"
|
|
payload["admin_action"] = "validated_and_processed"
|
|
payload["processing_time"] = time.Now().Format(time.RFC3339)
|
|
|
|
resultPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("failed to marshal result: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Payload: resultPayload,
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// UserProcessor handles user-specific tasks
|
|
type UserProcessor struct {
|
|
dag.Operation
|
|
}
|
|
|
|
func (p *UserProcessor) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("UserProcessor: Processing user task %s on node %s", task.ID, task.Topic)
|
|
|
|
// Simulate user processing
|
|
time.Sleep(150 * time.Millisecond)
|
|
|
|
// Parse the payload as JSON
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(task.Payload, &payload); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid payload: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// Add user-specific processing information
|
|
payload["processed_by"] = "UserProcessor"
|
|
payload["user_action"] = "authenticated_and_processed"
|
|
payload["processing_time"] = time.Now().Format(time.RFC3339)
|
|
|
|
resultPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("failed to marshal result: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Payload: resultPayload,
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// GuestProcessor handles guest-specific tasks
|
|
type GuestProcessor struct {
|
|
dag.Operation
|
|
}
|
|
|
|
func (p *GuestProcessor) ProcessTask(ctx context.Context, task *mq.Task) mq.Result {
|
|
log.Printf("GuestProcessor: Processing guest task %s on node %s", task.ID, task.Topic)
|
|
|
|
// Simulate guest processing
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Parse the payload as JSON
|
|
var payload map[string]any
|
|
if err := json.Unmarshal(task.Payload, &payload); err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("invalid payload: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// Add guest-specific processing information
|
|
payload["processed_by"] = "GuestProcessor"
|
|
payload["guest_action"] = "limited_access_processed"
|
|
payload["processing_time"] = time.Now().Format(time.RFC3339)
|
|
|
|
resultPayload, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return mq.Result{
|
|
Status: mq.Failed,
|
|
Error: fmt.Errorf("failed to marshal result: %v", err),
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
return mq.Result{
|
|
Status: mq.Completed,
|
|
Payload: resultPayload,
|
|
Ctx: ctx,
|
|
}
|
|
}
|
|
|
|
// createAdminSubDAG creates a sub-DAG for admin operations
|
|
func createAdminSubDAG() *dag.DAG {
|
|
adminDAG := dag.NewDAG("Admin Sub-DAG", "admin-subdag", func(taskID string, result mq.Result) {
|
|
log.Printf("Admin Sub-DAG completed for task %s: %s", taskID, string(result.Payload))
|
|
})
|
|
|
|
adminDAG.AddNode(dag.Function, "Admin Validate", "admin_validate", &AdminProcessor{Operation: dag.Operation{Type: dag.Function}}, true)
|
|
adminDAG.AddNode(dag.Function, "Admin Process", "admin_process", &AdminProcessor{Operation: dag.Operation{Type: dag.Function}})
|
|
adminDAG.AddNode(dag.Function, "Admin Finalize", "admin_finalize", &AdminProcessor{Operation: dag.Operation{Type: dag.Function}})
|
|
|
|
adminDAG.AddEdge(dag.Simple, "Validate to Process", "admin_validate", "admin_process")
|
|
adminDAG.AddEdge(dag.Simple, "Process to Finalize", "admin_process", "admin_finalize")
|
|
|
|
return adminDAG
|
|
}
|
|
|
|
// createUserSubDAG creates a sub-DAG for user operations
|
|
func createUserSubDAG() *dag.DAG {
|
|
userDAG := dag.NewDAG("User Sub-DAG", "user-subdag", func(taskID string, result mq.Result) {
|
|
log.Printf("User Sub-DAG completed for task %s: %s", taskID, string(result.Payload))
|
|
})
|
|
|
|
userDAG.AddNode(dag.Function, "User Auth", "user_auth", &UserProcessor{Operation: dag.Operation{Type: dag.Function}}, true)
|
|
userDAG.AddNode(dag.Function, "User Process", "user_process", &UserProcessor{Operation: dag.Operation{Type: dag.Function}})
|
|
userDAG.AddNode(dag.Function, "User Notify", "user_notify", &UserProcessor{Operation: dag.Operation{Type: dag.Function}})
|
|
|
|
userDAG.AddEdge(dag.Simple, "Auth to Process", "user_auth", "user_process")
|
|
userDAG.AddEdge(dag.Simple, "Process to Notify", "user_process", "user_notify")
|
|
|
|
return userDAG
|
|
}
|
|
|
|
// createGuestSubDAG creates a sub-DAG for guest operations
|
|
func createGuestSubDAG() *dag.DAG {
|
|
guestDAG := dag.NewDAG("Guest Sub-DAG", "guest-subdag", func(taskID string, result mq.Result) {
|
|
log.Printf("Guest Sub-DAG completed for task %s: %s", taskID, string(result.Payload))
|
|
})
|
|
|
|
guestDAG.AddNode(dag.Function, "Guest Welcome", "guest_welcome", &GuestProcessor{Operation: dag.Operation{Type: dag.Function}}, true)
|
|
guestDAG.AddNode(dag.Function, "Guest Info", "guest_info", &GuestProcessor{Operation: dag.Operation{Type: dag.Function}})
|
|
|
|
guestDAG.AddEdge(dag.Simple, "Welcome to Info", "guest_welcome", "guest_info")
|
|
|
|
return guestDAG
|
|
}
|
|
|
|
func main() {
|
|
// Create the main DAG
|
|
flow := dag.NewDAG("Role-Based Access Control DAG", "rbac-dag", func(taskID string, result mq.Result) {
|
|
log.Printf("Main DAG completed for task %s: %s", taskID, string(result.Payload))
|
|
})
|
|
|
|
// Add entry point
|
|
flow.AddNode(dag.Function, "Entry Point", "entry", &ExampleProcessor{Operation: dag.Operation{Type: dag.Function}}, true)
|
|
|
|
// Add sub-DAGs with role-based access
|
|
flow.AddDAGNode(dag.Function, "Admin Operations", "admin_ops", createAdminSubDAG())
|
|
flow.AddDAGNode(dag.Function, "User Operations", "user_ops", createUserSubDAG())
|
|
flow.AddDAGNode(dag.Function, "Guest Operations", "guest_ops", createGuestSubDAG())
|
|
|
|
// Add edges from entry to sub-DAGs
|
|
flow.AddEdge(dag.Simple, "Entry to Admin", "entry", "admin_ops")
|
|
flow.AddEdge(dag.Simple, "Entry to User", "entry", "user_ops")
|
|
flow.AddEdge(dag.Simple, "Entry to Guest", "entry", "guest_ops")
|
|
|
|
// Add global middlewares
|
|
flow.Use(LoggingMiddleware, ValidationMiddleware)
|
|
|
|
// Add role-based middlewares for sub-DAGs
|
|
flow.UseNodeMiddlewares(
|
|
dag.NodeMiddleware{
|
|
Node: "admin_ops",
|
|
Middlewares: []mq.Handler{RoleCheckMiddleware("admin", "superuser")},
|
|
},
|
|
dag.NodeMiddleware{
|
|
Node: "user_ops",
|
|
Middlewares: []mq.Handler{RoleCheckMiddleware("user", "admin", "superuser")},
|
|
},
|
|
dag.NodeMiddleware{
|
|
Node: "guest_ops",
|
|
Middlewares: []mq.Handler{RoleCheckMiddleware("guest", "user", "admin", "superuser")},
|
|
},
|
|
)
|
|
|
|
if flow.Error != nil {
|
|
panic(flow.Error)
|
|
}
|
|
|
|
// Define test users with different roles
|
|
users := []User{
|
|
{ID: "1", Name: "Alice", Roles: []string{"admin", "superuser"}},
|
|
{ID: "2", Name: "Bob", Roles: []string{"user"}},
|
|
{ID: "3", Name: "Charlie", Roles: []string{"guest"}},
|
|
{ID: "4", Name: "Dave", Roles: []string{"user", "admin"}},
|
|
{ID: "5", Name: "Eve", Roles: []string{}}, // No roles
|
|
}
|
|
|
|
// Test each user
|
|
for _, user := range users {
|
|
log.Printf("\n=== Testing user: %s (Roles: %v) ===", user.Name, user.Roles)
|
|
|
|
// Create payload with user information
|
|
payload := map[string]any{
|
|
"user": user,
|
|
"message": fmt.Sprintf("Request from %s", user.Name),
|
|
"data": "test data",
|
|
}
|
|
|
|
payloadBytes, err := json.Marshal(payload)
|
|
if err != nil {
|
|
log.Printf("Failed to marshal payload for user %s: %v", user.Name, err)
|
|
continue
|
|
}
|
|
|
|
log.Printf("Processing request for user %s with payload: %s", user.Name, string(payloadBytes))
|
|
|
|
result := flow.Process(context.Background(), payloadBytes)
|
|
if result.Error != nil {
|
|
log.Printf("❌ DAG processing failed for user %s: %v", user.Name, result.Error)
|
|
} else {
|
|
log.Printf("✅ DAG processing completed successfully for user %s: %s", user.Name, string(result.Payload))
|
|
}
|
|
}
|
|
}
|