整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          神秘SDK暗刷百度廣告 植入數千款APP

          神秘SDK暗刷百度廣告 植入數千款APP

          騰訊安全了解到,騰訊安全反詐騙實驗室追蹤到暴風影音、天天看、塔讀文學等眾多應用中集成的某SDK存在下載惡意子包,通過webview配合js腳本在用戶無感知的情況下刷百度廣告的惡意操作。

          該惡意SDK通過眾多應用開發者所開發的正規應用,途經各中應用分發渠道觸達千萬級用戶;其背后的黑產則通過惡意SDK留下的后門控制千萬用戶,動態下發刷量代碼,大量刷廣告曝光量和點擊量,賺取大量廣告費用,給廣告主造成了巨額廣告費損失。

          根據安全人員詳細分析,此惡意SDK主要存在以下特點:

          1、該SDK被1000+千應用開發者使用,通過應用開發者的分發渠道抵達用戶。主要涉及的應用包括掌通家園、暴風影音、天天看、塔讀文學等,潛在可能影響上千萬用戶;

          2、刷量子包通過多次下載并加載,并從服務器獲取刷量任務,使用webview加載js腳本實現在用戶無感知的情況下自動化的進行刷量任務。

          此類流量黑產給傳統的廣告反作弊帶來了極大挑戰,傳統通過IP、曝光頻率、點擊率等表象數據形成的反作弊策略難以識別這種控制大量真實設備做’肉雞’的刷量作弊,使得大量廣告費用流入黑產手中,卻無法給廣告主帶來應有的廣告效果。

          SDK作惡流程和影響范圍

          此惡意SDK集成在應用中的那部分代碼沒有提供實際功能,其在被調用后會定時上報設備相關信息,獲取動態子包的下載鏈接,下載子包并加載調用。然后由子包執行相應的惡意行為。

          惡意SDK作惡流程示意圖:



          受惡意SDK影響的主要應用列表:



          惡意SDK作惡行為詳細分析

          此惡意SDK被眾多的中小應用開發者集成,我們以應用塔讀文學為例,對其惡意行為進行詳細分析。



          惡意SDK代碼結構



          此sdk代碼較少,沒有什么實際的功能。其在被加載調用后,會設置定時任務,每隔3600秒(1小時)啟動GatherService,上報設備相關信息,獲取動態子包__gather_impl.jar的下載鏈接



          GatherService鏈接服務器,獲取__gather_impl.jar的下載鏈接



          請求鏈接:http://gather.andr****.com:5080/gupdate/v1

          請求數據:包括uid、應用包名、設備id、應用版本、手機廠商、型號、系統版本、imei、sdk版本等內容



          返回內容:包括子包的版本、下載url、文件md5



          動態加載下載的__gather_impl.jar



          子包__gather_impl.jar代碼結構,此子包的主要功能有:1、上傳用戶設備信息,2、下載并動態加載子包stat-impl.jar



          1)、鏈接服務器,上傳用戶設備信息



          服務器鏈接:http://userdata.andr****.com/userdata/userdata.php (此url在分析時已失效,無法鏈接)

          上報內容:包括位置信息(經緯度),用戶安裝列表(軟件名、包名),設備信息(廠商、型號、fingerprint,是否root),deviceid、手機號、運營商、imei、mac等。



          2)、再次請求服務器,獲取stat-impl.jar的下載鏈接



          請求鏈接:http://iupd.andr****.com:6880/wupdate/v1

          請求數據:包括uid、imei、sdk版本、手機廠商、型號、系統版本、應用包名、設備id、設備指令集等內容



          返回內容:包括子包的版本、下載url、文件md5



          子包下載完成后,調用native方法動態加載此子包






          stat-impl.jar的代碼結構:



          stat-impl.jar子包被加載后,線程com.drudge.rmt.g會被啟動,其作用主要是用來聯網獲取刷量任務,并調度任務的執行。



          主要的刷量任務包括:1、刷百度搜索的關鍵字,2、使用js腳本實現自動點擊、滑動來刷百度廣告和億量廣告的點擊,3、使用webview刷網頁訪問。

          1、刷百度關鍵字搜索

          此任務會根據獲取json字符串,進行相應的操作,包括設置BAIDUID、更新配置、添加任務、設置剪切板和使用關鍵字刷百度搜索



          設置關鍵字,使用webview加載對應的url



          捕獲到的刷百度關鍵字的webview加載請求:



          鏈接服務器http://tw.andr****.com:6080/wtask/v1獲取相關任務,并將任務內容存入[package]/cache/volley目錄下





          2、使用js腳本刷百度廣告

          使用webview加載http://mobads.baidu.com/ads/index.htm,并在加載完成后執行js腳本實現自動滑動、點擊、保存等操作來自動刷廣告



          相關的js腳本

          1)、js函數定義滑動、點擊、保存等操作



          Java層解析并實現js層傳遞過來的操作命令



          2)、js函數判斷并獲取頁面元素



          ...



          3)、js函數計算頁面元素相對位置,并進行滑動、點擊操作



          ...

          捕獲到的刷百度廣告的webview加載請求:



          3、使用webview刷網頁訪問

          此任務向服務器請求需要訪問的url鏈接,在獲取到相應的網頁url后,使用webview加載進行訪問。

          請求需要訪問的url鏈接



          請求鏈接

          http://us.yiqimeng.men:8080/geturls?k=beike-xinshiye&c=5

          返回內容

          ["http://m.xinshiye.cc/cars/17/10/21/707989.html?content_id=707989u0026key=x2HAJuZaaa9YWpVa8EXTqOmHHUxhSnj75xhhAS7f6tveQsphsCm3jc9xrhV4RZbRzgm%2FQqzCVcw2dvukMqw25Q%3D%3Du0026_t=1511190410",

          "http://m.xinshiye.cc/cars/17/10/11/234818.html?content_id=234818u0026key=NzLZyHQXsCdpS6bkAWab2LSzd2XApbGOJYUuN%2Bm4PFsoWk1l%2FnZSD8M1yp1cuhz%2FdL0uoNG93TVt8ai6zEU%2BQw%3D%3Du0026_t=1511190560",

          "http://m.xinshiye.cc/cars/17/11/26/1769446.html?content_id=1769446u0026key=8KLxL1fm2gwNDxqT6nsSAbQ07kcEZRHBrekhzNSJcNaAg1nZmbW49pQ3EaEYJfMUeMlwSX4KzdliXJ3O37fs9g%3D%3Du0026_t=1513046929",

          "http://m.xinshiye.cc/cars/17/10/31/1444661.html?content_id=1444661u0026key=mODVhDy0zyzBGH1G6sTwDYXqiy3D7pDfymsirda6s5%2BW8tarfIDPjuhT3mkqeMMDKzKr%2BFVC2Py2gzsNkMniHw%3D%3Du0026_t=1509589907",

          "http://m.xinshiye.cc/cars/17/12/09/1921549.html?content_id=1921549u0026key=0XFxkCX0Bn4k%2Fw5%2FqvlSIOCREqEWoJ5jimqn%2BZAeJIwksQzydyT0AZFAVZJAritm3hpGza4TFNlONZDtoY%2BfTA%3D%3Du0026_t=1513045278"]

          使用webview訪問獲取url



          捕獲到的刷求醫不如健身網的webview加載請求:



          相關URL整理



          安全建議和防范手段

          從近期Android端惡意應用的作惡手法來看,惡意開發者更多地從直接開發App應用轉向開發SDK,向Android應用供應鏈的上游轉移。通過提供惡意的SDK給應用開發者,惡意開發者可以復用這些應用的分發渠道,十分有效的擴大影響用戶的范圍。

          而在惡意SDK的類別方面,黑產從業者主要把精力放在用戶無感知的廣告刷量和網站刷量等方向,通過使用代碼分離和動態代碼加載技術,可以完全從云端下發實際執行的代碼,控制用戶設備作為“肉雞”進行廣告、網站刷量等黑產行為,具有很強的隱蔽性。

          這類流量型黑產逐漸增多,不僅對手機用戶造成了危害,同時也給移動端廣告反作弊帶來了很大的挑戰,傳統基于IP、曝光頻率、點擊率等表象數據形成的反作弊策略難以識別這種控制大量真實設備做’肉雞’的刷量作弊,難以保障應用開發者和廣告主的正當權益。

          針對終端用戶,有如下安全建議:

          1、盡可能使用正版和官方應用市場提供的APP應用;

          2、移動設備即使進行安全更新;

          3、安裝手機管家等安全軟件,實時進行防護。

          apr 是一個可移植的、事件驅動的運行時,它使任何開發人員能夠輕松構建出彈性的、無狀態和有狀態的應用程序,并可運行在云平臺或邊緣計算中,它同時也支持多種編程語言和開發框架。Dapr 確保開發人員專注于編寫業務邏輯,不必分神解決分布式系統難題,從而顯著提高了生產力。Dapr 降低了構建微服務架構類現代云原生應用的門檻。

          系列

          • Dapr 與 NestJs ,實戰編寫一個 Pub & Sub 裝飾器
          • NodeJS 基于 Dapr 構建云原生微服務應用,從 0 到 1 快速上手指南
          • 本地使用 Docker Compose 與 Nestjs 快速構建基于 Dapr 的 Redis 發布/訂閱分布式應用

          JavaScript

          用于在 JavaScript 和 TypeScript 中構建 Dapr 應用程序的客戶端庫。該客戶端抽象了公共 Dapr API,例如服務到服務調用、狀態管理、發布/訂閱、機密等,并為構建應用程序提供了一個簡單、直觀的 API。

          安裝

          要開始使用 Javascript SDK,請從 NPM 安裝 Dapr JavaScript SDK 包:

          npm install --save @dapr/dapr
          

          ?? dapr-client 現在已棄用。 請參閱#259 了解更多信息。

          https://github.com/dapr/js-sdk/issues/259

          結構

          Dapr Javascript SDK 包含兩個主要組件:

          • DaprServer: 管理所有 Dapr sidecar 到應用程序的通信。
          • DaprClient: 管理所有應用程序到 Dapr sidecar 的通信。

          上述通信可以配置為使用 gRPC 或 HTTP 協議。

          Client

          介紹

          Dapr Client 允許您與 Dapr Sidecar 通信并訪問其面向客戶端的功能,例如發布事件、調用輸出綁定、狀態管理、Secret 管理等等。

          前提條件

          • Dapr CLI 已安裝https://docs.dapr.io/getting-started/install-dapr-cli/
          • 初始化 Dapr 環境https://docs.dapr.io/getting-started/install-dapr-selfhost/
          • 最新 LTS 版本的 Node 或更高版本https://nodejs.org/en/

          安裝和導入 Dapr 的 JS SDK

          1. 使用 npm 安裝 SDK:
          npm i @dapr/dapr --save
          
          1. 導入庫:
          import { DaprClient, DaprServer, HttpMethod, CommunicationProtocolEnum } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; // Dapr Sidecar Host
          const daprPort="3500"; // Dapr Sidecar Port of this Example Server
          const serverHost="127.0.0.1"; // App Host of this Example Server
          const serverPort="50051"; // App Port of this Example Server 
          
          // HTTP Example
          const client=new DaprClient(daprHost, daprPort);
          
          // GRPC Example
          const client=new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
          

          運行

          要運行示例,您可以使用兩種不同的協議與 Dapr sidecar 交互:HTTP(默認)或 gRPC。

          使用 HTTP(默認)

          import { DaprClient } from "@dapr/dapr";
          const client=new DaprClient(daprHost, daprPort);
          
          # Using dapr run
          dapr run --app-id example-sdk --app-protocol http -- npm run start
          
          # or, using npm script
          npm run start:dapr-http
          

          使用 gRPC

          由于 HTTP 是默認設置,因此您必須調整通信協議以使用 gRPC。 您可以通過向客戶端或服務器構造函數傳遞一個額外的參數來做到這一點。

          import { DaprClient, CommunicationProtocol } from "@dapr/dapr";
          const client=new DaprClient(daprHost, daprPort, CommunicationProtocol.GRPC);
          
          # Using dapr run
          dapr run --app-id example-sdk --app-protocol grpc -- npm run start
          
          # or, using npm script
          npm run start:dapr-grpc
          

          代理請求

          通過代理請求,我們可以利用 Dapr 通過其 sidecar 架構帶來的獨特功能,例如服務發現、日志記錄等,使我們能夠立即“升級”我們的 gRPC 服務。 gRPC 代理的這一特性在community call 41 中得到了展示。

          community call 41

          https://www.youtube.com/watch?v=B_vkXqptpXY&t=71s

          創建代理

          要執行 gRPC 代理,只需調用 client.proxy.create() 方法創建一個代理:

          // As always, create a client to our dapr sidecar
          // this client takes care of making sure the sidecar is started, that we can communicate, ...
          const clientSidecar=new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
          
          // Create a Proxy that allows us to use our gRPC code
          const clientProxy=await clientSidecar.proxy.create<GreeterClient>(GreeterClient);
          

          我們現在可以調用 GreeterClient 接口中定義的方法(在本例中來自 Hello World 示例)

          • https://github.com/grpc/grpc-go/blob/master/examples/helloworld/helloworld/helloworld.proto

          幕后(技術工作)

          1. gRPC 服務在 Dapr 中啟動。 我們通過 --app-port 告訴 Dapr 這個 gRPC 服務器在哪個端口上運行,并通過 --app-id <APP_ID_HERE> 給它一個唯一的 Dapr 應用 ID
          2. 我們現在可以通過將連接到 Sidecar 的客戶端調用 Dapr Sidecar
          3. 在調用 Dapr Sidecar 時,我們提供了一個名為 dapr-app-id 的元數據鍵,其中包含在 Dapr 中啟動的 gRPC 服務器的值(例如,我們示例中的 server
          4. Dapr 現在會將調用轉發到配置的 gRPC 服務器

          構建塊

          JavaScript 客戶端 SDK 允許您與專注于 Client to Sidecar 功能的所有 Dapr 構建塊進行交互。

          • https://docs.dapr.io/developing-applications/building-blocks/

          調用 API

          調用一個服務

          import { DaprClient, HttpMethod } from "@dapr/dapr"; 
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort); 
          
            const serviceAppId="my-app-id";
            const serviceMethod="say-hello";
            
            // POST Request
            const response=await client.invoker.invoke(serviceAppId , serviceMethod , HttpMethod.POST, { hello: "world" });
          
            // GET Request
            const response=await client.invoker.invoke(serviceAppId , serviceMethod , HttpMethod.GET);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關服務調用的完整指南,請訪問 How-To: Invoke a service。

          https://docs.dapr.io/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services/

          狀態管理 API

          保存、獲取和刪除應用程序狀態

          import { DaprClient } from "@dapr/dapr"; 
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort); 
          
            const serviceStoreName="my-state-store-name";
          
            // Save State
            const response=await client.state.save(serviceStoreName, [
              {
                key: "first-key-name",
                value: "hello"
              },
              {
                key: "second-key-name",
                value: "world"
              }
            ]);
          
            // Get State
            const response=await client.state.get(serviceStoreName, "first-key-name");
          
            // Get Bulk State
            const response=await client.state.getBulk(serviceStoreName, ["first-key-name", "second-key-name"]);
          
            // State Transactions
            await client.state.transaction(serviceStoreName, [
              {
                operation: "upsert",
                request: {
                  key: "first-key-name",
                  value: "new-data"
                }
              },
              {
                operation: "delete",
                request: {
                  key: "second-key-name"
                }
              }
            ]);
          
            // Delete State
            const response=await client.state.delete(serviceStoreName, "first-key-name");
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關狀態操作的完整列表,請訪問 How-To: Get & save state。

          https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-get-save-state/

          查詢狀態 API

          import { DaprClient } from "@dapr/dapr";
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort);
          
            const res=await client.state.query("state-mongodb", {
              filter: {
                OR: [
                  {
                    EQ: { "person.org": "Dev Ops" }
                  },
                  {
                    "AND": [
                      {
                        "EQ": { "person.org": "Finance" }
                      },
                      {
                        "IN": { "state": ["CA", "WA"] }
                      }
                    ]
                  }
                ]
              },
              sort: [
                {
                  key: "state",
                  order: "DESC"
                }
              ],
              page: {
                limit: 10
              }
            });
          
            console.log(res);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          發布訂閱 API

          發布消息

          import { DaprClient } from "@dapr/dapr"; 
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort); 
          
            const pubSubName="my-pubsub-name";
            const topic="topic-a";
            const message={ hello: "world" }
          
            // Publish Message to Topic
            const response=await client.pubsub.publish(pubSubName, topic, message);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          訂閱消息

          import { DaprServer } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; // Dapr Sidecar Host
          const daprPort="3500"; // Dapr Sidecar Port of this Example Server
          const serverHost="127.0.0.1"; // App Host of this Example Server
          const serverPort="50051"; // App Port of this Example Server "
          
          async function start() {
            const server=new DaprServer(serverHost, serverPort, daprHost, daprPort);
          
            const pubSubName="my-pubsub-name";
            const topic="topic-a";
          
            // Configure Subscriber for a Topic
            await server.pubsub.subscribe(pubSubName, topic, async (data: any)=> console.log(`Got Data: ${JSON.stringify(data)}`));
          
            await server.start();
          }
          

          有關狀態操作的完整列表,請訪問 How-To: Publish and subscribe。

          https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/

          綁定 API

          調用輸出綁定

          import { DaprClient } from "@dapr/dapr"; 
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort); 
          
            const bindingName="my-binding-name";
            const bindingOperation="create";
            const message={ hello: "world" };
          
            const response=await client.binding.send(bindingName, bindingOperation, message);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關輸出綁定的完整指南,請訪問 How-To: Use bindings。

          https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/

          Secret API

          檢索 secret

          import { DaprClient } from "@dapr/dapr"; 
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          
          async function start() {
            const client=new DaprClient(daprHost, daprPort); 
          
            const secretStoreName="my-secret-store";
            const secretKey="secret-key";
          
            // Retrieve a single secret from secret store
            const response=await client.secret.get(secretStoreName, secretKey);
          
            // Retrieve all secrets from secret store
            const response=await client.secret.getBulk(secretStoreName);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關 secrets 的完整指南,請訪問 How-To: Retrieve Secrets。

          https://docs.dapr.io/developing-applications/building-blocks/secrets/howto-secrets/

          配置 API

          獲取配置 key

          import { DaprClient } from "@dapr/dapr";
          
          const daprHost="127.0.0.1";
          const daprAppId="example-config";
          
          async function start() {
          
            const client=new DaprClient(daprHost, process.env.DAPR_HTTP_PORT);
          
            const config=await client.configuration.get('config-store', ['key1', 'key2']);
            console.log(config);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          Server

          介紹

          Dapr Server 將允許您接收來自 Dapr Sidecar 的通信并訪問其面向服務器的功能,例如:訂閱事件、接收輸入綁定等等。

          前提條件

          • Dapr CLI 已安裝https://docs.dapr.io/getting-started/install-dapr-cli/
          • 初始化 Dapr 環境https://docs.dapr.io/getting-started/install-dapr-selfhost/
          • 最新 LTS 版本的 Node 或更高版本https://nodejs.org/en/

          安裝和導入 Dapr 的 JS SDK

          1. 使用 npm 安裝 SDK:
          npm i @dapr/dapr --save
          
          1. 導入庫:
          import { DaprClient, DaprServer, HttpMethod, CommunicationProtocolEnum } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; // Dapr Sidecar Host
          const daprPort="3500"; // Dapr Sidecar Port of this Example Server
          const serverHost="127.0.0.1"; // App Host of this Example Server
          const serverPort="50051"; // App Port of this Example Server 
          
          // HTTP Example
          const client=new DaprClient(daprHost, daprPort);
          
          // GRPC Example
          const client=new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
          

          運行

          要運行示例,您可以使用兩種不同的協議與 Dapr sidecar 交互:HTTP(默認)或 gRPC。

          使用 HTTP(默認)

          import { DaprServer } from "@dapr/dapr";
          
          const server=new DaprServer(appHost, appPort, daprHost, daprPort);
          // initialize subscribtions, ... before server start
          // the dapr sidecar relies on these
          await server.start(); 
          
          # Using dapr run
          dapr run --app-id example-sdk --app-port 50051 --app-protocol http -- npm run start
          
          # or, using npm script
          npm run start:dapr-http
          

          ?? Note:這里需要 app-port,因為這是我們的服務器需要綁定的地方。 Dapr 將在完成啟動之前檢查應用程序是否綁定到此端口。

          使用 gRPC

          由于 HTTP 是默認設置,因此您必須調整通信協議以使用 gRPC。 您可以通過向客戶端或服務器構造函數傳遞一個額外的參數來做到這一點。

          import { DaprServer, CommunicationProtocol } from "@dapr/dapr";
          
          const server=new DaprServer(appHost, appPort, daprHost, daprPort, CommunicationProtocol.GRPC);
          // initialize subscribtions, ... before server start
          // the dapr sidecar relies on these
          await server.start(); 
          
          # Using dapr run
          dapr run --app-id example-sdk --app-port 50051 --app-protocol grpc -- npm run start
          
          # or, using npm script
          npm run start:dapr-grpc
          

          ?? Note:這里需要 app-port,因為這是我們的服務器需要綁定的地方。 Dapr 將在完成啟動之前檢查應用程序是否綁定到此端口。

          構建塊

          JavaScript Server SDK 允許您與專注于 Sidecar 到 App 功能的所有 Dapr 構建塊進行交互。

          調用 API

          監聽調用

          import { DaprServer } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; // Dapr Sidecar Host
          const daprPort="3500"; // Dapr Sidecar Port of this Example Server
          const serverHost="127.0.0.1"; // App Host of this Example Server
          const serverPort="50051"; // App Port of this Example Server "
          
          async function start() {
            const server=new DaprServer(serverHost, serverPort, daprHost, daprPort);
          
            await server.invoker.listen('hello-world', mock, { method: HttpMethod.GET });
          
            // You can now invoke the service with your app id and method "hello-world"
          
            await server.start();
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關服務調用的完整指南,請訪問 How-To: Invoke a service。

          https://docs.dapr.io/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services/

          發布訂閱 API

          訂閱消息

          import { DaprServer } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; // Dapr Sidecar Host
          const daprPort="3500"; // Dapr Sidecar Port of this Example Server
          const serverHost="127.0.0.1"; // App Host of this Example Server
          const serverPort="50051"; // App Port of this Example Server "
          
          async function start() {
            const server=new DaprServer(serverHost, serverPort, daprHost, daprPort);
          
            const pubSubName="my-pubsub-name";
            const topic="topic-a";
          
            // Configure Subscriber for a Topic
            await server.pubsub.subscribe(pubSubName, topic, async (data: any)=> console.log(`Got Data: ${JSON.stringify(data)}`));
          
            await server.start();
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關狀態操作的完整列表,請訪問 How-To: Publish and subscribe。

          https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/

          綁定 API

          接收一個輸入綁定

          import { DaprServer } from "@dapr/dapr";
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          const serverHost="127.0.0.1";
          const serverPort="5051";
          
          async function start() {
            const server=new DaprServer(serverHost, serverPort, daprHost, daprPort);
          
            const bindingName="my-binding-name";
          
            const response=await server.binding.receive(bindingName, async (data: any)=> console.log(`Got Data: ${JSON.stringify(data)}`));
          
            await server.start();
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          有關輸出綁定的完整指南,請訪問 How-To: Use bindings。

          https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/

          配置 API

          配置 API 目前只能通過 gRPC 使用

          獲取配置值

          import { DaprServer } from "dapr-client";
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          const serverHost="127.0.0.1";
          const serverPort="5051";
          
          async function start() {
              const client=new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
              const config=await client.configuration.get("config-redis", ["myconfigkey1", "myconfigkey2"]);
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          訂閱 key 更改

          import { DaprServer } from "dapr-client";
          
          const daprHost="127.0.0.1"; 
          const daprPort="3500"; 
          const serverHost="127.0.0.1";
          const serverPort="5051";
          
          async function start() {
              const client=new DaprClient(daprHost, daprPort, CommunicationProtocolEnum.GRPC);
              const stream=await client.configuration.subscribeWithKeys("config-redis", ["myconfigkey1", "myconfigkey2"], ()=> {
                  // Received a key update
              });
          
              // When you are ready to stop listening, call the following
              await stream.close();
          }
          
          start().catch((e)=> {
            console.error(e);
            process.exit(1);
          });
          

          Actors

          Dapr actors 包允許您從 JavaScript 應用程序與 Dapr virtual actors 進行交互。下面的示例演示了如何使用 JavaScript SDK 與 virtual actors 進行交互。

          如需更深入地了解 Dapr actors,請訪問 actors 概覽頁面。

          前提條件

          • Dapr CLI 已安裝https://docs.dapr.io/getting-started/install-dapr-cli/
          • 初始化 Dapr 環境https://docs.dapr.io/getting-started/install-dapr-selfhost/
          • 最新 LTS 版本的 Node 或更高版本https://nodejs.org/en/
          • 已安裝 JavaScript NPM 包https://www.npmjs.com/package/@dapr/dapr

          場景

          下面的代碼示例粗略地描述了停車場現場監控系統的場景,可以在 Mark Russinovich 的這段視頻中看到。

          • https://www.youtube.com/watch?v=eJCu6a-x9uo&t=3785

          一個停車場由數百個停車位組成,每個停車位都包含一個傳感器,可為中央監控系統提供更新。 停車位傳感器(我們的 actors)檢測停車位是否被占用或可用。

          要自己跳入并運行此示例,請克隆源代碼,該源代碼可在 JavaScript SDK 示例目錄中找到。

          • https://github.com/dapr/js-sdk/tree/master/examples/http/actor-parking-sensor

          Actor 接口

          Actor 接口定義了在 Actor 實現和調用 Actor 的客戶端之間共享的合約。 在下面的示例中,我們為停車場傳感器創建了一個接口。 每個傳感器有 2 個方法:carEntercarLeave,它們定義了停車位的狀態:

          export default interface ParkingSensorInterface {
            carEnter(): Promise<void>;
            carLeave(): Promise<void>;
          }
          

          Actor 實現

          Actor 實現通過擴展基本類型 AbstractActor 并實現 Actor 接口(在本例中為 ParkingSensorInterface)來定義一個類。

          下面的代碼描述了一個 actor 實現以及一些輔助方法。

          import { AbstractActor } from "@dapr/dapr";
          import ParkingSensorInterface from "./ParkingSensorInterface";
          
          export default class ParkingSensorImpl extends AbstractActor implements ParkingSensorInterface {
            async carEnter(): Promise<void> {
              // Implementation that updates state that this parking spaces is occupied.
            }
          
            async carLeave(): Promise<void> {
              // Implementation that updates state that this parking spaces is available.
            }
          
            private async getInfo(): Promise<object> {
              // Implementation of requesting an update from the parking space sensor.
            }
          
            /**
             * @override
             */
            async onActivate(): Promise<void> {
              // Initialization logic called by AbstractActor.
            }
          }
          

          注冊 Actor

          使用 DaprServer 包初始化和注冊你的 actors:

          import { DaprServer } from "@dapr/dapr";
          import ParkingSensorImpl from "./ParkingSensorImpl";
          
          const daprHost="127.0.0.1";
          const daprPort="50000";
          const serverHost="127.0.0.1";
          const serverPort="50001";
          
          const server=new DaprServer(serverHost, serverPort, daprHost, daprPort);
          
          await server.actor.init(); // Let the server know we need actors
          server.actor.registerActor(ParkingSensorImpl); // Register the actor
          await server.start(); // Start the server
          
          // To get the registered actors, you can invoke `getRegisteredActors`:
          const resRegisteredActors=await server.actor.getRegisteredActors();
          console.log(`Registered Actors: ${JSON.stringify(resRegisteredActors)}`);
          

          調用 Actor 方法

          注冊 Actor 后,使用 ActorProxyBuilder 創建一個實現 ParkingSensorInterface 的代理對象。 您可以通過直接調用 Proxy 對象上的方法來調用 actor 方法。在內部,它轉換為對 Actor API 進行網絡調用并取回結果。

          import { DaprClient, ActorId } from "@dapr/dapr";
          import ParkingSensorImpl from "./ParkingSensorImpl";
          import ParkingSensorInterface from "./ParkingSensorInterface";
          
          const daprHost="127.0.0.1";
          const daprPort="50000";
          
          const client=new DaprClient(daprHost, daprPort);
          
          // Create a new actor builder. It can be used to create multiple actors of a type.
          const builder=new ActorProxyBuilder<ParkingSensorInterface>(ParkingSensorImpl, client);
          
          // Create a new actor instance.
          const actor=builder.build(new ActorId("my-actor"));
          // Or alternatively, use a random ID
          // const actor=builder.build(ActorId.createRandomId());
          
          // Invoke the method.
          await actor.carEnter();
          

          將狀態與 Actor 一起使用

          // ...
          
          const PARKING_SENSOR_PARKED_STATE_NAME="parking-sensor-parked"
          
          const actor=builder.build(new ActorId("my-actor")) 
          
          // SET state
          await actor.getStateManager().setState(PARKING_SENSOR_PARKED_STATE_NAME, true);
          
          // GET state
          const value=await actor.getStateManager().getState(PARKING_SENSOR_PARKED_STATE_NAME);
          if (!value) {
            console.log(`Received: ${value}!`);
          }
          
          // DELETE state
          await actor.removeState(PARKING_SENSOR_PARKED_STATE_NAME);
          ...
          

          Actor 計時器和提醒器

          JS SDK 支持 actors 可以通過注冊 timers 或 reminders 來為自己安排定期工作。timers 和 reminders 之間的主要區別在于,Dapr actor runtime 在停用后不保留有關 timers 的任何信息,而是使用 Dapr actor state provider 保留 reminders 信息。

          這種區別允許用戶在輕量級但無狀態的 timers 和更需要資源但有狀態的 reminders 之間進行權衡。

          Timers 和 reminders 的調度界面是相同的。要更深入地了解調度配置,請參閱 actors timers 和 reminders 文檔。

          • https://docs.dapr.io/developing-applications/building-blocks/actors/howto-actors/#actor-timers-and-reminders

          Actor Timers

          // ...
          
          const actor=builder.build(new ActorId("my-actor"));
          
          // Register a timer
          await actor.registerActorTimer(
            "timer-id", // Unique name of the timer.
            "cb-method", // Callback method to execute when timer is fired.
            Temporal.Duration.from({ seconds: 2 }), // DueTime
            Temporal.Duration.from({ seconds: 1 }), // Period
            Temporal.Duration.from({ seconds: 1 }), // TTL
            50 // State to be sent to timer callback.
          );
          
          // Delete the timer
          await actor.unregisterActorTimer("timer-id");
          

          Actor Reminders

          // ...
          
          const actor=builder.build(new ActorId("my-actor"));
          
          // Register a reminder, it has a default callback: `receiveReminder`
          await actor.registerActorReminder(
            "reminder-id", // Unique name of the reminder.
            Temporal.Duration.from({ seconds: 2 }), // DueTime
            Temporal.Duration.from({ seconds: 1 }), // Period
            Temporal.Duration.from({ seconds: 1 }), // TTL
            100 // State to be sent to reminder callback.
          );
          
          // Delete the reminder
          await actor.unregisterActorReminder("reminder-id");
          

          要處理回調,您需要覆蓋 actor 中的默認 receiveReminder 實現。 例如,從我們最初的 actor 實現中:

          export default class ParkingSensorImpl extends AbstractActor implements ParkingSensorInterface {
            // ...
          
            /**
             * @override
             */
            async receiveReminder(state: any): Promise<void> {
              // handle stuff here
            }
          
            // ...
          }
          

          有關 actors 的完整指南,請訪問 How-To: Use virtual actors in Dapr。

          • https://docs.dapr.io/developing-applications/building-blocks/actors/howto-actors/

          Logging

          介紹

          JavaScript SDK 帶有一個開箱即用的基于 Console 的 logger。SDK 發出各種內部日志,以幫助用戶了解事件鏈并解決問題。此 SDK 的使用者可以自定義日志的詳細程度,并為 logger 提供自己的實現。

          配置日志級別

          有五個級別的日志記錄,按重要性降序排列 - errorwarninfoverbosedebug。 將日志設置為一個級別意味著 logger 將發出至少與上述級別一樣重要的所有日志。 例如,設置為 verbose 日志意味著 SDK 不會發出 debug 級別的日志。默認日志級別是 info

          Dapr Client

          import { CommunicationProtocolEnum, DaprClient, LogLevel } from "@dapr/dapr";
          
          // create a client instance with log level set to verbose.
          const client=new DaprClient(
              daprHost, 
              daprPort, 
              CommunicationProtocolEnum.HTTP, 
              { logger: { level: LogLevel.Verbose } });
          

          有關如何使用 Client 的更多詳細信息,請參閱 JavaScript Client。

          https://docs.dapr.io/developing-applications/sdks/js/js-client/

          DaprServer

          import { CommunicationProtocolEnum, DaprServer, LogLevel } from "@dapr/dapr";
          
          // create a server instance with log level set to error.
          const server=new DaprServer(
              serverHost,
              serverPort, 
              daprHost,
              daprPort,
              CommunicationProtocolEnum.HTTP,
              { logger: { level: LogLevel.Error } });
          

          有關如何使用 Server 的更多詳細信息,請參閱 JavaScript Server。

          https://docs.dapr.io/developing-applications/sdks/js/js-server/

          自定義 LoggerService

          JavaScript SDK 使用內置 Console 進行日志記錄。要使用 Winston 或 Pino 等自定義 logger,您可以實現 LoggerService 接口。

          基于 Winston 的日志記錄:

          創建 LoggerService 的新實現。

          import { LoggerService } from "@dapr/dapr";
          import * as winston from 'winston';
          
          export class WinstonLoggerService implements LoggerService {
              private logger;
          
              constructor() {
                  this.logger=winston.createLogger({
                      transports: [
                          new winston.transports.Console(),
                          new winston.transports.File({ filename: 'combined.log' })
                      ]
                  });
              }
          
              error(message: any, ...optionalParams: any[]): void {
                  this.logger.error(message, ...optionalParams)
              }
              warn(message: any, ...optionalParams: any[]): void {
                  this.logger.warn(message, ...optionalParams)
              }
              info(message: any, ...optionalParams: any[]): void {
                  this.logger.info(message, ...optionalParams)
              }
              verbose(message: any, ...optionalParams: any[]): void {
                  this.logger.verbose(message, ...optionalParams)
              }
              debug(message: any, ...optionalParams: any[]): void {
                  this.logger.debug(message, ...optionalParams)
              }
          }
          

          將新實現傳遞給 SDK。

          import { CommunicationProtocolEnum, DaprClient, LogLevel } from "@dapr/dapr";
          import { WinstonLoggerService } from "./WinstonLoggerService";
          
          const winstonLoggerService=new WinstonLoggerService();
          
          // create a client instance with log level set to verbose and logger service as winston.
          const client=new DaprClient(
              daprHost,
              daprPort,
              CommunicationProtocolEnum.HTTP,
              { logger: { level: LogLevel.Verbose, service: winstonLoggerService } });
          

          官方示例代碼庫

          • https://github.com/dapr/js-sdk/tree/main/examples

          者: 為少 來源: 黑客下午茶

          源碼倉庫地址

          https://github.com/getsentry/sentry-javascript

          支持的平臺

          對于每個主要的 JavaScript 平臺,都有一個特定的高階 SDK,可以在單個包中提供您需要的所有工具。有關更多詳細信息,請參閱這些 SDK 的 README 和說明:

          • @sentry/browser: 瀏覽器的 SDK,包括對基礎主干(GlobalHandlers, TryCatch, Breadcrumbs, LinkedErrors, UserAgent, Dedupe)的集成。
          • @sentry/node: 適用于 Node 的 SDK,包括 Express、Koa、Loopback、Sails 和 Connect 的集成。
          • @sentry/angular: 啟用 Angular 集成的瀏覽器 SDK。
          • @sentry/react: 啟用 React 集成的瀏覽器 SDK。
          • @sentry/ember: 啟用 Ember 集成的瀏覽器 SDK。
          • @sentry/vue: 啟用 Vue 集成的瀏覽器 SDK。
          • @sentry/gatsby: Gatsby 的 SDK。
          • @sentry/nextjs: Next.js 的 SDK。
          • @sentry/integrations: 可用于增強 JS SDK 的可插拔集成。
          • @sentry/electron: 支持原生崩潰的 Electron SDK。
          • @sentry/react-native: 支持原生崩潰的 React Native SDK。
          • @sentry/capacitor:支持原生崩潰的 Capacitor App 和 Ionic 的 SDK。
          • sentry-cordova:支持原生崩潰的 Cordova App 的 SDK。
          • raven-js:舊的穩定 JavaScript SDK,我們仍然支持并發布 SDK 的錯誤修復,但所有新功能都將在 @sentry/browser 中實現,它是繼任者。
          • raven:舊的穩定 Node SDK,與 raven-js 一樣,我們仍然支持并發布 SDK 的錯誤修復,但所有新功能都將在 @sentry/node 中實現,它是繼任者。

          用于平臺 SDK 開發的共享軟件包

          • @sentry/tracing: 為性能監控/跟蹤提供集成和擴展。
          • @sentry/hub: SDK 的全局狀態管理。
          • @sentry/minimal: Sentry 支持的最小 SDK
          • @sentry/core: 具有接口、類型定義和基類的所有 JavaScript SDK 的基礎。
          • @sentry/utils: 一組對各種 SDK 有用的輔助程序和實用函數。
          • @sentry/types: 所有軟件包中使用的類型定義。

          開發調試

          設置環境

          要運行 test suite 和 code linter,需要 node.js 和 yarn。

          • https://nodejs.org/download
          • https://yarnpkg.com/en/docs/install

          sentry-javascript 是一個包含多個軟件包的 monorepo,使用 lerna 管理它們。首先,安裝所有依賴項,使用 lerna 引導工作區,然后執行初始構建,以便 TypeScript 可以讀取所有鏈接的類型定義。

          yarn 
           
          yarn lerna bootstrap 
           
          yarn build 

          這樣,repo 就完全設置好了,您可以運行所有命令了。

          構建軟件包

          由于我們使用的是 TypeScript,因此您需要將代碼轉換為 JavaScript 才能使用它。來自 repo 的頂層,有三個可用命令:

          • yarn build:dev,它運行每個包的 ES5 和 ES6 版本的一次性構建。
          • yarn build:dev:filter ,它只在與給定包相關的項目中運行 yarn build:dev(例如,運行 yarn build:dev:filter @sentry/react 將構建 react 包、它的所有依賴項(utils、core、browser 等),以及所有依賴它的包(目前是 gatsby 和 nextjs))。
          • yarn build:dev:watch,在 watch 模式下運行 yarn build:dev(推薦)

          添加測試

          任何重要的修復/功能都應該包括測試。您會在每個軟件包中找到一個 test 文件夾。

          請注意,僅對于 browser 包,如果您將新文件添加到集成測試套件中,您還需要將其添加到shell.js 中的列表中。在所有包中,向現有文件添加測試都可以開箱即用。

          運行測試

          運行測試與構建的工作方式相同 - 在項目根目錄運行 yarn test 將對所有包運行測試,在特定包中運行 yarn test 將為該包運行測試。還有一些命令可以在每個位置運行測試的子集。查看相應 package.json 的 scripts 條目以了解詳細信息。

          注意:你必須在 yarn test 工作之前運行 yarn build。

          調試測試

          如果您在編寫測試時遇到麻煩并需要調試其中之一,您可以使用 VSCode 的 debugger 來完成。

          如果您尚未安裝它,請安裝 Tasks Shell Input 擴展,您可以在側邊欄的“擴展”選項卡中找到它作為推薦的工作區擴展之一。

          • 將斷點或 debugger 語句放置在測試或底層代碼中您希望 jest 暫停的任何位置。
          • 打開包含相關測試的文件,并確保其選項卡處于活動狀態(以便您可以看到文件的內容)。
          • 切換到側邊欄中的 debugger,然后從下拉列表中選擇 Debug unit tests - just open file。
          • 單擊綠色的 “play” 按鈕以 watch 模式在打開的文件中運行測試。

          實戰

          測試代碼:

          https://github.com/getsentry/sentry-javascript/blob/master/packages/minimal/test/lib/minimal.test.ts

          專業提示:如果您的任何斷點在由多個測試運行的代碼中,并且您運行整個測試文件,您將在不關心的測試中間一遍又一遍地停留在這些斷點上。為避免這種情況,請將測試的初始 it 或 test 替換為 it.only 或 test.only。這樣,當您遇到斷點時,您就會知道您到達了有問題的測試的一部分。

          Linting

          與構建和測試類似,linting 可以通過調用 yarn lint 在項目根目錄或單個包中完成。

          注意:你必須在 yarn lint 工作之前運行 yarn build。


          主站蜘蛛池模板: 性色av无码免费一区二区三区| 视频在线一区二区三区| 亚洲国产一区二区三区在线观看| 日本在线视频一区二区三区| 国产精品自在拍一区二区不卡| 91精品国产一区| 中文字幕一区二区人妻性色| 国产激情一区二区三区在线观看| 国产午夜精品一区二区三区| 狠狠做深爱婷婷综合一区| 国产一区高清视频| 日本一区二区三区不卡在线视频| 人妻AV一区二区三区精品| 久久一区二区三区精品| 国产精品亚洲产品一区二区三区 | 一区二区三区影院| 日本一区二区三区在线网 | 国产精品无码一区二区在线观| 无码AV天堂一区二区三区| 亚洲永久无码3D动漫一区| 嫩B人妻精品一区二区三区| 国产激情一区二区三区| 中文字幕在线无码一区| 免费无码一区二区三区蜜桃| 91福利国产在线观看一区二区| 国产精品福利区一区二区三区四区| 国产成人av一区二区三区不卡| 日本在线视频一区二区三区| 一区二区三区视频在线播放| 变态拳头交视频一区二区| 国产精品区一区二区三在线播放| 久久青草精品一区二区三区| 中文字幕视频一区| 国产Av一区二区精品久久| 精品一区二区三区无码免费直播| 久久国产精品免费一区| 精品无码人妻一区二区免费蜜桃| 精品少妇人妻AV一区二区三区| 国产综合精品一区二区| 中文字幕一区二区三区四区| 无码精品黑人一区二区三区|