Conception d'un Système de Workflow Documentaire avec Spring et Activiti

Refonte de l'Architecture du Contrôleur de Workflow

L'architecture initiale d'un système de gestion documentaire (OA) mélange souvent la logique métier, l'orchestration du workflow et la manipulation des fichiers au sein même des contrôleurs Spring MVC. Pour garantir la maintenabilité et l'évolutivité, il est impératif d'adopter une approche RESTful, de séparer les préoccupations et de déléguer les opérations complexes à des services dédiés.

Le code ci-dessous illustre une refonte moderne du processus de création de document et d'initialisation du workflow Activiti. L'utilisatoin de @RestController, de l'injection par constructeur et d'objets de transfert de données (DTO) remplace les anciennes pratiques basées sur les vues JSP et les objets Map.


@RestController
@RequestMapping("/api/v1/documents")
public class DocumentWorkflowController {

    private final DocumentService documentService;
    private final WorkflowOrchestrator workflowOrchestrator;
    private final SecureStorageService storageService;

    public DocumentWorkflowController(DocumentService documentService, 
                                      WorkflowOrchestrator workflowOrchestrator, 
                                      SecureStorageService storageService) {
        this.documentService = documentService;
        this.workflowOrchestrator = workflowOrchestrator;
        this.storageService = storageService;
    }

    @PostMapping("/drafts")
    @Transactional(rollbackFor = Exception.class)
    public ResponseEntity<ApiResponse<DocumentResponse>> createDraft(
            @Valid @RequestPart("metadata") DraftRequestDto request,
            @RequestPart("mainFile") MultipartFile mainDoc,
            @RequestPart(value = "attachments", required = false) MultipartFile[] attachments) {

        // 1. Persistance des métadonnées et validation
        DocumentEntity doc = documentService.initializeDraft(request);

        // 2. Chiffrement et stockage du document principal via JackRabbit
        String mainFileId = storageService.encryptAndStore(mainDoc, CryptoAlgorithm.SM4_ECB);
        documentService.attachMainFile(doc.getId(), mainFileId, mainDoc.getOriginalFilename());

        // 3. Traitement des pièces jointes supplémentaires
        if (attachments != null && attachments.length > 0) {
            Arrays.stream(attachments).forEach(att -> {
                String attId = storageService.encryptAndStore(att, CryptoAlgorithm.SM4_ECB);
                documentService.addAttachment(doc.getId(), attId, att.getOriginalFilename());
            });
        }

        // 4. Démarrage du processus de révision Activiti
        String processInstanceId = workflowOrchestrator.startReviewProcess(
                doc.getId(), 
                request.getAuditorId(), 
                request.getAuthorId()
        );
        documentService.updateProcessInstance(doc.getId(), processInstanceId);

        DocumentResponse response = new DocumentResponse(doc.getId(), processInstanceId);
        return ResponseEntity.ok(ApiResponse.success("Document créé et workflow initié.", response));
    }
}

Gestion des Accès et Sécurité RBAC

La gestion des rôles et des permissions (RBAC) nécessite une validation rigoureuse et une synchronisation efficace avec la base de données. Au lieu de renvoyer des chaînes de caractères ou des redirections, l'API ci-dessous standardise les réponses et utilise les capacités de validation de Spring pour assurer l'intégrité des données lors de l'attribution des droits ou de l'importation d'utilisateurs.


@RestController
@RequestMapping("/api/v1/iam")
public class IdentityAndAccessController {

    private final RoleService roleService;
    private final PermissionService permissionService;
    private final UserImportService userImportService;

    public IdentityAndAccessController(RoleService roleService, 
                                       PermissionService permissionService, 
                                       UserImportService userImportService) {
        this.roleService = roleService;
        this.permissionService = permissionService;
        this.userImportService = userImportService;
    }

    @PutMapping("/roles/{roleId}/permissions")
    public ResponseEntity<ApiResponse<Void>> assignPermissions(
            @PathVariable Integer roleId,
            @RequestBody @NotEmpty List<Integer> permissionIds) {

        roleService.validateRoleExists(roleId);
        permissionService.syncRolePermissions(roleId, permissionIds);

        return ResponseEntity.ok(ApiResponse.success("Permissions mises à jour avec succès."));
    }

    @PostMapping("/users/batch-import")
    public ResponseEntity<ApiResponse<ImportResult>> importUsers(
            @RequestParam("excelFile") MultipartFile excelFile) {
        
        if (!ExcelValidator.isValidFormat(excelFile)) {
            return ResponseEntity.badRequest()
                    .body(ApiResponse.error("Format de fichier Excel invalide."));
        }
        
        ImportResult result = userImportService.processAndPersist(excelFile);
        return ResponseEntity.ok(ApiResponse.success("Importation terminée.", result));
    }
}

Service de Stockage Sécurisé et Déchiffrement

Le traitement des fichiers dans un environnement d'entreprise exige une sécurité robuste. L'extraction de la logique de chiffrement SM4 et de l'interaction avec le référentiel JackRabbit dans un service dédié permet de réutiliser cette logique à travers l'application, que ce soit pour l'upload ou le téléchargement de documents.


@Service
public class SecureStorageService {

    private final JackRabbitClient jackRabbitClient;
    private final SM4CryptoProvider sm4CryptoProvider;

    public SecureStorageService(JackRabbitClient jackRabbitClient, SM4CryptoProvider sm4CryptoProvider) {
        this.jackRabbitClient = jackRabbitClient;
        this.sm4CryptoProvider = sm4CryptoProvider;
    }

    public String encryptAndStore(MultipartFile file, CryptoAlgorithm algorithm) {
        try (InputStream inputStream = file.getInputStream()) {
            byte[] encryptedData = sm4CryptoProvider.encrypt(inputStream.readAllBytes(), algorithm);
            return jackRabbitClient.upload(new ByteArrayInputStream(encryptedData), file.getContentType());
        } catch (IOException e) {
            throw new StorageException("Échec du chiffrement et du stockage du fichier", e);
        }
    }

    public byte[] retrieveAndDecrypt(String fileId, CryptoAlgorithm algorithm) {
        byte[] encryptedData = jackRabbitClient.download(fileId);
        return sm4CryptoProvider.decrypt(encryptedData, algorithm);
    }
}

Étiquettes: SpringMVC Activiti JackRabbit SM4 RBAC

Publié le 2 juillet à 02h30