{ "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/2023/10/29/%E7%BB%99%E6%88%91%E7%9A%84%E8%80%81%E7%AC%94%E8%AE%B0%E6%9C%AC%E6%B8%85%E7%90%86%E7%81%B0%E5%B0%98/", "url": "https://hitoli.com/2023/10/29/%E7%BB%99%E6%88%91%E7%9A%84%E8%80%81%E7%AC%94%E8%AE%B0%E6%9C%AC%E6%B8%85%E7%90%86%E7%81%B0%E5%B0%98/", "title": "给我的老笔记本清理灰尘", "date_published": "2023-10-29T12:41:00.000Z", "content_html": "
今天闲着无事就把我的老笔记本拆了,清理了一下灰尘。笔记本已经 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 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 | |
}) | |
} | |
}) |
在开发前端页面时会存在相同的页面展示,只是菜单名称不同、数据类型不同的情况。如果再拷贝一个页面来展示后期就需要维护两个页面,同时也增加了工作量。但是只使用一个页面通过传参来改变数据就方便多了。
\n{ | |
path: '/test/:type/:menuIndex', | |
name: 'Test', | |
component: _import('xxx/Test.vue') | |
} |
<template> | |
<el-menu | |
class=\"aside-menu\" | |
:default-active=\"menuIndex.toString()\" | |
@select=\"handleSelect\" | |
> | |
<el-menu-item | |
index=\"1\" | |
class=\"aside-menu-item\" | |
> | |
<template #title> | |
<span>TEST1</span> | |
<el-icon :size=\"20\"><TrendCharts /></el-icon> | |
</template> | |
</el-menu-item> | |
<el-menu-item | |
index=\"2\" | |
class=\"aside-menu-item\" | |
> | |
<template #title> | |
<span>TEST2</span> | |
<el-icon :size=\"20\"><Orange /></el-icon> | |
</template> | |
</el-menu-item> | |
</el-menu> | |
</template> | |
methods: { | |
handleSelect (val) { | |
if (val === '1') { | |
this.$router.push({ path: '/test/type1/' + val }) | |
} else if (val === '2') { | |
this.$router.push({ path: '/test/type2/' + val }) | |
} | |
} | |
} |
created () { | |
this.$watch( | |
() => this.$route.params, | |
(toParams, previousParams) => { | |
if (isNotEmpty(toParams.menuIndex)) { | |
this.menuIndex = toParams.menuIndex | |
} | |
if (isNotEmpty(toParams.type)) { | |
this.type = toParams.type | |
} | |
// 根据type查询数据 | |
... | |
} | |
) | |
if (isNotEmpty(this.$route.params.menuIndex)) { | |
this.menuIndex = this.$route.params.menuIndex | |
} | |
if (isNotEmpty(this.$route.params.type)) { | |
this.type = this.$route.params.type | |
} | |
}, | |
mounted () { | |
\t// 根据type查询数据 | |
\t... | |
} |
项目中需要画一幅收益概览图,研究了一下 Highcharts 的用法。特此收录方便以后再次使用时查阅。
\n
//x 轴时间集合 | |
var chartXAxisDateTimes = ['2022-01-01 00:00:00', '2022-02-01 00:00:00', '2022-03-01 00:00:00', '2022-04-01 00:00:00', '2022-05-01 00:00:00'] | |
//y 轴数据集合 | |
var chartYAxisDatas = [ | |
{ | |
title: '累计收益', | |
name: '我的策略累计收益', | |
type: 'spline', | |
unit: '%', | |
valueDecimals: 3, | |
height: '28%', | |
yAxisIndex: 0, //y 轴分区 index(从上到下,默认从 0 开始) | |
data: [{ | |
x: new Date('2022-01-01 00:00:00').getTime(), | |
y: 1, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-02-01 00:00:00').getTime(), | |
y: 2, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-03-01 00:00:00').getTime(), | |
y: 3, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-04-01 00:00:00').getTime(), | |
y: 4, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-05-01 00:00:00').getTime(), | |
y: 5, | |
color: '' | |
}] | |
}, | |
{ | |
title: '每月盈亏', | |
name: '当月盈利', | |
type: 'column', | |
unit: 'k', | |
valueDecimals: 0, | |
top: '34%', | |
height: '28%', | |
yAxisIndex: 1, //y 轴分区 index(从上到下,默认从 0 开始) | |
formatter: function () { | |
return this.value < 0 ? (-this.value) + 'k' : this.value + 'k' | |
}, | |
data: [{ | |
x: new Date('2022-01-01 00:00:00').getTime(), | |
y: 1, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-02-01 00:00:00').getTime(), | |
y: 2, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-03-01 00:00:00').getTime(), | |
y: 3, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-04-01 00:00:00').getTime(), | |
y: 4, | |
color: '' | |
}, | |
{ | |
x: new Date('2022-05-01 00:00:00').getTime(), | |
y: 5, | |
color: '' | |
}] | |
}, | |
{ | |
title: '成交记录', | |
name: '当月买入', | |
type: 'column', | |
unit: 'k', | |
valueDecimals: 0, | |
top: '68%', | |
height: '28%', | |
yAxisIndex: 2, //y 轴分区 index(从上到下,默认从 0 开始) | |
data: [{ | |
x: new Date('2022-01-01 00:00:00').getTime(), | |
y: 1, | |
color: 'red' | |
}, | |
{ | |
x: new Date('2022-02-01 00:00:00').getTime(), | |
y: 2, | |
color: 'red' | |
}, | |
{ | |
x: new Date('2022-03-01 00:00:00').getTime(), | |
y: 3, | |
color: 'red' | |
}, | |
{ | |
x: new Date('2022-04-01 00:00:00').getTime(), | |
y: 4, | |
color: 'red' | |
}, | |
{ | |
x: new Date('2022-05-01 00:00:00').getTime(), | |
y: 5, | |
color: 'red' | |
}] | |
}, | |
{ | |
title: '成交记录', | |
name: '当月卖出', | |
type: 'column', | |
unit: 'k', | |
valueDecimals: 0, | |
top: '68%', | |
height: '28%', | |
yAxisIndex: 2, //y 轴分区 index(从上到下,默认从 0 开始) | |
data: [{ | |
x: new Date('2022-01-01 00:00:00').getTime(), | |
y: 1, | |
color: 'green' | |
}, | |
{ | |
x: new Date('2022-02-01 00:00:00').getTime(), | |
y: 2, | |
color: 'green' | |
}, | |
{ | |
x: new Date('2022-03-01 00:00:00').getTime(), | |
y: 3, | |
color: 'green' | |
}, | |
{ | |
x: new Date('2022-04-01 00:00:00').getTime(), | |
y: 4, | |
color: 'green' | |
}, | |
{ | |
x: new Date('2022-05-01 00:00:00').getTime(), | |
y: 5, | |
color: 'green' | |
}] | |
} | |
] | |
\t\t | |
// 最大回测范围 | |
var plotBandScope = [new Date('2022-02-01 00:00:00').getTime(), new Date('2022-04-01 00:00:00').getTime()] | |
// 图表配置 | |
var option = { | |
\tchart: { | |
\t height: 600, | |
\t type: 'spline' // 指定图表的类型,默认是折线图(line) | |
\t}, | |
\tplotOptions: { | |
\t spline: { | |
\t\tborderWidth: 0, | |
\t\tlineWidth: 1, | |
\t\tstates: { | |
\t\t hover: { | |
\t\t\tlineWidth: 2 | |
\t\t } | |
\t\t}, | |
\t\t//pointWidth: 4, // 柱子内宽度容 | |
\t\tdataLabels: { | |
\t\t style: { | |
\t\t\tfontSize: 11 | |
\t\t }, | |
\t\t enabled: false | |
\t\t}, | |
\t\tshowInLegend: true | |
\t }, | |
\t column: { | |
\t\tborderWidth: 0, | |
\t\tpointWidth: 4, // 柱子内宽度容 | |
\t\tdataLabels: { | |
\t\t style: { | |
\t\t\tfontSize: 11 | |
\t\t }, | |
\t\t enabled: false | |
\t\t}, | |
\t\tshowInLegend: true, | |
\t\tgrouping: true | |
\t } | |
\t}, | |
\trangeSelector: { | |
\t enabled: false | |
\t}, | |
\tcredits: { | |
\t enabled: false | |
\t}, | |
\t// legend: { | |
\t// enabled: true, | |
\t// verticalAlign: 'top' | |
\t// }, | |
\tnavigator: { // 导航器样式 | |
\t enabled: true, | |
\t height: 20 | |
\t}, | |
\tscrollbar: { // 滚动条样式 | |
\t barBackgroundColor: 'rgb(209, 218, 237)', | |
\t barBorderRadius: 0, | |
\t barBorderWidth: 0, | |
\t buttonBackgroundColor: 'rgb(242, 242, 242)', | |
\t buttonBorderWidth: 0, | |
\t buttonBorderRadius: 0, | |
\t trackBackgroundColor: 'none', | |
\t trackBorderWidth: 1, | |
\t trackBorderRadius: 0, | |
\t trackBorderColor: '#CCC' | |
\t}, | |
\txAxis: { | |
\t lineWidth: 1, | |
\t type: 'datetime', | |
\t categories: [], | |
\t dateTimeLabelFormats: { | |
\t\t// millisecond: '%H:%M:%S.%L', | |
\t\t// second: '%H:%M:%S', | |
\t\t// minute: '%H:%M', | |
\t\t// hour: '%m-%d %H:%M', | |
\t\tday: '%m-%d', | |
\t\tweek: '%m-%d', | |
\t\tmonth: '%Y-%m', | |
\t\tyear: '%Y' | |
\t }, | |
\t gridLineWidth: 1 | |
\t}, | |
\tyAxis: [], | |
\ttooltip: { | |
\t xDateFormat: '%Y-%m-%d %H:%M:%S', | |
\t style: { | |
\t\tfontSize: 16, | |
\t\tfontFamily: '微软雅黑', | |
\t\tfontWeight: 'normal', | |
\t\tcolor: '#666', | |
\t\tpadding: 10, | |
\t\tborderWidth: 0, | |
\t\tbackgroundColor: '#ddd' | |
\t }, | |
\t shared: true, | |
\t useHTML: true, | |
\t headerFormat: '<small style=\"font-size: 12px\">{point.key}</small><table>', // 标题格式, | |
\t pointFormat: '<tr><td><li style=\"color:{series.color};padding:0px\">{series.name}: </li>' + | |
\t '<div style=\"float: right;\">{point.y} {series.tooltip.valueDecimals}</div></td></tr>', | |
\t footerFormat: '</table>', | |
\t shadow: true, | |
\t followPointer: true // 跟随鼠标 | |
\t}, | |
\tseries: [] | |
} | |
// 根据最大回测范围修改图表配置 | |
if (plotBandScope && plotBandScope.length === 2) { | |
\toption.xAxis.plotBands = [{ | |
\t from: plotBandScope[0], | |
\t to: plotBandScope[1], | |
\t color: '#FCFFC5', | |
\t label: { | |
\t\ttext: '最大回测区间', | |
\t\talign: 'center', | |
\t\tverticalAlign: 'top', | |
\t\ty: 14 | |
\t } | |
\t}] | |
} | |
// 根据 x 轴时间集合修改图表配置 | |
if (chartXAxisDateTimes && chartXAxisDateTimes.length > 0) { | |
\toption.categories = chartXAxisDateTimes | |
} | |
// 根据 y 轴数据集合修改图表配置 | |
if (chartYAxisDatas && chartYAxisDatas.length > 0) { | |
\tchartYAxisDatas.forEach(function (item, i) { | |
\t if (item.type === 'spline' || item.type === 'line') { | |
\t\toption.yAxis.push({ | |
\t\t plotLines: [{ | |
\t\t\tcolor: 'black', // 线的颜色,定义为红色 | |
\t\t\tdashStyle: 'solid', // 默认值,这里定义为实线 | |
\t\t\tvalue: 0, // 定义在那个值上显示标示线,这里是在 x 轴上刻度为 3 的值处垂直化一条线 | |
\t\t\twidth: 2 // 标示线的宽度,2px | |
\t\t }], | |
\t\t tickAmount: 4, // 规定坐标轴上的刻度总数 | |
\t\t gridLineColor: '#ddd', | |
\t\t gridLineDashStyle: 'Solid', | |
\t\t labels: { // 坐标轴上刻度的样式及单位 | |
\t\t\talign: 'right', | |
\t\t\tx: 6, | |
\t\t\tstyle: { | |
\t\t\t color: '#999999', | |
\t\t\t fontSize: 12 | |
\t\t\t}, | |
\t\t\tformat: '{value}' + item.unit // 坐标轴上的单位 | |
\t\t }, | |
\t\t title: { | |
\t\t\ttext: item.title, | |
\t\t\tmargin: 20, | |
\t\t\tstyle: { | |
\t\t\t color: '#999999', | |
\t\t\t fontSize: 12, | |
\t\t\t align: 'middle' // 坐标轴对齐方式(相对于坐标轴的值) 默认是:middle 居中对齐 | |
\t\t\t} | |
\t\t }, | |
\t\t height: item.height, | |
\t\t lineWidth: 0 | |
\t\t}) | |
\t\toption.series.push({ | |
\t\t name: item.name, | |
\t\t data: item.data, | |
\t\t type: item.type, | |
\t\t color: 'rgb(170, 70, 67)', | |
\t\t tooltip: { | |
\t\t\tvalueSuffix: item.unit, // 数值后缀 | |
\t\t\tvalueDecimals: item.valueDecimals // 提示框数据精度 (保留小数点后 3 位) | |
\t\t }, | |
\t\t yAxis: item.yAxisIndex, | |
\t\t showInNavigator: false | |
\t\t}) | |
\t } else if (item.type === 'column') { | |
\t\toption.yAxis.push({ | |
\t\t type: item.type, | |
\t\t tickAmount: 4, // 规定坐标轴上的刻度总数 | |
\t\t startOnTick: true, | |
\t\t reversed: false, //y 轴刻度反转 | |
\t\t showFirstLabel: false, | |
\t\t showLastLabel: true, | |
\t\t gridLineColor: '#ddd', | |
\t\t gridLineDashStyle: 'Solid', | |
\t\t labels: { | |
\t\t\talign: 'right', | |
\t\t\tx: 12, | |
\t\t\tstyle: { | |
\t\t\t color: '#999999', | |
\t\t\t fontSize: 12 | |
\t\t\t}, | |
\t\t\tformatter: item.formatter, | |
\t\t\tformat: '{value}' + item.unit // 坐标轴上的单位 | |
\t\t }, | |
\t\t title: { | |
\t\t\ttext: item.title, | |
\t\t\tmargin: 20, | |
\t\t\tstyle: { | |
\t\t\t color: '#999999', | |
\t\t\t fontSize: 12, | |
\t\t\t align: 'middle' // 坐标轴对齐方式(相对于坐标轴的值) 默认是:middle 居中对齐 | |
\t\t\t} | |
\t\t }, | |
\t\t top: item.top, | |
\t\t height: item.height, | |
\t\t offset: 0, | |
\t\t lineWidth: 0 | |
\t\t}) | |
\t\toption.series.push({ | |
\t\t name: item.name, | |
\t\t type: item.type, | |
\t\t data: item.data, | |
\t\t tooltip: { | |
\t\t\tpointFormatter: function () { | |
\t\t\t return '<tr><td><li style=\"color: ' + this.color + ';padding:0px;\"></span> ' + item.name + | |
\t\t\t\t': </li><div style=\"float: right;\">' + this.y.toFixed(item.valueDecimals) + item.unit + '</div></td></tr>' | |
\t\t\t} | |
\t\t }, | |
\t\t yAxis: item.yAxisIndex, | |
\t\t showInNavigator: false | |
\t\t}) | |
\t } | |
\t}) | |
} | |
// 画图(container 为 div 的 id) | |
Highcharts.stockChart('container', option) |
最近重装了家里一台 mini 电脑,把系统升级成了最新的 win10 系统。但是使用起来却异常卡顿,查看任务管理器发现有个 wsappx 进程占用 cpu 严重。于是百度搜索得知它是微软商店的依赖进程,而我根本就用不上,所以直接禁用。
\n本次任务是需要在一个指标图上通过点击标记画出此标记参与计算的数据范围、最高最低值、参考线等等,于是有了以下代码。代码仅供参考,如有错误的地方请指正!
\n
// 箱体指标 | |
const boxDataScope = 300 // 箱体范围 | |
// 黄金线参数 | |
const goldenSectionA = 0.191 | |
const goldenSectionB = 0.382 | |
const goldenSectionC = 0.5 | |
const goldenSectionD = 0.618 | |
const goldenSectionE = 0.809 | |
this.chart.addTechnicalIndicatorTemplate({ | |
\tname: 'custom_box_solid', | |
shortName: '箱体', | |
precision: 2, | |
plots: [ //key 属性的值最好在主图数据范围内,否则 Y 轴的值会跟着变大可能会导致主图变成一条线 | |
{ key: 'max', title: '最高:' }, | |
{ key: 'min', title: '最低:' } | |
], | |
calcParams: [boxDataScope, goldenSectionA, goldenSectionB, goldenSectionC, goldenSectionD, goldenSectionE], | |
calcTechnicalIndicator: (dataList, { params, plots }) => { | |
let allDatas = [] // 所有数据 | |
let selectedDatas = [] // 选中的数据 | |
for (let i = 0; i < dataList.length; i++) { | |
let kLineData = dataList[i] | |
if (new Date(dateConvert(kLineData.timestamp)).getTime() === getGlobalObject('boxId')) { | |
const size = params[0] | |
// 找出当前数据往前的 size 条数据(包含自己) | |
let startData = i - size + 1 | |
if (startData < 0) { | |
startData = 0 | |
} | |
let endData = i + 1 | |
if (endData > dataList.length) { | |
endData = dataList.length | |
} | |
selectedDatas = dataList.slice(startData, endData) | |
} | |
allDatas.push({ | |
timestamp: kLineData.timestamp | |
}) | |
} | |
// 找出选中数据中最高最低差价 | |
let max, min | |
selectedDatas.forEach(function (item) { | |
let value = item.close - item.close2 | |
if (!max || value > max) { | |
max = value | |
} | |
if (!min || value < min) { | |
min = value | |
} | |
}) | |
\t // 返回指标最终数据(未选中的数据用空对象替换) | |
\t // 必须返回和 dataList 一样条数的数据,否则 title 不会显示 | |
return allDatas.map((data, i) => { | |
let item = { | |
} | |
selectedDatas.map((selected, j) => { | |
if (data.timestamp === selected.timestamp) { | |
item.timestamp = selected.timestamp | |
item.max = max | |
item.min = min | |
} | |
}) | |
return item | |
}) | |
}, | |
render: ({ ctx, dataSource, viewport, styles, xAxis, yAxis }) => { | |
if (dataSource.technicalIndicatorDataList.length <= 0) { // 无指标数据则不处理 | |
return | |
} | |
\t // X 轴起始像素 | |
let x = xAxis.convertToPixel(0) | |
// 标记选中数据的起止位置 | |
let start | |
let end = dataSource.technicalIndicatorDataList.length - 1 | |
dataSource.technicalIndicatorDataList.forEach(function (kLineData, i) { | |
if (kLineData.timestamp) { | |
if (!start) { | |
start = i | |
} | |
if (i < end && !dataSource.technicalIndicatorDataList[i + 1].timestamp) { | |
end = i | |
} | |
let max = kLineData.max | |
let min = kLineData.min | |
ctx.fillStyle = '#fff' | |
ctx.textBaseline = 'middle' | |
ctx.textAlign = 'center' | |
// 画箱体 | |
\t\t // 箱体颜色 | |
\t\t ctx.strokeStyle = '#DC143C' | |
\t\t //y 轴最高点位置 | |
\t\t let yHigh = yAxis.convertToPixel(max) | |
\t\t //y 轴最低点位置 | |
\t\t let yLow = yAxis.convertToPixel(min) | |
\t\t ctx.beginPath() | |
\t\t // 画笔移动到数据的 x 轴起始点,y 轴最高点 | |
\t\t ctx.moveTo(x, yHigh) | |
if (i === start) { // 如果是第一条数据则需要画一条竖线 | |
ctx.lineTo(x, yLow) // 画竖线 | |
ctx.moveTo(x, yHigh) // 画笔移回 | |
} | |
if (i === end) { // 如果是最后一条数据则需要画一条竖线 | |
ctx.lineTo(x, yLow) // 画竖线 | |
ctx.fillText(max, x + viewport.dataSpace, yHigh) // 标识箱体最高点的值 | |
ctx.moveTo(x, yLow) // 画笔移动到 Y 轴最低点 | |
ctx.fillText(min, x + viewport.dataSpace, yLow) // 标识箱体最低点的值 | |
} else { // 画两条横线,一条在 y 轴最高点,一条在 y 轴最低点 | |
ctx.lineTo(x + viewport.dataSpace, yHigh) //y 轴最高点横线 | |
ctx.moveTo(x, yLow) // 画笔移动到 y 轴最低点 | |
ctx.lineTo(x + viewport.dataSpace, yLow) //y 轴最低点横线 | |
} | |
ctx.stroke() | |
ctx.closePath() | |
// 画黄金线 | |
\t\t // 黄金线颜色 | |
ctx.strokeStyle = '#ffffff' | |
\t\t // 根据黄金线参数计算黄金线的值 | |
let goldenSectionLineA = (max - min) * goldenSectionA + min | |
let goldenSectionLineB = (max - min) * goldenSectionB + min | |
let goldenSectionLineC = (max - min) * goldenSectionC + min | |
let goldenSectionLineD = (max - min) * goldenSectionD + min | |
let goldenSectionLineE = (max - min) * goldenSectionE + min | |
\t\t // 根据黄金线的值获取 Y 轴高度 | |
let yA = yAxis.convertToPixel(goldenSectionLineA) | |
let yB = yAxis.convertToPixel(goldenSectionLineB) | |
let yC = yAxis.convertToPixel(goldenSectionLineC) | |
let yD = yAxis.convertToPixel(goldenSectionLineD) | |
let yE = yAxis.convertToPixel(goldenSectionLineE) | |
ctx.beginPath() | |
\t\t // 画第一条黄金线 | |
ctx.moveTo(x, yA) | |
ctx.lineTo(x + viewport.barSpace / 2, yA) | |
if (i === end) { // 是否最后一条数据,如果是则需要标识黄金线的值 | |
\t\t // 标识第一条黄金线的值 | |
ctx.fillText(goldenSectionLineA.toFixed(2), x + viewport.dataSpace, yA) | |
} | |
\t\t // 画第二条黄金线 | |
ctx.moveTo(x, yB) | |
ctx.lineTo(x + viewport.barSpace / 2, yB) | |
if (i === end) { | |
ctx.fillText(goldenSectionLineB.toFixed(2), x + viewport.dataSpace, yB) | |
} | |
\t\t // 画第三条黄金线 | |
ctx.moveTo(x, yC) | |
ctx.lineTo(x + viewport.barSpace / 2, yC) | |
if (i === end) { | |
ctx.fillText(goldenSectionLineC.toFixed(2), x + viewport.dataSpace, yC) | |
} | |
\t\t // 画第四条黄金线 | |
ctx.moveTo(x, yD) | |
ctx.lineTo(x + viewport.barSpace / 2, yD) | |
if (i === end) { | |
ctx.fillText(goldenSectionLineD.toFixed(2), x + viewport.dataSpace, yD) | |
} | |
\t\t // 画第五条黄金线 | |
ctx.moveTo(x, yE) | |
ctx.lineTo(x + viewport.barSpace / 2, yE) | |
if (i === end) { | |
ctx.fillText(goldenSectionLineE.toFixed(2), x + viewport.dataSpace, yE) | |
} | |
ctx.stroke() | |
ctx.closePath() | |
} | |
\t\t// 计算 X 轴的下一个位置 | |
x += viewport.dataSpace | |
}) | |
} | |
}) |
以上代码只是箱体的指标模版,还需要根据业务逻辑在标记上实现点击事件,然后通过事件动态添加移除箱体指标。
\nTengine 的性能和稳定性已经在大型的网站如淘宝网,天猫商城等得到了很好的检验。它的最终目标是打造一个高效、稳定、安全、易用的 Web 平台。从 2011 年 12 月开始,Tengine 成为一个开源项目。现在,它由 Tengine 团队开发和维护。Tengine 团队的核心成员来自于淘宝、搜狗等互联网企业。
\ntengine 简单来说就是淘宝自己基于 nginx 优化的网页引擎,在 nginx 原先基础上继续保持兼容,同时功能扩展,效率提高,可以看到目前淘宝网在这么多人同时使用的情况下依然稳定,我们足以相信 tengine,由于它是 nginx 的一个分生版本,所以几乎完全兼容 nginx,所以我认为 tengine 是搭建 lnmp 环境的不二之选。
\n首先访问 tengine 官方网站,获取最新的下载地址。
\n wget http://tengine.taobao.org/download/tengine-2.3.3.tar.gz
nginx -V
./configure --prefix=/usr/local/nginx --user=www --group=www --with-http_ssl_module --with-http_gzip_static_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --without-http_uwsgi_module --without-http_scgi_module
\n./configure 后面的参数是上一步获取的
make 或者 make -j 内核数
\n生成的文件在 objs 目录下
停止 nginx 服务 service nginx stop
\n 查看 nginx 目录 whereis nginx
\n 备份旧 nginx mv /usr/sbin/nginx /usr/sbin/nginx.old
\n 拷贝 objs 下的 nginx 替换旧 nginx cp ./objs/nginx /usr/sbin/
\n 备份旧 so 文件
\n拷贝 objs 下的 so 文件替换旧的 so 文件 cp ./objs/*.so /usr/lib/nginx/modules/
nginx -t
如果打印 test is successful 则表示替换成功。
\n然后执行 service nginx start 进行启动即可