sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://huecker.io",
"https://dockerhub.timeweb.cloud",
"https://noohub.ru"
]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
需要有一台国外服务器, 按下面添加 Nginx 配置即可,配置完成后,在 /etc/docker/daemon.json
中修改成你的域名,具体参考上方配置,然后重启 docker
server {
listen 443 ssl;
server_name 域名;
ssl_certificate 证书地址;
ssl_certificate_key 密钥地址;
proxy_ssl_server_name on; # 启用SNI
ssl_session_timeout 24h;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
location / {
proxy_pass https://registry-1.docker.io; # Docker Hub 的官方镜像仓库
proxy_set_header Host registry-1.docker.io;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# 关闭缓存
proxy_buffering off;
# 转发认证相关的头部
proxy_set_header Authorization $http_authorization;
proxy_pass_header Authorization;
# 对 upstream 状态码检查,实现 error_page 错误重定向
proxy_intercept_errors on;
# error_page 指令默认只检查了第一次后端返回的状态码,开启后可以跟随多次重定向。
recursive_error_pages on;
# 根据状态码执行对应操作,以下为301、302、307状态码都会触发
#error_page 301 302 307 = @handle_redirect;
error_page 429 = @handle_too_many_requests;
}
#处理重定向
location @handle_redirect {
resolver 1.1.1.1;
set $saved_redirect_location '$upstream_http_location';
proxy_pass $saved_redirect_location;
}
# 处理429错误
location @handle_too_many_requests {
proxy_set_header Host 替换为在CloudFlare Worker设置的域名; # 替换为另一个服务器的地址
proxy_pass http://替换为在CloudFlare Worker设置的域名;
proxy_set_header Host $http_host;
}
}
登录到 Cloudflare 控制台, 新建 worker, 在 worker.js 文件中输入以下代码, 注意需要自行修改代码中的域名
'use strict'
const hub_host = 'registry-1.docker.io'
const auth_url = 'https://auth.docker.io'
const workers_url = 'https://你的域名'
const workers_host = '你的域名'
const home_page_url = 'https://qninq.cn/file/html/dockerproxy.html'
/** @type {RequestInit} */
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
}
/**
* @param {any} body
* @param {number} status
* @param {Object<string, string>} headers
*/
function makeRes(body, status = 200, headers = {}) {
headers['access-control-allow-origin'] = '*'
return new Response(body, {status, headers})
}
/**
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}
addEventListener('fetch', e => {
const ret = fetchHandler(e)
.catch(err => makeRes('cfworker error:\n' + err.stack, 502))
e.respondWith(ret)
})
/**
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const getReqHeader = (key) => e.request.headers.get(key);
let url = new URL(e.request.url);
if (url.pathname === '/') {
// Fetch and return the home page HTML content with replacement
let response = await fetch(home_page_url);
let text = await response.text();
text = text.replace(/{workers_host}/g, workers_host);
return new Response(text, {
status: response.status,
headers: response.headers
});
}
if (url.pathname === '/token') {
let token_parameter = {
headers: {
'Host': 'auth.docker.io',
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
}
};
let token_url = auth_url + url.pathname + url.search
return fetch(new Request(token_url, e.request), token_parameter)
}
url.hostname = hub_host;
let parameter = {
headers: {
'Host': hub_host,
'User-Agent': getReqHeader("User-Agent"),
'Accept': getReqHeader("Accept"),
'Accept-Language': getReqHeader("Accept-Language"),
'Accept-Encoding': getReqHeader("Accept-Encoding"),
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
},
cacheTtl: 3600
};
if (e.request.headers.has("Authorization")) {
parameter.headers.Authorization = getReqHeader("Authorization");
}
let original_response = await fetch(new Request(url, e.request), parameter)
let original_response_clone = original_response.clone();
let original_text = original_response_clone.body;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
if (new_response_headers.get("Www-Authenticate")) {
let auth = new_response_headers.get("Www-Authenticate");
let re = new RegExp(auth_url, 'g');
new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url));
}
if (new_response_headers.get("Location")) {
return httpHandler(e.request, new_response_headers.get("Location"))
}
let response = new Response(original_text, {
status,
headers: new_response_headers
})
return response;
}
/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers
// preflight
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT)
}
let rawLen = ''
const reqHdrNew = new Headers(reqHdrRaw)
const refer = reqHdrNew.get('referer')
let urlStr = pathname
const urlObj = newUrl(urlStr)
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'follow',
body: req.body
}
return proxy(urlObj, reqInit, rawLen, 0)
}
/**
*
* @param {URL} urlObj
* @param {RequestInit} reqInit
*/
async function proxy(urlObj, reqInit, rawLen) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)
// verify
if (rawLen) {
const newLen = resHdrOld.get('content-length') || ''
const badLen = (rawLen !== newLen)
if (badLen) {
return makeRes(res.body, 400, {
'--error': `bad len: ${newLen}, except: ${rawLen}`,
'access-control-expose-headers': '--error',
})
}
}
const status = res.status
resHdrNew.set('access-control-expose-headers', '*')
resHdrNew.set('access-control-allow-origin', '*')
resHdrNew.set('Cache-Control', 'max-age=1500')
resHdrNew.delete('content-security-policy')
resHdrNew.delete('content-security-policy-report-only')
resHdrNew.delete('clear-site-data')
return new Response(res.body, {
status,
headers: resHdrNew
})
}
部署完成后,点击设置->触发器->添加自定义域,绑定自己的域名即可。配置完成后,在 /etc/docker/daemon.json
中修改成你的域名,具体参考上方配置,然后重启 docker。
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.10.0</version>
</dependency>
package demo;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
class ApiProxyJava {
public static void main(String[] args) throws IOException {
testHttpWithOkHttp();
testSocks5WithOkHttp();
}
public static void testHttpWithOkHttp() throws IOException {
String url = "https://www.google.com/";
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(IP, PORT));
OkHttpClient client = new OkHttpClient().newBuilder().proxy(proxy).build();
Request request = new Request.Builder().url(url).build();
okhttp3.Response response = client.newCall(request).execute();
String responseString = response.body().string();
System.out.println(responseString);
response.close();
}
public static void testSocks5WithOkHttp() throws IOException {
String url = "https://www.google.com/";
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(IP, PORT));
OkHttpClient client = new OkHttpClient().newBuilder().proxy(proxy).build();
Request request = new Request.Builder().url(url).build();
okhttp3.Response response = client.newCall(request).execute();
String responseString = response.body().string();
System.out.println(responseString);
response.close();
}
package demo;
import okhttp3.Credentials;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
/**
* compile 'com.squareup.okhttp3:okhttp:3.10.0'
*/
class AutProxyJava {
public static void main(String[] args) throws IOException {
testWithOkHttp();
testSocks5WithOkHttp();
}
public static void testWithOkHttp() throws IOException {
String url = "https://www.google.com/";
String gateIp = ""
int gatePort = 1000
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(gateIp, gatePort));
OkHttpClient client = new OkHttpClient().newBuilder().proxy(proxy).proxyAuthenticator((route, response) -> {
String credential = Credentials.basic("账户", "密码");
return response.request().newBuilder()
.header("Proxy-Authorization", credential)
.build();
}).build();
Request request = new Request.Builder().url(url).build();
okhttp3.Response response = client.newCall(request).execute();
String responseString = response.body().string();
System.out.println(responseString);
response.close();
}
public static void testSocks5WithOkHttp() throws IOException {
String url = "https://www.google.com/";
String gateIp = ""
int gatePort = 2000
Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(gateIp, gatePort));
java.net.Authenticator.setDefault(new java.net.Authenticator() {
private PasswordAuthentication authentication =
new PasswordAuthentication("账户", "密码".toCharArray());
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return authentication;
}
});
OkHttpClient client = new OkHttpClient().newBuilder().proxy(proxy).build();
Request request = new Request.Builder().url(url).build();
okhttp3.Response response = client.newCall(request).execute();
String responseString = response.body().string();
System.out.println(responseString);
response.close();
}
}
]]> Java 在 java.math 包中提供的 API 类 BigDecimal,用来对超过 16 位有效位的数进行精确的运算。双精度浮点型变量 double 可以处理 16 位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用 Float 和 Double 处理,但是 Double.valueOf(String) 和 Float.valueOf(String) 会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用 BigDecimal 类来操作。
BigDecimal所创建的是对象,故我们不能使用传统的 +、-、*、/
等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是 BigDecimal 的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。
// 创建一个具有参数所指定整数值的对象
BigDecimal(int)
// 创建一个具有参数所指定双精度值的对象
BigDecimal(double)
// 创建一个具有参数所指定长整数值的对象
BigDecimal(long)
// 创建一个具有参数所指定以字符串表示的数值的对象
BigDecimal(String)
使用示例:
BigDecimal a =new BigDecimal(0.1);
System.out.println("a values is:"+a);
System.out.println("=====================");
BigDecimal b =new BigDecimal("0.1");
System.out.println("b values is:"+b);
结果示例:
a values is:0.1000000000000000055511151231257827021181583404541015625
=====================
b values is:0.1
原因分析:
1)参数类型为 double 的构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 newBigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。
2)String 构造方法是完全可预知的:写入 newBigDecimal(“0.1”) 将创建一个 BigDecimal,它正好等于预期的 0.1。因此,比较而言, 通常建议优先使用String构造方法。
3)当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。
// BigDecimal 对象中的值相加,返回 BigDecimal 对象
add(BigDecimal)
// BigDecimal 对象中的值相减,返回 BigDecimal 对象
subtract(BigDecimal)
// BigDecimal 对象中的值相乘,返回 BigDecimal 对象
multiply(BigDecimal)
// BigDecimal 对象中的值相除,返回 BigDecimal 对象
divide(BigDecimal)
// 将 BigDecimal 对象中的值转换成字符串
toString()
// 将 BigDecimal 对象中的值转换成双精度数
doubleValue()
// 将 BigDecimal 对象中的值转换成单精度数
floatValue()
// 将 BigDecimal 对象中的值转换成长整数
longValue()
// 将 BigDecimal 对象中的值转换成整数
intValue()
java 中对 BigDecimal 比较大小一般用的是 bigdemical 的 compareTo 方法
int a = bigdemical.compareTo(bigdemical2)
返回结果分析:
a = -1,表示bigdemical小于bigdemical2;
a = 0,表示bigdemical等于bigdemical2;
a = 1,表示bigdemical大于bigdemical2;
举例:a大于等于b
new bigdemica(a).compareTo(new bigdemical(b)) >= 0
由于 NumberFormat 类的 format() 方法可以使用 BigDecimal 对象作为其参数,可以利用 BigDecimal 对超出 16 位有效数字的货币值,百分值,以及一般数值进行格式化控制。
以利用 BigDecimal 对货币和百分比格式化为例。首先,创建 BigDecimal 对象,进行 BigDecimal 的算术运算后,分别建立对货币和百分比格式化的引用,最后利用 BigDecimal 对象作为 format() 方法的参数,输出其格式化的货币值和百分比。
NumberFormat currency = NumberFormat.getCurrencyInstance(); //建立货币格式化引用
NumberFormat percent = NumberFormat.getPercentInstance(); //建立百分比格式化引用
percent.setMaximumFractionDigits(3); //百分比小数点最多3位
BigDecimal loanAmount = new BigDecimal("15000.48"); //贷款金额
BigDecimal interestRate = new BigDecimal("0.008"); //利率
BigDecimal interest = loanAmount.multiply(interestRate); //相乘
System.out.println("贷款金额:\t" + currency.format(loanAmount));
System.out.println("利率:\t" + percent.format(interestRate));
System.out.println("利息:\t" + currency.format(interest));
结果:
贷款金额: ¥15,000.48 利率: 0.8% 利息: ¥120.00
BigDecimal 格式化保留两位小数,不足则补 0:
public class NumberFormat {
public static void main(String[] s){
System.out.println(formatToNumber(new BigDecimal("3.435")));
System.out.println(formatToNumber(new BigDecimal(0)));
System.out.println(formatToNumber(new BigDecimal("0.00")));
System.out.println(formatToNumber(new BigDecimal("0.001")));
System.out.println(formatToNumber(new BigDecimal("0.006")));
System.out.println(formatToNumber(new BigDecimal("0.206")));
}
/**
* @desc 1.0~1之间的BigDecimal小数,格式化后失去前面的0,则前面直接加上0。
* 2.传入的参数等于0,则直接返回字符串"0.00"
* 3.大于1的小数,直接格式化返回字符串
* @param obj传入的小数
* @return
*/
public static String formatToNumber(BigDecimal obj) {
DecimalFormat df = new DecimalFormat("#.00");
if(obj.compareTo(BigDecimal.ZERO)==0) {
return "0.00";
}else if(obj.compareTo(BigDecimal.ZERO)>0&&obj.compareTo(new BigDecimal(1))<0){
return "0"+df.format(obj).toString();
}else {
return df.format(obj).toString();
}
}
}
结果为:
3.44
0.00
0.00
0.00
0.01
0.21
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result
原因分析:
通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
解决方法:
divide 方法设置精确的小数点,如:divide(xxxxx,2)
package com.vivo.ars.util;
import java.math.BigDecimal;
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
*
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
}
]]>在我们书写代码的时候,会书写许多日志代码,但是有些敏感数据是需要进行安全脱敏处理的。
对于日志脱敏的方式有很多,常见的有
① 使用 conversionRule 标签,继承 MessageConverter
② 书写一个脱敏工具类,在打印日志的时候对特定特字段进行脱敏返回
两种方式各有优缺点:
第一种方式需要修改代码,不符合开闭原则。
第二种方式,需要在日志方法的参数进行脱敏,对原生日志有入侵行为。
一个项目在书写了很多打印日志的代码,但是后面有了脱敏需求,如果我们去手动改动代码,会花费大量时间。如果引入本组件,完成配置即可轻松完成脱敏。(仅需三步可轻松配置)
前提是你将Jar包打入本地仓库,Jar包地址见后文。
<dependency>
<groupId>pers.liuchengyin</groupId>
<artifactId>logback-desensitization</artifactId>
<version>1.0.0</version>
</dependency>
日志打印方式都只需要替换成脱敏的类即可,如果你的业务不需要,则无需替换。
① ConsoleAppender - 控制台脱敏
// 原类
ch.qos.logback.core.ConsoleAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyConsoleAppender
② RollingFileAppender - 滚动文件
// 原类
ch.qos.logback.core.rolling.RollingFileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyRollingFileAppender
③ FileAppender - 文件
// 原类
ch.qos.logback.core.FileAppender
// 替换类
pers.liuchengyin.logbackadvice.LcyFileAppender
替换示例:
<property name="CONSOLE_LOG_PATTERN"
value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level) |%blue(%thread) |%blue(%file:%line) |%green(%logger) |%cyan(%msg%n)"/>
<!-- ConsoleAppender 控制台输出日志 -->
<appender name="CONSOLE" class="pers.liuchengyin.logbackadvice.LcyConsoleAppender">
<encoder>
<pattern>
${CONSOLE_LOG_PATTERN}
</pattern>
</encoder>
</appender>
该配置文件应该放在 resources
文件下
八大基本类型及其包装类型、Map、List、业务里的Pojo对象、List<业务里的Pojo对象>、JSON字符串。
注:在配置文件中配置的时候,只需要配置对象里的属性值就行。
List<八大基本类型及包装类型>,因为不知道脱敏的数据源具体是哪一个。
key + 分割符 + value
,目前仅支持冒号 (:) 和等号 (=),示例如下:
log.info("your email:{}, your phone:{}", "123456789@qq.com","15310763497");
log.info("your email={}, your cellphone={}", "123456789@qq.com","15310763497");
key:定义了对应需要脱敏的关键字,如上诉的 email、phone 等以及业务对象中的字段、Map 中的 Key、JSON 中的 Key
value:需要脱敏的值,如上诉的 123456789@qq.com、15310763497
建议书写日志的时候尽量规范,对于key为中文的是没有办法脱敏的,规范程度可以见脱敏效果演示里的代码。
# 日志脱敏
log-desensitize:
# 是否忽略大小写匹配,默认为true
ignore: true
# 是否开启脱敏,默认为false
open: true
# pattern下的key/value为固定脱敏规则
pattern:
# 邮箱 - @前第4-7位脱敏
email: "@>(4,7)"
# qq邮箱 - @后1-3位脱敏
qqemail: "@<(1,3)"
# 姓名 - 姓脱敏,如*杰伦
name: 1,1
# 密码 - 所有需要完全脱敏的都可以使用内置的password
password: password
patterns:
# 身份证号,key后面的字段都可以匹配以下规则(用逗号分隔)
- key: identity,idcard
# 定义规则的标识
custom:
# defaultRegex表示使用组件内置的规则:identity表示身份证号 - 内置的18/15位
- defaultRegex: identity
position: 9,13
# 内置的other表示如果其他规则都无法匹配到,则按该规则处理
- defaultRegex: other
position: 9,10
# 电话号码,key后面的字段都可以匹配以下规则(用逗号分隔)
- key: phone,cellphone,mobile
custom:
# 手机号 - 内置的11位手机匹配规则
- defaultRegex: phone
position: 4,7
# 自定义正则匹配表达式:座机号(带区号,号码七位|八位)
- customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
# -后面的1-4位脱敏
position: "-<(1,4)"
# 自定义正则匹配表达式:座机号(不带区号)
- customRegex: "^[0-9]{7,8}"
position: 3,5
# 内置的other表示如果其他规则都无法匹配到,则按该规则处理
- defaultRegex: other
position: 1,3
# 这种方式不太推荐 - 一旦匹配不上,就不会脱敏
- key: localMobile
custom:
customRegex: "^0[0-9]{2,3}-[0-9]{7,8}"
position: 1,3
上面这个配置是相对完整的,一定要严格遵守层级配置格式。
phone:4,7
,表示 phone 属性的 4-7 位进行脱敏
原始数据:13610357861
脱敏后:136**7861
email:"@>(4,7)"
,@
为脱敏标志,>
表示其为结束节点,<
表示其为开始节点。即 @> 表示对 @ 之前的进行脱敏,@< 表示对 @ 之后的进行脱敏。这个示例就是 @ 前的数据的第 4-7 位进行脱敏。注意:这种规则里的双引号、括号不能省略,其次 : 和 = 不能作为标志符号,因为和匹配规则有冲突。
原始数据:123456789@qq.com"@>(4,7)"
脱敏后:123**89@qq.com"@<(1,3)"
脱敏后:123456789@*com
patterns:
# 手机号
- key: phone,mobile
custom:
# 手机号的正则
- customRegex: "^1[0-9]{10}"
# 脱敏范围
position: 4,7
customRegex:正则表达式,如果符合该表达式,则使用其对应的脱敏规则 (position)
比如说,username 字段的值可以是手机号、也可以是邮箱,这个值动态改变的,前面几种方式都没办法解决,可以使用该方式。
patterns:
- key: username
custom:
# 手机号 - 11位
- defaultRegex: phone
position : 4,7
# 邮箱 - @
- defaultRegex: email
position : "@>(3,12)"
# 身份证 - 15/18位
- defaultRegex: identity
position : 1,3
# 自定义正则
- customRegex: "^1[0-9]{10}"
position : 1,3
# 都匹配不到时,按照这种规则来
- defaultRegex: other
position : 1,3
注意:上面示例中匹配规则里的 双引号和括号 都不能省略
该组件内置四种匹配规则:手机号、身份证号、邮箱、other(其他匹配不到时用的),内置一种脱敏方式:password,表示完全脱敏,可用于 pattren 下的。
注:当pattern和patterns下的key有重复的时候,只会使用pattern下指定的方式进行脱敏。
Jar包Github地址 - logback-desensitization-1.0.0.jar
Github地址: Logback和slf4j的日志脱敏组件Demo
Gitee地址: Logback和slf4j的日志脱敏组件Demo
1、下载Jar包,放在一个文件夹里
2、在这个文件夹里打开cmd(打开cmd,进入到这个文件夹)
3、执行命令(前提保证maven配置正常,使用mvn -v命令查看是否正常,如果显示版本号表示正常)
mvn install:install-file -DgroupId=pers.liuchengyin -DartifactId=logback-desensitization -Dversion=1.0.0 -Dpackaging=jar -Dfile=logback-desensitization-1.0.0.jar
命令说明:
-DgroupId
表示jar对应的groupId
<groupId>pers.liuchengyin</groupId>
-DartifactId:
表示jar对应的artifactId
<artifactId>logback-desensitization</artifactId>
-Dversion
表示jar对应的 version
<version>1.0.0</version>
原文作者: 九月清晨柳成荫
原文地址:https://blog.csdn.net/qq_40885085/article/details/113385261?spm=1001.2014.3001.5501
1、a.equals(b), a 是 null, 抛出 NullPointException 异常。
2、a.equals(b), a不是 null, b是null, 返回 false
3、Objects.equals(a, b) 比较时, 若 a 和 b 都是 null, 则返回 true, 如果 a 和 b 其中一个是 null, 另一个不是 null, 则返回 false。注意:不会抛出空指针异常。
null.equals("abc") → 抛出 NullPointerException 异常
"abc".equals(null) → 返回 false
null.equals(null) → 抛出 NullPointerException 异常
Objects.equals(null, "abc") → 返回 false
Objects.equals("abc",null) → 返回 false
Objects.equals(null, null) → 返回 true
1、a 和 b 如果都是空值字符串:"", 则 a.equals(b), 返回的值是 true, 如果 a 和 b 其中有一个不是空值字符串,则返回 false;
2、这种情况下 Objects.equals 与情况 1 行为一致。
"abc".equals("") → 返回 false
"".equals("abc") → 返回 false
"".equals("") → 返回 true
Objects.equals("abc", "") → 返回 false
Objects.equals("","abc") → 返回 false
Objects.equals("","") → 返回 true
* @since 1.7
*/
public final class Objects {
private Objects() {
throw new AssertionError("No java.util.Objects instances for you!");
}
/**
* Returns {@code true} if the arguments are equal to each other
* and {@code false} otherwise.
* Consequently, if both arguments are {@code null}, {@code true}
* is returned and if exactly one argument is {@code null}, {@code
* false} is returned. Otherwise, equality is determined by using
* the {@link Object#equals equals} method of the first
* argument.
*
* @param a an object
* @param b an object to be compared with {@code a} for equality
* @return {@code true} if the arguments are equal to each other
* and {@code false} otherwise
* @see Object#equals(Object)
*/
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
1) 进行了对象地址的判断,如果是真,则不再继续判断。
2) 如果不相等,后面的表达式的意思是,先判断 a 不为空,然后根据上面的知识点,就不会再出现空指针。
3) 如果都是 null,在第一个判断上就为 true 了。如果不为空,地址不同,就重要的是判断 a.equals(b)。
如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true。
而 a.equals(b) 是进行逻辑比较,当内容相同时,返回 true,所以通常需要重写该方法来提供逻辑一致性的比较。
]]>插件支持 Chrome、Firefox、MS-Edge 浏览器,内部工具包括 JSON自动/手动格式化、JSON内容比对、代码美化与压缩、信息编解码转换、二维码生成与解码、图片Base64编解码转换、随机密码生成、Markdown、 网页油猴、网页取色器、脑图(Xmind)等贴心工具等
https://www.baidufe.com/fehelper/index/index.html
插件内置工具包含:时间戳转换、URL编码、BASE64编码、加解密、二维码生成、ip地址查询、DNS/Whois查询、JSON解析、翻译、万年历、色彩查询等。
我常用的内置工具有:字符串加解密、时间戳转换、随机字符生成、Json解析【这个就不用介绍了】、IP地址查询等
功能:MD5加解密、URL编解码、Base64编解码、SHA1-512加密
在线时间戳值转换及各种语言下时间转换写法
可根据位数、数字、字母等自定义生成随机字符串,常用于生成随机密码
查询当前IP地址,通过IP地址查询地域。
{hide}
{cloud title="插件下载" type="lz" url="https://ydyno.lanzoui.com/iEhdHqu0jdi" password="g4is"/}
{/hide}
下载后,最好保存到一个单独的文件夹,避免误删除,打开你的Chrome浏览器:
直接在网址输入: chrome://extensions/
打开开发者模式,将插件【crx格式】拖入浏览器安装即可
]]>在日常开发工作和自己学习跑 demo 的时候,往往都需要快速构建一个 springboot 基础工程。除了用 IDEA 开发工具构建,更多就是用 Spring Initializr
来生成,但用的时间长了发现,它也就仅仅只能帮我们引入一些必要的 jar 包,其他插件轮子还是得自己配置。
Aliyun Java Initializr
覆盖了 Spring Initializr
的所有功能,又在其基础上增加了很多实用功能,它几乎可以集成当下所有主流技术,只要勾选相应的模块就可以自动集成进来。
可不是简单的引入 jar 包,而是帮你把工程目录,相关配置文件和基础 java 代码全部生成了,省了很多环境搭建的时间。
地址:https://start.aliyun.com/bootstrap.html
magicalcoder
是一个自动生成代码的工具,JAVA 代码自动生成,还有 H5 样式布局器。
对于有写页面需求的后端开发来说是个神器,可以拖拽控件布局直接生成 css、js,支持 element、layui、bootstrap4 这些常用的前端框架。
地址:http://bbs.magicalcoder.com/
这是一个非常强大的工具类站点,我个人常用得就是 JSON 格式化工和 python 在线工具,虽然主做 Java 但平时还是会写一些 python 脚本,在线运行随时随地写很方便。
BEJSON站内提供了很多实用的小工具。像我最近正在开发一个调度任务的配置平台,免不了要写 Cron 表达式,但搞过定时任务的都知道 Cron 表达式可不太好记,而且用的频率相对不高,花太多时间在死记硬背上就有点不值当。这个在线工具提供了图形化界面,只要我们输入想要定时执行的周期,就可以自动生成Cron表达式。
地址:https://www.bejson.com/othertools/cron
正则表达式的写法既复杂又麻烦,而且通常还需要经过大量的测试和验证才能被采用。这个在线工具提供了几乎所有常见的正则校验规则。
地址:http://tool.chinaz.com/tools/regexgenerate
这个也是一个在线工具站点,但是他的功能相对就比较杂了
工作中时不时就会有运营人员或者老板直接甩个任务,给我统计一下某某销量的指标,用 SQL 查询很简单,但给外行人看这类数据,还要是更直观一点,反正都是干活为啥不完成的出彩一点。
文图可以根据 excel 里的数据,自动生成对应的统计图形,样式可以自由切换,生成 PDF、JPG 等格式。
Diffchecker是一个使用很不错代码差异对比工具,使过 svn 或者 git 的人对 diffcheck 肯定不陌生,而且他支持比对的类型比较丰富,excel、pdf 支持。
地址:https://www.diffchecker.com/
总修改 nginx 配置,这个可以治愈你的强迫症
地址:http://www.html580.com/tool/nginx/index.php
代码生成图片,这个不写博客的小伙伴可能不太常用,像我会时常分享一些技术类的文章,里边难免会贴出代码块,出于观看方便和样式美观就会转成图片。
地址:https://www.dute.org/code-snapshot
上边这些是我常用到的几个在线工具,其实远不止这些,虽然说不上神器,但确实实在在是提升了我的工作效率,希望也能对你有点帮助。
]]>docker kill $(docker ps -a -q)
docker rm $(docker ps -a -q)
docker rmi $(docker images -q)
systemctl stop docker
rm -rf /etc/docker
rm -rf /run/docker
rm -rf /var/lib/dockershim
rm -rf /var/lib/docker
如果发现删除不掉,需要先 umount,如
umount /var/lib/docker/devicemapper
查看已安装的 docker 包
yum list installed | grep docker
卸载相关包
yum remove docker-ce*
yum remove containerd.io.x86_64
]]>如:阿里云盾(安骑士)、云监控(Cloudmonitor)
这些监控服务的存在使得人们可以直接在控制台看到当前系统的状态,某种程度上来说确实方便了服务器的管理。但是对于我们这种“爱折腾党”,基本没有用到这些服务的时候,而且天天被这些监控盯着还总有种被偷窥的感觉。
在 官方文档 页面只介绍了怎么在控制台去卸载,手动卸载的操作步骤需要提交工单获取,不是很方便。
在这里介绍一种用脚本手动卸载的方法(其实用的也是官方提供的两个脚本),执行以下命令即可。
wget http://update.aegis.aliyun.com/download/uninstall.sh && chmod +x uninstall.sh &&./uninstall.sh
wget http://update.aegis.aliyun.com/download/quartz_uninstall.sh && chmod +x quartz_uninstall.sh && ./quartz_uninstall.sh
测试发现上面的脚本运行完之后可能还会有一些服务/文件/文件夹残留,所以我们手动清理一下,顺便把刚刚下下来的两个脚本文件也删了(如果提示文件不存在就不用管了)。
sudo rm -r /usr/local/aegis
sudo systemctl disable aliyun.service
sudo rm /usr/sbin/aliyun-service
sudo rm /usr/sbin/aliyun-service.backup
sudo rm /usr/sbin/aliyun_installer
sudo rm /etc/systemd/system/aliyun.service
sudo rm /lib/systemd/system/aliyun.service
rm uninstall.sh quartz_uninstall.sh
云监控有 Java 版本和 Go 语言版本两种(Java 版本已经不再提供升级了,现在新开的服务器应该都是 Go 语言版本的),可以根据其安装目录 /usr/local/cloudmonitor/
下的文件名判断你的服务器上装的具体是哪一种。下面分别介绍两个版本的卸载方法。
云监控 Go 语言版的可执行文件名为 CmsGoAgent.linux-${ARCH}
,其中的 ARCH 根据 Linux 架构的不同,分为 amd64
和 386
,可以在其安装目录 /usr/local/cloudmonitor/
下找到具体的文件名。
为了方便表述,下面的命令中也使用 ${ARCH}
替代文件名中的系统架构部分。要能够直接复制下面的命令行去执行的话,可以先设置一下临时环境变量(相当于后面输入命令中的 ${ARCH}
会被自动替换成我们在这里设置的值)
# 64 位系统
export ARCH=amd64
# 32 位系统
export ARCH=386
或者的话你也可以在找到具体的文件名后自行替换。下面是卸载相关的一些命令:
# 从系统服务中移除
/usr/local/cloudmonitor/CmsGoAgent.linux-${ARCH} uninstall
# 停止
/usr/local/cloudmonitor/CmsGoAgent.linux-${ARCH} stop
# 卸载
/usr/local/cloudmonitor/CmsGoAgent.linux-${ARCH} stop && \
/usr/local/cloudmonitor/CmsGoAgent.linux-${ARCH} uninstall && \
rm -rf /usr/local/cloudmonitor
附上官方文档页面链接:https://help.aliyun.com/document_detail/97929.html
# 停止
/usr/local/cloudmonitor/wrapper/bin/cloudmonitor.sh stop
# 卸载
/usr/local/cloudmonitor/wrapper/bin/cloudmonitor.sh remove && \
rm -rf /usr/local/cloudmonitor
附上官方文档页面链接:https://help.aliyun.com/knowledge_detail/38859.html
]]>