Python多線程與隊列

Python多線程與Queue隊列多線程在感官上類似於同時執行多個程序,雖然由於GIL的存在,在Python中無法實現線程的真正并行,但是對於某些場景,多線程仍不失為一個有效的處理方法:

1,不緊急的,無需阻塞主線程的任務,此時可以利用多線程在後台慢慢處理;
2,IO密集型操作,比如文件讀寫、用戶輸入和網絡請求等,此時多線程可以近似達到甚至優於多進程的表現;

多線程的基本使用不再贅述,以下語法便可輕鬆實現:

1 def task(args1, args2):
2     pass
3 
4 Thread(
5     target=task,
6     args=(args1, args2)
7 ).start()

這裏我們重點關注線程通信。

假設有這麼一種場景:有一批源數據,指定一個操作係數N,需要分別對其進行與N的加減乘除操作,並將結果匯總。
當然這裏的加減乘除只是一種簡單處理,在實際的生產環境中,它其實代表了一步較為複雜的業務操作,並包含了較多的IO處理。

自然我們想到可以開啟多線程處理,那麼緊接着的問題便是:如何劃分線程,是根據處理步驟劃分,還是根據源數據劃分?

對於前者,我們把涉及的業務操作單獨劃分位一個線程,即有4個線程分別進行加減乘除的操作,顯然上一個線程的結果是下一個線程的輸入,這類似於流水線操作;

而後者則是把源數據分為若干份,每份啟動一個線程進行處理,最終把結果匯總。一般來說,我們推薦第一種方式。因為在一個線程中完成所有的操作不如每步一個線程清晰明了,

尤其是在一些複雜的場景下,會加大單個線程的出錯概率和測試難度。

那麼我們將開闢4個線程,分別執行加減乘除操作。最後一個除法線程結束則任務完成:

 

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 
 4 from Queue import Queue
 5 from threading import Thread
 6 
 7 
 8 class NumberHandler(object):
 9     def __init__(self, n):
10         self.n = n
11 
12     def add(self, num):
13         return num + self.n
14 
15     def subtract(self, num):
16         return num - self.n
17 
18     def multiply(self, num):
19         return num * self.n * self.n
20 
21     def divide(self, num):
22         return num / self.n
23 
24 
25 class ClosableQueue(Queue):
26     SENTINEL = object()
27 
28     def close(self):
29         self.put(self.SENTINEL)
30 
31     def __iter__(self):
32         while True:
33             item = self.get()
34             try:
35                 if item is self.SENTINEL:
36                     return
37                 yield item
38             finally:
39                 self.task_done()
40 
41 
42 class StoppableWorker(Thread):
43     def __init__(self, func, in_queue, out_queue):
44         super(StoppableWorker, self).__init__()
45         self.in_queue = in_queue
46         self.out_queue = out_queue
47         self.func = func
48 
49     def run(self):
50         for item in self.in_queue:
51             result = self.func(item)
52             self.out_queue.put(result)
53             print self.func
54 
55 
56 if __name__ == '__main__':
57     source_queue = ClosableQueue()
58     add_queue = ClosableQueue()
59     subtract_queue = ClosableQueue()
60     multiply_queue = ClosableQueue()
61     divide_queue = ClosableQueue()
62     result_queue = ClosableQueue()
63 
64     number_handler = NumberHandler(5)
65 
66     threads = [
67         StoppableWorker(number_handler.add, add_queue, subtract_queue),
68         StoppableWorker(number_handler.subtract, subtract_queue, multiply_queue),
69         StoppableWorker(number_handler.multiply, multiply_queue, divide_queue),
70         StoppableWorker(number_handler.divide, divide_queue, result_queue),
71     ]
72 
73     for _thread in threads:
74         _thread.start()
75 
76     for i in range(10):
77         add_queue.put(i)
78 
79     add_queue.close()
80     add_queue.join()
81     print 'add job done...'
82     subtract_queue.close()
83     subtract_queue.join()
84     print 'subtract job done...'
85     multiply_queue.close()
86     multiply_queue.join()
87     print 'multiply job done...'
88     divide_queue.close()
89     divide_queue.join()
90     print 'divide job done...'
91     result_queue.close()
92 
93     print "%s items finished, result: %s" % (result_queue.qsize(), result_queue)
94 
95     for i in result_queue:
96         print i

運行結果:

線程執行日誌:

 

 

 總的結果:

 

 可見線程交叉運行,但是任務卻是順序結束,這符合我們的預期。

值得注意的是,我們在ClosableQueue定義了一個close()方法,通過放入一個特殊的類變量SENTINEL告訴隊列應該關閉。此外,由於直接加減乘除結果不變,因此我特意乘了兩次來便於我們判斷結果。

總結:

1. Queue是一種高效的任務處理方式,它可以把任務處理流程劃分為若干階段,並使用多條python線程來同時執行這些子任務;

2. Queue類具備阻塞式的隊列操作、能夠指定緩衝區尺寸,而且還支 持join方法,這使得開發者可以構建出健壯的流水線。

 

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

2019年11月20日開發手記

兩種運動檢測算法的介紹:

幀差法:

幀差法是目前運動目標檢測中最常用的算法。幀差法依據的原則是:當視頻中存在移動物體的時候,相鄰幀(或相鄰三幀)之間在灰度上會有差別,求取兩幀圖像灰度差的絕對值,則靜止的物體在差值圖像上表現出來全是0,而移動物體特別是移動物體的輪廓處由於存在灰度變化為非0,當絕對值超過一定閾值時,即可判斷為運動目標,從而實現目標的檢測功能。

二維頻域運動目標檢測:

通過對動態圖像的行列分解, 將三維頻域內的運動檢測問題轉化到兩組二維頻域內進行, 從而降低了濾波器設計的難度。給出了一種提取主運動能量的自適應濾波算法, 通過剔除背景和噪聲的頻率成分, 有效地檢測出運動目標。

 

複雜度分析:

針對幀差法進行分析,代碼複雜度主要集中在absdiff與findContours部分,其中absdiff的迭代次數為2*500*500=50000次,時間為88.46ms(取兩百幀計算平均的時間)

 

針對二維頻域運動目標檢測算法,這裡有兩個代碼版本:

針對py-new-fuliye.py,代碼的複雜度主要集中在兩個部分:傅里恭弘=叶 恭弘變換以及遍歷,在py-new-fuliye.py中,共使用了兩次傅里恭弘=叶 恭弘變換與兩次遍歷,遍歷的迭代次數次數為2*50*30=300次,時間為:54.175ms

 

針對pepoplefft.py(改進版)進行分析,使用了兩次傅里恭弘=叶 恭弘變換(一次正一次逆),進行了一次嵌套遍歷,遍歷次數為:50*10=500次,時間為:  ms

 

針對pepoplefft.py進行優化調參:

搜尋噪點:

要找到噪點,就要知道經傅里恭弘=叶 恭弘高通濾波變換后,剩餘的邊緣部分在數組中的表現規律,採用numpy繪圖表示出來:

 

經過閾值去噪后,效果如圖:

 

在隨後的視頻測試中發現其面對複雜環境表現仍不理想,此時考慮選擇繪製多個矩形來框選標記多個候選目標,暫時不考慮使用其他的濾波進行去噪,原因為1、會佔用原本就不多的處理時間,2、不認為在經過高通濾波后還未被濾掉的噪點會被其他濾波函數濾掉,目標被濾掉的可能性反而更大。

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

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

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

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

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

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

除了獲取 MAC 地址還能幹啥

        以前寫過一篇《》的文章,文章的地址是:https://www.cnblogs.com/tosser/p/9022187.html,我當時使用 OCX 來實現,可是 OCX 只支持 IE 瀏覽器,後來在往上找了一個 Chrome 的擴展,也解決了該問題。不過,總覺得無論使用 OCX 還是使用 Chrome 的擴展,都是瀏覽器相關的,並不通用。後來,使用 Socket 寫了一個簡單的 DEMO,用來模擬 HTTP 服務器,然後使用 Ajax 進行通信,問題解決了。也不再是瀏覽器相關了。

 

Web 頁面獲取 MAC 地址的設計思路

        Web 頁面獲取 MAC 地址的設計思路是比較簡單的,只需要在本地模擬一個 HTTP 服務器,然後讓 Web 頁面通過 Ajax 來請求 HTTP 服務器,HTTP 服務器直接返回本機的 MAC 地址就可以了。

        具體流程如下圖:

 

        流程圖非常的清楚,主要就是 HostServer 和 Ajax 的通信,這樣就可以得到 MAC 地址,然後通過 DOM 操作,即可把 MAC 地址寫入到 input 框中。這樣,就可以和用戶名、密碼一起提交給服務器進行驗證了。

 

 

 

除了獲取 MAC 地址還能幹啥

 

       之前做過一個物流提貨的項目,涉及到一些硬件設備,包括:小票打印機、刷卡器(身份證、銀聯卡)、進幣器、密碼数字鍵盤等。這些設備、電腦主機和显示器在一個類似 ATM 機那樣的機櫃中(其實就是 ATM 機的設備,本身這套東西就是銀行提供的)。

 

       操作這些硬件的接口廠家提供了一個 OCX,而整個項目是 B/S 架構的。那麼,在客戶端想要操作這些硬件,就要調用 OCX,而 OCX 只能在 IE 瀏覽器下使用(Chrome、FireFox 是不支持 OCX 的)。眾所周知,IE 對 Web 並不友好,但是如何又能在不使用 IE 的情況下,又去調用 OCX 來完成操作硬件的功能呢?那麼就是我們上面的方法了。

 

       簡單的描述一下,就不貼圖了。

 

       在終端上放一個 HostServer 用來接受頁面中 Ajax 的請求,並根據請求去調用 OCX 中相應的功能,把 OCX 的返回信息,再以 Json 的格式返回給 Ajax 即可。

 

       這樣,把 瀏覽器 和 OCX 文件進行了分離,中間加入了一個 HostServer,頁面 和 OCX 的通信通過了 HostServer,那麼以後如果接口是 DLL 文件,也可以通過 HostServer 來進行完成,當然,還可以完成更多的功能。

 

 

 

總結

        其實整個獲取 MAC 地址的功能,對於登錄頁面而言是一個服務端,它在本地是一個可執行的程序,那麼它和普通的 EXE 文件是沒有區別的,那麼它能完成的功能其實遠遠不是獲取一個 MAC 地址的功能,對於上面的例子來說,把服務的提供者和使用者進行了分離,而且針對於本機的擴展也十分的方便了。當然,如果你願意的話,可以讓 HostServer 充當客戶端直接和後端的服務器進行通信而不通過瀏覽器,這樣是不是還能做一些讓用戶沒有感知的事情?

 

 

我的微信公眾號:“碼農UP2U”

 

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

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

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

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

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

PowerMock學習(三)之Mock局部變量

編寫powermock用例步驟:

  • 類上面先寫這兩個註解@RunWith(PowerMockRunner.class)、@PrepareForTest(StudentService.class)
  • 先模擬一個假對象即studentdao方法中的局部變量
  • 用無參的方式new對象
  • 再模擬這個對象被調用時,是否有返回,有返回值給出默認值,沒有用doNothing()
  • 驗證有返回值使用assertEquals即可,無返回值使用Mockito.verify驗證

實際案例

接着上一篇文章中的代碼,修改下service中的代碼,這次我不通過構造器注入Dao,在方法中new一個StudentDao,創建一個名為StudentNewService的類。

具體示例代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/17 21:13
 */
public class StudentNewService {


    /**
     * 獲取學生個數
     * @return返回學生總數
     */
    public int getTotal() {
        StudentDao studentDao = new StudentDao();
        return studentDao.getTotal();
    }

    /**
     * 創建學生
     * @param student
     */
    public void createStudent(Student student) {
        StudentDao studentDao = new StudentDao();
        studentDao.createStudent(student);
    }
}

針對上面修改部分代碼,進行單元測試,以下代碼有採用傳統方式測試和採用powermock方式進行測試,具體代碼如下:

package com.rongrong.powermock.service;

import com.rongrong.powermock.dao.StudentDao;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
 * @author rongrong
 * @version 1.0
 * @date 2019/11/20 21:42
 */
@RunWith(PowerMockRunner.class)
@PrepareForTest(StudentNewService.class)
public class TestNewStudentService {

    /**
     * 傳統方式測試
     */
    @Test
    public void testGetStudentTotal() {
        StudentNewService studentNewService = new StudentNewService();
        int total = studentNewService.getTotal();
        assertEquals(total, 10);
    }

    /**
     * @desc測試有返回值類型 採用powermock進行測試獲取學生個數
     */
    @Test
    public void testGetStudentTotalWithPowerMock() {
        //先模擬一個假對象即studentdao方法中的局部變量
        StudentDao studentDao = PowerMockito.mock(StudentDao.class);
        try {
            //這句話我按照英文理解就是,我用無參的方式new了一個StudentDao對象
            PowerMockito.whenNew(StudentDao.class).withNoArguments().thenReturn(studentDao);
            //再模擬這個對象被調用時,我們默認假定返回10個證明調用成功
            PowerMockito.when(studentDao.getTotal()).thenReturn(10);
            //這裏就是service就不用再說了
            StudentNewService studentNewService = new StudentNewService();
            int total = studentNewService.getTotal();
            assertEquals(total, 10);
        } catch (Exception e) {
            fail("測試失敗了!!!");
            e.printStackTrace();
        }

    }

    /**
     * @desc測試的無返回值類型 採用powermock進行測試創建學生
     */
    @Test
    public void testCreateStudentWithPowerMock() {
        //先模擬一個假對象即studentdao方法中的局部變量
        StudentDao studentDao = PowerMockito.mock(StudentDao.class);
        try {
            //這句話我按照英文理解就是,我用無參的方式new了一個StudentDao對象
            PowerMockito.whenNew(StudentDao.class).withNoArguments().thenReturn(studentDao);
            Student student = new Student();
            //這句話註釋與否都能運行通過,也就是我只能判斷他是否被調用
            //PowerMockito.doNothing().when(studentDao).createStudent(student);
            //這裏就是service就不用再說了
            StudentNewService studentNewService = new StudentNewService();
            studentNewService.createStudent(student);
            Mockito.verify(studentDao).createStudent(student);
        } catch (Exception e) {
            fail("測試失敗了!!!");
            e.printStackTrace();
        }

    }

}

運行上面的測試用例,會發現第一個失敗,後面兩個都運行成功,即有返回值和無返回值類型的測試(void類型)。

 

 

注意:對於無返回值類型的測試,只能驗證其是否被調用,這裏還請注意。

 

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

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

java property 配置文件管理工具框架,避免寫入 property 亂序

property

是 java 實現的 property 框架。

特點

  • 優雅地進行屬性文件的讀取和更新

  • 寫入屬性文件后屬性不亂序

  • 靈活定義編碼信息

  • 使用 OO 的方式操作 property 文件

  • 支持多級對象引用

變更日誌

快速開始

環境依賴

Maven 3.x

Jdk 1.7+

Maven 引入依賴

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>property</artifactId>
    <version>0.0.4</version>
</dependency>

入門案例

讀取屬性

PropertyBs.getInstance("read.properties").get("hello");

read.properties 為文件路徑,hello 為存在的屬性值名稱。

讀取屬性指定默認值

final String value = PropertyBs.getInstance("read.properties")
                .getOrDefault("hello2", "default");

read.properties 為文件路徑,hello2 為不存在的屬性值名稱,default 為屬性不存在時返回的默認值。

設置屬性

PropertyBs.getInstance("writeAndFlush.properties").setAndFlush("hello", "world-set");

writeAndFlush.properties 為文件路徑,hello 為需要設置的屬性信息。

引導類方法概覽

序號 方法 說明
1 getInstance(propertyPath) 獲取指定屬性文件路徑的引導類實例
2 charset(charset) 指定文件編碼,默認為 UTF-8
3 get(key) 獲取 key 對應的屬性值
4 getOrDefault(key, defaultValue) 獲取 key 對應的屬性值,不存在則返回 defaultValue
5 set(key, value) 設置值(內存)
6 remove(key) 移除值(內存)
7 flush() 刷新內存變更到當前文件磁盤
9 flush(path) 刷新內存變更到指定文件磁盤
10 set(map) 設置 map 信息到內存
11 set(bean) 設置 bean 對象信息到內存
12 asMap() 返回內存中屬性信息,作為 Map 返回
13 asBean(bean) 返回內存中屬性信息到 bean 對象中

對象

簡介

我們希望操作 property 可以想操作對象一樣符合 OO 的思想。

設置值

User user = new User();
user.setName("hello");
user.setHobby("hobby");

final long time = 1574147668411L;
user.setBirthday(new Date(time));

PropertyBs propertyBs = PropertyBs.getInstance("setBean.properties")
        .set(user);

Assert.assertEquals("hobby", propertyBs.get("myHobby"));
Assert.assertEquals("1574147668411", propertyBs.get("birthday"));

讀取值

PropertyBs propertyBs = PropertyBs.getInstance("setBean.properties"
        .set("myHobby", "play")
        .set("birthday", "1574147668411");
User user = new User();
propertyBs.asBean(user);
Assert.assertEquals("play", user.getHobby());
Assert.assertEquals(1574147668411L, user.getBirthday().getTime());

對象定義

  • User.java
public class User {

    private String name;

    @PropertyField("myHobby")
    private String hobby;

    @PropertyField(converter = DateValueConverter.class)
    private Date birthday;

}

@PropertyField 註解

序號 屬性 默認值 說明
1 value 當前字段名稱 對應的 property 屬性名稱
2 converter 默認轉換實現 DefaultValueConverter 對當前字段進行屬性的轉換處理

自定義轉換類

  • DateValueConverter.java

這個就是我們針對 Date 類型,自己實現的處理類型。

實現如下:

public class DateValueConverter implements IValueConverter {

    @Override
    public Object fieldValue(String value, IFieldValueContext context) {
        return new Date(Long.parseLong(value));
    }

    @Override
    public String propertyValue(Object value, IPropertyValueContext context) {
        Date date = (Date)value;
        return date.getTime()+"";
    }

}

集合

說明

有時候一個屬性可能是集合或者數組,這裏暫時給出比較簡單的實現。

將字段值直接根據逗號分隔,作為屬性值。

測試案例

UserArrayCollection userArrayCollection = buildUser();
PropertyBs propertyBs = PropertyBs.getInstance("setBeanArrayCollection.properties")
        .set(userArrayCollection);
Assert.assertEquals("array,collection", propertyBs.get("alias"));
Assert.assertEquals("array,collection", propertyBs.get("hobbies"));

對象定義

  • UserArrayCollection.java
public class UserArrayCollection {

    private List<String> alias;

    private String[] hobbies;
}

暫時只支持 String 類型,不想做的過於複雜。

後期將考慮添加各種類型的支持。

多級對象

說明

有時候我們在一個對象中會引用其他對象,比如 對象 a 中包含對象 b。

這裏採用 a.b.c 這種方式作為屬性的 key, 更加符合使用的習慣。

測試案例

設置

Book book = new Book();
book.name("《海底兩萬里》").price("12.34");
UserEntry user = new UserEntry();
user.name("海倫").book(book).age("10");
PropertyBs propertyBs = PropertyBs.getInstance("setBeanEntry.properties")
        .set(user);
Assert.assertEquals("海倫", propertyBs.get("name"));
Assert.assertEquals("10", propertyBs.get("age"));
Assert.assertEquals("《海底兩萬里》", propertyBs.get("book.name"));
Assert.assertEquals("12.34", propertyBs.get("book.price"));

讀取

Map<String, String> map = new HashMap<>();
map.put("name", "海倫");
map.put("age", "10");
map.put("book.name", "《海底兩萬里》");
map.put("book.price", "12.34");
UserEntry userEntry = new UserEntry();
PropertyBs.getInstance("setBeanEntry.properties")
        .set(map)
        .asBean(userEntry);
Assert.assertEquals("UserEntry{name='海倫', age=10, book=Book{name='《海底兩萬里》', price=12.34}}",
        userEntry.toString());

對象定義

  • UserEntry.java
public class UserEntry {

    private String name;

    private String age;

    @PropertyEntry
    private Book book;

}
  • Book.java
public class Book {

    private String name;

    private String price;

}

@PropertyEntry 說明

@PropertyEntry 註解用來標識一個字段是否採用多級對象的方式表示。

這個註解只有一個屬性,就是 value(),可以用來給當前字段指定一個別稱,和 @PropertyField 別稱類似。

後續特性

  • 提供更多內置的類型轉換實現

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

【其他文章推薦】

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

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

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

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

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

※試算大陸海運運費!

Mysql備份還有這麼多套路,還不了解下?

邏輯備份和物理備份

邏輯備份

邏輯備份用於備份數據庫的結構(CREAET DATABASE、CREATE TABLE)和數據(INSERT),這種備份類型適合數據量小、跨SQL服務器、需要修改數據等場景。如mysqldump命令就是產生一個邏輯備份工具,使用mysqldump輸出的文件包含CREATE TABLEINSERT語句,能夠直接重建表內容和表結構。

使用邏輯備份有以下優勢和劣勢:

優勢
  • 可移植性高,SQL語句可直接適用於其他SQL服務器;
  • 在數據恢復之前可增加、修改數據;
  • 數據恢復粒度小可以是服務器、數據庫、表級別;
  • 使用文本格式,可讀性高;
劣勢
  • 備份時需要訪問mysql服務器,影響其他客戶端;
  • 需要將數據轉換成邏輯格式(SQL,CSV);
  • 如果命令運行在客戶端,mysql服務器還需要將數據發送給客戶端;
  • 因為輸出格式為文本文件,佔用空間較大;

物理備份

物理備份是包括存儲數據庫內容的目錄和文件的副本,這種類型的備份適用於需要在出現問題時快速恢復的大型重要數據庫。

優勢
  • 完整的Mysql文件和目錄備份,只需要複製文件不需要轉換,速度比邏輯備份更快;
  • 除了備份數據,還能備份配置文件和日誌文件;
  • 不需要運行Mysql服務器就可以完成備份;
  • 備份工具簡單使用cp、scp、tar命令即可完成備份;
劣勢
  • 可移植性不高,恢複數據只適用於相同或類似的機器上;
  • 為了保持數據庫文件的一致性,需要停機備份;
  • 恢復粒度不能按表或用戶恢復;

在線備份和離線備份

在線備份需要mysql服務器處理運行狀態,以便備份工具從mysql服務器中獲取數據。離線備份表示mysql服務器處理停止狀態。兩種備份形式也可以稱為“熱備份”和“冷備份“。

在線備份的主要特性

  • 備份不需要停機,對其他客戶端影響較小其他連接能夠正常訪問mysql服務器(依賴操作類型,如讀操作);
  • 備份需要加鎖,以免在備份期間對數據做出修改;

離線備份的主要特性

  • 備份期間服務器不可用;
  • 備份過程更簡單,不會受到客戶端的干擾;

邏輯備份(mysqldump使用)

mysqldump屬於邏輯備份命令,使用mysqldump備份的優勢是它非常方便和靈活,可以直接編輯輸出文件或者使用導入到其他的SQL服務器中去,但是它不能用作備份大量數據的快速解決方案,對於大數據量,即使備份花費的時候可以接受,但是恢複數據也可能會非常緩慢,因為執執行SQL語句會涉及磁盤I/O進行插入,創建索引等。mysqldump的使用方式非常簡單:

shell> mysqldump [options] db_name [tbl_name ...]
shell> mysqldump [options] --databases db_name ...
shell> mysqldump [options] --all-databases

使用mysqldump備份時要注意:數據庫的一致狀態,在執行mysqldump命令時要保證數據不會再發生變更,保持數據的一致性有二種方法:

  • 使Mysql服務器只讀
  • 使用事務加上隔離級別:REPEATABLE READ

使用REPEATABLE READ事務隔離級別執行mysqldump命令(使用事務保持數據庫的一致狀態):

mysqldump --master-data=2 \
 --flush-logs  \
 --single-transaction  \
 --all-databases > /backup/`date +%F-%H`-mysql-all.sql  

備份參數說明:

  • –master-data: 將二進制日誌文件的名稱和位置備份
  • –flush-logs: 開始備份之前刷新mysql服務器日誌文件
  • –single-transaction:開始備份之前設置事務隔離級別為REPEATABLE READ然後發送一個START TRANSACTION命令。
  • –all-databases:備份所有數據庫

物理備份(複製原始文件)

為了保證複製文件的完整性,備份原始文件最好是停止mysql服務器,複製原始文件備份由以下步驟完成:

  1. 停止mysql服務器
    $ mysqladmin shutdown
  2. 使用合適的工具複製原始數據文件
    $ tar cf /tmp/dbbackup.tar ./data
  3. 備份完成后,運行mysql服務器
    $ mysqld_safe

使用主從備份模式

使用mysqldumptar備份或多或少都會對業務產生影響,使用mysqldump備份需要對數據加鎖,加鎖就意味着其他客戶端操作受到限制。使用tar命令需要停止服務器直接導致數據庫服務器不可用,有沒有辦法能解決這兩種問題呢?答案是有的,就是使用主從備份模式。

在單機的基礎上增加一台Slave機器對Master機器的數據進行同步:

開始備份時對Slave進行備份,這樣即使Slave停機或對數據加鎖也不會影響業務的正常使用,如果公司有條件或業務非常重要可以選擇這種方案來備份數據。

歡迎關注微信公眾號《架構文摘》,高質量技術文章第一時間推送。

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

【其他文章推薦】

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

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

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

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

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

asp.net core 自定義 Policy 替換 AllowAnonymous 的行為

asp.net core 自定義 Policy 替換 AllowAnonymous 的行為

Intro

最近對我們的服務進行了改造,原本內部服務在內部可以匿名調用,現在增加了限制,通過 identity server 來管理 api 和 client,網關和需要訪問api的客戶端或api服務相互調用通過 client_credencial 的方式來調用,這樣一來我們可以清晰知道哪些 api 服務會被哪些 api/client 所調用,而且安全性來說更好。
為了保持後端服務的代碼更好的兼容性,希望能夠實現相同的代碼通過在 Startup 里不同的配置實現不同的 Authorization 邏輯,原來我們的服務的 Authorize 都是以 Authorize("policyName") 的形式來寫的,這樣一來我們只需要修改這個 Policy 的授權配置就可以了。對於 AllowAnonymous 就希望可以通過一種類似的方式來實現,通過自定義一個 Policy 來實現自己的邏輯

實現方式

將 action 上的 AllowAnonymous 替換為 Authorize("policyName"),在沒有設置 Authorize 的 controller 上增加 Authorize("policyName")

public class AllowAnonymousPolicyTransformer : IApplicationModelConvention
{
    private readonly string _policyName;

    public AllowAnonymousPolicyTransformer() : this("anonymous")
    {
    }

    public AllowAnonymousPolicyTransformer(string policyName) => _policyName = policyName;

    public void Apply(ApplicationModel application)
    {
        foreach (var controllerModel in application.Controllers)
        {
            if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AuthorizeFilter)))
            {
                foreach (var actionModel in controllerModel.Actions)
                {
                    if (actionModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                    {
                        var allowAnonymousFilter = actionModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                        actionModel.Filters.Remove(allowAnonymousFilter);
                        actionModel.Filters.Add(new AuthorizeFilter(_policyName));
                    }
                }
            }
            else
            {
                if (controllerModel.Filters.Any(_ => _.GetType() == typeof(AllowAnonymousFilter)))
                {
                    var allowAnonymousFilter = controllerModel.Filters.First(_ => _.GetType() == typeof(AllowAnonymousFilter));
                    controllerModel.Filters.Remove(allowAnonymousFilter);
                }
                controllerModel.Filters.Add(new AuthorizeFilter(_policyName));
            }
        }
    }
}

public static class MvcBuilderExtensions
{
    public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder)
    {
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer());
        });
        return builder;
    }

    public static IMvcBuilder AddAnonymousPolicyTransformer(this IMvcBuilder builder, string policyName)
    {
        builder.Services.Configure<MvcOptions>(options =>
        {
            options.Conventions.Insert(0, new AllowAnonymousPolicyTransformer(policyName));
        });
        return builder;
    }
}

controller 中的代碼:

[Route("api/[controller]")]
public class ValuesController : Controller
{
    private readonly ILogger _logger;

    public ValuesController(ILogger<ValuesController> logger)
    {
        _logger = logger;
    }

    // GET api/values
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        var msg = $"IsAuthenticated: {User.Identity.IsAuthenticated} ,UserName: {User.Identity.Name}";
        _logger.LogInformation(msg);
        return new string[] { msg };
    }

    // GET api/values/5
    [Authorize]
    [HttpGet("{id:int}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }
    // ...
}

Startup 中 ConfigureServices 配置:

var anonymousPolicyName = "anonymous";

services.AddAuthorization(options =>
{
    options.AddPolicy(anonymousPolicyName, builder => builder.RequireAssertion(context => context.User.Identity.IsAuthenticated));

    options.DefaultPolicy = new AuthorizationPolicyBuilder(HeaderAuthenticationDefaults.AuthenticationSchema)
        .RequireAuthenticatedUser()
        .RequireAssertion(context => context.User.GetUserId<int>() > 0)
        .Build();
});

services.AddMvc(options =>
    {
        options.Conventions.Add(new ApiControllerVersionConvention());
    })
    .AddAnonymousPolicyTransformer(anonymousPolicyName)
    ;

實現效果

訪問原來的匿名接口

userId 為0訪問原來的匿名接口

userId 大於0訪問原來的匿名接口

userId 為0訪問需要登錄的接口

userId 大於0訪問需要登錄的接口

More

注:按照上面的做法已經可以做到自定義 policy 代替 AllowAnonymous 的行為,但是原來返回的401,現在可能返回到就是 403 了

Reference

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

【其他文章推薦】

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

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

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

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

併發編程-硬件加持的CAS操作夠快么?

Talk is cheap

CAS(Compare And Swap),即比較並交換。是解決多線程并行情況下使用鎖造成性能損耗的一種機制,CAS操作包含三個操作數——內存位置(V)、預期原值(A)和新值(B)。如果內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新為新值。否則,處理器不做任何操作。無論位置V的值是否等於A, 都將返回V原有的值。

CAS的含義是”我認為V的值應該是A,如果是,那我將V的值更新為B,否則不修改並告訴V的值實際是多少“

Show you my code

在單線程環境中分別使用無鎖,加鎖以及cas進行十組5億次累加運算,然後打印出平均耗時。

 /**
 * cas對比加鎖測試
 *
 * @author Jann Lee
 * @date 2019-11-21 0:12
 **/
public class CasTest {

    @Test
    public void test() {
        long times = 500_000_000;
        // 記錄耗時
        List<Long> elapsedTime4NoLock = new ArrayList<>(10);
        List<Long> elapsedTime4Synchronized = new ArrayList<>(10);
        List<Long> elapsedTime4ReentrantLock = new ArrayList<>(10);
        List<Long> elapsedTime4Cas = new ArrayList<>(10);

        // 進行10組試驗
        for (int j = 0; j < 10; j++) {
            // 無鎖
            long startTime = System.currentTimeMillis();
            for (long i = 0; i < times; i++) {
            }
            long endTime = System.currentTimeMillis();
            elapsedTime4NoLock.add(endTime - startTime);

            // synchronized 關鍵字(隱式鎖)
            startTime = endTime;
            for (long i = 0; i < times; ) {
                i = addWithSynchronized(i);
            }
            endTime = System.currentTimeMillis();
            elapsedTime4Synchronized.add(endTime - startTime);

            // ReentrantLock 顯式鎖
            startTime = endTime;
            ReentrantLock lock = new ReentrantLock();
            for (long i = 0; i < times; ) {
                i = addWithReentrantLock(i, lock);
            }
            endTime = System.currentTimeMillis();
            elapsedTime4ReentrantLock.add(endTime - startTime);

            // cas(AtomicLong底層是用cas實現)
            startTime = endTime;
            AtomicLong atomicLong = new AtomicLong();
            while (atomicLong.getAndIncrement() < times) {
            }
            endTime = System.currentTimeMillis();
            elapsedTime4Cas.add(endTime - startTime);
        }

        System.out.println("無鎖計算耗時: " + average(elapsedTime4NoLock) + "ms");
        System.out.println("synchronized計算耗時: " + average(elapsedTime4Synchronized) + "ms");
        System.out.println("ReentrantLock計算耗時: " + average(elapsedTime4ReentrantLock) + "ms");
        System.out.println("cas計算耗時: " + average(elapsedTime4Cas) + "ms");

    }

    /**
     * synchronized加鎖
     */
    private synchronized long addWithSynchronized(long i) {
        i = i + 1;
        return i;
    }

    /**
     * ReentrantLock加鎖
     */
    private long addWithReentrantLock(long i, Lock lock) {
        lock.lock();
        i = i + 1;
        lock.unlock();
        return i;
    }

    /**
     * 計算平均耗時
     */
    private double average(Collection<Long> collection) {
        return collection.stream().mapToLong(i -> i).average().orElse(0);
    }
}

從案例中我們可能看出在單線程環境場景下cas的性能要高於鎖相關的操作。當然,在競爭比較激烈的情況下性能可能會有所下降,因為要不斷的重試和回退或者放棄操作,這也是CAS的一個缺點所在,因為這些重試,回退等操作通常用開發者來實現。

CAS的實現並非是簡單的代碼層面控制的,而是需要硬件的支持,因此在不同的體系架構之間執行的性能差異很大。但是一個很管用的經驗法則是:在大多數處理器上,在無競爭的鎖獲取和釋放的”快速代碼路徑“上的開銷,大約是CAS開銷的兩倍。

為何CAS如此優秀

硬件加持,現代大多數處理器都從硬件層面通過一些列指令實現CompareAndSwap(比較並交換)同步原語,進而使操作系統和JVM可以直接使用這些指令實現鎖和併發的數據結構。我們可以簡單認為,CAS是將比較和交換合成是一個原子操作

JVM對CAS的支持, 由於Java程序運行在JVM上,所以應對不同的硬件體系架構的處理則需要JVM來實現。在不支持CAS操作的硬件上,jvm將使用自旋鎖來實現。

CAS的ABA問題

cas操作讓我們減少了鎖帶來的性能損耗,同時也給我們帶來了新的麻煩-ABA問題。

在線程A讀取到x的值與執行CAS操作期間,線程B對x執行了兩次修改,x的值從100變成200,然後再從200變回100;而後在線程A執行CAS操作過程中並未發現x發生過變化,成功修改了x的值。由於x的值100 ->200->100,所以稱之為ABA的原因。

魔高一尺道高一丈,解決ABA的問題目前最常用的辦法就是給數據加上“版本號”,每次修改數據時同時改變版本號即可。

Q&A

在競爭比較激烈的情況下,CAS要進行回退,重試等操作才能得到正確的結果,那麼CAS一定比加鎖性能要高嗎?

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

【其他文章推薦】

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

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

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

小三通海運與一般國際貿易有何不同?

小三通快遞通關作業有哪些?

jwt 實踐應用以及特殊案例思考

JSON Web Token 是 出的一份標準,使用 JSON 來傳遞數據,用於判定用戶是否登錄狀態。

jwt 之前,使用 session 來做用戶認證。

以下代碼均使用 javascript 編寫。

  • 原文鏈接:

session

傳統判斷是否登錄的方式是使用 session + token

token 是指在客戶端使用 token 作為用戶狀態憑證,瀏覽器一般存儲在 localStorage 或者 cookie 中。

session 是指在服務器端使用 redis 或者 sql 類數據庫,存儲 user_id 以及 token 的鍵值對關係,基本工作原理如下。

在服務器端使用 sessions 存儲鍵值對

const sessions = {
  "ABCED1": 10086,
  "CDEFA0": 10010
}

每次客戶端請求帶權限數據時攜帶 token,在服務器端根據 token 與 sessions 獲取 user_id, 完成認證過程

function getUserIdByToken (token) {
  return sessions[token]
}

如果存儲在 cookie 中就是經常聽到的 session + cookie 的登錄方案。其實存儲在 cookielocalStorage 甚至 IndexedDB 或者 WebSQL 各有利弊,核心思想一致。

關於 cookie 以及 token 優缺點,在 中有討論。

如果不使用 cookie,可以採取 localStorage + Authorization 的方式進行認證,更加無狀態化

// http 的頭,每次請求權限接口時,需要攜帶 Authorization Header
const headers = {
  Authorization: `Bearer ${localStorage.get('token')}`
}

推薦一個前端的存儲庫 ,使用 IndexedDBWebSQL 以及 IndexedDB 做鍵值對存儲。

無狀態登錄

session 需要在數據庫中保持用戶及token對應信息,所以叫 有狀態

試想一下,如何在數據庫中不保持用戶狀態也可以登錄。

第一種方法: 前端直接傳 user_id 給服務端

缺點也特別特別明顯,容易被用戶篡改成任意 user_id,權限設置形同虛設。不過思路正確,接着往下走。

改進: 對 user_id 進行對稱加密

服務端對 user_id 進行對稱加密后,作為 token 返回客戶端,作為用戶狀態憑證。比上邊略微強點,但由於對稱加密,選擇合適的算法以及密鑰比較重要

改進: 對 user_id 不需要加密,只需要進行簽名,保證不被篡改

這便是 jwt 的思想:user_id,加密算法和簽名組成 token 一起存儲到客戶端,每當客戶端請求接口時攜帶 token,服務器根據 token 解析出加密算法與 user_id 來判斷簽名是否一致。

Json Web Token

jwt 根據 HeaderPayload 以及 Signature 三個部分由 . 拼接而成。

Header

Header 由非對稱加密算法和類型組成,如下

const header = {
  // 加密算法
  alg: 'HS256',
  type: 'jwt'
}

Payload

Payload 中由 以及需要通信的數據組成。這些數據字段也叫 Claim

Registered Claim 中比較重要的是 "exp" Claim 表示過期時間,在用戶登錄時會設置過期時間。

const payload = {
  // 表示 jwt 創建時間
  iat: 1532135735,

  // 表示 jwt 過期時間
  exp: 1532136735,

  // 用戶 id,用以通信
  user_id: 10086
}

Signature

SignatureHeaderPayload 以及 secretOrPrivateKey 計算而成。secretOrPrivateKey 作為敏感數據存儲在服務器端,可以考慮使用 vault secret 或者 k8s secret

對於 secretOrPrivateKey,如果加密算法採用 HMAC,則為字符串,如果採用 RSA 或者 ECDSA,則為 PrivateKey。

// 由 HMACSHA256 算法進行簽名,secret 不能外泄
const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret)

// jwt 由三部分拼接而成
const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign

從生成 jwt 規則可知客戶端可以解析出 payload,因此不要在 payload 中攜帶敏感數據,比如用戶密碼

校驗過程

在生成規則中可知,jwt 前兩部分是對 header 以及 payload 的 base64 編碼。

當服務器收到客戶端的 token 后,解析前兩部分得到 header 以及 payload,並使用 header 中的算法與 secretOrPrivateKey 進行簽名,判斷與 jwt 中攜帶的簽名是否一致。

帶個問題,如何判斷 token 過期?

應用

由上可知,jwt 並不對數據進行加密,而是對數據進行簽名,保證不被篡改。除了在登錄中可以用到,在進行郵箱校驗,圖形驗證碼和短信驗證碼時也可以用到。

圖形驗證碼

在登錄時,輸入密碼錯誤次數過多會出現圖形驗證碼。

圖形驗證碼的原理是給客戶端一個圖形,並且在服務器端保存與這個圖片配對的字符串,以前也大都通過 session 來實現。

可以把驗證碼配對的字符串作為 secret,進行無狀態校驗。

const jwt = require('jsonwebtoken')

// 假設驗證碼為字符驗證碼,字符為 ACDE,10分鐘失效
const token = jwt.sign({}, secrect + 'ACDE', { expiresIn: 60 * 10 })

const codeImage = getImageFromString('ACDE')

// 給前端的響應
const res = {
  // 驗證碼圖片的 token,從中可以校驗前端發送的驗證碼
  token,
  // 驗證碼圖片
  codeImage,
}

短信驗證碼與圖形驗證碼同理

郵箱校驗

現在網站在註冊成功後會進行郵箱校驗,具體做法是給郵箱發一個鏈接,用戶點開鏈接校驗成功。

// 把郵箱以及用戶id綁定在一起
const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 })

// 在此鏈接校驗驗證碼
const link = `https://example.com/code=${code}`

無狀態 VS 有狀態

關於無狀態和有狀態,在其它技術方向也有對比,比如 React 的 stateLess component 以及 stateful component,函數式編程中的副作用可以理解為狀態,http 也是一個無狀態協議,需要靠 header 以及 cookie 攜帶狀態。

在用戶認證這裏,有無狀態是指是否依賴外部數據存儲,如 mysql,redis 等。

案例

思考以下幾個關於登錄的問題如何使用 session 以及 jwt 實現,來更加清楚 jwt 的使用場景

當用戶註銷時,如何使該 token 失效

因為 jwt 無狀態,不保存用戶設備信息,沒法單純使用它完成以上問題,可以再利用數據庫保存一些狀態完成。

  • session: 只需要把 user_id 對應的 token 清掉即可
  • jwt: 使用 redis,維護一張黑名單,用戶註銷時把該 token 加入黑名單,過期時間與 jwt 的過期時間保持一致。

如何允許用戶只能在一個設備登錄,如微信

  • session: 使用 sql 類數據庫,對用戶數據庫表添加 token 字段並加索引,每次登陸重置 token 字段,每次請求需要權限接口時,根據 token 查找 user_id
  • jwt: 假使使用 sql 類數據庫,對用戶數據庫表添加 token 字段(不需要添加索引),每次登陸重置 token 字段,每次請求需要權限接口時,根據 jwt 獲取 user_id,根據 user_id 查用戶表獲取 token 判斷 token 是否一致。另外也可以使用計數器的方法,如下一個問題。

對於這個需求,session 稍微簡單些,畢竟 jwt 也需要依賴數據庫。

如何允許用戶只能在最近五個設備登錄,如諸多播放器

  • session: 使用 sql 類數據庫,創建 token 數據庫表,有 id, token, user_id 三個字段,user 與 token 表為 1:m 關係。每次登錄添加一行記錄。根據 token 獲取 user_id,再根據 user_id 獲取該用戶有多少設備登錄,超過 5 個,則刪除最小 id 一行。
  • jwt: 使用計數器,使用 sql 類數據庫,在用戶表中添加字段 count,默認值為 0,每次登錄 count 字段自增1,每次登錄創建的 jwt 的 Payload 中攜帶數據 current_count 為用戶的 count 值。每次請求權限接口時,根據 jwt 獲取 count 以及 current_count,根據 user_id 查用戶表獲取 count,判斷與 current_count 差值是否小於 5

對於這個需求,jwt 略簡單些,而使用 session 還需要多維護一張 token 表。

如何允許用戶只能在最近五個設備登錄,而且使某一用戶踢掉除現有設備外的其它所有設備,如諸多播放器

  • session: 在上一個問題的基礎上,刪掉該設備以外其它所有的token記錄。
  • jwt: 在上一個問題的基礎上,對 count + 5,並對該設備重新賦值為新的 count。

如何显示該用戶登錄設備列表 / 如何踢掉特定用戶

  • session: 在 token 表中新加列 device
  • jwt: 需要服務器端保持設備列表信息,做法與 session 一樣,使用 jwt 意義不大

總結

從以上問題得知,如果不需要控制登錄設備數量以及設備信息,無狀態的 jwt 是一個不錯的選擇。一旦涉及到了設備信息,就需要對 jwt 添加額外的狀態支持,增加了認證的複雜度,此時選用 session 是一個不錯的選擇。

jwt 不是萬能的,是否採用 jwt,需要根據業務需求來確定。

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

【其他文章推薦】

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

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

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

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

小三通物流營運型態?

※快速運回,大陸空運推薦?

使用Spring安全表達式控制系統功能訪問權限

一、SPEL表達式權限控制

spring security 3.0開始已經可以使用spring Expression表達式來控制授權,允許在表達式中使用複雜的布爾邏輯來控制訪問的權限。Spring Security可用表達式對象的基類是SecurityExpressionRoot。

表達式函數 描述
hasRole([role]) 用戶擁有指定的角色時返回true (Spring security默認會帶有ROLE_前綴),去除前綴參考
hasAnyRole([role1,role2]) 用戶擁有任意一個指定的角色時返回true
hasAuthority([authority]) 擁有某資源的訪問權限時返回true
hasAnyAuthority([auth1,auth2]) 擁有某些資源其中部分資源的訪問權限時返回true
permitAll 永遠返回true
denyAll 永遠返回false
anonymous 當前用戶是anonymous時返回true
rememberMe 當前用戶是rememberMe用戶返回true
authentication 當前登錄用戶的authentication對象
fullAuthenticated 當前用戶既不是anonymous也不是rememberMe用戶時返回true
hasIpAddress('192.168.1.0/24')) 請求發送的IP匹配時返回true

部分朋友可能會對Authority和Role有些混淆。Authority作為資源訪問權限可大可小,可以是某按鈕的訪問權限(如資源ID:biz1),也可以是某類用戶角色的訪問權限(如資源ID:ADMIN)。當Authority作為角色資源權限時,hasAuthority(’ROLE_ADMIN’)與hasRole(’ADMIN’)是一樣的效果。

二、SPEL在全局配置中的使用

我們可以通過繼承WebSecurityConfigurerAdapter,實現相關的配置方法,進行全局的安全配置(之前的章節已經講過) 。下面就為大家介紹一些如何在全局配置中使用SPEL表達式。

2.1.URL安全表達式

config.antMatchers("/system/*").access("hasAuthority('ADMIN') or hasAuthority('USER')")
      .anyRequest().authenticated();

這裏我們定義了應用/person/*URL的範圍,只有擁有ADMIN或者USER權限的用戶才能訪問這些person資源。

2.2.安全表達式中引用bean

這種方式,比較適合有複雜權限驗證邏輯的情況,當Spring Security提供的默認表達式方法無法滿足我們的需求的時候。首先我們定義一個權限驗證的RbacService。

@Component("rbacService")
@Slf4j
public class RbacService {
    //返回true表示驗證通過
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {
        //驗證邏輯代碼
        return true;
    }
    public boolean checkUserId(Authentication authentication, int id) {
        //驗證邏輯代碼
        return true;
    }
}

對於”/person/{id}”對應的資源的訪問,調用rbacService的bean的方法checkUserId進行權限驗證,傳遞參數為authentication對象和person的id。該id為PathVariable,以#開頭表示。

config.antMatchers("/person/{id}").access("@rbacService.checkUserId(authentication,#id)")
      .anyRequest().access("@rbacService.hasPermission(request,authentication)");

三、 Method表達式安全控制

如果我們想實現方法級別的安全配置,Spring Security提供了四種註解,分別是@PreAuthorize , @PreFilter , @PostAuthorize 和 @PostFilter

3.1.開啟方法級別註解的配置

在Spring安全配置代碼中,加上EnableGlobalMethodSecurity註解,開啟方法級別安全配置功能。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

3.2 使用PreAuthorize註解

@PreAuthorize 註解適合進入方法前的權限驗證。只有擁有ADMIN角色才能訪問findAll方法。

@PreAuthorize("hasRole('ADMIN')")
List<Person> findAll();

3.3 使用PostAuthorize註解

@PostAuthorize 在方法執行后再進行權限驗證,適合根據返回值結果進行權限驗證。Spring EL 提供返回對象能夠在表達式語言中獲取返回的對象returnObject。下文代碼只有返回值的name等於authentication對象的name才能正確返回,否則拋出異常。

@PostAuthorize("returnObject.name == authentication.name")
Person findOne(Integer id);

3.4 使用PreFilter註解

PreFilter 針對參數進行過濾,下文代碼錶示針對ids參數進行過濾,只有id為偶數才能訪問delete方法。

//當有多個對象是使用filterTarget進行標註
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> usernames) {

3.5 使用PostFilter 註解

PostFilter 針對返回結果進行過濾,特別適用於集合類返回值,過濾集合中不符合表達式的對象。

@PostFilter("filterObject.name == authentication.name")
List<Person> findAll();

期待您的關注

  • 博主最近新寫了一本書:
  • 本文轉載註明出處(必須帶連接,不能只轉文字):。

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

【其他文章推薦】

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

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

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

台灣寄大陸海運貨物規則及重量限制?

大陸寄台灣海運費用試算一覽表