標籤: 收購3c

  • 附009.Kubernetes永久存儲之GlusterFS獨立部署

    附009.Kubernetes永久存儲之GlusterFS獨立部署

    一 前期準備

    1.1 基礎知識


    Heketi提供了一個RESTful管理界面,可以用來管理GlusterFS卷的生命周期。Heketi會動態在集群內選擇bricks構建所需的volumes,從而確保數據的副本會分散到集群不同的故障域內。同時Heketi還支持任意數量的ClusterFS集群。

    提示:本實驗基於glusterfs和Kubernetes分開部署,heketi管理glusterfs,Kubernetes使用heketi提供的API,從而實現glusterfs的永久存儲,,而非Kubernetes部署glusterfs。

    1.2 架構示意






    提示:本實驗Heketi僅管理單zone的glusterfs集群。

    1.3 相關規劃

































    主機 IP 磁盤 備註
    servera 172.24.8.41 sdb glusterfs節點
    serverb 172.24.8.42 sdb glusterfs節點
    serverc 172.24.8.43 sdb glusterfs節點
    heketi 172.24.8.44 Heketi主機










































    servera serverb serverc
    PV sdb1 sdb1 sdb1
    VG vg0 vg0 vg0
    LV datalv datalv datalv
    bricks目錄 /bricks/data /bricks/data /bricks/data

    1.4 其他準備


    所有節點NTP配置;

    所有節點添加相應主機名解析:

    172.24.8.41 servera

    172.24.8.42 serverb

    172.24.8.43 serverc

    172.24.8.44 heketi

    注意:若非必要,建議關閉防火牆和SELinux。

    二 規劃相應存儲卷

    2.1 劃分LVM

      1 [root@servera ~]# fdisk /dev/sdb				#創建lvm的sdb1,過程略
      2 [root@servera ~]# pvcreate /dev/sdb1			#使用/dev/vdb1創建PV
      3 [root@servera ~]# vgcreate vg0 /dev/sdb1			#創建vg
      4 [root@servera ~]# lvcreate -L 15G -T vg0/thinpool		#創建支持thin的lv池
      5 [root@servera ~]# lvcreate -V 10G -T vg0/thinpool -n datalv	#創建相應brick的lv
      6 [root@servera ~]# vgdisplay					#驗證確認vg信息
      7 [root@servera ~]# pvdisplay					#驗證確認pv信息
      8 [root@servera ~]# lvdisplay					#驗證確認lv信息



    提示:serverb、serverc類似操作,根據規劃需求創建完所有基於LVM的brick。

    三 安裝glusterfs

    3.1 安裝相應RPM源

      1 [root@servera ~]# yum -y install centos-release-gluster


    提示:serverb、serverc、client類似操作,安裝相應glusterfs源;

    安裝相應源之後,會在/etc/yum.repos.d/目錄多出文件CentOS-Storage-common.repo,內容如下:

      1 # CentOS-Storage.repo
      2 #
      3 # Please see http://wiki.centos.org/SpecialInterestGroup/Storage for more
      4 # information
      5 
      6 [centos-storage-debuginfo]
      7 name=CentOS-$releasever - Storage SIG - debuginfo
      8 baseurl=http://debuginfo.centos.org/$contentdir/$releasever/storage/$basearch/
      9 gpgcheck=1
     10 enabled=0
     11 gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Storage


    3.2 安裝glusterfs

      1 [root@servera ~]# yum -y install glusterfs-server


    提示:serverb、serverc類似操作,安裝glusterfs服務端。

    3.3 啟動glusterfs

      1 [root@servera ~]# systemctl start glusterd
      2 [root@servera ~]# systemctl enable glusterd



    提示:serverb、serverc類似操作,所有節點啟動glusterfs服務端;

    安裝完glusterfs之後建議exit退出終端重新登錄,從而可以補全glusterfs相關命令。

    3.4 添加信任池

      1 [root@servera ~]# gluster peer probe serverb
      2 peer probe: success.
      3 [root@servera ~]# gluster peer probe serverc
      4 peer probe: success.
      5 [root@servera ~]# gluster peer status		#查看信任池狀態
      6 [root@servera ~]# gluster pool list			#查看信任池列表




    提示:加信任池的操作,只需要在servera、serverb、serverc所有集群節點主機中的任意一台上面執行添加其他三個節點的操作即可。

    提示:若未關閉防火牆,在添加信任池之前必須放通防火牆相應規則,操作如下:

      1 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=glusterfs
      2 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=nfs
      3 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=rpc­bind
      4 [root@servera ~]# firewall­cmd ­­permanent ­­add­service=mountd
      5 [root@servera ~]# firewall­cmd ­­permanent ­­add­port=5666/tcp
      6 [root@servera ~]# firewall­cmd ­­reload


    四 部署Heketi

    4.1 安裝heketi服務

      1 [root@heketi ~]# yum -y install centos-release-gluster
      2 [root@heketi ~]# yum -y install heketi heketi-client


    4.2 配置heketi

      1 [root@heketi ~]# vi /etc/heketi/heketi.json
      2 {
      3   "_port_comment": "Heketi Server Port Number",
      4   "port": "8080",					#默認端口
      5 
      6   "_use_auth": "Enable JWT authorization. Please enable for deployment",
      7   "use_auth": true,					#基於安全考慮開啟認證
      8 
      9   "_jwt": "Private keys for access",
     10   "jwt": {
     11     "_admin": "Admin has access to all APIs",
     12     "admin": {
     13       "key": "admin123"					#管理員密碼
     14     },
     15     "_user": "User only has access to /volumes endpoint",
     16     "user": {
     17       "key": "xianghy"					#普通用戶
     18     }
     19   },
     20 
     21   "_glusterfs_comment": "GlusterFS Configuration",
     22   "glusterfs": {
     23     "_executor_comment": [
     24       "Execute plugin. Possible choices: mock, ssh",
     25       "mock: This setting is used for testing and development.",	#用於測試
     26       "      It will not send commands to any node.",
     27       "ssh:  This setting will notify Heketi to ssh to the nodes.",	#ssh方式
     28       "      It will need the values in sshexec to be configured.",
     29       "kubernetes: Communicate with GlusterFS containers over",		#在GlusterFS由kubernetes創建時採用
     30       "            Kubernetes exec api."
     31     ],
     32     "executor": "ssh",
     33 
     34     "_sshexec_comment": "SSH username and private key file information",
     35     "sshexec": {
     36       "keyfile": "/etc/heketi/heketi_key",
     37       "user": "root",
     38       "port": "22",
     39       "fstab": "/etc/fstab"
     40     },
     41 ……
     42 ……
     43     "loglevel" : "warning"
     44   }
     45 }


    4.3 配置免秘鑰

      1 [root@heketi ~]# ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N ""
      2 [root@heketi ~]# chown heketi:heketi /etc/heketi/heketi_key
      3 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@servera
      4 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverb
      5 [root@heketi ~]# ssh-copy-id -i /etc/heketi/heketi_key.pub root@serverc


    4.4 啟動heketi

      1 [root@heketi ~]# systemctl enable heketi.service
      2 [root@heketi ~]# systemctl start heketi.service
      3 [root@heketi ~]# systemctl status heketi.service
      4 [root@heketi ~]# curl http://localhost:8080/hello		#測試訪問
      5 Hello from Heketi


    4.5 配置Heketi拓撲


           拓撲信息用於讓Heketi確認可以使用的存儲節點、磁盤和集群,必須自行確定節點的故障域。故障域是賦予一組節點的整數值,這組節點共享相同的交換機、電源或其他任何會導致它們同時失效的組件。必須確認哪些節點構成一個集群,Heketi使用這些信息來確保跨故障域中創建副本,從而提供數據冗餘能力,Heketi支持多個Gluster存儲集群。

    配置Heketi拓撲注意以下幾點:

    • 可以通過topology.json文件定義組建的GlusterFS集群;
    • topology指定了層級關係:clusters –> nodes –> node/devices –> hostnames/zone;
    • node/hostnames字段的manage建議填寫主機ip,指管理通道,注意當heketi服務器不能通過hostname訪問GlusterFS節點時不能填寫hostname;
    • node/hostnames字段的storage建議填寫主機ip,指存儲數據通道,與manage可以不一樣,生產環境管理網絡和存儲網絡建議分離;
    • node/zone字段指定了node所處的故障域,heketi通過跨故障域創建副本,提高數據高可用性質,如可以通過rack的不同區分zone值,創建跨機架的故障域;
    • devices字段指定GlusterFS各節點的盤符(可以是多塊盤),必須是未創建文件系統的裸設備。

      1 [root@heketi ~]# vi /etc/heketi/topology.json
      2 {
      3     "clusters": [
      4         {
      5             "nodes": [
      6                 {
      7                     "node": {
      8                         "hostnames": {
      9                             "manage": [
     10                                 "172.24.8.41"
     11                             ],
     12                             "storage": [
     13                                 "172.24.8.41"
     14                             ]
     15                         },
     16                         "zone": 1
     17                     },
     18                     "devices": [
     19                         "/dev/mapper/vg0-datalv"
     20                     ]
     21                 },
     22                 {
     23                     "node": {
     24                         "hostnames": {
     25                             "manage": [
     26                                 "172.24.8.42"
     27                             ],
     28                             "storage": [
     29                                 "172.24.8.42"
     30                             ]
     31                         },
     32                         "zone": 1
     33                     },
     34                     "devices": [
     35                         "/dev/mapper/vg0-datalv"
     36                     ]
     37                 },
     38                 {
     39                     "node": {
     40                         "hostnames": {
     41                             "manage": [
     42                                 "172.24.8.43"
     43                             ],
     44                             "storage": [
     45                                 "172.24.8.43"
     46                             ]
     47                         },
     48                         "zone": 1
     49                     },
     50                     "devices": [
     51                         "/dev/mapper/vg0-datalv"
     52                     ]
     53                 }
     54             ]
     55         }
     56     ]
     57 }
     58 
     59 [root@heketi ~]# echo "export HEKETI_CLI_SERVER=http://heketi:8080" >> /etc/profile.d/heketi.sh
     60 [root@heketi ~]# echo "alias heketi-cli='heketi-cli --user admin --secret admin123'" >> .bashrc
     61 [root@heketi ~]# source /etc/profile.d/heketi.sh
     62 [root@heketi ~]# source .bashrc
     63 [root@heketi ~]# echo $HEKETI_CLI_SERVER
     64 http://heketi:8080
     65 [root@heketi ~]# heketi-cli --server $HEKETI_CLI_SERVER --user admin --secret admin123 topology load --json=/etc/heketi/topology.json




    4.6 集群管理

      1 [root@heketi ~]# heketi-cli cluster list					#集群列表
      2 [root@heketi ~]# heketi-cli cluster info aa83b0045fafa362bfc7a8bfee0c24ad	#集群詳細信息
      3 Cluster id: aa83b0045fafa362bfc7a8bfee0c24ad
      4 Nodes:
      5 189ee41572ebf0bf1e297de2302cfb39
      6 46429de5666fc4c6cc570da4b100465d
      7 be0209387384299db34aaf8377c3964c
      8 Volumes:
      9 
     10 Block: true
     11 
     12 File: true
     13 [root@heketi ~]# heketi-cli topology info aa83b0045fafa362bfc7a8bfee0c24ad	#查看拓撲信息




      1 [root@heketi ~]# heketi-cli node list						#卷信息
      2 Id:189ee41572ebf0bf1e297de2302cfb39     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      3 Id:46429de5666fc4c6cc570da4b100465d     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      4 Id:be0209387384299db34aaf8377c3964c     Cluster:aa83b0045fafa362bfc7a8bfee0c24ad
      5 [root@heketi ~]# heketi-cli node info 189ee41572ebf0bf1e297de2302cfb39		#節點信息
      6 [root@heketi ~]# heketi-cli volume create --size=2 --replica=2			#默認為3副本的replica模式



      1 [root@heketi ~]# heketi-cli volume list						#卷信息
      2 [root@heketi ~]# heketi-cli volume info 7da55685ebeeaaca60708cd797a5e391
      3 [root@servera ~]# gluster volume info						#通過glusterfs節點查看


    4.7 測試驗證

      1 [root@heketi ~]# yum -y install centos-release-gluster
      2 [root@heketi ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse
      3 [root@heketi ~]# mount -t glusterfs 172.24.8.41:vol_7da55685ebeeaaca60708cd797a5e391 /mnt



      1 [root@heketi ~]# umount /mnt
      2 [root@heketi ~]# heketi-cli volume delete 7da55685ebeeaaca60708cd797a5e391	#驗證完畢刪除



    參考:https://www.jianshu.com/p/1069ddaaea78

    https://www.cnblogs.com/panwenbin-logs/p/10231859.html

    五 Kubernetes動態掛載glusterfs

    5.1 StorageClass動態存儲


    kubernetes共享存儲provider模式:

    靜態模式(Static):集群管理員手工創建PV,在定義PV時設置後端存儲的特性;

    動態模式(Dynamic):集群管理員不需要手工創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種”類型(Class)”;此時要求PVC對存儲的類型進行說明,系統將自動完成PV的創建及與PVC的綁定;PVC可以聲明Class為””,說明PVC禁止使用動態模式。

    基於StorageClass的動態存儲供應整體過程如下圖所示:


    1. 集群管理員預先創建存儲類(StorageClass);
    2. 用戶創建使用存儲類的持久化存儲聲明(PVC:PersistentVolumeClaim);
    3. 存儲持久化聲明通知系統,它需要一個持久化存儲(PV: PersistentVolume);
    4. 系統讀取存儲類的信息;
    5. 系統基於存儲類的信息,在後台自動創建PVC需要的PV;
    6. 用戶創建一個使用PVC的Pod;
    7. Pod中的應用通過PVC進行數據的持久化;
    8. 而PVC使用PV進行數據的最終持久化處理。


    提示:關於Kubernetes的部署參考《附003.Kubeadm部署Kubernetes》。

    5.2 定義StorageClass


    關鍵字說明:

    • provisioner:表示存儲分配器,需要根據後端存儲的不同而變更;
    • reclaimPolicy: 默認即”Delete”,刪除pvc后,相應的pv及後端的volume,brick(lvm)等一起刪除;設置為”Retain”時則保留數據,若需刪除則需要手工處理;
    • resturl:heketi API服務提供的url;
    • restauthenabled:可選參數,默認值為”false”,heketi服務開啟認證時必須設置為”true”;
    • restuser:可選參數,開啟認證時設置相應用戶名;
    • secretNamespace:可選參數,開啟認證時可以設置為使用持久化存儲的namespace;
    • secretName:可選參數,開啟認證時,需要將heketi服務的認證密碼保存在secret資源中;
    • clusterid:可選參數,指定集群id,也可以是1個clusterid列表,格式為”id1,id2”;
    • volumetype:可選參數,設置卷類型及其參數,如果未分配卷類型,則有分配器決定卷類型;如”volumetype: replicate:3”表示3副本的replicate卷,”volumetype: disperse:4:2”表示disperse卷,其中‘4’是數據,’2’是冗餘校驗,”volumetype: none”表示distribute卷


    提示:關於glusterfs各種不同類型的卷見《004.RHGS-創建volume》。

      1 [root@k8smaster01 ~]# kubectl create ns heketi		#創建命名空間
      2 [root@k8smaster01 ~]# echo -n "admin123" | base64		#將密碼轉換為64位編碼
      3 YWRtaW4xMjM=
      4 [root@k8smaster01 ~]# mkdir -p heketi
      5 [root@k8smaster01 ~]# cd heketi/
      6 [root@k8smaster01 ~]# vi heketi-secret.yaml			#創建用於保存密碼的secret
      7 apiVersion: v1
      8 kind: Secret
      9 metadata:
     10   name: heketi-secret
     11   namespace: heketi
     12 data:
     13   # base64 encoded password. E.g.: echo -n "mypassword" | base64
     14   key: YWRtaW4xMjM=
     15 type: kubernetes.io/glusterfs
     16 [root@k8smaster01 heketi]# kubectl create -f heketi-secret.yaml	#創建heketi
     17 [root@k8smaster01 heketi]# kubectl get secrets -n heketi
     18 NAME                  TYPE                                  DATA   AGE
     19 default-token-5sn5d   kubernetes.io/service-account-token   3      43s
     20 heketi-secret         kubernetes.io/glusterfs               1      5s
     21 [root@kubenode1 heketi]# vim gluster-heketi-storageclass.yaml	#正式創建StorageClass
     22 apiVersion: storage.k8s.io/v1
     23 kind: StorageClass
     24 metadata:
     25   name: gluster-heketi-storageclass
     26 parameters:
     27   resturl: "http://172.24.8.44:8080"
     28   clusterid: "aa83b0045fafa362bfc7a8bfee0c24ad"
     29   restauthenabled: "true"					#若heketi開啟認證此處也必須開啟auth認證
     30   restuser: "admin"
     31   secretName: "heketi-secret"				#name/namespace與secret資源中定義一致
     32   secretNamespace: "heketi"
     33   volumetype: "replicate:3"
     34 provisioner: kubernetes.io/glusterfs
     35 reclaimPolicy: Delete
     36 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-storageclass.yaml



    注意:storageclass資源創建后不可變更,如修改只能刪除后重建。

      1 [root@k8smaster01 heketi]# kubectl get storageclasses		#查看確認
      2 NAME                          PROVISIONER               AGE
      3 gluster-heketi-storageclass   kubernetes.io/glusterfs   85s
      4 [root@k8smaster01 heketi]# kubectl describe storageclasses gluster-heketi-storageclass




    5.3 定義PVC

      1 [root@k8smaster01 heketi]# cat gluster-heketi-pvc.yaml
      2 apiVersion: v1
      3 metadata:
      4   name: gluster-heketi-pvc
      5   annotations:
      6     volume.beta.kubernetes.io/storage-class: gluster-heketi-storageclass
      7 spec:
      8   accessModes:
      9   - ReadWriteOnce
     10   resources:
     11     requests:
     12       storage: 1Gi



    注意:accessModes可有如下簡寫:

    • ReadWriteOnce:簡寫RWO,讀寫權限,且只能被單個node掛載;
    • ReadOnlyMany:簡寫ROX,只讀權限,允許被多個node掛載;
    • ReadWriteMany:簡寫RWX,讀寫權限,允許被多個node掛載。

      1 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pvc.yaml
      2 [root@k8smaster01 heketi]# kubectl get pvc
      3 [root@k8smaster01 heketi]# kubectl describe pvc gluster-heketi-pvc
      4 [root@k8smaster01 heketi]# kubectl get pv
      5 [root@k8smaster01 heketi]# kubectl describe pv pvc-5f7420ef-082d-11ea-badf-000c29fa7a79




      1 [root@k8smaster01 heketi]# kubectl describe endpoints glusterfs-dynamic-5f7420ef-082d-11ea-badf-000c29fa7a79




    提示:由上可知:PVC狀態為Bound,Capacity為1G。查看PV詳細信息,除容量,引用storageclass信息,狀態,回收策略等外,同時可知GlusterFS的Endpoint與path。EndpointsName為固定格式:glusterfs-dynamic-PV_NAME,且endpoints資源中指定了掛載存儲時的具體地址。

    5.4 確認查看


    通過5.3所創建的信息:

    • volume與brick已經創建;
    • 主掛載點(通信)在172.24.8.41節點,其餘兩個節點備選;
    • 三副本的情況下,所有節點都會創建brick。

      1 [root@heketi ~]# heketi-cli topology info			#heketi主機查看
      2 [root@serverb ~]# lsblk						#glusterfs節點查看
      3 [root@serverb ~]# df -hT					#glusterfs節點查看
      4 [root@servera ~]# gluster volume list				#glusterfs節點查看
      5 [root@servera ~]# gluster volume info vol_e4c948687239df9833748d081ddb6fd5




    5.5 Pod掛載測試

      1 [root@xxx ~]# yum -y install centos-release-gluster
      2 [root@xxx ~]# yum -y install glusterfs-fuse					#安裝glusterfs-fuse



    提示:所有需要使用glusterfs volume的Kubernetes節點都必須安裝glusterfs-fuse以便於正常掛載,同時版本需要和glusterfs節點一致。

      1 [root@k8smaster01 heketi]# vi gluster-heketi-pod.yaml
      2 kind: Pod
      3 apiVersion: v1
      4 metadata:
      5   name: gluster-heketi-pod
      6 spec:
      7   containers:
      8   - name: gluster-heketi-container
      9     image: busybox
     10     command:
     11     - sleep
     12     - "3600"
     13     volumeMounts:
     14     - name: gluster-heketi-volume			#必須和volumes中name一致
     15       mountPath: "/pv-data"
     16       readOnly: false
     17   volumes:
     18   - name: gluster-heketi-volume
     19     persistentVolumeClaim:
     20       claimName: gluster-heketi-pvc			#必須和5.3創建的PVC中的name一致
     21 [root@k8smaster01 heketi]# kubectl create -f gluster-heketi-pod.yaml -n heketi		#創建Pod


    5.6 確認驗證

      1 [root@k8smaster01 heketi]# kubectl get pod -n heketi | grep gluster
      2 gluster-heketi-pod          1/1     Running   0          2m43s
      3 [root@k8smaster01 heketi]# kubectl exec -it gluster-heketi-pod /bin/sh		#進入Pod寫入測試文件
      4 / # cd /pv-data/
      5 /pv-data # echo "This is a file!" >> a.txt
      6 /pv-data # echo "This is b file!" >> b.txt
      7 /pv-data # ls
      8 a.txt  b.txt
      9 [root@servera ~]# df -hT					#在glusterfs節點查看Kubernetes節點的測試文件
     10 [root@servera ~]# cd /var/lib/heketi/mounts/vg_47c90d90e03de79696f90bd94cfccdde/brick_721243c3e0cf8a2372f05d5085a4338c/brick/
     11 [root@servera brick]# ls
     12 [root@servera brick]# cat a.txt
     13 [root@servera brick]# cat b.txt




    5.7 刪除資源

      1 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pod.yaml
      2 [root@k8smaster01 heketi]# kubectl delete -f gluster-heketi-pvc.yaml
      3 [root@k8smaster01 heketi]# kubectl get pvc
      4 [root@k8smaster01 heketi]# kubectl get pv
      5 [root@servera ~]# gluster volume list
      6 No volumes present in cluster



    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 微服務框架 – Jimu(積木) 升級 1.0.0 支持 .Net Core 3.0

    微服務框架 – Jimu(積木) 升級 1.0.0 支持 .Net Core 3.0

    如果不知道 Jimu(積木) 是啥,請移步
    這次升級除了支持 .Net Core 3.0 還新增部分功能,如 REST, 鏈路跟蹤等,以下為詳細;

    一、功能列表

    功能 說明 Jimu 1.0.0 Jimu 0.6.0
    平台 .Net Core 2.1
    .Net Core 3.0
    服務註冊與發現 consul
    網關 Asp.Net Core Web
    RPC DotNetty
    鑒權 JWT
    負載均衡 輪訓
    容錯策略 重試
    容器 docker
    路由配置 Attribute註解
    日誌記錄 log4net
    nlog
    文檔 swagger
    鏈路跟蹤 skywalking
    REST Attribute註解
    健康監測 心跳
    文件上存下載 多文件上存,單文件下載
    跳轉 在服務端跳轉到指定url
    ORM Dapper
    DDD MiniDDD

    二、建議用積木結合 docker 搭建分佈式架構

    三、swagger

    四、skywalking

    拓撲圖: user -> jimu_apigateway -> jimu_order -> jimu_user

    Trace 跟蹤

    五、網關

    服務器

    微服務

    微服務詳細

    六、源碼

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 多線程編程(3)——synchronized原理以及使用

    多線程編程(3)——synchronized原理以及使用

    一、對象頭

      通常在java中一個對象主要包含三部分:

    • 對象頭 主要包含GC的狀態、、類型、類的模板信息(地址)、synchronization狀態等,在後面介紹。

    • 實例數據:程序代碼中定義的各種類型的字段內容。

    • 對齊數據:對象的大小必須是 8 字節的整數倍,此項根據情況而定,若對象頭和實例數據大小正好是8的倍數,則不需要對齊數據,否則大小就是8的差數。

    先看下面的實例、程序的輸出以及解釋。

    /*需提前引入jar包
    <!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core 解析java對象布局 -->
            <dependency>
                <groupId>org.openjdk.jol</groupId>
                <artifactId>jol-core</artifactId>
                <version>0.9</version>
            </dependency>
    ​
    */
    //Java對象以8個字節對其,不夠則使用對其數據
    public class Student {
        private int id;       // 4字節
        private boolean sex;  // 1字節
        public Student(int id, boolean sex){
            this.id = id;
            this.sex = sex;
        }
    }
    public class Test01 {
        public static void main(String[] args) {
            Student stu = new Student(6, true);
            //計算對象hash,底層是C++實現,不需要java去獲取,如果此處不調用,則後面的hash值不會去計算
            System.out.println("hashcode: " + stu.hashCode());  
            System.out.println(ClassLayout.parseInstance(stu).toPrintable());
        }
    }
    /* output
    hashcode: 523429237
    com.thread.synchronizeDemo.Student object internals:
    OFFSET SIZE TYPE DESCRIPTION        VALUE
     0     4    (object header)    01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
    4      4     (object header)    1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
    8      4     (object header)    43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
    12     4       int Student.id                                6
    16     1   boolean Student.sex                               true
    17     7           (loss due to the next object alignment)
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
    ​
    備註:上述代碼在64位的機器上運行,此時
    對象頭占  (4+4+4)*8 = 96 位(bit)
    實例數據  (4+1)*8 = 40 位(bit)
    對齊數據  7*8 = 56 位(bit) 因為Java對象以8個字節對其的方式,需補7byte去對齊
    */

       下面主要陳述對對象頭的解釋,內容從hotspot官網摘抄下來的信息:

    object header

      Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.

    mark word

      The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

    klass pointer

      The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original objec

      由此可知,對象頭主要包含GC的狀態(用4位表示——表示範圍0-15,用來記錄GC年齡,這也就是為什麼對象在survivor中從from區到to區來迴轉換15次後轉入到老年代tenured區)、類型、類的模板信息(地址)、synchronization 狀態等,由兩個字組成mark word和klass pointer(類元素據信息地址,具體數據通常在堆的方法區中,即8字節,但有時候會有一些優化設置,會開啟指針壓縮,將代表klass pointer的8字節變成4字節大小,這也是為什麼在上述代碼中對象頭大小是(8+4)byte,而不是16byte。)。本節最主要介紹對象頭的mark word這部分。關於對象頭中每部分bit所代表的意義可以查看hotspot源碼中代碼的注,這段註釋是從openjdk中拷貝的。

    JVM和hotspot、openjdk的區別

    JVM是一種產品的規範定義,hotspot(Oracle公司)是對該規範實現的產品,還有遵循這些規範的其他產品,比如J9(IBM開發的一個高度模塊化的JVM)、Zing VM等。

    openjdk是一個hotspot項目的大部分源代碼(可以通過編譯后變成.exe文件),hotspot小部分代碼Oracle並未公布

    // openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\oops\markOop.hpp
    /*
    Bit-format of an object header (most significant first, big endian layout below):
    ​
    32 bits:
    --------
            hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
            JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
            size:32 ------------------------------------------>| (CMS free block)
            PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
    ​
    64 bits:
    --------
    unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
    JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
    PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
    size:64 ----------------------------------------------------->| (CMS free block)
    ​
    */

    可以看到在32位機器和64位機器中,對象的布局的差異還是很大的,本文主要 敘述64位機器下的布局,其實兩者無非是位數不同而已,大同小異。在64位機器用64位(8byte)表示Mark Word,首先前25位(0-25)是未被使用,接下來31位表示hash值,然後是對象分代年齡大小,最後Synchronized的鎖信息,分為兩部分,共3bit,如下錶,鎖的嚴格性依次是鎖、偏向鎖、輕量級鎖、重量級鎖。

     

    關於鎖的一些解釋

    無鎖

      無鎖沒有對資源進行鎖定,所有的線程都能訪問並修改同一個資源,但同時只有一個線程能修改成功。

    偏向鎖

      引入偏向鎖是為了在無多線程競爭的情況下,一段同步代碼一直被一個線程所訪問因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令,當由另外的線程所訪問,偏向鎖就會升級為輕量級鎖。

    輕量級鎖 

      當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

    重量級鎖

      依賴於操作系統Mutex Lock所實現的鎖,JDK中對Synchronized做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6以後,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”。  

    GC

      這並不是鎖的狀態,而是GC標誌,等待GC回收。

    現在開始從程序層面分析前面程序的對象頭的布局信息,在此之前需要知道的是,在windows中對於數據的存儲採用的是小端存儲,所以要反過來讀

    大端模式——是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。

    小端模式——是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。 一般在網絡中用的大端;本地用的小端;

    運行程序如下,可以看到對應的hashcode值被打印出來:

    public static void main(String[] args) {
         Student stu = new Student(6, true);
        //Integer.toHexString()此方法返回的字符串表示的無符號整數參數所表示的值以十六進制
         System.out.println("hashcode: " + Integer.toHexString(stu.hashCode()));
         System.out.println(ClassLayout.parseInstance(stu).toPrintable());
    }
    /*
    hashcode: 1f32e575
    com.thread.synchronizeDemo.Student object internals:
     OFFSET  SIZE      TYPE DESCRIPTION                               VALUE
          0     4           (object header)                           01 75 e5 32 (00000001 01110101 11100101 00110010) (853898497)
          4     4           (object header)                           1f 00 00 00 (00011111 00000000 00000000 00000000) (31)
          8     4           (object header)                           43 c1 00 20 (01000011 11000001 00000000 00100000) (536920387)
         12     4       int Student.id                                6
         16     1   boolean Student.sex                               true
         17     7           (loss due to the next object alignment)
    Instance size: 24 bytes
    Space losses: 0 bytes internal + 7 bytes external = 7 bytes total

    //前8個字節反過來看,可以看出對象頭的hash是1f32e575,同時是無鎖的狀態00000001
    */

     二、Monitor

           可以把它理解為一個同步工具(數據結構),也可以描述為一種同步機制,通常被描述為一個對象。每個對象都存在着一個 monitor 與之關聯,對象與其 monitor 之間的關係有存在多種實現方式,如monitor可以與對象一起創建銷毀或當線程試圖獲取對象鎖時自動生成,但當一個 monitor 被某個線程持有后,它便處於鎖定狀態(每一個線程都有一個可用 monitor record 列表)[具體可以看參考資料5]。需要注意的是這種監視器鎖是發生在對象的內部鎖已經變成重量級鎖的時候。

    /*  openjdk-8-src-b132-03_mar_2014\openjdk\hotspot\src\share\vm\runtime\ObjectMonitor.hpp
    // initialize the monitor, exception the semaphore, all other fields // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; //記錄個數 _waiters = 0, _recursions = 0; _object = NULL; _owner = NULL; _WaitSet = NULL; //處於wait狀態的線程,會被加入到_WaitSet _WaitSetLock = 0 ; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //處於等待鎖block狀態的線程,會被加入到該列表 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; } */

      Monitor的實現主要藉助三個結構去完成多線程的併發操作——_owner、_WaitSet 、_EntryList。當多個線程同時訪問由synchronized修飾的對象、類或一段同步代碼時,首先會進入_EntryList 集合,如果某個線程取得了_owner的所有權,該線程就可以去執行,如果該線程調用了wait()方法,就會放棄_owner的所有權,進入等待狀態,等下一次喚醒。如下圖(圖片摘自參考資料5)。

     三、synchronized的用法

         synchronized修飾方法和修飾一個代碼塊類似,只是作用範圍不一樣,修飾代碼塊是大括號括起來的範圍,而修飾方法範圍是整個函數。其中synchronized(this) 與synchronized(class) 之間的區別有以下五點要注意:

        1、對於靜態方法,由於此時對象還未生成,所以只能採用類鎖;

        2、只要採用類鎖,就會攔截所有線程,只能讓一個線程訪問。

        3、對於對象鎖(this),如果是同一個實例,就會按順序訪問,但是如果是不同實例,就可以同時訪問。

       4、如果對象鎖跟訪問的對象沒有關係,那麼就會都同時訪問。

       5、當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

    當然,Synchronized也可修飾一個靜態方法,而靜態方法是屬於類的而不屬於對象的,所以synchronized修飾的靜態方法鎖定的是這個類的所有對象。關於如下synchronized的用法,我們經常會碰到的案例:

    public class Thread5 implements Runnable {
        private static int count = 0;
        public synchronized static void add() {
            count++;
        }
        @Override
        public void run() {
            for (int i = 0; i < 1000000; i++) {
                synchronized (Thread5.class){
                    count++;
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 20; i++) {
                es.execute(new Thread5());
            }
            es.shutdown();
            es.awaitTermination(6, TimeUnit.SECONDS);
            System.out.println(count);
        }
    }
    /* 類鎖
      20000000
      */

    而一旦換成對象鎖,不同實例,就可以同時訪問。則會出錯:

    public void run() {
            for (int i = 0; i < 1000000; i++) {
                synchronized (this){
                    count++;
                }
            }
    }
    /* 對象鎖
     10746948
    */
    

    這是因為靜態變量並不屬於某個實例對象,而是屬於類所有,所以對某個實例加鎖,並不會改變count變量臟讀和臟寫的情況,還是造成結果不正確。

     

    參考資料

    1. 對象布局的各部分介紹——

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

  • 小程序雲開發:菜鳥也能全棧做產品

    小程序雲開發:菜鳥也能全棧做產品

    我想獨立實現一個全棧產品為什麼這麼難

    日常生活中,我們會使用很多軟件產品。在使用這些產品的時候,我們看得見的東西稱為“前端界面”如一個輸入框、一個按鈕,點擊按鈕之後發生的一切看不見的東西稱為“後端服務”。與之對應的創造者分別稱為“前端程序員”、“後端程序員”,然而,一個完整產品的開發不僅僅是只有前端和後端,還有設計師,架構師,運維等。有沒有可能這些所有的事情都一個人干呢?有可能,事實上如今就有很多的“全棧工程師”,他們身兼數職,是多面手。能獨立完成一個產品的方方面面。這種人固然十分了得,他們通常具有多年的經驗,涉獵廣泛,是老手,也是高手,當有一個產品想法的時候,他們可以用自己的全面專業技能,盡情的發揮去實現自己的想法。所以,從某種意義上講“全棧也是一種自由”,你可以自由的實現你的想法,這簡直太美妙了!

    然而,很多時候當我們有一個產品想法的時候,我們往往發現,前端寫完了,後端怎麼搞?數據庫怎麼搞?域名怎麼搞?域名還要備案?應用部署怎麼搞?我的買什麼樣的服務器啊?靜態資源 CDN 怎麼搞?文件上傳服務器怎麼搞?萬一訪問用戶多了能撐住嗎?等等……問題很多,導致你的一個個想法,都只是在腦海中曇花一現,從來都無法將她們實現,或者說你激情飽滿的實現了其中自己最擅長的一部分,當碰到其他難題的時候就止步了。於是仰天長嘯:我就想獨立做一個完整的產品為什麼這麼難?年輕人,這一切都不怪你……

    破局:小程序雲開發

    為什麼使用小程序雲開發來破局?

    為啥是用“小程序雲開發”來破局?首先,我們的目的是全棧實現一個產品。全棧可以有多種技術方案,你可用任何你能會的技能來達到全棧的目的。你可以開發安卓,IOS,或者 PC 站,然而小程序是最實際的!為啥?手機上能做的事情為啥要用 PC 版?OK,既然手機版比較好,那能不能再簡單一點?能,就是小程序,不需要開發IOS,安卓兩個版本。可以快速產出,快速試錯。

    其次,前面說到了,全棧實現一個產品並不容易,對很多人來說甚至是巨難!選擇了小程序已經是比較划算的方案。而再集成雲開發,全棧立馬就有了。這就是為什麼選擇“小程序雲開發”來破局。

    小程序雲開發是什麼?

    小程序雲開發是什麼?官方文檔是這麼說的:開發者可以使用雲開發開發微信小程序、小遊戲,無需搭建服務器,即可使用雲端能力。雲開發為開發者提供完整的原生雲端支持和微信服務支持,弱化後端和運維概念,無需搭建服務器,使用平台提供的 API 進行核心業務開發,即可實現快速上線和迭代,同時這一能力,同開發者已經使用的雲服務相互兼容,並不互斥。

    看完上面的描述,也許你仍然無法非常清楚的知道什麼是“小程序雲開發”,沒關係,你只需要注意加粗的部分,大概知道它“無需搭建服務器”,從傳統觀念將,這個似乎“毀三觀”咋可能沒服務器啊?是的,可以沒有傳統意義上的服務器,這種模式是 serveless 的。

    那麼,小程序雲開發提供了哪些東西來破局呢?且看下面的表格:

    能 力 作 用 說 明
    雲函數 無需自建服務器 在雲端運行的代碼,微信私有協議天然鑒權,開發者只需編寫自身業務邏輯代碼
    數據庫 無需自建數據庫 一個既可在小程序前端操作,也能在雲函數中讀寫的 JSON 數據庫
    存儲 無需自建存儲和 CDN 在小程序前端直接上傳/下載雲端文件,在雲開發控制台可視化管理
    雲調用 原生微信服務集成 基於雲函數免鑒權使用小程序開放接口的能力,包括服務端調用、獲取開放數據等能力

    上面的表格中提到了“雲開發”中的一些能力:“雲函數”,“數據庫”,“存儲”,“雲調用”,我們可以將這些詞帶入你曾經開發過的應用,看看它們分別代表了哪些部分。對於程序員來說,如果有疑問的話,沒有什麼是一個 helloword 解決不了的。

    實戰:獨立開發一個簡易的零售小程序

    哆嗦再多,不如實戰。下面我們就來使用小程序雲開發實現一個簡單的零售小程序。

    項目構思

    既然是一個零售小程序,那麼我們可以思考一下零售小程序的大致業務流程,以及粗略的梳理一下,其功能點。現根據自己的想法,大致畫一下草圖,如果沒有靈感可以參考一下別的 APP 是如何設計的。

    我根據自己的想法設計之後是這樣的:

    功能模塊:首頁,商品列表頁,購物車,確認訂單,個人中心,個人訂單,管你模塊(商品添加,分類添加)其中商品需要上傳圖片。

    梳理完功能之後,我們對於要實現的東西已經有個初步的概念了。接下來,我們需要大概畫一下頁面設計、及功能流轉。初次設計可能沒有太多經驗,沒關係,開始做就行了,做着做着就會想法越來越多,然後優化的越來越好。。我也是經過了多番修改調整,最終找到了一些思路。我的(拙劣)設計如下,圖片如果看不清楚可複製圖片鏈接在新窗口打開查看:

    說明,以上圖片是根據成品(我真的開發了一個雲小程序並上線使用了)截圖的,而實際我再設計的時候也是經過幾番修改才最終定成這樣。

    同時,補充說明一下,這裏前端頁面使用的是 vant-weapp控件,非常好用。推薦!如果你和我一樣是一個純後端程序員,建議使用 vant-weapp 來作為 ui,非常方便。否則自己寫頁面樣式的話可能就做不出來了。全棧不是那麼好乾的啊。選擇自己能駕馭的,能實現最終功能,就是一個合格的全棧。

    創建小程序雲開發項目

    我們先下載微信小程序開發工具,下載地址,安裝好了之後,新建項目,界面如下,APPID 需要你自己去註冊一個。然後注意,選擇“小程序雲開發”,如下圖所示:

    創建好了之後,項目目錄如下,先看 1 標註的地方:

    如果你曾經有過小程序的開發經驗,那麼miniprogram文件夾下面的結構你肯定熟悉了,miniprogram下面的子目錄分別是小程序對應的組件、圖片、頁面、樣式以及app.js,app.json,sitemap.json,其中components下面的vant-weapp就是上面提到的 ui 組件。

    最後一個比較重要的文件夾就是cloudfunctions,這個目錄是用來存放“雲函數的”,雲函數就是我們的後端。每一個雲函數提供一個服務。一個個的雲函數組成了我們整體的後端服務。雲函數可以看做是 FaaS(function as a service)。途中,2 標記的位置的“雲開發”按鈕,我們點進去,就可以看到“雲開發的控制台”,如下圖所示:

    如果上圖看不清楚,可以複製鏈接到新的瀏覽器窗口查看,如圖,小程序雲開發默認的免費套餐有一定的額度可供使用。首頁便是使用統計。然後我們能看到,有“數據庫”,“存儲”,“雲函數”。

    這裏的“數據庫”其實就是類似於一個 MongoDB,你可以點進去創建一個個的 collection(即:關係型數據庫中的table);這裏的“存儲”其實就是“文件夾”,我們可以通過微信提供的 api把圖片上傳到“存儲”中;這裏的“雲函數”就是我們需要實現的後端業務邏輯,他就是一個個的函數(函數由我們自己寫好後上傳)。一般開發過程中我們在開發者工具中的cloudfunctions目錄下創建雲函數(比方說是:user-add)開發完成之後在雲函數目錄點擊右鍵——上傳即可。然後就可以在小程序的代碼中調用這個user-add雲函數。

    雲開發之——3 分鐘實現文件上傳

    注意:在開始雲開發之前,我們現在 小程序代碼的 app.js 中加入wx.cloud.init,如下:

    App({
      onLaunch: function () {
        if (!wx.cloud) {
          console.error('請使用 2.2.3 或以上的基礎庫以使用雲能力')
        } else {
          wx.cloud.init({
            // env 參數說明:
            //   env 參數決定接下來小程序發起的雲開發調用(wx.cloud.xxx)會默認請求到哪個雲環境的資源
            //   此處請填入環境 ID, 環境 ID 可打開雲控制台查看
            //   如不填則使用默認環境(第一個創建的環境)
            env: 'your-env-id',
            traceUser: true,
          })
        }
        this.globalData = {}
      }
    })

    上面的圖中,我們已經看到了“商品添加”頁面的效果,它需要我們輸入商品名稱、價格、並上傳圖片,然後保存。傳統架構中,上傳圖片需要前端頁面擺一個控件,然後後端提供一個 api用來接收前端傳來的文件,通常來說這個後端 api 接收到圖片之後,會將圖片文件保存到自己的文件服務器或者是阿里雲存儲、或者是七牛雲存儲之類的。然後返回給你一個文件鏈接地址。非常麻煩,然而,小程序雲開發上傳文件超級簡單,上代碼:

    頁面代碼:
    <van-notice-bar
      scrollable="false"
      text="發布商品"
    />
      <van-field
        value="{{ productName }}"
        required
        clearable
        label="商品名稱"
        placeholder="請輸入商品名稱"
        bind:change="inputName"
      />
        <van-field
        value="{{ productPrice }}"
        required
        clearable
        label="價格"
        icon="question-o"
         bind:click-icon="onClickPhoneIcon"
        placeholder="請輸入價格"
        error-message="{{phoneerr}}"
        border="{{ false }}"
        bind:change="inputPrice"
      />
    
    <van-action-sheet
      required
      show="{{ showSelect }}"
      actions="{{ actions }}"
      close-on-click-overlay="true"
      bind:close="toggleSelect"
      bind:select="onSelect" cancel-text="取消"
    />
      <van-field
        value="{{ productCategory }}"
        center
        readonly
        label="商品分類"
        border="{{ false }}"
        use-button-slot
      >
        <van-button slot="button" size="small" plain type="primary"  
         bind:click="toggleSelect">選擇分類</van-button>
      </van-field>
      
      <van-button class="rightside" type="default" bind:click="uploadImage" >上傳商品圖片</van-button>
      <view class="imagePreview">
        <image src="{{productImg}}" />
      </view>
     <van-submit-bar
      price="{{ totalShow }}"
      button-text="提交"
      bind:submit="onSubmit"
      tip="{{ false }}"
     >
     </van-submit-bar> 
    <van-toast id="van-toast" />
    <van-dialog id="van-dialog" />

    這裡有個控件,綁定了uploadImage方法,其代碼為:

      uploadImage:function(){
        let that = this;
        wx.chooseImage({
          count: 1,
          sizeType: ['compressed'],
          sourceType: ['album', 'camera'],
          success(res) {
            wx.showLoading({
              title: '上傳中...',
            })
            const tempFilePath = res.tempFilePaths[0]
            const name = Math.random() * 1000000;
            const cloudPath = name + tempFilePath.match(/\.[^.]+?$/)[0]
            wx.cloud.uploadFile({
              cloudPath:cloudPath,//雲存儲圖片名字
              filePath: tempFilePath,//臨時路徑
              success: res => {
                let fileID = res.fileID;
                that.setData({
                  productImg: res.fileID,
                });
                wx.showToast({
                  title: '圖片上傳成功',
                })
              },
              fail: e =>{
                wx.showToast({
                  title: '上傳失敗',
                })
              },
              complete:()=>{
                wx.hideLoading();
              }
            });
          }
        })
      }

    這裏,wx.chooseImage用於調起手機選擇圖片(相冊/相機拍照),然後wx.cloud.uploadFile用於上傳圖片到上面說到的雲開發能力之一的“存儲”中。上傳圖片成功之後返回一個文件 ID,類似:

    cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  

    這個鏈接可以直接在小程序頁面展示:

    <image src="cloud://release-0kj63.7265-release-0kj63-1300431985/100477.13363146288.jpg  " />

    也可以通過微信 api,裝換成 http 形式的圖片鏈接。

    雲開發之——操作數據庫,1 分鐘寫完保存商品到數據庫的代碼

    上面我們實現了商品圖片上傳,但是,商品圖片並沒有保存到數據庫。正常錄入商品的時候,我們會填好商品名稱,價格等,然後上傳圖片,最終點擊“保存”按鈕,將商品保存到數據庫。傳統模式下,前端仍然是需要調用一個後端接口,通過 post 提交數據,最終由後端服務(比如 java 服務)將數據保存到數據庫。小程序雲開發使得操作數據庫十分簡單,首先我們在雲開發控制台創建“商品表”,即一個 collection,取名為:products。然後我們就可以保存數據到數據庫了,代碼如下:

    onSubmit:function(){
        // 校驗代碼,略
        let product = {};
        product.imgId = this.data.productImg;
        product.name= this.data.productName;
        product.categoryId = this.data.productCategoryId;
        product.price = this.data.productPrice;
        // 其他賦值,略
        const db = wx.cloud.database();
        db.collection('products').add({
         data: product,
         success(res) {
           wx.showToast({
             title: '保存成功',
           })
         }
       });
      }

    以上就實現了數據入庫,就這點代碼,超簡單,1 分鐘寫完,誠不欺我。其中這裏的products就是我們的“商品表”,之前說過,類似 MongoDB 數據庫,這裏操作的是db.collection,這和 MongoDB 的語法差不多。

    雲開發之——使用雲函數完成後端業務邏輯,訂單創建

    小程序雲開發提供了幾大能力:“數據庫”,“存儲”,“雲函數”,前兩項我們已經有所體會了。下面我們能創建一個雲函數來實現訂單創建。這裏說明,雲函數其實就是 一段JavaScript 代碼,上傳至雲服務器之後,最終也是運行在 nodejs 環境的,只是這一切,我們不需要關心。我們只需要關心我們這個雲函數提供的功能是什麼就可以了。

    創建雲函數很簡單,直接在開發工具中右鍵“新建Node.js 雲函數”。然後以創建訂單為例,假設我們創建一個雲函數名為c-order-add,創建好了之後,目錄是這樣:

    雲函數的主要代碼在 index.js 中,其完整代碼是這樣:

    // 雲函數入口文件
    const cloud = require('wx-server-sdk')
    cloud.init({
      env: 'release-xxx'// your-env-id
    })
    const db = cloud.database()
    
    // 雲函數入口函數
    exports.main = async (event, context) => {
      const wxContext = cloud.getWXContext();
      console.log("雲函數 c-order-add : ")  
      // 這裡是一些邏輯處理...
      
      return await db.collection('uorder').add({
        data: {
          openid: event.userInfo.openId,
          address: event.address,
          userName: event.userName,
          phone: event.phone,
          shoppingInfo: event.shoppingInfo,
          totlePrice: event.totlePrice,
          shoppingStr: event.shoppingStr,
          remark:event.remark,
          createTime: now,
          // ...
        }
      });
    }

    這個雲函數寫好之後,需要上傳到服務器,直接在雲函數目錄點擊右鍵,然後點擊“上傳並部署”即可,這就相當於部署好了後端服務。前端小程序頁面調用的寫法是這樣的:

    let orderData={};
    orderData.userName = this.data.userName;
    orderData.phone = this.data.phone;
    orderData.address = this.data.address;
    // ....
    wx.cloud.callFunction({
          // 雲函數名稱
          name: 'c-order-add',
          // 傳給雲函數的參數
          data: orderData,
          complete: res => {
            Dialog.alert({
              title: '提交成功',
              message: '您的訂單成功,即將配送,請保持手機通暢。'
            }).then(() => {
              // ....
              wx.redirectTo({
                url: '../uorder/uorder'
              });
            });
          }
    })

    這裏,向程序前端,通過wx.cloud.callFunction完成了對雲函數的調用,也可以理解為對後端服務的調用。至此我們我們介紹完了,小程序雲開發的功能。雖然,我只貼出了少量的代碼,即保存商品,和提交訂單。由於時間和篇幅有限,我不可能把整個完整的程序代碼貼出來。但是你可以參照這個用法示例,將剩下的業務邏輯補充完整,最終完成“項目構思”一節中展示的成品截圖效果。

    小程序審核的一點經驗

    我開發的小程序審核在提交審核的時候遭遇了兩次退回,第一次是因為:“小程序具備電商性質,個人小程序號不支持”。所以,我只好申請了一個企業小程序號,使用的是超市的營業執照。服務類目的選擇也被打回了一次,最後選擇了食品還提交了食品經營許可證。第二次打回是因為:“用戶體驗問題”。其實就是“授權索取”的問題,微信不讓打開首頁就“要求授權”,同時不能強制用戶接受授權,得提供拒絕授權也能使用部分功能。

    上面兩條解決之後,更新新了好幾版,都沒有出現過被拒的情況。並且,有次我是夜晚 10 左右提價的審核,結果10 點多就提示審核通過,當時沒看具體時間,就是接盆水泡了個腳的時間審核通過了。所以,我推斷小程序審核初次審核會比較嚴,之後如果改動不大應該直接機審就過了。

    總結及對比

    這裏我們可以對小程序雲開發和傳統模式做一個對比:

    對比條目 傳統模式 雲開發
    是否需要後端服務 需要 (如一個java應用部署在 Tomcat 中) 不需要 只需要“雲函數”
    是否需要域名 需要 (還得在微信後台的把域名加入安全域名) 不需要
    是否需要購買服務器 需要 (你得部署後端 Java 應用,還得安裝數據庫) 不需要
    開通雲開發之後免費套餐夠用
    不夠的話購買套餐按調用量計費
    是否需要懂運維 需要
    (你得會折騰服務器,數據庫之類的
    還得配置好相關的用戶,端口,啟動服務)
    不需要
    圖片上傳及 CDN 麻煩 簡單
    獲取微信 openID 麻煩 超級簡單,雲函數中直接獲取
    ···

    就對比這麼多吧,總之,我非常喜歡小程序雲開發,小程序真的可以讓你輕鬆干全棧。或者咱們別動不動就提“全棧”,姑且說,小程序雲開發可以讓你更簡單、更快速、更便宜的實現你的產品落地。我自己開發的雲小程序上線之後,使用了一兩個月,沒出現任何問題。我也不用操心服務器什麼的。所以,我已經給身邊很多人安利了小程序雲開發了。這裏我就不貼出我的小程序碼了,因為已經正式給我同學的超市使用了,所以不方便讓別人去產生測試數據。如果你感興趣想看的話,可以聯繫我。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • [學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

    [學習筆記] 在Eclipse中使用Hibernate,並創建第一個Demo工程,數據庫為Oracle XE

    前文參考:

    在Eclipse中使用Hibernate

    安裝 Hibernate Tools 插件

    https://tools.jboss.org/downloads/

    Add the following URL to your Eclipse 4.13 (2019-09) installation, via:

    Help > Install New Software… > Work with:

    http://download.jboss.org/jbosstools/photon/stable/updates/

    Then select the individual features that you want to install:

    點擊Next

    點擊Next
    同意相關協議,點擊Finish .

    則會開始下載安裝。

    視網絡速度,可能需要幾分鐘到十幾分鐘的時間才能完成安裝。

    最後會提示重啟Eclipse才能生效。

    在Eclipse中新建Hibernate應用

    File->New -> Java Project

    點擊Finish

    項目結構圖

    在Eclipse中新建用戶庫

    此時下面显示了已經建立的用戶庫列表

    我們要添加Hibernate的依賴庫,因此點擊用戶庫

    Hibernate_4.3.5_final

    選擇jar文件

    項目結構圖

    繼續配置Hibernate

    最後自動形成 如下的文件內容:[本例使用oracle數據庫]

    Oracle 11g xe 在windows安裝請看如下鏈接:

    hibernate.cfg.xml

     <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
            <property name="hibernate.connection.password">123456</property>
            <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
            <property name="hibernate.connection.username">test</property>
            <property name="hibernate.default_schema">test</property>
            <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        </session-factory>
    </hibernate-configuration>

    再增加幾個屬性

    配置文件更新后的內容如下: 注意要去掉name屬性 更改 為

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                             "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
     <session-factory >
      <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
      <property name="hibernate.connection.password">123456</property>
      <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:xe:orcl</property>
      <property name="hibernate.connection.username">test</property>
      <property name="hibernate.default_schema">test</property>
      <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
      <property name="hibernate.show_sql">true</property>
      <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
      <property name="hibernate.hbm2ddl.auto">update</property> 
      <property name="hibernate.format_sql">true</property>
      <property name="hibernate.default_entity_mode">pojo</property>
     </session-factory>
    </hibernate-configuration>

    繼續完善工程Hibernate_demo_001

    新建一個包:mytest001.demo

    在包mytest001.demo之下新建一個PO類: Emp

    package mytest001.demo;
    
    public class Emp {
        // 員工的標識屬性
        private Integer id;
        // 姓名
        private String name;
        // 年齡
        private Integer age;
        // 工資 (分)
        private Integer salary;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Integer getSalary() {
            return salary;
        }
    
        public void setSalary(Integer salary) {
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
        }
    }
    
    

    此刻此PO Emp.java 尚不具備持久化能力。下面為其添加註解。

    @Entity 註解聲明該類是一個Hibernate持久化類
    @Table 指定該類映射的表,對應的數據庫表名是T_EMP
    @Id 指定該類的標識屬性,映射到數據庫的主鍵列
    @GeneratedValue(strategy=GenerationType.SEQUENCE) 指定了主鍵生成策略,由於本文使用Oracle Database, 因此指定了使用 SEQUENCE

    在hibernate.cfg.xml中增加持久化映射類名

    增加一個新類:EmpManager,用於管理員工。

    package mytest001.demo;
    
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.Transaction;
    import org.hibernate.cfg.Configuration;
    import org.hibernate.service.Service;
    import org.hibernate.service.ServiceRegistry;
    import org.hibernate.service.ServiceRegistryBuilder;
    
    
    public class EmpManager {
    
        public static void main(String[] args)  throws Exception  {
             
        //實例化配置
        Configuration configuration  = new Configuration()
                //不帶參數則默認加載hibernate.cfg.xml
                .configure();
        
        ServiceRegistry serviceRegistry = new ServiceRegistryBuilder()
                .applySettings(configuration.getProperties()).build();
        SessionFactory sFactory = configuration.buildSessionFactory(serviceRegistry);
        
        //創建session 
        Session session = sFactory.openSession();
        
        //開始事務
        Transaction tx = session.beginTransaction();
        //創建員工對象
        Emp emp = new Emp();
        // 設置員工信息
        emp.setAge(28);
        emp.setName("scott");
        emp.setSalary(10000);
        session.save(emp);
        // 提交事務
        tx.commit();
        session.close();
        sFactory.close();
                
        
        }
    
    }

    最後配置文件的內容:hibernate.cfg.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
                                             "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
     <session-factory >
      <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
      <property name="hibernate.connection.password">123456</property>
      <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:1521:xe</property>
      <property name="hibernate.connection.username">test</property>
      <property name="hibernate.default_schema">test</property>
      <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
      <property name="hibernate.show_sql">true</property>
      <!-- 第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等 應用第一次運行起來后才會。 -->
      <property name="hibernate.hbm2ddl.auto">update</property> 
      <property name="hibernate.format_sql">true</property>
      <property name="hibernate.default_entity_mode">pojo</property>
      <mapping class="mytest001.demo.Emp"/>
     </session-factory>
    </hibernate-configuration>
    

    Emp.java

    package mytest001.demo;
    
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    @Entity
    @Table(name="T_EMP")
    public class Emp {
        // 員工的標識屬性
        @Id
        @GeneratedValue(strategy=GenerationType.SEQUENCE)
        private Integer id;
        // 姓名
        private String name;
        // 年齡
        private Integer age;
        // 工資 (分)
        private Integer salary;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Integer getSalary() {
            return salary;
        }
    
        public void setSalary(Integer salary) {
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Emp [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + "]";
        }
    }
    

    最後的工程結構如下:

    運行EmpManger

    十一月 23, 2019 8:50:47 上午 org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
    INFO: HCANN000001: Hibernate Commons Annotations {4.0.4.Final}
    十一月 23, 2019 8:50:47 上午 org.hibernate.Version logVersion
    INFO: HHH000412: Hibernate Core {4.3.5.Final}
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment <clinit>
    INFO: HHH000206: hibernate.properties not found
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Environment buildBytecodeProvider
    INFO: HHH000021: Bytecode provider name : javassist
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration configure
    INFO: HHH000043: Configuring from resource: /hibernate.cfg.xml
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration getConfigurationInputStream
    INFO: HHH000040: Configuration resource: /hibernate.cfg.xml
    十一月 23, 2019 8:50:47 上午 org.hibernate.cfg.Configuration doConfigure
    INFO: HHH000041: Configured SessionFactory: null
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
    WARN: HHH000402: Using Hibernate built-in connection pool (not for production use!)
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000401: using driver [oracle.jdbc.driver.OracleDriver] at URL [jdbc:oracle:thin:@localhost:1521:xe]
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000046: Connection properties: {user=test, password=****}
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
    INFO: HHH000006: Autocommit mode: false
    十一月 23, 2019 8:50:47 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
    INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
    十一月 23, 2019 8:50:48 上午 org.hibernate.dialect.Dialect <init>
    INFO: HHH000400: Using dialect: org.hibernate.dialect.Oracle10gDialect
    十一月 23, 2019 8:50:48 上午 org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
    INFO: HHH000399: Using default transaction strategy (direct JDBC transactions)
    十一月 23, 2019 8:50:48 上午 org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
    INFO: HHH000397: Using ASTQueryTranslatorFactory
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000228: Running hbm2ddl schema update
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000102: Fetching database metadata
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000396: Updating schema
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
    INFO: HHH000262: Table not found: T_EMP
    十一月 23, 2019 8:50:48 上午 org.hibernate.tool.hbm2ddl.SchemaUpdate execute
    INFO: HHH000232: Schema update complete
    Hibernate: 
        select
            test.hibernate_sequence.nextval 
        from
            dual
    Hibernate: 
        insert 
        into
            test.T_EMP
            (age, name, salary, id) 
        values
            (?, ?, ?, ?)
    十一月 23, 2019 8:50:48 上午 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
    INFO: HHH000030: Cleaning up connection pool [jdbc:oracle:thin:@localhost:1521:xe]
    

    到數據庫中查詢表:(這個表會被自動創建)
    select * from t_emp;

    如果再次運行會增加新的記錄。

    至此,本文完成。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • javascript閉包詳解

    閉包(closure)是Javascript語言的一個難點,也是它的特色,很多高級應用都要依靠閉包實現。

    下面就是我的學習筆記,對於Javascript初學者應該是很有用的。

    一、變量的作用域

    要理解閉包,首先必須理解Javascript特殊的變量作用域。

    變量的作用域無非就是兩種:全局變量和局部變量。

    Javascript語言的特殊之處,就在於函數內部可以直接讀取全局變量。

      

    var n=999;
    
      function f1(){
        alert(n);
      }
    
      f1(); // 999
    

      

    另一方面,在函數外部自然無法讀取函數內的局部變量。

     

     function f1(){
        var n=999;
      }
    
      alert(n); // error
    

      

    這裡有一個地方需要注意,函數內部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!

     

     function f1(){
        n=999;
      }
    
      f1();
    
      alert(n); // 999
    

      

    二、如何從外部讀取局部變量?

    出於種種原因,我們有時候需要得到函數內的局部變量。但是,前面已經說過了,正常情況下,這是辦不到的,只有通過變通方法才能實現。

    那就是在函數的內部,再定義一個函數。

      

    function f1(){
    
        var n=999;
    
        function f2(){
          alert(n); // 999
        }
    
      }
    

      

    在上面的代碼中,函數f2就被包括在函數f1內部,這時f1內部的所有局部變量,對f2都是可見的。但是反過來就不行,f2內部的局部變量,對f1就是不可見的。這就是Javascript語言特有的”鏈式作用域”結構(chain scope),子對象會一級一級地向上尋找所有父對象的變量。所以,父對象的所有變量,對子對象都是可見的,反之則不成立。

    既然f2可以讀取f1中的局部變量,那麼只要把f2作為返回值,我們不就可以在f1外部讀取它的內部變量了嗎!

     

     function f1(){
    
        var n=999;
    
        function f2(){
          alert(n);
        }
    
        return f2;
    
      }
    
      var result=f1();
    
      result(); // 999
    

      

    三、閉包的概念

    上一節代碼中的f2函數,就是閉包。

    各種專業文獻上的”閉包”(closure)定義非常抽象,很難看懂。我的理解是,閉包就是能夠讀取其他函數內部變量的函數。

    由於在Javascript語言中,只有函數內部的子函數才能讀取局部變量,因此可以把閉包簡單理解成”定義在一個函數內部的函數”。

    所以,在本質上,閉包就是將函數內部和函數外部連接起來的一座橋樑。

    四、閉包的用途

    閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數內部的變量,另一個就是讓這些變量的值始終保持在內存中。

    怎麼來理解這句話呢?請看下面的代碼。

      function f1(){

        var n=999;

        nAdd=function(){n+=1}

        function f2(){
          alert(n);
        }

        return f2;

      }

      var result=f1();

      result(); // 999

      nAdd();

      result(); // 1000

    在這段代碼中,result實際上就是閉包f2函數。它一共運行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數f1中的局部變量n一直保存在內存中,並沒有在f1調用后被自動清除。

    為什麼會這樣呢?原因就在於f1是f2的父函數,而f2被賦給了一個全局變量,這導致f2始終在內存中,而f2的存在依賴於f1,因此f1也始終在內存中,不會在調用結束后,被垃圾回收機制(garbage collection)回收。

    這段代碼中另一個值得注意的地方,就是”nAdd=function(){n+=1}”這一行,首先在nAdd前面沒有使用var關鍵字,因此nAdd是一個全局變量,而不是局部變量。其次,nAdd的值是一個匿名函數(anonymous function),而這個匿名函數本身也是一個閉包,所以nAdd相當於是一個setter,可以在函數外部對函數內部的局部變量進行操作。

    五、使用閉包的注意點

    1)由於閉包會使得函數中的變量都被保存在內存中,內存消耗很大,所以不能濫用閉包,否則會造成網頁的性能問題,在IE中可能導致內存泄露。解決方法是,在退出函數之前,將不使用的局部變量全部刪除。

    2)閉包會在父函數外部,改變父函數內部變量的值。所以,如果你把父函數當作對象(object)使用,把閉包當作它的公用方法(Public Method),把內部變量當作它的私有屬性(private value),這時一定要小心,不要隨便改變父函數內部變量的值。

    六、思考題

    如果你能理解下面兩段代碼的運行結果,應該就算理解閉包的運行機制了。

    代碼片段一。

      var name = “The Window”;

      var object = {
        name : “My Object”,

        getNameFunc : function(){
          return function(){
            return this.name;
          };

        }

      };

      alert(object.getNameFunc()());

    代碼片段二。

      var name = “The Window”;

      var object = {
        name : “My Object”,

        getNameFunc : function(){
          var that = this;
          return function(){
            return that.name;
          };

        }

      };

      alert(object.getNameFunc()());

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

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

  • PowerMock學習(四)之Mock static的使用

    PowerMock學習(四)之Mock static的使用

    我們編寫代碼的時候,總會寫一些工具類,為了方便調用喜歡使用static關鍵字來修飾對應方法。

    那麼現在舉例說明,還是準備兩個接口,第一個是查詢學生總數,第二個是新增學生兩個接口,具體示例代碼如下:

    package com.rongrong.powermock.mockstatic;
    
    import com.rongrong.powermock.service.Student;
    
    /**
     * @author rongrong
     * @version 1.0
     * @date 2019/11/23 8:08
     */
    public class StudentStaticService {
    
        /**
         * 獲取學生總數
         * @return
         */
        public int getStudentTotal(){
            return StudentUtils.getStudent();
        }
    
        /**
         * 創建一個學生
         * @param student
         */
        public void createStudent(Student student){
            StudentUtils.createStudent(student);
        }
    }

    接着我們再來看看這個靜態工具類StudentUtils,具體代碼示例如下:

    package com.rongrong.powermock.mockstatic;
    
    import com.rongrong.powermock.service.Student;
    
    /**
     * @author rongrong
     * @version 1.0
     * @date 2019/11/23 7:38
     */
    public class StudentUtils {
        /**
         * 獲取學生總數
         * @return
         */
        public static int getStudent(){
            throw new UnsupportedOperationException();
        }
    
        /**
         * 創建一個學生
         * @param student
         */
        public static void createStudent(Student student){
            throw new UnsupportedOperationException();
        }
    }

    接下來我們用傳統方式,來做單元測試,示例代碼如下:

        @Test
        public void testGetStudnetTotal(){
            StudentStaticService staticService = new StudentStaticService();
            int studentTotal = staticService.getStudentTotal();
            assertEquals(studentTotal,10);
        }
    
        @Test
        public void testCreateStudent(){
            StudentStaticService staticService = new StudentStaticService();
            staticService.createStudent(new Student());
            assertTrue(true);
        }

    接着運行下測試用例,結果肯定報錯了,為什麼報錯,這裏就不再細說了,參考之前文章,報錯,如下圖所示:

     

    接下來我們使用powermock來進行測試,具體示例代碼如下:

     @Test
        public void testGetStudentWithMock(){
            //先mock工具類對象
            PowerMockito.mockStatic(StudentUtils.class);
            //模擬靜態類調用
            PowerMockito.when(StudentUtils.getStudent()).thenReturn(10);
            //構建service
            StudentStaticService service = new StudentStaticService();
            int studentTotal = service.getStudentTotal();
            assertEquals(10,studentTotal);
        }
    
        @Test
        public void testCreateStudentWithMock(){
            //先模擬靜態工具類
            PowerMockito.mockStatic(StudentUtils.class);
            //模擬調用
            PowerMockito.doNothing().when(StudentUtils.class);
            //構建service
            StudentStaticService service = new StudentStaticService();
            Student student = new Student();
            service.createStudent(student);
            //這裏用powermock來驗證,而不是mock,更體現了powermock的強大
            PowerMockito.verifyStatic();
        }

    再次運行,測試通過,如下圖所示:

     

     

    運行之前先讓powermock為我們準備了StudentUtils工具類,而且採用mockstatic的方法,最後我們用powermock.verifyStatic()驗證,而不是mock,更體現了powermock的強大。

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • Vue項目使用CSS變量實現主題化

    主題化管理經常能在網站上看到,一般的思路都是將主題相關的CSS樣式獨立出來,在用戶選擇主題的時候加載相應的CSS樣式文件。現在大部分瀏覽器都能很好的兼容,主題化樣式更容易管理了。最近,使用CSS變量在Vue項目中做了一個主題化實踐,下面來看看整個過程。

    可行性測試

    為了檢驗方法的可行性,在public文件夾下新建一個themes文件夾,並在themes文件夾新建一個default.css文件:

    :root {
      --color: red;
    }

    在public文件夾的index.html文件中引入外部樣式theme.css,如下:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title>vue-skin-peeler-demo</title>
        <!-- 引入themes文件夾下的default.css -->
        <link rel="stylesheet" type="text/css" href="src/themes/default.css" rel="external nofollow">
      </head>
      <body>
        <noscript>
          <strong>We're sorry but vue-skin-peeler-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
        </noscript>
        <div id="app"></div>
        <!-- built files will be auto injected -->
      </body>
    </html>

    然後,在Home.vue中使用CSS變量:

    <template>
      <div class="home">
        <div :class="$style.demo">變紅色</div>
      </div>
    </template>
    
    <script>
    export default {
      name: 'home'
    }
    </script>
    
    <style module lang="scss">
      .demo {
        color: var(--color);
      }
    </style>

    然後,運行項目並在瀏覽器中打開頁面,頁面显示效果正常。

    注意:@vue/cli使用link標籤引入css樣式可能報錯“We’re sorry but vue-skin-peeler-demo doesn’t work properly without JavaScript enabled. Please enable it to continue.”。這是因為@vue/cli將src目錄下的文件都通過webpack打包所引起,所以,靜態文件資源要放在public(如果是@vue/cli 2.x版本放在static)文件夾下。

    實現主題切換

    這裏主題切換的思路是替換link標籤的href屬性,因此,需要寫一個替換函數,在src目錄下新建themes.js文件,代碼如下:

    // themes.js
    const createLink = (() => {
      let $link = null
      return () => {
        if ($link) {
          return $link
        }
        $link = document.createElement('link')
        $link.rel = 'stylesheet'
        $link.type = 'text/css'
        document.querySelector('head').appendChild($link)
        return $link
      }
    })()
    
    /**
     * 主題切換函數
     * @param {string} theme - 主題名稱, 默認default
     * @return {string} 主題名稱
     */
    const toggleTheme = (theme = 'default') => {
      const $link = createLink()
      $link.href = `./themes/${theme}.css`
      return theme
    }
    
    export default toggleTheme

    然後,在themes文件下創建default.css和dark.css兩個主題文件。創建CSS變量,實現主題化。CSS變量實現主題切換請參考另一篇文章

    兼容性

    IE瀏覽器以及一些舊版瀏覽器不支持CSS變量,因此,需要使用,是一個,可在舊版和現代瀏覽器中為CSS自定義屬性(也稱為“ CSS變量”)提供客戶端支持。由於要開啟watch監聽,所以還有安裝。

    安裝:

    npm install css-vars-ponyfill mutationobserver-shim --save

    然後,在themes.js文件中引入並使用:

    // themes.js
    import 'mutationobserver-shim'
    import cssVars from 'css-vars-ponyfill'
    
    cssVars({
      watch: true
    })
    
    const createLink = (() => {
      let $link = null
      return () => {
        if ($link) {
          return $link
        }
        $link = document.createElement('link')
        $link.rel = 'stylesheet'
        $link.type = 'text/css'
        document.querySelector('head').appendChild($link)
        return $link
      }
    })()
    
    /**
     * 主題切換函數
     * @param {string} theme - 主題名稱, 默認default
     * @return {string} 主題名稱
     */
    const toggleTheme = (theme = 'default') => {
      const $link = createLink()
      $link.href = `./themes/${theme}.css`
      return theme
    }
    
    export default toggleTheme

    開啟watch后,在IE 11瀏覽器點擊切換主題開關不起作用。因此,每次切換主題時都重新執行cssVars(),還是無法切換主題,原因是開啟watch后重新執行cssVars()是無效的。最後,只能先關閉watch再重新開啟。成功切換主題的themes.js代碼如下:

    // themes.js
    import 'mutationobserver-shim'
    import cssVars from 'css-vars-ponyfill'
    
    const createLink = (() => {
      let $link = null
      return () => {
        if ($link) {
          return $link
        }
        $link = document.createElement('link')
        $link.rel = 'stylesheet'
        $link.type = 'text/css'
        document.querySelector('head').appendChild($link)
        return $link
      }
    })()
    
    /**
     * 主題切換函數
     * @param {string} theme - 主題名稱, 默認default
     * @return {string} 主題名稱
     */
    const toggleTheme = (theme = 'default') => {
      const $link = createLink()
      $link.href = `./themes/${theme}.css`
      cssVars({
        watch: false
      })
      setTimeout(function () {
        cssVars({
          watch: true
        })
      }, 0)
      return theme
    }
    
    export default toggleTheme

    查看所有代碼,請移步。

    記住主題

    實現記住主題這個功能,一是可以向服務器保存主題,一是使用本地存儲主題。為了方便,這裏主要使用本地存儲主題的方式,即使用localStorage存儲主題。具體實現請移步。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • Tesla超級充電站計畫開放給他牌電動車充電

    Tesla超級充電站計畫開放給他牌電動車充電

     

    由於各大廠牌的電動車充電標準不同,加上充電站設立不足,續航力一直是電動車普及化的最大障礙之一,但這個情況隨著特斯拉的投入,或許未來即將有望改變。

    Electrek 報導,特斯拉過去經常談到將超級充電站網路(Supercharger network)開放給其他車商的可能,但一直都沒有後續消息傳出。就在16 日能源博覽會中,技術長JB Straubel 再度提及這個想法,他表示,目前正與其他車商針對充電站的設置「積極交流」中。

    特斯拉最早提到這個概念是在2015 年9 月,當時執行長馬斯克(Elon Musk)表示,特斯拉會持續擁有並經營所有超級充電站,其他車商只需要為該廠牌電動車的充電費用付費即可。

    身為世界領先的電動車大廠,特斯拉的超級充電站無論充電率、覆蓋率都領先其他車廠許多,如果這樣的合作方式成真,相信對電動車普及會很有幫助,只怕充電站的塞車情況會更嚴重。

    特斯拉一直有在擴大超級充電站網路,根據官網介紹,目前全球已有超過861 個特斯拉超級充電站,但充電站塞車的情況仍舊持續上演,更別提目前多數電動車充電率都沒辦法達到特斯拉的一半;一旦開放其他廠牌車輛使用,車主恐怕得在充電站等上更久時間。

    為了改善這種情況,除了宣布進一步擴展網路以外,特斯拉推出一種新型用戶付費系統,可以提供其他車商的用戶使用。值得一提的是,特斯拉也加入了CCS 標準協會,這意味著未來超級充電站的充電接口兼容性或許會更廣泛。

    除此之外,一些車商正在打造更高充電率的電動車,很快特斯拉就不會是唯一能以100kW+ 充電率充電的車輛,包括保時捷、奧迪、賓士都宣布,未來2 年會推出新款電動車,估計這些車輛能接受直流快速充電。

    (合作媒體:。圖片出處:wikipedia)

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

    【其他文章推薦】

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

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

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

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

  • 電動車發展快速,旭化成增產鋰電池材料「分隔模」

    電動車發展快速,旭化成增產鋰電池材料「分隔模」

     

    日刊工業新聞23日報導,因車廠加快電動車(EV)的研發腳步、帶動電池材料市場成長速度超乎預期,故旭化成(Asahi Kasei)計畫上修鋰離子電池關鍵材料「分隔膜(separator)」的增產計畫,目標在2020年結束前將分隔膜年產能最高擴增至15億平方公尺(m2)、將達現行的2.5倍,且將遠高於原先規劃的11億m2目標,期望藉由積極投資、鞏固全球龍頭位置。預估追加擴產所需的投資額約300億日圓。

    據報導,2016年全球分隔膜市場規模約15億m2、且預估2020年最高將擴大至35億m2的水準。就用途別來看,車用需求佔整體比重7成;就國別來看,因強化環保規範、提振EV需求急增的中國為全球最大市場、佔全球比重過半。

    旭化成於3月30日宣布,因電動車(EV)、油電混合車(HV)等車用鋰離子電池需求預估將呈現急速增長,故決議擴增鋰離子電池關鍵材料「分隔膜」產能,計畫投下約150億日圓,在守山製造所(滋賀縣守山市)增設年產能約2億平方公尺(m2)的分隔膜產線,並預計於2019年度上半年商轉。

    旭化成指出,待上述增產工程完工後,該公司整體分隔膜年產能將從現行的約6.6億m2提高3成至約8.6億m2。

    旭化成為全球分隔膜龍頭廠、全球市佔率達5成,目前於滋賀縣、宮崎縣以及美國和南韓生產分隔膜。

    根據日本市調機構富士經濟(Fuji Keizai)預估,2020年全球分隔膜市場規模將增至3,000億日圓、將達2015年的2倍水準,而EV、HV等車用用途是推動分隔膜需求急增的最大功臣,預估2020年車用分隔膜佔整體市場比重將達約45%。

    富士經濟6月22日公布調查報告指出,預估2030年時EV年銷售量將增至407萬台、超越HV(2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。富士經濟預估,在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

    (本文內容由授權使用。圖片出處:)  

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

    【其他文章推薦】

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

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

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