分類
發燒車訊

廣汽三菱收購進口三菱 或著重發展新能源汽車

日前,從廣汽三菱汽車有限公司瞭解到,該公司已經完成對三菱汽車銷售有限公司的100%股份的收購。值得關注的是完成收購後的廣汽三菱下一步的汽車整體佈局。

廣汽三菱汽車有限公司副總經理、三菱汽車銷售有限公司董事長杜志堅對外透露了廣汽三菱的新能源汽車發展的幾個方向。

首先,基於三菱的插電式混合動力的優勢,廣汽三菱首推插電式混動車。據杜志堅描述這款新車的三個優勢,第一,這款車充滿電、加滿油,續航里程超過800公里,根本不用擔心去哪裡回不來;第二,燃油經濟性,百公里油耗不超過2L;第三,解決了對充電樁的依賴,只要提供一個插座,車隨時可以充電。

其次,廣汽三菱的首款國產新能源汽車將基於插電式混合動力汽車歐藍德研發。據瞭解,目前廣汽三菱已經進口了10輛歐藍德,正處於廠商進行驗證測試階段。這款車的儘快導入,應該說對整個廣汽三菱產品線的提升、科技感提升、品牌提升都將有巨大的幫助。
 

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

FastDFS圖片服務器單機安裝步驟(修訂版)

前面已經講 ,通過此文章可以了解FastDFS組件中單機安裝流程。

單機版架構圖

以下為單機FastDFS安裝步驟

一、環境準備

CentOS 7.X

使用的版本: libfastcommon-1.0.41.tar.gz

使用的版本: fastdfs-6.01.tar.gz

使用的版本:fastdfs-nginx-module-1.21.tar.gz

使用的版本: nginx-1.16.1.tar.gz

二、安裝過程

1、安裝 libfastcommon-1.0.41.tar.gz

tar -zxvf libfastcommon-1.0.41.tar.gz
cd libfastcommon-1.0.41/
./make.sh
./make.sh install

2、安裝 FastDFS

tar -zxvf  fastdfs-6.01.tar.gz
cd fastdfs-6.01/
./make.sh
./make.sh install

準備配置文件

cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
cd /opt/apps/fastdfs-6.01/conf
cp http.conf mime.types /etc/fdfs/

Tracker Server 配置

vim /etc/fdfs/tracker.conf
修改配置如下:
#tracker server端口號
port=22122
#存儲日誌和數據的根目錄
base_path=/opt/fastdfs/tracker
#HTTP服務端口
http.server_port=80
開放防火牆端口

1、打開跟蹤端口

vim /etc/sysconfig/iptables

2、添加以下端口行:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT

3、重啟防火牆

service iptables restart
啟動Tracker
/etc/init.d/fdfs_trackerd start

Storage Server 配置

vim /etc/fdfs/storage.conf
修改配置如下:
#storage server端口號
port=23000
#數據和日誌文件存儲根目錄
base_path=/opt/fastdfs/storage
#第一個存儲目錄
store_path0=/opt/fastdfs/storepath0
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122
#http訪問文件的端口(默認8888,看情況修改,和nginx中保持一致)
http.server_port=8888
開放防火牆端口

1、打開跟蹤端口

vim /etc/sysconfig/iptables

2、添加以下端口行:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT

3、重啟防火牆

service iptables restart
啟動Storage
/etc/init.d/fdfs_storaged start
查看集群狀態
 fdfs_monitor /etc/fdfs/storage.conf list

查看狀態是否正常

Storage 1:
id = 6.0.36.243
ip_addr = 6.0.36.243 (anantes-651-1-49-net.w2-0.abo.wanadoo.fr) ACTIVE

Client配置

vim /etc/fdfs/client.conf
修改配置如下:
#
base_path=/opt/apps/fastdfs/client
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122 
上傳一個圖片測試是否能上傳成功
 fdfs_upload_file /etc/fdfs/client.conf test.jpg

test.jpg 是測試本地上傳的圖片,路徑請填寫正確

3、安裝Nginx和 fastdfs-nginx-module

#解壓fastdfs-nginx-module
tar -zxvf fastdfs-nginx-module-1.21.tar.gz
cd fastdfs-nginx-module-1.21/
cp ./src/mod_fastdfs.conf /etc/fdfs
#解壓nginx
tar -zxvf nginx-1.16.1.tar.gz
cd nginx-1.16.1/
#安裝nginx_http_image_filter_module
yum -y install gd-devel
yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
#添加模塊
./configure --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module 
#編譯nginx
make
#安裝nginx
make install

查看是否安裝成功

/usr/local/nginx/sbin/nginx -V

查看插件是否安裝成功

[root@FastDFS nginx-1.16.1]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) 
configure arguments: --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module
[root@FastDFS nginx-1.16.1]# 

修改Nginx訪問

vim /etc/fdfs/mod_fastdfs.conf

修改配置如下:

#
connect_timeout=10
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122
#是否啟用group組名
url_have_group_name=true
#
store_path0=/opt/fastdfs/storepath0

修改Nginx配置:

vim /usr/local/nginx/conf/nginx.conf

修改配置如下:

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        root   html;
        index  index.html index.htm;
    }
    #圖片帶壓縮訪問
    location ~ /group1/M00/(.*)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+) {
        alias  /home/fastdfs/storage/data/;
        ngx_fastdfs_module;
        set $w $3;
        set $h $4;

        rewrite group1/M00(.+)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+)$ group1/M00$1.$2 break;

        image_filter resize $w $h;
        image_filter_buffer 5M;
    }
    #主圖訪問
    location ~ /group([0-9])/M00/(.+)\.?(.+) {
        alias /home/fastdfs/storage/data/;
        ngx_fastdfs_module;
    }
...
}

啟動Nginx

#啟動
/usr/local/nginx/sbin/nginx
#停止
/usr/local/nginx/sbin/nginx -s stop
#重啟
/usr/local/nginx/sbin/nginx -s reload

通過以上配置完成FastDFS的搭建。

測試圖片訪問

圖片訪問示例:

主圖訪問

http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png

壓縮圖片 (主圖后加 !format=寬度_高度)訪問

http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png!format=400_10

未解決的問題

壓縮圖片使用主圖后?format=寬度_高度

本文由博客一文多發平台 發布!

再次感謝!!! 您已看完全文,歡迎關注微信公眾號猿碼 ,你的支持是我持續更新文章的動力!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

基於 HTML5 + WebGL 實現 3D 挖掘機系統

前言

在工業互聯網以及物聯網的影響下,人們對於机械的管理,机械的可視化,机械的操作可視化提出了更高的要求。如何在一個系統中完整的显示机械的運行情況,机械的運行軌跡,或者机械的机械動作顯得尤為的重要,因為這會幫助一個不了解這個机械的小白可以直觀的了解机械的運行情況,以及机械的所有可能發生的動作,對於三一或者其它國內國外重工机械的公司能夠有一個更好的展示或者推廣。
挖掘機,又稱挖掘机械(excavating machinery),從近幾年工程机械的發展來看,挖掘機的發展相對較快,挖掘機已經成為工程建設中最主要的工程机械之一。所以該系統實現了對挖掘機的 3D 可視化,在傳統行業一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,而且都是 2D 面板部分數據的監控,從後台獲取數據前台显示數據,但是對於挖掘機本身來說,挖掘機的模型,挖掘機的動作,挖掘機的運行可視化卻是更讓人眼前一亮的,所以該系統對於挖機的 3D 模型做出了動作的可視化,大體包括以下幾個方面:

  • 前進後退 — 用戶可以通過鍵盤 wasd 實現前後左右,或者點擊 2D 界面 WASD 來實現挖機的前進後退。
  • 機身旋轉 — 用戶可以通過鍵盤左右鍵實現機身的旋轉,或者點擊 2D 界面 < > 來實現挖機機身的旋轉。
  • 大臂旋轉 — 用戶可點擊 2D 界面第一個滑塊部分實現大臂的旋轉。
  • 小臂旋轉 — 用戶可點擊 2D 界面第二個滑塊部分實現小臂的旋轉。
  • 挖斗挖掘 — 用戶可點擊 2D 界面第三個滑塊部分實現挖斗部分的旋轉挖掘。
  • 挖機動畫 — 用戶可點擊 2D 界面鏟子圖標,點擊之後系統會把挖機本身幾個動畫做一個串聯展示。

本篇文章通過對挖掘機可視化場景的搭建,挖機机械動作代碼的實現進行闡述,幫助我們了解如何使用  實現一個挖掘機的可視化。

預覽地址: 

界面效果預覽

挖機机械運動效果

通過上面 gif 圖片可以看出挖掘機的幾個主要動作。

挖機挖斗運動效果

滑動頁面的第三個滑桿控制挖斗的旋轉挖掘。

挖機機身運動

通過上面 gif 圖片可以看出挖掘機的前進後退以及機身旋轉幾個運動。

場景搭建

該 3D 場景中所有形狀都是用 HT 內部的牆面工具進行構建,通過設置牆面透明屬性 shape3d.transparent 為 true 以及對構建出的牆面進行貼圖來構造出場景中的類似建築的显示效果,具體的樣式可以參考 HT 的 ,場景效果:

通過上圖我們可以看到場景中有許許多多的牆面建築,所以它們有許多相同的地方,例如樣式以及貼圖都是一樣的,所以在 HT 中可以通過批量的操作對這些牆面進行處理,批量的意思指的是在當前未處理的情況下的牆面圖元是一個個獨立繪製的模型,所以性能會比較差,而當一批圖元聚合成一個大模型進行一次性的繪製時,則會極大提高 WebGL 刷新性能,這就是批量所以要做的事情,具體可以參考 HT 的 

該系統 2d 面板部分則也是通過 HT 的矢量進行繪製,面板部分主要包括當前挖機的作業情況,工作時間,保修信息,故障信息等,通過二維的方式展示這些數據信息,面板截圖:

机械運動代碼分析

該系統中挖機的動作是十分的重要和關鍵的,大小臂運動時液壓杠該如何運動,挖斗運動時液壓桿,旋轉點零件,以及連接到挖鬥上的零部件如何聯動起來是關鍵點,机械動畫中用到大部分數學知識進行點面位置的計算,以下是幾個關鍵的數學知識點作為基礎:

在數學中,向量(也稱為幾何向量、矢量),指具有大小和方向的量。它可以形象化地表示為帶箭頭的線段。系統中會通過向量的叉乘算出與某個面垂直的向量即法向量,在計算挖斗旋轉時需要計算出與挖斗面垂直的法向量來進行點的計算,HT 中封裝了 ht.Math 的數學函數,裏面的 ht.Math.Vector2 指的即為二維向量,ht.Math.Vector3 則為三維的向量,可以傳入三個數值進行初始化向量,向量的原型中有 cross 方法用來計算兩個向量的法向量,例如以下偽代碼:

1 var Vector3 = ht.Math.Vector3;
2 var a = new Vector3([10, 10, 0]);
3 var b = new Vector3([10, 0, 0]);
4 var ab = a.clone().cross(b);

以上代碼中 ab 即為計算法向量,a.clone 是為了避免 cross 運算會修改原本的 a 內容,所以克隆出一個新的向量進行叉乘,以下為示意圖:

挖斗机械運動分析

在進行挖斗部分的机械代碼時會將挖斗的位置以及挖斗所有連接點的設備轉化為相對於某個節點的相對位置,例如節點 A 在世界中的坐標為 [100, 100, 100],世界中還有一個節點 B,而且節點 B 的坐標為 [10, 10, 10] 則節點 A 相對於節點 B 的相對位置即為 [90, 90, 90],因為在計算挖斗的位置時,挖機可能此時已經運動到某一點或者旋轉到某一個軸,所以此時不能使用相對世界的坐標,需要使用相對挖機機身的相對坐標來進行計算,代碼中提供了 toLocalPostion(node, worldPosition) 用來將世界的坐標 worldPosition 轉化為相對 node 的相對坐標,以下為代碼實現:

1 var Matrix4 = ht.Math.Matrix4,
2 Vector3 = ht.Math.Vector3;
3 var mat = new Matrix4().fromArray(this.getNodeMat(g3d, node)),
4 matInverse = new Matrix4().getInverse(mat),
5 position = new Vector3(worldPosition).applyMatrix4(matInverse);
6 return position.toArray();

該函數的返回值即為相對坐標,挖機中需要轉化的坐標為連接着挖斗以及小臂的兩個零部件,系統中用 armHinge 以及 bucketHinge 來分別表示小臂樞紐以及挖斗樞紐這兩個零部件,可以從側面來看挖斗的動作,從下圖可以看出,關鍵點是算出交點 P 的坐標,交點 P 的坐標則是以 armHinge 與 bucketHinge位置為圓心,armHinge 與 bucketHinge 的長度為半徑的兩個圓的交點,而且這兩個圓的圓心在挖斗旋轉的過程中是不斷變化的,所以需要通過數學計算不斷算出交點的位置,以下為示意圖:

通過上圖可以知道交點的位置有兩個 p1 以及 p2,程序中通過計算圓心 1 與圓心 2 構成的向量 c2ToC1,以下為偽代碼:

1 var Vector2 = ht.Math.Vector2;
2 var c2ToC1 = new Vector2({ x: c1.x, y: c1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

c1 和 c2 為 armHinge 以及 bucketHinge 的圓心坐標,接下來是計算圓心 2 與點 p1 以及 p2 構成的向量 c2ToP1 以及 c2ToP2,以下為偽代碼:

1 var Vector2 = ht.Math.Vector2;
2 var c2ToP1 = new Vector2({ x: p1.x, y: p1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));
3 var c2ToP2 = new Vector2({ x: p2.x, y: p2.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

通過上述操作我們可以獲得三個向量 c2ToC1c2ToP1c2ToP2 所以我們可以用到我上述講的向量叉乘的概念進行 p1 與 p2 點的選取,通過向量 c2ToC1 與 c2ToP1,以及向量 c2ToC1 與 c2ToP2 分別進行叉乘得到的結果肯定一個是大於 0 一個小於 0,二維向量的叉乘可以直接把它們視為 3d 向量,z軸補 0 的三維向量,不過二維向量叉乘的結果 result 不是向量而是數值,如果 result > 0 時,那麼 a 正旋轉到 b 的角度為 <180°,如果 k < 0,那麼 a 正旋轉到 b 的角度為 >180°,如果 k = 0 那麼a,b向量平行,所以通過上面的理論知識我們可以知道結果肯定是一個大於 0 一個小於 0,我們可以在程序中測下可以知道我們需要獲取的是大於 0 的那個點 P1,所以每次可以通過上述的方法進行兩個交點的選擇。

以下為挖斗部分動畫的執行流程圖:

通過上述運算之後我們可以獲取到最終需要的點 P 坐標,點 P 坐標即為挖斗與小臂連接部分的一個重要點,獲取該點之後我們可以通過 HT 中提供的 lookAtX 函數來實現接下來的操作,lookAtX 函數的作用為讓某個物體看向某一點,使用方式如下:

1 node.lookAtX(position, 'bottom');

node 即為需要看向某一個點的節點,position 為看向的點的坐標,第二個參數有六個枚舉值可以選擇,分別為 ‘bottom’,’back’,’front’,’top’,’right’,’left’,第二個參數的作用是當我們需要把某個物體看向某一個點的時候我們也要指定該物體的哪一個面看向該點,所以需要提供第二個參數來明確,獲取到該函數之後我們可以通過將 bucketHinge 看向點 P,armHinge 看向點 P,就可以保持這兩個連接的設備永遠朝向該點,以下為部分偽代碼:

1 bucketHinge.lookAtX(P, 'front');
2 armHinge.lookAtX(P, 'bottom');

所以通過上述操作之後我們已經把挖斗部分的兩個關鍵零件的位置已經擺放正確,接下來是要正確的擺放與挖斗連接的小臂上液壓部分的位置,下一部分為介紹該節點如何進行擺放。

液壓聯動分析

在場景中我們可以看到液壓主要分為兩個部分,一部分為白色的較細的液壓桿,一部分為黑色的較厚的液壓桿,白色的液壓桿插在黑色的液壓桿中,所以在小臂或者挖斗旋轉的過程中我們要保持兩個節點始終保持相對的位置,通過上一步驟中我們可以知道 lookAtX 這個函數的作用,所以在液壓桿部分我們也是照樣用該函數來實現。

在上一步我們獲取到了挖斗旋轉過程中的關鍵點 P,所以在挖斗旋轉的過程我們小臂上的液壓桿也要相應的進行變化,具體的操作就是將小臂的白色液壓桿的位置設置為上步中計算出來的點 P 的位置,當然需要把白色液壓桿的錨點進行相應的設置,之後讓白色液壓桿 lookAt 黑色液壓桿,同時讓黑色液壓桿 lookAt 白色液壓桿,這樣下來兩個液壓桿都在互相看着對方,所以它們呈現出來的效果就是白色液壓桿在黑色液壓桿中進行伸縮,以下為偽代碼:

1 bucketWhite.p3(P);
2 bucketWhite.lookAtX(bucketBlack.p3(), 'top');
3 bucketBlack.lookAtX(P, 'bottom');

代碼中 bucketWhite 節點即為小臂上白色液壓桿,bucketBlack 節點為小臂上黑色液壓桿,通過以上的設置就可以實現伸縮的動畫效果,以下為液壓的運行圖:

同理挖機身上的大臂的液壓動作以及機身與大臂連接部分的液壓動作都是使用上面的方法來實現,以下為這兩部分的代碼:


 1 rotateBoom: (rotateVal) = >{
 2     excavatorBoomNode.setRotationX(dr * rotateVal);
 3     let archorVector = [0.5 - 0.5, 0.56 - 0.5, 0.22 - 0.5];
 4     let pos = projectUtil.toWorldPosition(g3d, excavatorBoomNode, archorVector);
 5     boomWhite.lookAtX(boomBlack.p3(), 'bottom');
 6     boomBlack.lookAtX(pos, 'top');
 7 },
 8 rotateArm: (rotateVal) = >{
 9     projectUtil.applyRelativeRotation(excavatorArmNode, excavatorBoomNode, -rotateVal);
10     let archorVector = [0.585 - 0.5, 0.985 - 0.5, 0.17 - 0.5];
11     let pos = projectUtil.toWorldPosition(g3d, excavatorArmNode, archorVector);
12     armWhite.lookAtX(armBlack.p3(), 'bottom');
13     armBlack.lookAtX(pos, 'top');
14 }


我將兩部分的運動封裝為兩個函數 rotateBoom 以及 rotateArm 分別是大臂與機身連接處的液壓運動與大臂上的液壓運動,在該部分中為了精確的獲取看向的點,我通過 toWorldPosition 方法將相對坐標轉化為世界坐標,相對坐標為黑白液壓桿的錨點坐標,轉化為相對大臂或者機身的世界坐標。

基本運動分析

挖機的基本運動包括前進後退,機身旋轉,這一部分會相對上面的運動簡單許多,在 HT 的三維坐標系中,不斷修改挖機機身的 x,y,z 的坐標值就可以實現挖機的前進後退,通過修改機身的 y 軸旋轉角度則可以控制機身的旋轉,當然挖機身體上的所有其它零部件需要吸附在機身身上,當機身進行旋轉時其它零部件則會進行相應的旋轉,在進行前進的時候挖機底部的履帶會進行對應的滾動,當然履帶我們這邊是用了一個履帶的貼圖貼在上面,當挖機前進的時候修改貼圖的偏移值就可以實現履帶的滾動,修改偏移值的偽代碼如下:

1 node.s('shape3d.uv.offset', [x, y]);

上面的 x,y 分別為 x 軸與 y 軸方向的偏移值,在挖機前進後退的過程中不斷修改 y 的值可以實現履帶的滾動效果,具體的文檔說明可以查看 

在挖機前進後退的過程中我們可以 wasd 四個鍵同時按下,並且可以對按鍵進行一直的響應,在 js 中可以通過 document.addEventListener(‘keydown’, (e) => {}) 以及 document.addEventListener(‘keyup’, (e) => {}) 進行監聽,但是這隻能每次執行一次需要執行的動作,所以我們可以在外部起一個定時器,來執行 keydown 時候需要不斷執行的動作,可以用一個 keyMap 來記錄當前已經點擊的按鍵,在 keydown 的時候紀錄為 true 在 keyup 的時候記錄為 false,所以我們可以在定時器中判斷這個 bool 值,當為 true 的時候則執行相應的動作,否則不執行,以下為對應的部分關鍵代碼:


 1 let key_pressed = {
 2     65 : {
 3         status: false,
 4         action: turnLeft
 5     },
 6     87 : {
 7         status: false,
 8         action: goAhead
 9     },
10     68 : {
11         status: false,
12         action: turnRight
13     },
14     83 : {
15         status: false,
16         action: back
17     },
18     37 : {
19         status: false,
20         action: bodyTurnLeft
21     },
22     39 : {
23         status: false,
24         action: bodyTurnRight
25     }
26 };
27 setInterval(() = >{
28     for (let key in key_pressed) {
29         let {
30             status,
31             action
32         } = key_pressed[key];
33         if (status) {
34             action();
35         }
36     }
37 },
38 50);
39 document.addEventListener('keydown', (event) = >{
40     let keyCode = event.keyCode;
41     key_pressed[keyCode] && (key_pressed[keyCode].status = true);
42     event.stopPropagation();
43 },
44 true);
45 document.addEventListener('keyup', (event) = >{
46     let keyCode = event.keyCode;
47     key_pressed[keyCode] && (key_pressed[keyCode].status = false);
48     event.stopPropagation();
49 },
50 true);


從上面代碼可以看出我在 key_pressed 變量中記錄對應按鍵以及按鍵對應的 action 動作,在 keydown 與 keyup 的時候對應修改當前 key 的 status 的狀態值,所以可以在 Interval 中根據 key_pressed 這個變量的 status 值執行對應的 action 動作,以下為執行流程圖:

HT 的輕量化,自適應讓當前系統在手機端也能流暢的運行,當然目前移動端與電腦端的 2D 圖紙部分是加載不同的圖紙,在移動端的 2D 部分只留下操作挖機的操作部分,其它部分進行了相應的捨棄,不然在移動端小屏幕下無法展示如此多的數據,在 3D 場景部分都是共用同一個場景,通過場景搭建部分的批量操作使得 3D 在手機端也十分流暢的運行,以下為手機端運行截圖:

總結

物聯網已經融入了現代生活,通過內嵌到机械設備中的电子設備,我們能夠完成對机械設備的運轉、性能的監控,以及對机械設備出現的問題進行及時的預警。在該系統 2D 面板監控部分就是對採集過來的數據進行可視化的展示,而且我們可以藉助大數據和物聯網技術,將一台台机械通過機載控制器、傳感器和無線通訊模塊,與一個龐大的網絡連接,每揮動一鏟、行動一步,都形成數據痕迹。大數據精準描繪出基礎建設開工率等情況,成為觀察固定資產投資等經濟變化的風向標。所以在實現上述挖機動作之後,通過與挖機傳感器進行連接之後,可以將挖掘機此時的真實動作通過數據傳遞到系統,系統則會根據動作進行相應的真實操作,真正實現了挖機與網絡的互聯互通。

程序運行截圖:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

分類
發燒車訊

自動任務調度 – Timer

一、概述:

最近維護一個老項目,裏面使用的是Timer的時間調度器,以前沒接觸過,對着代碼鼓搗了半天,查閱了部分博客,最後總結出自己的見解,新項目一般是不會用這種老掉牙的時間調度器了,但是維護老項目還是用的着的。就當筆記記錄一下了,自己寫的才是符合自己的思路走向的。有時間再補上Quartz調度器,這個才是現在使用最多的。

二、常用的三種調度器分類

Java自帶的java.util.Timer類,這個類允許你調度一個java.util.TimerTask任務。使用這種方式可以讓你的程序按照某一個頻度執行,但不能在指定時間運行。

使用Quartz,這是一個功能比較強大的的調度器,可以讓你的程序在指定時間執行,也可以按照某一個頻度執行,配置起來稍顯複雜。

Spring3.0以後自帶的task,可以將它看成一個輕量級的Quartz,而且使用起來比Quartz簡單許多。

三、使用Spring體系來完成代碼的搭建

1、代碼結構:

                                  

 

 

 

2、springContext.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"
       default-lazy-init="true">

    <!--定義了一個TimerFactoryBean類,並且把ScheduledTimerTask類的實例作為需要調度的task。-->
    <bean id="timerFactory" class="org.springframework.scheduling.timer.TimerFactoryBean" lazy-init="false">
        <property name="scheduledTimerTasks">
            <list>
                <ref local="scheduledTask1"/>
                <ref local="scheduledTask2"/>
            </list>
        </property>
    </bean>

    <!--利用ScheduledTimerTask類來配置每個task的啟動時間延時,每次啟動之間的間隔,當然還有最重要的是需要運行那個對象,也就是MethodInvokingTimerTaskFactoryBean類的實例-->
    <bean id="scheduledTask1" class="org.springframework.scheduling.timer.ScheduledTimerTask">
        <property name="delay" value="0" />
        <property name="period" value="1000" />
        <property name="timerTask">
            <ref bean="methodInvokingTask1"/>
        </property>
    </bean>

    <bean id="scheduledTask2" class="org.springframework.scheduling.timer.ScheduledTimerTask">
        <property name="delay" value="0" />
        <property name="period" value="1000" />
        <property name="timerTask">
            <ref bean="methodInvokingTask2"/>
        </property>
    </bean>

    <!--利用spring提供的MethodInvokingTimerTaskFactoryBean類來實現來實現對對task類和方法的聲明,聲明目標對象和方法,從而使spring知道要運行那個類的那個方法-->
    <bean id="methodInvokingTask1" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
        <property name="targetObject" ref="myTask1"/>
        <property name="targetMethod" value="run"/>
    </bean>

    <bean id="methodInvokingTask2" class="org.springframework.scheduling.timer.MethodInvokingTimerTaskFactoryBean">
        <property name="targetObject" ref="myTask2"/>
        <property name="targetMethod" value="run"/>
    </bean>

    <!--被指定自動任務的類對象-->
    <bean id="myTask1" class="com.turtle.test.MyTask">
        <property name="name" value="啟動一"/>
    </bean>

    <bean id="myTask2" class="com.turtle.test.MyTask_2">
        <property name="name" value="啟動二"/>
    </bean>

</beans>

 

3、MyTask文件

package com.turtle.test;

import java.util.TimerTask;

/**
 * 自定義一個定時任務
 * 推薦是繼承自 TimerTask
 */
public class MyTask extends TimerTask {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static int i = 0;

    // 使用線程中的方法  run
    @Override
    public void run() {
        System.out.println("定時任務啟動"+name+"----出現了"+i++);
    }
}

 

4、MyTask_2文件

package com.turtle.test;

import java.util.TimerTask;

/**
 * 自定義一個定時任務
 * 推薦是繼承自 TimerTask
 */
public class MyTask_2 extends TimerTask {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    private static int i = 0;

    // 使用線程中的方法  run
    @Override
    public void run() {
        System.out.println("定時任務啟動"+name+"----出現了"+i++);
    }
}

 

 

5、MyTestTask_Test_01

package com.turtle.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTestTask_Test_01 {
    public static void main(String[] args) {
        // 啟動測試
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("springContext.xml");
    }
}

 

 

6、結果:

 

                        

四、總結:

如果要使用TImer的調度器的話,推薦使用新的ScheduledExecutorService,這個目前沒使用,就沒進行代碼驗證了,推薦一博客,大概看了下,寫得挺好的

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

分類
發燒車訊

PCA降維的原理、方法、以及python實現。

參考:菜菜的sklearn教學之降維算法.pdf!!

PCA(主成分分析法)

1. PCA(最大化方差定義或者最小化投影誤差定義)是一種無監督算法,也就是我們不需要標籤也能對數據做降維,這就使得其應用範圍更加廣泛了。那麼PCA的核心思想是什麼呢?

  • 例如D維變量構成的數據集,PCA的目標是將數據投影到維度為K的子空間中,要求K<D且最大化投影數據的方差。這裏的K值既可以指定,也可以利用主成分的信息來確定。
  • PCA其實就是方差與協方差的運用。
  • 降維的優化目標:將一組 N 維向量降為 K 維,其目標是選擇 K 個單位正交基,使得原始數據變換到這組基上后,各變量兩兩間協方差為 0,而變量方差則盡可能大(在正交的約束下,取最大的 K 個方差)。

2. PCA存在的問題:

  • 原來的數據中比如包括了年齡,性別,身高等指標降維后的數據既然維度變小了,那麼每一維都是什麼含義呢?這個就很難解釋了,所以PCA本質來說是無法解釋降維后的數據的物理含義,換句話說就是降維完啦計算機能更好的認識這些數據,但是咱們就很難理解了。
  • PCA對數據有兩個假設:數據必須是連續數值型;數據中沒有缺失值。
  • 過擬合:PCA 保留了主要信息,但這個主要信息只是針對訓練集的,而且這個主要信息未必是重要信息。有可能捨棄了一些看似無用的信息,但是這些看似無用的信息恰好是重要信息,只是在訓練集上沒有很大的表現,所以 PCA 也可能加劇了過擬合;

3. PCA的作用:

  • 緩解維度災難:PCA 算法通過捨去一部分信息之後能使得樣本的採樣密度增大(因為維數降低了),這是緩解維度災難的重要手段;
  • 降噪:當數據受到噪聲影響時,最小特徵值對應的特徵向量往往與噪聲有關,將它們捨棄能在一定程度上起到降噪的效果;
  • 特徵獨立:PCA 不僅將數據壓縮到低維,它也使得降維之後的數據各特徵相互獨立

4. 方差的作用:咱們可以想象一下,如果一群人都堆疊在一起,我們想區分他們是不是比較困難,但是如果這群人站在馬路兩側,我們就可以很清晰的判斷出來應該這是兩伙人。所以基於方差我們可以做的就是讓方差來去判斷咱們數據的擁擠程度,在這裏我們認為方差大的應該辨識度更高一些,因為分的比較開(一條馬路給隔開啦)。方差可以度量數值型數據的,數據若是想要區分開來,他那他們的離散程度就需要比較大,也就是方差比較大。

5. 協方差的作用:

6. 計算過程:(下圖為採用特徵值分解的計算過程,若採用SVM算法,則無需計算協方差矩陣!)

為什麼我們需要協方差矩陣?我們最主要的目的是希望能把方差和協方差統一到一個矩陣里,方便後面的計算。

  假設我們只有 a 和 b 兩個變量,那麼我們將它們按行組成矩陣 X:(與matlab不同的是,在numpy中每一列表示每個樣本的數據,每一行表示一個變量。比如矩陣X,該矩陣表示的意義為:有m個樣本點,每個樣本點由兩個變量組成!)

  然後:

          

  Cov(a,a) = E[(a-E(a))(a-E(a))], Cov(b,a) = E[(b-E(b))(a-E(a))],因為E(b)=E(a)=0,所以大大簡化了計算!!!(這就體現了去中心化的作用!)

  我們可以看到這個矩陣對角線上的分別是兩個變量的方差,而其它元素是 a 和 b 的協方差。兩者被統一到了一個矩陣里。

7. 特徵值與特徵向量的計算方法—--特徵值分解奇異值分解法(SVD)(有關特徵值與奇異值可見我的博文!)

(1) 特徵值分解的求解過程較為簡單,以下圖為例子

(2) 特徵值分解存在的缺點:

  • 特徵值分解中要求協方差矩陣A必須是方陣,即規模必須為n*n。
  • 後期計算最小投影維度K時,計算量過大。
  • 當樣本維度很高時,協方差矩陣計算太慢;

(3) SVD算法(奇異值分解)的提出克服這些缺點,目前幾乎所有封裝好的PCA算法內部採用的都是SVD算法進行特徵值、特徵向量以及K值的求解。

  • 奇異值(每個矩陣都有):設A是一個mXn矩陣,稱正半定矩陣A‘A的特徵值的非負平方根為矩陣A的奇異值,其中A‘表示矩陣A的共扼轉置矩陣(實數矩陣的共軛就是轉置矩陣,複數矩陣的共軛轉置矩陣就是上面所說的行列互換后每個元素取共軛)
  • 只有方陣才有特徵值。

(4) SVD算法的計算過程:(numpy中已經將SVD進行了封裝,所以只需要調用即可)

可以發現,採用SVD算法無需計算協方差矩陣,這樣在數據量非常大的時候可以降低消耗。

  • A為數據矩陣,大小為M*N(2*5)
  • U是一個由與數據點之間具有最小投影誤差的方向向量所構成的矩陣,大小為M*M(2*2),假如想要將數據由M維降至K維,只需要從矩陣U中選擇前K個列向量,得到一個M*K的矩陣,記為Ureduce。按照下面的公式即可計算降維后的新數據:降維后的數據矩陣G = A.T * Ureduce. 
  • sigma為一個列向量,其包含的值為矩陣A的奇異值。
  • VT是一個大小為N*N的矩陣,具體意義我們無需了解。

利用python實現PCA降維(採用SVD的方法):

 1 from numpy import linalg as la
 2 import numpy as np
 3 #1.矩陣A每個變量的均值都為0,所以不用進行“去平均值”處理。倘若矩陣A的每個變量的均值不為0,則首先需要對數據進行預處理
 4 #  才可以進行協方差矩陣的求解。
 5 #2.與matlab不同的是,在numpy中每一列表示每個樣本的數據,每一行表示一個變量。
 6 #  比如矩陣A,該矩陣表示的意義為:有5個樣本點,每個樣本點由兩個變量組成!
 7 #3.np.mat()函數中矩陣的乘積可以使用 * 或 .dot()函數
 8 #  array()函數中矩陣的乘積只能使用 .dot()函數。而星號乘(*)則表示矩陣對應位置元素相乘,與numpy.multiply()函數結果相同。
 9 A = np.mat([[-1, -1, 0, 2, 0], [-2, 0, 0, 1, 1]])
10 # A = np.mat([[-1, -2], [-1, 0], [0, 0], [2, 1], [0, 1]]).T
11 U, sigma, VT = la.svd(A)
12 print("U:")
13 print(U)
14 print("sigma:")
15 print(sigma)
16 print("VT:")
17 print(VT)
18 print("-"*30)
19 print("降維前的數據:")
20 print(A.T)
21 print("降維后的數據:")
22 print(A.T * U[:,0])

運行結果圖:與上文採用特徵值分解所得到的降維結果一致!

8.PCA的重建

 眾所周知,PCA可以將高維數據壓縮為較少維度的數據,由於維度有所減少,所以PCA屬於有損壓縮,也就是,壓縮后的數據沒有保持原來數據的全部信息,根據壓縮數據無法重建原本的高維數據,但是可以看作原本高維數據的一種近似。

 還原的近似數據矩陣Q = 降維后的矩陣G * Ureduce.T

9.採用sklearn封裝好的PCA實現數據降維(採用的是SVD算法):

 1 import numpy as np
 2 from sklearn.decomposition import PCA
 3 # 利用sklearn進行PCA降維處理的時候,數據矩陣A的行數表示數據的個數,數據矩陣A的列數表示每條數據的維度。這與numpy中是相反的!
 4 # A = np.mat([[-1, -1, 0, 2, 0], [-2, 0, 0, 1, 1]]).T
 5 A = np.mat([[-1, -2], [-1, 0], [0, 0], [2, 1], [0, 1]])
 6 pca = PCA(n_components = 1)
 7 pca.fit(A)
 8 # 投影后的特徵維度的方差比例
 9 print(pca.explained_variance_ratio_)
10 # 投影后的特徵維度的方差
11 print(pca.explained_variance_)
12 print(pca.transform(A))

 可以發現,採用sklearn封裝的方法實現PCA與上文的方法達到的結果一致!

10.如何確定主成分數量(針對於Sklearn封裝的PCA方法而言)

PCA算法將D維數據降至K維,顯然K是需要選擇的參數,表示要保持信息的主成分數量。我們希望能夠找到一個K值,既能大幅降低維度,又能最大限度地保持原有數據內部的結構信息。實現的過程是通過SVD方法得到的S矩陣進行操作求解,

 

11.sklearn中封裝的PCA方法的使用介紹。

PCA的函數原型

 (1)主要參數介紹

n_components

  • 這個參數類型有int型,float型,string型,默認為None。 它的作用是指定PCA降維后的特徵數(也就是降維后的維度)。 
  • 若取默認(None),則n_components==min(n_samples, n_features),即降維后特徵數取樣本數和原有特徵數之間較小的那個;
  • 若n_components}設置為‘mle’並且svd_solver設置為‘full’則使用MLE算法根據特徵的方差分佈自動去選擇一定數量的主成分特徵來降維; 
  • 若0<n_components<1,則n_components的值為主成分方差的閾值; 通過設置該變量,即可調整主成分數量K。
  • 若n_components≥1,則降維后的特徵數為n_components; 

copy

  •  bool (default True) 
  • 在運行算法時,將原始訓練數據複製一份。參數為bool型,默認是True,傳給fit的原始訓練數據X不會被覆蓋;若為False,則傳給fit后,原始訓練數據X會被覆蓋。 

whiten

  • bool, optional (default False)
  • 是否對降維后的數據的每個特徵進行歸一化。參數為bool型,默認是False。

(2)主要方法介紹:

fit(X,y=None) :用訓練數據X訓練模型,由於PCA是無監督降維,因此y=None。 

transform(X,y=None) :對X進行降維。 

fit_transform(X) :用訓練數據X訓練模型,並對X進行降維。相當於先用fit(X),再用transform(X)。 

inverse_transform(X) :將降維后的數據轉換成原始數據。(PCA的重建)

 (3)主要屬性介紹:

components:array, shape (n_components, n_features) ,降維后各主成分方向,並按照各主成分的方差值大小排序。 

explained_variance:array, shape (n_components,) ,降維后各主成分的方差值,方差值越大,越主要。 

explained_variance_ratio:array, shape (n_components,) ,降維后的各主成分的方差值佔總方差值的比例,比例越大,則越主要。 

singular_values:array, shape (n_components,) ,奇異值分解得到的前n_components個最大的奇異值。

 

 二、LDA

1. 類間距離最大,類內距離最小(核心思想)

2. LDA的原理,公式推導見西瓜書,這裏主要講一下PCA與LDA的異同點!

  • PCA為非監督降維,LDA為有監督降維PCA希望投影后的數據方差盡可能的大(最大可分性),因為其假設方差越多,則所包含的信息越多;而LDA則希望投影后相同類別的組內方差小,而組間方差大。LDA能合理運用標籤信息,使得投影后的維度具有判別性,不同類別的數據盡可能的分開。舉個簡單的例子,在語音識別領域,如果單純用PCA降維,則可能功能僅僅是過濾掉了噪聲,還是無法很好的區別人聲,但如果有標籤識別,用LDA進行降維,則降維后的數據會使得每個人的聲音都具有可分性,同樣的原理也適用於臉部特徵識別。
  • 所以,可以歸納總結為有標籤就盡可能的利用標籤的數據(LDA),而對於純粹的非監督任務,則還是得用PCA進行數據降維。
  • LDA降維最低可以降維到(類別數-1),而PCA沒有限制

 

參考資料:https://zhuanlan.zhihu.com/p/77151308?utm_source=qq&utm_medium=social&utm_oi=1095998405318430720

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

分類
發燒車訊

用python實現對元素的長截圖

一.目標

瀏覽網頁的時候,看見哪個元素,就能截取哪個元素當圖片,不管那個元素有多長

 

二.所用工具和第三方庫

python ,PIL,selenium

pycharm

三.代碼部分

長截圖整體思路:

1.獲取元素

2.移動,截圖,移動,截圖,直到抵達元素的底部

3.把截圖按照元素所在位置切割,在所有圖片中只保留該元素

4.拼接

 

如果driver在環境變量中,那麼不用指定路徑

b=webdriver.Chrome(executable_path=r"C:\Users\Desktop\chromedriver.exe")#指定一下driver
b.get("")
b.maximize_window()#最大化窗口

打開網站

 

 

 我們可以看見一個ID為maincontent的元素,寬度為850PX,長度為3828PX,這個長度必須使用才能長截圖才能完整截下來

 

el=b.find_element_by_id("maincontent")#找到元素

我們還需要一個重要的參數,就是你電腦一次能截取多高的像素

先用下圖代碼獲取一個圖片

#fp為存放圖片的地址
b.get_screenshot_as_file(fp)

 

也就是說用我電腦上截圖的默認高度為614像素

 

 所以我設置一個變量:

sc_hight=614

然後設置一下其他變量

    count = int(el.size["height"] / sc_hight)  # 元素的高度除以你每次截多少就是次數
    start_higth = el.location["y"]  # 元素的初始高度
    max_px = start_higth + (count - 1) * sc_hight  # for循環中最大的px
    last_px = el.size["height"] + start_higth - sc_hight  # 元素最底部的位置
    surplus_px = last_px - max_px  # 剩餘的邊的高度
    img_path = []  # 用來存放圖片地址

註釋:

1.count為元素的高度/每次截取的高度,比如這次實例中元素高度為3828PX,我每次截614px,需要6.2次,int之後變成6,也就是截6次,還剩一點,那一點後面再說

2.start_higth為初始高度,這個沒有什麼可說的

3.max_px為循環結束后,到達的高度

4.last_px為元素最底部的高度

5.surplus_px就是移動6次后,還沒有截取的高度

屏幕每次移動,移動sc_hight個像素,初始位置為(0,元素的Y值)

    for i in range(0, count):
        js = "scrollTo(0,%s)" % (start_higth + i * sc_hight)  # 用於移動滑輪,每次移動614px,初始值是元素的初始高度
        b.execute_script(js)  # 執行js
        time.sleep(0.5)
        fp = r"C:\Users\wdj\Desktop\%s.png" % i  # 圖片地址,運行的話,改一下
        b.get_screenshot_as_file(fp)  # 屏幕截圖,這裡是截取是完整的網頁圖片,你可以打斷點看一下圖片
        img = Image.open(fp=fp)
        img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], sc_hight))  # 剪切圖片
        img2.save(fp)  # 保存圖片,覆蓋完整的網頁圖片
        img_path.append(fp)  # 添加圖片路徑
        time.sleep(0.5)
        print(js)
    else:
        js = "scrollTo(0,%s)" % last_px  # 滾動到最後一個位置
        b.execute_script(js)
        fp = r"C:\Users\wdj\Desktop\last.png"
        b.get_screenshot_as_file(fp)
        img = Image.open(fp=fp)
        print((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
        img2 = img.crop((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
        img2.save(fp)
        img_path.append(fp)
        print(js)

上面是把該元素的在頁面都截完,並且剪切,把圖片保存的路徑放入img_path

最後一步:把所有截圖都貼到新創建的圖片中

    new_img = Image.new("RGB", (el.size["width"], el.size["height"]))  # 創建一個新圖片,大小為元素的大小
    k = 0
    for i in img_path:
        tem_img = Image.open(i)
        new_img.paste(tem_img, (0, sc_hight * k))  # 把圖片貼上去,間隔一個截圖的距離
        k += 1
    else:
        new_img.save(r"C:\Users\wdj\Desktop\test.png")  # 保存

 

運行效果圖:


說明完整的截取下來了

 

 

 

補充優化:

如果是個小元素怎麼辦,不用長截圖就能截取的那種

因為很簡單我就直接貼代碼了

    start_higth = el.location["y"]
    js = "scrollTo(0,%s)" % (start_higth)
    b.execute_script(js)  # 執行js
    time.sleep(0.5)
    fp = r"C:\Users\wdj\Desktop\test.png" # 圖片地址,運行的話,改一下
    b.get_screenshot_as_file(fp)
    img = Image.open(fp=fp)
    img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], el.size["height"]))  # 剪切圖片
    img2.save(fp)

效果如下:

 

 

完整代碼:

from selenium import webdriver
from PIL import Image
import time
def short_sc(el,b):
    start_higth = el.location["y"]
    js = "scrollTo(0,%s)" % (start_higth)
    b.execute_script(js)  # 執行js
    time.sleep(0.5)
    fp = r"C:\Users\wdj\Desktop\test.png" # 圖片地址,運行的話,改一下
    b.get_screenshot_as_file(fp)
    img = Image.open(fp=fp)
    img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], el.size["height"]))  # 剪切圖片
    img2.save(fp)

def long_sc(el,b):
    count = int(el.size["height"] / sc_hight)  # 元素的高度除以你每次截多少就是次數
    start_higth = el.location["y"]  # 元素的初始高度
    max_px = start_higth + (count - 1) * sc_hight  # for循環中最大的px
    last_px = el.size["height"] + start_higth - sc_hight  # 元素最底部的位置
    surplus_px = last_px - max_px  # 剩餘的邊的高度
    img_path = []  # 用來存放圖片地址
    for i in range(0, count):
        js = "scrollTo(0,%s)" % (start_higth + i * sc_hight)  # 用於移動滑輪,每次移動614px,初始值是元素的初始高度
        b.execute_script(js)  # 執行js
        time.sleep(0.5)
        fp = r"C:\Users\wdj\Desktop\%s.png" % i  # 圖片地址,運行的話,改一下
        b.get_screenshot_as_file(fp)  # 屏幕截圖,這裡是截取是完整的網頁圖片,你可以打斷點看一下圖片
        img = Image.open(fp=fp)
        img2 = img.crop((el.location["x"], 0, el.size["width"] + el.location["x"], sc_hight))  # 剪切圖片
        img2.save(fp)  # 保存圖片,覆蓋完整的網頁圖片
        img_path.append(fp)  # 添加圖片路徑
        time.sleep(0.5)
        print(js)
    else:
        js = "scrollTo(0,%s)" % last_px  # 滾動到最後一個位置
        b.execute_script(js)
        fp = r"C:\Users\wdj\Desktop\last.png"
        b.get_screenshot_as_file(fp)
        img = Image.open(fp=fp)
        print((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
        img2 = img.crop((el.location["x"], sc_hight - surplus_px, el.size["width"] + el.location["x"], sc_hight))
        img2.save(fp)
        img_path.append(fp)
        print(js)

    new_img = Image.new("RGB", (el.size["width"], el.size["height"]))  # 創建一個新圖片,大小為元素的大小
    k = 0
    for i in img_path:
        tem_img = Image.open(i)
        new_img.paste(tem_img, (0, sc_hight * k))  # 把圖片貼上去,間隔一個截圖的距離
        k += 1
    else:
        new_img.save(r"C:\Users\wdj\Desktop\test.png")  # 保存

b=webdriver.Chrome(executable_path=r"C:\Users\wdj\Desktop\chromedriver.exe")#指定一下driver
b.get("https://www.w3school.com.cn/html/html_links.asp")
b.maximize_window()#最大化窗口
# b.get_screenshot_as_file(fp)
sc_hight=614#你屏幕截圖默認的大小,可以去截一張,去畫圖裡面看看是多少像素,我這裡是614像素

# b.switch_to.frame(b.find_element_by_xpath('//*[@id="intro"]/iframe'))
el=b.find_element_by_id("maincontent")#找到元素
if el.size["height"]>sc_hight:
    long_sc(el,b)
else:
    short_sc(el,b)

完整代碼

 

PS:

有些特殊情況,比如截取的元素在iframe中,直接用driver.switch_to.frame(iframe元素)即可

或者不是iframe,但是元素有overflow屬性,直接用JS把他的overflow去掉就行

 

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

C表達式中的彙編指令

C 表達式中的彙編指令

asm 為 gcc 中的關鍵字,asm 表達式為在 C代碼中嵌套彙編指令,該表達式只是單純的替換出彙編代碼,並不對彙編代碼的含義進行解析。

asm 表達式有兩種形式,第二種 asm-qualifiers 包含了 goto 語句。
第一種形式為常見的用法,AssemblerTemplate 和 OutputOperands 必須存在, 其中 Clobbers 存在需要 InputOperands 也出現。

asm asm-qualifiers ( AssemblerTemplate 
                 : OutputOperands 
                 [ : InputOperands
                 [ : Clobbers ] ])

asm asm-qualifiers ( AssemblerTemplate 
                      : 
                      : InputOperands
                      : Clobbers
                      : GotoLabels)

Qualifiers 的類型

  • volatile, 避免編譯器的優化
  • inline, 內斂限定符,最小的體積
  • goto, 包含跳轉指令

參數

  • AssemblerTemplate
    – 彙編指令模板是包含彙編器指令的文字字符串,編輯器替換引用輸入,編譯器不會解析該指令的含義。
  • OutputOperands
    – 由 AssemblerTemplate 中的指令修改的C變量的逗號分隔列表,允許使用空列表。
  • InputOperands
    – 由 AssemblerTemplate 中的指令讀取的C變量的逗號分隔列表,允許使用空列表。
  • Clobbers
    – 用逗號分隔的寄存器列表或由 AssemblerTemplate 修改的值,不能出現在 OutputOperands 和 InputOperands 中被提及,允許使用空列表。
  • GotoLabels
    – 當使用asm的goto形式時,此部分包含 AssemblerTemplate 中的代碼可能跳轉到的所有C標籤的列表。

AssemblerTemplate

彙編指令由一個字符串給出,多條彙編指令結合在一起使用的時候,中間以 \r\t 隔開,如

asm("inc %0\n\tinc %0" : "=r"(res) : "0"(res));

/APP
# 11 "asm.c" 1
        inc %rax
        inc %rax
# 0 "" 2
/NO_APPs

需要轉義的字符:%, =, {, }, |

故在ATT彙編中,對寄存器進行操作的需要雙 %%, 如 inc %%rax.

OutputOperands

操作數之間用逗號分隔。 每個操作數具有以下格式:

[ [asmSymbolicName] ] constraint (cvariablename)
  • asmSymbolicName
    – 為操作數指定名稱,格式為 %[name]
    c // res = num asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
    – 如果未指定名稱使用数字, 從 output 域開始,第一個參數為 %0, 一次類推, 這裏的 res 為 %0, num 為 %1
    c // res = num asm("movq %1, %0" : "=r"(res) : "m"(num));
  • constraint
    – 一個字符串常量,用於指定對操作數的存儲的 , 需要以 “=” 或 “+” 開頭
  • cvariablename
    – 指定一個C左值表達式來保存輸出,通常是一個變量名。 括號是語法的必需部分

第一個參數為增加可讀性使用的,現在我們有代碼如下

int64_t res;
int64_t num = 1;

asm("movq %[num], %[res]" : [res] "=r"(res) : [num] "m"(num));
asm("movq %1, %0" : "=r"(res) : "m"(num));
asm("movq %1, %0" : "=m"(res) : "m"(num));
asm("movq %1, %0" : "=r"(res) : "r"(num));

// 對應的彙編代碼, 只保留asm表達式中的代碼
# 13 "asm.c" 1
        movq -16(%rbp), %rax  // asm-1
 # 0 "" 2
/NO_APP

/APP
 # 15 "asm.c" 1
        movq -16(%rbp), %rax  // asm-2
 # 0 "" 2
/NO_APP

/APP
 # 17 "asm.c" 1
        movq -16(%rbp), -8(%rbp)  // asm-3
 # 0 "" 2
/NO_APP

/APP
 # 19 "asm.c" 1
        movq %rax, %rax  // asm-4
 # 0 "" 2
/NO_APP
  1. 使用名稱替換和数字替換效果一樣,見 asm-1 和 asm-2
  2. 約束的用法,這裏使用比較簡單通用的的兩種情況,r 為通過寄存器尋址操作,m 通過內存尋址操作,所以看到當約束了 r 就對應寄存器的操作。
  3. 結果保存在 res 也就是 cvariablename 中

InputOperands

輸入操作數使C變量和表達式中的值可用於彙編代碼。

[ [asmSymbolicName] ] constraint (cexpression)
  • asmSymbolicName 和輸出列表的用法完全一致
  • constraint 約束不能使用 =+. 可以使用 “0”, 這表明在輸出約束列表中(從零開始)的條目,指定的輸入必須與輸出約束位於同一位置。
int64_t res = 3;
int64_t num = 1;
asm("addq %1, %0" : "=g"(res) : "0"(num));

// 輸入輸出位置相同
        movq    $3, -8(%rbp)
        movq    $1, -16(%rbp)
        movq    -16(%rbp), %rax
/APP
# 32 "asm.c" 1
        addq %rax, %rax
# 0 "" 2
/NO_APP
  • cexpression 可以不為左值,作為彙編表達式的輸入值即可

Clobbers

破壞列表,主要用於指示編譯器生成的彙編指令。

從asm表達式中看到輸出操作數中列出條目的更改編譯器是可以確定的,但內聯彙編代碼可能不僅對輸出進行了修改。 例如,計算可能需要其他寄存器,或者處理器可能會由於特定彙編程序指令而破壞寄存器的值。 為了將這些更改通知編譯器,在Clobber列表中列出這些會產生副作用的條目。 破壞列表條目可以是寄存器名稱,也可以是特殊的破壞列表項(在下面列出)。 每個內容列表條目都是一個字符串常量,用雙引號引起來並用逗號分隔。

  • 寄存器

      ```c
      asm volatile("movc3 %0, %1, %2"
              : /* No outputs. */
              : "r"(from), "r"(to), "g"(count)
              : "%rbx", "%rcx", "%rdx", "memory");
    
      /APP
      # 25 "asm.c" 1
              movc3 %rax, %r8, -72(%rbp)
      # 0 "" 2
      /NO_APP
      ```
    
      可以看到使用到了 rax 寄存器,然後修改程序在 Clobbers 增加 %rax, 結果如下
    
      ```c
      asm volatile("movc3 %0, %1, %2"
              : /* No outputs. */
              : "r"(from), "r"(to), "g"(count)
              : "%rax", "%rbx", "%rcx", "%rdx", "memory");
    
      /APP
      # 25 "asm.c" 1
              movc3 %r8, %r9, -72(%rbp)
      # 0 "" 2
      /NO_APP
      ```
      編譯器在產生的彙編代碼中就未使用 %rax 寄存器了。
  • 特殊破壞列表項
    – “cc”, 表示彙編代碼修改了標誌寄存器
    – “memory”, 為了確保內存中包含正確的值,編譯器可能需要在執行asm之前將特定的寄存器值刷新到內存中

編譯器為了破壞列表項的值受到破壞,當這些條目是寄存器時,不對其進行使用;為特殊參數時,重新刷新得到最新的值。

約束

  • 一些基礎的約束
約束名 說明
whitespace 空白字符被忽略
m 允許使用內存操作數,以及機器通常支持的任何類型的地址
o 允許使用內存操作數,但前提是地址是可偏移的
V 允許使用內存操作數,不可偏移的內存地址,與 “o’互斥
r 允許在通用寄存器中使用的寄存器操作數,其中可以指定寄存器,如 a(%rax), b(%rbx)
i 允許使用立即整數操作數
n 允許使用具有已知數值的立即整數操作數, ‘I’, ‘J’, ‘K’, … ‘P’ 更應該使用 n
F 允許使用浮點立即數
g 允許使用任何寄存器,內存或立即數整數操作數,但非通用寄存器除外
X 允許任何操作數, ‘0’, ‘1’, ‘2’, … ‘9’
p 允許使用有效內存地址的操作數
  • 標識符約束
標識符 說明
= 表示此操作數是由該指令寫入的:先前的值將被丟棄並由新數據替換
+ 表示該操作數由指令讀取和寫入
& 表示(在特定替代方法中)此操作數是早期指令操作數,它是在使用輸入操作數完成指令之前寫入的,故輸入操作數部分不能分配與輸出操作數相同的寄存器
% 表示該操作數與後續操作數的可交換指令

內核示例

  1. x86 的內存屏障指令。
// 避免編譯器的優化,聲明此處內存可能發生破壞
#define barrier() asm volatile("" ::: "memory")
// 在32位的CPU下,lock 指令為鎖總線,加上一條內存操作指令就達到了內存屏障的作用,64位的cpu已經有新增的 *fence 指令可以使用
// mb() 執行一個內存屏障作用的指令,為指定CPU操作;破壞列表聲明 cc memory 指示避免編譯器進行優化
#ifdef CONFIG_X86_32
#define mb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "mfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#define rmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "lfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#define wmb() asm volatile(ALTERNATIVE("lock; addl $0,-4(%%esp)", "sfence", \
                                X86_FEATURE_XMM2) ::: "memory", "cc")
#else
#define mb()    asm volatile("mfence":::"memory")
#define rmb()   asm volatile("lfence":::"memory")
#define wmb()   asm volatile("sfence" ::: "memory")
#endif
  1. x86 下獲取 current 的值
DECLARE_PER_CPU(struct task_struct *, current_task);

#define this_cpu_read_stable(var)   percpu_stable_op("mov", var)

static __always_inline struct task_struct *get_current(void)
{
        return this_cpu_read_stable(current_task);
}

#define percpu_stable_op(op, var)           \
({                          \
        typeof(var) pfo_ret__;              \
        switch (sizeof(var)) {              \
        case 8:                     \
                asm(op "q "__percpu_arg(P1)",%0"    \
                : "=r" (pfo_ret__)          \
                : "p" (&(var)));            \
                break;                  \
        }                       \
        pfo_ret__;                  \
})

current_task 為一個 struct task_struct 類型的指針,追蹤宏調用,在x86-64 下命中了 case 8: 的彙編代碼, 展開的代碼為

asm("mov" "q ""%%""gs" ":" "%" "P1"",%0" : "=r" (pfo_ret__) : "p" (&(current_task)));
// 變換一下為
asm("movq %%gs:%P1, %0" : "=r"(pfo_ret__) : "p"(&(current_task)));

這行代碼的含義為將 約束輸入部分必須為有效的地址(p約束), 將CPU id(通過段寄存器gs和偏移通過GDT得到,這裏後文分析了)通過寄存器(r約束)賦值給 pfo_ret__.

參考

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

南投搬家前需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

分類
發燒車訊

手寫jwt驗證,實現java和node無縫切換

前言

前端時間和我寫了一個簡易用戶管理後台,功能其實很簡單,涉及到的技術棧有:vue+elementUI,java+spring MVC以及node+egg,數據庫用的mysql,簡單方便。

一開始是我是只負責前端,但是前端開發的的速度太快,老是沒事,加上他小子並沒有接觸過實戰的項目,又怕他出亂子,所以考慮我也寫一個後端,

開始考慮的是用python+django,但是還是在半途放棄了,因為總感覺Django不過靈活,使用起來非常彆扭,也可能是用scrapy寫爬蟲寫多了,難以理解django的框架設計,總是會把他想象成一個scrapy架構,也導致代碼寫的很亂,下面上一張scrapy架構圖。

所以最後考慮的用node,Express和Koa框架學習過node應該都知道,我以前也用過express(寫過一個小demo),但是給我的感覺這到像是一個只能有5年以上node開發經驗才能玩的轉的,因為express是非常的精簡的,安裝它后,會發現它幾乎不會為你提供任何編碼規範,也沒有約束你的框架應該怎麼設計,以至於新手下載完成后完全不知道自己應該幹嘛,甚至不知道直接的文件應該寫在哪,沒有框架本身的約定,標準的MVC模型有着千奇百怪的寫法,所以我覺得沒有一定的架構思想和經驗是很難駕馭的。

koa我並沒有直接的使用過,只是聽說是express的原班人馬打造的,中間件是基於洋蔥圈模型實現的。下面上一張圖洋蔥圈模型圖。

而是後來直接就接觸的egg,egg標語是“為企業級框架和應用而生”,

廢話說的有點多了,重點是通一套前端代碼,開發了兩套後端代碼,功能是完全一致的,後端都實現了相應的jwt驗證功能,–>,密鑰都是我們約定好的固定值,但是最後發現一樣的密鑰,相同的數據,使用的jwt包不同產生的結果也不同,這也就導致兩端之間無法相互切換,每次切換必須從登陸重新開始,這不太符合邏輯,而且重要的是後面的安排有需要這樣的功能,無法做到無縫切換就直接導致實現不了下面的功能。

提綱內容

  • jwt實現原理
  • jwt未能解決的疑惑
  • base64和base64url

jwt實現原理

其實作為一個前端開發人員,jwt實現原理我是沒必要懂得的,但是如果你不限於此,這算是個必不可少的內容了吧。

先上一張圖。

圖中数字1是後端使用jwt工具包生成的token,通常是由三部分組成,也就是token中間的兩個“.”將其分為三部分,第一部分對應的是右邊的数字2部分,然後依次對應。

這三部分分別是頭部、有效載荷、簽名。

頭部:alg是指說用到的算法,type當然是令牌類型

有效荷載:sub所簽發的用戶,name是簽發者的姓名,lat是這簽發時間,exp是指到期時間,當然還有一些其他的,這些數據都是非必要數據,實則只有exp可能有用,因為有效數據實際都是在data裏面,當然你也可以不這麼做。

簽名:前兩者都是通過base64url編碼過的,而非是算法加密的,所以幾乎是透明的。但是簽名是默認是通過hsa256算法加密的 ,加密的規則是:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

如何驗證token呢?那更加簡單了,就是用前端傳回來的token前兩部分和服務器存的秘鑰再次加密一次,然後做base64url處理和第三部分比較,一樣代表這個token是自己簽發的,不一樣代表是偽造的,完了過後再將有效載荷部分進行base64url反編碼,就得到exp,然後和當前時間比較是否過期。

jwt未能解決的疑惑

經過測試,發現三個問題。

1、 node和java使用對應的jwt工具包生成的token不相同,這裡是指在一系列參數完全相同的情況下。

2、 還發現用java生成的token在jwt官網解析時候輸入秘鑰結果的到的簽名和自己的簽名不一樣,但是node是一樣的,於是就產生了java工具包在輸入簽名的時候對密鑰進行了其他處理的想法,但是他並沒有找出做出來怎麼樣的處理,也就是說我們在生成簽名的時候根本就連密鑰都是不一樣的。

3、 不管是node還是java生成的token,我們用原來一模一樣的數據和一樣的算法公式都產生不了和工具包生成一樣的簽名,也就是可以懷疑在進行hsa256算法加密前確實對秘鑰進行處理了。

base64和base64url

base64就是一種二進制編碼方式,原理很簡單,先準備一個包含64個字符的數組:

['A','B','C',...'a','b','c',...'0','1','2',...'+','/']

然後對二進制數據進行處理,每三個字節一組,一共24bit,一個字節8bit嘛,然後再將24分為4組,每組正好6bit。6bit的話就剛好能表示64個字符。如果編碼字符不是3的倍數,就會剩下一個字節或者兩個字節,這個時候就在後面填充\x00,再在編碼末尾加上一個或者兩個=號。解碼的時候制動力去掉就好了。

base64url編碼就是將字符編碼成url能傳遞的參數,因為base64編碼會出現+號和/號,然後就會在url中出現問題,所以base64url其實就是將+和/分別變成-和_

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

分類
發燒車訊

Spring Boot 2.X(十六):應用監控之 Spring Boot Actuator 使用及配置

Actuator 簡介

Actuator 是 Spring Boot 提供的對應用系統的自省和監控功能。通過 Actuator,可以使用數據化的指標去度量應用的運行情況,比如查看服務器的磁盤、內存、CPU等信息,系統的線程、gc、運行狀態等等。

Actuator 通常通過使用 HTTP 和 JMX 來管理和監控應用,大多數情況使用 HTTP 的方式。

Actuator 端點說明

端點 描述
auditevents 獲取當前應用暴露的審計事件信息
beans 獲取應用中所有的 Spring Beans 的完整關係列表
caches 獲取公開可以用的緩存
conditions 獲取自動配置條件信息,記錄哪些自動配置條件通過和沒通過的原因
configprops 獲取所有配置屬性,包括默認配置,显示一個所有 @ConfigurationProperties 的整理列版本
env 獲取所有環境變量
flyway 獲取已應用的所有Flyway數據庫遷移信息,需要一個或多個 Flyway Bean
liquibase 獲取已應用的所有Liquibase數據庫遷移。需要一個或多個 Liquibase Bean
health 獲取應用程序健康指標(運行狀況信息)
httptrace 獲取HTTP跟蹤信息(默認情況下,最近100個HTTP請求-響應交換)。需要 HttpTraceRepository Bean
info 獲取應用程序信息
integrationgraph 显示 Spring Integration 圖。需要依賴 spring-integration-core
loggers 显示和修改應用程序中日誌的配置
logfile 返回日誌文件的內容(如果已設置logging.file.name或logging.file.path屬性)
metrics 獲取系統度量指標信息
mappings 显示所有@RequestMapping路徑的整理列表
scheduledtasks 显示應用程序中的計劃任務
sessions 允許從Spring Session支持的會話存儲中檢索和刪除用戶會話。需要使用Spring Session的基於Servlet的Web應用程序
shutdown 關閉應用,要求endpoints.shutdown.enabled設置為true,默認為 false
threaddump 獲取系統線程轉儲信息
heapdump 返回hprof堆轉儲文件
jolokia 通過HTTP公開JMX bean(當Jolokia在類路徑上時,不適用於WebFlux)。需要依賴 jolokia-core
prometheus 以Prometheus服務器可以抓取的格式公開指標。需要依賴 micrometer-registry-prometheus

Actuator 使用及配置

快速使用

項目依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
</dependencies>

配置文件

management.endpoints.enabled-by-default=true
#啟動所有端點
management.endpoints.web.exposure.include=*
#自定義管理端點路徑
#management.endpoints.web.base-path=/manage

Spring Boot 2.X 中,Actuator 默認只開放 health 和 info 兩個端點。

添加management.endpoints.web.exposure.include=*配置后啟動應用,訪問 http://127.0.0.1:8080/actuator 我們可以看到所有的 Actuator 端點列表。

如果將management.endpoints.enabled-by-default設置為false,則禁用所有端點,如需啟用則如下:

management.endpoints.enabled-by-default=false
management.endpoint.info.enabled=true

禁用的端點將從應用程序上下文中完全刪除。如果只想更改公開端點,使用include和exclude屬性。使用如下:

management.endpoints.web.exposure.include=*
management.endpoints.web.exposure.exclude=env,beans

management.endpoints.web.base-path=/manage 配置表示將 /actuator 路徑重定義為 /manage。

常用端點詳解

health

主要用來檢測應用的運行狀況,是使用最多的一個監控點。監控軟件通常使用該接口實時監測應用運行狀況,在系統出現故障時把報警信息推送給相關人員,如磁盤空間使用情況、數據庫和緩存等的一些健康指標。
默認情況下 health 端點是開放的,訪問 http://127.0.0.1:8080/actuator/health 即可看到應用運行狀態。

{"status":"UP"}

如果需要看到詳細信息,則需要做添加配置:

management.endpoint.health.show-details=always

訪問返回信息如下:

{"status":"UP","details":{"diskSpace":{"status":"UP","details":{"total":180002725888,"free":8687988736,"threshold":10485760}}}}

info

查看應用信息是否在 application.properties 中配置。如我們在項目中配置是:

info.app.name=Spring Boot Actuator Demo
info.app.version=v1.0.0
info.app.description=Spring Boot Actuator Demo

啟動項目,訪問 http://127.0.0.1:8080/actuator/info 返回信息如下:

{"app":{"name":"Spring Boot Actuator Demo","version":"v1.0.0","description":"Spring Boot Actuator Demo"}}

env

通過 env 可以獲取到所有關於當前 Spring Boot 應用程序的運行環境信息,如:操作系統信息(systemProperties)、環境變量信息、JDK 版本及 ClassPath 信息、當前啟用的配置文件(activeProfiles)、propertySources、應用程序配置信息(applicationConfig)等。

可以通過 http://127.0.0.1:8080/actuator/env/{name} ,name表示想要查看的信息,可以獨立显示。

beans

訪問 http://127.0.0.1:8080/actuator/beans 返回部分信息如下:

{
    "contexts": {
        "Spring Boot Actuator Demo": {
            "beans": {
                "endpointCachingOperationInvokerAdvisor": {
                    "aliases": [
                    ],
                    "scope": "singleton",
                    "type": "org.springframework.boot.actuate.endpoint.invoker.cache.CachingOperationInvokerAdvisor",
                    "resource": "class path resource [org/springframework/boot/actuate/autoconfigure/endpoint/EndpointAutoConfiguration.class]",
                    "dependencies": [
                        "environment"
                    ]
                },
                "defaultServletHandlerMapping": {
                    "aliases": [
                    ],
                    "scope": "singleton",
                    "type": "org.springframework.web.servlet.HandlerMapping",
                    "resource": "class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]",
                    "dependencies": [
                    ]
                },
                ...
            }
        }
    }
}

從返回的信息中我們可以看出主要展示了 bean 的別名、類型、是否單例、類的地址、依賴等信息。

conditions

通過 conditions 可以在應用運行時查看代碼了某個配置在什麼條件下生效,或者某個自動配置為什麼沒有生效。

訪問 http://127.0.0.1:8080/actuator/conditions 返回部分信息如下:

{
    "contexts": {
        "Spring Boot Actuator Demo": {
            "positiveMatches": {
                "SpringBootAdminClientAutoConfiguration": [
                    {
                        "condition": "OnWebApplicationCondition",
                        "message": "@ConditionalOnWebApplication (required) found 'session' scope"
                    },
                    {
                        "condition": "SpringBootAdminClientEnabledCondition",
                        "message": "matched"
                    }
                ],
                "SpringBootAdminClientAutoConfiguration#metadataContributor": [
                    {
                        "condition": "OnBeanCondition",
                        "message": "@ConditionalOnMissingBean (types: de.codecentric.boot.admin.client.registration.metadata.CompositeMetadataContributor; SearchStrategy: all) did not find any beans"
                    }
                ],
                ...
            }
        }
    }
}

loggers

獲取系統的日誌信息。

訪問 http://127.0.0.1:8080/actuator/loggers 返回部分信息如下:

{
    "levels": [
        "OFF",
        "ERROR",
        "WARN",
        "INFO",
        "DEBUG",
        "TRACE"
    ],
    "loggers": {
        "ROOT": {
            "configuredLevel": "INFO",
            "effectiveLevel": "INFO"
        },
        "cn": {
            "configuredLevel": null,
            "effectiveLevel": "INFO"
        },
        "cn.zwqh": {
            "configuredLevel": null,
            "effectiveLevel": "INFO"
        },
        "cn.zwqh.springboot": {
            "configuredLevel": null,
            "effectiveLevel": "INFO"
        },
        ...
    }
}

mappings

查看所有 URL 映射,即所有 @RequestMapping 路徑的整理列表。

訪問 http://127.0.0.1:8080/actuator/mappings 返回部分信息如下:

{
    "contexts": {
        "Spring Boot Actuator Demo": {
            "mappings": {
                "dispatcherServlets": {
                    "dispatcherServlet": [
                        {
                            "handler": "ResourceHttpRequestHandler [class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/], class path resource []]",
                            "predicate": "/**/favicon.ico",
                            "details": null
                        },
                        ...
                    ]
                }
            }
        }
    }
}

heapdump

訪問:http://127.0.0.1:8080/actuator/heapdump會自動生成一個 GZip 壓縮的 Jvm 的堆文件 heapdump,我們可以使用 JDK 自帶的 Jvm 監控工具 VisualVM 打開此文件查看。如圖:

VisualVM下載:https://visualvm.github.io/download.html

threaddump

獲取系統線程的轉儲信息,主要展示了線程名、線程ID、線程的狀態、是否等待鎖資源等信息。在工作中,我們可以通過查看線程的情況來排查相關問題。

訪問 http://127.0.0.1:8080/actuator/threaddump 返回部分信息如下:

{
    "threads": [
        {
            "threadName": "DestroyJavaVM",
            "threadId": 40,
            "blockedTime": -1,
            "blockedCount": 0,
            "waitedTime": -1,
            "waitedCount": 0,
            "lockName": null,
            "lockOwnerId": -1,
            "lockOwnerName": null,
            "inNative": false,
            "suspended": false,
            "threadState": "RUNNABLE",
            "stackTrace": [
            ],
            "lockedMonitors": [
            ],
            "lockedSynchronizers": [
            ],
            "lockInfo": null
        },
        ...
    ]
}

shutdown

開啟可以接口關閉 Spring Boot 應用,要使用這個功能需要做如下配置:

management.endpoint.shutdown.enabled=true

可以通過 post(僅支持 post) 請求訪問 http://127.0.0.1:8080/actuator/shutdown 關閉應用。

metrics

訪問 http://127.0.0.1:8080/actuator/metrics 可以獲取系統度量指標信息項如下:

{
    "names": [
        "jvm.memory.max",
        "jvm.threads.states",
        "jvm.gc.pause",
        "http.server.requests",
        "process.files.max",
        "jvm.gc.memory.promoted",
        "system.load.average.1m",
        "jvm.memory.used",
        "jvm.gc.max.data.size",
        "jvm.memory.committed",
        "system.cpu.count",
        "logback.events",
        "tomcat.global.sent",
        "jvm.buffer.memory.used",
        "tomcat.sessions.created",
        "jvm.threads.daemon",
        "system.cpu.usage",
        "jvm.gc.memory.allocated",
        "tomcat.global.request.max",
        "tomcat.global.request",
        "tomcat.sessions.expired",
        "jvm.threads.live",
        "jvm.threads.peak",
        "tomcat.global.received",
        "process.uptime",
        "tomcat.sessions.rejected",
        "process.cpu.usage",
        "tomcat.threads.config.max",
        "jvm.classes.loaded",
        "jvm.classes.unloaded",
        "tomcat.global.error",
        "tomcat.sessions.active.current",
        "tomcat.sessions.alive.max",
        "jvm.gc.live.data.size",
        "tomcat.threads.current",
        "process.files.open",
        "jvm.buffer.count",
        "jvm.buffer.total.capacity",
        "tomcat.sessions.active.max",
        "tomcat.threads.busy",
        "process.start.time"
    ]
}

對應訪問 names 中的指標,可以查看具體的指標信息。如訪問 http://127.0.0.1:8080/actuator/metrics/jvm.memory.used 返回信息如下:

{
    "name": "jvm.memory.used",
    "description": "The amount of used memory",
    "baseUnit": "bytes",
    "measurements": [
        {
            "statistic": "VALUE",
            "value": 1.16828136E8
        }
    ],
    "availableTags": [
        {
            "tag": "area",
            "values": [
                "heap",
                "nonheap"
            ]
        },
        {
            "tag": "id",
            "values": [
                "Compressed Class Space",
                "PS Survivor Space",
                "PS Old Gen",
                "Metaspace",
                "PS Eden Space",
                "Code Cache"
            ]
        }
    ]
}

示例代碼

參考文檔

https://docs.spring.io/spring-boot/docs/2.2.1.RELEASE/reference/html/production-ready-features.html

非特殊說明,本文版權歸 所有,轉載請註明出處.

原文標題:Spring Boot 2.X(十六):應用監控之 Spring Boot Actuator 使用及配置

原文地址:

如果文章對您有幫助,請掃碼關注下我的公眾號,文章持續更新中…

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

go中的數據結構-切片slice

1. 部分基本類型

  go中的類型與c的相似,常用類型有一個特例:byte類型,即字節類型,長度為,默認值是0;

1 bytes = [5]btye{'h', 'e', 'l', 'l', 'o'}

  變量bytes的類型是[5]byte,一個由5個字節組成的數組。它的內存表示就是連起來的5個字節,就像C的數組。

1.1 字符串

  字符串在Go語言內存模型中用一個2字長(64位,32位內存布局方式下)的數據結構表示。它包含一個指向字符串數據存儲地方的指針,和一個字符串長度數據如下圖:

  s是一個string類型的字符串,因為string類型不可變,對於多字符串共享同一個存儲數據是安全的。切分操作str[i:j]會得到一個新的2字長結構t,一個可能不同的但仍指向同一個字節序列(即上文說的存儲數據)的指針和長度數據。所以字符串切分不涉及內存分配或複製操作,其效率等同於傳遞下標。

1.2 數組

  數組是內置(build-in)類型,是一組同類型數據的集合,它是值類型,通過從0開始的下標索引訪問元素值。數組類型定義了長度和元素類型。如, [4]int 類型表示一個四個整數的數組,其長度是固定的,長度是數組類型的一部分( [4]int 和 [5]int 是完全不同的類型)。 數組可以以常規的索引方式訪問,表達式 s[n] 訪問數組的第 n 個元素。數組不需要顯式的初始化;數組的零值是可以直接使用的,數組元素會自動初始化為其對應類型的零值。

1  var a [4]int
2  a[0] = 1
3  i := a[0]
4  // i == 1
5  // a[2] == 0, int 類型的零值
6 [5] int {1,2}  //長度為5的數組,其元素值依次為:1,2,0,0,0 。在初始化時沒有指定初值的元素將會賦值為其元素類型int的默認值0,string的默認值是"" 
7 [...] int {1,2,3,4,5}  //長度為5的數組,其長度是根據初始化時指定的元素個數決定的 
8 [5] int { 2:1,3:2,4:3}  //長度為5的數組,key:value,其元素值依次為:0,0,1,2,3。在初始化時指定了2,3,4索引中對應的值:1,2,3 
9 [...] int {2:1,4:3}  //長度為5的數組,起元素值依次為:0,0,1,0,3。由於指定了最大索引4對應的值3,根據初始化的元素個數確定其長度為5賦值與使用

  Go的數組是值語義。一個數組變量表示整個數組,它不是指向第一個元素的指針(不像 C 語言的數組)。 當一個數組變量被賦值或者被傳遞的時候,實際上會複製整個數組。 (為了避免複製數組,你可以傳遞一個指向數組的指針,但是數組指針並不是數組。) 可以將數組看作一個特殊的struct,結構的字段名對應數組的索引,同時成員的數目固定。

b := [2]string{"Penn", "Teller"}
b := [...]string{"Penn", "Teller"}

  這兩種寫法, b 都是對應 [2]string 類型。

2. 切片slice 

2.1 結構

  切片類型的寫法是[]T ,T是切片元素的類型。和數組不同的是,切片類型並沒有給定固定的長度。切片的字面值和數組字面值很像,不過切片沒有指定元素個數,切片可以通過數組來初始化,也可以通過內置函數make()初始化。

letters := []string{"a", "b", "c", "d"}   //直接初始化切片,[]表示是切片類型,{"a", "b", "c", "d"},初始化值依次是a,b,c,d.其cap=len=4
s := letters [:]  //初始化切片s,是數組letters的引用(a slice referencing the storage of x)
func make([]T, len, cap) []T    //使用內置函數 make 創建
s :=make([]int,len,cap)    //通過內置函數make()初始化切片s,[]int 標識為其元素類型為int的切片
s := arr[startIndex:endIndex]   //將arr中從下標startIndex到endIndex-1 下的元素創建為一個新的切片
s := arr[startIndex:]   //缺省endIndex時將表示一直到arr的最後一個元素
s := arr[:endIndex]   //缺省startIndex時將表示從arr的第一個元素開始
s1 := s[startIndex:endIndex]   //通過切片s初始化切片s1

  slice可以從一個數組或一個已經存在的slice中再次聲明。slice通過array[i:j]來獲取,其中i是數組的開始位置,j是結束位置,但不包含array[j],它的長度是j-i

1 var ar = [10]byte{'a','b','c','d','e','f','g','h','i','j'}   // 聲明一個含有10個元素元素類型為byte的數組
2 var a, b []byte  // 聲明兩個含有byte的slice
3 a = ar[2:5]  //現在a含有的元素: ar[2]、ar[3]和ar[4]
4  
5 // b是數組ar的另一個slice
6 b = ar[3:5]// b的元素是:ar[3]和ar[4]

  一個slice是一個數組某個部分的引用。在內存中它是一個包含三個域的結構體:指向slice中第一個元素的指針ptr,slice的長度數據len,以及slice的容量cap。長度是下標操作的上界,如x[i]中i必須小於長度。容量是分割操作的上界,如x[i:j]中j不能大於容量。slice在Go的運行時庫中就是一個C語言動態數組的實現,在$GOROOT/src/pkg/runtime/runtime.h中定義:

struct    Slice
{    // must not move anything
    byte*    array;        // actual data
    uintgo    len;        // number of elements
    uintgo    cap;        // allocated number of elements
};

  數組的slice會創建一份新的數據結構,包含一個指針,一個指針和一個容量數據。如同分割一個字符串,分割數組也不涉及複製操作,它只是新建了一個結構放置三個數據。如下圖:

  示例中,對[]int{2,3,5,7,11}求值操作會創建一個包含五個值的數組,並設置x的屬性來描述這個數組。分割表達式x[1:3]不重新分配內存數據,只寫了一個新的slice結構屬性來引用相同的存儲數據。上例中,長度為2–只有y[0]和y[1]是有效的索引,但是容量為4–y[0:4]是一個有效的分割表達式。

  因為slice分割操作不需要分配內存,也沒有通常被保存在堆中的slice頭部,這種表示方法使slice操作和在C中傳遞指針、長度對一樣廉價。

2.2 擴容

  其實slice在Go的運行時庫中就是一個C語言動態數組的實現,要增加切片的容量必須創建一個新的、更大容量的切片,然後將原有切片的內容複製到新的切片。在對slice進行append等操作時,可能會造成slice的自動擴容。其擴容時的大小增長規則是:

  • 如果新的大小是當前大小2倍以上,則大小增長為新大小
  • 否則循環以下操作:如果當前大小小於1024,按每次2倍增長,否則每次按當前大小1/4增長。直到增長的大小超過或等於新大小。

  下面的例子將切片 s 容量翻倍,先創建一個2倍 容量的新切片 t ,複製 s 的元素到 t ,然後將 t 賦值給 s : 

t := make([]byte, len(s), (cap(s)+1)*2) // +1 in case cap(s) == 0
for i := range s {
        t[i] = s[i]
}
s = t

  循環中複製的操作可以由 copy 內置函數替代,返回複製元素的數目。此外, copy 函數可以正確處理源和目的切片有重疊的情況。

一個常見的操作是將數據追加到切片的尾部。必要的話會增加切片的容量,最後返回更新的切片:

func AppendByte(slice []byte, data ...byte) []byte {
    m := len(slice)
    n := m + len(data)
    if n > cap(slice) { // if necessary, reallocate
        // allocate double what's needed, for future growth.
        newSlice := make([]byte, (n+1)*2)
        copy(newSlice, slice)
        slice = newSlice
    }
    slice = slice[0:n]
    copy(slice[m:n], data)
    return slice
}

  Go提供了一個內置函數 append,也實現了這樣的功能。

func append(s []T, x ...T) []T
//append 函數將 x 追加到切片 s 的末尾,並且在必要的時候增加容量。
a := make([]int, 1)
// a == []int{0}
a = append(a, 1, 2, 3)
// a == []int{0, 1, 2, 3}

  如果是要將一個切片追加到另一個切片尾部,需要使用 ... 語法將第2個參數展開為參數列表。

a := []string{"John", "Paul"}
b := []string{"George", "Ringo", "Pete"}
a = append(a, b...) // equivalent to "append(a, b[0], b[1], b[2])"
// a == []string{"John", "Paul", "George", "Ringo", "Pete"}

  由於切片的零值 nil 用起來就像一個長度為零的切片,我們可以聲明一個切片變量然後在循環 中向它追加數據:

// Filter returns a new slice holding only
// the elements of s that satisfy fn()
func Filter(s []int, fn func(int) bool) []int {
    var p []int // == nil
    for _, v := range s {
        if fn(v) {
            p = append(p, v)
        }
    }
    return p
}

3. 使用切片需要注意的陷阱

  切片操作並不會複製底層的數組。整個數組將被保存在內存中,直到它不再被引用。 有時候可能會因為一個小的內存引用導致保存所有的數據。

  如下, FindDigits 函數加載整個文件到內存,然後搜索第一個連續的数字,最後結果以切片方式返回。

var digitRegexp = regexp.MustCompile("[0-9]+")

func FindDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return digitRegexp.Find(b)
}

  這段代碼的行為和描述類似,返回的 []byte 指向保存整個文件的數組。因為切片引用了原始的數組, 導致 GC 不能釋放數組的空間;只用到少數幾個字節卻導致整個文件的內容都一直保存在內存里。要修復整個問題,可以將需要的數據複製到一個新的切片中:

func CopyDigits(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = digitRegexp.Find(b)
    c := make([]byte, len(b))
    copy(c, b)
    return c
}

  使用 append 實現一個更簡潔的版本:

    8  func CopyDigitRegexp(filename string) []byte {
    7     b,_ := ioutil.ReadFile(filename)
    6     b = digitRefexp.Find(b)
    5     var c []intb
    4    // for _,v := range b{
    3         c =append(c, b)
    2     //}
    1     return c
    0  }

4. make和new

Go有兩個數據結構創建函數:make和new,也是兩種不同的內存分配機制。

make和new的基本的區別是new(T)返回一個*T,返回的是一個指針,指向分配的內存地址,該指針可以被隱式地消除引用)。而make(T, args)返回一個普通的T。通常情況下,T內部有一些隱式的指針。所以new返回一個指向已清零內存的指針,而make返回一個T類型的結構。更詳細的區別在後面內存分配的學習里研究。

5. 數組和切片的區別

  • 數組長度不能改變,初始化后長度就是固定的;切片的長度是不固定的,可以追加元素,在追加時可能使切片的容量增大。
  • 結構不同,數組是一串固定數據,切片描述的是截取數組的一部分數據,從概念上說是一個結構體。
  • 初始化方式不同,如上。另外在聲明時的時候:聲明數組時,方括號內寫明了數組的長度或使用...自動計算長度,而聲明slice時,方括號內沒有任何字符。
  • unsafe.sizeof的取值不同,unsafe.sizeof(slice)返回的大小是切片的描述符,不管slice里的元素有多少,返回的數據都是24。unsafe.sizeof(arr)的值是在隨着arr的元素的個數的增加而增加,是數組所存儲的數據內存的大小。

  unsafe.sizeof總是在編譯期就進行求值,而不是在運行時,這意味着,sizeof的返回值可以賦值給常量。  在編譯期求值,還意味着可以獲得數組所佔的內存大小,因為數組總是在編譯期就指明自己的容量,並且在以後都是不可變的。

  unsafe.sizeof(string)時大小始終是16,不論字符串的len有多大,sizeof始終返回16,這是因為字符串類型對應一個結構體,該結構體有兩個域,第一個域是指向該字符串的指針,第二個域是字符串的長度,每個域佔8個字節,但是並不包含指針指向的字符串的內容。

6. nil

  按照Go語言規範,任何類型在未初始化時都對應一個零值:布爾類型是false,整型是0,字符串是””,而指針,函數,interface,slice,channel和map的零值都是nil。

interface

  一個interface在沒有進行初始化時,對應的值是nil。也就是說var v interface{},此時v就是一個nil。在底層存儲上,它是一個空指針。與之不同的情況是,interface值為空。比如:

1 var v *T
2 var i interface{}
3 i = v

  此時i是一個interface,它的值是nil,但它自身不為nil。

string

  string的空值是””,它是不能跟nil比較的。即使是空的string,它的大小也是兩個機器字長的。slice也類似,它的空值並不是一個空指針,而是結構體中的指針域為空,空的slice的大小也是三個機器字長的。

channel

  channel跟string或slice有些不同,它在棧上只是一個指針,實際的數據都是由指針所指向的堆上面。

  跟channel相關的操作有:初始化/讀/寫/關閉。channel未初始化值就是nil,未初始化的channel是不能使用的。下面是一些操作規則:

  • 讀或者寫一個nil的channel的操作會永遠阻塞。
  • 讀一個關閉的channel會立刻返回一個channel元素類型的零值。
  • 寫一個關閉的channel會導致panic。

map

  map也是指針,實際數據在堆中,未初始化的值是nil。

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務