sa-token 配置自定义 Token 前缀后cookie无效问题

自定义 Token 前缀
需求场景

在某些系统中,前端提交token时会在前面加个固定的前缀,例如:

{

"satoken": "Bearer xxxx-xxxx-xxxx-xxxx"

}

此时后端如果不做任何特殊处理,框架将会把Bearer 视为token的一部分,无法正常读取token信息,导致鉴权失败。

为此,我们需要在yml中添加如下配置:

sa-token:

# token前缀
token-prefix: Bearer

此时 Sa-Token 便可在读取 Token 时裁剪掉 Bearer,成功获取xxxx-xxxx-xxxx-xxxx。

注意点

Token前缀 与 Token值 之间必须有一个空格。
一旦配置了 Token前缀,则前端提交 Token 时,必须带有前缀,否则会导致框架无法读取 Token。
由于Cookie中无法存储空格字符,所以配置 Token 前缀后,Cookie 模式将会失效,此时只能将 Token 提交到header里进行传输。

@EnableDubbo @SpringBootApplication

  1. @EnableDubbo

@EnableDubbo 是 Apache Dubbo 提供的注解,用来启用 Dubbo 的功能。它告诉 Spring 容器扫描并加载 Dubbo 的相关组件,包括 服务提供者 和 服务消费者。
作用

启用 Dubbo 自动配置:加载 Dubbo 的核心功能,比如注册服务、消费服务等。
扫描指定包路径下的 Dubbo 注解:
    @DubboService:标记服务提供者(即要暴露给其他服务的接口实现)。
    @DubboReference:标记服务消费者(即调用远程接口)。

常见使用

@EnableDubbo
@SpringBootApplication
public class DubboApplication {

public static void main(String[] args) {
    SpringApplication.run(DubboApplication.class, args);
}

}

功能细节

默认扫描主类(@SpringBootApplication 所在类)所在包及其子包中的 Dubbo 注解。
可以指定扫描路径:

@EnableDubbo(scanBasePackages = "com.example.dubbo.service")
@SpringBootApplication
public class DubboApplication {
    public static void main(String[] args) {
        SpringApplication.run(DubboApplication.class, args);
    }
}
  1. @SpringBootApplication

@SpringBootApplication 是 Spring Boot 提供的核心注解,用于标记一个 Spring Boot 应用的启动类。
作用

标记应用入口:这是 Spring Boot 应用的启动类(即 main 方法所在类)。
启用 Spring Boot 自动配置:
    自动加载与应用相关的 Spring 配置(如 DataSource, JPA, RestController)。
    简化开发,无需手动配置 XML 或 Java 配置类。
组件扫描:
    默认扫描当前类所在包及其子包中的所有组件(如 @Controller, @Service, @Component 等)。

注解组合

@SpringBootApplication 是以下 3 个注解的组合:

@Configuration:声明当前类是一个配置类。
@EnableAutoConfiguration:启用 Spring Boot 的自动配置机制。
@ComponentScan:扫描当前包及子包中的组件。

常见使用

@SpringBootApplication
public class MyApplication {

public static void main(String[] args) {
    SpringApplication.run(MyApplication.class, args);
}

}

  1. @EnableDubbo + @SpringBootApplication 的联合使用

当 @EnableDubbo 和 @SpringBootApplication 一起使用时:

@SpringBootApplication:
    加载 Spring Boot 的核心配置。
    提供一个运行时环境(包括 Web 服务、配置文件加载等)。
@EnableDubbo:
    启用 Dubbo 的服务注册与发现功能。
    自动扫描并加载 Dubbo 的服务提供者和消费者。
  1. 示例
    服务提供者

@EnableDubbo
@SpringBootApplication
public class ProviderApplication {

public static void main(String[] args) {
    SpringApplication.run(ProviderApplication.class, args);
}

}

@DubboService(version = "1.0.0") // 暴露 Dubbo 服务
public class HelloServiceImpl implements HelloService {

@Override
public String sayHello(String name) {
    return "Hello, " + name;
}

}

服务消费者

@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {

public static void main(String[] args) {
    SpringApplication.run(ConsumerApplication.class, args);
}

}

@RestController
public class HelloController {

@DubboReference(version = "1.0.0") // 引用 Dubbo 服务
private HelloService helloService;

@GetMapping("/sayHello")
public String sayHello(@RequestParam String name) {
    return helloService.sayHello(name);
}

}

配置示例

application.yml 配置 Dubbo:

dubbo:
application:

name: dubbo-example

registry:

address: zookeeper://127.0.0.1:2181

provider:

timeout: 3000

consumer:

check: false

总结

@EnableDubbo:启用 Dubbo 的功能,负责加载和扫描 Dubbo 的相关组件。
@SpringBootApplication:Spring Boot 的核心注解,用来加载 Spring 的配置,构建运行时环境。
两者结合使用,可以快速启动一个基于 Spring Boot 的 Dubbo 分布式服务应用。

您说:
为啥有些启动类不加@EnableDubbo也能被注册和发现
ChatGPT 说:
ChatGPT

有些启动类在没有显式加 @EnableDubbo 的情况下,仍然能注册和发现 Dubbo 服务,通常是因为以下原因:

  1. Spring Boot 和 Dubbo 自动配置

Spring Boot 和 Dubbo 集成时,使用了 Spring Boot Starter 和 自动配置机制。当项目中引入了 Dubbo 的 Starter 依赖(如 dubbo-spring-boot-starter),以下机制会自动启用 Dubbo 的功能:
关键点

Dubbo 的自动配置类:
    org.apache.dubbo.config.spring.context.annotation.DubboComponentScanRegistrar
    org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor

这些自动配置类会扫描项目中的 Dubbo 注解(如 @DubboService, @DubboReference),并注册相关的服务。
触发条件

当 dubbo-spring-boot-starter 被引入,Spring Boot 的自动配置机制会:

加载 DubboAutoConfiguration 类。
自动启用 Dubbo 的核心功能,包括注册服务和消费服务。

结论

因此,即使没有手动加 @EnableDubbo,Dubbo 也可以通过 Spring Boot 的自动配置机制正常工作。

  1. Spring Boot 主类默认扫描包

在 Spring Boot 应用中,@SpringBootApplication 默认会扫描当前类所在包及其子包中的所有组件,包括标注了 Dubbo 注解的类,例如:

@DubboService:Dubbo 服务提供者。
@DubboReference:Dubbo 服务消费者。

如果 Dubbo 注解的类位于默认扫描路径中,Spring Boot 会自动发现并加载它们,无需额外配置。

  1. 隐式加载 Dubbo 的功能

一些情况下,Dubbo 的功能被隐式加载:

spring.factories 机制:
    Dubbo 的 Spring Boot Starter 通过 spring.factories 文件,加载自动配置类。
    例如,DubboAutoConfiguration 类会隐式启用 Dubbo 的相关功能。
@DubboService 或 @DubboReference 自动触发:
    这些注解本身会触发服务注册和发现,即使没有显式使用 @EnableDubbo。
  1. 使用了默认的注解扫描路径

在没有 @EnableDubbo 的情况下,Dubbo Starter 默认会扫描主类所在包和子包中包含的注解(如 @DubboService, @DubboReference),因此:

如果启动类所在包路径和服务类一致,Dubbo 的注解会被正常加载。
  1. @EnableDubbo 作用的补充说明

虽然 @EnableDubbo 显式启用 Dubbo 的注解扫描,但在以下情况下可以省略:

项目中已经引入了 dubbo-spring-boot-starter,并且默认扫描路径包含了服务类。
没有特殊的扫描路径需求(如需要扫描其他非默认包路径的 Dubbo 注解)。

但加上 @EnableDubbo 的好处是:

明确表明应用依赖 Dubbo 功能。
可以自定义扫描路径,例如:

@EnableDubbo(scanBasePackages = "com.example.services")
@SpringBootApplication
public class MyApplication {
    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

如何确认 Dubbo 的自动配置生效?

可以通过以下方法确认 Dubbo 的自动配置是否生效:

查看 Spring Boot 日志:
    如果 Dubbo 的自动配置生效,启动时日志会显示类似:

[Dubbo] The Dubbo application [xxx] has been registered to Zookeeper [127.0.0.1:2181]

启用 Debug 日志: 在 application.yml 中启用自动配置调试:

logging:
  level:
    org.apache.dubbo: DEBUG
    org.springframework.boot.autoconfigure: DEBUG

排查自动装配状态: 使用 Spring Boot 的 ConditionEvaluationReport 检查自动装配状态:
    启动项目后访问 /actuator/conditions。
    查看 DubboAutoConfiguration 是否为 matched。

总结

没有显式加 @EnableDubbo 的启动类能正常工作,是因为 Spring Boot 自动配置机制 和 Dubbo Starter 的自动装配。
如果服务类或消费者类位于默认扫描路径中,Dubbo 会自动扫描并加载它们。
建议:显式加上 @EnableDubbo 可以增强可读性,尤其是需要自定义扫描路径时。

上传本地文件夹上传到minio

import os
import boto3
from mimetypes import guess_type

# MinIO 配置信息
MINIO_ENDPOINT = "http://xx:9000"  # MinIO 地址
MINIO_ACCESS_KEY = "AT03z5LqjDlaJ4fjn"      # MinIO Access Key
MINIO_SECRET_KEY = "jOjWgkDhOc3zC8RgRYLzm4wKP3q4rnR"      # MinIO Secret Key
BUCKET_NAME = "lanyuancc"          # MinIO 中的桶名

# 本地文件夹路径
LOCAL_FOLDER = r"D:\Backup\Documents\Downloads\蓝源产城图片"  # 替换为本地文件夹路径

# 初始化 MinIO 客户端
s3_client = boto3.client(
    's3',
    endpoint_url=MINIO_ENDPOINT,
    aws_access_key_id=MINIO_ACCESS_KEY,
    aws_secret_access_key=MINIO_SECRET_KEY
)

def upload_to_minio(local_folder, bucket_name, target_directory=""):
    """
    将本地文件夹中的文件上传到 MinIO,支持指定桶内的文件夹路径

    :param local_folder: 本地文件夹路径
    :param bucket_name: MinIO 中的桶名称
    :param target_directory: MinIO 中目标文件夹路径(子目录,例如 "specific-directory")
    """
    for root, dirs, files in os.walk(local_folder):
        for file_name in files:
            local_path = os.path.join(root, file_name)  # 本地文件路径
            relative_path = os.path.relpath(local_path, local_folder)  # 相对路径
            
            # 在 MinIO 中的目标路径,加入指定目录前缀
            minio_path = os.path.join(target_directory, relative_path).replace("\\", "/")

            mime_type, _ = guess_type(local_path)  # 推测文件的 MIME 类型

            try:
                # 上传文件到 MinIO
                s3_client.upload_file(
                    local_path,
                    bucket_name,
                    minio_path,
                    ExtraArgs={"ContentType": mime_type or "application/octet-stream"}
                )
                print(f"上传成功: {minio_path}")
            except Exception as e:
                print(f"上传失败: {minio_path},错误: {e}")

# 调用函数进行文件上传
if __name__ == "__main__":
    # 修改 target_directory 为桶中希望的子目录路径
    target_directory = ""  # 示例子目录:上传到 BUCKET_NAME/specific-directory/
    upload_to_minio(LOCAL_FOLDER, BUCKET_NAME, target_directory)

springboot cloud项目数据库导出并新建数据库

使用步骤
安装依赖模块
确保安装 mysql-connector-python:

pip install mysql-connector-python

确保 mysqldump 和 mysql 命令可用
此脚本依赖于系统中安装的 mysqldump 和 mysql 命令。请确保它们已安装并配置在系统路径中。

配置参数
替换脚本中的 host、user、password、original_db 和 new_db 变量,使用你自己的数据库连接信息和数据库名称。

运行脚本:

    python clone_database.py

检查结果
脚本运行后,原始数据库的数据将被复制到一个新数据库中。

import mysql.connector
import subprocess
import os

def export_and_clone_databases(host, user, password, databases, suffix):
    try:
        # Step 1: 创建 MySQL 连接
        connection = mysql.connector.connect(
            host=host,
            user=user,
            password=password
        )
        cursor = connection.cursor()

        for original_db in databases:
            # Step 2: 检查原始数据库是否存在(使用反引号转义数据库名)
            cursor.execute(f"SHOW DATABASES LIKE '{original_db}';")
            if not cursor.fetchone():
                print(f"Error: Database '{original_db}' does not exist.")
                continue

            # Step 3: 创建新数据库(用反引号转义数据库名)
            new_db = f"{original_db}_{suffix}"
            cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{new_db}`;")
            print(f"New database '{new_db}' created successfully.")

            # Step 4: 使用 mysqldump 导出原数据库
            dump_file = f"{original_db}.sql"
           # dump_command = f"mysqldump -h {host} -u {user} -p{password} `{original_db}` > {dump_file}"
            dump_command = f'"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysqldump.exe" -h {host} -u {user} -p{password} {original_db} > {dump_file}'
            print(f"Executing: {dump_command}")
            subprocess.run(dump_command, shell=True, check=True)
            print(f"Database '{original_db}' exported to '{dump_file}'.")

            # Step 5: 导入 SQL 文件到新数据库
           # import_command = f"mysql -h {host} -u {user} -p{password} `{new_db}` < {dump_file}"
            import_command = f'"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysql.exe" -h {host} -u {user} -p{password} {new_db} < {dump_file}'

            print(f"Executing: {import_command}")
            subprocess.run(import_command, shell=True, check=True)
            print(f"Data imported into new database '{new_db}' successfully.")

            # Step 6: 删除临时文件(可选)
            if os.path.exists(dump_file):
                os.remove(dump_file)
                print(f"Temporary file '{dump_file}' deleted.")
    except mysql.connector.Error as err:
        print(f"Error: {err}")
    except subprocess.CalledProcessError as err:
        print(f"Command failed: {err}")
    finally:
        if connection.is_connected():
            cursor.close()
            connection.close()
            print("MySQL connection closed.")

# 使用示例
if __name__ == "__main__":
    host = "localhost"       # 数据库地址
    user = "root"            # 数据库用户名
    password = "xxx"    # 数据库密码

    # 定义需要复制的数据库列表
    databases = [
        "industry-xxx"
    ]

    # 新数据库的后缀
    suffix = "xxx"  # 可更改为需要的后缀

    export_and_clone_databases(host, user, password, databases, suffix)

linux 用脚本启动rocketmq控制台会被卡住

先给脚本设置权限
chmod +x manage-services.sh

#!/bin/bash

# 基础配置
SRC_DIR=/data/HH-Cloud  # 源码路径
WORK_DIR=/data/jar      # 工作目录
LOG_DIR=/data/scripts/logs      # 日志目录

# 服务脚本列表
SERVICE_SCRIPTS=(
    "xx-gateway"
)

# Nacos、RocketMQ 和 Seata 启动和停止脚本
NACOS_START_CMD="bash /data/nacos/bin/startup.sh -m standalone"
NACOS_STOP_CMD="bash /data/nacos/bin/shutdown.sh"
ROCKETMQ_START_CMD="nohup sh /data/rocketmq-5.2.0/bin/mqnamesrv > /data/logs/rocketmq_namesrv.log 2>&1 &"
ROCKETMQ_BROKER_CMD="nohup sh /data/rocketmq-5.2.0/bin/mqbroker -n 127.0.0.1:9876 > /data/logs/rocketmq_broker.log 2>&1 &"
ROCKETMQ_STOP_CMD="sh /data/rocketmq-5.2.0/bin/mqshutdown namesrv && sh /data/rocketmq-5.2.0/bin/mqshutdown broker"
SEATA_START_CMD="bash /data/seata-server-1.6.0/bin/seata-server.sh"
SEATA_STOP_CMD="bash /data/seata-server-1.6.0/bin/seata-server.sh stop"

OPTIONS="-Xms512m -Xmx512m -XX:+UseG1GC -XX:MaxGCPauseMillis=200"

# 确保目录存在
mkdir -p $WORK_DIR
mkdir -p $LOG_DIR

# 启动单个服务
start_service() {
    local SERVICE=$1
    local JAR_PATH=""
    local LOG_FILE=$LOG_DIR/$SERVICE.log
    local PID_FILE=/var/run/$SERVICE.pid

    echo "Starting $SERVICE..."

    # 查找 JAR 文件
    for DIR in "$SRC_DIR/hh-gateway/target" "$SRC_DIR/hh-modules/$SERVICE/target" "$SRC_DIR/hh-visual/$SERVICE/target" "$SRC_DIR/hh-auth/target"; do
        if [ -f "$DIR/$SERVICE.jar" ]; then
            JAR_PATH="$DIR/$SERVICE.jar"
            break
        fi
    done

    if [ -z "$JAR_PATH" ]; then
        echo "Error: JAR file for $SERVICE not found!"
        return
    fi

    # 检查是否已在运行
    if [ -f $PID_FILE ]; then
        PID=$(cat $PID_FILE)
        if kill -0 $PID 2>/dev/null; then
            echo "$SERVICE is already running (PID: $PID)"
            return
        else
            echo "Stale PID file found for $SERVICE. Removing it."
            rm -f $PID_FILE
        fi
    fi

    # 停止旧进程(如果未正确停止)
    OLD_PID=$(ps -ef | grep "$SERVICE".jar | grep -v grep | awk '{print $2}')
    if [ -n "$OLD_PID" ]; then
        echo "Stopping old instance of $SERVICE (PID: $OLD_PID)..."
        kill -9 $OLD_PID
        echo "Old instance stopped."
    fi

    # 启动新服务
    nohup java $OPTIONS -jar $JAR_PATH --spring.profiles.active=stage > $LOG_FILE 2>&1 &
    NEW_PID=$!
    echo $NEW_PID > $PID_FILE
    echo "$SERVICE started (PID: $NEW_PID)"
}

# 停止单个服务
stop_service() {
    local SERVICE=$1
    local PID_FILE=/var/run/$SERVICE.pid

    echo "Stopping $SERVICE..."

    if [ -f $PID_FILE ]; then
        PID=$(cat $PID_FILE)
        if kill -0 $PID 2>/dev/null; then
            kill -9 $PID
            rm -f $PID_FILE
            echo "$SERVICE stopped."
        else
            echo "PID $PID not valid. Removing stale PID file."
            rm -f $PID_FILE
        fi
    else
        echo "$SERVICE is not running."
    fi
}

# 查询单个服务状态
status_service() {
    local SERVICE=$1
    local PID_FILE=/var/run/$SERVICE.pid

    if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE) 2>/dev/null; then
        echo "$SERVICE is running (PID: $(cat $PID_FILE))"
    else
        echo "$SERVICE is not running."
    fi
}

# 启动 Nacos、RocketMQ、Seata 服务
start_nacos() {
    echo "Starting Nacos..."
    $NACOS_START_CMD &
    echo "Nacos started."
}

start_rocketmq() {
 echo "Starting RocketMQ..."

    # 启动 NameServer
    eval $ROCKETMQ_START_CMD
    sleep 2  # 稍等一下,确保 NameServer 启动

    # 启动 Broker
    eval $ROCKETMQ_BROKER_CMD
    sleep 2  # 稍等一下,确保 Broker 启动

    # 使用 disown 完全将进程与终端分离
    disown

    echo "RocketMQ started. Logs are available in /data/logs/rocketmq_namesrv.log and /data/logs/rocketmq_broker.log"
}

start_seata() {
    echo "Starting Seata..."
    $SEATA_START_CMD
    sleep 3  # 给 Seata 一些时间启动
    echo "Seata started."
}

# 停止 Nacos、RocketMQ、Seata 服务
stop_nacos() {
    echo "Stopping Nacos..."
    $NACOS_STOP_CMD
    echo "Nacos stopped."
}

stop_rocketmq() {
    echo "Stopping RocketMQ..."
    $ROCKETMQ_STOP_CMD
    echo "RocketMQ stopped."
}

stop_seata() {
    echo "Stopping Seata..."
    $SEATA_STOP_CMD
    echo "Seata stopped."
}

# 检查 Nacos 服务状态
status_nacos() {
    if pgrep -f "nacos-server.jar" > /dev/null; then
        echo "Nacos is running."
    else
        echo "Nacos is not running."
    fi
}

# 检查 RocketMQ 服务状态
status_rocketmq() {
    if pgrep -f "mqnamesrv" > /dev/null && pgrep -f "mqbroker" > /dev/null; then
        echo "RocketMQ is running."
    else
        echo "RocketMQ is not running."
    fi
}

# 检查 Seata 服务状态
status_seata() {
     if pgrep -f "seata-server" > /dev/null; then
        echo "Seata is running."
    else
        echo "Seata is not running."
    fi
}

# 检查参数
if [ $# -lt 1 ]; then
    echo "Usage: $0 <start|stop|status> [service_name]"
    exit 1
fi

ACTION=$1
SERVICE=$2

# 根据参数执行操作
case $ACTION in
    start)
        if [ -z "$SERVICE" ]; then
            # 启动所有服务
            start_nacos
            start_rocketmq
            start_seata
            for SERVICE in "${SERVICE_SCRIPTS[@]}"; do
                start_service $SERVICE
                sleep 2  # 确保每个服务有足够时间启动
            done
            echo "All services started."
        else
            # 启动指定服务
            if [[ "$SERVICE" == "nacos" ]]; then
                start_nacos
            elif [[ "$SERVICE" == "rocketmq" ]]; then
                start_rocketmq
            elif [[ "$SERVICE" == "seata" ]]; then
                start_seata
            elif [[ " ${SERVICE_SCRIPTS[@]} " =~ " ${SERVICE} " ]]; then
                start_service $SERVICE
            else
                echo "Invalid service name: $SERVICE"
                exit 1
            fi
        fi
        ;;
    stop)
        if [ -z "$SERVICE" ]; then
            # 停止所有服务
            stop_nacos
            stop_rocketmq
            stop_seata
            for SERVICE in "${SERVICE_SCRIPTS[@]}"; do
                stop_service $SERVICE
            done
            echo "All services stopped."
        else
            # 停止指定服务
            if [[ "$SERVICE" == "nacos" ]]; then
                stop_nacos
            elif [[ "$SERVICE" == "rocketmq" ]]; then
                stop_rocketmq
            elif [[ "$SERVICE" == "seata" ]]; then
                stop_seata
            elif [[ " ${SERVICE_SCRIPTS[@]} " =~ " ${SERVICE} " ]]; then
                stop_service $SERVICE
            else
                echo "Invalid service name: $SERVICE"
                exit 1
            fi
        fi
        ;;
    status)
        if [ -z "$SERVICE" ]; then
            # 查询所有服务状态
            echo "Checking Nacos status..."
            status_nacos
            echo "Checking RocketMQ status..."
            status_rocketmq
            echo "Checking Seata status..."
            status_seata
            for SERVICE in "${SERVICE_SCRIPTS[@]}"; do
                status_service $SERVICE
            done
        else
            # 查询指定服务状态
            if [[ "$SERVICE" == "nacos" ]]; then
                status_nacos
            elif [[ "$SERVICE" == "rocketmq" ]]; then
                status_rocketmq
            elif [[ "$SERVICE" == "seata" ]]; then
                status_seata
            elif [[ " ${SERVICE_SCRIPTS[@]} " =~ " ${SERVICE} " ]]; then
                status_service $SERVICE
            else
                echo "Invalid service name: $SERVICE"
                exit 1
            fi
        fi
        ;;
    *)
        echo "Invalid action: $ACTION. Use 'start', 'stop', or 'status'."
        exit 1
        ;;
esac