{ "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": "
openssl 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 |
openssl genrsa -out private.key 2048 |
openssl req -new -key private.key -subj \"/C=CN/ST=EZ/L=EZ/O=EZ/CN=192.168.2.117\" -sha256 -out private.csr |
[ 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 |
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 |
ssl_certificate_key /usr/local/nginx/ssl/private.key; | |
ssl_certificate /usr/local/nginx/ssl/private.crt; |
需要安装 CA-certificate.crt 到受信任的根证书颁发机构下,即可从浏览器正常访问且不会报不安全警告。
\n#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
./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 |
make(不要make install) |
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old |
#关闭 nginx | |
nginx -s stop | |
#更新 nginx | |
cp /root/nginx-1.24.0/objs/nginx /usr/local/nginx/sbin/ | |
#启动 nginx | |
nginx |
create database nacos |
脚本文件
\ndocker run -d --restart=always --name=\"nacos\" -e MODE=standalone -p 8848:8848 -p 9848:9848 nacos/nacos-server:latest |
chmod 777 /home/nacos/conf | |
chmod 777 /home/nacos/data | |
chmod 777 /home/nacos/logs |
docker 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\\ |
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 或空字符串的属性,特此记录一下后续好复用。
\npublic static String toJSONString(Object object) { | |
SerializerFeature[] serializerFeatures = new SerializerFeature[] { | |
// 格式化时间 | |
SerializerFeature.WriteDateUseDateFormat | |
}; | |
return JSON.toJSONString(object, new ValueFilter() { | |
@Override | |
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
\n#!/bin/bash | |
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
\n#!/bin/bash | |
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
\n@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
\n@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 |
fdisk -l 查看当前磁盘的分区情况 |
\n可从图中获取以下信息:
\n/dev/vdb 数据盘容量为 60GB,包含 MBR 分区 /dev/vdb1,容量为 50GB。
\n/dev/vdc 数据盘容量为 60GB,包含 GPT 分区 /dev/vdc1,容量为 50GB。
df -TH 分区的文件系统类型 |
\n可从图中获取以下信息:
\n/dev/vdb1 文件系统类型为 ext4,已挂载至 /mnt/disk1。
\n/dev/vdc1 文件系统类型为 xfs,已挂载至 /mnt/disk2。
fdisk /dev/vdb 查看新磁盘情况 |
lsbl 查看分区情况 |
mkfs.ext4 /dev/vdb 格式化磁盘 |
cd /mnt | |
mkdir data 新建挂载点 | |
mount /dev/vdb /mnt/data 挂载 |
df -h 查看挂载情况 |
\n查看 UUID 有三种方式:
blkid |
lsblk -f |
ll /dev/disk/by-uuid/ |
设置自动挂载: | |
echo \"UUID=c8ac09ca-fd4d-4511-bd2c-4fdf96f08168 /data ext4 defaults 0 0\" >> /etc/fstab | |
自动挂载/etc/fstab里面的东西 | |
mount -a |
umount /dev/vdb 重启机器之后又恢复到挂载状态 |
vim /etc/fstab 把添加的磁盘信息删除即可。 |
科学上网的方法有多种,有很多第三方提供的免费方案,这些方案优缺点暂时不予讨论。实际工作生活中还是会有需要自己搭建的情况,这次介绍的是使用 squid+stunnel 方案进行搭建。
\n一台可以访问外网的服务器,如香港的云主机并安装 Ubuntu 系统。
\napt-get install -y squid |
生成用户文件
\napt-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两种方式都一样,在底部加入以下代码
#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 来实现此目的。
\napt-get install -y stunnel |
openssl req -new -x509 -days 3650 -nodes -out stunnel.pem -keyout stunnel.pem |
3、将证书 stunnel.pem 放到 /etc/stunnel/ 目录下
\n4、修改 stunnel 配置 (/etc/stunnle/stunnle.conf)
\n; 设置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
\n[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 中添加如下配置:
-Djps.track.ap.dependencies=false |
该篇文章介绍了如何对 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 行左右:
\n<script src=\"https://cdn.polyfill.io/v3/polyfill.js\"></script> | |
{{ _vendor_js() }} | |
{{ _js('app.js') }} | |
{{ partial('_partials/third-party/baidu-analytics.njk', {}, {cache: true}) }} |
更改为如下内容:
\n<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, 最后一行新建空行,增加如下内容:
\nhexo.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推荐内容,可根据自己情况更改
\nadvVendors: | |
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 |
下面为结构详解:
\nadvVendors: | |
\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 发起请求:
\nlog.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/** | |
* 兼容调 Https 接口 | |
*/ | |
public class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory { | |
@Override | |
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 { | |
@Override | |
public boolean verify(String s, SSLSession sslSession) { | |
return true; | |
} | |
} | |
private static class SkipX509TrustManager implements X509TrustManager { | |
@Override | |
public X509Certificate[] getAcceptedIssuers() { | |
return new X509Certificate[0]; | |
} | |
@Override | |
public void checkClientTrusted(X509Certificate[] chain, String authType) { | |
} | |
@Override | |
public void checkServerTrusted(X509Certificate[] chain, String authType) { | |
} | |
} | |
} |
// 配置 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); |
@Configuration | |
public class RestConfig { | |
//60 * 1000 | |
@Value(\"${rest.connectTimeout:60000}\") | |
private int connectTimeout; | |
//5 * 60 * 1000 | |
@Value(\"${rest.readTimeout:300000}\") | |
private int readTimeout; | |
@Bean | |
public RestTemplate restTemplate() { | |
SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); | |
simpleClientHttpRequestFactory.setConnectTimeout(connectTimeout); | |
simpleClientHttpRequestFactory.setReadTimeout(readTimeout); | |
RestTemplate restTemplate = new RestTemplate(simpleClientHttpRequestFactory); | |
return restTemplate; | |
} |
@Configuration | |
public class RestConfig { | |
//60 * 1000 | |
@Value(\"${rest.connectTimeout:60000}\") | |
private int connectTimeout; | |
//5 * 60 * 1000 | |
@Value(\"${rest.readTimeout:300000}\") | |
private int readTimeout; | |
@Value(\"${rest.connectionRequestTimeout:300000}\") | |
private int connectionRequestTimeout; | |
/** | |
* 使用 HttpComponentsClientHttpRequestFactory 创建 http 请求(推荐) | |
*/ | |
@Bean | |
public RestTemplate restTemplate() { | |
HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory(); | |
httpRequestFactory.setConnectionRequestTimeout(connectionRequestTimeout); | |
httpRequestFactory.setConnectTimeout(connectTimeout); | |
httpRequestFactory.setReadTimeout(readTimeout); | |
return new RestTemplate(httpRequestFactory); | |
} | |
} |
@Configuration | |
public class RestConfig { | |
/** | |
* 高并发采用 HttpClient 连接池 | |
*/ | |
@Bean | |
public RestTemplate restTemplate() { | |
return new RestTemplate(httpRequestFactory()); | |
} | |
@Bean | |
public ClientHttpRequestFactory httpRequestFactory() { | |
return new HttpComponentsClientHttpRequestFactory(httpClient()); | |
} | |
@Bean | |
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 默认打开了严格模式导致的错误。说明你代码里有地方写的不严谨。
\n#查询 sql_mode | |
select @@GLOBAL.sql_mode; | |
#删除 ONLY_FULL_GROUP_BY | |
#设置 sql_mode | |
set @@GLOBAL.sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'; |
业务要求:查询所有省份:
\n
SELECT | |
\tDISTINCT(province_code), | |
\tprovince_name | |
FROM | |
\tt_mip_base_area |
SELECT | |
\tprovince_code, | |
\tany_value(province_name) | |
FROM t_mip_base_area | |
GROUP BY province_code |
SELECT | |
province_code, | |
province_name | |
FROM t_mip_base_area | |
GROUP BY province_code |
则会报错
\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 () 会选择被分到同一组的数据里第一条数据的指定列值作为返回数据
\n#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 |
{ | |
\"builder\": { | |
\"gc\": { | |
\"defaultKeepStorage\": \"20GB\", | |
\"enabled\": true | |
} | |
}, | |
\"experimental\": false, | |
\"features\": { | |
\"buildkit\": true | |
}, | |
\"insecure-registries\": [ | |
\"http://192.168.86.117\" | |
] | |
} |
\n5. 本项目 clean、compile、package
\n
\n6. 添加一个 DockerFile 的启动配置
\n 192.168.86.117/zhsl/ys-zhslsgcgl:0.0.1
\n
\n7. 保证代码是最新的之后,运行上步的配置,会把刚才 install 生成的 jar 包上传到本机 Docker 生成一个本机 docker 镜像
\n
\n 8. 命令行登录本地的 KUBESPHERE,把本机 docker 镜像上传到 KUBESPHERE
docker login 192.168.86.117 |
docker images命令也可以看到本机docker镜像 |
把本机docker镜像上传到KUBESPHERE | |
docker push 192.168.86.117/zhsl/ys-zhslsgcgl:0.0.1 |
\n9. 网页登录本地的 KUBESPHERE。
\nhttp://192.168.86.117:30880/,进入 “工作负载” 找到刚上传 Docker 服务镜像,并启动。
\n
注意:54XXXXXXXXC8 为光猫背后 ONU MAC:54-XX-XX-XX-XX-C8
\n获取telnet的用户名 | |
cfg_cmd get InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetUserName | |
获取telnet的密码 | |
cfg_cmd get InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetPassword |
su | |
密码:Fh@XXXXXX(X为mac地址的后6位) |
cfg_cmd get InternetGatewayDevice.DeviceInfo.X_CT-COM_TeleComAccount.Password | grep 'get success' | cut -d = -f 2 |
7、访问 http://192.168.1.1:8080/html/logoffaccount.html 把隐藏用户打开,其它选项改为可修改
\n两种命令模式:
\nload_cli factory |
show admin_pwd |
? |
show ? |
set ? |
show allinfo |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetEnable 1 | |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetUserName 用户名 | |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.TelnetPassword 密码 |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.FtpEnable 1 | |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.FtpUserName 用户名 | |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_ServiceManage.FtpPassword 密码 |
cfg_cmd get InternetGatewayDevice.X_CT-COM_UserInfo.UserName |grep 'value' |cut -d '=' -f 2 |
cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.Enable | |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.Enable 0 #0 禁用,1 启用 | |
cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.VLANIDMark | |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.VLANIDMark 99 #默认 46 | |
cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.Mode | |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.Mode 2 #默认 2 | |
cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_AccessControl.Enabled | |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_AccessControl.Enabled 0 #0 禁用,1 启用 | |
cfg_cmd get InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.Enable | |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.1.Enable 0 #0 禁用,1 启用 |
cfg_cmd set InternetGatewayDevice.DeviceInfo.X_CT-COM_MiddlewareMgt.Tr069Enable 0 |
cfg_cmd set InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.X_CT-COM_WANEponLinkConfig.IP_Routed='' |
cfg_cmd get InternetGatewayDevice.Services.X_CT-COM_MWBAND.TotalTerminalNumber |
cfg_cmd set InternetGatewayDevice.Services.X_CT-COM_MWBAND.TotalTerminalNumber 255 |
cfg_cmd get InternetGatewayDevice.X_CT-COM_UserInfo.UserName |
cfg_cmd help |
cfg_cmd shownode InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1. [0|1] | |
(如cfg_cmd shownode InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1. [0]) |
网上搜索了几个修改 IDE 背景色和字体大小的方法,记录一下方便以后查看。
\n修改代码区颜色
\n
修改代码区字体大小
\n
修改控制台背景色
\n
修改控制台字体大小
\n
修改左侧背景色
\n
修改 UI 字体大小
\n
使用指定数据方式得到的线条总是不能达到想要的效果,一是阶梯线在价格发生变化的点存在倾斜,二是高低价的线很难完美显示,所以只能自己动手画了。代码仅供参考,如有错误的地方请指正!
\n
this.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 | |
}) | |
} | |
}) |