主頁 >  其他 > 騰訊AlloyTeam團隊給 VSCode 貢獻400+行核心代碼增強其插件化能力

騰訊AlloyTeam團隊給 VSCode 貢獻400+行核心代碼增強其插件化能力

2020-12-23 12:51:42 其他

作者:enoyao,騰訊工程師

在前幾天騰訊檔案 AlloyTeam 給 VSCode 合入了大概 400 行核心代碼,主要涉及到 VSCode 配置化的部分,增強其插件化能力,提供更多的匹配介面,整理部分代碼結構和補充功能單測,

由于騰訊檔案最近在完善我們的配置化系統,在完善的程序也探索了多種實作方案,也分析了很多產品的實作方式,如大名鼎鼎的 VSCode,我們也希望把我們積累經驗貢獻給開源社區,一起共同進步,

其中代碼的提交者為 AlloyTeam 的工程師 @Wscats ,而合入代碼的是微軟 VSCode 團隊現主要負責人之一 Alexdima,也是 Monaco Editor 負責人(VSCode 前身),也是同 Erich Gamma (VSCode 之父) 來自蘇黎世同一個團隊,特別感謝他和他團隊的支持,還幫我們挖坑的代碼寫了不少單測...

由于我們是給 VSCode 貢獻了這部分代碼,那我們就從 VSCode 本身開始聊起,我們給 VSCode 的配置化貢獻了什么?我相信大部分的開發者都使用過 VSCode,所以配置化應該不陌生,由于使用者眾多,任何編輯器其實都不能做到面面俱到去滿足所有的使用者,如果什么用戶的需求都要滿足,就需要把所有的功能都塞進去,這不但臃腫,還不好維護,

所以我們需要配置化去豐富和拓展,減輕編輯器本身的包袱,把部分內容移交給用戶/合作方去定制,就如我們可以在 VSCode 的設定面板選擇編輯器的顏色,更換它的主題背景,

也可以在快捷鍵面板里面系結或者解綁我們的快捷鍵,更換我們的字體大小和改變我們的懸浮資訊等,這些其實都離不開背后實作的一套配置化系統,

上面舉例的都是有默認的配置,可以通過面板去更改的,當然還有些隱藏的配置我們無需在面板改變也能實作配置,比如縮小 VSCode 的界面大小,某些功能就會自動隱藏,這種也是屬于配置化,

我們除了通過面板可視化操作,還可以通過插件來配置界面,VSCode 中插件的核心就是一個組態檔 package.json,里面擁有提供了配置點,只需按要求撰寫正確的配置點就可以改變 VSCode 的視圖狀態,里面最主要的欄位就是 contributes 欄位:

  • contributes.configuration:插件有哪些可供用戶配置的選項,提供的界面跟上面切背景顏色棉棒相似

  • contributes.configurationDefaults:覆寫 vscode 默認的一些編輯器配置

  • contributes.commands:向 vscode 的命令系統注冊一些可供用戶呼叫的命令

  • contributes.menus:擴展選單

  • ...

這是更換編輯器各個位置顏色的配置引數,很簡單明了,

{
    "colors": {
        "activityBar.background": "#333842",
        "activityBar.foreground": "#D7DAE0",
        "activityBarBadge.background": "#528BFF"
    }
}

里面的代碼思路其實就是挖了一個給第三方,然后支持引數的填入,下面代碼就是一個示例,把組態檔的顏色讀取出來,然后生成一個新的顏色規則,達到控制面板背景顏色的功能,

const color = theme.getColor(registerColor('activityBar.background'));
if (color) {
    collector.addRule(
        `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
    );
}

上面這個最基本的能力在代碼實作里面應該是毫無難度的,只需要挖空一個配置點即可,但是實際肯定會再復雜點,此時如果用戶想在此功能基礎上繼續做配置,比如編輯器在 Win 系統的時候顏色變深,在 Mac 系統的時候顏色變淺.

if (color) {
    if (isMacintosh) {
        color = darken(color);
    }
    if (isWindows) {
        color = lighter(color);
    }
    collector.addRule(
        `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
    );
}

這里的就需要知會開發者,講道理對開發者來說難度也不是很大,無非就是往代碼里面在插入幾段條件判斷的代碼而已嘛,但是某一天用戶說我又得改了,我想在解析度大于 855 的時候顏色變深,在解析度小于等于 855 的時候顏色變淺,并且遇到 Linux 系統也得顏色變深,那此時再變更代碼來滿足客戶的需求,不得繼續加代碼如下了:

if (color) {
    if (isMacintosh || window.innerWidth > 855) {
        color = darken(color);
    }
    if (isLinux) {
        color = darken(color);
    }
    if (isWindows || window.innerWidth <= 855) {
        color = lighter(color);
    }
    collector.addRule(
        `.monaco-workbench .activitybar > .content > .home-bar > .home-bar-icon-badge { background-color: ${color}}`
    );
}

那開發寶寶那能遭得住,誰知道那天的又得改呢,要知道編輯器用戶可是上千萬啊,用戶的需求可是花里胡哨,怎么接得住,

那開發的自己去定制規范,不能讓你隨意來,我提供變暗和變深的介面,但是規則我不再負責寫了,請用戶自己提供,所以開發可能會繼續調整下代碼:

class Color {
    color = theme.getColor(registerColor('activityBar.background'));

    @If(isLinux)
    @If(isMacintosh || window.innerWidth > 855)
    darken() {
        return darken(this.color);
    }

    @If(userRule1)
    @If(userRule2)
    @If(userRule3)
    @If([isWindows, window.innerWidth <= 855].includes(true))
    lighter() {
        return lighter(this.color);
    }
}

上面的只是列了下偽代碼,開發者想得很簡單,只提供純粹的 darken 和 lighter,不希望與頻繁的條件運算式耦合,所以可能會做判斷條件的前置化,那么后續開發者只需疊加裝飾器即可給用戶增加配置,并且可以動態保留一個裝飾器 @If(userRule) 作為組態檔的洞口,

然后提供官方配置檔案讓他們寫類似的 package.json 檔案填寫對應的引數,那么壓力就會轉嫁到使用者或者接入者身上,

這種寫法看似美好,但會出現很多致命情況,darkenlighter 在執行前已經被帶條件運算式給裝飾了,后面如果二次執行 darkenlighter 方法則不會再執行裝飾器中條件運算式的判斷,本質上這兩個函式的 descriptor.value 已經被覆寫,邏輯從根本上發生了改變,

export const If = (condition: boolean) => {
    console.log(condition);
    return (target: any, name?: any, descriptor?: any) => {
        const func = descriptor?.value;
        if (typeof func === 'function') {
            descriptor.value = function (...args: any) {
                return condition && func.apply(this, args);
            };
        }
    };
};

而正常情況下客戶端側 isLinuxisMacintoshisWindows 是不會發生改變的,但是 window.innerWidth 在客戶端卻是有可能持續發生變化,所以一般情況下對待客戶端環境經常變化的值或者需要通過作用域判斷值,我不建議寫成上面裝飾器暴露介面的方案,但是如果這是一個比較固定的配置值,那么這種方案配合 webpackDefinePlugin 會有意外的識訓,

new webpack.DefinePlugin({
    isLinux: JSON.stringify(true),
    VERSION: JSON.stringify('5fa3b9'),
    BROWSER_SUPPORTS_HTML5: true,
    TWO: '1+1',
    'typeof window': JSON.stringify('object'),
    'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV),
});

但是我們很多時候是需要在運行時候進行配置化,上述的其實大部分都能算是靜態的配置(俗話說是寫死的),比如 if(window.innerWidth > 855) 這個配置引數:

左邊 window.innerWidth 在運行時候是變化的,右邊 855 在代碼是寫死的,所以我們一般得把這整段扣一個洞出來進行外部的配置化,一般我們會選用 json 去描述這份配置,

在 VSCode 等應用中,很多地方你沒看到 json 檔案去配置,那是因為大部分情況給你提供可視化界面去修改配置,但你要知道本質是改動了 json 的組態檔去達到目的的,比如上面的 if(isMacintosh || window.innerWidth > 855) 就被扣到外面的 json 檔案中,

// if(isMacintosh || window.innerWidth > 855) ...
// if(isWindows || window.innerWidth <= 855) ...
// ↓

{
    "darken": { "when": "isMacintosh || window.innerWidth > 855" },
    "lighter": { "when": "isWindows || window.innerWidth <= 855" }
}

你一般會需要接入方或者使用者寫成上面類似的檔案,然后通過服務器配置系統,比如:七彩石,下發到客戶端,然后把貢獻點放入裝飾器中洞口,再執行對應的配置邏輯,大概如下:

@If(JSON.darken)
darken() {
return darken(this.color);
}

@If(JSON.lighter)
lighter() {
return lighter(this.color);
}

JSON.darkenJSON.lighter 分別是對應 JSON 檔案中的配置項,所以實際在代碼運行時的時候接受的字串引數:

@If("isMacintosh || window.innerWidth > 855")
darken() {
return darken(this.color);
}

@If("isWindows || window.innerWidth <= 855")
lighter() {
return lighter(this.color);
}

這是大部分配置化繞不開的問題,簡單的配置只需要傳承好字串語意即可,但是復雜的配置化可能是帶有條件運算式,代碼語法等東西,截一張 VSCode 官方插件的配置代碼,滿滿都是配置運算式,

本質上這些字串最終是要決議為布林值,作為開關去啟動 darken 或者 lighter 介面的,所以這里需要花費一些代價去實作運算式決議器,和字串轉義解釋引擎,

  • "window.innerWidth" => window.innerWidth

  • "isWindows" => isWindows

  • "isMacintosh || window.innerWidth > 855" => true/false

這個程序中還需要實作校驗函式,如果識別到是非法的字串則不允許決議,免得非法啟動配置介面,

  • "isMacintosh || window.innerWidth > 855" => 合法配置引數

  • "isMacintosh |&&| window.innerWidth > 855" => 非法配置引數

  • "isMacintosh \\// window.innerWidth > 855" => 非法配置引數

這種引擎的實作設計其實還有一種更暴力的解決方案,就是把讀取的配置字串完全交給 eval 去處理,這當然可以很快去實作,但是還是剛才上面說到的問題,這個東西如果接受了一段非法的字串,就會很容易執行一些非法的腳本,絕對不是一個最優的方案,

eval('window.innerWidth > 855'); // true 或者 false
{
    "darken": { "when": "isMacintosh || window.innerWidth > 855" },
    "lighter": { "when": "isWindows || window.innerWidth <= 855" }
}

那介紹下我們的解決方案,這里先讀取 json 檔案,定位到關鍵 when: xxx 這些地方(VSCode 目前只能暴露 when 對外匹配,騰訊檔案實際還沒開源的代碼是可以實作暴露更多的鍵值規則給使用方去匹配),不管后端配置系統讀取和前端配置系統讀取,解題思路都是一樣的,

然后讀取條件運算式字串 "isMacintosh || window.innerWidth > 855",并按照運算式的優先級拆解成幾個部分,放入下面的 contextMatchesRules 去匹配預埋的作用域回傳布林值(VSCode 只做到按對應的鍵值去決議,騰訊檔案可以做到對整個 JSON 配置表的鍵值掃描決議),

context.set('isMacNative', isMacintosh && !isWeb);
context.set('isEdge', _userAgent.indexOf('Edg/') >= 0);
context.set('isFirefox', _userAgent.indexOf('Firefox') >= 0);
context.set('isChrome', _userAgent.indexOf('Chrome') >= 0);
context.set('isSafari', _userAgent.indexOf('Safari') >= 0);
context.set('isIPad', _userAgent.indexOf('iPad') >= 0);
context.set(window.innerWidth, () => window.innerWidth);

contextMatchesRules(context, ['isMacintosh']); // true
contextMatchesRules(context, ['isEdge']); // false
contextMatchesRules(context, ['window.innerWidth', '>=', '800']); // true

其實 VSCode 只是實作了很簡單的運算式決議就支撐起了上萬個插件的配置,

因為 VSCode 有完善的檔案,足夠大的體量去定制規范,對開發者能做到了強約束,

那說明上面這些決議器其實在有約束的情況下,不亂增加規則,正常情況都是夠用的,但是能用或者夠用不代表好用開源專案和商業化專案對用戶側的約束和規范不可能一樣的,騰訊檔案基本把整個決議器實作完整了,并完善了 VSCode 的決議器,賦予其更多的配置功能,后續還會繼續推動并完善整個決議器,其實目前 VSCode 這方面還不是最完整的,

  • 支持變數

  • 支持常量:布林值、數字、字串

  • 支持正則

  • 支持全等intypeof

  • 支持全等=、不等!

  • 支持與&&、或||

  • 支持大于>、小于<、大于等于>=、小于等于<=的比較運算

  • 支持非!等邏輯運算

我們內部實作的的配置決議器支持上述所有的方法,這里再具體講述下思路:

我們使用下面這個再復雜的例子來概括不同的情況:

"when": "canEdit == true || platform == pc && window.innerWidth >= 1080"

我們可以封裝一個 deserialize 方法去決議 "when": "canEdit == true || platform == pc && window.innerWidth >= 1080" 這段字串,里面涉及了 ==,&&,>= 三個運算式的決議,使用 indexOfsplit 進行分詞,一般切割成三部分,key、type 和 value,特殊情況 canEdit == true,只要有 key 和 value 即可,

根據優先級,先拆解 || 再拆解 && 這兩個運算式

const _deserializeOrExpression: ContextKeyExpression | undefined = (
    serialized: string,
    strict: boolean
) => {
    // 先解 ||
    let pieces = serialized.split('||');
    // 再解 &&
    return ContextKeyOrExpr.create(
        pieces.map((p) => _deserializeAndExpression(p, strict))
    );
};

const _deserializeAndExpression: ContextKeyExpression | undefinedn = (
    serialized: string,
    strict: boolean
) => {
    let pieces = serialized.split('&&');
    return ContextKeyAndExpr.create(
        pieces.map((p) => _deserializeOne(p, strict))
    );
};

然后再拆解其他運算式,注意代碼決議的順序非常重要,比如有些時候你需要增加 !== 這種運算式的決議,那么一定注意先決議 == 再決議 !== 不然會拆解有誤,代碼的決議順序也決定運算式的執行優先級,由于大部分都是字串比對,所以一般也無需比對型別,特殊情況在使用大于和小于號的時候,如果出現 5 < '6' 也是判斷執行成功的,

const _deserializeOne: ContextKeyExpression = (
    serializedOne: string,
    strict: boolean
) => {
    serializedOne = serializedOne.trim();

    if (serializedOne.indexOf('!=') >= 0) {
        let pieces = serializedOne.split('!=');
        return ContextKeyNotEqualsExpr.create(
            pieces[0].trim(),
            this._deserializeValue(pieces[1], strict)
        );
    }

    if (serializedOne.indexOf('==') >= 0) {
        let pieces = serializedOne.split('==');
        return ContextKeyEqualsExpr.create(
            pieces[0].trim(),
            this._deserializeValue(pieces[1], strict)
        );
    }

    if (serializedOne.indexOf('=~') >= 0) {
        let pieces = serializedOne.split('=~');
        return ContextKeyRegexExpr.create(
            pieces[0].trim(),
            this._deserializeRegexValue(pieces[1], strict)
        );
    }

    if (serializedOne.indexOf(' in ') >= 0) {
        let pieces = serializedOne.split(' in ');
        return ContextKeyInExpr.create(pieces[0].trim(), pieces[1].trim());
    }

    if (serializedOne.indexOf('>=') >= 0) {
        const pieces = serializedOne.split('>=');
        return ContextKeyGreaterEqualsExpr.create(
            pieces[0].trim(),
            pieces[1].trim()
        );
    }

    if (serializedOne.indexOf('>') >= 0) {
        const pieces = serializedOne.split('>');
        return ContextKeyGreaterExpr.create(pieces[0].trim(), pieces[1].trim());
    }

    if (serializedOne.indexOf('<=') >= 0) {
        const pieces = serializedOne.split('<=');
        return ContextKeySmallerEqualsExpr.create(
            pieces[0].trim(),
            pieces[1].trim()
        );
    }

    if (serializedOne.indexOf('<') >= 0) {
        const pieces = serializedOne.split('<');
        return ContextKeySmallerExpr.create(pieces[0].trim(), pieces[1].trim());
    }

    if (/^\!\s*/.test(serializedOne)) {
        return ContextKeyNotExpr.create(serializedOne.substr(1).trim());
    }

    return ContextKeyDefinedExpr.create(serializedOne);
};

最終 when 會被決議為這種樹結構,type 是預先定義對運算式的轉義,如下表所示:

這里留了一個很有意思的 Defined 介面,它不屬于任何的運算式語法,但可以后續這樣使用:
export class RawContextKey<T> extends ContextKeyDefinedExpr {

 private readonly _defaultValue: T | undefined;

 constructor(key: string, defaultValue: T | undefined) {
  super(key);
  this._defaultValue = defaultValue;
 }

 public toNegated(): ContextKeyExpression {
  return ContextKeyExpr.not(this.key);
 }

 public isEqualTo(value: string): ContextKeyExpression {
  return ContextKeyExpr.equals(this.key, value);
 }

 public notEqualsTo(value: string): ContextKeyExpression {
  return ContextKeyExpr.notEquals(this.key, value);
 }
}

const Extension = new RawContextKey<string>('resourceExtname', undefined);
Extension.isEqualTo("abc");
const ExtensionContext = new Maps();
ExtensionContext.setValue("resourceExtname", "abc");
console.log(contextMatchesRules(ExtensionContext, Extension.isEqualTo("abc")));

在任何地方創建一個 ExtensionContext 作用域,然后建立鍵值對來使用 isEqualTo 進行等值比對,

條件運算式分詞規則再用一張圖來表示,以下面這顆樹生成的思路為例子,遵循我們常用運算式的一些語法規范和優先級規則,優先切割 || 兩邊所有的運算式,然后遍歷兩邊的運算式往下去切割 && 運算式,切完所有的 ||&& 再處理子節點的 !===>= 等這些符號,

當我們把切割完整個 when 配置項,會把這個樹結構結合上面的 ContextKey-Type 映射表,轉換出下面的 JS 物件,上面的存盤著 ContextKeyOrExprContextKeyAndExprContextKeyEqualsExprContextKeyGreaterOrEqualsExpr 這些重要的規則類,將該 JS 物件存盤到 MenuRegistry 里面,后面只需遍歷 MenuRegistry 就可以把里面存著的 key 和 value 根據 type 運算規則取出來進行比對并回傳布林值,

when: {
    ContextKeyOrExpr: {
        expr: [{
            ContextKeyDefinedExpr: {
                key: "canEdit",
                type: 2
            }
        }, {
            ContextKeyAndExpr: {
                expr: [{
                    ContextKeyEqualsExpr: {
                        key: "platform",
                        type: 4,
                        value: "pc",
                    },
                    ContextKeyGreaterOrEqualsExpr: {
                        key: "window.innerWidth",
                        type: 12,
                        value: "1080",
                    }
                }],
                type: 6
            }
        }],
        type: 9
    }
}

我們要上面也說了,"window.innerWidth"canEdit"platform" 這些是字串,不是真正可用于判斷的值,這些 key 有些是運行時才會得到值,有些是在某個作用域下才會得到值,我們也需要將這些 key 進行轉化,我們借鑒了 Vscode 的做法,在 Vscode 中,它會將這部分邏輯交給一個叫 context 的物件進行處理,它提供兩個關鍵的介面 setValuegetValue 方法,簡單的實作如下,

export class Maps {
    protected readonly _values = new Map<string, any>();
    public get values() {
        return this._values;
    }

    public getValue(key: string): any {
        if (this._values.has(key)) {
            let value = this._values.get(key);
            // 執行獲取最新的值,并回傳
            if (typeof value == 'function') {
                value = value();
            }
            return value;
        }
    }

    public removeValue(key: string): boolean {
        if (key in this._values) {
            this._values.delete(key);
            return true;
        }
        return false;
    }

    public setValue(key: string, value: any) {
        this._values.set(key, value);
    }
}

它本質是維護著一份 Map 物件,我們需要把 "window.innerWidth"canEdit"platform" 這些值系結進去,從而讓 key 可以轉化對應的變數或者常量,這里注意的是我們 getValue 里面有一段判斷是否是函式,如果是函式則執行獲取最新的值,這個地方非常關鍵,因為我們去收集 window.innerWidth 這些的值很可能是實時變化的,我們需要在判斷的時候觸發這個回呼獲取真正最新的值,保證條件運算式決議最終結果的正確性,當然如果是 platform 或者 isMacintosh 這些在運行的時候通常不會變,就直接寫入即可,不需要每次都觸發回呼來獲取最新的值,

const context = new Context();

context.setValue('platform', 'pc');
context.setValue('window.innerWidth', () => window.innerWidth);
context.setValue(
    'canEdit',
    window.SpreadsheetApp.sheetStatus.rangesStatus.status.canEdit
);

當然有些常量或者全域的固定變數,事先預埋就好,比如字串 "true" 肯定對應就是 true,字串 "false" 肯定對應就是 false

context.setValue(JSON.stringify(true), true);
context.setValue(JSON.stringify(false), false);

以后如果要交給第三方配置,我們就需要提前在這里規定好 key 值系結的變數和常量,輸出一份配置檔案就可以讓第三方使用這些關鍵 key 來進行個性化配置,

那么最后只要封裝上面例子用到 contextMatchesRules 方法,先讀取 json 組態檔為物件,遍歷出每一個 when,并關聯 context 最終得出一個布林值,這個布林值其實來之不易,生成的最終其實是一個帶布林值的策略樹,這棵樹的前后最終節點的目的都是為了求出布林值,如果是服務端下發的動態配置本質可以是 0 和 1 的策略樹即可,

要知道實作一個強大的配置系統還能保證整體的質量和性能確實是很不容易的,上圖是在我們實際專案其中的一個改造例子,左邊的運算式收集會轉化成右邊運算式配置,左邊所有的 if 會收到配置表里面轉嫁給接入方或者可視化配置界面,以后每當變動配置表的資訊,就可以配合作用域收集得到全信的策略樹來渲染視圖或者更新視圖,

騰訊檔案團隊

歡迎更多志同道合的人加入我們騰訊檔案 AlloyTeam 團隊,一起跟騰訊檔案,AlloyTeam 和開源社區成長,介紹下我們團隊部分開源專案和成員,

  • AlloyTeam 官網

  • AlloyTeam 開源專案

  • AlloyTeam 創始人,HTML5 夢工場深圳負責人 TAT.Kinvix

  • Omi 核心開發者 Wscats

  • Hippy 核心開發者 XuQingKuang

  • 《深入理解 Vue.js》美女作者 被刪

  • 公眾號@前端控作者 Cooper

  • Hexo Even 主題作者 YueXun

總結

關于這方面的相關文章不多,一路走來跳了不少的坑,感謝團隊成員的支持,并讓這個方案落地,后續希望能貢獻更多代碼回饋開源社區,也希望有更多志同道合的人加入我們騰訊檔案 AlloyTeam 團隊,一起去探索和遨游,最后也希望這篇文章能給到你們一些啟發吧 ????

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/239165.html

標籤:AI

上一篇:MTSC2020 | 手淘AIOPS實戰-訊息全鏈路智能監控

下一篇:1款開源工具,實作自動化升級K3S集群!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more