騰訊安全了解到,騰訊安全反詐騙實驗室追蹤到暴風影音、天天看、塔讀文學等眾多應用中集成的某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 降低了構建微服務架構類現代云原生應用的門檻。
用于在 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 包含兩個主要組件:
上述通信可以配置為使用 gRPC 或 HTTP 協議。
Dapr Client 允許您與 Dapr Sidecar 通信并訪問其面向客戶端的功能,例如發布事件、調用輸出綁定、狀態管理、Secret 管理等等。
npm i @dapr/dapr --save
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。
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
由于 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 示例)
JavaScript 客戶端 SDK 允許您與專注于 Client to Sidecar 功能的所有 Dapr 構建塊進行交互。
調用一個服務
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/
保存、獲取和刪除應用程序狀態
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/
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);
});
發布消息
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/
調用輸出綁定
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
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/
獲取配置 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);
});
Dapr Server 將允許您接收來自 Dapr Sidecar 的通信并訪問其面向服務器的功能,例如:訂閱事件、接收輸入綁定等等。
npm i @dapr/dapr --save
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。
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 將在完成啟動之前檢查應用程序是否綁定到此端口。
由于 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 構建塊進行交互。
監聽調用
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/
訂閱消息
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/
接收一個輸入綁定
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 目前只能通過 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);
});
Dapr actors 包允許您從 JavaScript 應用程序與 Dapr virtual actors 進行交互。下面的示例演示了如何使用 JavaScript SDK 與 virtual actors 進行交互。
如需更深入地了解 Dapr actors,請訪問 actors 概覽頁面。
下面的代碼示例粗略地描述了停車場現場監控系統的場景,可以在 Mark Russinovich 的這段視頻中看到。
一個停車場由數百個停車位組成,每個停車位都包含一個傳感器,可為中央監控系統提供更新。 停車位傳感器(我們的 actors)檢測停車位是否被占用或可用。
要自己跳入并運行此示例,請克隆源代碼,該源代碼可在 JavaScript SDK 示例目錄中找到。
Actor 接口定義了在 Actor 實現和調用 Actor 的客戶端之間共享的合約。 在下面的示例中,我們為停車場傳感器創建了一個接口。 每個傳感器有 2 個方法:carEnter 和 carLeave,它們定義了停車位的狀態:
export default interface ParkingSensorInterface {
carEnter(): Promise<void>;
carLeave(): Promise<void>;
}
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.
}
}
使用 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 后,使用 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();
// ...
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);
...
JS SDK 支持 actors 可以通過注冊 timers 或 reminders 來為自己安排定期工作。timers 和 reminders 之間的主要區別在于,Dapr actor runtime 在停用后不保留有關 timers 的任何信息,而是使用 Dapr actor state provider 保留 reminders 信息。
這種區別允許用戶在輕量級但無狀態的 timers 和更需要資源但有狀態的 reminders 之間進行權衡。
Timers 和 reminders 的調度界面是相同的。要更深入地了解調度配置,請參閱 actors timers 和 reminders 文檔。
// ...
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");
// ...
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。
JavaScript SDK 帶有一個開箱即用的基于 Console 的 logger。SDK 發出各種內部日志,以幫助用戶了解事件鏈并解決問題。此 SDK 的使用者可以自定義日志的詳細程度,并為 logger 提供自己的實現。
有五個級別的日志記錄,按重要性降序排列 - error、warn、info、verbose 和 debug。 將日志設置為一個級別意味著 logger 將發出至少與上述級別一樣重要的所有日志。 例如,設置為 verbose 日志意味著 SDK 不會發出 debug 級別的日志。默認日志級別是 info。
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/
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/
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/getsentry/sentry-javascript
對于每個主要的 JavaScript 平臺,都有一個特定的高階 SDK,可以在單個包中提供您需要的所有工具。有關更多詳細信息,請參閱這些 SDK 的 README 和說明:
設置環境
要運行 test suite 和 code linter,需要 node.js 和 yarn。
sentry-javascript 是一個包含多個軟件包的 monorepo,使用 lerna 管理它們。首先,安裝所有依賴項,使用 lerna 引導工作區,然后執行初始構建,以便 TypeScript 可以讀取所有鏈接的類型定義。
yarn
yarn lerna bootstrap
yarn build
這樣,repo 就完全設置好了,您可以運行所有命令了。
構建軟件包
由于我們使用的是 TypeScript,因此您需要將代碼轉換為 JavaScript 才能使用它。來自 repo 的頂層,有三個可用命令:
添加測試
任何重要的修復/功能都應該包括測試。您會在每個軟件包中找到一個 test 文件夾。
請注意,僅對于 browser 包,如果您將新文件添加到集成測試套件中,您還需要將其添加到shell.js 中的列表中。在所有包中,向現有文件添加測試都可以開箱即用。
運行測試
運行測試與構建的工作方式相同 - 在項目根目錄運行 yarn test 將對所有包運行測試,在特定包中運行 yarn test 將為該包運行測試。還有一些命令可以在每個位置運行測試的子集。查看相應 package.json 的 scripts 條目以了解詳細信息。
注意:你必須在 yarn test 工作之前運行 yarn build。
調試測試
如果您在編寫測試時遇到麻煩并需要調試其中之一,您可以使用 VSCode 的 debugger 來完成。
如果您尚未安裝它,請安裝 Tasks Shell Input 擴展,您可以在側邊欄的“擴展”選項卡中找到它作為推薦的工作區擴展之一。
實戰
測試代碼:
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。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。