# Infrastructure Refactoring Phase 2 Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 完成剩余 Handler 的函数式风格迁移、完善 Router 配置、优化其他 Converter、引入 MapStruct 和添加单元测试。

**Architecture:** 保持已建立的 DAO 层设计模式，将所有 Handler 从注解式迁移到函数式 WebFlux 风格，使用 MapStruct 自动生成转换代码，建立完整的单元测试覆盖。

**Tech Stack:** Spring WebFlux, R2DBC, MapStruct, JUnit 5, Mockito, Maven

---

## Phase 1: 完成其他 Handler 的函数式风格迁移

### Task 1: SysUserHandler 函数式迁移

**Files:**
- Modify: `handler/user/SysUserHandler.java`

**Step 1: 备份当前实现**

```bash
cp handler/user/SysUserHandler.java handler/user/SysUserHandler.java.bak
```

**Step 2: 修改为函数式风格**

```java
@Component
public class SysUserHandler {
    private final ISysUserService userService;

    public Mono<ServerResponse> getAllUsers(ServerRequest request) {
        boolean includeDeleted = Boolean.valueOf(request.queryParam("includeDeleted").orElse("false"));
        return ServerResponse.ok()
                .body(userService.findAll(includeDeleted), SysUser.class);
    }

    public Mono<ServerResponse> getUsersByPage(ServerRequest request) {
        int page = Integer.parseInt(request.queryParam("page").orElse("0"));
        int size = Integer.parseInt(request.queryParam("size").orElse("10"));
        String sort = request.queryParam("sort").orElse("id");
        String order = request.queryParam("order").orElse("asc");
        String keyword = request.queryParam("keyword").orElse(null);

        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(page);
        pageRequest.setSize(size);
        pageRequest.setSort(sort);
        pageRequest.setOrder(order);
        pageRequest.setKeyword(keyword);

        return userService.findUsersByPage(pageRequest)
                .flatMap(response -> ServerResponse.ok().bodyValue(response));
    }

    public Mono<ServerResponse> getUserById(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return userService.findById(id)
                .flatMap(user -> ServerResponse.ok().bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> getUserByUsername(ServerRequest request) {
        String username = request.pathVariable("username");
        return userService.findByUsername(username)
                .flatMap(user -> ServerResponse.ok().bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createUser(ServerRequest request) {
        return request.bodyToMono(SysUser.class)
                .flatMap(userService::createUser)
                .flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
    }

    public Mono<ServerResponse> updateUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return request.bodyToMono(UserUpdateRequest.class)
                .flatMap(req -> userService.findById(id)
                        .flatMap(existing -> {
                            if (req.getEmail() != null) existing.setEmail(req.getEmail());
                            if (req.getStatus() != null) existing.setStatus(req.getStatus());
                            if (req.getRoleId() != null) existing.setRoleId(req.getRoleId());
                            return userService.updateUser(existing);
                        }))
                .flatMap(user -> ServerResponse.ok().bodyValue(user))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> deleteUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return userService.deleteUser(id)
                .then(ServerResponse.noContent().build());
    }

    public Mono<ServerResponse> changePassword(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return request.bodyToMono(PasswordChangeRequest.class)
                .flatMap(req -> userService.changePassword(id, req.getOldPassword(), req.getNewPassword()))
                .flatMap(user -> ServerResponse.ok().bodyValue(user));
    }

    public Mono<ServerResponse> logicalDeleteUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return userService.logicalDeleteUser(id)
                .then(ServerResponse.noContent().build());
    }

    public Mono<ServerResponse> logicalDeleteUsers(ServerRequest request) {
        return request.bodyToMono(new ParameterizedTypeReference<List<Long>>() {})
                .flatMap(ids -> userService.logicalDeleteUsers(ids))
                .then(ServerResponse.noContent().build());
    }

    public Mono<ServerResponse> restoreUser(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return userService.restoreUser(id)
                .then(ServerResponse.noContent().build());
    }

    public Mono<ServerResponse> restoreUsers(ServerRequest request) {
        return request.bodyToMono(new ParameterizedTypeReference<List<Long>>() {})
                .flatMap(ids -> userService.restoreUsers(ids))
                .then(ServerResponse.noContent().build());
    }

    public Mono<ServerResponse> checkUsernameExists(ServerRequest request) {
        String username = request.queryParam("username").orElse(null);
        return userService.existsByUsername(username)
                .flatMap(exists -> ServerResponse.ok().bodyValue(exists));
    }

    public Mono<ServerResponse> checkEmailExists(ServerRequest request) {
        String email = request.queryParam("email").orElse(null);
        return userService.existsByEmail(email)
                .flatMap(exists -> ServerResponse.ok().bodyValue(exists));
    }
}
```

**Step 3: 测试路由配置**

```bash
curl -X GET http://localhost:8080/api/users
curl -X GET http://localhost:8080/api/users/1
curl -X POST http://localhost:8080/api/users -H "Content-Type: application/json" -d '{"username":"test","password":"123456"}'
```

**Step 4: 提交变更**

```bash
git add handler/user/SysUserHandler.java
git commit -m "refactor: migrate SysUserHandler to functional WebFlux style"
```

---

### Task 2: SysRoleHandler 函数式迁移

**Files:**
- Modify: `handler/role/SysRoleHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysRoleHandler {
    private final ISysRoleService roleService;

    public Mono<ServerResponse> getAllRoles(ServerRequest request) {
        return ServerResponse.ok()
                .body(roleService.findAll(), SysRole.class);
    }

    public Mono<ServerResponse> getRoleById(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return roleService.findById(id)
                .flatMap(role -> ServerResponse.ok().bodyValue(role))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createRole(ServerRequest request) {
        return request.bodyToMono(SysRole.class)
                .flatMap(roleService::save)
                .flatMap(role -> ServerResponse.status(HttpStatus.CREATED).bodyValue(role));
    }

    public Mono<ServerResponse> updateRole(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return request.bodyToMono(SysRole.class)
                .flatMap(role -> roleService.update(id, role))
                .flatMap(updatedRole -> ServerResponse.ok().bodyValue(updatedRole))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> deleteRole(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return roleService.deleteById(id)
                .then(ServerResponse.noContent().build());
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/role/SysRoleHandler.java
git commit -m "refactor: migrate SysRoleHandler to functional WebFlux style"
```

---

### Task 3: SysConfigHandler 函数式迁移

**Files:**
- Modify: `handler/config/SysConfigHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysConfigHandler {
    private final ISysConfigService configService;

    public Mono<ServerResponse> getAllConfigs(ServerRequest request) {
        return ServerResponse.ok()
                .body(configService.findAll(), SysConfig.class);
    }

    public Mono<ServerResponse> getConfigByKey(ServerRequest request) {
        String configKey = request.pathVariable("configKey");
        return configService.findByConfigKey(configKey)
                .flatMap(config -> ServerResponse.ok().bodyValue(config))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createConfig(ServerRequest request) {
        return request.bodyToMono(SysConfig.class)
                .flatMap(configService::save)
                .flatMap(config -> ServerResponse.status(HttpStatus.CREATED).bodyValue(config));
    }

    public Mono<ServerResponse> updateConfig(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return request.bodyToMono(SysConfig.class)
                .flatMap(config -> configService.update(id, config))
                .flatMap(updatedConfig -> ServerResponse.ok().bodyValue(updatedConfig))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> deleteConfig(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return configService.deleteById(id)
                .then(ServerResponse.noContent().build());
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/config/SysConfigHandler.java
git commit -m "refactor: migrate SysConfigHandler to functional WebFlux style"
```

---

### Task 4: SysNoticeHandler 函数式迁移

**Files:**
- Modify: `handler/notice/SysNoticeHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysNoticeHandler {
    private final ISysNoticeService noticeService;

    public Mono<ServerResponse> getAllNotices(ServerRequest request) {
        return ServerResponse.ok()
                .body(noticeService.findAll(), SysNotice.class);
    }

    public Mono<ServerResponse> getNoticeById(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return noticeService.findById(id)
                .flatMap(notice -> ServerResponse.ok().bodyValue(notice))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createNotice(ServerRequest request) {
        return request.bodyToMono(SysNotice.class)
                .flatMap(noticeService::save)
                .flatMap(notice -> ServerResponse.status(HttpStatus.CREATED).bodyValue(notice));
    }

    public Mono<ServerResponse> updateNotice(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return request.bodyToMono(SysNotice.class)
                .flatMap(notice -> noticeService.update(id, notice))
                .flatMap(updatedNotice -> ServerResponse.ok().bodyValue(updatedNotice))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> deleteNotice(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return noticeService.deleteById(id)
                .then(ServerResponse.noContent().build());
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/notice/SysNoticeHandler.java
git commit -m "refactor: migrate SysNoticeHandler to functional WebFlux style"
```

---

### Task 5: SysFileHandler 函数式迁移

**Files:**
- Modify: `handler/file/SysFileHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysFileHandler {
    private final ISysFileService fileService;

    public Mono<ServerResponse> getAllFiles(ServerRequest request) {
        return ServerResponse.ok()
                .body(fileService.findAll(), SysFile.class);
    }

    public Mono<ServerResponse> getFileById(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return fileService.findById(id)
                .flatMap(file -> ServerResponse.ok().bodyValue(file))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> uploadFile(ServerRequest request) {
        return request.multipartData()
                .flatMap(data -> {
                    String filename = data.toSingleValueMap().getFirst("file").filename();
                    return fileService.saveFile(data)
                            .flatMap(file -> ServerResponse.status(HttpStatus.CREATED).bodyValue(file));
                });
    }

    public Mono<ServerResponse> deleteFile(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return fileService.deleteById(id)
                .then(ServerResponse.noContent().build());
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/file/SysFileHandler.java
git commit -m "refactor: migrate SysFileHandler to functional WebFlux style"
```

---

### Task 6: SysLogHandler 函数式迁移

**Files:**
- Modify: `handler/log/SysLogHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysLogHandler {
    private final IOperationLogService operationLogService;
    private final ISysLoginLogService loginLogService;

    public Mono<ServerResponse> getOperationLogs(ServerRequest request) {
        return ServerResponse.ok()
                .body(operationLogService.findAll(), OperationLog.class);
    }

    public Mono<ServerResponse> getOperationLogsByPage(ServerRequest request) {
        int page = Integer.parseInt(request.queryParam("page").orElse("0"));
        int size = Integer.parseInt(request.queryParam("size").orElse("10"));
        String sort = request.queryParam("sort").orElse("createdAt");
        String order = request.queryParam("order").orElse("desc");

        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(page);
        pageRequest.setSize(size);
        pageRequest.setSort(sort);
        pageRequest.setOrder(order);

        return operationLogService.findLogsByPage(pageRequest)
                .flatMap(response -> ServerResponse.ok().bodyValue(response));
    }

    public Mono<ServerResponse> getLoginLogs(ServerRequest request) {
        return ServerResponse.ok()
                .body(loginLogService.findAll(), SysLoginLog.class);
    }

    public Mono<ServerResponse> getLoginLogsByPage(ServerRequest request) {
        int page = Integer.parseInt(request.queryParam("page").orElse("0"));
        int size = Integer.parseInt(request.queryParam("size").orElse("10"));
        String sort = request.queryParam("sort").orElse("createdAt");
        String order = request.queryParam("order").orElse("desc");

        PageRequest pageRequest = new PageRequest();
        pageRequest.setPage(page);
        pageRequest.setSize(size);
        pageRequest.setSort(sort);
        pageRequest.setOrder(order);

        return loginLogService.findLogsByPage(pageRequest)
                .flatMap(response -> ServerResponse.ok().bodyValue(response));
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/log/SysLogHandler.java
git commit -m "refactor: migrate SysLogHandler to functional WebFlux style"
```

---

### Task 7: SysAuthHandler 函数式迁移

**Files:**
- Modify: `handler/auth/SysAuthHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysAuthHandler {
    private final ISysUserService userService;
    private final JwtTokenProvider tokenProvider;

    public Mono<ServerResponse> login(ServerRequest request) {
        return request.bodyToMono(LoginRequest.class)
                .flatMap(loginRequest -> userService.authenticate(loginRequest.getUsername(), loginRequest.getPassword()))
                .flatMap(user -> {
                    String token = tokenProvider.generateToken(user);
                    AuthResponse response = new AuthResponse(token, user);
                    return ServerResponse.ok().bodyValue(response);
                });
    }

    public Mono<ServerResponse> register(ServerRequest request) {
        return request.bodyToMono(UserRegisterRequest.class)
                .flatMap(userService::registerUser)
                .flatMap(user -> ServerResponse.status(HttpStatus.CREATED).bodyValue(user));
    }

    public Mono<ServerResponse> refreshToken(ServerRequest request) {
        return request.bodyToMono(RefreshTokenRequest.class)
                .flatMap(refreshRequest -> tokenProvider.validateToken(refreshRequest.getToken())
                        .flatMap(username -> userService.findByUsername(username))
                        .flatMap(user -> {
                            String newToken = tokenProvider.generateToken(user);
                            AuthResponse response = new AuthResponse(newToken, user);
                            return ServerResponse.ok().bodyValue(response);
                        }));
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/auth/SysAuthHandler.java
git commit -m "refactor: migrate SysAuthHandler to functional WebFlux style"
```

---

### Task 8: SysUserMessageHandler 函数式迁移

**Files:**
- Modify: `handler/message/SysUserMessageHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class SysUserMessageHandler {
    private final ISysUserMessageService messageService;

    public Mono<ServerResponse> getAllMessages(ServerRequest request) {
        return ServerResponse.ok()
                .body(messageService.findAll(), SysUserMessage.class);
    }

    public Mono<ServerResponse> getMessageById(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return messageService.findById(id)
                .flatMap(message -> ServerResponse.ok().bodyValue(message))
                .switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> createMessage(ServerRequest request) {
        return request.bodyToMono(SysUserMessage.class)
                .flatMap(messageService::save)
                .flatMap(message -> ServerResponse.status(HttpStatus.CREATED).bodyValue(message));
    }

    public Mono<ServerResponse> markAsRead(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return messageService.markAsRead(id)
                .then(ServerResponse.ok().build());
    }

    public Mono<ServerResponse> deleteMessage(ServerRequest request) {
        Long id = Long.valueOf(request.pathVariable("id"));
        return messageService.deleteById(id)
                .then(ServerResponse.noContent().build());
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/message/SysUserMessageHandler.java
git commit -m "refactor: migrate SysUserMessageHandler to functional WebFlux style"
```

---

### Task 9: StatsHandler 函数式迁移

**Files:**
- Modify: `handler/stats/StatsHandler.java`

**Step 1: 修改为函数式风格**

```java
@Component
public class StatsHandler {
    private final ISysUserService userService;
    private final IOperationLogService operationLogService;

    public Mono<ServerResponse> getUserStats(ServerRequest request) {
        return userService.count()
                .flatMap(count -> {
                    Map<String, Object> stats = new HashMap<>();
                    stats.put("totalUsers", count);
                    return ServerResponse.ok().bodyValue(stats);
                });
    }

    public Mono<ServerResponse> getOperationStats(ServerRequest request) {
        return operationLogService.count()
                .flatMap(count -> {
                    Map<String, Object> stats = new HashMap<>();
                    stats.put("totalOperations", count);
                    return ServerResponse.ok().bodyValue(stats);
                });
    }

    public Mono<ServerResponse> getSystemStats(ServerRequest request) {
        return Mono.zip(
                userService.count(),
                operationLogService.count()
        ).map(tuple -> {
            Map<String, Object> stats = new HashMap<>();
            stats.put("totalUsers", tuple.getT1());
            stats.put("totalOperations", tuple.getT2());
            return stats;
        }).flatMap(stats -> ServerResponse.ok().bodyValue(stats));
    }
}
```

**Step 2: 提交变更**

```bash
git add handler/stats/StatsHandler.java
git commit -m "refactor: migrate StatsHandler to functional WebFlux style"
```

---

## Phase 2: 完善 Router 配置

### Task 10: 扩展 SystemRouter 配置

**Files:**
- Modify: `config/SystemRouter.java`

**Step 1: 添加所有模块的路由配置**

```java
package cn.novalon.manage.sys.config;

import cn.novalon.manage.sys.handler.auth.SysAuthHandler;
import cn.novalon.manage.sys.handler.config.SysConfigHandler;
import cn.novalon.manage.sys.handler.dictionary.DictionaryHandler;
import cn.novalon.manage.sys.handler.file.SysFileHandler;
import cn.novalon.manage.sys.handler.log.SysLogHandler;
import cn.novalon.manage.sys.handler.message.SysUserMessageHandler;
import cn.novalon.manage.sys.handler.notice.SysNoticeHandler;
import cn.novalon.manage.sys.handler.role.SysRoleHandler;
import cn.novalon.manage.sys.handler.stats.StatsHandler;
import cn.novalon.manage.sys.handler.user.SysUserHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;

import static org.springframework.web.reactive.function.server.RequestPredicates.*;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;

@Configuration
public class SystemRouter {

    @Bean
    public RouterFunction<ServerResponse> systemRoutes(
            DictionaryHandler dictionaryHandler,
            SysUserHandler userHandler,
            SysRoleHandler roleHandler,
            SysConfigHandler configHandler,
            SysNoticeHandler noticeHandler,
            SysFileHandler fileHandler,
            SysLogHandler logHandler,
            SysAuthHandler authHandler,
            SysUserMessageHandler messageHandler,
            StatsHandler statsHandler) {

        return route()
                .path("/api", builder -> builder
                        .path("/dictionaries", dictRoutes(dictionaryHandler))
                        .path("/users", userRoutes(userHandler))
                        .path("/roles", roleRoutes(roleHandler))
                        .path("/configs", configRoutes(configHandler))
                        .path("/notices", noticeRoutes(noticeHandler))
                        .path("/files", fileRoutes(fileHandler))
                        .path("/logs", logRoutes(logHandler))
                        .path("/auth", authRoutes(authHandler))
                        .path("/messages", messageRoutes(messageHandler))
                        .path("/stats", statsRoutes(statsHandler))
                        .build())
                .build();
    }

    private RouterFunction<ServerResponse> dictRoutes(DictionaryHandler handler) {
        return route()
                .GET(handler::getAllDictionaries)
                .GET("/{id}", handler::getDictionaryById)
                .GET("/type/{type}", handler::getDictionariesByType)
                .GET("/check/exists", handler::checkTypeAndCodeExists)
                .POST(handler::createDictionary)
                .PUT("/{id}", handler::updateDictionary)
                .DELETE("/{id}", handler::deleteDictionary)
                .build();
    }

    private RouterFunction<ServerResponse> userRoutes(SysUserHandler handler) {
        return route()
                .GET(handler::getAllUsers)
                .GET("/page", handler::getUsersByPage)
                .GET("/{id}", handler::getUserById)
                .GET("/username/{username}", handler::getUserByUsername)
                .POST(handler::createUser)
                .PUT("/{id}", handler::updateUser)
                .DELETE("/{id}", handler::deleteUser)
                .POST("/{id}/password", handler::changePassword)
                .DELETE("/{id}/logical", handler::logicalDeleteUser)
                .POST("/logical-delete", handler::logicalDeleteUsers)
                .POST("/{id}/restore", handler::restoreUser)
                .POST("/restore", handler::restoreUsers)
                .GET("/check/username", handler::checkUsernameExists)
                .GET("/check/email", handler::checkEmailExists)
                .build();
    }

    private RouterFunction<ServerResponse> roleRoutes(SysRoleHandler handler) {
        return route()
                .GET(handler::getAllRoles)
                .GET("/{id}", handler::getRoleById)
                .POST(handler::createRole)
                .PUT("/{id}", handler::updateRole)
                .DELETE("/{id}", handler::deleteRole)
                .build();
    }

    private RouterFunction<ServerResponse> configRoutes(SysConfigHandler handler) {
        return route()
                .GET(handler::getAllConfigs)
                .GET("/key/{configKey}", handler::getConfigByKey)
                .POST(handler::createConfig)
                .PUT("/{id}", handler::updateConfig)
                .DELETE("/{id}", handler::deleteConfig)
                .build();
    }

    private RouterFunction<ServerResponse> noticeRoutes(SysNoticeHandler handler) {
        return route()
                .GET(handler::getAllNotices)
                .GET("/{id}", handler::getNoticeById)
                .POST(handler::createNotice)
                .PUT("/{id}", handler::updateNotice)
                .DELETE("/{id}", handler::deleteNotice)
                .build();
    }

    private RouterFunction<ServerResponse> fileRoutes(SysFileHandler handler) {
        return route()
                .GET(handler::getAllFiles)
                .GET("/{id}", handler::getFileById)
                .POST("/upload", handler::uploadFile)
                .DELETE("/{id}", handler::deleteFile)
                .build();
    }

    private RouterFunction<ServerResponse> logRoutes(SysLogHandler handler) {
        return route()
                .GET("/operations", handler::getOperationLogs)
                .GET("/operations/page", handler::getOperationLogsByPage)
                .GET("/logins", handler::getLoginLogs)
                .GET("/logins/page", handler::getLoginLogsByPage)
                .build();
    }

    private RouterFunction<ServerResponse> authRoutes(SysAuthHandler handler) {
        return route()
                .POST("/login", handler::login)
                .POST("/register", handler::register)
                .POST("/refresh", handler::refreshToken)
                .build();
    }

    private RouterFunction<ServerResponse> messageRoutes(SysUserMessageHandler handler) {
        return route()
                .GET(handler::getAllMessages)
                .GET("/{id}", handler::getMessageById)
                .POST(handler::createMessage)
                .PUT("/{id}/read", handler::markAsRead)
                .DELETE("/{id}", handler::deleteMessage)
                .build();
    }

    private RouterFunction<ServerResponse> statsRoutes(StatsHandler handler) {
        return route()
                .GET("/users", handler::getUserStats)
                .GET("/operations", handler::getOperationStats)
                .GET("/system", handler::getSystemStats)
                .build();
    }
}
```

**Step 2: 测试路由配置**

```bash
curl -X GET http://localhost:8080/api/dictionaries
curl -X GET http://localhost:8080/api/users
curl -X GET http://localhost:8080/api/roles
curl -X GET http://localhost:8080/api/stats/system
```

**Step 3: 提交变更**

```bash
git add config/SystemRouter.java
git commit -m "feat: add comprehensive router configuration for all modules"
```

---

## Phase 3: 优化其他 Converter

### Task 11: 为其他 Converter 添加批量转换方法

**Files:**
- Modify: `converter/SysRoleConverter.java`
- Modify: `converter/SysConfigConverter.java`
- Modify: `converter/SysNoticeConverter.java`
- Modify: `converter/SysFileConverter.java`
- Modify: `converter/SysLoginLogConverter.java`
- Modify: `converter/SysDictDataConverter.java`
- Modify: `converter/SysDictTypeConverter.java`
- Modify: `converter/SysMenuConverter.java`
- Modify: `converter/SysUserMessageConverter.java`
- Modify: `converter/SysExceptionLogConverter.java`

**Step 1: 为 SysRoleConverter 添加批量转换**

```java
public List<SysRole> toDomainList(List<SysRoleEntity> entities) {
    if (entities == null) {
        return null;
    }
    return entities.stream()
            .map(this::toDomain)
            .collect(Collectors.toList());
}

public List<SysRoleEntity> toEntityList(List<SysRole> domains) {
    if (domains == null) {
        return null;
    }
    return domains.stream()
            .map(this::toEntity)
            .collect(Collectors.toList());
}
```

**Step 2: 为其他 Converter 添加批量转换方法**

重复 Step 1 的模式，为所有其他 Converter 添加 `toDomainList()` 和 `toEntityList()` 方法。

**Step 3: 提交变更**

```bash
git add converter/
git commit -m "feat: add batch conversion methods to all converters"
```

---

## Phase 4: 引入 MapStruct

### Task 12: 配置 MapStruct 依赖

**Files:**
- Modify: `pom.xml`

**Step 1: 添加 MapStruct 依赖**

```xml
<properties>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
    <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${org.mapstruct.version}</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok-mapstruct-binding</artifactId>
        <version>${lombok-mapstruct-binding.version}</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>${lombok.version}</version>
                    </path>
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok-mapstruct-binding</artifactId>
                        <version>${lombok-mapstruct-binding.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
```

**Step 2: 提交变更**

```bash
git add pom.xml
git commit -m "feat: add MapStruct dependency and configuration"
```

---

### Task 13: 创建 MapStruct Mapper 接口

**Files:**
- Create: `infrastructure/db/mapper/DictionaryMapper.java`
- Create: `infrastructure/db/mapper/SysUserMapper.java`
- Create: `infrastructure/db/mapper/SysRoleMapper.java`
- Create: `infrastructure/db/mapper/SysConfigMapper.java`
- Create: `infrastructure/db/mapper/SysNoticeMapper.java`
- Create: `infrastructure/db/mapper/SysFileMapper.java`
- Create: `infrastructure/db/mapper/OperationLogMapper.java`
- Create: `infrastructure/db/mapper/SysLoginLogMapper.java`
- Create: `infrastructure/db/mapper/SysDictDataMapper.java`
- Create: `infrastructure/db/mapper/SysDictTypeMapper.java`
- Create: `infrastructure/db/mapper/SysMenuMapper.java`
- Create: `infrastructure/db/mapper/SysUserMessageMapper.java`
- Create: `infrastructure/db/mapper/SysExceptionLogMapper.java`

**Step 1: 创建 DictionaryMapper 接口**

```java
package cn.novalon.manage.sys.infrastructure.db.mapper;

import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;

import java.util.List;

@Mapper(componentModel = "spring")
public interface DictionaryMapper {

    Dictionary toDomain(DictionaryEntity entity);

    DictionaryEntity toEntity(Dictionary domain);

    List<Dictionary> toDomainList(List<DictionaryEntity> entities);

    List<DictionaryEntity> toEntityList(List<Dictionary> domains);
}
```

**Step 2: 创建其他 Mapper 接口**

重复 Step 1 的模式，为所有实体创建对应的 Mapper 接口。

**Step 3: 提交变更**

```bash
git add infrastructure/db/mapper/
git commit -m "feat: add MapStruct mapper interfaces for all entities"
```

---

### Task 14: 使用 MapStruct 替换手动 Converter

**Files:**
- Modify: `repository/DictionaryRepository.java`
- Modify: `repository/SysUserRepository.java`
- Modify: `repository/SysRoleRepository.java`
- Modify: `repository/SysConfigRepository.java`
- Modify: `repository/SysNoticeRepository.java`
- Modify: `repository/SysFileRepository.java`
- Modify: `repository/OperationLogRepository.java`
- Modify: `repository/SysLoginLogRepository.java`
- Modify: `repository/SysDictDataRepository.java`
- Modify: `repository/SysDictTypeRepository.java`
- Modify: `repository/SysMenuRepository.java`
- Modify: `repository/SysUserMessageRepository.java`
- Modify: `repository/SysExceptionLogRepository.java`

**Step 1: 修改 DictionaryRepository 使用 MapStruct**

```java
@Repository
public class DictionaryRepository {

    private final DictionaryDao dao;
    private final DictionaryMapper mapper;

    public DictionaryRepository(DictionaryDao dao, DictionaryMapper mapper) {
        this.dao = dao;
        this.mapper = mapper;
    }

    public Flux<Dictionary> findByType(String type) {
        return dao.findByType(type)
                .map(mapper::toDomain);
    }

    public Mono<Dictionary> findByTypeAndCode(String type, String code) {
        return dao.findByTypeAndCode(type, code)
                .map(mapper::toDomain);
    }

    public Flux<Dictionary> findAll() {
        return dao.findByDeletedAtIsNullOrderBySortAsc()
                .map(mapper::toDomain);
    }

    public Mono<Dictionary> findById(Long id) {
        return dao.findById(id)
                .map(mapper::toDomain);
    }

    public Mono<Dictionary> save(Dictionary dictionary) {
        return dao.save(mapper.toEntity(dictionary))
                .map(mapper::toDomain);
    }

    public Mono<Void> deleteById(Long id) {
        return dao.deleteByIdAndDeletedAtIsNull(id);
    }
}
```

**Step 2: 修改其他 Repository 使用 MapStruct**

重复 Step 1 的模式，为所有 Repository 替换 Converter 为 Mapper。

**Step 3: 删除旧的 Converter 类**

```bash
rm converter/DictionaryConverter.java
rm converter/SysUserConverter.java
rm converter/SysRoleConverter.java
rm converter/SysConfigConverter.java
rm converter/SysNoticeConverter.java
rm converter/SysFileConverter.java
rm converter/OperationLogConverter.java
rm converter/SysLoginLogConverter.java
rm converter/SysDictDataConverter.java
rm converter/SysDictTypeConverter.java
rm converter/SysMenuConverter.java
rm converter/SysUserMessageConverter.java
rm converter/SysExceptionLogConverter.java
```

**Step 4: 提交变更**

```bash
git add repository/ converter/
git commit -m "refactor: replace manual converters with MapStruct mappers"
```

---

## Phase 5: 添加单元测试

### Task 15: 为 DictionaryRepository 添加单元测试

**Files:**
- Create: `infrastructure/db/repository/DictionaryRepositoryTest.java`

**Step 1: 创建测试类**

```java
package cn.novalon.manage.sys.infrastructure.db.repository;

import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.infrastructure.db.converter.DictionaryConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.DictionaryDao;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class DictionaryRepositoryTest {

    @Mock
    private DictionaryDao dao;

    @Mock
    private DictionaryConverter converter;

    private DictionaryRepository repository;

    private DictionaryEntity testEntity;
    private Dictionary testDomain;

    @BeforeEach
    void setUp() {
        repository = new DictionaryRepository(dao, converter);

        testEntity = new DictionaryEntity();
        testEntity.setId(1L);
        testEntity.setType("test_type");
        testEntity.setCode("test_code");
        testEntity.setName("Test Dictionary");
        testEntity.setValue("test_value");
        testEntity.setSort(1);
        testEntity.setCreatedAt(LocalDateTime.now());
        testEntity.setUpdatedAt(LocalDateTime.now());

        testDomain = new Dictionary();
        testDomain.setId(1L);
        testDomain.setType("test_type");
        testDomain.setCode("test_code");
        testDomain.setName("Test Dictionary");
        testDomain.setValue("test_value");
        testDomain.setSort(1);
        testDomain.setCreatedAt(LocalDateTime.now());
        testDomain.setUpdatedAt(LocalDateTime.now());
    }

    @Test
    void findByType_ShouldReturnDictionaries() {
        when(dao.findByType("test_type")).thenReturn(Flux.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDomain);

        Flux<Dictionary> result = repository.findByType("test_type");

        StepVerifier.create(result)
                .expectNext(testDomain)
                .verifyComplete();
    }

    @Test
    void findByTypeAndCode_ShouldReturnDictionary() {
        when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDomain);

        Mono<Dictionary> result = repository.findByTypeAndCode("test_type", "test_code");

        StepVerifier.create(result)
                .expectNext(testDomain)
                .verifyComplete();
    }

    @Test
    void findAll_ShouldReturnAllDictionaries() {
        List<DictionaryEntity> entities = Arrays.asList(testEntity);
        when(dao.findByDeletedAtIsNullOrderBySortAsc()).thenReturn(Flux.fromIterable(entities));
        when(converter.toDomain(testEntity)).thenReturn(testDomain);

        Flux<Dictionary> result = repository.findAll();

        StepVerifier.create(result)
                .expectNext(testDomain)
                .verifyComplete();
    }

    @Test
    void findById_ShouldReturnDictionary() {
        when(dao.findById(1L)).thenReturn(Mono.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDomain);

        Mono<Dictionary> result = repository.findById(1L);

        StepVerifier.create(result)
                .expectNext(testDomain)
                .verifyComplete();
    }

    @Test
    void save_ShouldSaveDictionary() {
        when(converter.toEntity(testDomain)).thenReturn(testEntity);
        when(dao.save(testEntity)).thenReturn(Mono.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDomain);

        Mono<Dictionary> result = repository.save(testDomain);

        StepVerifier.create(result)
                .expectNext(testDomain)
                .verifyComplete();
    }

    @Test
    void deleteById_ShouldDeleteDictionary() {
        when(dao.deleteByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());

        Mono<Void> result = repository.deleteById(1L);

        StepVerifier.create(result)
                .verifyComplete();
    }
}
```

**Step 2: 运行测试**

```bash
mvn test -Dtest=DictionaryRepositoryTest
```

**Step 3: 提交变更**

```bash
git add infrastructure/db/repository/DictionaryRepositoryTest.java
git commit -m "test: add unit tests for DictionaryRepository"
```

---

### Task 16: 为其他 Repository 添加单元测试

**Files:**
- Create: `infrastructure/db/repository/SysUserRepositoryTest.java`
- Create: `infrastructure/db/repository/SysRoleRepositoryTest.java`
- Create: `infrastructure/db/repository/SysConfigRepositoryTest.java`
- Create: `infrastructure/db/repository/OperationLogRepositoryTest.java`

**Step 1: 创建测试类**

重复 Task 15 的模式，为其他 Repository 创建单元测试类。

**Step 2: 运行所有测试**

```bash
mvn test
```

**Step 3: 提交变更**

```bash
git add infrastructure/db/repository/
git commit -m "test: add unit tests for all repositories"
```

---

### Task 17: 为 Service 层添加单元测试

**Files:**
- Create: `core/service/impl/DictionaryServiceTest.java`
- Create: `core/service/impl/SysUserServiceTest.java`
- Create: `core/service/impl/SysRoleServiceTest.java`

**Step 1: 创建 DictionaryServiceTest**

```java
package cn.novalon.manage.sys.core.service.impl;

import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.core.exception.DictionaryAlreadyExistsException;
import cn.novalon.manage.sys.infrastructure.db.converter.DictionaryConverter;
import cn.novalon.manage.sys.infrastructure.db.dao.DictionaryDao;
import cn.novalon.manage.sys.infrastructure.db.entity.DictionaryEntity;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class DictionaryServiceTest {

    @Mock
    private DictionaryDao dao;

    @Mock
    private DictionaryConverter converter;

    private DictionaryService service;

    private Dictionary testDictionary;
    private DictionaryEntity testEntity;

    @BeforeEach
    void setUp() {
        service = new DictionaryService(dao, converter);

        testDictionary = new Dictionary();
        testDictionary.setId(1L);
        testDictionary.setType("test_type");
        testDictionary.setCode("test_code");
        testDictionary.setName("Test Dictionary");
        testDictionary.setValue("test_value");
        testDictionary.setSort(1);

        testEntity = new DictionaryEntity();
        testEntity.setId(1L);
        testEntity.setType("test_type");
        testEntity.setCode("test_code");
        testEntity.setName("Test Dictionary");
        testEntity.setValue("test_value");
        testEntity.setSort(1);
    }

    @Test
    void findAll_ShouldReturnAllDictionaries() {
        when(dao.findByDeletedAtIsNullOrderBySortAsc()).thenReturn(Flux.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDictionary);

        Flux<Dictionary> result = service.findAll();

        StepVerifier.create(result)
                .expectNext(testDictionary)
                .verifyComplete();
    }

    @Test
    void save_NewDictionary_ShouldSaveSuccessfully() {
        when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.empty());
        when(converter.toEntity(testDictionary)).thenReturn(testEntity);
        when(dao.save(testEntity)).thenReturn(Mono.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDictionary);

        Mono<Dictionary> result = service.save(testDictionary);

        StepVerifier.create(result)
                .expectNextMatches(dict -> dict.getCreatedAt() != null && dict.getUpdatedAt() != null)
                .verifyComplete();
    }

    @Test
    void save_DuplicateDictionary_ShouldThrowException() {
        when(dao.findByTypeAndCode("test_type", "test_code")).thenReturn(Mono.just(testEntity));

        Mono<Dictionary> result = service.save(testDictionary);

        StepVerifier.create(result)
                .expectError(DictionaryAlreadyExistsException.class)
                .verify();
    }

    @Test
    void update_ShouldUpdateDictionary() {
        Dictionary updateDict = new Dictionary();
        updateDict.setName("Updated Name");

        when(dao.findById(1L)).thenReturn(Mono.just(testEntity));
        when(dao.save(testEntity)).thenReturn(Mono.just(testEntity));
        when(converter.toDomain(testEntity)).thenReturn(testDictionary);

        Mono<Dictionary> result = service.update(1L, updateDict);

        StepVerifier.create(result)
                .expectNextMatches(dict -> "Updated Name".equals(dict.getName()))
                .verifyComplete();
    }

    @Test
    void deleteById_ShouldDeleteDictionary() {
        when(dao.deleteByIdAndDeletedAtIsNull(1L)).thenReturn(Mono.empty());

        Mono<Void> result = service.deleteById(1L);

        StepVerifier.create(result)
                .verifyComplete();
    }
}
```

**Step 2: 创建其他 Service 测试类**

重复 Step 1 的模式，为其他 Service 创建单元测试类。

**Step 3: 运行所有测试**

```bash
mvn test
```

**Step 4: 提交变更**

```bash
git add core/service/impl/
git commit -m "test: add unit tests for all services"
```

---

### Task 18: 为 Handler 层添加单元测试

**Files:**
- Create: `handler/dictionary/DictionaryHandlerTest.java`
- Create: `handler/user/SysUserHandlerTest.java`
- Create: `handler/role/SysRoleHandlerTest.java`

**Step 1: 创建 DictionaryHandlerTest**

```java
package cn.novalon.manage.sys.handler.dictionary;

import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.core.service.IDictionaryService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.reactive.function.server.MockServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import java.util.List;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class DictionaryHandlerTest {

    @Mock
    private IDictionaryService dictionaryService;

    private DictionaryHandler handler;

    @BeforeEach
    void setUp() {
        handler = new DictionaryHandler(dictionaryService);
    }

    @Test
    void getAllDictionaries_ShouldReturnAllDictionaries() {
        List<Dictionary> dictionaries = List.of(new Dictionary());
        when(dictionaryService.findAll()).thenReturn(Flux.fromIterable(dictionaries));

        Mono<ServerResponse> result = handler.getAllDictionaries(MockServerRequest.builder().build());

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode().is2xxSuccessful())
                .verifyComplete();
    }

    @Test
    void getDictionaryById_ShouldReturnDictionary() {
        Dictionary dictionary = new Dictionary();
        dictionary.setId(1L);
        when(dictionaryService.findById(1L)).thenReturn(Mono.just(dictionary));

        MockServerRequest request = MockServerRequest.builder()
                .pathVariable("id", "1")
                .build();

        Mono<ServerResponse> result = handler.getDictionaryById(request);

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode().is2xxSuccessful())
                .verifyComplete();
    }

    @Test
    void getDictionaryById_NotFound_ShouldReturn404() {
        when(dictionaryService.findById(1L)).thenReturn(Mono.empty());

        MockServerRequest request = MockServerRequest.builder()
                .pathVariable("id", "1")
                .build();

        Mono<ServerResponse> result = handler.getDictionaryById(request);

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode() == HttpStatus.NOT_FOUND)
                .verifyComplete();
    }

    @Test
    void createDictionary_ShouldCreateDictionary() {
        Dictionary dictionary = new Dictionary();
        dictionary.setType("test_type");
        dictionary.setCode("test_code");
        dictionary.setName("Test Dictionary");

        when(dictionaryService.save(any(Dictionary.class))).thenReturn(Mono.just(dictionary));

        MockServerRequest request = MockServerRequest.builder()
                .body(dictionary)
                .build();

        Mono<ServerResponse> result = handler.createDictionary(request);

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode() == HttpStatus.CREATED)
                .verifyComplete();
    }

    @Test
    void updateDictionary_ShouldUpdateDictionary() {
        Dictionary existing = new Dictionary();
        existing.setId(1L);
        existing.setName("Old Name");

        Dictionary update = new Dictionary();
        update.setName("New Name");

        when(dictionaryService.findById(1L)).thenReturn(Mono.just(existing));
        when(dictionaryService.update(eq(1L), any(Dictionary.class))).thenReturn(Mono.just(update));

        MockServerRequest request = MockServerRequest.builder()
                .pathVariable("id", "1")
                .body(update)
                .build();

        Mono<ServerResponse> result = handler.updateDictionary(request);

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode().is2xxSuccessful())
                .verifyComplete();
    }

    @Test
    void deleteDictionary_ShouldDeleteDictionary() {
        when(dictionaryService.deleteById(1L)).thenReturn(Mono.empty());

        MockServerRequest request = MockServerRequest.builder()
                .pathVariable("id", "1")
                .build();

        Mono<ServerResponse> result = handler.deleteDictionary(request);

        StepVerifier.create(result)
                .expectNextMatches(response -> response.statusCode() == HttpStatus.NO_CONTENT)
                .verifyComplete();
    }
}
```

**Step 2: 创建其他 Handler 测试类**

重复 Step 1 的模式，为其他 Handler 创建单元测试类。

**Step 3: 运行所有测试**

```bash
mvn test
```

**Step 4: 提交变更**

```bash
git add handler/
git commit -m "test: add unit tests for all handlers"
```

---

## Phase 6: 集成测试和文档

### Task 19: 添加集成测试

**Files:**
- Create: `integration/DictionaryIntegrationTest.java`
- Create: `integration/SysUserIntegrationTest.java`

**Step 1: 创建集成测试**

```java
package cn.novalon.manage.sys.integration;

import cn.novalon.manage.sys.core.domain.Dictionary;
import cn.novalon.manage.sys.infrastructure.db.repository.DictionaryRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.utility.DockerImageName;
import reactor.test.StepVerifier;

@SpringBootTest
@ActiveProfiles("test")
class DictionaryIntegrationTest {

    @Container
    private static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
            new DockerImageName("postgres:15-alpine"))
            .withDatabaseName("testdb")
            .withUsername("test")
            .withPassword("test");

    @Autowired
    private DictionaryRepository dictionaryRepository;

    private Dictionary testDictionary;

    @BeforeEach
    void setUp() {
        testDictionary = new Dictionary();
        testDictionary.setType("test_type");
        testDictionary.setCode("test_code");
        testDictionary.setName("Test Dictionary");
        testDictionary.setValue("test_value");
        testDictionary.setSort(1);
    }

    @Test
    void saveAndFindDictionary_ShouldWorkEndToEnd() {
        StepVerifier.create(dictionaryRepository.save(testDictionary))
                .expectNextMatches(dict -> dict.getId() != null)
                .verifyComplete();

        StepVerifier.create(dictionaryRepository.findByType("test_type"))
                .expectNextMatches(dict -> "test_code".equals(dict.getCode()))
                .verifyComplete();
    }

    @Test
    void updateDictionary_ShouldPersistChanges() {
        StepVerifier.create(dictionaryRepository.save(testDictionary))
                .expectNextMatches(dict -> dict.getId() != null)
                .verifyComplete();

        testDictionary.setName("Updated Name");

        StepVerifier.create(dictionaryRepository.update(testDictionary.getId(), testDictionary))
                .expectNextMatches(dict -> "Updated Name".equals(dict.getName()))
                .verifyComplete();
    }

    @Test
    void deleteDictionary_ShouldRemoveFromDatabase() {
        StepVerifier.create(dictionaryRepository.save(testDictionary))
                .expectNextMatches(dict -> dict.getId() != null)
                .verifyComplete();

        StepVerifier.create(dictionaryRepository.deleteById(testDictionary.getId()))
                .verifyComplete();

        StepVerifier.create(dictionaryRepository.findById(testDictionary.getId()))
                .verifyComplete();
    }
}
```

**Step 2: 运行集成测试**

```bash
mvn test -Dtest=*IntegrationTest
```

**Step 3: 提交变更**

```bash
git add integration/
git commit -m "test: add integration tests for key modules"
```

---

### Task 20: 更新文档

**Files:**
- Modify: `README.md`
- Create: `docs/ARCHITECTURE.md`
- Create: `docs/CONTRIBUTING.md`

**Step 1: 更新 README.md**

```markdown
# Novalon Manage System

## 项目概述

Novalon 管理系统是一个基于 Spring WebFlux 的现代化管理系统，采用响应式编程和函数式路由设计。

## 技术栈

- **后端框架**: Spring Boot 3.x + WebFlux
- **数据库**: PostgreSQL + R2DBC
- **对象映射**: MapStruct
- **测试框架**: JUnit 5 + Mockito + Testcontainers
- **构建工具**: Maven

## 架构设计

系统采用分层架构设计：

```
Handler (函数式路由) → Service (业务逻辑) → Repository (数据访问) → DAO (数据库操作) → R2DBC Repository
```

### 层次职责

- **Handler 层**: 处理 HTTP 请求和响应，使用函数式 WebFlux 风格
- **Service 层**: 实现业务逻辑和事务管理
- **Repository 层**: 处理数据转换和复杂查询
- **DAO 层**: 直接数据库操作，继承 R2dbcRepository
- **Converter/Mapper 层**: 使用 MapStruct 自动生成转换代码

## 快速开始

### 环境要求

- JDK 17+
- Maven 3.8+
- PostgreSQL 15+

### 运行项目

```bash
# 克隆项目
git clone <repository-url>
cd novalon-manage-system

# 构建项目
mvn clean install

# 运行应用
mvn spring-boot:run
```

## 测试

```bash
# 运行单元测试
mvn test

# 运行集成测试
mvn test -Dtest=*IntegrationTest

# 运行所有测试
mvn verify
```

## 项目结构

```
novalon-manage-api/manage-sys/
├── config/                    # 配置类
│   ├── SystemRouter.java       # 统一路由配置
│   ├── SecurityConfig.java     # 安全配置
│   └── WebFluxConfig.java    # WebFlux 配置
├── core/                      # 核心业务逻辑
│   ├── domain/               # 领域模型
│   ├── service/              # 服务接口和实现
│   └── repository/           # 仓储接口
├── infrastructure/            # 基础设施
│   └── db/
│       ├── dao/             # 数据访问对象
│       ├── entity/          # 数据库实体
│       ├── mapper/          # MapStruct 映射器
│       └── repository/      # 仓储实现
└── handler/                   # 处理器（函数式路由）
    ├── dictionary/
    ├── user/
    ├── role/
    └── ...
```

## 开发规范

### 代码风格

- 使用函数式 WebFlux 风格的 Handler
- 使用 MapStruct 进行对象转换
- 遵循 DRY 原则
- 使用 TDD 开发模式

### 命名规范

- **Handler**: `{Entity}Handler`
- **Service**: `I{Entity}Service` 接口，`{Entity}Service` 实现
- **Repository**: `{Entity}Repository`
- **DAO**: `{Entity}Dao`
- **Mapper**: `{Entity}Mapper`
- **Entity**: `{Entity}Entity`

### 提交规范

```bash
feat: 新功能
fix: 修复问题
refactor: 重构代码
test: 添加测试
docs: 更新文档
```

## 许可证

[License Information]
```

**Step 2: 创建架构文档**

```markdown
# 系统架构文档

## 概述

Novalon 管理系统采用现代化的分层架构设计，结合响应式编程和函数式路由，提供高性能和可扩展性。

## 架构层次

### 1. Handler 层（表现层）

**职责**：
- 处理 HTTP 请求和响应
- 参数验证和类型转换
- 路由定义和配置

**技术实现**：
- 使用函数式 WebFlux 风格
- 通过 `SystemRouter` 集中配置路由
- 使用 `ServerRequest` 和 `ServerResponse`

**示例**：
```java
@Component
public class DictionaryHandler {
    public Mono<ServerResponse> getAllDictionaries(ServerRequest request) {
        return ServerResponse.ok()
                .body(dictionaryService.findAll(), Dictionary.class);
    }
}
```

### 2. Service 层（业务逻辑层）

**职责**：
- 实现业务逻辑
- 事务管理
- 业务规则验证
- 跨 Repository 编排

**技术实现**：
- 接口定义：`I{Entity}Service`
- 实现类：`{Entity}Service`
- 使用 `@Service` 注解

**示例**：
```java
@Service
public class DictionaryService implements IDictionaryService {
    private final DictionaryDao dao;
    private final DictionaryMapper mapper;

    public Mono<Dictionary> save(Dictionary dictionary) {
        return dao.save(mapper.toEntity(dictionary))
                .map(mapper::toDomain);
    }
}
```

### 3. Repository 层（数据访问层）

**职责**：
- 数据转换（Entity ↔ Domain）
- 复杂查询实现
- 分页查询支持
- 批量操作支持

**技术实现**：
- 使用 `{Entity}Repository` 类
- 依赖 `{Entity}Dao` 和 `{Entity}Mapper`
- 使用 `@Repository` 注解

**示例**：
```java
@Repository
public class DictionaryRepository {
    private final DictionaryDao dao;
    private final DictionaryMapper mapper;

    public Flux<Dictionary> findByType(String type) {
        return dao.findByType(type)
                .map(mapper::toDomain);
    }
}
```

### 4. DAO 层（数据库操作层）

**职责**：
- 直接数据库操作
- 继承 R2dbcRepository
- 定义查询方法

**技术实现**：
- 接口定义：`{Entity}Dao`
- 继承 `R2dbcRepository<{Entity}Entity, Long>`
- 使用 `@Repository` 注解

**示例**：
```java
@Repository
public interface DictionaryDao extends R2dbcRepository<DictionaryEntity, Long> {
    Flux<DictionaryEntity> findByType(String type);
    Mono<DictionaryEntity> findByTypeAndCode(String type, String code);
}
```

### 5. Mapper 层（对象转换层）

**职责**：
- Entity ↔ Domain 转换
- 批量转换支持
- 自动代码生成

**技术实现**：
- 使用 MapStruct
- 接口定义：`{Entity}Mapper`
- 使用 `@Mapper(componentModel = "spring")` 注解

**示例**：
```java
@Mapper(componentModel = "spring")
public interface DictionaryMapper {
    Dictionary toDomain(DictionaryEntity entity);
    DictionaryEntity toEntity(Dictionary domain);
    List<Dictionary> toDomainList(List<DictionaryEntity> entities);
    List<DictionaryEntity> toEntityList(List<Dictionary> domains);
}
```

## 数据流

### 读取流程

```
HTTP Request → Handler → Service → Repository → DAO → R2DBC → Database
```

### 写入流程

```
HTTP Request → Handler → Service → Repository → Mapper → DAO → R2DBC → Database
```

## 技术选型

### 为什么选择函数式 WebFlux？

1. **性能优势**: 非阻塞 I/O，高并发处理能力
2. **代码简洁**: 函数式编程减少样板代码
3. **类型安全**: 编译时类型检查
4. **测试友好**: 易于单元测试和模拟

### 为什么选择 MapStruct？

1. **性能**: 编译时生成代码，运行时零开销
2. **维护性**: 自动生成，减少手动维护
3. **类型安全**: 编译时验证映射正确性
4. **批量支持**: 内置批量转换方法

## 设计原则

### DRY (Don't Repeat Yourself)

- 提取公共逻辑到工具类
- 使用继承减少重复代码
- 使用 MapStruct 自动生成转换代码

### YAGNI (You Aren't Gonna Need It)

- 只实现当前需要的功能
- 避免过度设计
- 保持代码简洁

### TDD (Test-Driven Development)

- 先写测试，再写实现
- 保持高测试覆盖率
- 频繁提交小步改进

### SOLID 原则

- **单一职责**: 每个类只负责一个功能
- **开闭原则**: 对扩展开放，对修改关闭
- **里氏替换**: 子类可以替换父类
- **接口隔离**: 客户端不应该依赖不需要的接口
- **依赖倒置**: 依赖抽象而非具体实现

## 性能优化

### 1. 批量操作

- 使用批量转换方法
- 批量数据库操作
- 减少数据库往返

### 2. 缓存策略

- 使用 Caffeine 缓存热点数据
- 合理设置缓存过期时间
- 缓存穿透保护

### 3. 响应式编程

- 非阻塞 I/O 操作
- 背压处理
- 资源高效利用

## 安全考虑

### 1. 认证授权

- JWT Token 认证
- 基于角色的访问控制
- 密码加密存储

### 2. 数据验证

- 输入参数验证
- 业务规则验证
- SQL 注入防护

### 3. 审计日志

- 操作日志记录
- 登录日志记录
- 异常日志记录
```

**Step 3: 创建贡献指南**

```markdown
# 贡献指南

## 如何贡献

我们欢迎任何形式的贡献，包括但不限于：

- 报告 Bug
- 提出新功能
- 改进文档
- 提交代码

## 开发流程

### 1. Fork 项目

```bash
git fork <repository-url>
git clone <your-fork-url>
cd novalon-manage-system
```

### 2. 创建分支

```bash
git checkout -b feature/your-feature-name
```

### 3. 编写代码

遵循项目开发规范：
- 使用函数式 WebFlux 风格
- 使用 MapStruct 进行对象转换
- 编写单元测试
- 遵循命名规范

### 4. 运行测试

```bash
mvn clean test
```

确保所有测试通过。

### 5. 提交代码

```bash
git add .
git commit -m "feat: add your feature"
```

提交信息格式：
- `feat:` 新功能
- `fix:` 修复问题
- `refactor:` 重构代码
- `test:` 添加测试
- `docs:` 更新文档

### 6. 推送分支

```bash
git push origin feature/your-feature-name
```

### 7. 创建 Pull Request

在 GitHub 上创建 Pull Request，描述：
- 变更内容
- 相关 Issue
- 测试情况
- 截图（如适用）

## 代码规范

### 命名规范

- **类名**: PascalCase，如 `DictionaryHandler`
- **方法名**: camelCase，如 `getAllDictionaries`
- **常量名**: UPPER_SNAKE_CASE，如 `MAX_SIZE`
- **包名**: 小写，如 `cn.novalon.manage.sys`

### 注释规范

- 类注释：描述类的职责和用途
- 方法注释：描述方法的功能、参数、返回值
- 复杂逻辑：添加行内注释

### 测试规范

- 测试类名：`{ClassName}Test`
- 测试方法名：`{MethodName}_{ExpectedBehavior}`
- 使用 Given-When-Then 模式

## Pull Request 检查清单

- [ ] 代码通过所有测试
- [ ] 遵循代码规范
- [ ] 添加了必要的测试
- [ ] 更新了相关文档
- [ ] 提交信息格式正确
- [ ] 没有 TODO 或 FIXME 注释

## 问题报告

使用 GitHub Issues 报告问题，请包含：

- 问题描述
- 复现步骤
- 预期行为
- 实际行为
- 环境信息（OS、JDK 版本等）
- 相关日志或截图

## 功能建议

使用 GitHub Issues 提出新功能建议，请描述：

- 功能描述
- 使用场景
- 预期收益
- 可能的实现方案
```

**Step 4: 提交文档变更**

```bash
git add README.md docs/
git commit -m "docs: update project documentation and architecture guide"
```

---

## 总结

本实施计划涵盖了以下主要方面：

### Phase 1: Handler 函数式迁移（9 个任务）
- Task 1-9: 将所有 Handler 从注解式迁移到函数式 WebFlux 风格

### Phase 2: Router 配置完善（1 个任务）
- Task 10: 扩展 SystemRouter 配置，支持所有模块

### Phase 3: Converter 优化（1 个任务）
- Task 11: 为所有 Converter 添加批量转换方法

### Phase 4: MapStruct 引入（3 个任务）
- Task 12: 配置 MapStruct 依赖
- Task 13: 创建 MapStruct Mapper 接口
- Task 14: 使用 MapStruct 替换手动 Converter

### Phase 5: 单元测试（4 个任务）
- Task 15-18: 为 Repository、Service、Handler 层添加完整的单元测试

### Phase 6: 集成测试和文档（2 个任务）
- Task 19: 添加集成测试
- Task 20: 更新项目文档

**总计**: 20 个任务，覆盖所有未完成的工作和后续建议。

---

**Plan complete and saved to `docs/plans/2026-03-12-infrastructure-refactoring-phase2.md`. Two execution options:**

**1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration

**2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints

**Which approach?**