一份配置輕鬆搞定表單渲染,配置式表單渲染器在袋鼠雲的實現思路與實踐

来源:https://www.cnblogs.com/DTinsight/archive/2023/06/07/17463135.html
-Advertisement-
Play Games

前段時間,[袋鼠雲離線開發產品](https://www.dtstack.com/dtinsight?src=szsm)接到改造數據同步表單的需求。 一方面,[數據同步模塊](https://www.dtstack.com/dtinsight?src=szsm)的代碼可讀性和可維護性較差,導致在數據 ...


前段時間,袋鼠雲離線開發產品接到改造數據同步表單的需求。

一方面,數據同步模塊的代碼可讀性和可維護性較差,導致在數據同步模塊開發新功能和定位問題的效率很低。另一方面,整體規划上,希望在對接新的數據源時,可以不再關心表單渲染相關問題,從數據源中心新建數據源一直到數據源在數據同步模塊的應用,全鏈路的表單都可以通過配置化的方式解決。

本文就將以此為例,拋磚引玉,為大家詳細介紹配置式表單渲染器實現的實踐之路。

數據同步表單背景

數據同步模塊整體上分為四個部分,數據來源表單、同步目標表單、欄位映射組件和通道控製表單

file

其中前三個部分對應的代碼非常混亂,代碼量也很大,單個組件代碼 5000+ 行,這裡著重說一下數據來源表單和同步目標表單。

數據來源和同步目標表單的主要功能是收集數據源對應的配置信息,並且根據數據源類型的不同,對應需要渲染的表單項也不同。

目前袋鼠雲離線開發產品 BatchWorks 數據同步功能的數據源多達50+種。在長時間的迭代過程中,日積月累出現了很多強行復用的代碼,這些強行復用的代碼內部又包含著大量的 if else 邏輯。另外,數據同步模塊的表單內部有很多聯動關係,比如:

· 某個表單項的值變化時,需要發起介面請求,請求的返回值被用作另一個表單項下拉框的數據

· 某個表單項的值變化時,需要去清空/重置其他一些表單項的值

· 某個表單項的值變化時,需要顯示/隱藏某個表單項

· 某個表單項的值變化時,某個表單項的 label 文案、表單項組件(比如從 select 變成 input ) 等隨之發生變化

這些表單項的聯動處理邏輯在代碼中混雜交叉,另外還要加上表單回顯的特殊邏輯處理,表單的值收集到 redux 的特殊邏輯處理等。

需求分析

基於上述需求背景,表單渲染器的核心功能是輸入一份配置,輸出表單 UI 組件。

file

基於上述數據同步表單背景,我們希望渲染器可以儘可能吸收掉表單內部的複雜度,也就是說在表單的配置中要能夠描述上述的聯動關係,那麼可以大概得出表單的配置需要描述:

· 表單項的基礎信息,比如欄位名、label、表單組件、校驗信息等

· 表單項數據之間的聯動

· 表單項 UI 的聯動(控制顯示/隱藏)

· 表單項的值變化時需要觸發的副作用(比如調用介面)

表單基礎信息描述

這裡配置格式使用 JSON 格式,用一個數組描述所有的表單項信息,UI 上表單項的渲染順序即配置數組中表單項配置的順序,表單組件使用 Ant Design Form。

對於表單項基礎信息的描述配置,大多可以直接搬用 Ant Design Form Item 的 props,比如 label、rules、Tooltip 等屬性,這裡不多贅述。

比較特殊的是,需要在配置里描述表單項描述的 UI 組件,比如 Select、Input,那麼這裡使用 widget 欄位去描述。另外,組件的描述除了組件名稱,還需要描述組件的 props, 所以還需要一個 widgetProps 欄位去描述組件的屬性,比如 placeholder、disabled 等。

那麼一個用於選擇數據源的表單項應該這樣描述:

{
  "fieldName": "sourceId",
  "label": "數據源",
  "rules": [
    {
      "required": true,
      "message": "請選擇數據源!",
    },
  ],
  "widget": "Select",
  "widgetProps": {
    "placeholder": "請選擇數據源",
    "options": [
      {
        "lable": "數據源1",
        "value": 1
      }
    ]
  },
}

當然可能會存在某些表單項的 UI 組件有自定義的情況,比如可編輯表格,代碼編輯器等。這個時候就需要開發自定義表單組件了,然後把這些組件註入到 FormRenderer 中,偽代碼如下所示:

import { Editor, EditableTable } from './customWigets'

export const getWidets = (widgetsName) => {
    switch(widgetsName) {
      case 'Editor': {
        return Editor
      }
      case 'EditableTable': {
        return EditableTable
      }
    }
} 

那麼目前的結構如圖所示:

file

這份配置寫到這裡的時候,問題出現了:

· 無法在配置中描述 onChange、onSelect 等事件回調函數

· 相比於 jsx 強大的表達能力,JSON 中只能表達基本的數據結構,而沒辦法直接表達邏輯

· Select 下拉框的數據可能來源於介面,這種情況在業務中相當常見,這裡也沒辦法表達

· 不能自定義表單校驗器,無法支持複雜的 Tootip 提示,比如帶有 a 標簽的 Tootip

上述問題產生的根本原因,實際上是 JSON 與 jsx 之間表達能力的差距。但是從另一個角度來講,正因為 JSON 的表達能力和靈活性不如 jsx,所以在用來描述 UI 時,JSON 更不容易導致混亂。

我們先思考如何表達 Select 下拉框的數據來源於介面,這裡可以拆解為兩個部分,數據獲取和取得介面的返回值併在配置項中表達。

數據獲取

實際上,Select 下拉框中的數據也並不一定來源於介面,也可能是來源於其他業務數據,所以在配置項描述數據獲取時,不應該關心數據的來源。

很顯然,數據獲取邏輯需要用 js 描述 ,這裡我們抽象出一個 Service 的概念,用於描述/聲明數據獲取邏輯,Service 的聲明使用 js,在 JSON 配置中,只需要去描述 Service 的調用邏輯即可。對於 JSON 配置來說, Service 調用需要三個要素:

· Service 的標識/名稱,表示哪一個 Service 被觸發

· Service 的觸發時機

· Service 返回的數據如何存儲

● Service 的觸發時機

Service 的觸發一般來說是由於用戶的交互引起的,當然也存在在表單項組件掛載時就需要觸發的情況,那麼調用時機大概就是以下幾種:

· onMount

· onChange

· onSearch

· onFocus

· onBlur

● Service 返回的數據如何存儲

這裡 Service 返回的數據存儲需要能被 UI 獲取到,那麼需要將返回的數據都維護在 FormRender 內部,這裡將存儲數據的地方命名為 extraData。那麼我們描述 Service 返回的數據的存儲,可以使用一個 fieldInExtraData 的欄位,描述當前 Service 返回的數據被存儲在 extraData 的那個欄位中,取值時:extraData[fieldInExtraData]。

那麼在表單項配置中描述 Service,如下所示:

{
  "serviceName": "getSourceList",
  "triggers": ["onMount", "onSearch"],
  "fieldInExtraData": "schemaList"
}

● Service 的聲明

對於 Service 本身來說,要做的事情就是獲取並處理數據然後返回,當然 Service 本身可能需要接受一些參數,比如當前 Form 收集到的數據、Service 是被哪個欄位觸發的、觸發時機是什麼等等,那麼 Service 的格式如下所示:

const getSourceList = ({ formData, extraData, trigger, triggerFieldName }) => {
  return Promise((resolve) => {
    resolve(...)
  })
}

由於 Service 可能是非同步的,所以這裡 Service 都返回一個 Promise,然後將所有的 Service 都註入到 FormRenderer 中,FormRenderer 根據表單項配置中聲明的調用時機去調用 Service,整個數據獲取的鏈路就完成了。

獲取 Service 返回值併在配置項中表達

上文中提到,Service 的返回的數據都被存儲在 FormRenderer 內部的 extraData 中,一般情況下如果使用 jsx 當然能很容易地取到對應的值,但是在 JSON 中,是沒辦法表達的。但是我們可以借鑒 jsx 的插值表達式和 vue 的插值表達式。

<div>{user.name}</div>

在 jsx 中,如果在一對標簽內部寫了一串字元串,對應的會有兩種解析策略,第一種是直接識別為字元串,第二種如果識別到花括弧,則將其視為 js 表達式。 同理,在 JSON 配置中也可以使用這種方式去取值。

{
  "fieldName": "sourceId",
  "label": "數據源",
  "widget": "Select",
  "widgetProps": {
    "placeholder": "請選擇數據源",
    "options": "{{ extraData.sourceList }}"
  },
  "triggerServices": [
    {
      "serviceName": "getSourceList",
      "triggers": ["onMount", "onSearch"],
      "fieldInExtraData": "sourceList"
    }
  ]
}

● 函數表達式

上例中,使用一對花括弧聲明函數表達式,錶面上是借鑒了 jsx 的插值表達式,但是其實兩者有很大的區別。jsx 的插值表達式是在編譯階段就轉化成了 js 表達式。而在 JSON 中的這種自定義的函數表達式要在運行時轉換,上述的函數表達式只能被轉換為函數執行。即:

"{{ extraData.schemaList  }}"
// 轉化為
const valueGetter = new Function('extraData', 'return extraData.schemaList')

出於安全問題考慮,表達式還需要被放在一個類似沙箱的環境中執行,避免表達式內部修改全局環境變數。創建簡易沙箱使用 proxy + with + symbol.unscopables 的方式,這裡不展開講解了。最終函數表達式的應用大概是如下形式:

function Comp () {
  return <Select options={valueGetter(extraData)} />
}

到目前為止,已經有了兩個新概念:Service 和 函數表達式,回到上文中提到的問題,我們已經解決了 Select 下拉框來源於介面的問題,那麼還剩下如下問題:

· JSON 中只能表達基本的數據結構,而沒辦法直接表達邏輯

· 無法在配置中描述 onChange、onSelect 等事件回調函數,也不能自定義表單校驗器

· 不能自定義表單校驗器,無法支持複雜的 Tootip 提示,比如帶有 a 標簽的 Tootip

json 中沒辦法表達邏輯的問題,其實已經可以通過函數表達式來解決了。函數表達式內部支持寫任意的 js 表達式,另外,在函數表達式中也可以支持訪問 form 表單數據,有了數據支持和邏輯表達能力支持,絕大多數情況下的已經能夠滿足 UI 渲染中的邏輯表達了。

而描述 onChange、onSelect 等事件回調函數可以通過配置 Service 來解決。

自定義表達校驗器可以通過函數表達式的變種來解決,可以向 FormRenderer 中註入 form 校驗器的集合,然後通過 {{ ruleMap.xxx }} 來指定表單項的某一條校驗規則的校驗器。

{
  "fieldName": "sourceId",
  "label": "數據源",
  "rules": [
    {
      validator: "{{ ruleMap.checkSourceId }}"
    },
  ],
}

Tooltip 提示也是如此。目前結構如下圖所示:

file

表單數據聯動

表單數據聯動實際上就是當表單中某個表單項值變化時,去重置其他表單項的值,那麼要在配置中描述這種聯動關係有兩種方式:

· 當前欄位受哪些欄位的影響

· 當前欄位的值變化會影響到哪些欄位

一般情況下,在代碼中描述這種邏輯時都是採用第二種方式,也就是監聽某個欄位的值的變化,然後在回調函數中去做對應的數據聯動操作。

但是在配置 JSON 時,第二種方式就變得不那麼友好了,那會讓欄位配置之間產生更多的耦合。更加友好的方式是在某個欄位內表達本欄位受到哪些欄位的影響,這樣做的另一個好處是,當開發者填寫或者修改某一個欄位的配置時,可以更加聚焦,不用關心其他欄位的配置。

這裡用 dependecies 欄位來表達當前欄位的值受哪些欄位的影響。舉個例子,表單中有數據源、schema、table 三個欄位,數據源變化時,schema 的值應該被重置;schema 變化時,table 的值會被重置。那麼在 json 中應該這樣描述:

[
  {fieldName: 'sourceId', dependencies: []},
  {fieldName: 'schema', dependencies: ['sourceId']},
  {fieldName: 'table', dependencies: ['schema']},
]

對應的依賴關係圖如下:

file

這裡新的問題產生了,當數據源變化時,table 的值是否要被重置?一般情況下是肯定的。那麼實際上它們的依賴關係是這樣的:

file

這裡有兩種方式來解決這種隱式的依賴關係:

· 開發者在配置時顯式得聲明所有的依賴關係

· 渲染器內部解析依賴關係時,將這種隱式的依賴關係也解析出來

那麼如何選擇使用哪一種方式呢?

如果採用第一種方式,優點是渲染器不再需要關心這種隱式的依賴關係了,但是在配置時的心智負擔可能比較大,很容易出現漏配依賴關係的情況。

如果採用第二種方式,優點是配置起來心智負擔低,但是也有可能出現 table 確實不依賴 sourceId 的情況,也就是間接依賴不生效的情況。

結合實際業務看,目前的業務中,所有的欄位之間間接依賴其實都是隱式依賴,也就是需要生效的,這裡採用第二種方式。前文中也提到了,期望是 FormRenderer 可以儘可能的吸收掉表單內部的複雜度。

特殊的表單數據聯動

在實際業務中還存在著一些比較特殊的表單數據聯動,比如:

· 選擇數據源時,除了需要收集數據源的 id,還需要收集數據源類型

· 選擇數據源後,需要將數據源的其他信息展示為表單項,比如下圖中的表單

file

對於這種業務場景,我們可以理解為某個表單項的值是由其他表單項的值派生出來的,那麼就需要去描述這種派生邏輯。當然,這種派生邏輯可以在業務代碼中描述,只需要在數據源變化時,手動的 setFieldValue 就可以了。但是還是上文中提到的期望,FormRenderer 可以儘可能吸收掉複雜度。

處理這種情況,需要新增一個配置項去描述派生邏輯,這裡配置項定為 valueDerived,這個配置項的值應該為一個取值表達式,那麼以第一個例子為例,配置應該如下所示:

[
  {
    "fieldName": "sourceId",
    "label": "數據源",
    "widget": "Select",
    "widgetProps": {
      "placeholder": "請選擇數據源",
      "options": "{{ extraData.sourceList }}"
    },
  },
  {
    "fieldName": "sourceType",
    "label": "數據源類型",
    "hidden": true,
    "valueDerived": "{{ extraData.sourceList.find(s => s.value === formData.sourceId).type }}",
  },
]

FormRenderer 內部根據配置的 valueDerived 去自動更新表單中對應欄位的值。

表單 UI 聯動

表單 UI 聯動可以分為以下兩個部分。

表單項 UI 文案、樣式等根據數據聯動

表單項的 UI 聯動在 React 和 JSX 中,都能很輕易、很自然的發生。但是想要在 JSON 中描述,由於JSON本身不具備表達邏輯的能力,還是要藉助函數表達式。只需要支持對應的配置項可以使用函數表達式就能完成表單項的聯動。舉個例子:

[
  {
    "fieldName": "time",
    "label": "{{ extraData.type === 1 ? '開始時間' : '結束時間' }}",
    "widget": "Input",
    "widgetProps": {
      "placeholder":"{{ extraData.type === 1 ? '請輸入開始時間' : '請輸入結束時間' }}",
    },
  }
]

那麼它們實際渲染時等同於以下偽代碼:

function Comp (props) {
  const {fieldName, label, widget, widgetProps, extraData} = props
  
  const form = useFormInstance()
  const formData = form.getFieldsValue()
  
  const tarnsformer = (configItem) => {
    const fn = new Function('formData', 'extraData', `return $[configItem}`)
    return fn.call(null, formData, extraData)
  }

  return 
    <Form.Item
      name=fieldName
      label={tarnsformer(label)}
    >
      <widget  placeholder={tarnsformer(widgetProps.placeholder)}/>
    </Form.Item>
}

這樣就能做到表單項的文案樣式等根據數據變化自然的聯動。

表單項的顯示與隱藏

表單項的隱藏也能拆分為以下兩種情況:

· 隱藏但不銷毀,表單項的值仍然會被收集和保留

· 銷毀,不再保留/收集表單項的值

隱藏但不銷毀的情況,antd form 本身就有 hidden 配置支持,那麼這裡只需要支持 hidden 配置使用函數表達式就可以了。

對於表單項的銷毀,就需要新增一個欄位了,這裡命名為 destory,同樣通過支持使用函數表達式完成聯動,但是這裡需要考慮一些其他情況。比如從銷毀狀態變成顯示狀態時,需要去觸發 mount service 等。

思路小結

回顧上文需求分析中所說的需要實現的功能:

· 表單項的基礎信息,比如欄位名、label、表單組件、校驗信息等

· 表單項數據之間的聯動

· 表單項 UI 的聯動(控制顯示/隱藏)

· 表單項的值變化時需要觸發的副作用(比如調用介面)

目前在思路上,上述功能都是可以實現的。除了基礎的渲染功能以外,FormRender 需要額外實現的功能有:

· 內置一個 extraData 存儲 Service 返回的數據

· 支持根據配置在正確的時機觸發 Service

· 支持函數表達式

· 支持根據配置在內部處理數據聯動邏輯

大體實現

整體上,導出一個 FormRenderer 組件,上文中提到的 json config、Service 聲明、自定義的表單校驗器,自定義表單項組件等,都通過 FormRenderer 的 props 傳入。

內置 extraData

由於 extraData 內部存儲的數據變化可能導致視圖更新,那麼只能使用 React.Context 或者 state,事實上即使使用 Context 也還是需要聲明 state 來觸發視圖更新,但是 Conetxt 在傳遞數據時有著獨特的優勢,這裡直接使用 Context 存儲數據。

// 避免閉包問題
export function useExtraData(init: IExtraDataType) {
    const stateRef = useRef<IExtraDataType>(init);
    const [_, updateState] = useReducer((preState, action) => {
        stateRef.current =
            typeof action === 'function'
                ? { ...action(preState) }
                : { ...action };
        return stateRef.current;
    }, init);

    return [stateRef, updateState] as const;
}

// 創建context
const ExtraContext = React.createContext<ExtraContextType>({
    extraDataRef: { current: { } },
    update: () => void 0,
});
import { useExtraData, ExtraContext } from 'extraDataContext.ts'

const FormRenderer: React.FC = () => {
  const [extraDataRef, updateExtraData] = useExtraData({});
  // ....
  return(
    <ExtraContext.Provider
      value={{ extraDataRef, update: updateExtraData }}
    >
      {....}
    </ExtraContext.Provider>
  )     
}

在正確的時機觸發 Service

在 JSON 配置中 Service 相關描述如下所示:

[
  {
    "fieldName": "sourceId",
    "label": "數據源",
    "triggerServices": [
      {
        "serviceName": "getSourceList",
        "triggers": ["onMount", "onSearch"],
        "fieldInExtraData": "sourceList"
      },
      {
        "serviceName": "getSchemaList",
        "triggers": ["onChange"],
        "fieldInExtraData": "schemaList"
      },
    ]
  }
]

triggerServices 已經很清楚直觀的描述了,該欄位在什麼時機應該調用哪個 service,在代碼實現上,為了這部分觸發邏輯與視圖渲染分離,採用發佈訂閱模式。大體流程如下圖所示:

file

這裡流程已經走通了,但是可以發現,renderer 中仍然需要去處理訂閱的邏輯,Service 觸發邏輯與視圖渲染邏輯分離的不夠徹底,那麼可以繼續優化一下,加入一個訂閱器去處理這部分邏輯,優化後的邏輯如下圖所示:

file

支持函數表達式

上文中提到了,函數表達式的實現是用 new Function,以及處於安全問題考慮需要將函數表達式放到模擬沙箱環境中執行,執行流程如下所示:

file

實現代碼如下所示(不包含正則處理):

class FnExpressionTransformer {
    private sandboxProxiesMap: WeakMap<ScopeType, InstanceType<typeof Proxy>> =
        new WeakMap();

    private createProxy(scopeObj: ScopeType) {
        /** 存儲創建的 proxy 避免重覆創建 */
        if (this.sandboxProxiesMap.has(scopeObj)) {
            return this.sandboxProxiesMap.get(scopeObj);
        }
        const scope = {
            extraData: scopeObj.extraDataRef,
            formData: scopeObj.formData,
            Math: Math,
            Date: Date,
        };
        const proxy = new Proxy(scope, {
            has() {
                return true;
            },
            get(target, prop) {
                if (prop === Symbol.unscopables) return undefined;
                if (prop === 'extraData') {
                    return target[prop]['current'];
                }
                return target[prop];
            },
        });
        this.sandboxProxiesMap.set(scopeObj, proxy);
        return proxy;
    }

    transform = (code: string): TransformedFnType => {
        return (scope: ScopeType) => {
            const proxy = this.createProxy(scope);
            const fnBody = `with(scope) {  return ${code} }`;
            const fn = new Function('scope', fnBody);
            return fn(proxy);
        };
    };
}

比如在 label 配置中使用了函數表達式:

[
  {
    "fieldName": "name",
    "label": "{{ extraData.xxx ? '用戶名' : '昵稱' }}"
  }
]

那麼經過轉換後,就是等同於以下函數:

function lableValue (scope) {
  return scope.extraData.xxx;
} 

具體應用如下:

<FormItem
  name={name}
  label={lableValue({ formData, extraData })}
>
{/* xxxx */}
</FormItem>

支持根據配置在內部處理數據聯動邏輯

與上文中 Service 觸發邏輯一樣,將這部分聯動的邏輯通過發佈訂閱與視圖渲染邏輯分離。但是相比於 Service 觸發邏輯,這裡多了分析依賴的步驟。比如,有如下 json 配置:

[
  {fieldName: 'schema', dependencies: []},
  {fieldName: 'table', dependencies: ['schema']},
  {fieldName: 'partition', dependencies: ['schema', 'table']},
  {fieldName: 'coprate', dependencies: ['table', 'partition']}
]

那麼生成的依賴關係圖就應該是:

[
  {fieldName: 'schema', isField: true]},
  {fieldName: 'table', isField: true},
  {fieldName: 'partition', isField: true},
  {fieldName: 'coprate', isField: true},
  {fieldName: 'schema', dependBy: 'table', isRelation: true},
  {fieldName: 'schema', dependBy: 'partition', isRelation: true},
  {fieldName: 'table', dependBy: 'partition', isRelation: true},
  {fieldName: 'table', dependBy: 'coprate', isRelation: true},
  {fieldName: 'partition', dependBy: 'coprate', isRelation: true},
]

生成上述依賴關係後,剩下的流程與觸發Service 的流程類似,在這裡不多做贅述了。

《數棧產品白皮書》:https://www.dtstack.com/resources/1004?src=szsm

《數據治理行業實踐白皮書》下載地址:https://www.dtstack.com/resources/1001?src=szsm

想瞭解或咨詢更多有關袋鼠雲大數據產品、行業解決方案、客戶案例的朋友,瀏覽袋鼠雲官網:https://www.dtstack.com/?src=szbky

同時,歡迎對大數據開源項目有興趣的同學加入「袋鼠雲開源框架釘釘技術qun」,交流最新開源技術信息,qun號碼:30537511,項目地址:https://github.com/DTStack


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MCU:STM32F429ZIT6 開發環境:STM32CubeMX+MDK5 外購了一個SPI介面的SD Card模塊,想要實現SD卡存儲數據的功能。 首先需要打開STM32CubeMX工具。輸入開發板MCU對應型號,找到開發板對應封裝的MCU型號,雙擊打開(圖中第三)。 此時,雙擊完後會關閉此界 ...
  • MCU:STM32F103VET6 開發環境:STM32CubeMX+MDK5 實現USB的虛擬串口不需要去理解USB的底層驅動,只需要STM32CubeMX去配置生成工程即可。在野火的指南者中,是沒有這一類的視頻和示例的,博主使用這款開發板實現USB虛擬串口。 首先需要打開STM32CubeMX工 ...
  • ## 前言 本篇文章主要介紹的關於本人從剛工作到現在使用Sql一些使用方法和經驗,從最基本的SQL函數使用,到一些場景的業務場景SQL編寫。 ## SQL基礎函數使用 ### 1.欄位轉換 CASE WHEN 意義: If(a==b) a=c; 用法: 1, CASE 欄位 WHEN 欄位結果1 T ...
  • [TOC] ## 概述 Hive查看執行計劃的命令中還有兩個不怎麼常用但很重要的命令,接下來詳細介紹一下。 有一個問題:**如何在hiveSQL執行之前就探查到這段邏輯的血緣依賴關係?** hive血緣是很多生產級數倉必須要提供的功能,大多數解決方案都是**使用hive hooks的方法通過SQL執 ...
  • # 基本概念 ## 簡介 Kafka 最初是由 LinkedIn 即領英公司基於 Scala 和 Java 語言開發的分散式消息發佈-訂閱系統,現已捐獻給Apache 軟體基金會。其具有高吞吐、低延遲的特性,許多大數據實時流式處理系統比如 Storm、Spark、Flink等都能很好地與之集成。 總 ...
  • > 不要哀求,學會爭取。若是如此,終有所獲。 > > 原文:https://mp.weixin.qq.com/s/zbOqyAtsWsocarsFIGdGgw ## 前言 你是否還在煩惱 SQL 該從何學起,或者學了 SQL 想找個地方練練手?好巧不巧,最近在工作之餘登上牛客,發現了牛客不知道啥時候 ...
  • 摘要:GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔離反覆被終止的查詢,防止爛SQL再次執行。 本文分享自華為雲社區《GaussDB(DWS)查詢過濾器原理與應用》,作者:門前一棵葡萄樹 。 一、概述 GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔 ...
  • 啟動安裝程式 下載sqlserver2014,雙擊startup.exe進行安裝 系統配置檢查器 使用系統配置檢查器,看系統是否符合安裝sqlserver2014的所有要求 開始安裝 然後點擊安裝,全新sqlserver獨立安裝或向現有安裝添加功能 安裝規則 然後就是使用預設的設置,點開詳細信息,可 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...