springboot项目实现分库

本文是根据仓库编码 和 仓库id进行按仓库进行分库处理,可以根据例子自行按照业务需要进行分库

1.核心是实现 Spring 的 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法,实现动态数据源的目的

@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * ThreadLocal 线程独立存储
     */
    private static final ThreadLocal<String> THREAD_HOLD_SOURCE = new InheritableThreadLocal<>();

    /**
     * 决定使用哪个数据源之前需要把多个数据源的信息以及默认数据源信息配置好
     *
     * @param defaultTargetDataSource 默认数据源
     * @param targetDataSources       目标数据源
     */
    public DynamicDataSource(DataSource defaultDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultDataSource);
        super.setTargetDataSources(targetDataSources);
        super.afterPropertiesSet();
    }

    /**
     * 如果不希望数据源在启动配置时就加载好,可以定制这个方法,从任何你希望的地方读取并返回数据源
     * 比如从数据库、文件、外部接口等读取数据源信息,并最终返回一个DataSource实现类对象即可
     */
    @Override
    protected DataSource determineTargetDataSource() {
        return super.determineTargetDataSource();
    }


    /**
     * 如果希望所有数据源在启动配置时就加载好,这里通过设置数据源Key值来切换数据,定制这个方法
     *
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return getDataSource();
    }

    /**
     * 通过同一线程获取对应
     */
    public static String getDataSource() {
        log.info("获取选择对应的数据源名称:{}", THREAD_HOLD_SOURCE.get());
        return THREAD_HOLD_SOURCE.get();
    }

    public static void setDataSource(String sourceName) {
        log.info("设置对应的数据源名称:{}", sourceName);
        THREAD_HOLD_SOURCE.set(sourceName);
    }

    public static void clearDataSource() {
        THREAD_HOLD_SOURCE.remove();
    }

}

2.springboot项目启动类同级配置动态数据源配置

@Configuration
public class DynamicDataSourceConfig {




    /**
     * 数据源01 默认数据源
     */
    @Bean(name = DataSourceConst.test_01)
    @ConfigurationProperties("spring.datasource.test01")
    public DataSource test01() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源02
     */
    @Bean(name = DataSourceConst.test_02)
    @ConfigurationProperties("spring.datasource.test02")
    public DataSource test02() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 从库数据源
     */
    @Bean(name = DataSourceConst.test_01_SLAVE)
    @ConfigurationProperties("spring.datasource.test01-slave")
    public DataSource test01Slave() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 数据源02
     */
    @Bean(name = DataSourceConst.test_02_SLAVE)
    @ConfigurationProperties("spring.datasource.test02-slave")
    public DataSource test02Slave() {
        return DruidDataSourceBuilder.create().build();
    }


    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(@Qualifier("test01") DataSource test01, @Qualifier("test02") DataSource test02,
                                               @Qualifier("test01-slave") DataSource test01Slave, @Qualifier("test02-slave") DataSource test02Slave) {
        //构建数据源集合
        Map<Object, Object> dataSourceMap = new HashMap<>(5);
        dataSourceMap.put(DataSourceConst.test_01, test01);
        dataSourceMap.put(DataSourceConst.test_02, test02);
        dataSourceMap.put(DataSourceConst.test_01_SLAVE, test01Slave);
        dataSourceMap.put(DataSourceConst.test_02_SLAVE, test02Slave);
        return new DynamicDataSource(test01, dataSourceMap);
    }


    @Bean(name = "tidbJdbcTemplate")
    public JdbcTemplate tidbJdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }


}

pom文件引入包

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

 

 3.在公共模块配置所需配置Bean

public class DataSourceConst {

    public static final String test_01 = "test01";

    public static final String test_02 = "test02";

    public static final String test_01_SLAVE = "test01-slave";

    public static final String test_02_SLAVE = "test02-slave";


    /获取仓库编码字段//
    public static final String WH_CODE = "whCode";


    ///获取动态数据源redisKey///

    public static final String DATA_SOURCE_REDIS_KEY = "dynamic:data:source:";

    public static final String DATA_SOURCE_PROJECT_REDIS_KEY = "dynamic:project:data:source:";
	
}	

4.创建自定义异常类及枚举值

/**
 * 异常错误code
 * 重复操作
 */
@AllArgsConstructor
@Getter
public enum ErrorCodeEnum {

    VALID_ERROR_MESSAGE("10001", "参数校验异常"),
    ERROR_PARAMS("10002", "参数异常");

    private String code;
    private String message;

}
@Setter
@Getter
public class CustomException extends RuntimeException{

    private String code;
    private String message;

    public CustomException(String message) {
        this.message = message;
    }

    public CustomException(ErrorCodeEnum errorCodeEnum) {
        this.code = errorCodeEnum.getCode();
        this.message = errorCodeEnum.getMessage();
    }

    public CustomException(String message, ErrorCodeEnum errorCodeEnum) {
        this.code = errorCodeEnum.getCode();
        this.message = message;
    }

    public CustomException(String code, String message) {
        this.code = code;
        this.message = message;
    }

}

5.在全局异常处理类中配置全局公共返回

@Slf4j
@ControllerAdvice
public class TestServerGlobalExceptionHandler {

   
    @ExceptionHandler(CustomException.class)
    @ResponseBody
    public String handleCustomException(CustomException e) {
        log.error("自定义异常:{},{}", e.getMessage(), e);
        
        String result ="操作失败:"+e.getMessage();
        return result;
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public String handleException(Exception e) {
        log.error("接口异常:{},{}", e.getMessage(), e);
        String result ="操作失败:"+e.getMessage();
        return result ;
    }



}

6.编写获取动态数据源公共类,动态

@Slf4j
@Component
public class DataSourceUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private TestDataMappingManager testDataMappingManager;

    /**
     * 通过映射表 仓库编码 获取对应数据源的映射关系
     */
    public String dataSourceByWhCode(String whCode) {
        Assert.notNull(whCode, "仓库编码不能为空");
        String dataSourceName = null;
        final String WHCODE_SOURCE_REDIS_KEY = DataSourceConst.DATA_SOURCE_REDIS_KEY + whCode;
        //判断缓存是否命中
        if (redisTemplate.hasKey(WHCODE_SOURCE_REDIS_KEY)) {
            dataSourceName = String.valueOf(redisTemplate.opsForValue().get(WHCODE_SOURCE_REDIS_KEY));
        } else {
            //查询数据库
            TestDataMappingModel TestDataMappingModel = new TestDataMappingModel();
            TestDataMappingModel.setWhCode(whCode);
            List<TestDataMappingModel> byExample = testDataMappingManager.findByExample(TestDataMappingModel);
            //这里如果返回多条或者为空 则不做处理抛出异常
            if (byExample != null && byExample.size() == 1) {
                TestDataMappingModel TestDataMappingModelData = byExample.get(0);
                redisTemplate.opsForValue().set(WHCODE_SOURCE_REDIS_KEY, TestDataMappingModelData.getDataSourceName());
                dataSourceName = TestDataMappingModelData.getDataSourceName();
            }
        }
        return dataSourceName;
    }

    /**
     * 通过映射表 仓库ID 获取对应数据源的映射关系
     */
    public String dataSourceByWhId(String projectId) {
        Assert.notNull(projectId, "仓库ID不能为空");
        String dataSourceName = null;
        final String DATA_SOURCE_PROJECT_REDIS_KEY = DataSourceConst.DATA_SOURCE_PROJECT_REDIS_KEY + projectId;
        //判断缓存是否命中
        if (redisTemplate.hasKey(DATA_SOURCE_PROJECT_REDIS_KEY)) {
            dataSourceName = String.valueOf(redisTemplate.opsForValue().get(DATA_SOURCE_PROJECT_REDIS_KEY));
        } else {
            //查询数据库
            TestDataMappingModel TestDataMappingModel = new TestDataMappingModel();
            TestDataMappingModel.setWhId(Long.valueOf(projectId));
            List<TestDataMappingModel> byExample = testDataMappingManager.findByExample(TestDataMappingModel);
            //这里如果返回多条或者为空 则不做处理抛出异常
            if (byExample != null && byExample.size() == 1) {
                TestDataMappingModel TestDataMappingModelData = byExample.get(0);
                redisTemplate.opsForValue().set(DATA_SOURCE_PROJECT_REDIS_KEY, TestDataMappingModelData.getDataSourceName());
                dataSourceName = TestDataMappingModelData.getDataSourceName();
            }
        }
        return dataSourceName;
    }


    /**
     * 通过注解信息获应仓库编码字段内容映射获取数据源 以及 selectName选项
     * 注意  这里仅支持获取第一个参数值的  并且仅支持第一个对象参数第一层,不支持嵌套
     * <p>
     * 这里注明如果选择selectName 则优先获取对应数据源
     *
     * @return
     */
    public String getwhCodeByAnnotation(JoinPoint proceedingJoinPoint, DSource dSource) {

        String dataSourceName = null;
        try {
            String whCode = null;
            String projectId = null;

            if(dSource !=null){
                //通过注解selectName选项获取
                if (dSource.selectName().length() != 0) {
                    return dSource.selectName();
                }

                //通过jsonPath表达式来解析对应的仓库编码字段
                if (StringUtils.isNotBlank(dSource.jsonPathExpression())) {
                    String jsonBody = JSON.toJSONString(proceedingJoinPoint.getArgs()[0]);
                    if (StringUtils.isNotEmpty(jsonBody) && StringUtils.isNotBlank(jsonBody)) {
                        whCode = JsonPath.parse(jsonBody).read(dSource.jsonPathExpression());
                    }
                }


                //通过jsonPath表达式来解析对应的projectId字段
                if (StringUtils.isNotBlank(dSource.jsonPathExpression2())) {
                    String jsonBody = JSON.toJSONString(proceedingJoinPoint.getArgs()[0]);
                    if (StringUtils.isNotEmpty(jsonBody) && StringUtils.isNotBlank(jsonBody)) {
                        projectId = JsonPath.parse(jsonBody).read(dSource.jsonPathExpression2());
                        dataSourceName = dataSourceByWhId(projectId);
                    }
                }

                //通过参数值来获取对应的仓库Id 这里支持获取第一参数的内容
                if (dSource.useUserUtilBean()) {
                    if (proceedingJoinPoint.getArgs()[0] instanceof String) {
                        JSONObject jobParamsObject = JSONObject.parseObject(String.valueOf(proceedingJoinPoint.getArgs()[0]));
                        UserUtilBean userUtilBean = JSONObject.toJavaObject(jobParamsObject, UserUtilBean.class);
                        projectId = userUtilBean.getProjectId();
                        log.info("获取定时任务仓库id:{}", projectId);
                        dataSourceName = dataSourceByWhId(projectId);
                    }else if(proceedingJoinPoint.getArgs()[0] instanceof UserUtilBean ){
                        UserUtilBean userUtilBean = (UserUtilBean) proceedingJoinPoint.getArgs()[0];
                        projectId = userUtilBean.getProjectId();
                        log.info("获取定时任务仓库id:{}", projectId);
                        dataSourceName = dataSourceByWhId(projectId);
                    }
                }

                Object[] args = proceedingJoinPoint.getArgs();
                if (dataSourceName == null) {
                    for (Object o : args) {
                        if (o instanceof UserUtilBean) {
                            UserUtilBean userUtilBean = (UserUtilBean) o;
                            projectId = userUtilBean.getProjectId();
                            whCode = userUtilBean.getWhCode();
                            log.info("获取接口多个参数情况:{}", JSON.toJSONString(userUtilBean));
                            if(projectId!=null && !"".equals(projectId)){
                                log.info("获取接口多个参数情况的仓库id:{}", projectId);
                                dataSourceName = dataSourceByWhId(projectId);
                            }
                            if(whCode!=null && !"".equals(whCode) && dataSourceName == null ){
                                log.info("获取接口多个参数情况的仓库code:{}", whCode);
                                dataSourceName = dataSourceByWhCode(whCode);
                            }
                        }
                    }
                }
            }

            if (StringUtils.isNotEmpty(whCode) && StringUtils.isNotBlank(whCode)) {
                dataSourceName = dataSourceByWhCode(whCode);
            }
        } catch (Exception e) {
            log.error("DataSourceAspect 获取仓库编码异常:{}", e);
        }

        if (StringUtils.isNotEmpty(dataSourceName) && StringUtils.isNotBlank(dataSourceName)) {
            DynamicDataSource.setDataSource(dataSourceName);
        } else {
            throw new CustomException("数据源获取不能为空,请联系管理员");
        }

        return dataSourceName;

    }

}

7.设置注解切点

/**
外部接口,内部项目接口对接使用注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DSource {

    /**
     * 这里用来处理根据注解字段来获取对应分片字段的值
     *
     * @return
     */
    String columnName() default DataSourceConst.WH_CODE;

    /**
     * 是否通过userUtilBean projectId来获取
     *
     * @return
     */
    boolean useUserUtilBean() default false;

    /**
     *  之类采用jsonPath表达式路径来获取对应的属性值
     *  参考文档
     *  https://github.com/json-path/JsonPath
     *
     */
    String  jsonPathExpression() default  "" ;

    /**
     *  之类采用jsonPath表达式路径来获取对应的属性值 解析projectId
     *  参考文档
     *  https://github.com/json-path/JsonPath
     *
     */
    String  jsonPathExpression2() default  "" ;

    /**
     * 手动选择使用那个逻辑数据源名称
     *
     * @return
     */
    String selectName() default "";


}
/**
报表查询走只读 注解
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface IgnoreSource {
}

8.编写web请求拦截器及反射,从而实现动态数据源

@Configuration
public class DataSourceFeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
     /*   if(requestAttributes!=null){
            HttpServletRequest requestServlet = requestAttributes.getRequest();
            Enumeration<String> headerNames = requestServlet.getHeaderNames();
            if(headerNames!=null){
                while (headerNames.hasMoreElements()){
                    String headerName = headerNames.nextElement();
                    String headerValue = requestServlet.getHeader(headerName);
                    // 将header向下传递
                    template.header(headerName,headerValue);
                }
            }
        }*/
        //获取对应请求头whCode参数字段内容
        if(requestAttributes!=null){
            if(requestAttributes.getRequest()!=null){
                String whCode = requestAttributes.getRequest().getHeader("whCode");
                if (whCode != null && !"".equals(whCode)) {
                    template.header("whCode", whCode);
                }
            }

        }

    }
}
@Order(2)
@Slf4j
@Aspect
@Component
public class OperatorRequestAspect {

    @Autowired
    private DataSourceUtil dataSourceUtil;


    @PostConstruct
    public void init() {
        log.info("OperatorRequestAspect切面初始化");
    }

    @Pointcut("(@target(org.springframework.web.bind.annotation.RestController))  &&  (execution( * *..*.*ServiceImpl.*(..))) && !(@annotation(com.xx.dynamic.datasource.IgnoreSource)) && !(@annotation(com.xx.dynamic.datasource.DSource))")
    //@Pointcut("(@target(org.springframework.web.bind.annotation.RestController)) && !(@annotation(com.xx.dynamic.datasource.DSource))")
    public void feignControllerAspect() {

    }

    /**
     * 方法调用之前调用
     */
    @Around("feignControllerAspect()")
    public Object doFeignAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object proceedResult = null;
        try {
            //判断当前方法是否有数据源
            String dataSource = DynamicDataSource.getDataSource();
            if (dataSource != null) {
                DynamicDataSource.setDataSource(dataSource);
                return  proceedingJoinPoint.proceed();
            }

            String dataSourceName = null;
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
            DSource ds = method.getAnnotation(DSource.class);
            log.info("获取切面方法名称:{},切面类名称:{}", method.getName(), proceedingJoinPoint.getTarget().getClass());
            //这里用来过滤DSource 注解切面逻辑 避免和其他切面处理业务重复
            if (ds == null) {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                        .getRequest();
                //获取对应请求头whCode参数字段内容
                String whCode = request.getHeader("whCode");
                String projectId = request.getHeader("projectId");

                //通过template获取对应whCode/projectId

                Object[] args = proceedingJoinPoint.getArgs();
                if ((whCode == null || "".equals(whCode)) && (projectId == null || "".equals(projectId))) {
                    for (Object o : args) {
                        if (o instanceof UserUtilBean) {
                            UserUtilBean userUtilBean = (UserUtilBean) o;
                            log.info("获取多参数userUtilBean:{}", JSON.toJSONString(userUtilBean));
                            projectId = userUtilBean.getProjectId();
                            whCode = userUtilBean.getWhCode();
                        }
                    }
                }

                if (projectId != null && !"".equals(projectId)) {
                    //执行projectId 映射获取对应数据源信息
                    dataSourceName = dataSourceUtil.dataSourceByWhId(projectId);
                    if (StringUtils.isNotEmpty(dataSourceName) && StringUtils.isNotBlank(dataSourceName)) {
                        DynamicDataSource.setDataSource(dataSourceName);
                        proceedResult = proceedingJoinPoint.proceed();
                        return proceedResult;
                    }

                }

                if (whCode != null && !"".equals(whCode)) {
                    //执行whCode 映射获取对应数据源信息
                    dataSourceName = dataSourceUtil.dataSourceByWhCode(whCode);
                    if (StringUtils.isNotEmpty(dataSourceName) && StringUtils.isNotBlank(dataSourceName)) {
                        DynamicDataSource.setDataSource(dataSourceName);
                        proceedResult = proceedingJoinPoint.proceed();
                        return proceedResult;
                    }

                }

            }

        } finally {
            DynamicDataSource.clearDataSource();
        }
        throw new CustomException("数据源获取不能为空,请联系管理员");
    }


}

8.编写报表查询请求,走只读库反射

@Order(5)
@Slf4j
@Aspect
@Component
public class SlaveRequestAspect {

    @Autowired
    private DataSourceUtil dataSourceUtil;

    private static final String SLAVE_PRIX = "-slave";

    @PostConstruct
    public void init() {
        log.info("SlaveRequestAspect切面初始化");
    }

    @Pointcut("(@target(org.springframework.web.bind.annotation.RestController))  &&  (execution( * *..*.*ReportServiceImpl.*(..))) && (@annotation(com.xx.dynamic.datasource.IgnoreSource))")
    public void reportControllerAspect() {

    }

    /**
     * 方法调用之前调用
     */
    @Around("reportControllerAspect()")
    public Object doReportAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object proceedResult = null;
        try {
            //判断当前方法是否有数据源
            String dataSource = DynamicDataSource.getDataSource();
            String dataSourceName = null;
            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
            if (dataSource != null) {
                DynamicDataSource.setDataSource(dataSource);
                return proceedingJoinPoint.proceed();
            }
            log.info("获取切面方法名称:{},切面类名称:{}", method.getName(), proceedingJoinPoint.getTarget().getClass());
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            //获取对应请求头whCode参数字段内容
            String whCode = request.getHeader("whCode");
            String projectId = request.getHeader("projectId");

            //通过template获取对应whCode/projectId

            Object[] args = proceedingJoinPoint.getArgs();
            if ((whCode == null || "".equals(whCode)) && (projectId == null || "".equals(projectId))) {
                for (Object o : args) {
                    if (o instanceof UserUtilBean) {
                        UserUtilBean userUtilBean = (UserUtilBean) o;
                        log.info("获取多参数userUtilBean:{}", JSON.toJSONString(userUtilBean));
                        projectId = userUtilBean.getProjectId();
                        whCode = userUtilBean.getWhCode();
                    }
                }
            }

            if (projectId != null && !"".equals(projectId)) {
                //执行projectId 映射获取对应数据源信息
                dataSourceName = dataSourceUtil.dataSourceByWhId(projectId);
                if (StringUtils.isNotEmpty(dataSourceName) && StringUtils.isNotBlank(dataSourceName)) {
                    DynamicDataSource.setDataSource(dataSourceName + SLAVE_PRIX);
                    proceedResult = proceedingJoinPoint.proceed();
                    return proceedResult;
                }

            }

            if (whCode != null && !"".equals(whCode)) {
                //执行whCode 映射获取对应数据源信息
                dataSourceName = dataSourceUtil.dataSourceByWhCode(whCode);
                if (StringUtils.isNotEmpty(dataSourceName) && StringUtils.isNotBlank(dataSourceName)) {
                    DynamicDataSource.setDataSource(dataSourceName + SLAVE_PRIX);
                    proceedResult = proceedingJoinPoint.proceed();
                    return proceedResult;
                }

            }

        } finally {
            DynamicDataSource.clearDataSource();
        }
        throw new CustomException("数据源获取不能为空,请联系管理员");

    }


}

9.编写注解反射,根据注解从而使接口请求,获取数据源

@Order(3)
@Aspect
@Slf4j
@Component
public class DataSourceAspect {

    @Autowired
    private DataSourceUtil dataSourceUtil;

    @PostConstruct
    public void init() {
        log.info("DataSourceAspect切面初始化 ");
    }

    /**
     * 定义pointCut
     */
    @Pointcut("@annotation(com.sinoservices.dynamic.datasource.DSource) ")
    public void dataSourcePoint() {

    }


    /**
     * 默认数据源走wms01
     * 其他情况走动态选择
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("dataSourcePoint()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //这通过切面获取对应分片字段值(仓库编码字段)
        try {

            //判断当前方法是否有数据源
            String dataSource = DynamicDataSource.getDataSource();
            if (dataSource != null) {
                DynamicDataSource.setDataSource(dataSource);
                return proceedingJoinPoint.proceed();
            }

            MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
            Method method = signature.getMethod();
            log.info("获取切面方法名称:{},切面类名称:{}", method.getName(), proceedingJoinPoint.getTarget().getClass());
            DSource ds = method.getAnnotation(DSource.class);

//            Object[] args = proceedingJoinPoint.getArgs();
//            if (ds == null) {
//                DynamicDataSource.setDataSource(DataSourceConst.WMS_01);
//                log.debug("set default datasource is :{} ", DataSourceConst.WMS_01);
//            } else {
            //如果注解不会空,支持两种情况,
            // 第一种 通过注解属性seletName选择对应数据源
            // 第二种 通过注解属性columnName标注获取仓库编码字段名称来获取对应的仓库编码内容
            String dataSourceName = dataSourceUtil.getwhCodeByAnnotation(proceedingJoinPoint, ds);
            DynamicDataSource.setDataSource(dataSourceName);
//            }
            return proceedingJoinPoint.proceed();
        } finally {
            log.info("移除数据源:{}", DynamicDataSource.getDataSource());
            DynamicDataSource.clearDataSource();
        }
    }


}

11.FeignInterceptor拦截器增加仓库编码参数

@Configuration
public class FeignInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        if("/loginService/login".equals(template.url())
                || "/loginService/loadUserDetails".equals(template.url())){
            return;
        }
        UserDetails userDetails = UserContextHolder.getCurrentUser();
        String language= "zh_CN";
        if(null != userDetails && userDetails.getLanguage()!= null){
            language = userDetails.getLanguage();
        }
        language=language.replace("_","-");
        LocaleContextHolder.setLocale(Locale.forLanguageTag(language));
        //添加仓库编码到对应请求头
        if(ObjectUtils.isNotEmpty(userDetails)){
            template.header("whCode",userDetails.getWhCode());
            template.header("language",language);
        }
    }
}

10.注解使用案例

//test中包含 projectId 字段值
@DSource(jsonPathExpression2 = "$.projectId")
public String saveList(TestModel test) {
}


//直接包含用户对象
@DSource(useUserUtilBean = true)
	public void testSave(UserUtilBean userUtilBean) {
}

//test中包含 whCode字段值
@DSource(jsonPathExpression = "$.whCode")
public String saveList(TestModel test) {
}

//直接设置数据源名称
@DSource(selectName = DataSourceConst.WMS_02_SLAVE)
public String saveList(TestModel test) {
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/874121.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

铁威马秋季新品即将上线,你想要的NAS我都有!

各位铁粉们&#xff0c;注意啦&#xff01; 一场关于存储的饕餮盛宴即将拉开帷幕 铁威马&#xff0c;带着九款全新力作NAS 将于9月19日席卷全球市场 是的&#xff0c;你没听错 九款&#xff01; 从入门级到专业级 从桌面型到机架式 全系搭载TOS 6 总有一款能击中你的心…

如何利用大数据与AI技术革新相亲交友体验

在数字化时代&#xff0c;大数据和人工智能&#xff08;AI&#xff09;技术正逐渐革新相亲交友体验&#xff0c;为寻找爱情的过程带来前所未有的变革&#xff08;编辑h17711347205&#xff09;。通过精准分析和智能匹配&#xff0c;这些技术能够极大地提高相亲交友系统的效率和…

架空输电线路故障监测:可视精灵的导线全方位监测之道

集高科技与智能化于一身的“架空输电线路故障可视精灵”&#xff0c;以精准、高效、全面的监测能力&#xff0c;成为守护输电线路安全的得力助手&#xff0c;接下来&#xff0c;深圳鼎信智慧带您走近它&#xff1a; 技术概述 物联网&#xff08;IoT&#xff09;&#xff1a;通…

JavaSE-易错题集-004

1. 以下关于对象序列化描述正确的是 A 使用FileOutputStream可以将对象进行传输 B 使用PrintWriter可以将对象进行传输 C 使用transient修饰的变量不会被序列化 D 对象序列化的所属类需要实现Serializable接口 参考答案&#xff1a;CD 考点&#xff1a;对象序列化 题解&…

MYSQL1

一、为什么学习数据库 1、岗位技能需求 2、现在的世界,得数据者得天下 3、存储数据的方法 4、程序,网站中,大量数据如何长久保存? 5、数据库是几乎软件体系中最核心的一个存在。 二、数据库相关概念 (一)数据库DB 数据库是将大量数据保存起来&#xff0c;通过计算机加…

反向沙箱-安全上网解决方案

随着信息化的发展&#xff0c;企业日常办公越来越依赖互联网。终端以及普通PC终端在访问互联网过程中&#xff0c;会遇到各种各样不容忽视的风险&#xff0c;例如员工主动故意的数据泄漏&#xff0c;后台应用程序偷偷向外部发信息&#xff0c;木马间谍软件的外联&#xff0c;以…

Leetcode 最大子数组和

使用“Kadane’s Algorithm”来解决。 Kadane’s Algorithm 在每个步骤中都保持着一个局部最优解&#xff0c;即以当前元素为结尾的最大子数组和(也就是局部最优解)&#xff0c;并通过比较这些局部最优解和当前的全局最优解来找到最终的全局最优解。 Kadane’s Algorithm的核…

Python全网最全基础课程笔记(七)——列表,跟着思维导图和图文来学习,爆肝2w字,无数代码案例!

本专栏系列为Pythong基础系列&#xff0c;每篇内容非常全面&#xff0c;包含全网各个知识点&#xff0c;非常长&#xff0c;请耐心看完。 每天都会更新新的内容&#xff0c;搜罗全网资源以及自己在学习和工作过程中的一些总结&#xff0c;可以说是非常详细和全面。 以至于为什么…

如何划分类/单一职权原则SRP

参考&#xff1a;单一职责 -- 每个类只负责一个功能_每个类应该只负责一个功能,遵循单一职责原则。-CSDN博客 类有且只有一个原因需要修改它&#xff0c;这样的才是一个结构简洁的类。 结合上面的例子&#xff0c;需要注意的点&#xff1a; 1.比如搜索数据库&#xff0c;需要…

解锁生活密码,AI答案之书解决复杂难题

本文由 ChatMoney团队出品 介绍说明 “答案之书智能体”是您贴心的智慧伙伴&#xff0c;随时准备为您解答生活中的种种困惑。无论您在工作中遭遇瓶颈&#xff0c;还是在情感世界里迷失方向&#xff0c;亦或是对个人成长感到迷茫&#xff0c;它都能倾听您的心声&#xff0c;并给…

SpringCloud之配置中心git示例

SpringCloud之配置中心git示例 随着线上项目变的日益庞大&#xff0c;每个项目都散落着各种配置文件&#xff0c;如果采用分布式的开发模式&#xff0c;需要的配置文件随着 服务增加而不断增多。 某一个基础服务信息变更&#xff0c;都会引起一系列的更新和重启&#xff0c;…

Flutter中自定义气泡框效果的实现

在用户界面的设计中&#xff0c;气泡框&#xff08;Bubble&#xff09;是一种非常有效的视觉工具&#xff0c;它可以用来突出显示信息或提示用户。气泡框广泛应用于聊天应用、通知提示等场景。在 Flutter 中&#xff0c;虽然有很多现成的气泡框组件&#xff0c;但如果你想要更多…

前端:HTML、CSS、JS、Vue

1 前端 内容概要 了解前端三件套(HTML、CSS、JS)在前端所起的作用掌握HTML标签的功能&#xff0c;掌握重要标签(a标签&#xff0c;form标签)了解CSS了解JS的基础语法掌握Vue的基础语法重点掌握Vue项目怎么启动项目掌握前后端分离是什么。前端做什么事情&#xff0c;后端做什么…

elementUI之不会用

form表单注意事项 <template><div class"container"><el-form :model"form" label-width"80px" :rules"RulesName" ref"loginForm"><el-form-item label"姓名" prop"username">…

【Unity错误】No cloud project ID was found by the Analytics SDK

在编译默认的URP 2D项目时&#xff0c;出现这样一个错误&#xff1a;No cloud project ID was found by the Analytics SDK. This means Analytics events will not be sent. Please make sure to link your cloud project in the Unity editor to fix this problem. 原因&…

hiprint打印/jsPDF使用/html2canvas

最初我知道hiprint.print是可以打印双模板的&#xff0c;于是查看hiprint.print的源码发现底层实现是this.getHtml(t).hiwprint,于是断点查看getHtm的实现&#xff0c;得知它是遍历我们对print传参的list&#xff0c;利用list中模板对象的getHtml()方法得到模板的dom对象&#…

干货 | Selenium+chrome自动批量下载地理空间数据云影像

1.背景介绍 1.1地理空间数据云 由中国科学院计算机网络信息中心科学数据中心成立的地理空间数据云平台是常见的下载空间数据的平台之一。其提供了较为完善的公开数据&#xff0c;如LANDSAT系列数据&#xff0c;MODIS的标准产品及其合成产品&#xff0c;DEM数据&#xff08;SR…

NVIDIA驱动学习

lspci | grep -i vga 输出&#xff1a; 2d:00.0 VGA compatible controller: NVIDIA Corporation Device 2204 (rev a1) 99:00.0 VGA compatible controller: NVIDIA Corporation Device 2230 (rev a1)import torch print(torch.version.cuda) # 应该显示 CUDA 版本 print(tor…

【自动化与控制系统】SCI一区TOP神刊!最快19天accept、稳定检索!

期刊解析 &#x1f6a9;本 期 期 刊 看 点 &#x1f6a9; 国人发文占比第一&#xff0c;TOP刊 审稿友好&#xff0c;审稿速度快 自引率9.8% 今天小编带来计算机领域SCI快刊的解读&#xff01; 如有相关领域作者有意投稿&#xff0c;可作为重点关注&#xff01; 01 期刊信息…

综合评价 | 基于熵权-变异系数-博弈组合法的综合评价模型(Matlab)

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 根据信息熵的定义&#xff0c;对于某项指标&#xff0c;可以用熵值来判断某个指标的离散程度&#xff0c;其信息熵值越小&#xff0c;指标的离散程度越大&#xff0c; 该指标对综合评价的影响&#xff08;即权重&…