编写多分支 Mono flatMap 的任何更好的方法

Any better way to write a multiple branched Mono flatMap

非响应式样式代码如下:

    public Response handleRequest(LoginContext ctx) {
        String username = ctx.getParameterMap().get("username");
        String password = ctx.getParameterMap().get("password");
        String ipAddr = ctx.getIpAddr();
        if (isIpAddressBlocked(ipAddr)) {
            return new Response("Your ip is blocked");
        }
        List<User> list = userCrudRepository.findByMobile(username);
        if (CollectionUtils.isEmpty(list)) {
            return new Response("User not found");
        }
        if (list.size() > 1) {
            return new Response("Data abnormal, please contact the site administrator");
        }
        User user = list.get(0);
        if (DigestUtils.md5Hex(password).equals(user.getPassword())) {
            doWhenLoginSuccessfully(user);
            return new Response("Login successfully");
        } else {
            return new Response("Username or password error");
        }
    }

为了将其重写为响应式样式,我将其更改为如下所示:

    @Autowired
    private UserCrudRepository userCrudRepository;
    
    private Map<String, Boolean> blockedIpMap = new ConcurrentHashMap<>();
    
    private Mono<Boolean> isIpAddressBlocked(String ipAddr) {
        return Mono.just(blockedIpMap.containsKey(ipAddr));
    }
    
    private Mono<Void> doWhenLoginSuccessfully(LoginContext ctx, User user) {
        ctx.getSession().getAttributes().put("loginStatus", 1);
        ctx.getSession().getAttributes().put("userId", user.getId());
        // This method is not finished yet.
        return Mono.just(0).then();
    }
    
    public Mono<Response> handleRequest(LoginContext ctx) {
        String username = ctx.getParameterMap().get("username");
        String password = ctx.getParameterMap().get("password");
        String ipAddr = ctx.getIpAddr();
        return isIpAddressBlocked(ipAddr)
                .filter((x)->x==Boolean.FALSE)
                .flatMapMany((x)->userCrudRepository.findByMobile(username))
                .collectList()
                .flatMap((x)->{
                    if (CollectionUtils.isEmpty(x)) {
                        return Mono.just(new Response("User not found"));
                    } else if (x.size() > 1) {
                        return Mono.just(new Response("Data abnormal, please contact the site administrator"));
                    } else {
                        User user = x.get(0);
                        if (DigestUtils.md5Hex(password).equals(user.getPassword())) {
                            return doWhenLoginSuccessfully(ctx, user)
                                .thenReturn(new Response("Login successfully"));
                        } else {
                            return Mono.just(new Response("Username or password error"));
                        }
                    }
                })
                .switchIfEmpty(Mono.just(new Response("Your ip is blocked")));
    }

但是flatMap中的ifs一点都不令人满意

我想知道是否有更好的方法来做到这一点。

感谢您的建议。

这是一个基于意见的问题,但我会如何做。

package com.vob.reactive.webflux.service;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.List;

import static java.util.Arrays.asList;

@Component
@Slf4j
public class CustomService {

    private List<String> blockedIpMap =  asList("127.0.0.1");
    private List<User> users =  asList(
            new User("invalid", DigestUtils.md5Hex("password")),
            new User("invalid", DigestUtils.md5Hex("password1")),
            new User("valid", DigestUtils.md5Hex("password"))
    );

    private Mono<Boolean> isIpAddressBlocked(String ipAddr) {
        return Mono.justOrEmpty(blockedIpMap.stream().filter(ip -> ip.equals(ipAddr)).findFirst())
                .flatMap(c -> Mono.just(true))
                .defaultIfEmpty(false);
    }

    private Mono<Boolean> doWhenLoginSuccessfully(User user) {
        // This method is not finished yet.
        return Mono.just(true);
    }

    public Mono<User> findByMobile(String username, String password) {
        return Flux.fromStream(users.stream().filter(u -> u.username.equals(username)))
                .collectList()
                .flatMap(x -> {
                    if (CollectionUtils.isEmpty(x)) {
                        return Mono.error(new IllegalArgumentException("User not found"));
                    } else if (x.size() > 1) {
                        return Mono.error(new IllegalArgumentException("Data abnormal, please contact the site administrator"));
                    }
                    User user = x.get(0);
                    if (!DigestUtils.md5Hex(password).equals(user.getPassword())) {
                        return Mono.error(new IllegalArgumentException("Username or password error"));
                    }
                    return Mono.just(user);
                });
    }

    public Mono<String> handleRequest(String username, String password,  String ipAddr) {
        return isIpAddressBlocked(ipAddr)
                .flatMap(isBlocked->{
                    if(isBlocked){
                        return Mono.error(new IllegalArgumentException("Your ip is blocked"));
                    }
                    return Mono.just(false);
                })
                .flatMap((x) -> findByMobile(username, password))
                .flatMap(this::doWhenLoginSuccessfully)
                .flatMap(x-> Mono.just("Login successfully"));
    }
    

    @Data
    public class User {
        private final String username;
        private final String password;
    }

}

然后测试

package com.vob.reactive.webflux.service;

import org.junit.jupiter.api.Test;
import reactor.test.StepVerifier;

class CustomServiceTest {
    @Test
    public void success() {
        var service = new CustomService();
        StepVerifier.create(service.handleRequest("valid", "password", "10.0.0.1"))
                .expectNext("Login successfully")
                .verifyComplete();
    }

    @Test
    public void invalidPassword() {
        var service = new CustomService();
        StepVerifier.create(service.handleRequest("valid", "password2", "10.0.0.1"))
                .expectErrorMessage("Username or password error")
                .verify();
    }

    @Test
    public void abnormal() {
        var service = new CustomService();
        StepVerifier.create(service.handleRequest("invalid", "password", "10.0.0.1"))
                .expectErrorMessage("Data abnormal, please contact the site administrator")
                .verify();
    }

    @Test
    public void blockedIpWithRightPassword() {
        var service = new CustomService();
        StepVerifier.create(service.handleRequest("valid", "password", "127.0.0.1"))
                .expectErrorMessage("Your ip is blocked")
                .verify();
    }
}

我喜欢这种方法,因为它将成功和错误分开,然后在控制器中你可以调用服务并将其映射到 200 OK 或错误请求 400

public Mono<Object> handleExample(String username, String password,  String ipAddr){
    return handleRequest(username, password, ipAddr)
            .onErrorMap(err-> // Return Error response)
            .flatMap();
}

是的,最后你仍然有这个带有 ifs 的 flatMap,但不幸的是,这就是你的逻辑。我不太了解您的逻辑,但我宁愿使用用户和密码从数据库中执行 select,所以最后您只能获得单声道。