2024-09-04



import com.github.luben.zstd.Zstd;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import tech.tablesaw.api.Table;
import tech.tablesaw.io.csv.CsvReadOptions;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
 
public class TablesawExample {
 
    private final ResourceLoader resourceLoader;
 
    public TablesawExample(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }
 
    public Table readCompressedCsvTable(String compressedCsvResourcePath) throws IOException {
        Resource resource = resourceLoader.getResource("classpath:" + compressedCsvResourcePath);
        byte[] decompressedBytes = decompressBytes(Files.readAllBytes(resource.getFile().toPath()));
        InputStream inputStream = new ByteArrayInputStream(decompressedBytes);
        return Table.read().csv(inputStream, CsvReadOptions.builder('|'));
    }
 
    private byte[] decompressBytes(byte[] compressedBytes) {
        return Zstd.decompress(compressedBytes);
    }
}

这段代码展示了如何使用Tablesaw库来读取经过压缩的CSV文件。首先,我们通过Spring的ResourceLoader来获取资源文件。然后,我们使用Zstd库来解压文件内容,并将解压后的数据作为输入流来读取CSV文件,最终得到一个Tablesaw的Table对象。这个例子简单地展示了如何在Spring Boot项目中集成并使用tablesaw进行数据处理。

2024-09-04

以下是一个简化版的Spring Cloud Alibaba微服务架构示例,包含了Nacos作为服务注册与发现,Seata用于分布式事务管理,RocketMQ用于消息队列,以及Feign和Gateway用于服务间通信和路由。

  1. 创建一个Spring Boot项目作为parent pom,包含以下依赖:



<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.1.RELEASE</version>
    <relativePath/>
</parent>
 
<properties>
    <java.version>1.8</java.version>
    <spring-cloud.version>Hoxton.SR5</spring-cloud.version>
    <spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>
 
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
  1. 创建微服务模块,例如service-provider,并添加以下依赖:



<dependencies>
    <!-- Nacos Discovery -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!-- Seata for distributed transaction -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
    </dependency>
    <!-- RocketMQ -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-rocketmq</artifactId>
    </dependency>
    <!-- Feign for service to service call -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>
  1. service-provider中配置Nacos作为服务注册中心,Seata作为分布式事务管理器,RocketMQ用于消息通信,并定义Feign客户端用于服务间调用。



@EnableDiscoveryClient
@EnableFeignClients
2024-09-04



@Configuration
public class DataSourceConfig {
 
    @Bean
    public DataSource dataSource() {
        // 创建动态数据源对象
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
 
        // 配置默认数据源
        dynamicDataSource.setDefaultTargetDataSource(primaryDataSource());
 
        // 配置多数据源
        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put("primary", primaryDataSource());
        dataSourceMap.put("secondary", secondaryDataSource());
 
        // 设置数据源映射
        dynamicDataSource.setTargetDataSources(dataSourceMap);
 
        // 返回动态数据源
        return dynamicDataSource;
    }
 
    @Bean
    @Primary
    public DataSource primaryDataSource() {
        // 配置并返回主数据源
        return DataSourceBuilder.create().build();
    }
 
    @Bean
    public DataSource secondaryDataSource() {
        // 配置并返回次数据源
        return DataSourceBuilder.create().build();
    }
}
 
// 使用时,可以通过以下方式切换数据源
DynamicDataSourceContextHolder.setDataSourceType("secondary");
// 执行数据库操作
DynamicDataSourceContextHolder.clearDataSourceType();

这个配置类定义了两个数据源,一个主数据源和一个次数据源,并通过DynamicDataSource类实现了数据源的切换。在实际使用时,可以通过DynamicDataSourceContextHolder.setDataSourceType()方法切换到不同的数据源,执行数据库操作后,记得清除数据源类型以避免影响后续操作。

2024-09-04



package org.springframework.cloud.gateway.handler.predicate;
 
import org.springframework.cloud.gateway.support.ConfigurationUtils;
import org.springframework.core.style.ExpressionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
 
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
 
@Validated
public class QueryRoutePredicateFactory extends AbstractRoutePredicateFactory<QueryRoutePredicateFactory.Config> {
    public static final String QUERY_KEY = "key";
 
    public QueryRoutePredicateFactory() {
        super(Config.class);
    }
 
    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList(QUERY_KEY);
    }
 
    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange -> {
            // 获取请求参数
            MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
            // 检查请求参数中是否包含配置的key
            boolean hasKey = queryParams.containsKey(config.key);
            // 根据配置判断是否应该断言为真
            if (config.present) {
                return hasKey;
            } else {
                return !hasKey;
            }
        };
    }
 
    public static class Config {
        @NotEmpty
        private String key;
 
        private boolean present = true;
 
        public String getKey() {
            return key;
        }
 
        public void setKey(String key) {
            this.key = key;
        }
 
        public boolean isPresent() {
            return present;
        }
 
        public void setPresent(boolean present) {
            this.present = present;
        }
    }
}

这个代码示例展示了如何实现一个自定义的路由断言工厂,用于检查HTTP请求中是否存在特定的查询参数。如果参数存在,断言结果为真;如果参数不存在,可以根据配置决定断言为真还是为假。这个例子中的工厂类名为QueryRoutePredicateFactory,它继承自抽象的路由断言工厂基类AbstractRoutePredicateFactory<T>,并使用了Config内部类来定义配置属性。这个例子展示了如何使用Java 8的函数式编程特性来实现路由断言。

2024-09-04

在Spring Boot项目中整合阿里云OSS实现云存储,你需要按照以下步骤操作:

  1. 添加阿里云OSS依赖到你的pom.xml文件中:



<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.6.0</version>
</dependency>
  1. application.propertiesapplication.yml中配置你的阿里云OSS信息:



# 阿里云OSS配置
aliyun.oss.endpoint=你的EndPoint
aliyun.oss.accessKeyId=你的AccessKeyId
aliyun.oss.accessKeySecret=你的AccessKeySecret
aliyun.oss.bucketName=你的BucketName
  1. 创建配置类,用于读取配置文件中的OSS信息:



@Configuration
public class OssConfig {
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
 
    @Value("${aliyun.oss.accessKeyId}")
    private String accessKeyId;
 
    @Value("${aliyun.oss.accessKeySecret}")
    private String accessKeySecret;
 
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;
 
    @Bean
    public OSS ossClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }
}
  1. 创建服务类,用于封装OSS操作:



@Service
public class OssService {
    @Autowired
    private OSS ossClient;
 
    public String uploadFile(MultipartFile file) {
        String fileName = file.getOriginalFilename();
        try (InputStream inputStream = file.getInputStream()) {
            ossClient.putObject(bucketName, fileName, inputStream);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        return "https://" + bucketName + "." + endpoint + "/" + fileName;
    }
}
  1. 在你的Controller中使用OssService上传文件:



@RestController
public class FileUploadController {
    @Autowired
    private OssService ossService;
 
    @PostMapping("/upload")
    public String uploadFile(@RequestParam("file") MultipartFile file) {
        return ossService.uploadFile(file);
    }
}

以上代码实现了文件上传至阿里云OSS的功能。你需要替换配置文件中的你的EndPoint你的AccessKeyId你的AccessKeySecret你的BucketName为你自己的阿里云OSS信息。

2024-09-04



import redis.clients.jedis.JedisSentinelPool;
 
public class RedisSentinelExample {
    public static void main(String[] args) {
        String masterName = "mymaster"; // Redis主服务器名称
        Set<String> sentinels = new HashSet<String>(); // 哨兵服务器集合
        sentinels.add("sentinel1-host:26379");
        sentinels.add("sentinel2-host:26379");
        sentinels.add("sentinel3-host:26379");
 
        JedisSentinelPool sentinelPool = new JedisSentinelPool(masterName, sentinels);
 
        try (Jedis jedis = sentinelPool.getResource()) {
            // 使用jedis连接到Redis哨兵,进行操作
            jedis.set("key", "value");
            System.out.println(jedis.get("key"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这段代码使用了Jedis客户端库中的JedisSentinelPool来创建一个Redis哨兵连接池。通过哨兵池获取的Jedis实例可以用来执行Redis命令,无需手动维护主节点的IP和端口。当主节点发生变更时,哨兵模式会自动将从节点晋升为主节点,并通知客户端新的主节点地址。

2024-09-04

开源微服务选型通常会考虑以下因素:

  1. 服务发现和注册:

    • Spring Cloud 使用Eureka。
    • Dubbo 使用Zookeeper。
    • Istio 使用Envoy。
  2. 负载均衡:

    • Spring Cloud 使用Ribbon。
    • Dubbo 使用内置负载均衡。
    • gRPC 使用内置的负载均衡。
  3. 服务间调用方式:

    • Spring Cloud 使用Feign或RestTemplate。
    • Dubbo 使用其自定义TCP通讯协议。
    • gRPC 使用HTTP/2。
  4. 服务网格支持:

    • Spring Cloud 不直接支持Istio。
    • Dubbo 不支持Istio。
    • gRPC 支持Istio。
  5. 分布式跟踪和监控:

    • Spring Cloud Sleuth 集成了Zipkin和Brave。
    • Dubbo 集成了阿里巴巴自研分布式跟踪系统。
    • gRPC 集成了Google的OpenCensus。
  6. 开发语言:

    • Spring Cloud 主要使用Java。
    • Dubbo 支持Java和其他语言。
    • gRPC 主要支持Java,同时有其他语言的API。
  7. 学习曲线:

    • Spring Cloud 相对较高,需要深入了解Spring生态。
    • Dubbo 相对较简单,适合Java开发者。
    • gRPC 和Istio 对于开发者要求较高,需要熟悉HTTP/2和Protocol Buffers。
  8. 文档和社区支持:

    • Spring Cloud 文档丰富,社区活跃。
    • Dubbo 文档和社区支持较为稳定。
    • gRPC 和Istio 都是Google在维护,文档和社区活跃。

选择开源微服务框架时,需要根据项目需求、团队技术栈和长期维护计划来权衡这些因素。例如,如果你的团队更熟悉Java,可能会偏好Dubbo;如果你需要服务网格的功能,可能会考虑Istio;如果你需要与Google生态系统集成,可能会选择gRPC。

具体选型时,可以考虑以下代码示例比较:




// Spring Cloud 使用Feign进行服务调用
@FeignClient(name = "service-provider")
public interface ServiceProviderClient {
    @GetMapping("/api/resource")
    String getResource();
}
 
// Dubbo 使用注解标记服务提供方
@Service
@DubboService(version = "1.0.0")
public class ServiceImpl implements Service {
    // ...
}
 
// gRPC 使用protocol buffers定义服务
// 服务定义(.proto文件)
syntax = "proto3";
 
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}
 
message HelloRequest {
  string name = 1;
}
 
message HelloReply {
  string message = 1;
}

每种框架都有自己的使用场景和优势,没有最好的框架,只有最适合的。选择时应考虑当前和未来的需求,以及团队的技术能力。

2024-09-04

在这个例子中,我们将使用Redis和JWT来实现一账N地登录。

首先,我们需要安装Redis和相关的Go包:




go get -u github.com/go-redis/redis/v8
go get -u github.com/golang-jwt/jwt/v4

以下是核心函数的实现:




package main
 
import (
    "context"
    "crypto/rand"
    "crypto/rsa"
    "encoding/json"
    "fmt"
    "github.com/go-redis/redis/v8"
    "github.com/golang-jwt/jwt/v4"
    "log"
    "time"
)
 
var (
    redisClient *redis.Client
    privateKey  *rsa.PrivateKey
    publicKey   rsa.PublicKey
)
 
type UserInfo struct {
    Username string `json:"username"`
    Password string `json:"password"`
}
 
type Payload struct {
    jwt.Payload
    UserInfo
}
 
func init() {
    redisClient = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
 
    // 生成私钥和公钥
    var err error
    privateKey, err = rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        log.Fatalf("failed to generate private key: %v", err)
    }
    publicKey = privateKey.PublicKey
}
 
func createToken(userInfo UserInfo) (string, error) {
    payload := Payload{
        Payload: jwt.NewWithConfig(jwt.GetSigningMethod("RS256"), jwt.StandardClaims{
            Issuer:    "example",
            Subject:   userInfo.Username,
            IssuedAt:  time.Now(),
            ExpiresAt: time.Now().Add(time.Hour * 24), // 设置token有效期1天
        }),
        UserInfo: userInfo,
    }
 
    token, err := jwt.Sign(&payload, jwt.NewRS256(privateKey))
    if err != nil {
        return "", err
    }
    return token, nil
}
 
func login(username, password string) (string, error) {
    ctx := context.Background()
    userKey := fmt.Sprintf("user:%s", username)
    userInfoBytes, err := redisClient.Get(ctx, userKey).Bytes()
    if err != nil {
        return "", err
    }
 
    var userInfo UserInfo
    if err := json.Unmarshal(userInfoBytes, &userInfo); err != nil {
        return "", err
    }
 
    if userInfo.Password != password {
        return "", fmt.Errorf("invalid password")
    }
 
    token, err := createToken(userInfo)
    if err != nil {
        return "", err
    }
 
    return token, nil
}
 
func main() {
    // 假设用户已经在其他地方登录,并且我们已经有了他们的凭证
    token, err := login("user1", "password123")
    if err != nil {
        log.Fatalf("login failed: %v", err)
    }
    fmt.Printf("Token: %s\n", token)
}

在这个例子中,我们首先初始化了Redis客户端,并生成了一对私钥和公钥,用于签发JWT。然后我们实现了createToken函数,它使用私钥来签发JWT。login函数会从Redis中获取用户信息,验证密码,如果验证通过,则为用户签发新的Tok

2024-09-04

在Oracle RAC环境中,移动本地数据文件到ASM磁盘组通常涉及以下步骤:

  1. 确保数据文件不在使用中。
  2. 将数据文件移动到一个共享的文件系统或网络位置。
  3. 在每个RAC节点上,使用sqlplus登录到数据库,然后执行以下步骤:

    • 将数据文件移动到ASM磁盘组。
    • 更新数据库中的数据文件位置。

以下是一个简化的示例代码,展示如何将本地数据文件迁移到ASM磁盘组:




-- 假设数据文件是users.dbf,在RAC节点1上执行
 
-- 1. 断开数据文件
ALTER DATABASE DATAFILE '/path/to/users.dbf' OFFLINE;
 
-- 2. 使用操作系统命令移动数据文件到共享存储位置
-- 这一步需要在所有RAC节点上执行
 
-- 3. 在RAC节点1上,使用DBMS_FILE_TRANSFER包将文件移动到ASM磁盘组
EXEC DBMS_FILE_TRANSFER.COPY_FILE('+ASMDISKGROUP', 'new_users.dbf', '/path/to/users.dbf');
 
-- 4. 更新数据库中的数据文件指向新的位置
ALTER DATABASE RENAME FILE '/path/to/users.dbf' TO '+ASMDISKGROUP/new_users.dbf';
 
-- 5. 使用以下命令将数据文件联机
ALTER DATABASE DATAFILE '+ASMDISKGROUP/new_users.dbf' ONLINE;
 
-- 注意:确保在执行这些操作之前已经备份了数据库,并且所有的数据文件都不再处于活跃状态。

请注意,这个示例假设你已经有一个配置好的ASM磁盘组,并且数据文件是处于OFFLINE状态的,以便可以进行迁移。实际操作中,你需要根据自己的Oracle RAC环境和数据库配置进行相应的调整。

2024-09-04



import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import reactor.core.publisher.Flux;
 
@SpringBootApplication
public class ReactorApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(ReactorApplication.class, args);
 
        Flux<String> stringFlux = Flux.just("Hello", "World", "!");
        stringFlux.subscribe(System.out::println);
    }
}

这段代码演示了如何在Spring Boot应用程序中创建一个简单的Reactor Flux并订阅它来打印发出的字符串。这是一个基本的Reactor模型应用,对于学习如何在Spring Boot中使用反应式编程非常有帮助。