{ "version": "https://jsonfeed.org/version/1", "title": "涛声依旧", "subtitle": "天下事有难易乎?为之,则难者亦易矣", "icon": "https://hitoli.com/images/favicon.ico", "description": "天生我材必有用", "home_page_url": "https://hitoli.com", "items": [ { "id": "https://hitoli.com/2024/01/03/%E8%A7%A3%E5%86%B3Nginx%E8%AE%BF%E9%97%AE%E8%87%AA%E7%AD%BEssl%E8%AF%81%E4%B9%A6%E6%8A%A5%E4%B8%8D%E5%AE%89%E5%85%A8%E5%91%8A%E8%AD%A6/", "url": "https://hitoli.com/2024/01/03/%E8%A7%A3%E5%86%B3Nginx%E8%AE%BF%E9%97%AE%E8%87%AA%E7%AD%BEssl%E8%AF%81%E4%B9%A6%E6%8A%A5%E4%B8%8D%E5%AE%89%E5%85%A8%E5%91%8A%E8%AD%A6/", "title": "解决Nginx访问自签ssl证书报不安全告警", "date_published": "2024-01-03T10:01:00.000Z", "content_html": "
1
2
3
4openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -subj "/C=国家/ST=省/L=市/O=机构" -keyout CA-private.key -out CA-certificate.crt -reqexts v3_req -extensions v3_ca
#示例
openssl req -x509 -nodes -days 36500 -newkey rsa:2048 -subj "/C=CN/ST=EZ/L=EZ/O=EZ" -keyout CA-private.key -out CA-certificate.crt -reqexts v3_req -extensions v3_ca
1
openssl genrsa -out private.key 2048
1
openssl req -new -key private.key -subj "/C=CN/ST=EZ/L=EZ/O=EZ/CN=192.168.2.117" -sha256 -out private.csr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15[ req ]
default_bits = 1024
distinguished_name = req_distinguished_name
req_extensions = san
extensions = san
[ req_distinguished_name ]
countryName = CN
stateOrProvinceName = Definesys
localityName = Definesys
organizationName = Definesys
[SAN]
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = IP:192.168.2.117
1
openssl x509 -req -days 36500 -in private.csr -CA CA-certificate.crt -CAkey CA-private.key -CAcreateserial -sha256 -out private.crt -extfile private.ext -extensions SAN
1
2ssl_certificate_key /usr/local/nginx/ssl/private.key;
ssl_certificate /usr/local/nginx/ssl/private.crt;
需要安装 CA-certificate.crt 到受信任的根证书颁发机构下,即可从浏览器正常访问且不会报不安全警告。
\n1
2
3
4
5
6
7
8
9#ssl测试
openssl s_client -connect localhost:8080
#检查证书格式
openssl x509 -in private.crt -text -noout
openssl rsa -in private.key -check
#检查证书是否过期(确保 "notBefore" 小于当前日期,"notAfter" 大于当前日期)
openssl x509 -in private.crt -noout -dates
#查看证书链
openssl x509 -in private.crt -noout -issuer -subject
./configure
\n# 安装目录
\n --prefix=/usr/local/nginx
\n#nginx 运行时的非特权用户
\n --user=nginx
\n#nginx 运行时的非特权用户组
\n --group=nginx
\n#nginx 运行时 pid 的目录
\n --pid-path=/var/run/nginx/nginx.pid
\n# 锁定文件目录,防止误操作,或其他使用
\n --lock-path=/var/lock/nginx.lock
\n#nginx 错误日志目录
\n --error-log-path=/var/log/nginx/error.log
\n#nginx 运行日志目录
\n --http-log-path=/var/log/nginx/access.log
\n# 开启 gz 模块,压缩静态页面
\n --with-http_gzip_static_module
\n--with-http_gunzip_module
\n# 开启 ssl 模块
\n --with-http_ssl_module
\n# 开启 http2 模块
\n --with-http_v2_module
\n#openssl 目录
\n --with-openssl=/home/openssl-3.2.0
\n#nginx 的客户端状态
\n --with-http_stub_status_module
\n--with-http_realip_module
\n# 设定客户端请求的临时目录
\n --http-client-body-temp-path=/usr/local/nginx/client
\n# 设定 http 代理临时目录
\n --http-proxy-temp-path=/usr/local/nginx/proxy
\n# 设定 fastcgi 临时目录
\n --http-fastcgi-temp-path=/usr/local/nginx/fastcgi
\n# 设定 uwsgi 临时目录
\n --http-uwsgi-temp-path=/usr/local/nginx/uwsgi
\n# 设定 scgi 临时目录
\n --http-scgi-temp-path=/usr/local/nginx/scgi
\n1
./configure --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-openssl=/home/openssl-3.2.0
1
make(不要make install)
1
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
1
2
3
4
5
6#关闭nginx
nginx -s stop
#更新nginx
cp /root/nginx-1.24.0/objs/nginx /usr/local/nginx/sbin/
#启动nginx
nginx
1
create database nacos
脚本文件
\n1
docker run -d --restart=always --name="nacos" -e MODE=standalone -p 8848:8848 -p 9848:9848 nacos/nacos-server:latest
1
2
3chmod 777 /home/nacos/conf
chmod 777 /home/nacos/data
chmod 777 /home/nacos/logs
1
2
3docker cp nacos:/home/nacos/conf D:\\docker\\nacos\\data\\
docker cp nacos:/home/nacos/data D:\\docker\\nacos\\data\\
docker cp nacos:/home/nacos/logs D:\\docker\\nacos\\data\\
1
docker run -d --name nacos --restart=always --network my-net -p 8848:8848 -p 9848:9848 -p 9849:9849 -e MODE=standalone --privileged=true -e SPRING_DATASOURCE_PLATFORM=mysql -e MYSQL_SERVICE_HOST=mysql地址 -e MYSQL_SERVICE_PORT=mysql端口 -e MYSQL_SERVICE_USER=mysql用户名 -e MYSQL_SERVICE_PASSWORD=mysql密码 -e MYSQL_SERVICE_DB_NAME=nacos -e TIME_ZONE='Asia/Shanghai' -v D:\\docker\\nacos\\data\\logs:/home/nacos/logs -v D:\\docker\\nacos\\data\\data:/home/nacos/data -v D:\\docker\\nacos\\data\\conf:/home/nacos/conf nacos/nacos-server:latest
今天在把对象转为 json 时需要去除 key 或者 value 为 null 或空字符串的属性,特此记录一下后续好复用。
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public static String toJSONString(Object object) {
SerializerFeature[] serializerFeatures = new SerializerFeature[] {
//格式化时间
SerializerFeature.WriteDateUseDateFormat
};
return JSON.toJSONString(object, new ValueFilter() {
public Object process(Object object, String name, Object value) {
// 如果名称或者值为null或空字符串,则不序列化该属性
if (name == null || (name instanceof String && ((String) name).isEmpty()) ||
value == null || (value instanceof String && ((String) value).isEmpty())) {
return null;
}
return value;
}
}, serializerFeatures);
}
只需要把 jar 和 yml 跟脚本放在同一目录下即可快速启动。
\n拷贝以下代码放入 txt 文本,然后改为 start.sh
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
export CLOUD_HOME=`pwd`
# 获取当前目录中的第一个JAR文件的名称
jar_file=$(find . -maxdepth 1 -type f -name "*.jar" | head -n 1)
if [ -n "$jar_file" ]; then
jar_file=${jar_file#./}
#echo "JAR文件的名称是: $jar_file"
jar_file_name=$(basename "$jar_file" .jar)
else
echo "当前目录没有JAR文件."
exit
fi
# 获取当前目录中的第一个yml文件的名称
yml_file=$(find . -maxdepth 1 -type f -name "*.yml" | head -n 1)
if [ -n "$yml_file" ]; then
yml_file=${yml_file#./}
#echo "YML文件的名称是: $yml_file"
else
echo "当前目录中没有YML文件."
fi
pids=$(ps -ef | grep java | grep $jar_file_name | grep -v grep | awk '{print $2}')
for pid in $pids; do
echo "$jar_file_name is running, pid="$pid
exit 0
done
echo "$jar_file_name is pedding..."
sleep 3
JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF8"
JAVA_OPTS="$JAVA_OPTS -Dsun.jnu.encoding=UTF8 -Xms512m -Xmx1024m"
JAVA_OPTS="$JAVA_OPTS -Dpid.path=$CLOUD_HOME/temp -Dspring.config.additional-location=$CLOUD_HOME/$yml_file"
JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5007"
nohup java $JAVA_OPTS -jar $CLOUD_HOME/$jar_file >/dev/null 2> $CLOUD_HOME/$jar_file_name.run &
#nohup java $JAVA_OPTS -jar $CLOUD_HOME/$jar_file > $CLOUD_HOME/$jar_file_name.run 2>&1 &
echo "$jar_file_name started."
拷贝以下代码放入 txt 文本,然后改为 stop.sh
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
export CLOUD_HOME=`pwd`
# 获取当前目录中的第一个JAR文件的名称
jar_file=$(find . -maxdepth 1 -type f -name "*.jar" | head -n 1)
if [ -n "$jar_file" ]; then
jar_file=${jar_file#./}
#echo "JAR文件的名称是: $jar_file"
jar_file_name=$(basename "$jar_file" .jar)
else
echo "当前目录没有JAR文件."
exit
fi
# 获取当前目录中的第一个yml文件的名称
yml_file=$(find . -maxdepth 1 -type f -name "*.yml" | head -n 1)
if [ -n "$yml_file" ]; then
yml_file=${yml_file#./}
#echo "YML文件的名称是: $yml_file"
else
echo "当前目录中没有YML文件."
fi
pids=$(ps -ef | grep java | grep $jar_file_name | grep -v grep | awk '{print $2}')
for pid in $pids; do
kill -9 $pid
done
echo "$jar_file_name is stopping..."
sleep 5
echo "$jar_file_name stopped."
今天闲着无事就把我的老笔记本拆了,清理了一下灰尘。笔记本已经 10 多年了,中间加过内存,换过固态硬盘。清理一下还能发挥它的余热!
\n
\n
\n
\n
\n
\n
\n
只需要把 jar 和 yml 跟批处理放在同一目录下即可点击快速启动。启动后再次点击会关闭上次启动的窗口并重新启动。
\n拷贝以下代码放入 txt 文本,然后改为 start.bat
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63@ECHO OFF
setlocal enabledelayedexpansion
REM 关闭上次进程
SET "pidFile=pid.txt"
if exist "%pidFile%" (
\tfor /f "usebackq" %%a in ("pid.txt") do (
\t\tset PID=%%a
\t)
\tif not "!PID!"=="" (
\t\ttaskkill /F /T /PID !pid!
\t\tdel pid.txt
\t)
)
REM 存储当前进程
for /f %%i in ('wmic process where "name='cmd.exe' and CommandLine like '%%<scriptname>.bat%%'" get ParentProcessId ^| findstr /r "[0-9]"') do set pid=%%i
echo %PID% > pid.txt
REM 设置title
for /f "tokens=2" %%i in ('chcp') do set codepage=%%i
chcp 65001 > nul
title 我的SpringBoot项目
chcp %codepage% > nul
cd %~dp0
REM 获取jar
set "jarFile="
for %%i in (*.jar) do (
if not defined jarFile (
set "jarFile=%%i"
)
)
if not defined jarFile (
echo not find jar
pause
exit
)
SET JAVA_OPTS=-Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF-8
set JAVA_OPTS=%JAVA_OPTS% -Dsun.jnu.encoding=UTF8 -Xms512m -Xmx1024m
set JAVA_OPTS=%JAVA_OPTS% -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5007
set JAVA_OPTS=%JAVA_OPTS% -Dpid.path=./temp
REM 获取yml
set "ymlFile="
for %%i in (*.yml) do (
if not defined ymlFile (
set "ymlFile=%%i"
)
)
if defined ymlFile (
\tset JAVA_OPTS=%JAVA_OPTS% -Dspring.config.additional-location=!ymlFile!
) else (
\techo not find yml
)
REM 启动服务
java %JAVA_OPTS% -jar !jarFile!
pause
拷贝以下代码放入 txt 文本,然后改为 stop.bat
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@ECHO OFF
setlocal enabledelayedexpansion
REM 关闭上次进程
SET "pidFile=pid.txt"
if exist "%pidFile%" (
\tfor /f "usebackq" %%a in ("pid.txt") do (
\t\tset PID=%%a
\t)
\tif not "!PID!"=="" (
\t\ttaskkill /F /T /PID !pid!
\t\tdel pid.txt
\t)
)
exit
1
fdisk -l 查看当前磁盘的分区情况
\n
\n 可从图中获取以下信息:
\n/dev/vdb 数据盘容量为 60GB,包含 MBR 分区 /dev/vdb1,容量为 50GB。
\n/dev/vdc 数据盘容量为 60GB,包含 GPT 分区 /dev/vdc1,容量为 50GB。
1
df -TH 分区的文件系统类型
\n
\n 可从图中获取以下信息:
\n/dev/vdb1 文件系统类型为 ext4,已挂载至 /mnt/disk1。
\n/dev/vdc1 文件系统类型为 xfs,已挂载至 /mnt/disk2。
\n1
fdisk /dev/vdb 查看新磁盘情况
\n
\n1
lsbl 查看分区情况
\n
1
mkfs.ext4 /dev/vdb 格式化磁盘
\n
\n1
2
3cd /mnt
mkdir data 新建挂载点
mount /dev/vdb /mnt/data 挂载
\n1
df -h 查看挂载情况
\n
\n 查看 UUID 有三种方式:
\n1
blkid
\n
\n1
lsblk -f
\n
\n1
ll /dev/disk/by-uuid/
\n
\n1
2
3
4设置自动挂载:
echo "UUID=c8ac09ca-fd4d-4511-bd2c-4fdf96f08168 /data ext4 defaults 0 0" >> /etc/fstab
自动挂载/etc/fstab里面的东西
mount -a
1
umount /dev/vdb 重启机器之后又恢复到挂载状态
1
vim /etc/fstab 把添加的磁盘信息删除即可。
科学上网的方法有多种,有很多第三方提供的免费方案,这些方案优缺点暂时不予讨论。实际工作生活中还是会有需要自己搭建的情况,这次介绍的是使用 squid+stunnel 方案进行搭建。
\n一台可以访问外网的服务器,如香港的云主机并安装 Ubuntu 系统。
\n1
apt-get install -y squid
生成用户文件
\n1
2apt-get install apache2-utils
htpasswd -c /etc/squid/squid_user.txt 用户名
修改 squid 配置
\n 1、直接修改 /etc/squid/squid.conf 文件
\n 2、修改 /etc/squid/conf.d/debian.conf 文件
\n两种方式都一样,在底部加入以下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40#dns服务器地址
dns_nameservers 8.8.8.8 8.8.4.4
dns_v4_first on
# 监听端口
http_port 3128
# 定义squid密码文件与ncsa_auth文件位置
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/squid_user.txt
# 认证进程的数量
auth_param basic children 15
# 认证对话框显示提示信息
auth_param basic realm Squid proxy-caching web server
# 认证有效期
auth_param basic credentialsttl 24 hours
# 是否区分用户名大小,off为不区分
auth_param basic casesensitive off
# 对定义的squid_user文件内的用户开启认证访问
acl 用户名 proxy_auth REQUIRED
# 允许squid_user文件内用户进行代理
http_access allow 用户名
# 顺序匹配,最后添加拒绝所有未允许的规则。不添加会发现,未匹配到的规则会被放行
http_access deny all
# 缓存设置
cache_dir ufs /var/spool/squid 100 16 256 read-only
cache_mem 0 MB
coredump_dir /var/spool/squid
# 配置高匿,不允许设置任何多余头信息,保持原请求header。
header_access Via deny all
header_access X-Forwarded-For deny all
header_access Server deny all
header_access X-Cache deny all
header_access X-Cache-Lookup deny all
forwarded_for off
via off
# logs相关配置
emulate_httpd_log on
logformat squid %{X-Forwarded-For}>h %ui %un [%tl] "%rm %ru HTTP/%rv" %Hs %<st "%{Referer}>h" "%{User-Agent}>h" %Ss:%Sh
access_log /var/log/squid/access.log squid
cache_log /var/log/squid/cache.log
cache_store_log /var/log/squid/store.log
logfile_rotate 20
至次已经可以通过填写安装 squid 的服务器 ip 加端口 3128 加用户名密码进行代理访问了(通过访问 https://www.ip.cn/ 查看 ip 就会发现自己的出口 ip 已经变成了 squid 服务器的 ip 了)。但是要想科学上网还必须对代理的数据进行加密,否则访问外网还是会被我国的长城防火墙阻挡,所以还需要安装 stunnel 来实现此目的。
\n1
apt-get install -y stunnel
1
openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem
3、将证书 stunnel.pem 放到 /etc/stunnel/ 目录下
\n4、修改 stunnel 配置 (/etc/stunnle/stunnle.conf)
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22; 设置stunnel的pid文件路径
pid = /etc/stunnel/stunnel.pid
; 设置stunnel工作的用户(组)
setuid = root
setgid = root
; 开启日志等级:emerg (0), alert (1), crit (2), err (3), warning (4), notice (5), info (6), or debug (7)
debug = 7
; 日志文件路径
output = /etc/stunnel/stunnel.log
; 证书文件
cert = /etc/stunnel/stunnel.pem
; 私钥文件
key = /etc/stunnel/stunnel.pem
; 自定义服务名squid-proxy
[squid-proxy]
; 服务监听的端口,client要连接这个端口与server通信
accept = 1234(自定义)
; 服务要连接的端口,连接到squid的3128端口,将数据发给squid
connect = 3128
可以安装在要代理的机器上,在需要代理的情况下再开启(代理地址填 127.0.0.1 加客户端监听端口)。也可以安装在国内的服务器上一直保持连接(代理信息填国内服务器 ip 加客户端监听端口)。本示例客户端为 windows 系统
\nhttps://www.stunnel.org/downloads.html
\n1
2
3
4
5
6
7
8
9
10
11[squid-proxy]
client = yes
; 监听3128端口,那么用户浏览器的代理设置就是 stunnel-client-ip:3128
accept = 3128
; 要连接到的stunnel server的ip与端口
connect = stunnel服务端ip:1234(服务端自定义端口)
; 需要验证对方发过来的证书
verify = 2
; 用来进行证书验证的文件(stunnel服务端生成的证书复制到以下目录并改名为stunnel-server.pem)
CAfile = C:\\Program Files (x86)\\stunnel\\config\\stunnel-server.pem
至次配置好代理 ip 为 stunnel 客户端 ip 加端口 3128 就可以正式科学上网了。如果只想对需要科学的 url 进行代理,可以通过安装 Proxy SwitchyOmega 插件实现(规则地址可通过 https://github.com/gfwlist/gfwlist 获取)。
\n
\n
1、按【Win+X】
\n2、选择【终端管理员】
\n3、输入以下命令并回车:
\n reg add "HKCU\\Software\\Classes\\CLSID\\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\\InprocServer32" /f /ve
4、重启电脑
\n", "tags": [ "Windows", "系统优化", "Windows 11" ] }, { "id": "https://hitoli.com/2023/07/08/%E8%A7%A3%E5%86%B3Lombok%E6%8A%A5%E9%94%99/", "url": "https://hitoli.com/2023/07/08/%E8%A7%A3%E5%86%B3Lombok%E6%8A%A5%E9%94%99/", "title": "解决Lombok报错", "date_published": "2023-07-08T02:51:00.000Z", "content_html": "java: You aren’t using a compiler supported by lombok, so lombok will not work and has been disabled.
\nYour processor is: com.sun.proxy.$Proxy26
\nLombok supports: OpenJDK javac, ECJ
问题分析
\n属于 lombok 编译不通过,原因可能是因为依赖没有更到最新版本
解决办法
\n在 IntelliJ IDEA 的全局配置 Compiler 中添加如下配置:
\n1
-Djps.track.ap.dependencies=false
\n
该篇文章介绍了如何对 shoka 主题进行 jsdelivr 聚合拆分,以便使用国内镜像源和异步加载,从而优化网站速度。具体操作包括更改模板、注册 helper 和更改配置。其中,推荐使用 advVendors 配置,可自定义加载源和 js 文件名,同时支持异步加载、pjax 刷新和 integrity 防 XXS 等特性。
\n众所周知,jsdelivr 在国内的速度可以用慢的一批来形容而 shoka 主题使用了 jsdelivr 的 combine 功能加载第三方 js, 而 combine 在国内没有镜像源并且阻断了使用 CDN 并发加速的道路,本篇博文会将 jsdelivr 聚合拆分为几个独立的 js, 以便使用国内镜像源和异步加载。
\n此方案相较于本地化而言有较大速度优势,尤其在 CDN 并发加持下
\n打开 shoka\\layout_partials\\layout.njk,找到第 144 行左右:
\n1
2
3
4<script src="https://cdn.polyfill.io/v3/polyfill.js"></script>
{{ _vendor_js() }}
{{ _js('app.js') }}
{{ partial('_partials/third-party/baidu-analytics.njk', {}, {cache: true}) }}
更改为如下内容:
\n1
2
3
4
5
6
7
8
9
10<script src="https://cdn.polyfill.io/v3/polyfill.js"></script>
{%- if theme.advVendors.enable %}
\t{% for i in _list_vendor_js() %}
\t\t{{ _adv_vendor_js(i) }}
\t{% endfor %}
{%- else %}
{{ _vendor_js() }}
{%- endif %}
{{ _js('app.js')}}
{{ partial('_partials/third-party/baidu-analytics.njk', {}, {cache: true}) }}
打开 shoka\\scripts\\helpers\\asset.js, 最后一行新建空行,增加如下内容:
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31hexo.extend.helper.register('_list_vendor_js', () => {
return hexo.theme.config.vendorsList.js;
});
hexo.extend.helper.register('_adv_vendor_js', function (js_name) {
const config = hexo.theme.config.advVendors.js[js_name];
const src = config["src"];
let result;
if (src.indexOf("http") !== -1) {
result = src;
} else if (src.indexOf("combine") !== -1) {
console.log("The combine feature is not recommended!")
result = hexo.theme.config.advVendors.combine + src;
} else if (src.indexOf("npm") !== -1) {
result = hexo.theme.config.advVendors.npm + src.slice(4);
} else if (src.indexOf("gh") !== -1) {
result = hexo.theme.config.advVendors.github + src.slice(3);
} else {
result = "/" + src;
}
let attr = {src: result};
if (config["async"]) attr["async"] = "async";
if (config["data-pjax"]) attr["data-pjax"] = "data-pjax";
if (config["hash-value"]) attr["integrity"]=config["hash-value"];
if (config["deferLoad"]) {
return htmlTag('script', {"data-pjax": true}, `
const script=document.createElement("script");script.src="${result}",script.async=true,document.body.appendChild(script)
`)
}
return htmlTag('script', attr, '');
})
在 shoka 目录下 _config.yml 增加如下内容:
\n推荐内容,可根据自己情况更改
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47advVendors:
enable: true
github: "https://cdn.jsdelivr.net/gh/"
combine: "https://cdn.jsdelivr.net/"
npm: "https://unpkg.com/"
js:
pace:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/pace/1.0.2/pace.min.js
pjax:
src: https://lib.baomitu.com/pjax/0.2.8/pjax.min.js
fetch:
src: npm/whatwg-fetch@3.4.0/dist/fetch.umd.js
anime:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/animejs/3.2.0/anime.min.js
algolia:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/algoliasearch/4.12.1/algoliasearch-lite.umd.min.js
instantsearch:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/instantsearch.js/4.39.0/instantsearch.production.min.js
lazyload:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/lozad.js/1.16.0/lozad.min.js
quicklink:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/quicklink/2.2.0/quicklink.umd.min.js
fancybox:
src: https://lf26-cdn-tos.bytecdntp.com/cdn/expire-1-M/??jquery/3.5.1/jquery.min.js,fancybox/3.5.7/jquery.fancybox.min.js,justifiedGallery/3.8.1/js/jquery.justifiedGallery.min.js
async: true
valine:
src: gh/amehime/MiniValine@4.2.2-beta10/dist/MiniValine.min.js
copy_tex:
src: https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/KaTeX/0.12.0/contrib/copy-tex.min.js
async: true
chart:
src: npm/frappe-charts@1.5.0/dist/frappe-charts.min.iife.js
vendorsList:
js:
- pace
- pjax
- fetch
- anime
- algolia
- instantsearch
- lazyload
- quicklink
- fancybox
- valine
- copy_tex
- chart
下面为结构详解:
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16advVendors:
\tenable: true #是否开启,关闭使用主题默认加载
\tgithub: #github 使用的加载源,需要协议头和末尾斜杠
\tcombine: #聚合 js 使用的加载源 (不建议使用)
\tnpm: #npm 的加载源
\tjs:
\t\tjspackage: #js 名,可以与文件名不一致
\t\t\tsrc: "资源地址,详情见后面"
\t\t\t# async: true 异步加载此 js
\t\t\t# data-pjax: true 在 pjax 加载时刷新此 js
\t\t\t# hash-value: 这个资源的 integrity 值,用于防 XXS
\t\t\t# deferLoad: true 使用动态 DOM 节点添加延迟 js 加载 (实验性)
vendorsList:
js:
\t- jspackage #与上方 jspackage 一致即可
资源地址格式如下:
\nhttps://example.com/xxx.js 使用 http (s) 地址加载 js
\ncombine/xxx.js,xxx.js 使用 jsdelivr 的 combine 功能加载 (不推荐)
\nnpm/xxx/xxx.js 使用 npm 源加载 js
\ngh/xxx/xxx.js 使用 gh 源加载 js
\nxxx.js 从本地加载 js
\n 优先级如下:
\nhttp>combine>npm>gh > 本地
http 请求发起后接收不到返回数据!!!【测试环境没出问题,发到正式环境就有问题】
\n项目中通过 restTemplate 发起请求:
\n1
2
3
4
5
6
7
8log.info("请求入参:{}",JSON.toJSONString(request));//打印日志1
// 配置http请求的连接超时时间和读取超时时间
HttpsClientRequestFactory factory = new HttpsClientRequestFactory();
factory.setConnectTimeout(60 * 1000);
factory.setReadTimeout(5 * 60 * 1000);
RestTemplate restTemplate = new RestTemplate(factory);
Result<InventoryResult> result = restTemplate.postForObject(address.concat(inventoryUrl), request, Result.class);
log.info("库存同步,返回数据: {}", result);//打印日志2
http 请求入参:{data=[{ productStatus=10,skuCode=null}], messageId=ewpfpr1t6ey5r6qj0su0w1h6rt73hr,token=vgvU5EJKuZbuHii7WH6pTINp40ZRicaqLz4dq5P7L6pDzWir8EEGZhCKPucQjljsw69EHasEy+iJfdTofDg==}
\n打印日志 2 内容为:没有打印内容!!!
\n最后发现是因为测试环境中数据量较小,http 请求后,很快能得到相应,而正式环境数据量较大,没有及时得到响应,连接或者读取超时!!!
\n添加 HttpsClientRequestFactory 类,并继承 SimpleClientHttpRequestFactory
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57/**
* 兼容调Https接口
*/
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
protected void prepareConnection(HttpURLConnection connection, String httpMethod)
throws IOException {
if (connection instanceof HttpsURLConnection) {
prepareHttpsConnection((HttpsURLConnection) connection);
}
super.prepareConnection(connection, httpMethod);
}
private void prepareHttpsConnection(HttpsURLConnection connection) {
connection.setHostnameVerifier(new SkipHostnameVerifier());
try {
connection.setSSLSocketFactory(createSslSocketFactory());
}
catch (Exception ex) {
// Ignore
}
}
private SSLSocketFactory createSslSocketFactory() throws Exception {
SSLContext context = SSLContext.getInstance("TLS");
context.init(null, new TrustManager[] { new SkipX509TrustManager() },
new SecureRandom());
return context.getSocketFactory();
}
private class SkipHostnameVerifier implements HostnameVerifier {
public boolean verify(String s, SSLSession sslSession) {
return true;
}
}
private static class SkipX509TrustManager implements X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
public void checkClientTrusted(X509Certificate[] chain, String authType) {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) {
}
}
}
使用 restTemplate 发起请求前先设置连接和超时时间或者通过容器加载配置类然后设置超时时间
\n 1
2
3
4
5
6//配置http请求的连接超时时间和读取超时时间
HttpsClientRequestFactory factory = new HttpsClientRequestFactory();
factory.setConnectTimeout(60 * 1000);
factory.setReadTimeout(5 * 60 * 1000);
RestTemplate restTemplate = new RestTemplate(factory);
BaseResult<QueryInventoryResult> result = restTemplate.postForObject(address.concat(inventoryUrl), request, Result.class);
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class RestConfig {
//60 * 1000
private int connectTimeout;
//5 * 60 * 1000
private int readTimeout;
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory();
simpleClientHttpRequestFactory.setConnectTimeout(connectTimeout);
simpleClientHttpRequestFactory.setReadTimeout(readTimeout);
RestTemplate restTemplate = new RestTemplate(simpleClientHttpRequestFactory);
return restTemplate;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class RestConfig {
//60 * 1000
private int connectTimeout;
//5 * 60 * 1000
private int readTimeout;
private int connectionRequestTimeout;
/**
* 使用 HttpComponentsClientHttpRequestFactory创建http请求(推荐)
*/
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
httpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout);
httpRequestFactory.setConnectTimeout(connectTimeout);
httpRequestFactory.setReadTimeout(readTimeout);
return new RestTemplate(httpRequestFactory);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class RestConfig {
/**
* 高并发采用HttpClient连接池
*/
public RestTemplate restTemplate() {
return new RestTemplate(httpRequestFactory());
}
public ClientHttpRequestFactory httpRequestFactory() {
return new HttpComponentsClientHttpRequestFactory(httpClient());
}
public HttpClient httpClient() {
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
//设置整个连接池最大连接数
connectionManager.setMaxTotal(1000);
\t\t\t//路由是对maxTotal的细分
connectionManager.setDefaultMaxPerRoute(100);
//定义不活动的时间(毫秒),超过的连接从连接池拿取需要重新验证
connectionManager.setValidateAfterInactivity(200);
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(30000) //返回数据的超时时间
.setConnectTimeout(20000) //连接上服务器的超时时间
.setConnectionRequestTimeout(1000) //从连接池中获取连接的超时时间
.build();
return HttpClientBuilder.create()
.setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.evictIdleConnections(2, TimeUnit.SECONDS) //保持空闲的最大时间
.build();
}
}
当我们迁移到 MySQL 5.7+ 的版本时,常会碰到 ERROR 1055 only_full_group_by 错误,这是 5.7 之后 SQL_MODE 默认打开了严格模式导致的错误。说明你代码里有地方写的不严谨。
\n1 | #查询sql_mode |
业务要求:查询所有省份:
\n
distinct 排除重复
\n 1
2
3
4
5SELECT
\tDISTINCT(province_code),
\tprovince_name
FROM
\tt_mip_base_area
group by 根据身份编码分组
\n 1
2
3
4
5SELECT
\tprovince_code,
\tany_value(province_name)
FROM t_mip_base_area
GROUP BY province_code
1
2
3
4
5SELECT
province_code,
province_name
FROM t_mip_base_area
GROUP BY province_code
\n 则会报错
\n
MySQL5.7 之后,sql_mode 中 ONLY_FULL_GROUP_BY 模式默认设置为打开状态。
\nONLY_FULL_GROUP_BY 的语义就是确定 select target list 中的所有列的值都是明确语义,简单的说来,在此模式下,target list 中的值要么是来自于聚合函数(sum、avg、max 等)的结果,要么是来自于 group by list 中的表达式的值
\nMySQL 提供了 any_value () 函数来抑制 ONLY_FULL_GROUP_BY 值被拒绝
\nany_value () 会选择被分到同一组的数据里第一条数据的指定列值作为返回数据
\n1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36#windows系统修复
DISM.exe /Online /Cleanup-image /Scanhealth
DISM.exe /Online /Cleanup-image /Restorehealth
sfc /scannow
#windows网络修复
netsh winsock reset
#合并ts文件
copy /b c:\\Users\\hito\\Downloads\\*.ts d:\\50.mp4
#windows11家庭版安装Hyper-V(以下命令拷贝到txt文本后重命名为bat文件然后用管理员权限运行)
pushd "%~dp0"
dir /b %SystemRoot%\\servicing\\Packages\\*Hyper-V*.mum >> hyper-v.txt
for /f %%i in ('findstr /i . hyper-v.txt 2^>nul') do dism /online /norestart /add-package:"%SystemRoot%\\servicing\\Packages\\%%i"
del hyper-v.txt
Dism /online /enable-feature /featurename:Microsoft-Hyper-V-All /LimitAccess /ALL
#mysql查看索引使用情况
EXPLAIN SELECT A.XXX, B.XXX FROM TABLE1 A, TABLE2 B ON A.XXX = B.XXX;
#mysql使用触发器插入数据自动生产uuid主键
insert_before BEFORE 插入
BEGIN
\t\t\tSET NEW.ID = REPLACE(UUID(),"-","");
END
#excel
#合并
=CONCATENATE("EX010",D2,C2,"1")
#截断
=LEFT(E2,6)
#判断取值
=IF(O2="宜都市","420500000000000",IF(O2="高新区","420592000000000",IF(O2="枝江市","420583000000000",IF(O2="当阳市","420582000000000",IF(O2="远安县","420525000000000",IF(O2="兴山县","420526000000000",IF(O2="秭归县","420527000000000",IF(O2="长阳","420528000000000",IF(O2="五峰县","420529000000000",IF(O2="夷陵区","420506000000000",IF(O2="点军区","420504000000000",IF(O2="西陵区","420502000000000",IF(OR(ISNUMBER(FIND({"伍家";"伍家区";"伍家岗区"},O2))),"420503000000000",IF(OR(ISNUMBER(FIND({"猇亭";"猇亭区"},O2))),"420505000000000"))))))))))))))
#经纬度计算
=LEFT(T2,FIND("°",T2)-1)+MID(T2,FIND("°",T2)+1,FIND("′",T2)-FIND("°",T2)-1)/60+MID(T2,FIND("′",T2)+1,FIND("″",T2)-FIND("′",T2)-1)/3600
1 | { |
1 | docker login 192.168.86.117 |
1 | docker images命令也可以看到本机docker镜像 |
1 | 把本机docker镜像上传到KUBESPHERE |
注意:54XXXXXXXXC8 为光猫背后 ONU MAC:54-XX-XX-XX-XX-C8
\n1 | 获取telnet的用户名 |
1 | su |
1 | cfg_cmd get InternetGatewayDevice.DeviceInfo.X_CT-COM_TeleComAccount.Password | grep 'get success' | cut -d = -f 2 |
两种命令模式:
\n1 | load_cli factory |
1 | show admin_pwd |
1 | ? |
1 | show ? |
1 | set ? |
1 | show allinfo |
1 | cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetEnable 1 |
1 | cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.FtpEnable 1 |
1 | cfg_cmd get InternetGatewayDevice.X_CT-COM_UserInfo.UserName |grep 'value' |cut -d '=' -f 2 |
1 | cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.Enable |
1 | cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_MiddlewareMgt.Tr069Enable 0 |
1 | cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.IP_Routed='' |
1 | cfg_cmd get InternetGatewayDevice.Services.X_CT-COM_MWBAND.TotalTerminalNumber |
1 | cfg_cmd set InternetGatewayDevice.Services.X_CT-COM_MWBAND.TotalTerminalNumber 255 |
1 | cfg_cmd get InternetGatewayDevice.X_CT-COM_UserInfo.UserName |
1 | cfg_cmd help |
1 | cfg_cmd shownode InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1. [0|1] |
网上搜索了几个修改 IDE 背景色和字体大小的方法,记录一下方便以后查看。
\n修改代码区颜色
\n
修改代码区字体大小
\n
修改控制台背景色
\n
修改控制台字体大小
\n
修改左侧背景色
\n
修改 UI 字体大小
\n
使用指定数据方式得到的线条总是不能达到想要的效果,一是阶梯线在价格发生变化的点存在倾斜,二是高低价的线很难完美显示,所以只能自己动手画了。代码仅供参考,如有错误的地方请指正!
\n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228this.chart.addTechnicalIndicatorTemplate({
\tname: 'BandHighLowInd',
shortName: '波段高低价',
calcParams: [{ value: 10 }],
precision: 2,
plots: [
{ key: 'high', title: '高: ', color: (data, options) => { return options.line.colors[0] } },
{ key: 'low', title: '低: ', color: (data, options) => { return options.line.colors[1] } },
{ key: 'highLow', title: '高低: ', color: (data, options) => { return options.line.colors[2] } }
],
calcTechnicalIndicator: (dataList, { params }) => {
let compareKlineSize = params[0] //取前后k的范围
let highs = []
let lows = []
let highLows = []
let findHigh //高低价当前查找标识
dataList.forEach(function (kLineData, i) {
let frontIndex = i - compareKlineSize
if (frontIndex <= 0) {
frontIndex = 0
}
let frontDatas = []
if (frontIndex < i) {
frontDatas = dataList.slice(frontIndex, i)
}
let afterIndex = i + compareKlineSize + 1
let afterDatas = []
if (i < dataList.length - 1) {
afterDatas = dataList.slice(i + 1, afterIndex)
}
if (frontDatas.length === compareKlineSize && afterDatas.length === compareKlineSize) { // 前后K线数据必须都找到要比较的条数
let high = null
let low = null
frontDatas.concat(afterDatas).forEach(function (kLineData, i) { // 找出前后k线中的最高和最低价
if (high === null || kLineData.high > high) {
high = kLineData.high
}
if (low === null || kLineData.low < low) {
low = kLineData.low
}
})
if (kLineData.high > high) { // 当前k的高大于前后k中找出的高,则放入高价集合中
highs.push({
time: kLineData.timestamp,
value: kLineData.high,
index: i
})
if (isNotEmpty(findHigh)) {
if (findHigh && highLows[highLows.length - 1].value !== kLineData.high) {
highLows.push({
time: kLineData.timestamp,
value: kLineData.high,
index: i
})
findHigh = false
}
} else {
highLows.push({
time: kLineData.timestamp,
value: kLineData.high,
index: i
})
findHigh = false
}
}
if (kLineData.low < low) { // 当前k的低小于前后k中找出的低,则放入低价集合中
lows.push({
time: kLineData.timestamp,
value: kLineData.low,
index: i
})
if (isNotEmpty(findHigh)) {
if (!findHigh && highLows[highLows.length - 1].value !== kLineData.low) {
highLows.push({
time: kLineData.timestamp,
value: kLineData.low,
index: i
})
findHigh = true
}
} else {
highLows.push({
time: kLineData.timestamp,
value: kLineData.low,
index: i
})
findHigh = true
}
}
}
})
let high, low, highLow
return dataList.map((kLineData, i) => {
let item = {
}
highs.forEach((data) => {
if (kLineData.timestamp === data.time) {
high = data.value
item.highOrigin = true
}
})
if (isNotEmpty(high)) { // 持续先前的高,画出阶梯线
item.timestamp = kLineData.timestamp
item.high = high
}
lows.forEach((data) => {
if (kLineData.timestamp === data.time) {
low = data.value
item.lowOrigin = true
}
})
if (isNotEmpty(low)) { // 持续先前的低,画出阶梯线
item.timestamp = kLineData.timestamp
item.low = low
}
highLows.forEach((data) => {
if (kLineData.timestamp === data.time) {
highLow = data.value
item.highLowOrigin = true
}
})
if (isNotEmpty(highLow)) { // 持续先前的高低,方便title显示
item.timestamp = kLineData.timestamp
item.highLow = highLow
}
return item
})
},
render: ({ ctx, dataSource, viewport, styles, xAxis, yAxis }) => {
if (dataSource.technicalIndicatorDataList.length <= 0) {
return
}
let x = xAxis.convertToPixel(0)
let high, low, highLow, highLowX
dataSource.technicalIndicatorDataList.forEach(function (data, i) {
// 画高线
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
ctx.fillStyle = '#fff'
ctx.strokeStyle = '#fff'
if (styles.line && styles.line.colors && styles.line.colors[0]) {
ctx.fillStyle = styles.line.colors[0]
ctx.strokeStyle = styles.line.colors[0]
}
if (isNotEmpty(high) && data.high !== high) {
ctx.beginPath()
ctx.moveTo(x, yAxis.convertToPixel(high))
ctx.lineTo(x, yAxis.convertToPixel(data.high))
ctx.stroke()
ctx.closePath()
}
high = data.high
let y = yAxis.convertToPixel(high)
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + viewport.dataSpace, y)
ctx.stroke()
ctx.closePath()
// 画低线
ctx.fillStyle = '#fff'
ctx.strokeStyle = '#fff'
if (styles.line && styles.line.colors && styles.line.colors[1]) {
ctx.fillStyle = styles.line.colors[1]
ctx.strokeStyle = styles.line.colors[1]
}
if (isNotEmpty(low) && data.low !== low) {
ctx.beginPath()
ctx.moveTo(x, yAxis.convertToPixel(low))
ctx.lineTo(x, yAxis.convertToPixel(data.low))
ctx.stroke()
ctx.closePath()
}
low = data.low
y = yAxis.convertToPixel(low)
ctx.beginPath()
ctx.moveTo(x, y)
ctx.lineTo(x + viewport.dataSpace, y)
ctx.stroke()
ctx.closePath()
// 画高低线
if (isNotEmpty(data.highLow) && data.highLowOrigin === true) {
if (isNotEmpty(highLow) && isNotEmpty(highLowX)) {
ctx.fillStyle = '#fff'
ctx.strokeStyle = '#fff'
if (styles.line && styles.line.colors && styles.line.colors[2]) {
ctx.fillStyle = styles.line.colors[2]
ctx.strokeStyle = styles.line.colors[2]
}
ctx.beginPath()
ctx.moveTo(highLowX, yAxis.convertToPixel(highLow))
ctx.lineTo(x, yAxis.convertToPixel(data.highLow))
ctx.stroke()
ctx.closePath()
}
highLow = data.highLow
highLowX = x
}
// 画价格
if (data.high !== undefined && data.highOrigin === true) { // 画高价
let text = Number.parseFloat(data.high).toFixed(2)
ctx.fillStyle = '#fff'
if (styles.line && styles.line.colors && styles.line.colors[0]) {
ctx.fillStyle = styles.line.colors[0]
}
let offset = 10
let y = yAxis.convertToPixel(data.high)
for (let i = 2; i < offset - 1; i += 2) {
ctx.fillText('.', x, y - i)
}
ctx.fillText(text, x, y - offset)
}
if (data.low !== undefined && data.lowOrigin === true) { // 画低价
let text = Number.parseFloat(data.low).toFixed(2)
ctx.fillStyle = '#fff'
if (styles.line && styles.line.colors && styles.line.colors[1]) {
ctx.fillStyle = styles.line.colors[1]
}
let offset = 15
let y = yAxis.convertToPixel(data.low)
for (let i = 2; i < offset - 5; i += 2) {
ctx.fillText('.', x, y + i)
}
ctx.fillText(text, x, y + offset)
}
x += viewport.dataSpace
})
}
})