package com.ruoyi.framework.config.properties;

import java.util.*;
import java.util.regex.Pattern;
import org.apache.commons.lang3.RegExUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.ruoyi.common.annotation.Anonymous;

/**
 * 设置Anonymous注解 允许 匿名访问不鉴权
 * 项目初始化时候，会将Anonymous注解的 路径 提取出来，放到urls里面
 *
 * InitializingBean：为bean提供了初始化方法的方式，凡是继承该接口的类，在初始化bean的时候都会执行afterPropertiesSet方法
 *
 * ApplicationContextAware：当一个类实现了这个接口之后，这个类就可以方便的获得ApplicationContext对象（spring上下文）
 * ，Spring发现某个Bean实现了ApplicationContextAware接口，Spring容器会在创建该Bean之后，自动调用该Bean的setApplicationContext（参数）方法
 * ，调用该方法时，会将容器本身ApplicationContext对象作为参数传递给该方法。
 *
 * @author ruoyi
 */
@Configuration
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware {
    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");

    // spring上下文
    private ApplicationContext applicationContext;

    // 前端请求接口的时候，如果在这个url里面，直接放行
    private List<String> urls = new ArrayList<>();

    // 替代字符串
    public String ASTERISK = "*";

    @Override
    public void afterPropertiesSet() {

        // 将整个项目所有bean都拿出来
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);

        // 获取 每个url 与 方法、类的对应关系
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();

        // info 是代表每个url对象
        map.keySet().forEach(info -> {
             // 获取类和方法的信息
            HandlerMethod handlerMethod = map.get(info);

            // 获取方法上边的注解，如果此方法被“Anonymous”注解，则返回该方法，否则返回null
            Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class);
            // method为null啥也不干，否则替代path variable 为 *
            Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));

            // 获取类上边的注解，如果此类被“Anonymous”注解，则返回该类，否则返回null
            Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class);
            // controller为null啥也不干，否则替代path variable 为 *
            Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPatternsCondition().getPatterns())
                    .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK))));
        });
    }

    /**
     * @Description // 这玩意是接口ApplicationContextAware带来的，就是为了在本类中方便获取 applicationContext
     * @Date 16:15 2023/3/22
     **/
    
    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        this.applicationContext = context;
    }

    public List<String> getUrls() {
        return urls;
    }

    public void setUrls(List<String> urls) {
        this.urls = urls;
    }
}

