一個非侵入的Go事務管理庫——如何使用

在文章”清晰架構(Clean Architecture)的Go微服務: 事物管理”中,我談到了如何在清晰架構中實現非侵入的事務管理。

它允許你把事務代碼與業務邏輯代碼分開,並且讓你在編寫業務邏輯時不必考慮事務。但它也有一些缺點。首先,它是整個清晰框架(Clean Architecture)的一部分,所以你不能拋開框架單獨使用它。其次,儘管它對業務邏輯沒有侵入,但它對框架有侵入。你需要修改框架的各個層,使其工作,這使他看起來比較複雜。 第三,正如我在文章中提到的,它存在一個依賴泄漏的漏洞。我雖然在文章中指出了解決方案,但它是一個比較大的改動,因此我當時就把它先放下了。現在,我終於有時間重新拾起它,並做了重要改進,結果令人非常滿意。

項目需求

以下是新的項目需求:

  1. 把事務管理代碼寫成一個單獨的第三方庫,這樣人們就可以在任何框架中使用它。
  2. 使其對框架無侵入,這意味着除了在用例層之外,在清晰架構的任何層中都沒有事務代碼。幾乎所有的事務代碼都在第三方的事務庫中。
  3. 修復以前設計中的依賴泄漏。

最終,我完成了所有的目標,結果出乎意料的好。我將寫兩篇文章來描述它,這篇文章討論如何使用這個第三方庫,下一篇文章討論事務管理庫的工作原理。當你要在應用程序里使用事務管理庫時,你的程序分成了兩部分。一部分是第三方庫的程序,另一部分是應用程序的代碼(它需要調用事務管理庫中的函數)。本文只講應用程序代碼。

如何在項目中使用事務管理庫

要想讓業務函數支持事務,需要做兩件事。首先,創建數據庫鏈接;其次,使用創建的數據庫鏈接運行SQL語句。我假設你在項目中使用了清晰架構。在這種情況下,“創建數據庫鏈接”會在應用程序容器((詳情參見”清晰架構(Clean Architecture)的Go微服務: 程序容器(Application Container)” )中完成,“運行SQL語句”會在業務邏輯(數據持久層)中完成。如果沒有使用清晰架構,你可能會使用某種非常類似的分層架構,結構還是一樣。如果你沒有使用任何框架或分層架構,那麼這兩種代碼可能會在一個地方。

你可能想知道它與沒有事務支持的代碼有什麼不同?幾乎沒有。不管有沒有事務支持,應用程序都要編寫相同的代碼,事務管理庫會在後端處理所有事情。

本文中的所有代碼都在”jfeng45/servicetmpl1″中,這是一個能自我進化的微服務框架,它提供了如何使用事務庫的例子。

創建數據庫鏈接

創建數據庫鏈接有兩種不同的方法,使用哪種方法取決於是否需要緩存數據庫鏈接。

獲取數據庫鏈接

下面是創建數據庫鏈接的代碼。它在”sqlFactory.go”文件中。因為清晰架構使用了工廠方法模式(factory method pattern),這裏的代碼是它的一部分。如果你不想使用工廠方法模式,也是一點問題都沒有的。因為數據庫鏈接在架構中是要被緩存的,所以框架代碼需要調用事務庫中的函數“BuildSqlDB()”,它首先檢查數據庫鏈接是否已經存在。如果沒有,則調用“factory.BuildSqlDB(&tdbc)”來創建一個。在此之前,它獲取需要的參數並將它們保存在“DatabaseConfig”中,“DatabaseConfig”也是在事務庫中定義的。之後它調用內部函數“buildGdbc()”生成合適的“gdbc.SqlGdbc”接口(要根據你是否需要事務)。最後,檢查如果數據庫鏈接不在緩存中,則把它放入緩存。

// implement Build method for SQL database
func (sf *sqlFactory) Build(c container.Container, dsc *config.DataStoreConfig) (DataStoreInterface, error) {
	logger.Log.Debug("sqlFactory")
	key := dsc.Code
	//if it is already in container, return
	if value, found := c.Get(key); found {
		logger.Log.Debug("found db in container for key:", key)
		sdb := value.(*sql.DB)
		return buildGdbc(sdb, dsc.Tx)
	}
	tdbc :=databaseConfig.DatabaseConfig{dsc.DriverName, dsc.UrlAddress, dsc.Tx}
	db, err := factory.BuildSqlDB(&tdbc)
	if err != nil {
		return nil, err
	}
	gdbc, err := buildGdbc(db, dsc.Tx)
	if err != nil {
		return nil, err
	}
	c.Put(key, gdbc)
	return gdbc, nil

}

下面是創建“SqlGdbc”接口的內部函數。”SqlGdbc”接口有兩種實現,一種是”SqlConnTx”,它支持事務。另一個是“SqlDBTx”,它不支持事務。

func buildGdbc(sdb *sql.DB,tx bool) (gdbc.SqlGdbc, error){
	var sdt gdbc.SqlGdbc
	if tx {
		tx, err := sdb.Begin()
		if err != nil {
			return nil, err
		}
		sdt = &gdbc.SqlConnTx{DB: tx}
		logger.Log.Debug("buildGdbc(), create TX:")
	} else {
		sdt = &gdbc.SqlDBTx{sdb}
		logger.Log.Debug("buildGdbc(), create DB:")
	}
	return sdt, nil
}

有一種更簡單的方法可以直接從事務庫中獲得”SqlGdbc”,函數是”factory.Build()”。但是當使用它時,你不能緩存數據庫鏈接,所以我沒有在框架中使用它。但是如果你不需要緩存數據庫鏈接,調用“factory.Build()”是一個更好的方法。

數據庫配置參數

數據庫配置參數是在第三方事務庫中定義的,但數據本身是保存在業務項目中。應用程序首先需要組裝參數並將它們傳遞給事務庫,以便得到合適的數據庫鏈接。在我們的框架中,包括數據庫參數在內的所有應用程序配置數據都保存在一個文件中。框架代碼將負責從文件中獲取數據。你如果不想將參數保存在文件中,直接將參數寫成程序中的硬編碼傳遞給事務庫更容易。

下面是配置文件“appConfigDev.yaml”中的部分代碼。對於數據庫來說,關鍵是如何讓事務庫知道需要的是事務鏈接還是非事務鏈接。它有多種辦法可以完成。例如,你可以為每個函數設置一個事務標誌,但這需要改動大量的代碼。我發現最簡單的方法是將所有支持事務的函數放在一個特殊的用例(Use Case)中。在下面的示例中,有三個用例:“registration”、“listUser”和“registrationTx”,其中只有“registrationTx”是支持事務的,因為它使用“*sqlConfigTx”作為“dataStoreConfig”。

useCaseConfig:
    registration:
        code: registration
        userDataConfig: &userDataConfig
            code: userData
            dataStoreConfig: *sqlConfig
    listUser:
        code: listUser
        userDataConfig: *userDataConfig
        cacheDataConfig: &cacheDataConfig
            code: cacheData
            dataStoreConfig: *cacheGrpcConfig
    registrationTx:
        code: registrationTx
        userDataConfig: &userDataConfigTx
            code: userData
            dataStoreConfig: *sqlConfigTx

下面是來自同一配置文件的部分代碼。可以看到,在“sqlConfigTx”中,有一個參數“tx:ture”,它表明它是支持事務的。

sqlConfig: &sqlConfig
    code: sqldb
    driverName: mysql
    urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8"
    dbName:
    tx:  false
sqlConfigTx: &sqlConfigTx
    code: sqldb
    driverName: mysql
    urlAddress: "root:@tcp(localhost:4333)/service_config?charset=utf8"
    dbName:
    tx: true

在業務邏輯中訪問數據庫

我們用一個業務函數做例子,來展示支持事務和不支持事務的兩種不同實現方式,這樣你就能看到他們的區別。

不支持事務的代碼

下面是“ModifyAndUnregister(user *model.User)”的非事務函數, 它在“registration.go”文件中。它是對業務函數“ModifyAndUnregister(ruc.UserDataInterface, user)”的一個簡單封裝,這個業務函數是被事務和非事務代碼共享的。

// The use case of ModifyAndUnregister without transaction
func (ruc *RegistrationUseCase) ModifyAndUnregister(user *model.User) error {
	return ModifyAndUnregister(ruc.UserDataInterface, user)
}

下面是共享的業務函數”ModifyAndUnregister(ruc.UserDataInterface, user)”的代碼,所有的業務邏輯都在這個函數里。

func ModifyAndUnregister(udi dataservice.UserDataInterface, user *model.User) error {
	//loggera.Log.Debug("ModifyAndUnregister")
	err := modifyUser(udi, user)
	if err != nil {
		return errors.Wrap(err, "")
	}
	err = unregisterUser(udi, user.Name)
	if err != nil {
		return errors.Wrap(err, "")
	}
	return nil
}
支持事務的代碼

下面是相同的業務函數,但支持事務的代碼。它在“registrationTx.go”文件中。你要做全部工作就是在“EnableTx()”中調用業務函數。

// The use case of ModifyAndUnregister with transaction
func (rtuc *RegistrationTxUseCase) ModifyAndUnregisterWithTx(user *model.User) error {

	udi := rtuc.UserDataInterface
	return udi.EnableTx(func() error {
		// wrap the business function inside the TxEnd function
		return ModifyAndUnregister(udi, user)
	})
}

下面是函數“EnableTx()”的實現代碼(它在文件“userDataSql.go”中)。 這個代碼是在持久層中。它的實現非常簡單,只需調用事務庫中的函數“TxEnd()”。

func (uds *UserDataSql) EnableTx(txFunc func() error) error {
	return uds.DB.TxEnd(txFunc)
}

以上就是為業務函數添加事務支持所需要做的全部工作,其餘代碼均在事務庫中。

如果你想了解更多關於事務庫本身的信息,請閱讀“一個非侵入的Go事務管理庫——工作原理”,

結論:

我對去年寫的事務管理代碼進行了升級,使其成為一個非侵入式的輕量級事務管理庫。當你使用它時,只需要在應用程序中額外增加兩三行代碼就能搞定,所有其他代碼都放在了事務管理庫。它很好地將業務代碼與數據庫事務代碼隔離開來,這樣你的業務代碼里就只有純粹的業務邏輯。它是一個庫而不是框架,所以不論你使用任何框架都可以使用它。

源代碼:

完整的源碼: “jfeng45/servicetmpl1”

索引:

1 “清晰架構(Clean Architecture)的Go微服務: 事物管理”

2 “清晰架構(Clean Architecture)的Go微服務: 程序容器(Application Container)”

3 “一個非侵入的Go事務管理庫——工作原理”

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

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

※台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

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

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

※回頭車貨運收費標準

數學-高精度

1.Integer Inquiry

題目鏈接:http://acm.hdu.edu.cn/showproblem.php?pid=1047

解法:一道求大數之和,我們利用java里的BigInteger就可以輕鬆的解決,不過需要注意下格式的問題,每輸出一個就要空一行,最後一個不用

代碼:

import java.math.BigInteger;
import java.util.Scanner;
public class Main {
    public static void main(String[] arg)
    {
        Scanner in=new Scanner(System.in);
        int t;
        while(in.hasNext())
        {
            t=in.nextInt();
            while(t-->0)
            {
                BigInteger b=new BigInteger("0");
                while(in.hasNextBigInteger())
                {
                    BigInteger c=new BigInteger("0");
                    c=in.nextBigInteger();
                    if(!c.equals(BigInteger.ZERO))
                    {
                        b=b.add(c);
                    }
                    else
                    {
                        System.out.println(b);
                        if(t!=0)
                            System.out.println();
                        break;
                    }
                }
            }
        }
    }
}

 

2.Exponentiation

 題目:https://vjudge.net/problem/POJ-1001/origin

解法:這道是一個浮點數的次冪求解,通過java里的BigDecimal的pow方法,即可完成對應的求解。

對於輸出有特殊要求,小數點后捨去無意義的0,用到stripTrailingZeros()方法去除所有尾部的0,然後再用toPlainString()將BigDecimal轉化為String

之後,在比對轉化的字符串的第一個字符是否為“0”,如果為0,則去掉它只保留小數點之後的數,利用substring(1),截取字符串

代碼:

import java.math.BigDecimal;
import java.util.Scanner;

public class Main {
    public static void main(String[] arg)
    {
        Scanner in=new Scanner(System.in);
        while(in.hasNext())
        {
            BigDecimal a=in.nextBigDecimal();
            int k=in.nextInt();
            a=a.pow(k);
            String str=a.stripTrailingZeros().toPlainString();
            if(str.charAt(0)=='0')
                System.out.println(str.substring(1));
            else
                System.out.println(str);
        }
    }
}

 

3.How many Fibs?

題目:https://vjudge.net/problem/POJ-2413/origin

思路:這是一道求在一定的範圍內,包含的斐波那契數的個數。由於範圍是10的100次方,因此我們設置大整數數組的長度為10000,足以包括。

代碼:

import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    static BigInteger a[]=new BigInteger[10000];
    public static void main(String[] arg)
    {
        Scanner in=new Scanner(System.in);
        for(int i=0;i<10000;i++)
            a[i]=BigInteger.ZERO;
        a[0]=BigInteger.valueOf(1);
        a[1]=BigInteger.valueOf(2);
        for(int i=2;i<10000;i++)
            a[i]=a[i].add(a[i-1].add(a[i-2]));
        BigInteger b,c;
        while(true)
        {
            b=in.nextBigInteger();
            c=in.nextBigInteger();
            if(b.equals(BigInteger.ZERO)&&c.equals(BigInteger.ZERO))
                break;
            int res=0;
            for(int i=0;i<10000;i++)
            {
                if((a[i].compareTo(b)==1||a[i].compareTo(b)==0)&&(a[i].compareTo(c)==-1||a[i].compareTo(c)==0))
                    res++;
                if(a[i].compareTo(c)==1)
                    break;
            }
            System.out.println(res);
        }
    }
}

 

4.Segment

 題目:http://acm.hdu.edu.cn/showproblem.php?pid=5666

解法:這是一道求在一個三角形區域內的整數點的個數,不包含在線上的點。通過遞推分析,找規律後知道,個數為:(q-1)*(q-2)/2%p

由於q很大,所以我們需要用到大整數來進行求解。

代碼:

import java.math.BigInteger;
import java.util.Scanner;

public class Main{
    public static void main(String[] arg)
    {
        Scanner in=new Scanner(System.in);
        int T=in.nextInt();
        while(T-->0)
        {
            BigInteger q=in.nextBigInteger();
            BigInteger p=in.nextBigInteger();
            BigInteger f=q.subtract(BigInteger.valueOf(2));
            BigInteger res=q.subtract(BigInteger.ONE).multiply(f).divide(BigInteger.valueOf(2));
            res=res.mod(p);
            System.out.println(res);
        }
    }
}

 

5.Problem B

題目:https://vjudge.net/problem/HDU-5686/origin

解法:這應該是百度的某道題,模擬幾組數據后發現竟然是一個斐波那契數列,因此就很簡單了,N<=200的因此普通的longlong滿足不了,我們需要使用BigInteger

代碼:

import java.math.BigInteger;
import java.util.Scanner;

public class Main {
    public static void main(String[] arg)
    {
        Scanner in=new Scanner(System.in);
        BigInteger a[]=new BigInteger[10000];
        for(int i=0;i<10000;i++)
            a[i]=BigInteger.ZERO;
        a[0]=BigInteger.valueOf(1);
        a[1]=BigInteger.valueOf(1);
        for(int i=2;i<10000;i++)
            a[i]=a[i].add(a[i-1].add(a[i-2]));
        while(in.hasNext())
        {
            int k=in.nextInt();
            if(k>=1&&k<=200)
                System.out.println(a[k]);
        }
    }
}

 

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

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

※超省錢租車方案

QT槽函數獲取信號發送對象

Qt 在槽函數中獲取信號發送對象

Qt中提供了一個函數 qobject_cast(QObject *object),可以通過這個函數判斷信號發出對象

Qt 幫助文檔的解釋:
Returns the given object cast to type T if the object is of type T (or of a subclass); otherwise returns nullptr. If object is nullptr then it will also return nullptr.
The class T must inherit (directly or indirectly) QObject and be declared with the Q_OBJECT macro.
A class is considered to inherit itself.
The qobject_cast() function behaves similarly to the standard C++ dynamic_cast(), with the advantages that it doesn’t require RTTI support and it works across dynamic library boundaries.

  • 簡單來說 就是使用這個函數會得到你轉型的的一個實例,但是這個類必須繼承自QObject 或者其子類並且聲明Q_OBJECT 這個宏
  • QT 幫助文檔中一個Example
    QObject *obj = new QTimer; 
    QTimer *timer = qobject_cast<QTimer *>(obj);
    

這裏順便記錄一下RTTI 與RAII,RAII 通常應用於對象資源管理,RTTI 可以動態判斷對象類型,但使用RTTI會增加程序運行時間,這裏簡單記錄區分一下 。

  1. RTTI : Run-time type information
    #include <iostream>
    #include <typeinfo>
    
    class Base {
    public:
        virtual ~Base() = default;
    };
    
    class Derived : public Base {};
    
    int main() {
        Base base;
        Derived derived;
        Base* ptr = &derived;
        Base& ref = derived;
        std::cout << typeid(base).name()<< std::endl;  // class Base 
        std::cout << typeid(derived).name()<< std::endl;  // class Derived
        std::cout << typeid(ptr).name()<< std::endl;  // class Base *
        std::cout << typeid(*ptr).name() << std::endl;  //class Derived           
        std::cout << typeid(ref).name() << std::endl;  //class Derived
    
    }
    
  2. RAII : Resource Acquisition Is Initialization
    代碼來源 https://en.cppreference.com/w/cpp/language/raii
    std::mutex m;
    
    void bad() 
    {
        m.lock();                    // acquire the mutex
        f();                         // if f() throws an exception, the mutex is never released
        if(!everything_ok()) return; // early return, the mutex is never released
        m.unlock();                  // if bad() reaches this statement, the mutex is released
    }
    
    void good()
    {
        std::lock_guard<std::mutex> lk(m); // RAII class: mutex acquisition is initialization
        f();                               // if f() throws an exception, the mutex is released
        if(!everything_ok()) return;       // early return, the mutex is released
    }
    

下面是QT通過qobject_cast獲取信號發送對象的一個Demo,通過Qt Desinger 繪製兩個按鈕和一個文本框,將兩個按鈕的點擊事件連接到同一個槽函數,在槽函數裏面判斷信號的發送者並作出不同的響應
主要的代碼如下:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public slots:
    void onButtonClicked();

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H


#include "mainwindow.h"
#include "ui_mainwindow.h"
#include<QDebug>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(ui->leftButton,&QPushButton::clicked,this,&MainWindow::onButtonClicked);
    connect(ui->rightButton,&QPushButton::clicked,this,&MainWindow::onButtonClicked);

}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onButtonClicked()
{
    QPushButton *button =  qobject_cast<QPushButton *>(sender());

    ui->textLabel->setText(button->text());

    if(ui->leftButton == button)
    {
        qDebug()<<"left Button clicked";
        ui->textLabel->setStyleSheet("background-color:yellow");
        button->setStyleSheet("background-color:yellow");

    }
    else
    {
        ui->textLabel->setStyleSheet("background-color:green");
        button->setStyleSheet("background-color:green");
    }

}


完整的代碼已上傳Github

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

【其他文章推薦】

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

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

※網頁設計最專業,超強功能平台可客製化

C/C++語言的學習方向

這個世界上有太多的坑,如果我們不具備查閱資料的能力和對現實世界的思考能力,入坑是大概率的事情。

C/C++語言能做什麼?C/C++程序員在做什麼?企業需要什麼樣的C/C++程序員?對初學者來說,要搞清楚這些問題並不容易。

一個农民種菜之前,會考慮菜的行情(包括銷量和價錢),如果您打算學習C/C++語言,也應該先了解C/C++程序員的行情。

了解菜的行情要去菜市場,了解C/C++程序員的行情去人才市場,中國最大的IT人才市是前程無憂。

我們先來查一些資料。

一、中國編程語言的排名情況

登錄前程無憂的官網,分別輸入java、C++、python、javascript、.net、C#、PHP、SQL、IOS關鍵字,城市選擇北京、上海、廣州和深圳,查詢到的招聘職位分別是31032、22842、19274、14091、6956、8018、5284、21302、6014個。(這是2020年6月查到的數據,不同時期的查詢結果略有不同)

數據显示企業的JAVA和C++職位比例約6:4,培訓機構的主流課程是JAVA和Python等,進一步導致了C/C++程序員的缺乏。

培訓機構不願意開設C/C++課程,C/C++語言的教學對老師的能力要求非常高,普通講師難以信任。

編程語言的選擇是人生大事,必須慎重,誰都不希望在工作幾年後被迫從一種編程語言轉向另一種編程語言。

JAVA和C/C++是程序員可選的兩個方向(其它編程語言根本不在考慮範圍),無所謂哪一種更好,我個人偏向C/C++,因為JAVA程序員的工作太累了。

更多的人選擇學習JAVA,在較短的時間內就可以上手,從簡單的WEB頁面做起,所見即所得,容易有成就感,但是C/C++不一樣,學生在學完基礎知識后還是不知道它能做什麼,離企業需求仍然差得很遠,堅持下去的人很少,“從入門到放棄”是多數人的無奈的選擇。

二、企業對C/C++程序員的技能要求

從前程無憂的招聘數據看,中國企業對C/C++的技能要求主要有網絡通信、硬件驅動(嵌入式)、數據庫、視頻音頻、網絡遊戲、桌面圖形、機器學習、大數據、物聯網,如下圖。

以上數據可以從前程無憂的官網查詢,分別輸入“C++ 網絡”、“C++ 嵌入式”、“C++ 數據庫”、“C++ 遊戲”等關鍵字,範圍選北京、上海、廣州和深圳。

值得注意的是,近年來被媒體反覆炒作的人工智能、大數據、物聯網、區塊鏈等高大上的概念,企業需求其實很少,現在股票也不抄這個概念了。

輸入“ C++ Linux”關鍵字搜索,結果有9767條記錄,輸入“C++ Windows”關鍵字搜索,只有2500條記錄。這說明windows作為服務器的操作系統,正在被淘汰中。

目前,大部分的學校和培訓機構讓學生windows平台下學習C/C++語言,而企業要求的卻是linux。

我們應該學習主流技術,不能成為非主流,否則有被淘汰的風險。

三、數據比石油更珍貴

在我們的生活中,數據無處不在,但是,常人對它不敏感。

人口戶藉數據在公安局。

駕照數據和車輛數據也在公安局的交警支隊。

醫院保存了每個病人的基本資料和就診數據。每個產婦在醫院生了小孩子,計劃生育部門也會知道,它們與公安局和醫院有聯網交換數據。

銀行有開戶數據,轉帳記錄。

證券公司有行情數據、股東基本數據、持股數據和交易日誌。

電信公司有開戶數據、通話清單、帳單。

電力局有居民的基本數據和用電帳單。

火車票、機票都是數據。

騰訊公司有QQ帳戶數據、微信帳戶數據、聊天日誌、轉帳日誌等。

阿里公司有淘寶帳戶數據,商戶數據,商品數據、交易日誌等。

煤氣公司、自來水公司……

一個人的身高、體重、學歷、年齡是數據。

圖片、視頻、音頻、文章也是數據。

……

四、C/C++程序員在做什麼

用C/C++做桌面程序的比例約11%,其它的C/C++程序都運行在後台Linux服務器上,普通用戶看不見。

在很多培訓機構的教程中,把貪吃蛇、俄羅斯方塊、打地鼠、球球大作戰等程序當成項目實戰,太過兒戲,這些程序的源代碼網上到處都是,真正的程序員不玩這個。

真正的C/C++程序員在工作,掌握的是謀生的技能,不是在玩過家家。

我們每天都在用微信軟件聊天、付款、轉帳等,對普通用戶來說,只看到微信軟件的操作界面,不清楚有後台系統的存在,以轉帳功能為例。

1)付款人在手機微信中輸入轉帳金額和支付密碼;

2)手機微信軟件把轉帳金額和密碼發送給騰訊公司的微信服務器;

3)微信服務器核對用戶的密碼和轉帳金額,向中國銀聯發起轉帳請求,中國銀聯把轉帳請求轉發給付款方的開戶行銀行,開戶銀行從付款人的帳戶中扣除金額。

4)微信服務器把扣款成功信息返回給手機微信軟件,显示轉帳成功。

5)微信服務器向收款人的手機微信軟件發送收錢提醒,收款人點擊收款后,微信服務器把金額存入收款人的微信帳戶中。

在上述轉帳流程中,蘋果手機的微信軟件採用Objective-C語言開發,其它品牌手機的微信軟件採用JAVA開發;微信服務器、中國銀聯和開戶銀行都是非常龐大的系統,系統之間進行了多次數據傳輸,對數據庫進行了查詢、修改和插入操作,都是採用C/C++語言開發,系統運行在UNIX系統上,效率極高、穩定性極強,工程量巨大,是精英程序員們的心血之作。

對騰訊的開發團隊來說,手機上安裝的微信APP軟件,開發的工作量不會超過整個微信項目的5%。

五、總結一下

1)網絡通信和數據處理以C/C++語言為主,數據輸入、數據展示以java為主。

2)作為服務器的操作系統,必選Linux,Windows已被淘汰,以VC、QT等作為C程序的開發環境極不明智。

3)網絡通信是C/C++程序員的必備技能。

4)數據處理是C/C++程序員的必備技能。

5)嵌入式開發的職位比較多,技術上偏硬件(通信、电子等專業),軟件為輔,無硬件基礎者要慎重考慮。

6)放棄幻想,不要追逐那些高大上的潮流(區塊鏈、大數據、人工智能等),也不要聽那些從入門到精通的鬼話,踏實一點,先成為一個普通程序員,這個世界上從來就沒有捷徑。

7)要學會自己查資料,www.51job.com上的信息量非常大,不要視而不見,不能被別人隨便忽悠。

六、關於視頻教程

2019年五月,我計劃錄製C/C++語言基礎知識的視頻課程,打算採用知名的書藉作為教材,在翻閱多本書藉之後,我非常的失望。作為一個骨灰程序員,二十年來,我非常清楚如何成為一個合格的C/C++程序員,包括學習方向、企業的需求和必備技能等。書店裡能買到的C/C++
書藉,絕大部分是教授和講師編寫出來的,普遍存在三個方面的問題:

1)只介紹知識點,不結合應用的場景,示例代碼和課後作業與實際項目開發嚴重脫節;

2)重點內容不突出,C/C++的知識點非常多,很多內容已經過時,在工作中極少使用,這些內容增加了學習的負擔;

3)紙上談編程,不注重培養程序員的感覺。

從職業程序員的角度出發,我把C/C++語言的基礎知識內容寫成文章,在C語言技術網發布,本教程有以下特點:

1)以Linux平台為學習和開發環境,Linux是C/C++程序員的主流工作平台;

2)斷章取義,重點突出實際開發中常用的內容,略過不常用的知識;

3)設計了大量的課後作業,這些作業與實際開發內容息息相關,把培養程序員的感覺放在首位;

4)本教程的目標是為職業C/C++程序員打基礎,不合適在校大學生的畢業考試和計算機等級考試。

七、獲取視頻教程

百萬年薪程序員錄製,《C/C++高性能服務開發基礎》視頻教程已在CSDN學院發布,優惠價12元,地址如下:
https://edu.csdn.net/course/play/29402

八、版權聲明

C語言技術網原創文章,轉載請說明文章的來源、作者和原文的鏈接。

來源:C語言技術網(www.freecplus.net)

作者:碼農有道

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

【其他文章推薦】

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

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

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

※南投搬家公司費用需注意的眉眉角角,別等搬了再說!

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

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

SSH免密登錄詳解

SSH免密登錄詳解

SSH(Security Shell)安全外殼協議,是較為可靠的,專為遠程登錄會話和其他網絡服務提供安全保證的協議。

​ 對於傳統的網絡服務程序(例如,FTP,Telnet等)來說,其本質上並不是安全的,主要原因在於,這些網絡應用程序在網絡上都是直接使用明文傳輸口令和數據的,對於別有用心的人來說,這些口令和數據是很容易被截獲的。另外,這些網絡服務程序的安全驗證方式也是存在弱點的,非常容易受到中間人(Man-In-The-Middle)這種方式的攻擊,簡而言之,就是中間人冒充真正的服務器接收你傳輸的數據,然後,再將數據轉發給真正的服務器,通過這種方式中間人就可以神不知鬼不覺地拿到你所有數據。

​ 通過使用SSH,則可以將所有傳輸的數據及口令進行加密,從而防止中間人攻擊,還可以防止DNS和IP欺騙,另外,使用SSH還有加快傳輸速度的好處,原因在於,SSH是可以對數據進行壓縮的。

SSH安裝詳解

SSH是安全外殼協議,而open-ssh則是SSH的開源實現,CentOS通常是默認安裝了open-ssh的。

整個SSH服務是包含SSH服務端(openssh-server)和SSH客戶端(openssh-clients)的,常用的ssh命令就是客戶端一部分。

SSH服務端與SSH客戶端之間的關係:節點A想要控制節點B,則節點A上需要安裝SSH客戶端,而節點B上需要安裝相應的SSH服務端,這樣,節點A才能向節點B發送控制命令和數據。

# 安裝openssh-server
[root@cos1 ~]# yum install -y openssh-server
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.ustc.edu.cn
 * extras: mirrors.ustc.edu.cn
 * updates: mirrors.ustc.edu.cn
Package openssh-server-7.4p1-21.el7.x86_64 already installed and latest version
# 啟動openssh-server
[root@cos1 ~]# systemctl start sshd.service
# 安裝openssh-clients
[root@cos1 ~]# yum install -y openssh-clients
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.ustc.edu.cn
 * extras: mirrors.ustc.edu.cn
 * updates: mirrors.ustc.edu.cn
Package openssh-clients-7.4p1-21.el7.x86_64 already installed and latest version

openssh服務的主配置文件路徑:/etc/ssh/sshd_config。

# SSH-Server常見的配置項
# 如果SSH連接提示端口不可用,可以在改文件中查看SSH端口,端口沒問題,就要考慮SSH服務是否正常啟動了
# SSH客戶端與服務端連接的默認端口號
Port 22

默認情況下,SSH服務提供了以下兩個非常有用的功能:

1、類似Telnet遠程連接服務器功能,即SSH服務(安全可靠的遠程登錄會話服務)

# 遠程連接服務
ssh user@hostname/ip

2、類似FTP的sftp-server服務,藉助SSH協議來傳輸數據,提供更安全的SFTP服務(vsftp, proftp).

# 文件拷貝服務
scp files user@hostname:/path/

SSH免密登錄配置

​ 對於分佈式環境組件(例如,Hadoop,HBase等)來說,組件的主/從節點通常需要遠程登錄到集群中其他節點並執行Shell命令,如果不使用SSH,則集群安全就無法保證,使用SSH服務的話,又需要用戶輸入目標服務器的免密,而主/從節點何時訪問其他節點是不可控的,而且,頻繁的需要用戶輸入密碼也是不可行的,這時候,免密登錄就非常重要的。

主機A通過SSH實現免密登錄主機B所需的配置過程大致可以分為以下兩步:

1、主機A在本地通過加密算法生成公鑰和私鑰

通過rsa算法生成公鑰和私鑰key對,默認情況下,身份驗證信息(也就是私鑰)保存在/user/.ssh/id_rsa文件下,而公鑰信息則保存在/user/.ssh/id_rsa.pub文件中。

[root@cos1 .ssh]# pwd
/root/.ssh
[root@cos1 .ssh]# ll
total 8
-rw-------. 1 root root 1679 Jun 22 00:04 id_rsa
-rw-r--r--. 1 root root  391 Jun 22 00:04 id_rsa.pub

2、將主機A的公鑰拷貝到主機B的授權文件(authorized_keys)中

# 通過ssh-copy-id命令將本地服務器公鑰上傳到指定服務器
[root@cos1 ~]# ssh-copy-id root@cos
... ... ... ... ... ... 
Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@cos'"
and check to make sure that only the key(s) you wanted were added.
# 現在,可以通過ssh root@cos訪問主機cos了
[root@cos1 ~]# ssh root@cos
Last login: Thu Jun 18 06:38:25 2020 from 192.168.58.1
[root@cos ~]# 

為了安全起見,通常將保存具體哪些主機可以免密訪問當前主機的授權文件authorized_keys文件設置為及root具有查看和修改的權限,用戶組及其他人無權訪問。

通過ssh-copy-id將主機A的公鑰拷貝到主機B之後,本質上,是在主機B的授權文件中添加了主機A的公鑰。

[root@cos .ssh]# ll
total 8
-rw-------. 1 root root 781 Jun 22 00:16 authorized_keys
-rw-r--r--. 1 root root 176 Jun 18 07:34 known_hosts

注意:

1、免密登錄,是用戶對用戶的,切換為其他用戶時,仍需要輸入密碼

2、免密登錄是單向的,也就是說,主機A將公鑰拷貝到主機B后,主機A可以免密登錄主機A,而主機B登錄主機A時仍然需要輸入密碼

圖解SSH免密登錄原理

通過上面一頓操作,主機A就可以免密登錄到主機A了,感覺有點神奇,那麼,免密登錄到底是怎麼實現的呢?也就是說,SSH協議又到底是什麼樣子呢?且看下圖分解~

上圖便是整個SSH免密登錄的配置及原理圖:

1、ServerA生成公鑰並上傳到ServerB上

2、Step 1: ServerA發送鏈接請求到ServerB

3、Step 2: ServerB在authorized_key中檢索ServerA的公鑰,隨機生成字符串,隨後利用公鑰對字符串進行加密,併發送給ServerA

4、Step 3: ServerA接收到密文後,利用私鑰進行解密,並將內容以明文的形式發送給ServerB

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

【其他文章推薦】

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

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

2020年年中總結

一、前言

  今年或許是因為疫情的原因吧,感覺時間過的嗖嗖的特別快,不知不覺間2020年已經過去了二分之一,如果把一整年的時間比作我們手機的電量的話,意味着只剩下百分之五十了,不知大家是否心理會有恐慌,在自己手機電量只剩百分之五十的時候,大家是否會找電源來進行充電呢?至少我會,不知大家是否還記得年初定下的目標,是否完成了年度任務的百分之五十?
  六月初的時候,我還在考慮是否要寫一個年中總結,來對我近半年的工作做一個總結,工作中發生的一件事情,讓我覺得還是應該寫這個一篇文章,在每年歲末年初的時候,我都會牽頭來整理我們下一年的年度計劃,然後把年度計劃分解到每個月,在制定月計劃的時候,結合年度計劃及當前實際情況,制定當月的月度計劃,每月統計所有人員月度工作完成情況,以此作為年底考核的一個指標,考核的目的是為了更好的完成工作,既然是為了完成年度目標,那麼在每月初、每季度初大家在制定計劃的時候,就需要看看計劃是否合理、是否合適必要、任務的輕重緩急安排的是否合適,在每月月末、每季度末的時候,對大家完成情況進行統計,對於延期的要了解原因,是工作是安排的不合理,還是其他主管原因造成的,如果延期,應該採取什麼補救措施等,是否要修訂年度工作計劃或者採取措施,在接下來的工作中進行補救;而不是放任不管,到年底統計大家年度工作完成情況,直接進行考核。這個也是我為什麼要寫一下年中總結的原因。

二、2020年上半年總結

  下面我將從工作、學習、生活三個方面對自己上半年做一個總結,我把2020年的年度目標分別歸類到學習與生活中,2020年目標詳見文章2019年總結與2020年展望,首先來說一下工作吧,如果沒有今年的疫情,按照年初的安排,上半年應該是大量的外出調研及廠商交流,4月低5月初就會啟動今年第一批信息化項目建設,由於疫情原因,6月底第一批信息化項目才會啟動進行建設;在接下來的半年中會有兩批信息化項目啟動建設;項目建設壓力還是挺重的;工作中的另外一部分就是自己的日常工作安排,這部分工作比去年有所進步,但是還有較大的進步空間,主要的問題就是工作的先後順序、輕重緩急安排的不合理,對自己的工作應該有側重點,而不是全部平均用力,另外一個就是工作中總結寫的比較少,這部分在下半年需要重點關注了,同時接下來我也會單獨寫一篇文章,對自己上邊工作的心得體會做一個總結,對上半年事務性工作部分進行梳理;其次來說說學習,截止目前,讀書六本,分別是阮一峰的《前方的路》、《來世界的倖存者》、《中台戰略》、《數據中台》、《麥肯錫教我的邏輯思維》、《團隊管理》,整理讀書筆記2篇,分別是前方的路、未來時間的倖存者;按照年度制定的計劃,平均每月最少寫兩篇文章,截止目前,共寫了八篇,還差四篇;項目管理師證書由於疫情原因,上半年的考試取消了,合併到下半年,11月7日、11月8日舉行考試,這個從下半年8月開始準備,一次性考過,不給自己找任何不努力的接口,鯤鵬認證開發工程師由於自己對未來職業的規劃調整,暫時不在考取,軟件開發平台的重構,由於上半年工作安排的太滿及對未來職業的規劃,接下來不在會當做一個主要工作來進行升級,有一個需要改進的地方,就是對手機的依賴太嚴重,短視頻、短新聞佔據了自己大量學習與獨立思考的時間,這個應該就是我們這個年代所面臨的誘惑吧,最後來說一下生活,上半年購買了自己人生的第一輛車,雖說不是豪車,但是是通過自己的努力奮鬥得來的,還是挺欣慰的,周末的時候可以帶着家人到郊區轉轉,確實給生活帶來了諸多便利,年初定計劃的時候說要帶自己的家人出去旅遊1~2次,由於疫情原因,看來要泡湯了,不過可以改為近郊自駕游,在疫情緩解之後再考慮旅遊的地方吧;另外一個受影響比較大的就是自己喜愛的游泳了,不得不暫停,但減肥計劃,依舊不變,今年減肥目標不變,減肥至少15斤,減肥是最考驗一個人自律,我一定會通過減肥,證明自己是一個自律能力強的人,證明自己我可以。

三、結語

  到現在為止,我越來越認為優秀是一種習慣,這也就解釋了為什麼好多優秀的人在這一行業可以做到很好,換到其他行業同樣也可以做的很好,一個人之所以優秀,是因為他有良好的行為習慣、生活習慣,其中包括對時間的安排、解決問題的思路及方法、對待新事物的態度、思考問題的方式方法及相應的學習方法論;同時我也越來越相信性格決定命運,一個人的性格在很大程度上決定了在面對某一件事情的時候會採取的行動,我們在這個社會上都不是孤立存在的,我們一定會與這個社會產生交互,只要產生交互,別人就會依據你的性格、個性、習慣來給你做畫像,做人設,不可避免的會影響到我們未來的發展;
  我們的現狀是過去努力的結果,如果我們對自己的現狀不太滿意,那或許是因為過去你不夠努力或者方向不對吧,要想儘快達到令自己滿意的狀態,應該馬上從力所能級的事情開始做起,就如同我寫的這篇總結一樣,是對我過去半年工作的復盤、反思及總結,我相信人生沒有徒勞的努力,總有一天他會間接或者直接地助你一臂之力。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

※FB行銷專家,教你從零開始的技巧

一文讀懂:梯度消失(爆炸)及其解決方法

梯度消失問題和梯度爆炸問題,總的來說可以稱為梯度不穩定問題。

【要背住的知識】:用ReLU代替Sigmoid,用BN層,用殘差結構解決梯度消失問題。梯度爆炸問題的話,可以用正則化來限制。sigmoid的導數是【0,0.25】.

出現原因

兩者出現原因都是因為鏈式法則。當模型的層數過多的時候,計算梯度的時候就會出現非常多的乘積項。用下面這個例子來理解:

這是每層只有1個神經元的例子,每個神經元的激活函數都是sigmoid,然後我們想要更新b1這個參數。
按照大家都公認的符號來表示:

  • \(w_1\*x_1 + b_1 = z_1\)這就是z的含義;
  • \(\sigma(z_1)=a_1\),這是a的含義。

可以得到這個偏導數:
\(\frac{\partial C}{\partial b_1} = \frac{\partial z_1}{\partial b_1}\frac{\partial a_1}{\partial z_1} \frac{\partial z_2}{\partial a_2}\frac{\partial a_2}{\partial z_2} \frac{\partial z_2}{\partial a_3}\frac{\partial a_3}{\partial z_3} \frac{\partial z_3}{\partial a_4}\frac{\partial a_4}{\partial z_4} \frac{\partial C}{\partial a_4}\)

然後化簡:
\(\frac{\partial C}{\partial b_1}=\sigma'(z_1)w_2\sigma'(z_2)w_3\sigma'(z_3)w_4\sigma'(z_4)\frac{\partial C}{\partial a_4}\)

關鍵在於這個\(\sigma'(z_1)\),sigmoid函數的導數,是在0~0.25這個區間的,這意味着,當網絡層數越深,那麼對於前面幾層的梯度,就會非常的小。下圖是sigmoid函數的導數的函數圖:

因此經常會有這樣的現象:

圖中,分別表示4層隱含層的梯度變化幅度。可以看到,最淺的那個隱含層,梯度更新的速度,是非常小的。【圖中縱軸是指數變化的】。

那麼梯度爆炸也很好理解,就是\(w_j\sigma'(z_j)>1\),這樣就爆炸了。
【注意:如果激活函數是sigmoid,那麼其導數最大也就0.25,而\(w_j\)一般不會大於4的,所以sigmoid函數而言,一般都是梯度消失問題】

【總結】:

  1. 梯度消失和梯度爆炸是指前面幾層的梯度,因為鏈式法則不斷乘小於(大於)1的數,導致梯度非常小(大)的現象;
  2. sigmoid導數最大0.25,一般都是梯度消失問題。

解決方案

更換激活函數

最常見的方案就是更改激活函數,現在神經網絡中,除了最後二分類問題的最後一層會用sigmoid之外,每一層的激活函數一般都是用ReLU。

【ReLU】:如果激活函數的導數是1,那麼就沒有梯度爆炸問題了。

【好處】:可以發現,relu函數的導數在正數部分,是等於1的,因此就可以避免梯度消失的問題。
【不好】:但是負數部分的導數等於0,這樣意味着,只要在鏈式法則中某一個\(z_j\)小於0,那麼這個神經元的梯度就是0,不會更新。

【leakyReLU】:在ReLU的負數部分,增加了一定的斜率:

解決了ReLU中會有死神經元的問題。

【elu】:跟LeakyReLU一樣是為了解決死神經元問題,但是增加的斜率不是固定的:

但是相比leakrelu,計算量更大。

batchnorm層

這個是非常給力的成功,在圖像處理中必用的層了。BN層提出來的本質就是為了解決反向傳播中的梯度問題。

在神經網絡中,有這樣的一個問題:Internal Covariate Shift。
假設第一層的輸入數據經過第一層的處理之後,得到第二層的輸入數據。這時候,第二層的輸入數據相對第一層的數據分佈,就會發生改變,所以這一個batch,第二層的參數更新是為了擬合第二層的輸入數據的那個分佈。然而到了下一個batch,因為第一層的參數也改變了,所以第二層的輸入數據的分佈相比上一個batch,又不太一樣了。然後第二層的參數更新方向也會發生改變。層數越多,這樣的問題就越明顯。

但是為了保證每一層的分佈不變的話,那麼如果把每一層輸出的數據都歸一化0均值,1方差不就好了?但是這樣就會完全學習不到輸入數據的特徵了。不管什麼數據都是服從標準正太分佈,想想也會覺得有點奇怪。所以BN就是增加了兩個自適應參數,可以通過訓練學習的那種參數。這樣吧每一層的數據都歸一化到\(\beta\)均值,\(\gamma\)標準差的正態分佈上。

【將輸入分佈變成正態分佈,是一種去除數據絕對差異,擴大相對差異的一種行為,所以BN層用在分類上效果的好的。對於Image-to-Image這種任務,數據的絕對差異也是非常重要的,所以BN層可能起不到相應的效果。】

殘差結構

殘差結構,簡單的理解,就是讓深層網絡通過走捷徑,讓網絡不那麼深層。這樣梯度消失的問題就緩解了。

正則化

之前提到的梯度爆炸問題,一般都是因為\(w_j\)過大造成的,那麼用L2正則化就可以解決問題。

喜歡的話請關注我們的微信公眾號~【你好世界煉丹師】。

  • 公眾號主要講統計學,數據科學,機器學習,深度學習,以及一些參加Kaggle競賽的經驗。
  • 公眾號內容建議作為課後的一些相關知識的補充,飯後甜點。
  • 此外,為了不過多打擾,公眾號每周推送一次,每次4~6篇精選文章。

微信搜索公眾號:你好世界煉丹師。期待您的關注。

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

【其他文章推薦】

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

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

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

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

※網頁設計最專業,超強功能平台可客製化

12.DRF-節流

Django rest framework源碼分析(3)—-節流

添加節流

自定義節流的方法

  • 限制60s內只能訪問3次

(1)API文件夾下面新建throttle.py,代碼如下:

# utils/throttle.py

from rest_framework.throttling import BaseThrottle
import time
VISIT_RECORD = {}   #保存訪問記錄

class VisitThrottle(BaseThrottle):
    '''60s內只能訪問3次'''
    def __init__(self):
        self.history = None   #初始化訪問記錄

    def allow_request(self,request,view):
        #獲取用戶ip (get_ident)
        remote_addr = self.get_ident(request)
        ctime = time.time()
        #如果當前IP不在訪問記錄裏面,就添加到記錄
        if remote_addr not in VISIT_RECORD:
            VISIT_RECORD[remote_addr] = [ctime,]     #鍵值對的形式保存
            return True    #True表示可以訪問
        #獲取當前ip的歷史訪問記錄
        history = VISIT_RECORD.get(remote_addr)
        #初始化訪問記錄
        self.history = history

        #如果有歷史訪問記錄,並且最早一次的訪問記錄離當前時間超過60s,就刪除最早的那個訪問記錄,
        #只要為True,就一直循環刪除最早的一次訪問記錄
        while history and history[-1] < ctime - 60:
            history.pop()
        #如果訪問記錄不超過三次,就把當前的訪問記錄插到第一個位置(pop刪除最後一個)
        if len(history) < 3:
            history.insert(0,ctime)
            return True

    def wait(self):
        '''還需要等多久才能訪問'''
        ctime = time.time()
        return 60 - (ctime - self.history[-1])

(2)settings中全局配置節流

#全局
REST_FRAMEWORK = {
    #節流
    "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.VisitThrottle'],
}

(3)現在訪問auth看看結果:

  • 60s內訪問次數超過三次,會限制訪問
  • 提示剩餘多少時間可以訪問

接着訪問

節流源碼分析

(1)dispatch

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

(2)initial

def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    #4.實現認證
    self.perform_authentication(request)
    #5.權限判斷
    self.check_permissions(request)
    #6.控制訪問頻率
    self.check_throttles(request)

(3)check_throttles

裏面有個allow_request

def check_throttles(self, request):
    """
    Check if request should be throttled.
    Raises an appropriate exception if the request is throttled.
    """
    for throttle in self.get_throttles():
        if not throttle.allow_request(request, self):
            self.throttled(request, throttle.wait())

(4)get_throttles

def get_throttles(self):
    """
    Instantiates and returns the list of throttles that this view uses.
    """
    return [throttle() for throttle in self.throttle_classes]

(5)thtottle_classes

內置節流類

上面是寫的自定義節流,drf內置了很多節流的類,用起來比較方便。

(1)BaseThrottle

  • 自己要寫allow_request和wait方法
  • get_ident就是獲取ip
class BaseThrottle(object):
    """
    Rate throttling of requests.
    """

    def allow_request(self, request, view):
        """
        Return `True` if the request should be allowed, `False` otherwise.
        """
        raise NotImplementedError('.allow_request() must be overridden')

    def get_ident(self, request):
        """
        Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
        if present and number of proxies is > 0. If not use all of
        HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
        """
        xff = request.META.get('HTTP_X_FORWARDED_FOR')
        remote_addr = request.META.get('REMOTE_ADDR')
        num_proxies = api_settings.NUM_PROXIES

        if num_proxies is not None:
            if num_proxies == 0 or xff is None:
                return remote_addr
            addrs = xff.split(',')
            client_addr = addrs[-min(num_proxies, len(addrs))]
            return client_addr.strip()

        return ''.join(xff.split()) if xff else remote_addr

    def wait(self):
        """
        Optionally, return a recommended number of seconds to wait before
        the next request.
        """
        return None

(2)SimpleRateThrottle

class SimpleRateThrottle(BaseThrottle):
    """
    A simple cache implementation, that only requires `.get_cache_key()`
    to be overridden.

    The rate (requests / seconds) is set by a `rate` attribute on the View
    class.  The attribute is a string of the form 'number_of_requests/period'.

    Period should be one of: ('s', 'sec', 'm', 'min', 'h', 'hour', 'd', 'day')

    Previous request information used for throttling is stored in the cache.
    """
    cache = default_cache
    timer = time.time
    cache_format = 'throttle_%(scope)s_%(ident)s'
    scope = None   #這個值自定義,寫什麼都可以
    THROTTLE_RATES = api_settings.DEFAULT_THROTTLE_RATES

    def __init__(self):
        if not getattr(self, 'rate', None):
            self.rate = self.get_rate()
        self.num_requests, self.duration = self.parse_rate(self.rate)

    def get_cache_key(self, request, view):
        """
        Should return a unique cache-key which can be used for throttling.
        Must be overridden.

        May return `None` if the request should not be throttled.
        """
        raise NotImplementedError('.get_cache_key() must be overridden')

    def get_rate(self):
        """
        Determine the string representation of the allowed request rate.
        """
        if not getattr(self, 'scope', None):
            msg = ("You must set either `.scope` or `.rate` for '%s' throttle" %
                   self.__class__.__name__)
            raise ImproperlyConfigured(msg)

        try:
            return self.THROTTLE_RATES[self.scope]
        except KeyError:
            msg = "No default throttle rate set for '%s' scope" % self.scope
            raise ImproperlyConfigured(msg)

    def parse_rate(self, rate):
        """
        Given the request rate string, return a two tuple of:
        <allowed number of requests>, <period of time in seconds>
        """
        if rate is None:
            return (None, None)
        num, period = rate.split('/')
        num_requests = int(num)
        duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
        return (num_requests, duration)

    def allow_request(self, request, view):
        """
        Implement the check to see if the request should be throttled.

        On success calls `throttle_success`.
        On failure calls `throttle_failure`.
        """
        if self.rate is None:
            return True

        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True

        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests:
            return self.throttle_failure()
        return self.throttle_success()

    def throttle_success(self):
        """
        Inserts the current request's timestamp along with the key
        into the cache.
        """
        self.history.insert(0, self.now)
        self.cache.set(self.key, self.history, self.duration)
        return True

    def throttle_failure(self):
        """
        Called when a request to the API has failed due to throttling.
        """
        return False

    def wait(self):
        """
        Returns the recommended next request time in seconds.
        """
        if self.history:
            remaining_duration = self.duration - (self.now - self.history[-1])
        else:
            remaining_duration = self.duration

        available_requests = self.num_requests - len(self.history) + 1
        if available_requests <= 0:
            return None

        return remaining_duration / float(available_requests)

我們可以通過繼承SimpleRateThrottle類,來實現節流,會更加的簡單,因為SimpleRateThrottle裏面都幫我們寫好了

(1)throttle.py

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    '''匿名用戶60s只能訪問三次(根據ip)'''
    scope = 'NBA'   #這裏面的值,自己隨便定義,settings裏面根據這個值配置Rate

    def get_cache_key(self, request, view):
        #通過ip限制節流
        return self.get_ident(request)

class UserThrottle(SimpleRateThrottle):
    '''登錄用戶60s可以訪問10次'''
    scope = 'NBAUser'    #這裏面的值,自己隨便定義,settings裏面根據這個值配置Rate

    def get_cache_key(self, request, view):
        return request.user.username

(2)settings.py

#全局
REST_FRAMEWORK = {
    #節流
    "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'],   #全局配置,登錄用戶節流限制(10/m)
    "DEFAULT_THROTTLE_RATES":{
        'NBA':'3/m',         #沒登錄用戶3/m,NBA就是scope定義的值
        'NBAUser':'10/m',    #登錄用戶10/m,NBAUser就是scope定義的值
    }
}

(3)views.py

局部配置方法

class AuthView(APIView):
    .
    .    
    .
    # 默認的節流是登錄用戶(10/m),AuthView不需要登錄,這裏用匿名用戶的節流(3/m)
    throttle_classes = [VisitThrottle,]   .    .
# views.py
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from API.utils.permission import SVIPPremission,MyPremission
from API.utils.throttle import  VisitThrottle

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()


class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []      #裏面為空,代表不需要認證
    permission_classes = []          #不裏面為空,代表不需要權限
    # 默認的節流是登錄用戶(10/m),AuthView不需要登錄,這裏用匿名用戶的節流(3/m)
    throttle_classes = [VisitThrottle,]

    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class OrderView(APIView):
    '''
    訂單相關業務(只有SVIP用戶才能看)
    '''

    def get(self,request,*args,**kwargs):
        self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    '''
       訂單相關業務(普通用戶和VIP用戶可以看)
       '''
    permission_classes = [MyPremission,]    #不用全局的權限配置的話,這裏就要寫自己的局部權限
    def get(self,request,*args,**kwargs):

        print(request.user)
        return HttpResponse('用戶信息')

說明:

  • API.utils.throttle.UserThrottle 這個是全局配置(根據ip限制,10/m)
  • DEFAULT_THROTTLE_RATES —>>>設置訪問頻率的
  • throttle_classes = [VisitThrottle,] —>>>局部配置(不適用settings裏面默認的全局配置)

總結

基本使用

  • 創建類,繼承BaseThrottle, 實現:allow_request ,wait
  • 創建類,繼承SimpleRateThrottle, 實現: get_cache_key, scope=’NBA’ (配置文件中的key)

全局

   #節流
    "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'],   #全局配置,登錄用戶節流限制(10/m)
    "DEFAULT_THROTTLE_RATES":{
        'NBA':'3/m',         #沒登錄用戶3/m,NBA就是scope定義的值
        'NBAUser':'10/m',    #登錄用戶10/m,NBAUser就是scope定義的值
    }
}

局部

throttle_classes = [VisitThrottle,]

所有代碼

認證、權限和節流

# MyProject/urls.py

from django.contrib import admin
from django.urls import path
from API.views import AuthView,OrderView,UserInfoView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/',AuthView.as_view()),
    path('api/v1/order/',OrderView.as_view()),
    path('api/v1/info/',UserInfoView.as_view()),
]
#全局 settings.py
REST_FRAMEWORK = {
    #認證
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',],
    #權限
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPermission'],
    #節流
    "DEFAULT_THROTTLE_CLASSES":['API.utils.throttle.UserThrottle'],   #全局配置,登錄用戶節流限制(10/m)
    "DEFAULT_THROTTLE_RATES":{
        'NBA':'3/m',         #沒登錄用戶3/m,NBA就是scope定義的值
        'NBAUser':'10/m',    #登錄用戶10/m,NBAUser就是scope定義的值
    }
}
# API/models.py


from django.db import models

class UserInfo(models.Model):
    USER_TYPE = (
        (1,'普通用戶'),
        (2,'VIP'),
        (3,'SVIP')
    )

    user_type = models.IntegerField(choices=USER_TYPE)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

class UserToken(models.Model):
    user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
    token = models.CharField(max_length=64)
# API/views.py

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from API.utils.permission import SVIPPermission,MyPermission
from API.utils.throttle import  VisitThrottle

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()


class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []      #裏面為空,代表不需要認證
    permission_classes = []          #不裏面為空,代表不需要權限
    # 默認的節流是登錄用戶(10/m),AuthView不需要登錄,這裏用匿名用戶的節流(3/m)
    throttle_classes = [VisitThrottle,]

    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class OrderView(APIView):
    '''
    訂單相關業務(只有SVIP用戶才能看)
    '''

    def get(self,request,*args,**kwargs):
        self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    '''
       訂單相關業務(普通用戶和VIP用戶可以看)
       '''
    permission_classes = [MyPermission,]    #不用全局的權限配置的話,這裏就要寫自己的局部權限
    def get(self,request,*args,**kwargs):

        print(request.user)
        return HttpResponse('用戶信息')
# API/utils/auth/py

from rest_framework import exceptions
from API import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass
# utils/permission.py

from rest_framework.permissions import BasePermission

class SVIPPermission(BasePermission):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPermission(BasePermission):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True
# utils/throttle.py
#
# from rest_framework.throttling import BaseThrottle
# import time
# VISIT_RECORD = {}   #保存訪問記錄
#
# class VisitThrottle(BaseThrottle):
#     '''60s內只能訪問3次'''
#     def __init__(self):
#         self.history = None   #初始化訪問記錄
#
#     def allow_request(self,request,view):
#         #獲取用戶ip (get_ident)
#         remote_addr = self.get_ident(request)
#         ctime = time.time()
#         #如果當前IP不在訪問記錄裏面,就添加到記錄
#         if remote_addr not in VISIT_RECORD:
#             VISIT_RECORD[remote_addr] = [ctime,]     #鍵值對的形式保存
#             return True    #True表示可以訪問
#         #獲取當前ip的歷史訪問記錄
#         history = VISIT_RECORD.get(remote_addr)
#         #初始化訪問記錄
#         self.history = history
#
#         #如果有歷史訪問記錄,並且最早一次的訪問記錄離當前時間超過60s,就刪除最早的那個訪問記錄,
#         #只要為True,就一直循環刪除最早的一次訪問記錄
#         while history and history[-1] < ctime - 60:
#             history.pop()
#         #如果訪問記錄不超過三次,就把當前的訪問記錄插到第一個位置(pop刪除最後一個)
#         if len(history) < 3:
#             history.insert(0,ctime)
#             return True
#
#     def wait(self):
#         '''還需要等多久才能訪問'''
#         ctime = time.time()
#         return 60 - (ctime - self.history[-1])

from rest_framework.throttling import SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):
    '''匿名用戶60s只能訪問三次(根據ip)'''
    scope = 'NBA'   #這裏面的值,自己隨便定義,settings裏面根據這個值配置Rate

    def get_cache_key(self, request, view):
        #通過ip限制節流
        return self.get_ident(request)

class UserThrottle(SimpleRateThrottle):
    '''登錄用戶60s可以訪問10次'''
    scope = 'NBAUser'    #這裏面的值,自己隨便定義,settings裏面根據這個值配置Rate

    def get_cache_key(self, request, view):
        return request.user.username

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

日產汽車2015年將生產首款中國品牌電動汽車

日產汽車高管日前表示,公司到2015年將通過其中國品牌開始生產首款Venucia e30。

公司2013年還將在中國15座城市與當地政府聯合啟動試驗項目,以推廣Venucia e30。日產目前正在武漢和廣州推廣自己品牌的電動汽車Leaf。

日產在中國有多家生產廠房,並計畫2014年在大連再新增1處,但受日本“國有化”釣魚島鬧劇影響,日產在中國的銷售大幅衰退。其10月在華銷售新車6.43萬輛,同比下滑40.7%,降幅較9月的35%進一步擴大。

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

【其他文章推薦】

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

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

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

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

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

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

廣汽首款混合動力車型傳祺亮相廣州車展

在此次廣州車展上,廣汽發佈了該品牌首款型傳祺混動版。

新車的外觀與普通版車型基本保持一致,只是車頭和車尾的LOGO採用了藍色底色以表明這款車與普通版車型的不同,另外前進氣格柵加入了更多鍍鉻裝飾條。

該車的混合動力技術為廣汽自主研發,新車已經進入國家的公務車採購目錄,預計公車採購將成為它的主要銷售份額。動力方面,混動版傳祺在驅動方式上將有兩驅和四驅可選,對應的百公里綜合油耗分別為7.5L/km和6.6L/km。汽油機方面,它搭載的1.8L發動機最大功率可達106kW(144PS),電動機方面的資訊還尚不明確。

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

【其他文章推薦】

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

※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

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

※推薦評價好的iphone維修中心