基于HarmonyOS Next的营销防薅羊毛设计:Device Certificate Kit的活动防护策略

news/2024/11/13 23:57:12/文章来源:https://www.cnblogs.com/samex/p/18541156

本文旨在深入探讨华为鸿蒙HarmonyOS Next系统(截止目前API12)的技术细节,基于实际开发实践进行总结。主要作为技术分享与交流载体,难免错漏,欢迎各位同仁提出宝贵意见和问题,以便共同进步。本文为原创内容,任何形式的转载必须注明出处及原作者。

在电商和营销活动日益频繁的今天,黑灰产利用各种手段薅羊毛的现象愈发猖獗,给企业带来了巨大的经济损失。基于华为鸿蒙HarmonyOS Next系统,我们可以借助Device Certificate Kit等强大工具构建一套有效的活动防护策略,确保营销活动的公平性和安全性。

一、场景描述

在电商平台的促销活动(如限时折扣、满减活动、抽奖等)以及各类营销推广活动中,黑灰产常常通过使用模拟器或非合法应用批量注册账号、自动参与活动,以获取不正当利益。例如,在限时抢购活动中,他们利用自动化脚本在模拟器上快速下单,抢购大量优惠商品,导致真正的用户无法享受到活动福利;在抽奖活动中,通过伪造请求或重放合法请求来增加中奖概率。因此,我们需要一种可靠的机制来识别并阻止这些非法行为,保障营销活动的正常进行。

二、架构设计

为了实现营销活动的安全防护,我们利用Device Certificate Kit和随机数生成机制,构建了一套多层验证的架构。

  1. Device Certificate Kit
    • 负责生成和管理应用的证书链,其中包含应用的公钥等关键信息。在应用安装时,为应用生成唯一的证书,并将证书相关信息安全存储。
    • 提供证书验证功能,用于在服务端验证请求设备或应用的证书链的有效性和真实性,确保请求来自合法的应用和设备。
  2. 随机数生成机制
    • 在服务端生成一次性的随机挑战值(challenge),并将其发送给应用端。这个挑战值在每次活动请求中都是唯一的,用于增加请求的随机性和不可预测性,防止重放攻击。
    • 应用端将挑战值与业务请求数据结合,使用证书中的私钥进行签名,然后将签名后的请求发送回服务端。服务端通过验证签名和挑战值的一致性,确保请求在传输过程中未被篡改。

三、实现步骤

  1. 为应用生成随机挑战值,用于验证活动请求的真实性
    • 服务端在收到活动请求前,使用安全的随机数生成算法(例如,基于Crypto Architecture Kit中的随机数生成功能)生成一个随机挑战值。示例代码如下:
import { cryptoFramework } from '@kit.CryptoArchitectureKit';async function generateChallenge() {let randomData = await cryptoFramework.generateRandomBytes(16); return randomData.data;
}let challenge = await generateChallenge();
console.log('Generated challenge:', challenge);
  • 服务端将挑战值发送给应用端,可以通过安全的通信协议(如HTTPS)进行传输,确保挑战值在传输过程中不被窃取或篡改。
  1. 应用端使用证书和私钥生成签名,确保请求未被篡改
    • 应用端接收到挑战值后,获取存储在设备中的证书私钥(通过Universal Keystore Kit)。示例代码如下:
import { huks } from '@kit.UniversalKeystoreKit';
import { BusinessError } from '@kit.BasicServicesKit';let keyAlias = 'your_app_key_alias'; async function getPrivateKey() {let options: huks.HuksOptions = {properties: []}try {let key = await huks.getKeyItem(keyAlias, options);return key;} catch (err) {let e: BusinessError = err as BusinessError;console.error(`getPrivateKey failed, error: ` + err.message);}
}let privateKey = await getPrivateKey();
  • 然后,应用端将挑战值与业务请求数据(如用户参与活动的信息、时间戳等)组合在一起,使用私钥对组合后的数据进行签名。假设业务请求数据为activityRequestData
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';let combinedData = new Uint8Array([...challenge,...new Uint8Array(textEncoder.encodeInto(activityRequestData).data)]);async function signRequestData(data: Uint8Array) {let signature = await cryptoFramework.sign({key: privateKey,data: data});console.log('Signature:', signature.data);return signature;
}let signature = await signRequestData(combinedData);
  • 最后,应用端将签名、挑战值(或其哈希值,用于减少数据传输量)和业务请求数据一起发送给服务端。
  1. 服务端对请求进行挑战值和证书链的多层验证
    • 服务端首先验证证书链的有效性,确保请求来自合法的应用和设备。使用Device Certificate Kit的相关API进行证书链验证,示例代码如下:
import { cert } from '@kit.DeviceCertificateKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';// 假设这是设备证书链数据(实际应用中需从请求中获取)
let deviceCertChainData = "-----BEGIN CERTIFICATE-----\n" +
"MIID6jCCAtKgAwIBAgIIIM2q/TmRoLcwDQYJKoZIhvcNAQELBQAwWjELMAkGA1\n" +
"UEBhMCRU4xEDAOBgNVBAgTB0VuZ2xhbmQxDzANBgNVBAcTBkxvbmRvbjEMMA\n" +
"oGA1UEChMDdHMyMQwwCgYDVQQLEwN0czIxDDAKBgNVBAMTA3RzMjAeFw0yMzEy\n" +
"MDUwNzM5MDBaFw0yNDEwMzEyMzU5MDBaMGExCzAJBgNVBAYTAkNOMRAwDgYDVQQI\n" +
"EwdKaWFuZ3N1MRAwDgYDVQQHEwdOYW5qaW5nMQwwCgYDVQQKEwN0czMxDDAKBg\n" +
"NVBAsTA3RzMzESMBAGA1UEAxMJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
"MIIBCgKCAQEAtt+2QxUevbolYLp51QGcUpageI4fwGLIqv4fj4aoVnHFOOBqVOVpfCLR\n" +
"p26LFV/F8ebwPyo8YEBKSwXzMD1573rMSbaH9BalscH5lZYAbetXoio6YRvzlcmc\n" +
"rVvLBNMeVnxY86xHpo0MTNyP7W024rZsxWO98xFQVdoiaBC+7+midlisx2Y+7u0\n" +
"zT9GjeUP6JLdLFUZJKUPSTK3jVzw9v1eZQZKYoNfU6vFMd6ndtwW6qEnwpzmmX\n" +
"/UT+p5ThAMH593zszlz330nTSXBjIsGkyvOz9gSB0Z0LAuJj06XUNhGL5xKJYKbdI3\n" +
"8MFQFJKvRHfgTAvVsvAvpBUM2DuBKwIDAQABo4GsMIGpMAkGA1UdEwQCMAA\n" +
"wHQYDVR0OBBYEFDfsHTMZwoA6eaDFlBUyDpka+sYtMAsGA1UdDwQEAwID+DAnBgN\n" +
"VHSUEIDAeBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMEMBQGA1UdEQQNM\n" +
"AuCCTEyNy4wLjAuMTARBglghkgBhvhCAQEEBAMCBkAwHgYJYIZIAYb4QgENBBEWD3hj\n" +
"YSBjZXJ0aWZpY2F0ZTANBgkqhkiG9w0BAQsFAAOCAQEAp5vTvXrt8ZpgRJVtzv9ss0lJ\n" +
"izp1fJf+ft5cDXrs7TSD5oHrSW2vk/ZieIMhexU4LFwhs4OE7jK6pgI48Dseqxx7\n" +
"B/KktxhVMJUmVXd9Ayjp6f+BtZlIk0cArPuoXToXjsV8caTGBXHRdzxpAk/w9syc\n" +
"GYrbH9TrdNMuTizOb+k268oKXUageZNxHmd7YvOXkcNgrd29jzwXKDYYiUa1DI\n" +
"SzDnYaJOgPt0B/5izhoWNK7GhJDy9KEuLURcTSWFysbbnljwO9INPT9MmlS83PdAg\n" +
"NiS8VXF4pce1W9U5jH7d7k0JDVSXybebe1iPFphsZpYM/NE+jap+mPy1nTCbf9g==\n" +
"-----END CERTIFICATE-----\n" +
"-----BEGIN CERTIFICATE-----\n" +
"MIIC0zCCAoWgAwIBAgIIXpLoPpQVWnkwBQYDK2VwMFoxCzAJBgNVBAYTAkV\n" +
"OMRAwDgYDVQQIEwdFbmdsYW5kMQ8wDQYDVQQHEwZMb25kb24xDDAKBgNVBAoT\n" +
"A3RzMTEMMAoGA1UECxMDdHMxMQwwCgYDVQQDEwN0czEwHhcNMjMxMjA1MDczNzA\n" +
"wWhcNMjQwOTAxMjM1OTAwWjBaMQswCQYDVQQGEwJFTjEQMA4GA1UECBMHRW5nbGFu\n" +
"ZDEPMA0GA1UEBxMGTG9uZG9uMQwwCgYDVQQKEwN0czIxDDAKBgNVBAsTA3RzMjEMM\n" +
"AoGA1UEAxMDdHMyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A\n" +
"MIIBCgKCAQEAtt+2QxUevbolYLp51QGcUpageI4fwGLIqv4fj4aoVnHFOOBqVOVpfCLRp26LFV/F8ebwPyo8YEBK\n" +
"SwXzMD1573rMSbaH9BalscH5lZYAbetXoio6YRvzlcmcrVvLBNMeVnxY86xHpo0\n" +
"MNTyP7W024rZsxWO98xFQVdoiaBC+7+midlisx2Y+7u0jzT9GjeUP6JLdLFUZJKUP\n" +
"STK3jVzw9v1eZQZKYoNfU6vFMd6ndtwW6qEnwpzmmX/UT+p5ThAMH593zszlz\n" +
"330nTSXBjIsGkyvOz9gSB0Z0LAuJj06XUNhGL5xKJYKbdI38MFQFJKvRHfgTAvVsvAv\n" +
"pBUM2DuBKwIDAQABo28wbTAMBgNVHRMEBTADAQH/MB0GA1UdDgQWBBQ3\n" +
"7B0zGcKAOnmgxZQVMg6ZGvrGLTALBgNVHQ8EBAMCAQYwEQYJYIZIAYb4QgEBBAQDAg\n" +
"AHMB4GCGCGSAGG+EIBDQQRFg94Y2EgY2VydGlmaWNhdGUwBQYDK2VwA0EAuasLBe\n" +
"55YgvFb4wmHeohylc9r8cFGS1LNQ5UcSn3cGqMYf6ehnef16NLuCW6upHCs8Sui4iAMvs\n" +
"uKPWR9dKBA==\n" +
"-----END CERTIFICATE-----\n";let textEncoder = new util.TextEncoder();
let encodingBlob: cert.EncodingBlob = {data: textEncoder.encodeInto(deviceCertChainData),encodingFormat: cert.EncodingFormat.FORMAT_PEM
};let x509CertChain: cert.X509CertChain = {} as cert.X509CertChain;try {x509CertChain = await cert.createX509CertChain(encodingBlob);
} catch (err) {let e: BusinessError = err as BusinessError;console.error(`createX509CertChain failed, errCode: ${e.code}, errMsg: ${e.message}`);
}// 证书链校验数据(假设,实际需根据真实情况配置)
const param: cert.CertChainValidationParameters = {date: '20231212080000Z',trustAnchors: [{CAPubKey: new Uint8Array([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70,0x03, 0x21, 0x00, 0xbb, 0x16, 0x9d, 0x8f, 0x5c, 0x30, 0xd0, 0xba, 0x8f, 0x37, 0x6e,0x33, 0xaf, 0x6f, 0x23, 0x71, 0x23, 0xa5, 0x49, 0x60, 0x1e, 0xd1, 0x07, 0x4b, 0xc9,0x11, 0x7e, 0x66, 0x01, 0xba, 0x92, 0x52]),CASubject: new Uint8Array([0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,0x04, 0x06, 0x13, 0x02, 0x45, 0x4e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04,0x08, 0x13, 0x07, 0x45, 0x6e, 0x67, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d,0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x06, 0x4c, 0x6f, 0x6e, 0x64, 0x6f, 0x6e, 0x31,0x0c, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31,0x0c, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x03, 0x74, 0x73, 0x31, 0x31,0x0c, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x03, 0x74, 0x73, 0x31])}]
};try {const validationRes = await x509CertChain.validate(param);if (validationRes) {console.log('X509CertChain validate success');} catch (err) {console.error('X509CertChain validate failed');}
  • 接着,服务端验证签名的有效性。从证书中获取公钥,使用公钥对签名进行验证,确保请求数据在传输过程中未被篡改。示例代码如下:
import { cert } from '@kit.DeviceCertificateKit';
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';// 假设这是从请求中获取的签名和挑战值(或其哈希值)以及业务请求数据
let signature = new Uint8Array([...]); 
let challengeReceived = new Uint8Array([...]); 
let activityRequestDataReceived = '...'; // 从证书链中获取证书(假设已经验证证书链有效)
let deviceCert = x509CertChain.getCertList()[0];// 获取证书中的公钥
let pubKey = deviceCert.getPublicKey();// 重新组合数据(根据之前签名时的组合方式)
let combinedDataForVerification = new Uint8Array([...challengeReceived,...new Uint8Array(textEncoder.encodeInto(activityRequestDataReceived).data)]);try {let result = await cryptoFramework.verify({key: pubKey,data: combinedDataForVerification,signature: signature});if (result) {console.log('Signature verification succeeded.');} else {console.log('Signature verification failed.');}
} catch (error) {let e: BusinessError = error as BusinessError;console.error(`verifySignature failed, errCode: ${e.code}, errMsg:${e.message}`);
}
  • 最后,服务端验证挑战值的一致性。将收到的挑战值与之前生成并发送给应用端的挑战值进行比对,如果一致,则说明请求是新鲜的,不是重放攻击。如果挑战值不一致或者签名验证失败,服务端则拒绝该活动请求。

四、技术亮点

  1. 如何在业务请求中利用挑战值和签名机制防止攻击
    • 挑战值的随机性和一次性:每次生成的挑战值都是随机且唯一的,这使得攻击者难以预测和伪造。即使攻击者获取了之前的请求信息,由于挑战值的变化,他们无法使用旧的签名来通过验证。例如,在限时抢购活动中,不同用户在不同时间的请求将具有不同的挑战值,有效防止了批量自动化脚本的攻击。
    • 签名确保数据完整性:应用端使用私钥对包含挑战值和业务请求数据的组合进行签名,服务端通过公钥验证签名。这确保了数据在传输过程中未被篡改,因为任何对数据的修改都会导致签名验证失败。例如,在抽奖活动中,防止攻击者修改抽奖请求中的用户信息或抽奖次数等关键数据。
  2. 活动场景中防重放、伪造请求的实现与设计
    • 防重放攻击:通过挑战值的一次性和时间戳(可以与挑战值一起包含在请求中)的结合,服务端可以轻松识别重放攻击。如果收到的挑战值在之前已经被使用过,或者请求的时间戳与当前时间相差过大(超出合理范围),则认为是重放攻击并拒绝请求。例如,在一些需要频繁交互的营销活动中,如签到领积分活动,防止攻击者利用截获的合法请求进行多次签到。
    • 防止伪造请求:证书链验证和签名机制共同防止了伪造请求。只有拥有合法证书的应用才能生成有效的签名,而证书链的验证确保了证书的真实性和有效性。即使攻击者试图伪造请求,由于无法获取正确的私钥进行签名,且无法伪造有效的证书链,其伪造的请求在服务端验证时必然会失败。

五、示例代码

  1. 生成和验证挑战值核心代码
    • 服务端生成挑战值(上述已部分展示,补充完整错误处理):
import { cryptoFramework } from '@kit.CryptoArchitectureKit';async function generateChallenge() {try {let randomData = await cryptoFramework.generateRandomBytes(16); return randomData.data;} catch (error) {console.error('Failed to generate challenge:', error);}
}let challenge = await generateChallenge();
console.log('Generated challenge:', challenge);
  • 服务端验证挑战值(假设在收到请求时进行验证):
function verifyChallenge(challengeReceived: Uint8Array, originalChallenge: Uint8Array) {if (challengeReceived.every((value, index) => value === originalChallenge[index])) {console.log('Challenge verification succeeded.');return true;} else {console.log('Challenge verification failed.');return false;}
}
  1. 签名和证书链校验核心代码
    • 应用端签名生成(上述已部分展示,补充完整数据处理和错误处理):
import { huks } from '@kit.UniversalKeystoreKit';
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { util } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';let keyAlias = 'your_app_key_alias'; async function getPrivateKey() {let options: huks.HuksOptions = {properties: []}try {let key = await huks.getKeyItem(keyAlias, options);return key;} catch (err) {let e: BusinessError = err as BusinessError;console.error(`getPrivateKey failed, error: ` + err.message);}
}let privateKey = await getPrivateKey();let combinedData = new Uint8Array([...challenge,...new Uint8Array(textEncoder.encodeInto(activityRequestData).data)]);async function signRequestData(data: Uint8Array) {try {let signature = await cryptoFramework.sign({key: privateKey,data: data});console.log('Signature:', signature.data);return signature;} catch (error) {let e: BusinessError = error as BusinessError;console.error(`signRequestData failed, errCode: ${e.code}, errMsg:${e.message}`);}
}let signature = await signRequestData(combinedData);
  • 服务端签名验证和证书链校验(上述已部分展示,优化错误处理和流程整合):
import { cert } from '@kit.DeviceCertificateKit';
import { cryptoFramework } from '@kit.CryptoArchitectureKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { util } from '@kit.ArkTS';// 假设这是从请求中获取的签名和挑战值(或其哈希值)以及业务请求数据
let signature = new Uint8Array([...]); 
let challengeReceived = new Uint8Array([...]); 
let activityRequestDataReceived = '...'; // 从证书链中获取证书(假设已经验证证书链有效)
let deviceCert = x509CertChain.getCertList()[0];// 获取证书中的公钥
let pubKey = deviceCert.getPublicKey();// 重新组合数据(根据之前签名时的组合方式)
let combinedDataForVerification = new Uint8Array([...challengeReceived,...new Uint8Array(textEncoder.encodeInto(activityRequestDataReceived).data)]);async function verifySignatureAndCertChain(signature: Uint8Array, combinedData: Uint8Array, deviceCert: cert.X509Cert) {try {// 验证签名let result = await cryptoFramework.verify({key: pubKey,data: combinedData,signature: signature});if (result) {console.log('Signature verification succeeded.');} else {console.log('Signature verification failed.');return false;}// 验证证书链(假设已经获取并验证了证书链,这里可以添加更详细的证书链验证逻辑)//...return true;} catch (error) {let e: BusinessError = error as BusinessError;console.error(`verifySignatureAndCertChain failed, errCode: ${e.code}, errMsg:${e.message}`);return false;}
}

六、总结

基于HarmonyOS Next的营销防薅羊毛活动防护架构主要通过Device Certificate Kit和随机数生成机制实现了对活动请求的多层验证。在整个流程中,从服务端生成挑战值开始,到应用端使用证书和私钥生成签名,再到服务端对请求进行挑战值、签名和证书链的全面验证,每个环节都紧密协作,确保了活动请求的真实性、完整性和合法性。通过这种方式,我们能够有效地防止黑灰产利用模拟器或非合法应用参与营销活动,保障企业营销活动的公平性和经济效益,为用户提供一个健康、有序的营销环境。在实际应用中,我们可以根据具体的营销活动需求和业务场景,进一步优化和扩展该防护策略,提高系统的安全性和性能。希望这篇文章能为从事电商和营销活动相关开发的人员提供有益的参考和帮助,让我们共同抵制薅羊毛行为,维护良好的商业生态。如果在实现过程中遇到问题,不要气馁,仔细分析,参考相关文档和示例代码,相信你一定能够找到解决方案。加油!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/832135.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docx 生成word报告

# -*- coding: utf-8 -*- import base64 import os from io import BytesIO from docx import Document from docx.shared import Inches, Pt from bs4 import BeautifulSoup from matplotlib import pyplot as plt from wordcloud import WordCloud # 设置全局字体 plt.rcPara…

leetcode算法题-有效的括号(简单)

有效的括号(简单) leetcode:https://leetcode.cn/problems/valid-parentheses/description/ 前言 防止脑袋生锈,做一下leetcode的简单算法题,难得也做不来哈哈。 大佬绕道,小白可看。 题目描述 给定一个只包括 (,),{,},[,] 的字符串 s ,判断字符串是否有效。 有效字符…

30+企业高管齐聚!医疗器械企业渠道优化与健康增长主题沙龙成功举办

10月29日,深圳医疗器械行业协会携手纷享销客,共同举办了一场以“渠道优化与健康增长”为主题,探索医疗器械企业在新形势下渠道管理及落地实践的沙龙活动。此次活动吸引了33位医疗器械企业的管理层,共同探寻医疗器械企业营销增长的新思路、新渠道与新路径。<活动照片>…

AutoCAD Blockview .net在wpf项目中的问题

之前使用Blockview是遇到平移的问题, 这几天在学习使用CommunityToolkit.MVVM框架来创建用户界面, 当创建GsPreviewCtrl控件时会遇到错误, 导致整个窗体不能显示, 错误信息如下:************** 异常文本 ************** System.InvalidProgramException: 公共语言运行时检…

html`` - function html(str) { return str+111 } 调用方式 - solidjs文档里面发现的

html`` - function html(str) { return str+111 } 调用方式 标签模板字符串Tagged Template Literals 这里是自己实现这个字符串模板,等于函数调用的另一种方式 html(111) html`111`solidjs文档里面发现的 https://www.solidjs.com/guides/getting-started#不使用构建工具----…

模态内重叠优化,简单有效的CLIP微调方法 | BMVC24 Oral

来源:晓飞的算法工程笔记 公众号,转载请注明出处论文: CLIP Adaptation by Intra-modal Overlap Reduction论文地址:https://arxiv.org/abs/2409.11338创新点提出一种基于轻量级适配的新方法,直接在图像空间中减少CLIP中的模态内重叠(IMO)。新特征与任何利用缓存模型的无…

一文了解:如何多纬度阐述数据安全传输问题,部署及解决方案!

企业的业务正常开展依赖安全有序的数据流转,数据传输环节融合在企业生产办公、日常经营、技术研究、战略发展等活动的方方面面。数据是任何企业的命脉,但企业数据在传输过程中仍然面临着监管机制不健全、传输主体涉及面广、网络环境复杂、攻击手段多样、数据泄露引发多米诺骨…

Ftrans文件自动化传输方案:释放双手,让数据流动更自由!

随着企业业务不断扩大发展,数据传输和汇集逐渐成为其业务链中的一个重要环节。在企业内部,一般会存在多台文件存储服务器,基于业务开展需要,存在将不同服务器上的不同数据文件自动化传输到同一台文件存储服务器上的场景需求。当下企业选择较多的文件自动化传输方式如FTP、R…

项目UML图

1.用例图2.类图3.时序图

【Unity】UGUI中ScrollView的设置

UI实现横向滚动展示元素,并可以点击指定项增加元素。UI实现横向滚动展示元素,并可以点击指定项增加元素。 成果展示Scene部分ScrollView的设置此案例取消了纵向的滚动条,可以直接删除对应的Scrollbar,然后取消勾选Scroll Rect中的Vertical属性; MovementType属性选择了Cla…

基于surging的木舟平台如何构建起微服务

一、概述木舟平台分为微服务平台和物联网平台, 上面几篇都是介绍如何通过网络组件接入设备,那么此篇文章就细致介绍下在木舟平台下如何构建微服务。木舟 (Kayak) 是什么?木舟(Kayak)是基于.NET6.0软件环境下的surging微服务引擎进行开发的, 平台包含了微服务和物联网平台。支…

PYCHARM-pycharm工具激活

通过百度网盘分享的文件:pojie.zip 链接:https://pan.baidu.com/s/1FGafhVPF4TwL83u9_3sPUw 提取码:sky1