0%

安装

yum install keepalived -y

编写检查端口是否通的脚本

#!/bin/bash
CHK_PORT=$1
if [ -n "$CHK_PORT" ];then
        PORT_PROCESS=`ss -lnt|grep $CHK_PORT|wc -l`
        if [ $PORT_PROCESS -eq 0 ];then
                echo "Port $CHK_PORT Is Not Used,End."
                exit 1
        fi
else
        echo "Check Port Cant Be Empty!"
fi

测试:./check_port.sh 8080检测8080端口是否开通.

keepalived主节点配置

! Configuration File for keepalived

global_defs {
   router_id 20.153
}

vrrp_script chk_port {
    script "/etc/keepalived/check_port.sh 8080"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state MASTER
    interface ens192
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    unicast_src_ip 192.168.1.1  # 本地节点的 IP 地址
    unicast_peer {
        192.168.1.2  # 另一个节点的 IP 地址
        192.168.1.3  # 另一个节点的 IP 地址
    }
    virtual_ipaddress {
        172.16.20.99
    }
}

keepalived备节点配置

! Configuration File for keepalived

global_defs {
   router_id 20.154
}

vrrp_script chk_port {
    script "/etc/keepalived/check_port.sh 8080"
    interval 2
    weight -20
}

vrrp_instance VI_1 {
    state BACKUP
    interface ens192
    virtual_router_id 51
    priority 90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    unicast_src_ip 192.168.1.2  # 本地节点的 IP 地址
    unicast_peer {
        192.168.1.1  # 另一个节点的 IP 地址
        192.168.1.3  # 另一个节点的 IP 地址
    }
    virtual_ipaddress {
        172.16.20.99
    }
}

非抢占式及通知服务

vrrp_instance VI_1 {
    state BACKUP // 主备都用BACKUP
    interface ens192
    virtual_router_id 51
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    track_script {
         chk_port
    }
    virtual_ipaddress {
        192.168.20.99
    }
    nopreempt // 非抢占式
    notify_master /etc/keepalived/notify_master.sh
    notify_backup /etc/keepalived/notify_backup.sh
    notify_fault /etc/keepalived/notify_fault.sh
    notify_stop /etc/keepalived/notify_stop.sh
}

说明

OGNL(Object-Graph Navigation Language)和SpEL(Spring Expression Language)都是用于在Java应用程序中进行对象导航和操作的表达式语言。

SpEL

SpEL是一种表达式语言,用于在运行时在Spring应用程序中进行查询和操作对象图。您可以使用SpEL来访问对象的属性、调用对象的方法、进行算术运算等等。SpelExpressionParser是Spring框架中的一部分,用于解析SpEL(Spring表达式语言)。

以下是一个简单的示例,演示如何在Spring应用程序中使用SpelExpressionParser:

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

public class SpelExample {

    public static void main(String[] args) {
        // 创建SpelExpressionParser对象
        ExpressionParser parser = new SpelExpressionParser();
        
        // 创建StandardEvaluationContext对象,它表示SpEL表达式的上下文
        StandardEvaluationContext context = new StandardEvaluationContext();

        // 在上下文中添加变量
        context.setVariable("name", "Alice");
        
        // 定义SpEL表达式
        String expression = "'Hello, ' + #name";
        
        // 解析并评估表达式
        String result = parser.parseExpression(expression).getValue(context, String.class);
        
        // 输出结果
        System.out.println(result); // 输出: Hello, Alice
    }
}
  1. 访问对象属性和方法:SpEL允许您通过对象的属性名称直接访问对象的属性,也可以调用对象的方法。
person.name   // 访问person对象的name属性
person.getName()   // 调用person对象的getName()方法
  1. 进行算术运算和逻辑运算:您可以在SpEL中执行各种算术运算和逻辑运算。
Copy code
5 + 3   // 执行加法运算,结果为8
age > 18   // 执行大于运算,判断age是否大于18
  1. 调用静态方法和属性:您可以调用静态方法和属性,甚至可以调用静态常量。
Copy code
T(java.lang.Math).random()   // 调用Math类的random()方法
T(java.lang.Integer).MAX_VALUE   // 获取Integer类的MAX_VALUE常量
  1. 集合操作:SpEL支持对集合进行操作,如访问集合元素、计算集合大小等。
Copy code
myList[0]   // 访问列表中的第一个元素
myMap['key']   // 访问映射中键为'key'的值
#myList.size()   // 计算列表的大小
  1. 条件运算:您可以在SpEL中使用条件运算符执行条件判断。
Copy code
isVip ? discountPrice : regularPrice   // 如果isVip为true,则返回discountPrice,否则返回regularPrice
  1. 正则表达式:SpEL支持对字符串进行匹配和替换操作,使用正则表达式。
Copy code
#email.matches('[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}')   // 检查email是否匹配电子邮件格式
  1. 安全导航运算符:SpEL引入了安全导航运算符(?.),用于避免空指针异常。
Copy code
person?.address?.city   // 如果person或address为null,则返回null,而不是抛出空指针异常

这只是SpEL的一些基本用法示例,SpEL还提供了更多功能,例如集成Spring Bean引用、类型转换、字符串操作等等。通过这些功能,您可以编写更加灵活和强大的表达式来满足各种需求。

说明

springboot动态增加和删除restful接口.

代码

@Service
public class Test {
    @Autowired
    RequestMappingHandlerMapping requestMappingHandlerMapping;

    // 初始化后自动添加一个 GET /test 接口
    @PostConstruct
    public void test() throws NoSuchMethodException {
        RequestMappingInfo.BuilderConfiguration options = new RequestMappingInfo.BuilderConfiguration();
        options.setPatternParser(new PathPatternParser());
        RequestMappingInfo requestMappingInfo = RequestMappingInfo
                .paths("/test")
                .produces("application/json;charset=UTF-8")
//                .consumes("text/plain;charset=UTF-8")
                .methods(RequestMethod.GET)
                .options(options)
                .build();
        requestMappingHandlerMapping.registerMapping(requestMappingInfo, "handler", Handle.class.getDeclaredMethod("test", HttpServletRequest.class));
        // 删除接口
        //requestMappingHandlerMapping.unregisterMapping(requestMappingInfo);
    }
}

@RestController
public class Handler {
    public String test(HttpServletRequest request)  {
        return "hello";
    }
}

说明

// Enum对象
@AllArgsConstructor
@Getter
public enum Type {
    SMS("sms"),
    APP("app"),
    UNKNOWN("unknown"),
    ;
    private String type;
}

// 实际的接口对象,里面有个属性是Enum
@Data
@Accessors(chain = true)
public class Noti {
    private Type type;
    private boolean sync;
}

springboot接口难免用到Enum类型,这个时候如何配置Enum类型序列化和反序列化呢?springboot默认使用的JSON序列化工具是jackson.

使用

// Enum对象
@AllArgsConstructor
@Getter
public enum Type {
    SMS("sms"),
    APP("app"),
    UNKNOWN("unknown"),
    ;
    private String type;

     @JsonCreator
    public static Type fromString(String value) {
        for (Type type : values()) {
            if (type.getType().equalsIgnoreCase(value)) {
                return type;
            }
        }
        return Type.UNKNOWN;
    }

    @JsonValue
    public String getTypeValue() {
        return type;
    }
}

// 实际的接口对象,里面有个属性是Enum
@Data
@Accessors(chain = true)
public class Noti {
    @JsonFormat(shape = JsonFormat.Shape.STRING)
    private Type type;
    private boolean sync;
}

通过以上配置就可以实现, json中的字符串转化成Enum对象,以及Enum对象转化成json中的字符串.

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

添加websocket配置类

@Configuration
public class WebSocketConfig {
    /**
     * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

添加websocket Endpoint

@Slf4j
@Component 
@ServerEndpoint("/ws/{name}")
public class WebSocketTest {
    // @Component 虽然表示是springboot的单例,但是这个创建的单例并不会用到.
    // 实际新的websocket连接进来的时候, 会创建新的 WebSocketTest 实例,所以这里的name、session属性都是该连接私有的
    private String name;
    private Session session;

    // 可以创建一个构造器测试,是不是新的websocket连接进来会创建新的实例.
    // public WebSocketTest() {
    //     System.out.println("创建");
    // }

    // 可以创建一个静态属性保存所有的websocket连接
    // private static ConcurrentHashMap<String, WebSocketTest> webSocketSet = new ConcurrentHashMap<>();

    @OnOpen
    public void onOpen(Session session, @PathParam(value = "name") String name) {
        this.name = name;
        this.session = session;
        // name 是用来表示唯一客户端
        webSocketSet.put(name, this);
        System.out.println("OnOpen");
    }

    @OnClose
    public void OnClose() {
        System.out.println("OnClose");
    }

    @OnMessage
    public void OnMessage(String msg) throws IOException {
        System.out.println(msg);
        session.getBasicRemote().sendText("Hello " + name);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.info("发生错误");
        error.printStackTrace();
    }
}

测试

可以通过在线 websocket 客户端来测试你的 ws 服务:http://coolaf.com/tool/chattest.

创建密码

# 创建一个test用户,终端输入密码,然后保存到pwd文件中
htpasswd -c pwd test
# 可以cat pwd 看看密码
test:$apr1$XV.xFTZA$Gvuw/g8TAYYS6Nm5c7w5Q1
# 可以向这个文件继续添加用户test2,密码sss
htpasswd -b pwd test2 sss

nginx.conf

将上面生成的文件 pwd 放到 /etc/nginx/pwd目录下,并在 nginx.conf 指定位置.

server {
        listen    80 default_server;
        server_name _;
        location / {
            auth_basic "you need login";
            auth_basic_user_file /etc/nginx/pwd; 
            proxy_pass   http://xxxx:222;
            # Default is HTTP/1, keepalive is only enabled in HTTP/1.1
            proxy_http_version 1.1;
            # Remove the Connection header if the client sends it,
            # it could be "close" to close a keepalive connection
            proxy_set_header Connection ""; 
      }
    }

注意proxy_http_version 1.1proxy_set_header Connection ""配置对于SSE这种需要保持长连接的接口很重要,一定要添加不然无数据返回. proxy_http_version指定版本,proxy_set_header设置Connection为空.

curl测试

# 第一种方式, -u 指定用户名密码
curl -u "test2:sss"   http://172.16.20.91:38750/sse/test

# 第二种,将用户名密码base64加密后添加到header
echo -n "test2:sss" | base64
curl -H "Authorization:Basic base64加密后的字符串" http://127.0.0.1:38750/test