selenium获取不到标签内容

Get the visible (i.e. not hidden by CSS) text of this element, including sub-elements.

WebElement.getText();

该方法只能获取到“可见的”标签的内容,可使用WebElement.getAttribute(“innerText”); 获取隐藏标签的内容;

部分异步加载的网页,可能需要滚动条滚动到指定位置才加载,可设置浏览器窗口高度使网页的代码判断浏览器可见高度达到了加载条件,触发后续页面加载;

WebDriver.manage().window().setSize(new Dimension(1920, 1080 * 2));

Elasticsearch 搜索不区分大小写

自定义 normalizer ,并且在需要忽略大小写的字段上指定自定义的 normalizer 。

创建索引

curl -XPUT -H "Content-Type: application/json" http://10.0.5.10:9200/account-list-v5?pretty -d '{
    "settings": {
      "index": {
        "mapper": {
          "dynamic": "true"
        },
        "analysis": {
          "analyzer": {
            "default": {
              "type": "ik_max_word"
            }
          },
          "searchAnalyzer": {
            "default": {
              "type": "ik_max_word"
            }
          },
          "normalizer": {
            "my_normalizer": {
              "type": "custom",
              "char_filter": [],
              "filter": ["lowercase", "asciifolding"]
            }
          }
        }
      }
    },
    "mappings": {
      "account-list-item": {
        "properties": {
          "wechatId": {
            "type": "keyword",
            "normalizer": "my_normalizer"
          },
          "badge": {
            "type": "keyword"
          }
        }
      }
    }
}';

重建索引

curl -XPOST -H "Content-Type: application/json" http://10.0.5.10:9200/_reindex?pretty -d '{
    "source": {"index": "account-list-v3"},
    "dest": {"index": "account-list-v5"}
}';

替换别名

curl -XPOST -H "Content-Type: application/json" http://10.0.5.10:9200/_aliases?pretty -d '{
    "actions" : [
      {"remove": {"index": "account-list-v4", "alias": "account-list"}},
      {"add": {"index": "account-list-v5", "alias": "account-list"}}
    ]
}';

浏览器中预览即将上传的图片

$("input[type=file]").on("change", function(){
	if( /^.+\.(jpg|jpeg|png)$/.test(this.value) ){
		var r = new FileReader(),
			name = this.name;
		r.onloadend = function(e){
			$("img[for="+name+"]").attr("src", e.target.result).addClass("loadend");
		};
		r.readAsDataURL(this.files[0]);
	}else{
		this.value = "";
		alert("仅支持(jpg、png)格式图片!");
	}
});

eclipse maven 项目添加 lombok 支持

Step 1: 为项目添加依赖

在pom.xml中添加依赖

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

等待eclipse下载完依赖包

Step 2: 为IDE添加 lombok 支持

在CMD控制台进去本地Maven仓库目录,找到 lombok 的 jar 文件,执行命令
在弹窗中选择需要安装lombok支持的IDE

Step 3: 安装完成后重启IDE

现在就可以在项目中使用 lombok 的注解了。

spring-cloud-gateway-example

package com.example.demo;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter;
import org.springframework.cloud.gateway.filter.ratelimit.RedisRateLimiter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder.Builder;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.CollectionUtils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@SpringBootApplication
public class GatewayApplication {

	private static final Logger LOGGER = LoggerFactory.getLogger(GatewayApplication.class);
	private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

	@Autowired
	private WhiteList whiteList;

	@Bean
	@Order(0)
	GlobalFilter globalFilter0() {
		return (exchange, chain) -> {
			ServerHttpRequest request = exchange.getRequest();
			ServerHttpResponse response = exchange.getResponse();
			List token = request.getHeaders().get("token");
			if (whiteList.getWhiteList().contains(request.getURI().getPath())) {
				return chain.filter(exchange);
			} else if (!CollectionUtils.isEmpty(token) && token.get(0).equals("abc")) {
				LOGGER.info("Token: " + token.get(0));
				request = exchange.getRequest().mutate().header("userId", "0").headers(c -> c.remove("token")).build();
				return chain.filter(exchange.mutate().request(request).build());
			} else {
				response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
				Map res = new HashMap<>();
				res.put("code", 403);
				res.put("data", "");
				res.put("message", "invalid token");
				try {
					return response.writeWith(Flux.just(response.bufferFactory().wrap(OBJECT_MAPPER.writeValueAsBytes(res))));
				} catch (JsonProcessingException e) {
					e.printStackTrace();
				}
				return null;
			}
		};
	}

	@Bean
	@Order(1)
	GlobalFilter globalFilter1() {
		return (exchange, chain) -> {
			LOGGER.info(System.currentTimeMillis() + "");
			return chain.filter(exchange).then(Mono.fromRunnable(() -> {
				HttpHeaders headers = exchange.getResponse().getHeaders();
				headers.add("Access-Control-Allow-Origin", "*");
				headers.add("Access-Control-Allow-Credentials", "true");
				LOGGER.info(System.currentTimeMillis() + "");
			}));
		};
	}

	@Bean
	KeyResolver keyResolver() {
		return (exchange) -> {
			return Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
		};
	}

	@Bean
	RateLimiter< ?> rateLimiter() {
		return new RedisRateLimiter(4, 16);
	}

	@Bean
	RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
		Builder b = builder.routes();
		// @formatter:off
		b.route("h5", r -> r.path("/h5/**")
				.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(rateLimiter()).setKeyResolver(keyResolver())))
				.uri("http://192.168.100.252:801"));
		b.route("otc", r -> r.path("/otc/**")
				.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(rateLimiter()).setKeyResolver(keyResolver())))
				.uri("http://192.168.100.252:801"));
		// @formatter:on
		return b.build();
	}

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

}

package com.example.demo;

import java.util.List;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "bitx")
public class WhiteList {

	private List whiteList;

	public List getWhiteList() {
		return whiteList;
	}

	public void setWhiteList(List whiteList) {
		this.whiteList = whiteList;
	}

}

SQL变量

https://leetcode-cn.com/problems/department-top-three-salaries/description/

SELECT
    d.Name AS Department,
    e.Name AS Employee,
    e.Salary AS Salary
FROM
    Department AS d
JOIN(
      SELECT
        t.*,
        (CASE @k WHEN DepartmentId THEN @n := IF(@s=Salary,@n,@n+1) ELSE @n := 1 END) AS n,
        IF(@k = DepartmentId, @k, @k := DepartmentId) AS k,
        @s := Salary AS s
      FROM
        (SELECT @n := 0, @k := 0, @s := 0) AS _,
        (SELECT * FROM Employee ORDER BY DepartmentId ASC, Salary DESC) AS t
    ) AS e ON d.Id = e.DepartmentId
WHERE e.n < 4;

个人站点使用免费证书部署HTTPS


#下载辅助脚本
git clone https://github.com/certbot/certbot.git

cd certbot

#查看帮助文档
./certbot-auto --help all

#使用网站根目录验证域名所有权并生成域名对应证书
#生成后的证书存放在 /etc/letsencrypt/live/blog.nt00.com 目录下
#根据 /etc/letsencrypt/live/blog.nt00.com/README 文件所述配置 http server
./certbot-auto certonly --webroot --email yangdaye2010@gmail.com -d blog.nt00.com -w /www/blog.nt00.com

#由于证书只有三个月有效期,因此需要定时更新证书
#上一条命令执行成功后,会自动生成刷新证书所需的配置文件 /etc/letsencrypt/renewal/blog.nt00.com.conf
#只需要将如下命令配置到系统定时任务中即可
./certbot-auto renew

MySQL数据库迁移


#!/bin/sh

#表结构(排除触发器、数据)
mysqldump -d --skip-triggers yourDBname -h 10.2.20.2 -u root -pyourPassword > yourDBname-1.sql

#数据(排除触发器、表结构)
mysqldump -nt --skip-triggers yourDBname -h 10.2.20.2 -u root -pyourPassword > yourDBname-2.sql

#存储过程、函数(排除触发器、数据、表结构)
mysqldump -Rdnt --skip-triggers yourDBname -h 10.2.20.2 -u root -pyourPassword > yourDBname-3.sql

#事件(排除触发器、数据、表结构)
mysqldump -Ednt --skip-triggers yourDBname -h 10.2.20.2 -u root -pyourPassword > yourDBname-4.sql

#触发器(排除数据、表结构)
mysqldump -dnt yourDBname -h 10.2.20.2 -u root -pyourPassword > yourDBname-5.sql

#删除 DEFINER
sed -i 's/DEFINER=`\w*`@`[^`]*` //g' yourDBname-3.sql
sed -i 's/DEFINER=`\w*`@`[^`]*` //g' yourDBname-4.sql
sed -i 's/DEFINER=`\w*`@`[^`]*` //g' yourDBname-5.sql

使用脚本拦截近期尝试SSH攻击的IP

如果在近期的日志中有超过两次登录失败的记录,则拒绝其再次发起连接。
vim /root/bin/filterIPs.sh

#!/bin/sh
sed -i '/^sshd:.*/d' /etc/hosts.deny
tail -n 500 /var/log/secure | grep -E "sshd:auth.+failure.+rhost" | awk '{print $14}' | awk -F "=" '{print $2}' | sort | uniq -c | awk '{if($1>1)print "sshd:"$2}' >> /etc/hosts.deny

每分钟更新一次
crontab -e

* * * * * /root/bin/filterIPs.sh

浏览器多标签页之间通信

打开多个加载以下JS代码的标签页,当其中一个标签页调用 window.localStorage.setItem 方法时,其他标签页的 storage 事件就会被触发,event 变量中包含修改前后的值,可实现不同标签页之间的消息传递,调用 window.localStorage.setItem 方法的标签页的 storage 事件不会被触发(Win10中的IE11除外)!!!

var data = {"type":"message", "data":"abc"};
window.localStorage.setItem("message", JSON.stringify(data));
window.addEventListener("storage", function(e){
	console.log(e);
}, false);