嵌入式、C語言位操作的一些技巧匯總

下面分享關於位操作的一些筆記:

一、位操作簡單介紹

首先,以下是按位運算符:

嵌入式編程中,常常需要對一些寄存器進行配置,有的情況下需要改變一個字節中的某一位或者幾位,但是又不想改變其它位原有的值,這時就可以使用按位運算符進行操作。下面進行舉例說明,假如有一個8位的TEST寄存器:

當我們要設置第0位bit0的值為1時,可能會這樣進行設置:

TEST = 0x01;

但是,這樣設置是不夠準確的,因為這時候已經同時操作到了高7位:bit1~bit7,如果這高7位沒有用到的話,這麼設置沒有什麼影響;但是,如果這7位正在被使用,結果就不是我們想要的了。

在這種情況下,我們就可以借用按位操作運算符進行配置。

對於二進制位操作來說,不管該位原來的值是0還是1,它跟0進行&運算,得到的結果都是0,而跟1進行&運算,將保持原來的值不變;不管該位原來的值是0還是1,它跟1進行|運算,得到的結果都是1,而跟0進行|運算,將保持原來的值不變。

所以,此時可以設置為:

TEST = TEST | 0x01;

其意義為:TEST寄存器的高7位均不變,最低位變成1了。在實際編程中,常改寫為:

TEST |= 0x01;

這種寫法可以一定程度上簡化代碼,是 C 語言常用的一種編程風格。設置寄存器的某一位還有另一種操作方法,以上的等價方法如:

TEST |= (0x01 << 0);

第幾位要置1就左移幾位。

同樣的,要給TEST的低4位清0,高4位保持不變,可以進行如下配置:

TEST &= 0xF0;

二、嵌入式中位操作一些常見用法

1、一個32bit數據的位、字節讀取操作

(1)獲取單字節:

#define GET_LOW_BYTE0(x)    ((x >>  0) & 0x000000ff)    /* 獲取第0個字節 */
#define GET_LOW_BYTE1(x)    ((x >>  8) & 0x000000ff)    /* 獲取第1個字節 */
#define GET_LOW_BYTE2(x)    ((x >> 16) & 0x000000ff)    /* 獲取第2個字節 */
#define GET_LOW_BYTE3(x)    ((x >> 24) & 0x000000ff)    /* 獲取第3個字節 */

示例:

(2)獲取某一位:

#define GET_BIT(x, bit) ((x & (1 << bit)) >> bit)   /* 獲取第bit位 */

示例:

2、一個32bit數據的位、字節清零操作

(1)清零某個字節:

#define CLEAR_LOW_BYTE0(x)  (x &= 0xffffff00)   /* 清零第0個字節 */
#define CLEAR_LOW_BYTE1(x)  (x &= 0xffff00ff)   /* 清零第1個字節 */
#define CLEAR_LOW_BYTE2(x)  (x &= 0xff00ffff)   /* 清零第2個字節 */
#define CLEAR_LOW_BYTE3(x)  (x &= 0x00ffffff)   /* 清零第3個字節 */

示例:

(2)清零某一位:

#define CLEAR_BIT(x, bit)   (x &= ~(1 << bit))  /* 清零第bit位 */

示例:

3、一個32bit數據的位、字節置1操作

(1)置某個字節為1:

#define SET_LOW_BYTE0(x)    (x |= 0x000000ff)   /* 第0個字節置1 */   
#define SET_LOW_BYTE1(x)    (x |= 0x0000ff00)   /* 第1個字節置1 */   
#define SET_LOW_BYTE2(x)    (x |= 0x00ff0000)   /* 第2個字節置1 */   
#define SET_LOW_BYTE3(x)    (x |= 0xff000000)   /* 第3個字節置1 */

示例:

(2)置位某一位:

#define SET_BIT(x, bit) (x |= (1 << bit))   /* 置位第bit位 */

4、判斷某一位或某幾位連續位的值

(1)判斷某一位的值

舉例說明:判斷0x68第3位的值。

也就是說,要判斷第幾位的值,if里就左移幾位(當然別過頭了)。在嵌入式編程中,可通過這樣的方式來判斷寄存器的狀態位是否被置位。

(2)判斷某幾位連續位的值

/* 獲取第[n:m]位的值 */
#define BIT_M_TO_N(x, m, n)  ((unsigned int)(x << (31-(n))) >> ((31 - (n)) + (m)))

示例:

這是一個查詢連續狀態位的例子,因為有些情況不止有0、1兩種狀態,可能會有多種狀態,這種情況下就可以用這種方法來取出狀態位,再去執行相應操作。

以上是對32bit數據的一些操作進行總結,其它位數的數據類似,可根據需要進行修改。

三、STM32寄存器配置

STM32有幾套固件庫,這些固件庫函數以函數的形式進行1層或者多層封裝(軟件開發中很重要的思想之一:分層思想),但是到了最裏面的一層就是對寄存器的配置。我們平時都比較喜歡固件庫來開發,大概是因為固件庫用起來比較簡單,用固件庫寫出來的代碼比較容易閱讀。最近一段時間一直在配置寄存器,越發地發現使用寄存器來進行一些外設的配置也是很容易懂的。使用寄存器的方式編程無非就是往寄存器的某些位置1、清零以及對寄存器一些狀態位進行判斷、讀取寄存器的內容等。

這些基本操作在上面的例子中已經有介紹,我們依舊以實例來鞏固上面的知識點(以STM32F1xx為例):

(1)寄存器配置

看一下GPIO功能的端口輸出數據寄存器 (GPIOx_ODR) (x=A..E) :

假設我們要讓PA10引腳輸出高、輸出低,可以這麼做:

方法一:

GPIOA->ODR |= 1 << 10;      /* PA10輸出高(置1操作) */
GPIOA->ODR &= ~(1 << 10);  /* PA10輸出低(清0操作) */

也可用我們上面的置位、清零的宏定義:

SET_BIT(GPIOA->ODR, 10);    /* PA10輸出高(置1操作) */
CLEAR_BIT(GPIOA->ODR, 10);  /* PA10輸出低(清0操作) */

方法二:

GPIOA->ODR |= (uint16_t)0x0400;   /* PA10輸出高(置1操作) */
GPIOA->ODR &= ~(uint16_t)0x0400;  /* PA10輸出低(清0操作) */

貌似第二種方法更麻煩?還得去細心地去構造一個數據。

但是,其實第二種方法其實是ST推薦我們用的方法,為什麼這麼說呢?因為ST官方已經把這些我們要用到的值給我們配好了,在stm32f10x.h中:

這個頭文件中存放的就是外設寄存器的一些位配置。

所以我們的方法二等價於:

GPIOA->ODR |= GPIO_ODR_ODR10;   /* PA10輸出高(置1操作) */
GPIOA->ODR &= ~GPIO_ODR_ODR10;  /* PA10輸出低(清0操作) */

兩種方法都是很好的方法,但方法一似乎更好理解。

配置連續幾位的方法也是一樣的,就不介紹了。簡單介紹配置不連續位的方法,以TIM1的CR1寄存器為例:

設置CEN位為1、設置CMS[1:0]位為01、設置CKD[1:0]位為10:

TIM1->CR1 |= (0x1 << 1)| (0x1 << 5) |(0x2 << 8);

這是組合的寫法。當然,像上面一樣拆開來寫也是可以的。

(2)判斷標誌位

以狀態寄存器(USART_SR) 為例:

判斷RXNE是否被置位:

/* 數據寄存器非空,RXNE標誌置位 */
if (USART1->SR & (1 << 5))
{
    /* 其它代碼 */
    
    USART1->SR &= ~(1 << 5);  /* 清零RXNE標誌 */
}

或者:

/* 數據寄存器非空,RXNE標誌置位 */
if (USART1->SR & USART_SR_RXNE)
{
    /* 其它代碼 */
    
    USART1->SR &= ~USART_SR_RXNE;  /* 清零RXNE標誌 */
}

四、總結

以上就是本次關於位操作的一點總結筆記,有必要掌握。雖然說在用STM32的時候有庫函數可以用,但是最接近芯片內部原理的還是寄存器。有可能之後有用到其它芯片沒有像ST這樣把寄存器相關配置封裝得那麼好,那就不得不直接操控寄存器了。

此外,使用庫函數的方式代碼佔用空間大,用寄存器的話,代碼佔用空間小。之前有個需求,我們能用的Flash的空間大小隻有4KB,遇到類似這樣的情況就不能那麼隨性的用庫函數了。

最後,應用的時候當然是怎麼簡單就怎麼用。學從“難”處學,用從易處用,與君共勉~

END:以上筆記中如有錯誤,歡迎指出!謝謝

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

3c收購,鏡頭 收購有可能以全新價回收嗎?

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

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

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

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

d3.js 地鐵軌道交通項目實戰

上一章說了如何製作一個線路圖,當然上一章是手寫的JSON數據,當然手寫的json數據有非常多的好處,例如可以應對客戶的各種BT需求,但是大多數情況下我們都是使用地鐵公司現成的JSON文件,話不多說我們先看一下。

就是這樣的,今天我們就來完成它的大部分需求,以及地鐵公司爸爸提出來的需求。

需求如下:
1.按照不同顏色显示地鐵各線路,显示對應站點。
2.用戶可以點擊手勢縮放和平移(此項目為安卓開發)。
3.用戶在線路menu里點擊線路,對應線路平移值屏幕中心並高亮。
4.根據後台數據,渲染問題路段。
5.點擊問題路段站點,显示問題詳情。

大致需求就是這些,下面看看看代碼

1.定義一些常量和變量

const dataset = subwayData; //線路圖數據源
let subway = new Subway(dataset); //線路圖的類文件
let baseScale = 2; //基礎縮放倍率
let deviceScale = 1400 / 2640; //設備與畫布寬度比率
let width = 2640; //畫布寬
let height = 1760; //畫布高
let transX = 1320 + 260; //地圖X軸平移(將畫布原點X軸平移)
let transY = 580; //地圖X軸平移(將畫布原點Y軸平移)
let scaleExtent = [0.8, 4]; //縮放倍率限制
let currentScale = 2; //當前縮放值
let currentX = 0; //當前畫布X軸平移量
let currentY = 0; //當前畫布Y軸平移量
let selected = false; //線路是否被選中(在右上角的線路菜單被選中)
let scaleStep = 0.5; //點擊縮放按鈕縮放步長默認0.5倍
let tooltip = d3.select('#tooltip'); //提示框
let bugArray = []; //問題路段數組
let svg = d3.select('#sw').append('svg'); //畫布
let group = svg.append('g').attr('transform', `translate(${transX}, ${transY}) scale(1)`);//定義組並平移
let whole = group.append('g').attr('class', 'whole-line') //虛擬線路(用於點擊右上角響應線路可以定位當視野中心,方法不唯一)
let path = group.append('g').attr('class', 'path'); //定義線路
let point = group.append('g').attr('class', 'point'); //定義站點
const zoom = d3.zoom().scaleExtent(scaleExtent).on("zoom", zoomed); //定義縮放事件

這就是我們需要使用的一些常量和變量。注意transX不是寬度的一半,是因為北京地鐵線路網西線更密集。

2.讀官方JSON

使用d3.js數據必不可少,然而官方的數據並不通俗易懂,我們先解讀一下官方JSON數據。

每條線路對象都有一個l_xmlattr屬性和一個p屬性,l_xmlattr是整條線路的屬性,p是站點數組,我們看一下站點中我們需要的屬性。ex是否是中轉站,lb是站名,sid是站的id,rx、ry是文字偏移量,st是是否為站點(因為有的點不是站點而是為了渲染貝塞爾曲線用的),x、y是站點坐標。

3.構造自己的類方法

官方給了我們數據,但是並不是我們能直接使用的,所以我們需要構造自己的方法類

class Subway {
    constructor(data) {
        this.data = data;
        this.bugLineArray = [];
    }
    getInvent() {} //獲取虛擬線路數據
    getPathArray() {} //獲取路徑數據
    getPointArray() {} //獲取站點數組
    getCurrentPathArray() {} //獲取被選中線路的路徑數組
    getCurrentPointArray() {} //獲取被選中線路的站點數組
    getLineNameArray() {} // 獲取線路名稱數組
    getBugLineArray() {} //獲取問題路段數組
}

 

下面是我們方法內容,裏面的操作不是很優雅(大家將就看啦)
getInvent() {
    let lineArray = [];
    this.data.forEach(d => {
        let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr;
        let allPoints = d.p.slice(0);
        loop && allPoints.push(allPoints[0]);
        let path = this.formatPath(allPoints, 0, allPoints.length - 1);
        lineArray.push({
            lid: lid,
            path: path,
        })
    })
    return lineArray;
}
getPathArray() {
    let pathArray = [];
    this.data.forEach(d => {
        let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr;
        let allPoints = d.p.slice(0);
        loop && allPoints.push(allPoints[0])
        let allStations = [];
        allPoints.forEach((item, index) => item.p_xmlattr.st && allStations.push({...item.p_xmlattr, index}))
        let arr = [];
        for(let i = 0; i < allStations.length - 1; i++) {
            let path = this.formatPath(allPoints, allStations[i].index, allStations[i + 1].index);
            arr.push({
                lid: lid,
                id: `${allStations[i].sid}_${allStations[i + 1].sid}`,
                path: path,
                color: lc.replace(/0x/, '#')
            })
        }
        pathArray.push({
            path: arr,
            lc: lc.replace(/0x/, '#'),
            lb,lbx,lby,lid
        })
    })
    return pathArray;
}
getPointArray() {
    let pointArray = [];
    let tempPointsArray = [];
    this.data.forEach(d => {
        let {lid,lc,lb} = d.l_xmlattr;
        let allPoints = d.p;
        let allStations = [];
        allPoints.forEach(item => {
            if(item.p_xmlattr.st && !item.p_xmlattr.ex) {
                allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')})
            } else if (item.p_xmlattr.ex) {
                if(tempPointsArray.indexOf(item.p_xmlattr.sid) == -1) {
                    allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')})
                    tempPointsArray.push(item.p_xmlattr.sid);
                }
            }
        });
        pointArray.push(allStations);
    })
    return pointArray;
}
getCurrentPathArray(name) {
    let d = this.data.filter(d => d.l_xmlattr.lid == name)[0];
    let { loop, lc, lbx, lby, lb, lid} = d.l_xmlattr;
    let allPoints = d.p.slice(0);
    loop && allPoints.push(allPoints[0])
    let allStations = [];
    allPoints.forEach((item, index) => item.p_xmlattr.st && allStations.push({...item.p_xmlattr, index}))
    let arr = [];
    for(let i = 0; i < allStations.length - 1; i++) {
        let path = this.formatPath(allPoints, allStations[i].index, allStations[i + 1].index);
        arr.push({
            lid: lid,
            id: `${allStations[i].sid}_${allStations[i + 1].sid}`,
            path: path,
            color: lc.replace(/0x/, '#')
        })
    }
    return {
        path: arr,
        lc: lc.replace(/0x/, '#'),
        lb,lbx,lby,lid
    }
}
getCurrentPointArray(name) {
    let d = this.data.filter(d => d.l_xmlattr.lid == name)[0];
    let {lid,lc,lb} = d.l_xmlattr;
    let allPoints = d.p;
    let allStations = [];
    allPoints.forEach(item => {
        if(item.p_xmlattr.st && !item.p_xmlattr.ex) {
            allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')})
        } else if (item.p_xmlattr.ex) {
            allStations.push({...item.p_xmlattr, lid, pn: lb, lc: lc.replace(/0x/, '#')})
        }
    });
    return allStations;
}
getLineNameArray() {
    let nameArray = this.data.map(d => {
        return {
            lb: d.l_xmlattr.lb,
            lid: d.l_xmlattr.lid,
            lc: d.l_xmlattr.lc.replace(/0x/, '#')
        }
    })
    return nameArray;
}
getBugLineArray(arr) {
    if(!arr || !arr.length) return [];
    this.bugLineArray = [];
    arr.forEach(item => {
        let { start, end, cause, duration, lid, lb } = item;
        let lines = [];
        let points = [];
        let tempObj = this.data.filter(d => d.l_xmlattr.lid == lid)[0];
        let loop = tempObj.l_xmlattr.loop;
        let lc = tempObj.l_xmlattr.lc;
        let allPoints = tempObj.p;
        let allStations = [];
        allPoints.forEach(item => {
            if(item.p_xmlattr.st) {
                allStations.push(item.p_xmlattr.sid)
            }
        });
        loop && allStations.push(allStations[0]);
        for(let i=allStations.indexOf(start); i<=allStations.lastIndexOf(end); i++) {
            points.push(allStations[i])
        }
        for(let i=allStations.indexOf(start); i<allStations.lastIndexOf(end); i++) {
            lines.push(`${allStations[i]}_${allStations[i+1]}`)
        }
        this.bugLineArray.push({cause,duration,lid,lb,lines,points,lc: lc.replace(/0x/, '#'),start: points[0],end:points[points.length - 1]});
    })
    return this.bugLineArray;
這種方法大家也不必看懂,知道傳入了什麼,輸入了什麼即可,這就是我們的方法類。

4.d3渲染畫布並添加方法

這裡是js的核心代碼,既然class文件都寫完了,這裏的操作就方便了很多,主要就是下面幾個人方法,
renderInventLine(); //渲染虛擬新路
renderAllStation(); //渲染所有的線路名稱(右上角)
renderBugLine(); //渲染問題路段
renderAllLine(); //渲染所有線路
renderAllPoint(); //渲染所有點
renderCurrentLine() //渲染當前選中的線路
renderCurrentPoint() //渲染當前選中的站點
zoomed() //縮放時執行的方法
getCenter() //獲取虛擬線中心點的坐標
scale() //點擊縮放按鈕時執行的方法
下面是對應的方法體
svg.call(zoom);
svg.call(zoom.transform, d3.zoomIdentity.translate((1 - baseScale) * transX, (1 - baseScale) * transY).scale(baseScale));

let pathArray = subway.getPathArray();
let pointArray = subway.getPointArray();

renderInventLine();
renderAllStation();
renderBugLine();

function renderInventLine() {
    let arr = subway.getInvent();
    whole.selectAll('path')
    .data(arr)
    .enter()
    .append('path')
    .attr('d', d => d.path)
    .attr('class', d => d.lid)
    .attr('stroke', 'none')
    .attr('fill', 'none')
}

function renderAllLine() {
    for (let i = 0; i < pathArray.length; i++) {
        path.append('g')
        .selectAll('path')
        .data(pathArray[i].path)
        .enter()
        .append('path')
        .attr('d', d => d.path)
        .attr('lid', d => d.lid)
        .attr('id', d => d.id)
        .attr('class', 'lines origin')
        .attr('stroke', d => d.color)
        .attr('stroke-width', 7)
        .attr('stroke-linecap', 'round')
        .attr('fill', 'none')
        path.append('text')
        .attr('x', pathArray[i].lbx)
        .attr('y', pathArray[i].lby)
        .attr('dy', '1em')
        .attr('dx', '-0.3em')
        .attr('fill', pathArray[i].lc)
        .attr('lid', pathArray[i].lid)
        .attr('class', 'line-text origin')
        .attr('font-size', 14)
        .attr('font-weight', 'bold')
        .text(pathArray[i].lb)
    }
}

function renderAllPoint() {
    for (let i = 0; i < pointArray.length; i++) {
        for (let j = 0; j < pointArray[i].length; j++) {
            let item = pointArray[i][j];
            let box = point.append('g');
            if (item.ex) {
                box.append('image')
                .attr('href', './trans.png')
                .attr('class', 'points origin')
                .attr('id', item.sid)
                .attr('x', item.x - 8)
                .attr('y', item.y - 8)
                .attr('width', 16)
                .attr('height', 16)
            } else {
                box.append('circle')
                .attr('cx', item.x)
                .attr('cy', item.y)
                .attr('r', 5)
                .attr('class', 'points origin')
                .attr('id', item.sid)
                .attr('stroke', item.lc)
                .attr('stroke-width', 1.5)
                .attr('fill', '#ffffff')
            }
            box.append('text')
            .attr('x', item.x + item.rx)
            .attr('y', item.y + item.ry)
            .attr('dx', '0.3em')
            .attr('dy', '1.1em')
            .attr('font-size', 11)
            .attr('class', 'point-text origin')
            .attr('lid', item.lid)
            .attr('id', item.sid)
            .text(item.lb)
        }
    }
}

function renderCurrentLine(name) {
    let arr = subway.getCurrentPathArray(name);
    path.append('g')
    .attr('class', 'temp')
    .selectAll('path')
    .data(arr.path)
    .enter()
    .append('path')
    .attr('d', d => d.path)
    .attr('lid', d => d.lid)
    .attr('id', d => d.id)
    .attr('stroke', d => d.color)
    .attr('stroke-width', 7)
    .attr('stroke-linecap', 'round')
    .attr('fill', 'none')
    path.append('text')
    .attr('class', 'temp')
    .attr('x', arr.lbx)
    .attr('y', arr.lby)
    .attr('dy', '1em')
    .attr('dx', '-0.3em')
    .attr('fill', arr.lc)
    .attr('lid', arr.lid)
    .attr('font-size', 14)
    .attr('font-weight', 'bold')
    .text(arr.lb)
}

function renderCurrentPoint(name) {
    let arr = subway.getCurrentPointArray(name);
    for (let i = 0; i < arr.length; i++) {
        let item = arr[i];
        let box = point.append('g').attr('class', 'temp');
        if (item.ex) {
            box.append('image')
            .attr('href', './trans.png')
            .attr('x', item.x - 8)
            .attr('y', item.y - 8)
            .attr('width', 16)
            .attr('height', 16)
            .attr('id', item.sid)
        } else {
            box.append('circle')
            .attr('cx', item.x)
            .attr('cy', item.y)
            .attr('r', 5)
            .attr('id', item.sid)
            .attr('stroke', item.lc)
            .attr('stroke-width', 1.5)
            .attr('fill', '#ffffff')
        }
        box.append('text')
        .attr('class', 'temp')
        .attr('x', item.x + item.rx)
        .attr('y', item.y + item.ry)
        .attr('dx', '0.3em')
        .attr('dy', '1.1em')
        .attr('font-size', 11)
        .attr('lid', item.lid)
        .attr('id', item.sid)
        .text(item.lb)
    }
}

function renderBugLine(modal) {
    let bugLineArray = subway.getBugLineArray(modal);
    d3.selectAll('.origin').remove();
    renderAllLine();
    renderAllPoint();
    bugLineArray.forEach(d => {
        console.log(d)
        d.lines.forEach(dd => {
            d3.selectAll(`path#${dd}`).attr('stroke', '#eee');
        })
        d.points.forEach(dd => {
            d3.selectAll(`circle#${dd}`).attr('stroke', '#ddd')
            d3.selectAll(`text#${dd}`).attr('fill', '#aaa')
        })
    })
    d3.selectAll('.points').on('click', function () {
        let id = d3.select(this).attr('id');
        let bool = judgeBugPoint(bugLineArray, id);
        if (bool) {
            let x, y;
            if (d3.select(this).attr('href')) {
                x = parseFloat(d3.select(this).attr('x')) + 8;
                y = parseFloat(d3.select(this).attr('y')) + 8;
            } else {
                x = d3.select(this).attr('cx');
                y = d3.select(this).attr('cy');
            }
            let toolX = (x * currentScale + transX - ((1 - currentScale) * transX - currentX)) * deviceScale;
            let toolY = (y * currentScale + transY - ((1 - currentScale) * transY - currentY)) * deviceScale;
            let toolH = document.getElementById('tooltip').offsetHeight;
            let toolW = 110;
            if (toolY < 935 / 2) {
                tooltip.style('left', `${toolX - toolW}px`).style('top', `${toolY + 5}px`);
            } else {
                tooltip.style('left', `${toolX - toolW}px`).style('top', `${toolY - toolH - 5}px`);
            }
        }
    });
}

function judgeBugPoint(arr, id) {
    if (!arr || !arr.length || !id) return false;
    let bugLine = arr.filter(d => {
        return d.points.indexOf(id) > -1
    });
    if (bugLine.length) {
        removeTooltip()
        tooltip.select('#tool-head').html(`<span>${id}</span><div class="deletes" onclick="removeTooltip()">×</div>`);
        bugLine.forEach(d => {
            let item = tooltip.select('#tool-body').append('div').attr('class', 'tool-item');
            item.html(`
                <div class="tool-content">
                    <div style="color: #ffffff;border-bottom: 2px solid ${d.lc};">
                        <span style="background: ${d.lc};padding: 4px 6px;">${d.lb}</span>
                    </div>
                    <div>
                        <div class="content-left">封路時間</div><div class="content-right">${d.duration}</div>
                    </div>
                    <div>
                        <div class="content-left">封路原因</div><div class="content-right">${d.cause}</div>
                    </div>
                    <div>
                        <div class="content-left">封路路段</div><div class="content-right">${d.start}-${d.end}</div>
                    </div>
                </div>
            `)
        })
        d3.select('#tooltip').style('display', 'block');
        return true;
    } else {
        return false;
    }
}

function removeTooltip() {
    d3.selectAll('.tool-item').remove();
    d3.select('#tooltip').style('display', 'none');
}

function zoomed() {
    removeTooltip();
    let {x, y, k} = d3.event.transform;
    currentScale = k;
    currentX = x;
    currentY = y;
    group.transition().duration(50).ease(d3.easeLinear).attr("transform", () => `translate(${x + transX * k}, ${y + transY * k}) scale(${k})`)
}

function getCenter(str) {
    if (!str) return null;
    let x, y;
    let tempArr = [];
    let tempX = [];
    let tempY = [];
    str.split(' ').forEach(d => {
        if (!isNaN(d)) {
            tempArr.push(d)
        }
    })

    tempArr.forEach((d, i) => {
        if (i % 2 == 0) {
            tempX.push(parseFloat(d))
        } else {
            tempY.push(parseFloat(d))
        }
    })
    x = (d3.min(tempX) + d3.max(tempX)) / 2;
    y = (d3.min(tempY) + d3.max(tempY)) / 2;
    return [x, y]
}

function renderAllStation() {
    let nameArray = subway.getLineNameArray();
    let len = Math.ceil(nameArray.length / 5);
    let box = d3.select('#menu').append('div')
    .attr('class', 'name-box')
    for (let i = 0; i < len; i++) {
        let subwayCol = box.append('div')
        .attr('class', 'subway-col')
        let item = subwayCol.selectAll('div')
        .data(nameArray.slice(i * 5, (i + 1) * 5))
        .enter()
        .append('div')
        .attr('id', d => d.lid)
        .attr('class', 'name-item')
        item.each(function (d) {
            d3.select(this).append('span').attr('class', 'p_mark').style('background', d.lc);
            d3.select(this).append('span').attr('class', 'p_name').text(d.lb);
            d3.select(this).on('click', d => {
                selected = true;
                d3.selectAll('.origin').style('opacity', 0.1);
                d3.selectAll('.temp').remove();
                renderCurrentLine(d.lid);
                renderCurrentPoint(d.lid);
                let arr = getCenter(d3.select(`path.${d.lid}`).attr('d'));
                svg.call(zoom.transform, d3.zoomIdentity.translate((width / 2 - transX) - arr[0] - (arr[0] + transX) * (currentScale - 1), (height / 2 - transY) - arr[1] - (arr[1] + transY) * (currentScale - 1)).scale(currentScale));
            })
        })
    }
}

function scale(type) {
    if (type && currentScale + scaleStep <= scaleExtent[1]) {
        svg.call(zoom.transform, d3.zoomIdentity.translate((1 - currentScale - scaleStep) * transX - ((1 - currentScale) * transX - currentX) * (currentScale + scaleStep) / currentScale, (1 - currentScale - scaleStep) * transY - ((1 - currentScale) * transY - currentY) * (currentScale + scaleStep) / currentScale).scale(currentScale + scaleStep));
    } else if (!type && currentScale - scaleStep >= scaleExtent[0]) {
        svg.call(zoom.transform, d3.zoomIdentity.translate((1 - (currentScale - scaleStep)) * transX - ((1 - currentScale) * transX - currentX) * (currentScale - scaleStep) / currentScale, (1 - (currentScale - scaleStep)) * transY - ((1 - currentScale) * transY - currentY) * (currentScale - scaleStep) / currentScale).scale(currentScale - scaleStep));
    }
}

上面是大部分代碼,想看全部的可以查看demo。

原文鏈接

大家轉載請註明一下原文 謝謝大家

 

 

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

※公開收購3c價格,不怕被賤賣!

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

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

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

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

[ch02-03] 梯度下降

系列博客,原文在筆者所維護的github上:,
點擊star加星不要吝嗇,星越多筆者越努力。

2.3 梯度下降

2.3.1 從自然現象中理解梯度下降

在大多數文章中,都以“一個人被困在山上,需要迅速下到谷底”來舉例,這個人會“尋找當前所處位置最陡峭的地方向下走”。這個例子中忽略了安全因素,這個人不可能沿着最陡峭的方向走,要考慮坡度。

在自然界中,梯度下降的最好例子,就是泉水下山的過程:

  1. 水受重力影響,會在當前位置,沿着最陡峭的方向流動,有時會形成瀑布(梯度下降);
  2. 水流下山的路徑不是唯一的,在同一個地點,有可能有多個位置具有同樣的陡峭程度,而造成了分流(可以得到多個解);
  3. 遇到坑窪地區,有可能形成湖泊,而終止下山過程(不能得到全局最優解,而是局部最優解)。

2.3.2 梯度下降的數學理解

梯度下降的數學公式:

\[\theta_{n+1} = \theta_{n} – \eta \cdot \nabla J(\theta) \tag{1}\]

其中:

  • \(\theta_{n+1}\):下一個值;
  • \(\theta_n\):當前值;
  • \(-\):減號,梯度的反向;
  • \(\eta\):學習率或步長,控制每一步走的距離,不要太快以免錯過了最佳景點,不要太慢以免時間太長;
  • \(\nabla\):梯度,函數當前位置的最快上升點;
  • \(J(\theta)\):函數。

梯度下降的三要素

  1. 當前點;
  2. 方向;
  3. 步長。

為什麼說是“梯度下降”?

“梯度下降”包含了兩層含義:

  1. 梯度:函數當前位置的最快上升點;
  2. 下降:與導數相反的方向,用數學語言描述就是那個減號。

亦即與上升相反的方向運動,就是下降。

圖2-9 梯度下降的步驟

圖2-9解釋了在函數極值點的兩側做梯度下降的計算過程,梯度下降的目的就是使得x值向極值點逼近。

2.3.3 單變量函數的梯度下降

假設一個單變量函數:

\[J(x) = x ^2\]

我們的目的是找到該函數的最小值,於是計算其微分:

\[J'(x) = 2x\]

假設初始位置為:

\[x_0=1.2\]

假設學習率:

\[\eta = 0.3\]

根據公式(1),迭代公式:

\[x_{n+1} = x_{n} – \eta \cdot \nabla J(x)= x_{n} – \eta \cdot 2x\tag{1}\]

假設終止條件為J(x)<1e-2,迭代過程是:

x=0.480000, y=0.230400
x=0.192000, y=0.036864
x=0.076800, y=0.005898
x=0.030720, y=0.000944

上面的過程如圖2-10所示。

圖2-10 使用梯度下降法迭代的過程

2.3.4 雙變量的梯度下降

假設一個雙變量函數:

\[J(x,y) = x^2 + \sin^2(y)\]

我們的目的是找到該函數的最小值,於是計算其微分:

\[{\partial{J(x,y)} \over \partial{x}} = 2x\]
\[{\partial{J(x,y)} \over \partial{y}} = 2 \sin y \cos y\]

假設初始位置為:

\[(x_0,y_0)=(3,1)\]

假設學習率:

\[\eta = 0.1\]

根據公式(1),迭代過程是的計算公式:
\[(x_{n+1},y_{n+1}) = (x_n,y_n) – \eta \cdot \nabla J(x,y)\]
\[ = (x_n,y_n) – \eta \cdot (2x,2 \cdot \sin y \cdot \cos y) \tag{1}\]

根據公式(1),假設終止條件為\(J(x,y)<1e-2\),迭代過程如表2-3所示。

表2-3 雙變量梯度下降的迭代過程

迭代次數 x y J(x,y)
1 3 1 9.708073
2 2.4 0.909070 6.382415
15 0.105553 0.063481 0.015166
16 0.084442 0.050819 0.009711

迭代16次后,J(x,y)的值為0.009711,滿足小於1e-2的條件,停止迭代。

上面的過程如表2-4所示,由於是雙變量,所以需要用三維圖來解釋。請注意看兩張圖中間那條隱隱的黑色線,表示梯度下降的過程,從紅色的高地一直沿着坡度向下走,直到藍色的窪地。

表2-4 在三維空間內的梯度下降過程

觀察角度1 觀察角度2

2.3.5 學習率η的選擇

在公式表達時,學習率被表示為\(\eta\)。在代碼里,我們把學習率定義為learning_rate,或者eta。針對上面的例子,試驗不同的學習率對迭代情況的影響,如表2-5所示。

表2-5 不同學習率對迭代情況的影響

學習率 迭代路線圖 說明
1.0 學習率太大,迭代的情況很糟糕,在一條水平線上跳來跳去,永遠也不能下降。
0.8 學習率大,會有這種左右跳躍的情況發生,這不利於神經網絡的訓練。
0.4 學習率合適,損失值會從單側下降,4步以後基本接近了理想值。
0.1 學習率較小,損失值會從單側下降,但下降速度非常慢,10步了還沒有到達理想狀態。

代碼位置

ch02, Level3, Level4, Level5

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

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

JavaScript 關於setTimeout與setInterval的小研究

說明

在開發功能“軌跡播放”時,遇到了一個情況。
原先同事已經開發了一版,這次有個新功能:點擊線上任意一點后可以從點擊處重新播放。
看了一下原來的版本,發現同時使用了setTimeout和setInterval,兩者配合實現點線播放。
簡單結構如下

        function test() {
            setInterval(function () {
                console.log("interval");
                                //省略插值方法得到arr
                                (...)
                play(arr);
            }, 2000);
        }
        function play(arr) {
            setTimeout(function () {
                play(arr);
                console.log("setTimeout");
            }, 40);
        }

我覺得這個結構欠妥,兩個定時器配合必定會出現失誤!因此重構了一版,將兩個定時器改為一個,用setInterval解決。
但是此時我並不知道欠妥欠在什麼地方,缺乏理論支持,現在閑下來仔細研究了一下

找問題

在仔細研究了舊版本后,我先把舊版本結構扒了出來,排除其他因素,自己模擬了一個簡單版(就是上面的代碼)
setTimeout:在執行時,是在載入后延遲指定時間后,去執行一次表達式,僅執行一次
setInterval:在執行時,它從載入后,每隔指定的時間就執行一次表達式

  • 實驗一:在使用setInterval和setTimeout方法上,並沒有什麼問題,決定跑一下,結果如下

從結果得出兩點結論

  1. setTimeout與setInterval並不是50倍速度配合運行着
  2. 兩次interval間,timeout運行的次數越來越多,表明setInterval運行間隔越來越長,延遲越來越大
  • 實驗二:加一點人工干預再執行
        function test() {
            setInterval(function () {
                console.log("interval");
                play();
            }, 2000);
        }
        function play() {
                    //延遲執行
            for (var i = 0; i < 100000000; i++) {
                
             }
            setTimeout(function () {
                play();
                console.log("setTimeout");
            }, 40);
        }

從結果得出兩點結論

  1. setInterval可能會隨函數處理時間,減少間隔
  2. 推測,因為Javascript是單線程的,setInterval和setTimeout是放隊列里執行的,很容易受到回調事件影響
  • 實驗三:拖動縮放瀏覽器

從結果得出結論

  1. 當瀏覽器標籤切換到其他頁面,或者瀏覽器最小化,會影響計時器,兩者會出現間隔減小

涉及知識點

綜上實驗結果,網上搜集了一些資料能說明問題:

  1. JavaScript是單線程,但是瀏覽器是多線程,Javascript是瀏覽器多線程中的一個線程。(圖參考自:)
  1. Javascript會把執行的回調函數、瀏覽器的觸發事件、UI渲染事件,先放到隊列中,隊列根據先進先出的規則,依次執行他們,當執行到隊列中的setInterval時很難保證其與setTimeout同步關係還保持。
  2. setInterval無視代碼錯誤:代碼報錯,但是setInterval依舊會按時執行,不會中斷。
  3. setInterval無視網絡延遲:如果調用ajax或其他服務,他不會管是否返回回調,會繼續按時執行。
  4. setInterval不保證執行:因為setInterval會定時執行,如果函數邏輯很長,間隔時間內執行不完,後續方法會被拋棄。
  5. 會受瀏覽器狀態影響,tab切換、最小化等

解決方案

在做軌跡播放時,setInterval的延遲還在可接受範圍之內,但是網上給出的最佳解決方案是用setTimeout做。
setTimeout只會執行一次,在執行完成后,重新啟動新的Timeout,時間runtime計算設置為差時,減少出現間隔越來越大的情況

        function test() {
                    //runTime,計算差時
                        runTime = 1000 - 執行耗時;
            setTimeout(callback, runTime);
        }
        setTimeout(test, 1000);

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

※高價收購3C產品,價格不怕你比較

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

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

3c收購,鏡頭 收購有可能以全新價回收嗎?

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

關係型數據庫幾大範式的理解總結

範式的定義

  • 關係型數據庫中的關係是需要滿足一定條件的,滿足這些不同程度的規範化就叫做範式。

  • 範式按照規範化程度從低到高排序為第一範式,第二範式,第三範式,BC範式,第四範式,第五範式。

前導知識

函數依賴

R(U)是屬性集U的關係模型,X,Y是U的一個子集,對於R(U)中的任一個關係r,不可能存在兩個元組在X上屬性值相同,而在Y上屬性值不同。則稱X函數確定Y,或Y函數依賴X。

  • 說人話:U是表(可能不止一個表,可以是有關係的多個表)的所有列,X,Y分別是這些屬性列的一個子集,也就是若干個屬性,對於所有在X這些屬性上的值一樣的行,在Y上的屬性上也必須一樣,滿足這樣條件的這若干個屬性 X和Y叫稱其函數依賴。
  • X相同則Y必須相同,但X不同Y可以相同,也可以不同
  • 如果Y是X的子集,就叫平凡的函數依賴,一般不考慮這種,因為就是廢話,X整個都相同,子集肯定相同。
  • 如果Y不是X的子集,叫做非平凡的函數依賴
  • 如果Y函數依賴X,那麼X稱為決定因素。
  • 如果Y函數依賴X,但不依賴X的任何一個真子集,也就是X是極小的,那就稱Y完全函數依賴X,否則稱Y部分函數依賴X
  • 如果X決定Y,Y決定Z,且Y不決定X,那麼稱Z對X傳遞函數依賴

碼(鍵)

  • U是屬性全集,K是U的子集,若U完全函數依賴K,則稱K為候選碼,候選碼若有多個,任意選擇一個都可作為主碼,若U部分函數依賴K,則稱K為超碼。顯然,候選碼當然也是超碼,而且是最小的超碼。
  • 包含在任何一個候選碼的屬性都叫主屬性,其他都叫非主屬性
  • 在本書中主碼和候選碼統稱為,屬性集K不是該關係模式(表)的碼,而是另一個關係模式(表)的碼,則稱K為該關係模式(表)的外碼

求候選碼

例子:

舉例

有這樣一個配件管理表WPE(WNO,PNO,ENO,QNT),其中WNO表示倉庫號,PNO表示配件號,ENO表示職工號,QNT表示數量。

有以下約束要求:

(1)一個倉庫有多名職工;

(2)一個職工僅在一個倉庫工作;

(3)每個倉庫里一種型號的配件由專人負責,但一個人可以管理幾種配件;

(4)同一種型號的配件可以分放在幾個倉庫中。

分析表中的函數依賴關係,可以得到:

(1)ENO->WNO;

(2)(WNO,PNO)->QNT

(3)(WNO,PNO)->ENO

(4)(ENO,PNO)->QNT

觀察法?:

候選碼的定義就是一組能決定所有列(某一個元組)的屬性。

所以根據這4個函數依賴關係,(WNO,PNO)顯然肯定是,因為它可以決定QNT,也可以決定ENO,加上它本身,就是屬性全集U了。

而(ENO,PNO),雖然只有一個決定QNT,但是ENO可以單獨決定WNO,所以顯然(ENO,PNO)也就能一起決定QNT和WNO,因此也是候選碼。

六大範式?

第一範式

定義

滿足最基本的條件,每一個分量都是不可分的數據項。

  • 說人話,每一列對應只有一個值。

第二範式

定義

R屬於第一範式,且每一個非主屬性完全函數依賴於任何一個候選碼,則R屬於第二範式

  • 說人話,除了主碼候選碼之外的其他屬性都要完全函數依賴於主碼。
  • 因為任意一個候選碼都能作為主碼,所以,也就是說,如果存在某個屬性不是完全函數依賴於某一個候選碼,可能是部分函數依賴,那就沒了。
  • 比如主鍵是(學號,課程號),但是現在有一個屬性完全函數依賴於學號,而部分函數依賴於(學號,課程號),那就不滿足第二範式。

第三範式

定義

R屬於第二範式,若R中不存在碼X,屬性子集Y,非主屬性Z,使得X決定Y,Y不決定X,Y決定Z,則R屬於第三範式。

  • 說人話,非主屬性必須直接完全函數依賴於主鍵,中間不能有其他函數,即不能是傳遞函數依賴。

BC範式

定義

R屬於第一範式,若X決定Y,且Y不是X的子集時X必含有碼,即每一個決定因素都包含碼,則R屬於BC範式。

  • 說人話, 若R是第一範式,且每個屬性不部分函數依賴於候選碼也不傳遞函數依賴於候選碼,則R是BC範式,具體以下三點。
    • 所有非主屬性對每一個碼都是完全函數依賴。(也是第二範式要求)
    • 所有主屬性對每一個不包含它的碼也是完全函數依賴。(也就是排除了所有屬性對碼的部分依賴)
    • 沒有任何屬性完全函數依賴於非碼的任何一組屬性。(排除傳遞函數依賴)
  • 實際上,BC範式就是在第三範式的基礎上消除了主屬性的傳遞依賴

第四範式

多值依賴

  • 說人話,多值依賴就是一個表中多對多的關係,如果可以分成兩列,這兩列多對多,這就平凡的多值依賴,如果是分成三列,固定某一列的值,其他兩列多對多,這就是非平凡的多值依賴,第四範式要消除的就是非平凡的多值依賴。

  • 函數依賴是特殊的多值依賴,因為多對多其實也是一對多。

定義

R屬於第一範式,對應R的每一個非平凡多值依賴,X->->Y,X都含有碼,則R屬於第四範式。

  • 說人話,在滿足第三範式的基礎上,關係表中不能含有一個實體的兩個或多個相互獨立的多值因子。
  • 或者說,滿足第四範式即要求每個非平凡的多值依賴都含有碼,也就是實際上是函數依賴。

第五範式

定義

第五範式是指關係模式R依賴均由R候選碼所隱含。

這輩子應該不會用到的內容,就不管了。

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

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

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

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

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

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

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

Spring Cloud Alibaba(四)實現Dubbo服務消費

本項目演示如何使用 Spring Cloud Alibaba 完成 Dubbo 的RPC調用。

Spring Cloud與Dubbo

  • Spring Cloud是一套完整的微服務架構方案

  • Dubbo是國內目前非常流行的服務治理與RPC實現方案

由於Dubbo在國內有着非常大的用戶群體,但是其周邊設施與組件相對來說並不那麼完善(比如feign,ribbon等等)。很多開發者使用Dubbo,又希望享受Spring Cloud的生態,因此也會有一些Spring Cloud與Dubbo一起使用的案例與方法出現。

Spring Cloud Alibaba的出現,實現了Spring Cloud與Dubbo的完美融合。在之前的教程中,我們已經介紹過使用Spring Cloud Alibaba中的Nacos來作為服務註冊中心,並且在此之下可以如傳統的Spring Cloud應用一樣地使用Ribbon或Feign來實現服務消費。這篇,我們就來繼續說說Spring Cloud Alibaba 下額外支持的RPC方案:Dubbo

代碼實現

我們通過一個簡單的例子,使用Nacos做服務註冊中心,利用Dubbo來實現服務提供方與服務消費方。這裏省略Nacos的安裝與使用,下面就直接進入Dubbo的使用步驟。

定義 Dubbo 服務接口

創建 ali-nacos-dubbo-api 工程

Dubbo 服務接口是服務提供方與消費方的遠程通訊契約,通常由普通的 Java 接口(interface)來聲明,如 HelloService 接口:

public interface HelloService {
    String hello(String name);
}

為了確保契約的一致性,推薦的做法是將 Dubbo 服務接口打包在jar包中,如以上接口就存放在 ali-nacos-dubbo-api 之中。 對於服務提供方而言,不僅通過依賴 artifact 的形式引入 Dubbo 服務接口,而且需要將其實現。對應的服務消費端,同樣地需要依賴該 artifact, 並以接口調用的方式執行遠程方法。接下來進一步討論怎樣實現 Dubbo 服務提供方和消費方。

實現 Dubbo 服務提供方

創建 ali-nacos-dubbo-provider,端口:9001 工程

pom.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-alibaba</artifactId>
        <groupId>com.easy</groupId>
        <version>1.0.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>ali-nacos-dubbo-provider</artifactId>

    <dependencies>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <!-- API -->
        <dependency>
            <groupId>com.easy</groupId>
            <artifactId>ali-nacos-dubbo-api</artifactId>
            <version>${project.version}</version>
        </dependency>

        <!--dubbo-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

        <!--nacos-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

bootstrap.yaml配置

dubbo:
  scan:
    # dubbo 服務掃描基準包
    base-packages: com.easy.andProvider.service

  #Dubbo 服務暴露的協議配置,其中子屬性 name 為協議名稱,port 為協議端口( -1 表示自增端口,從 20880 開始)
  protocol:
    name: dubbo
    port: -1

  #Dubbo 服務註冊中心配置,其中子屬性 address 的值 “spring-cloud://localhost”,說明掛載到 Spring Cloud 註冊中心
  registry:
    address: spring-cloud://localhost

spring:
  application:
    # Dubbo 應用名稱
    name: ali-nacos-dubbo-provider
  main:
    allow-bean-definition-overriding: true
  cloud:
    # Nacos 服務發現與註冊配置
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

Dubbo Spring Cloud 繼承了 Dubbo Spring Boot 的外部化配置特性,也可以通過標註 @DubboComponentScan 來實現基準包掃描。

實現 Dubbo 服務

HelloService 作為暴露的 Dubbo 服務接口,服務提供方 ali-nacos-dubbo-provider 需要將其實現:

package com.easy.andProvider.service;

import com.easy.and.api.service.HelloService;
import org.apache.dubbo.config.annotation.Service;

@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String hello(String name) {
        return "你好 " + name;
    }
}

import org.apache.dubbo.config.annotation.Service 是 Dubbo 服務註解,僅聲明該 Java 服務實現為 Dubbo 服務

貼上啟動類代碼:

@EnableDiscoveryClient
@EnableAutoConfiguration
public class AndProviderApplication {
    public static void main(String[] args) {
        SpringApplication.run(AndProviderApplication.class);
    }
}

實現 Dubbo 服務消費方

創建 ali-nacos-dubbo-consumer,端口:9103 工程

pom.xml依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>cloud-alibaba</artifactId>
        <groupId>com.easy</groupId>
        <version>1.0.0</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <artifactId>ali-nacos-dubbo-consumer</artifactId>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>com.easy</groupId>
            <artifactId>ali-nacos-dubbo-api</artifactId>
            <version>${project.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-dubbo</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

yaml配置文件

dubbo:
  registry:
    address: spring-cloud://localhost
  cloud:
    subscribed-services: ali-nacos-dubbo-provider

spring:
  application:
    name: ali-nacos-dubbo-consumer
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

實現 Dubbo 服務消費方

HomeController.java

package com.easy.andConsumer;

import com.easy.and.api.service.HelloService;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class HomeController {

    @Reference
    HelloService helloService;

    @GetMapping("/hello")
    public String hello(String name) {
        return helloService.hello("雲天");
    }
}

AndConsumerApplication.java啟動類

package com.easy.andConsumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class AndConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(AndConsumerApplication.class, args);
    }
}

使用示例

示例關聯項目

本示例我們創建了三個項目實現

  • ali-nacos-dubbo-api:定義Dubbo服務接口工程

  • ali-nacos-dubbo-provider:Dubbo服務提供方並向nacos註冊服務,服務名:ali-nacos-dubbo-provider,端口:9001

  • ali-nacos-dubbo-consumer:Dubbo服務消費方並向nacos註冊服務,服務名:ali-nacos-dubbo-consumer,端口:9103

運行示例測試

首先要啟動服務註冊中心 nacos、ali-nacos-dubbo-provider服務及ali-nacos-dubbo-consumer服務

  • 訪問服務消費方地址: http://localhost:9103/hello

返回

你好 雲天

或者你也可以通過 curl 命令執行 HTTP GET 方法

$curl http://127.0.0.1:9103/hello

HTTP 響應為:

你好 雲天

以上結果說明應用 ali-nacos-dubbo-consumer 通過消費 Dubbo 服務,返回服務提供方 ali-nacos-dubbo-provider 運算后的內容。

以上我們完成了 Dubbo 服務提供方和消費方的入門運用,源代碼請直接參考模塊:

資料

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

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

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

Matplotlib入門簡介

Matplotlib是一個用Python實現的繪圖庫。現在很多機器學習,深度學習教學資料中都用它來繪製函數圖形。在學習算法過程中,Matplotlib是一個非常趁手的工具。

一般概念

圖形(figure)
類似於畫布,它包含一個或多個子坐標系(axes)。至少有一個坐標系才能有用。

下面是一段簡單的示例代碼,只是創建了一個子坐標系

import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure() #空figure,沒有坐標系.
fig.suptitle("No Axes on this figure") #設置頂部標題

fig, ax_lst = plt.subplots(2, 2) #一個2 x 2 網格的的坐標系

坐標系(Axes): figure的繪圖區域。一個figure只能有可以有多個Axes,但一個Axes只能位於一個figure中。一個Axes包含兩個(在3D情況下有3個)坐標軸(Axis),Axis的主要作用是限制數據的範圍(可使用Axes的set_xlim()和set_ylim()方法設限制)。每個坐標系有一個標題(title),使用set_title()設置,一個x軸標籤(x-label,使用set_xlabel()設置),一個y軸標籤(y-label,使用set_ylabel()設置)。

坐標軸(Axis): 類似於数字線( number-line-like)的對象,可設置圖表的限制並生成刻度和刻度標籤。Locator對象用來決定刻度的位置。刻度標籤字符串使用Formattor格式化。恰當的Locator和Formattor組合可以有效地控制刻度位置可刻度標籤。

畫家(Artist): 一般來說,所有你能在figure中看到的都使用一個畫家(Artist)(包括Figure, Axes和Axis對象),這其中包含:文本對象(Text), 2D線條(line2D), 集合對象,點(Path)對象等等。當一個figure被渲染時,所有的Artist都會在畫布上回繪圖。大多數Artist被綁定在一個Axes上,不能被多個Axes共享,或從一個Axes移動到另一個。

繪圖函數的輸入類型

所有的繪圖函數期待的輸入類型是np.array或np.ma.masked_array。看起來像數組的類比如np.martrix可能能正常使用。

Matplotlib,pyplot和pylab之間的關係

Matplotlib是整個包,matplotlib.pyplot是Matplotlib中的一個模塊。
對pyplot模塊中的函數來說,總是有一個”當前的”figure和axes。例如在下面的例子中,第一次調用pyplot.plot會創建一個axes,接下來的一系列pyplot.plot調用迴向同一個axes中添加多條線,plt.xlabel, plt.ylabel, plt.title and plt.legend調用回在這個axes中添加標籤,標題和圖例。

x = np.linspace(0, 2, 100)

plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')

plt.xlabel('x label')
plt.ylabel('y label')

plt.title("Simple Plot")

plt.legend()

plt.show()
這段代碼輸出的圖形如下。可以把最後一行的plt.show(),改成plt.savefig("simplePlot.png"),把圖形輸出成png格式的文件。

pylab是一個可方便地把matplotlib.pyplot和numpy批量導入到一個獨立命名空間的模塊,現已被棄用,建議使用pyplot代替。

 

 

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

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

※高價3c回收,收購空拍機,收購鏡頭,收購 MACBOOK-更多收購平台討論專區

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

收購3c瘋!各款手機、筆電、相機、平板,歡迎來詢價!

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

ASP.NET Core gRPC 使用 Consul 服務註冊發現

一. 前言

gRPC 在當前最常見的應用就是在微服務場景中,所以不可避免的會有服務註冊與發現問題,我們使用gRPC實現的服務可以使用 Consul 或者 etcd 作為服務註冊與發現中心,本文主要介紹Consul。

二. Consul 介紹

Consul是一種服務網絡解決方案,可跨任何運行平台以及公共或私有雲來連接和保護服務。它可以讓你發現服務並保護網絡流量。它可以在Kubernetes中使用,實現服務發現和服務網格功能(k8s默認etcd)。 提供安全服務通訊,保護和觀察服務之間的通信,而無需修改其代碼。 提供動態負載平衡, 使用Consul和HAProxy,Nginx或F5自動執行負載均衡器配置。 Consul 可以用於服務發現和服務網格。

翻譯自官網

三. Consul 安裝配置

安裝

Consul 下載地址:

根據自己的系統來選擇,我這裏選擇的是 Windows 版本的,直接解壓即可運行。

啟動

consul agent -dev -ui

本文不詳細介紹Consul使用,如需請自行查看相關資料

四. .NET Core Consul 客戶端的選擇

Consul 提供了 HTTP API 的方式來進行通訊,我們可以直接調用API或者是使用第三方封裝好的客戶端組件,通過Nuget搜索可以發現許多。

這裏面我沒有一一測試,但是目前使用量最多的 Consul 組件是不支持設置 GRPC 健康檢查的,而且 github 也停止了更新。

所以我 Fork 了這個倉庫,然後添加了 GRPC 的健康檢查支持,本文也將使用這個庫,歡迎大家使用:

因為原倉庫已經 Archived 了,所以我才 Fork 了自己改一下,改動很小,不影響原來的穩定性。

Nuget:

Github:

求個star

關於支持 GPRC 健康檢查的好處:

偷個懶,不翻譯了,摘自GRPC官方文檔

五. 註冊GRPC服務與健康檢查

基於前文()的Demo

1.為服務端項目安裝 NConsul.AspNetCore ( )

這裏面對 AspNetCore 做了適配,使用簡單。

2.在 Startup 的 ConfigureServices方法內進行配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddGrpc();

    services.AddConsul("http://localhost:8500")
        .AddGRPCHealthCheck("localhost:5000")
        .RegisterService("grpctest","localhost",5000,new []{"xc/grpc/test"});
}

AddConsul 添加 Consul Server 地址。

AddGRPCHealthCheck 添加 GRPC 健康檢查,即健康檢查走的是 GRPC 協議,該值為 GRPC 服務的地址,不需要path不需要提供 http/https

RegisterService 註冊服務

到這步,還不能啟動運行,如果運行健康檢查是會失敗的。

3.編寫 Health Check 服務 **

對於 GRPC 的健康檢查,官方有標準的定義,新建一個 proto 文件,命名為 HealthCheck.proto

syntax = "proto3";

package grpc.health.v1;

message HealthCheckRequest {
    string service = 1;
}

message HealthCheckResponse {
    enum ServingStatus {
        UNKNOWN = 0;
        SERVING = 1;
        NOT_SERVING = 2;
    }
    ServingStatus status = 1;
}

service Health {
    rpc Check(HealthCheckRequest) returns (HealthCheckResponse);

    rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

這裏面的內容不得更改,是官方標準,資料見後文

這裏編譯一下項目,以便自動生成代碼。

然後,添加一個服務的實現類 HealthCheckService

public class HealthCheckService:Health.HealthBase
{
    public override Task<HealthCheckResponse> Check(HealthCheckRequest request, ServerCallContext context)
    {
        //TODO:檢查邏輯
        return Task.FromResult(new HealthCheckResponse(){Status = HealthCheckResponse.Types.ServingStatus.Serving});
    }

    public override async Task Watch(HealthCheckRequest request, IServerStreamWriter<HealthCheckResponse> responseStream, ServerCallContext context)
    {
        //TODO:檢查邏輯
        await responseStream.WriteAsync(new HealthCheckResponse()
            {Status = HealthCheckResponse.Types.ServingStatus.Serving});
    }
}

示例代碼直接返回了檢查結果,實際使用中應該在這裏編寫檢查邏輯,然後根據情況返回相應的檢查結果。檢查結果有3種情況:

結果類型 說明
Unknown 未知狀態
Serving 正常
NotServing 異常,不能提供服務

最後別忘了註冊服務:

4.測試運行

啟動 GRPC 服務

然後訪問 http://localhost:8500/ui 訪問 Consul 控制台

可以看到服務成功註冊,並且健康檢查也是通過了的。通過控制台日誌,還可以看到健康檢查的請求:

六. 客戶端使用服務發現

客戶端項目安裝 Consul 組件,然後改造下代碼:

static async Task Main(string[] args)
{
    var serviceName = "grpctest";
    var consulClient = new ConsulClient(c => c.Address = new Uri("http://localhost:8500"));
    var services = await consulClient.Catalog.Service(serviceName);
    if (services.Response.Length == 0)
    {
        throw new Exception($"未發現服務 {serviceName}");
    }

    var service = services.Response[0];
    var address = $"http://{service.ServiceAddress}:{service.ServicePort}";

    Console.WriteLine($"獲取服務地址成功:{address}");

    //啟用通過http使用http2.0
    AppContext.SetSwitch(
        "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    var channel = GrpcChannel.ForAddress(address);
    var catClient = new LuCat.LuCatClient(channel);
    var catReply = await catClient.SuckingCatAsync(new Empty());
    Console.WriteLine("調用擼貓服務:"+ catReply.Message);
    Console.ReadKey();
}

通過服務名稱獲取服務地址,然後來進行訪問。

運行測試:

可以看到,成功的從Consul獲取了我們的服務地址,然後調用。

六. 參考資料

  • gRPC in Asp.Net Core :

  • GPRC Health Check Doc:

  • 本文 Demo:

  • 本系列文章目錄:

  • NConsul:

  • by Edison Zhou

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

3c收購,鏡頭 收購有可能以全新價回收嗎?

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

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

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

賣IPHONE,iPhone回收,舊換新!教你怎麼賣才划算?

MySQL權限管理

目錄

設置用戶與權限

一個MySQL系統可能有許多用戶。為了安全起見,root用戶通常只用管理目的。對於每個需要使用該系統的用戶,應該為他們創建一個賬號和密碼。這些用戶名和密碼不必與MySQL之外的用戶名稱和密碼(例如,Linux或NT用戶名和密碼)相同。同樣的原則也適合於root用戶。對於系統用戶和MySQL用戶最好使用不同的密碼,這一點對root用戶尤其應該這樣。
為用戶設置密碼不是必須的,但是強烈建議為所有創建的用戶設定密碼。要建立一個Web數據庫,最好為每個網站應用程序建立一個用戶。你可能會問,“為什麼要這麼做呢?”—-答案在於權限。

用戶管理

我們的MySQL的用戶,都被記錄在了mysql數據庫的user表中,如下圖:

在MySQL數據庫中,一個完整的用戶賬號應該包含用戶名登錄主機,也就是說 用戶名@主機名才是一個完整的用戶賬號。

創建用戶基本語法:

CREATE USER 用戶名@主機名 IDENTIFIED BY '密碼';

刪除用戶基本語法:

DROP USER 用戶名@主機名;

修改用戶密碼:

- 修改自己的密碼
set password = password('新密碼');
- 修改別人的密碼(需要有該權限)
set password for 用戶名@主機名 = password('新密碼');

MySQL權限系統介紹

MySQL的最好特性之一是支持複雜的權限系統權限是對特定對象執行特定操作的權力,它與特定用戶相關。其概念非常類似於文件的權限。擋在MySQL中創建一個用戶時,就賦予了該用戶一定的權限,這些權限指定了用戶在本系統中可以做什麼和不可以做什麼。

最少權限原則

最少權限原則可以用來提高任何計算機系統的安全性。它是一個基本的、但又是非常重要的而且容易為我們忽略的原則。該原則包含如下內容:

一個用戶(或一個進程)應該擁有能夠執行分配給他的任務的最低級別的權限。

該原則同樣適用於MySQL,就像它應用於其他地方一樣。例如,要在網站上運行查詢,用戶並不需要root用戶所擁有的所有權限。因此,我們應該創建另一個用戶,這個用戶只有訪問我們剛剛建立的數據庫的必要權限。

創建用戶:GRANT命令

GRANT和REVOKE命令分別用來授予取消MySQL用戶的權限,這些權限分4個級別。它們分別是:

  • 全局
  • 數據庫

GRANT命令用來創建用戶並賦予他們權限。GRANT命令的常見形式是:

GRANT privileges [columns]
ON item
TO user_name [IDENTIFIED BY 'password']
[REQUIRE ssl_options]
[WITH [GRANT OPTION | limit_options]]

普通寫法可以簡略如下:

GRANT 權限列表 ON 數據庫.表 TO 用戶名@主機名 [IDENTIFIED BY '密碼'];

identified by可以省略,也可以寫出
(1)如果寫了,用戶存在,就是修改用戶的密碼
(2)如果寫了,該用戶不存在,就是創建用戶,同時指定密碼

方括號內的子句是可選的。在本語法中,出現了許多佔位符。第一個佔位符是 privileges,應該是由逗號分開的一組權限。MySQL已經有一組已定義的權限。它們在下一節詳細介紹。
佔位符 columns是可選的。可以用它對每一個列指定權限。也可以使用單列的名稱或者逗號分開的一組列的名稱。

佔位符 item是新權限的所應用於的數據庫或表。可以將項目指定為“.”,而將權限應用於所有數據庫。這叫做賦予全局權限。如果沒有使用在特定的數據庫,也可以通過只指定*完成賦予全局權限。更常見的是,以dbname.*的形式指定數據庫中所有的表,以dbname.tablename的形式指定單個表,或者通過指定tablename來指定特定的列。這些分別表示其他3個可以利用的權限:數據庫、表、列。如果在輸入命令的時候正在使用一個數據庫,tablename本身將被解釋成當前數據庫中的一個表。

user_name應該是用戶登錄MySQL所使用的用戶名。請注意,它不必與登錄系統時所使用的用戶名相同。MySQL中的user_name也可以包含一個主機名。可以用它來區分如itbsl(解釋成itbsl@localhost)和itbsl@somewhere.com。這是非常有用的一項能力,因為來自不同域的用戶經常可能使用同一個名字。這也提高了安全性能,因為可以指定用戶從什麼地方連接到本機,甚至可以指定它們在特定的地方可以訪問那些表和數據庫。

password應該是用戶登錄時使用的密碼。常見的密碼選擇規則在這裏都適用。我們後面將更詳細地講述安全問題,但是密碼應該不容易被猜出來。這意味着,密碼不應該是一個字段單詞或與用戶名相同。理想的密碼應該是大、小寫字母和非字母的組合。

REQUIRE子句允許指定用戶是否必須通過加密套接字連接,或者指定其它的SSL選項,關於SSL到MySQL連接的更多信息,請參閱MySQL手冊。

WITH GRANT OPTION選項,如果指定,表示允許指定的用戶向別人授予自己所擁有的權限。
我們也可以指定如下所示的WITH子句:
MAX_QUERIES_PER_HOUR n
或者
MAX_UPDATES_PER_HOUR n
或者
MAX_CONNECTIONS_PER_HOUR n
這些子句可以指定每個用戶每小時執行的查詢、更新和鏈接的數量。在共享的系統上限制單個用戶的負載時,這些子句是非常有用的。
權限存儲在名為mysql的數據庫中的5個系統中。這些表分別是mysql.user、mysql.db、mysql.host、mysql.tables_priv和mysql.columns_priv。作為GRANT命令的替代,可以直接修改這些表。

權限的類型和級別

MySQL中存在3個基本類型的權限:適用於賦予一般用戶的權限、適用於賦予管理員的權限和幾個特定的權限。任何用戶都可以被賦予這3類權限,但是根據最少權限原則,最好嚴格限定只將管理員類型的權限賦予管理員。

我們應該只賦予用戶訪問他們必須使用的數據庫和表的權限。而不應該將訪問mysql的權限賦予不是管理員的人。mysql數據庫是所有用戶名、密碼等信息存儲的地方。

常規用戶的權限直接與特定的SQL命令類型以及用戶是否被允許運行它們相關。下錶所示的是基本用戶權限。“應用於”列下面的對象給出了該類型權限可以授予的對象。

用戶的權限

權限 應用於 描述
SELECT 表、列 允許用戶從表中查詢行(記錄)
INSERT 表、列 允許用戶在表中插入新行
UPDATE 表、列 允許用戶修改現存表裡行中的值
DELETE 允許用戶刪除現存表的行
INDEX 允許用戶創建和拖動特定表索引
ALTER 允許用戶改變現存表的結構,例如,可添加列、重命名列或表、修改列的數據類型
CREATE 數據庫、表 允許用戶創建新數據庫或表。如果在GRANT中指定了一個特定的數據庫或表,它們只能夠創建該數據庫或表,即它們必須首先刪除(drop)它
DROP 數據庫、表 允許用戶拖動(刪除)數據庫或表

從系統的安全性方面考慮,適於常規用戶的權限大多數是相對無害的。ALTER權限通過重命名表可能會影響權限系統,但是大多數用戶需要它。安全性常常是可用性與保險性的折中。遇到ALTER的時候,應當做出自己的選擇,但是通常還是會將這個權限授予用戶。

下面的表給出了適用於管理員用戶使用的權限。
可以將這些權限授予非管理員用戶,這樣做的時候要非常小心。
FILE權限有些不同,它對普通用戶非常有用,因為它可以將數據從文件載入數據庫,從而可以節省許多時間,否則,每次將數據輸入數據庫都需要重新輸入,這很浪費時間。
然而,文件載入可以用來載入MySQL可識別的任何文件,包括屬於其他用戶的數據庫和潛在的密碼文件。授予該權限的時候需要小心,或者自己為用戶載入數據。

管理員權限

權限 描述
CREATE TEMPORARY TABLES 允許管理員在CREATE TABLE語句中使用TEMPORARY關鍵字
FILE 允許將數據從文件讀入表,或從表讀入文件
LOCK TABLES 允許使用LOCK TABLES語句
PROCESS 允許管理員查看屬於所有用戶的服務器進程
RELOAD 允許管理員重新載入授權表、清空授權、主機、日誌和表
REPLICATION CLIENT 允許管理員重新載入授權表、和從機(Slave)上使用SHOW STATUS
REPLICATION SLAVE 允許複製從服務器連接到主服務器
SHOW DATABASES 允許使用SHOW DATABASES語句查看所有數據庫列表。沒有這個權限,用戶只能看到他們能夠看到的數據庫
SHUTDOWN 允許管理員關閉MySQL服務器
SUPER 允許管理員關閉屬於任何用戶的的線程

特別的權限

權限 描述
ALL 授予上面兩個表列表的所有權限。也可以將ALL寫成ALL PRIVILEGES
USAGE 不授予權限。這創建一個用戶並允許他登錄,但是不允許進行任何操作。通常會在以後授予該用戶更多的權限

REVOKE命令

與GRANT相反的命令是REVOKE。它用來從一個用戶收回權限。在語法上與GRANT非常相似:

REVOKE privileges [(columns)]
ON item
FROM user_name

中文翻譯:

REVOKE 權限列表 ON 數據庫.對象 FROM 用戶名@主機名;

如果已經給出了WITH GRANT OPTION子句,可以按如下方式撤銷它(以及所有其他權限):

REVOKE ALL PRIVILEGES, GRANT
FROM user_name

權限立即生效

當我們修改用戶權限之後,如果想不重啟立即生效,需要執行以下flush privileges,這樣能快速刷新權限

FULSH PRIVILEGES;

使用GRANT和REVOKE的例子

要創建一個管理員,可以輸入如下所示的命令:

grant all on * to fred identified by 'mnb123' with grant option;

以上命令授予了用戶名為fred、密碼為mnb123的用戶使用所有數據庫的所有權限,並允許他向其他人授予這些權限。

如果不希望用戶在系統中存在,可以按如下方式撤銷:

revoke all privileges, grant from red;

現在,我們可以按如下方式創建一個沒有任何權限的常規用戶:

grant usage on books.* to sally identified by 'magic123';

在與sally交談后,我們對她需要進行的操作有了一定的了解,因此按如下方式可以授予她適當的權限:

grant select, insert, update, delete, index, alter, create, drop on books.* to sally;

請注意,要完成這些,並不需要指定sally密碼。

如果我們認為sally權限過高,可能會決定按如下方式減少一些權限:

revoke alter, create, drop on books.* from sally;

後來,當她不再需要使用數據庫時,可以按如下方式撤銷所有的權限:

revoke all on books.* from sally;

參考資料:

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

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

平板收購,iphone手機收購,二手筆電回收,二手iphone收購-全台皆可收購

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

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

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

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

Spring Boot (一) 校驗表單重複提交

一、前言

在某些情況下,由於網速慢,用戶操作有誤(連續點擊兩下提交按鈕),頁面卡頓等原因,可能會出現表單數據重複提交造成數據庫保存多條重複數據。

存在如上問題可以交給前端解決,判斷多長時間內不能再次點擊保存按鈕,當然,如果存在聰明的用戶能夠繞過前端驗證,後端更應該去進行攔截處理,下面小編將基於SpringBoot 2.1.8.RELEASE環境通過AOP切面+ 自定義校驗註解+ Redis緩存來解決這一問題。

二、Spring Boot 校驗表單重複提交操作

1、pom.xml中引入所需依賴

<!-- ==================  校驗表單重複提交所需依賴 ===================== -->
<!-- AOP依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、application.yml中引入Redis配置

spring:
  redis:
    # Redis數據庫索引(默認為0)
    database: 0
    # Redis服務器地址
    host: 127.0.0.1
    # Redis服務器連接端口
    port: 6379
    timeout: 6000
    # Redis服務器連接密碼(默認為空)
    #      password:
    jedis:
      pool:
        max-active: 1000  # 連接池最大連接數(使用負值表示沒有限制)
        max-wait: -1      # 連接池最大阻塞等待時間(使用負值表示沒有限制)
        max-idle: 10      # 連接池中的最大空閑連接
        min-idle: 5       # 連接池中的最小空閑連接

3、自定義註解 @NoRepeatSubmit

// 作用到方法上
@Target(ElementType.METHOD)
// 運行時有效
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
    /**
     * 默認時間3秒
     */
    int time() default 3 * 1000;
}

4、AOP 攔截處理

注:這裏redis存儲的key值可由個人具體業務靈活發揮,這裏只是示例
ex:單用戶登錄情況下可以組合token + url請求路徑,多個用戶可以同時登錄的話,可以再加上ip地址

@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {

    @Autowired
    RedisUtil redisUtil;

    /**
     * <p> 【環繞通知】 用於攔截指定方法,判斷用戶表單保存操作是否屬於重複提交 <p>
     *
     *      定義切入點表達式: execution(public * (…))
     *      表達式解釋: execution:主體    public:可省略   *:標識方法的任意返回值  任意包+類+方法(…) 任意參數
     *
     *      com.zhengqing.demo.modules.*.api : 標識AOP所切服務的包名,即需要進行橫切的業務類
     *      .*Controller : 標識類名,*即所有類
     *      .*(..) : 標識任何方法名,括號表示參數,兩個點表示任何參數類型
     *
     * @param pjp:切入點對象
     * @param noRepeatSubmit:自定義的註解對象
     * @return: java.lang.Object
     */
    @Around("execution(* com.zhengqing.demo.modules.*.api.*Controller.*(..)) && @annotation(noRepeatSubmit)")
    public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

            // 拿到ip地址、請求路徑、token
            String ip = IpUtils.getIpAdrress(request);
            String url = request.getRequestURL().toString();
            String token = request.getHeader(Constants.REQUEST_HEADERS_TOKEN);

            // 現在時間
            long now = System.currentTimeMillis();

            // 自定義key值方式
            String key = "REQUEST_FORM_" + ip;
            if (redisUtil.hasKey(key)) {
                // 上次表單提交時間
                long lastTime = Long.parseLong(redisUtil.get(key));
                // 如果現在距離上次提交時間小於設置的默認時間 則 判斷為重複提交  否則 正常提交 -> 進入業務處理
                if ((now - lastTime) > noRepeatSubmit.time()) {
                    // 非重複提交操作 - 重新記錄操作時間
                    redisUtil.set(key, String.valueOf(now));
                    // 進入處理業務
                    ApiResult result = (ApiResult) pjp.proceed();
                    return result;
                } else {
                    return ApiResult.fail("請勿重複提交!");
                }
            } else {
                // 這裡是第一次操作
                redisUtil.set(key, String.valueOf(now));
                ApiResult result = (ApiResult) pjp.proceed();
                return result;
            }
        } catch (Throwable e) {
            log.error("校驗表單重複提交時異常: {}", e.getMessage());
            return ApiResult.fail("校驗表單重複提交時異常!");
        }

    }

}

5、其中用到的Redis工具類

由於太多,這裏就不直接貼出來了,可參考文末給出的案例demo源碼

三、測試

在需要校驗的方法上加上自定義的校驗註解@NoRepeatSubmit即可

@RestController
public class IndexController extends BaseController {

    @NoRepeatSubmit
    @GetMapping(value = "/index", produces = "application/json;charset=utf-8")
    public ApiResult index() {
        return ApiResult.ok("Hello World ~ ");
    }

}

這裏重複訪問此indexapi請求以模擬提交表單測試

第一次訪問

多次刷新此請求,則提示請勿重複提交!

四、總結

實現思路
  1. 首先利用AOP切面在進入方法前攔截進行表單重複提交校驗邏輯處理
  2. 通過Rediskey-value鍵值對存儲需要的邏輯判斷數據【ex:key存儲用戶提交表單的api請求路徑,value存儲提交時間】
  3. 邏輯處理
    第一次提交時存入相應數據到redis中
    當再次提交保存時從redis緩存中取出上次提交的時間與當前操作時間做判斷,
    如果當前操作時間距離上次操作時間在我們設置的’判斷為重複提交的時間(3秒內)’則為重複提交直接返回重複提交提示語句或其它處理,
    否則為正常提交,進入業務方法處理…
補充

如果api遵從的是嚴格的Restful風格@PostMapping用於表單提交操作,則可不用自定義註解方式去判斷需要校驗重複提交的路徑,直接在aop切面攔截該請求路徑後,通過反射拿到該方法上的註解是否存在@PostMapping如果存在則是提交表單的api,即進行校驗處理,如果不存在即是其它的@GetMapping@PutMapping@DeleteMapping操作…

本文案例demo源碼

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

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

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

※想要讓你的商品在網路上成為最夯、最多人討論的話題?

※高價收購3C產品,價格不怕你比較

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