iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > Python >使用注解解决ShardingJdbc不支持复杂SQL方法
  • 768
分享到

使用注解解决ShardingJdbc不支持复杂SQL方法

2024-04-02 19:04:59 768人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录背景介绍问题复现解决思路具体实现编写autoConfig 类自定义数据源aop 拦截器:aop 切面定义ThreadLocal启动类配置背景介绍 公司最近做分库分表业务,接入了

背景介绍

公司最近做分库分表业务,接入了 Sharding JDBC,接入完成后,回归测试时发现好几个 sql 执行报错,关键这几个表都还不是分片表。

报错如下:

这下糟了嘛。熟悉 Sharding JDBC 的同学应该知道,有很多 SQL 它是不支持的。官方截图如下:

如果要去修改这些复杂 SQL 的话,可能要花费很多时间。那怎么办呢?只能从 Sharding JDBC 这里找突破口了,两天的研究,出来了下面这个只需要加一个注解轻松解决 Sharding Jdbc 不支持复杂 SQL 的方案。

问题复现

我本地写了一个复杂 SQL 进行测试:

public List<Map<String, Object>> queryOrder(){
        List<Map<String, Object>> orders = borderRepository.findOrders();
        return orders;
    }
public interface BOrderRepository extends JpaRepository<BOrder,Long> {
    @Query(value = "SELECT * FROM (SELECT id,CASE WHEN company_id =1 THEN '小' WHEN company_id=4 THEN '中' ELSE '大' END AS com,user_id as userId FROM b_order0) t WHERE t.com ='中'",nativeQuery =true)
    List<Map<String, Object>> findOrders();
}

写了个测试 controller 来调用,调用后果然报错了。

解决思路

因为查询的复杂 SQL 的表不是分片表,那能不能指定这几个复杂查询的时候不用 Sharding JDBC 的数据源呢?

  • 在注入 Sharding JDBC 数据源的地方做处理,注入一个我们自定义的数据源
  • 这样我们获取连接的时候就能返回原生数据源了
  • 另外我们声明一个注解,对标识了注解的就返回原生数据源,否则还是返回 Sharding 数据源

具体实现

编写autoConfig 类

  • 编写一个 autoConfig 类,来替换 ShardingSphereAutoConfiguration 类

@Configuration
@ComponentScan("org.apache.shardingsphere.spring.boot.converter")
@EnableConfigurationProperties(SpringBootPropertiesConfiguration.class)
@ConditionalOnProperty(prefix = "spring.shardingsphere", name = "enabled", havingValue = "true", matchIfMissing = true)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
public class DynamicDataSourceAutoConfiguration implements EnvironmentAware {
    private String databaseName;
    private final SpringBootPropertiesConfiguration props;
    private final Map<String, DataSource> dataSourceMap = new LinkedHashMap<>();
    public DynamicDataSourceAutoConfiguration(SpringBootPropertiesConfiguration props) {
        this.props = props;
    }
    
    @Bean
    public ModeConfiguration modeConfiguration() {
        return null == props.getMode() ? null : new ModeConfigurationYamlSwapper().swapToObject(props.getMode());
    }
    
    @Bean
    @Conditional(LocalRulesCondition.class)
    @Autowired(required = false)
    public DataSource shardingSphereDataSource(final ObjectProvider<List<RuleConfiguration>> rules, final ObjectProvider<ModeConfiguration> modeConfig) throws SQLException {
        Collection<RuleConfiguration> ruleConfigs = Optional.ofNullable(rules.getIfAvailable()).orElseGet(Collections::emptyList);
        DataSource dataSource = ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig.getIfAvailable(), dataSourceMap, ruleConfigs, props.getProps());
        return new WrapShardingDataSource((ShardingSphereDataSource) dataSource,dataSourceMap);
    }
    
    @Bean
    @ConditionalOnMissingBean(DataSource.class)
    public DataSource dataSource(final ModeConfiguration modeConfig) throws SQLException {
        DataSource dataSource = !dataSourceMap.isEmpty() ? ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig, dataSourceMap, Collections.emptyList(), props.getProps())
                : ShardingSphereDataSourceFactory.createDataSource(databaseName, modeConfig);
        return new WrapShardingDataSource((ShardingSphereDataSource) dataSource,dataSourceMap);
    }
    
    @Bean
    public TransactionTypeScanner transactionTypeScanner() {
        return new TransactionTypeScanner();
    }
    @Override
    public final void setEnvironment(final Environment environment) {
        dataSourceMap.putAll(DataSourceMapSetter.getDataSourceMap(environment));
        databaseName = DatabaseNameSetter.getDatabaseName(environment);
    }
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    @Bean
    @ConditionalOnProperty(prefix = "spring.datasource.dynamic.aop", name = "enabled", havingValue = "true", matchIfMissing = true)
    public Advisor dynamicDatasourceAnnotationAdvisor() {
        DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(true);
        DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
        return advisor;
    }
}

自定义数据源

public class WrapShardingDataSource extends AbstractDataSourceAdapter implements AutoCloseable{
    private ShardingSphereDataSource dataSource;
    private Map&lt;String, DataSource&gt; dataSourceMap;
    public WrapShardingDataSource(ShardingSphereDataSource dataSource, Map&lt;String, DataSource&gt; dataSourceMap) {
        this.dataSource = dataSource;
        this.dataSourceMap = dataSourceMap;
    }
    public DataSource getTargetDataSource(){
        String peek = DynamicDataSourceContextHolder.peek();
        if(StringUtils.isEmpty(peek)){
            return dataSource;
        }
        return dataSourceMap.get(peek);
    }
    @Override
    public Connection getConnection() throws SQLException {
        return getTargetDataSource().getConnection();
    }
    @Override
    public Connection getConnection(final String username, final String passWord) throws SQLException {
        return getConnection();
    }
    @Override
    public void close() throws Exception {
        DataSource targetDataSource = getTargetDataSource();
        if (targetDataSource instanceof AutoCloseable) {
            ((AutoCloseable) targetDataSource).close();
        }
    }
    @Override
    public int getLoginTimeout() throws SQLException {
        DataSource targetDataSource = getTargetDataSource();
        return targetDataSource ==null ? 0 : targetDataSource.getLoginTimeout();
    }
    @Override
    public void setLoginTimeout(final int seconds) throws SQLException {
        DataSource targetDataSource = getTargetDataSource();
        targetDataSource.setLoginTimeout(seconds);
    }
}
  • 声明指定数据源注解
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DS {
    
    String value();
}
  • 另外使用 AOP 的方式拦截使用了注解的类或方法,并且要将这些用了注解的方法存起来,在获取数据源连接的时候取出来进行判断。这就还要用到 ThreadLocal。

aop 拦截器:

public class DynamicDataSourceAnnotationInterceptor implements MethodInterceptor {
    private final DataSourceClassResolver dataSourceClassResolver;
    public DynamicDataSourceAnnotationInterceptor(Boolean allowedPublicOnly) {
        dataSourceClassResolver = new DataSourceClassResolver(allowedPublicOnly);
    }
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String dsKey = determineDatasourceKey(invocation);
        DynamicDataSourceContextHolder.push(dsKey);
        try {
            return invocation.proceed();
        } finally {
            DynamicDataSourceContextHolder.poll();
        }
    }
    private String determineDatasourceKey(MethodInvocation invocation) {
        String key = dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
        return key;
    }
}

aop 切面定义


public class DynamicDataSourceAnnotationAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    private final Advice advice;
    private final Pointcut pointcut;
    private final Class<? extends Annotation> annotation;
    public DynamicDataSourceAnnotationAdvisor(MethodInterceptor advice,
                                               Class<? extends Annotation> annotation) {
        this.advice = advice;
        this.annotation = annotation;
        this.pointcut = buildPointcut();
    }
    @Override
    public Pointcut getPointcut() {
        return this.pointcut;
    }
    @Override
    public Advice getAdvice() {
        return this.advice;
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        if (this.advice instanceof BeanFactoryAware) {
            ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
        }
    }
    private Pointcut buildPointcut() {
        Pointcut cpc = new AnnotationMatchingPointcut(annotation, true);
        Pointcut mpc = new AnnotationMethodPoint(annotation);
        return new ComposablePointcut(cpc).uNIOn(mpc);
    }
    
    private static class AnnotationMethodPoint implements Pointcut {
        private final Class<? extends Annotation> annotationType;
        public AnnotationMethodPoint(Class<? extends Annotation> annotationType) {
            Assert.notNull(annotationType, "Annotation type must not be null");
            this.annotationType = annotationType;
        }
        @Override
        public ClassFilter getClassFilter() {
            return ClassFilter.TRUE;
        }
        @Override
        public MethodMatcher getMethodMatcher() {
            return new AnnotationMethodMatcher(annotationType);
        }
        private static class AnnotationMethodMatcher extends StaticMethodMatcher {
            private final Class<? extends Annotation> annotationType;
            public AnnotationMethodMatcher(Class<? extends Annotation> annotationType) {
                this.annotationType = annotationType;
            }
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
                if (matchesMethod(method)) {
                    return true;
                }
                // Proxy classes never have annotations on their redeclared methods.
                if (Proxy.isProxyClass(targetClass)) {
                    return false;
                }
                // The method may be on an interface, so let's check on the target class as well.
                Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
                return (specificMethod != method && matchesMethod(specificMethod));
            }
            private boolean matchesMethod(Method method) {
                return AnnotatedElementUtils.hasAnnotation(method, this.annotationType);
            }
        }
    }
}

public class DataSourceClassResolver {
    private static boolean mpEnabled = false;
    private static Field mapperInterfaceField;
    static {
        Class<?> proxyClass = null;
        try {
            proxyClass = Class.forName("com.baomidou.mybatisplus.core.override.MybatisMapperProxy");
        } catch (ClassNotFoundException e1) {
            try {
                proxyClass = Class.forName("com.baomidou.mybatisplus.core.override.PageMapperProxy");
            } catch (ClassNotFoundException e2) {
                try {
                    proxyClass = Class.forName("org.apache.ibatis.binding.MapperProxy");
                } catch (ClassNotFoundException ignored) {
                }
            }
        }
        if (proxyClass != null) {
            try {
                mapperInterfaceField = proxyClass.getDeclaredField("mapperInterface");
                mapperInterfaceField.setAccessible(true);
                mpEnabled = true;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }
    
    private final Map<Object, String> dsCache = new ConcurrentHashMap<>();
    private final boolean allowedPublicOnly;
    
    public DataSourceClassResolver(boolean allowedPublicOnly) {
        this.allowedPublicOnly = allowedPublicOnly;
    }
    
    public String findKey(Method method, Object targetObject) {
        if (method.getDeclarinGClass() == Object.class) {
            return "";
        }
        Object cacheKey = new MethodClassKey(method, targetObject.getClass());
        String ds = this.dsCache.get(cacheKey);
        if (ds == null) {
            ds = computeDatasource(method, targetObject);
            if (ds == null) {
                ds = "";
            }
            this.dsCache.put(cacheKey, ds);
        }
        return ds;
    }
    
    private String computeDatasource(Method method, Object targetObject) {
        if (allowedPublicOnly && !Modifier.isPublic(method.getModifiers())) {
            return null;
        }
        //1. 从当前方法接口中获取
        String dsAttr = findDataSourceAttribute(method);
        if (dsAttr != null) {
            return dsAttr;
        }
        Class<?> targetClass = targetObject.getClass();
        Class<?> userClass = ClassUtils.getUserClass(targetClass);
        // jdk代理时,  获取实现类的方法声明.  method: 接口的方法, specificMethod: 实现类方法
        Method specificMethod = ClassUtils.getMostSpecificMethod(method, userClass);
        specificMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
        //2. 从桥接方法查找
        dsAttr = findDataSourceAttribute(specificMethod);
        if (dsAttr != null) {
            return dsAttr;
        }
        // 从当前方法声明的类查找
        dsAttr = findDataSourceAttribute(userClass);
        if (dsAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return dsAttr;
        }
        //since 3.4.1 从接口查找,只取第一个找到的
        for (Class<?> interfaceClazz : ClassUtils.getAllInterfacesForClassAsSet(userClass)) {
            dsAttr = findDataSourceAttribute(interfaceClazz);
            if (dsAttr != null) {
                return dsAttr;
            }
        }
        // 如果存在桥接方法
        if (specificMethod != method) {
            // 从桥接方法查找
            dsAttr = findDataSourceAttribute(method);
            if (dsAttr != null) {
                return dsAttr;
            }
            // 从桥接方法声明的类查找
            dsAttr = findDataSourceAttribute(method.getDeclaringClass());
            if (dsAttr != null && ClassUtils.isUserLevelMethod(method)) {
                return dsAttr;
            }
        }
        return getDefaultDataSourceAttr(targetObject);
    }
    
    private String getDefaultDataSourceAttr(Object targetObject) {
        Class<?> targetClass = targetObject.getClass();
        // 如果不是代理类, 从当前类开始, 不断的找父类的声明
        if (!Proxy.isProxyClass(targetClass)) {
            Class<?> currentClass = targetClass;
            while (currentClass != Object.class) {
                String datasourceAttr = findDataSourceAttribute(currentClass);
                if (datasourceAttr != null) {
                    return datasourceAttr;
                }
                currentClass = currentClass.getSuperclass();
            }
        }
        // mybatis-plus, mybatis-spring 的获取方式
        if (mpEnabled) {
            final Class<?> clazz = getMapperInterfaceClass(targetObject);
            if (clazz != null) {
                String datasourceAttr = findDataSourceAttribute(clazz);
                if (datasourceAttr != null) {
                    return datasourceAttr;
                }
                // 尝试从其父接口获取
                return findDataSourceAttribute(clazz.getSuperclass());
            }
        }
        return null;
    }
    
    private Class<?> getMapperInterfaceClass(Object target) {
        Object current = target;
        while (Proxy.isProxyClass(current.getClass())) {
            Object currentRefObject = AopProxyUtils.getSingletonTarget(current);
            if (currentRefObject == null) {
                break;
            }
            current = currentRefObject;
        }
        try {
            if (Proxy.isProxyClass(current.getClass())) {
                return (Class<?>) mapperInterfaceField.get(Proxy.getInvocationHandler(current));
            }
        } catch (IllegalAccessException ignore) {
        }
        return null;
    }
    
    private String findDataSourceAttribute(AnnotatedElement ae) {
        AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ae, DS.class);
        if (attributes != null) {
            return attributes.getString("value");
        }
        return null;
    }
}

ThreadLocal

public final class DynamicDataSourceContextHolder {
    
    private static final ThreadLocal<Deque<String>> LOOKUP_KEY_HOLDER = new NamedThreadLocal<Deque<String>>("dynamic-datasource") {
        @Override
        protected Deque<String> initialValue() {
            return new ArrayDeque<>();
        }
    };
    private DynamicDataSourceContextHolder() {
    }
    
    public static String peek() {
        return LOOKUP_KEY_HOLDER.get().peek();
    }
    
    public static String push(String ds) {
        String dataSourceStr = StringUtils.isEmpty(ds) ? "" : ds;
        LOOKUP_KEY_HOLDER.get().push(dataSourceStr);
        return dataSourceStr;
    }
    
    public static void poll() {
        Deque<String> deque = LOOKUP_KEY_HOLDER.get();
        deque.poll();
        if (deque.isEmpty()) {
            LOOKUP_KEY_HOLDER.remove();
        }
    }
    
    public static void clear() {
        LOOKUP_KEY_HOLDER.remove();
    }
}

启动类配置

引入我们写的自动配置类,排除 ShardingJdbc 的自动配置类。

@SpringBootApplication(exclude = ShardingSphereAutoConfiguration.class)
@Import({DynamicDataSourceAutoConfiguration.class})
public class ShardingRunApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShardingRunApplication.class);
    }
}

最后,我们给之前写的 Repository 加上注解:

public interface BOrderRepository extends JpaRepository<BOrder,Long> {
    @DS("slave0")
    @Query(value = "SELECT * FROM (SELECT id,CASE WHEN company_id =1 THEN '小' WHEN company_id=4 THEN '中' ELSE '大' END AS com,user_id as userId FROM b_order0) t WHERE t.com ='中'",nativeQuery =true)
    List<Map<String, Object>> findOrders();
}

再次调用,查询成功!!!

以上就是使用注解解决ShardingJdbc不支持复杂SQL方法的详细内容,更多关于ShardingJdbc不支持复杂SQL的资料请关注编程网其它相关文章!

--结束END--

本文标题: 使用注解解决ShardingJdbc不支持复杂SQL方法

本文链接: https://www.lsjlt.com/news/167438.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 使用注解解决ShardingJdbc不支持复杂SQL方法
    目录背景介绍问题复现解决思路具体实现编写autoConfig 类自定义数据源aop 拦截器:aop 切面定义ThreadLocal启动类配置背景介绍 公司最近做分库分表业务,接入了 ...
    99+
    2024-04-02
  • php不支持mysqli的解决方法
    本文将为大家详细介绍“php不支持mysqli的解决方法”,内容步骤清晰详细,细节处理妥当,而小编每天都会更新不同的知识点,希望这篇“php不支持mysqli的解决方法”能够给你意想不到的收获,请大家跟着小编的思路慢慢深入,具体内容如下,一...
    99+
    2023-06-06
  • phpstrom支持注解的方法
    小编给大家分享一下phpstrom支持注解的方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!php有什么用php是一个嵌套的缩写名称,是英文超级文本预处理语言,...
    99+
    2023-06-06
  • 浏览器不支持javascript的解决方法
    这篇文章主要介绍浏览器不支持javascript的解决方法,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!浏览器不支持javascript的解决办法:首先打开【Internet选项】对话框,选择【安全】选项卡;然后单击...
    99+
    2023-06-14
  • 复杂SQL查询跑不动以及DRDS 只读的解决方法
    复杂SQL查询跑不动以及DRDS 只读的解决方法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。背景在实际业务生产环境中,业务应用系统在使用...
    99+
    2024-04-02
  • mysql不支持group by的解决方法小结
    下载安装的是最新版的mysql5.7.x版本,默认是开启了 only_full_group_by 模式的,但开启这个模式后,原先的 group by 语句就报错,然后又把它移除了。 一旦开启 only_fu...
    99+
    2024-04-02
  • ie浏览器不支持javascript的解决方法
    小编给大家分享一下ie浏览器不支持javascript的解决方法,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!ie浏览器不支持javascript的方法:首先打开...
    99+
    2023-06-14
  • ph中gd库不支持jpg的解决方法
    这篇文章主要介绍了ph中gd库不支持jpg的解决方法,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。php是什么语言php,一个嵌套的缩写名称,是英文超级文本预处理语言(PHP...
    99+
    2023-06-14
  • Ubuntu14.04不支持U盘exfat格式的解决方法
    这篇文章主要讲解了“Ubuntu14.04不支持U盘exfat格式的解决方法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Ubuntu14.04不支持U盘exfat格式的解决方法”吧!  很...
    99+
    2023-06-13
  • vscode不支持es6语法如何解决
    这篇文章主要讲解了“vscode不支持es6语法如何解决”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“vscode不支持es6语法如何解决”吧! ...
    99+
    2024-04-02
  • CMS 移动应用支持:解决您的疑难杂症
    故障诊断和解决 应用崩溃或卡住:检查是否安装了最新版本。清除缓存和数据或重新安装应用。联系开发人员以解决潜在错误。 登录问题:确保输入正确的凭证。检查互联网连接。尝试重置密码或联系管理员。 内容显示不正确:刷新页面。检查内容是否已发布。...
    99+
    2024-04-03
    CMS移动应用支持:解决您的疑难杂症
  • 解决BeanUtils.copyProperties不支持复制集合的问题
    工作中,经常使用Spring的工具类BeanUtils.copyProperties对bean属性进行复制,这里的复制属于浅复制。且不能复制集合和数组。本文会对该工具进行一些测试。 ...
    99+
    2024-04-02
  • fileinfo 模块 no 检测不通过不支持fileinfo解决方法
    函数|扩展fileinfo不支持的解决方法  在建站的时候,安装CMS时显示缺少fileinfo插件,记录一下。 一,我用的是宝塔软件 我们就以宝塔为例 【1】打开宝塔面板找到 软件管理   【2】选择你安装的PHP,打开设置选择你安...
    99+
    2023-09-21
    php 开发语言
  • 如何解决JavaScript find方法不支持IE的问题
    小编给大家分享一下如何解决JavaScript find方法不支持IE的问题,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!最近在...
    99+
    2024-04-02
  • idea中es6语法不支持如何解决
    这篇文章主要介绍“idea中es6语法不支持如何解决”,在日常操作中,相信很多人在idea中es6语法不支持如何解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”idea中e...
    99+
    2024-04-02
  • mysql5.6解析JSON字符串方式(支持复杂的嵌套格式)
    目录mysql5.6 解析JSON字符串支持复杂的嵌套格式mysql5.6及以下解析json方法先说一下问题的背景下面是对应的代码 mysql5.6 解析JSON字符串 支...
    99+
    2024-04-02
  • windows对象不支持此属性或方法如何解决
    今天小编给大家分享一下windows对象不支持此属性或方法如何解决的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。解决方法:首...
    99+
    2023-06-30
  • win7对象不支持此属性或方法怎么解决
    本篇内容主要讲解“win7对象不支持此属性或方法怎么解决”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“win7对象不支持此属性或方法怎么解决”吧!解决win7对象不支持此属性或方法的步骤打开浏览...
    99+
    2023-07-01
  • win7不支持GIF图片打开的两种解决方法
      其实这个真和Win7没关系,在XP的时代,GIF所用的LZW压缩算法是Compuserv所开发的一种免费算法。但是后来,GIF文件所采用的压缩算法忽然成了Unisys公司的专利,如果要开发生成(或显示)GIF文件的程...
    99+
    2023-05-29
    win7 GIF图片 解决 图片 GIF 方法
  • vscode不支持nvue语法高亮如何解决
    本篇内容介绍了“vscode不支持nvue语法高亮如何解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!问题描述例如:vscode中使用*....
    99+
    2023-07-05
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作