当前位置: 首页 > news >正文

Unity客户端接入原生Google支付

Unity客户端接入原生Google支付

  • 1. Google后台配置
  • 2. 开始接入
    • Java部分
    • C#部分
    • Lua部分
  • 3. 导出工程打包测试
  • 参考
  • 踩坑注意

1. Google后台配置

  1. 找到内部测试(这个测试轨道过审最快),打包上传,这个包不需要接入支付,如果已经有上传过包了那就跳过这一步
    在这里插入图片描述

  2. 在许可测试里添加测试人员,勾选测试人员列表,并且设置许可相应为LICENSED,这样才可以使用测试卡测试支付
    在这里插入图片描述

  3. 确认已经添加了付款方式,以及开放地区有香港,否则可能需要挂VPN才能进行支付流程测试
    在这里插入图片描述
    在这里插入图片描述

  4. 流程图
    在这里插入图片描述

2. 开始接入

确保Unity Plugins/Android里有com.android.billingclient.billing,并且是v3版本以上,这里用的5.0.0版本
在这里插入图片描述

Java部分

java文件,也放到Plugins/Android下,开头package需要根据项目而定
GoogleBillingManager.java

package com.xxx.xxx;import android.app.Activity;
import android.content.Context;
import android.util.Log;import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.unity3d.player.UnityPlayer;import java.util.List;public class GoogleBillingManager {private static GoogleBillingManager instance;private static BillingClient billingClient;private static GoogleBillingListener billingListener;public static boolean isConnection = false;private GoogleBillingManager() {instance = this;createClient(UnityPlayer.currentActivity);}public static GoogleBillingManager getInstance() {if (instance == null) {synchronized (GoogleBillingManager.class) {if (instance == null) {instance = new GoogleBillingManager();}}}return instance;}/*** 创建支付客户端*/public static void createClient(Activity activity) {if (isReady()) {return;}if (null == activity) {Log.e("TAG","谷歌支付CreateClient, activity = null");return;}billingClient = BillingClient.newBuilder(activity).enablePendingPurchases().setListener(new PurchasesUpdatedListener() {@Overridepublic void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {if (null != billingListener) {billingListener.onPurchasesUpdated(billingResult, purchases);}}}).build();//启动支付连接startConn();}public BillingClient getBillingClient() {return billingClient;}/*** 添加监听事件*/public void setBillingListener(GoogleBillingListener listener) {billingListener = listener;}/*** 是否准备好了** @return*/public static boolean isReady() {return !(null == billingClient || !billingClient.isReady());}/*** 启动连接*/private static void startConn() {if (isReady()) {return;}billingClient.startConnection(new BillingClientStateListener() {@Overridepublic void onBillingSetupFinished(BillingResult billingResult) {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {isConnection = true;Log.e("TAG", "连接成功,可以开始操作了~~~");}}@Overridepublic void onBillingServiceDisconnected() {isConnection = false;//连接失败。 可以尝试调用 startConnection 重新建立连接Log.e("TAG", "连接失败");}});}/*** 结束连接*/public void endConn() {if (null != billingClient) {billingClient.endConnection();isConnection = false;}}
}

GoogleBillHelper.java

package com.xxx.xxx;import android.app.Activity;
import android.util.Log;import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.QueryProductDetailsParams;
import com.android.billingclient.api.QueryPurchasesParams;
import com.unity3d.player.UnityPlayer;import java.util.ArrayList;
import java.util.List;import io.reactivex.annotations.NonNull;/*** Desc:支付的具体操作* 1.查询* 2.购买* 3.消费*/
public class GoogleBillHelper {public static final String TAG = GoogleBillHelper.class.getSimpleName();/*** 查询商品详情** @param billingListener : 接口监听* @param productIds      :商品id 。对应Google 后台的* @param productType     :取值*                        BillingClient.ProductType.INAPP(一次性商品)*                        BillingClient.ProductType.SUBS(订阅)*/public static void onQuerySkuDetailsAsync(GoogleBillingListener billingListener, String productType, String productIds, String orderId) {if (null == productIds){return;}String[] productList = productIds.split(",");Log.e("TAG", "onQuerySkuDetailsAsync: " + productIds + "   ----->" + productList[0]);if (productList.length == 0 || !GoogleBillingManager.getInstance().isReady()) {Log.e("TAG", "productList.length:" + productList.length + ",client:" + GoogleBillingManager.getInstance().isReady());return;}List<QueryProductDetailsParams.Product> skuList = new ArrayList<>();for (String productId : productList) {QueryProductDetailsParams.Product product = QueryProductDetailsParams.Product.newBuilder().setProductId(productId).setProductType(productType).build();//添加对应的 产品id 去查询详情skuList.add(product);}QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder().setProductList(skuList).build();GoogleBillingManager.getInstance().getBillingClient().queryProductDetailsAsync(params, (billingResult, list) -> {if (null != billingListener) {billingListener.onProductDetailsSus(billingResult, list, orderId);}});}/*** 打开支付面板** @param billingListener* @param activity* @param details*/public static void onOpenGooglePlay(GoogleBillingListener billingListener, Activity activity, ProductDetails details, String orderId) {if (null == details) {return;}List<BillingFlowParams.ProductDetailsParams> params = new ArrayList<>();//添加购买数据BillingFlowParams.ProductDetailsParams productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder().setProductDetails(details).build();params.add(productDetailsParams);BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(params).setObfuscatedAccountId(orderId).build();//添加购买监听GoogleBillingManager.getInstance().setBillingListener(billingListener);//响应code 码GoogleBillingManager.getInstance().getBillingClient().launchBillingFlow(activity, billingFlowParams).getResponseCode();}/*** 消费商品* 对于购买类型的商品需要手动调用一次消费方法 (目的:用户可以再次购买此商品)** @param billingListener* @param purchase*/public static void onConsumeAsync(GoogleBillingListener billingListener, Purchase purchase) {if (!GoogleBillingManager.getInstance().isReady()) {return;}ConsumeParams consumeParams =ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();ConsumeResponseListener listener = (billingResult, purchaseToken) -> {if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {String result = "消费code : " + billingResult.getResponseCode() + " message : " + billingResult.getDebugMessage();if (null == billingListener) {Log.e(TAG, result);}billingListener.onConsumeSus(billingResult.getResponseCode(), result, purchaseToken, purchase);}};GoogleBillingManager.getInstance().getBillingClient().consumeAsync(consumeParams, listener);}/*** 检查补单** @param billingListener* @param productType*/public static void queryPurchases(String productType, GoogleBillingListener billingListener){PurchasesResponseListener mPurchasesResponseListener = new PurchasesResponseListener() {@Overridepublic void onQueryPurchasesResponse(@NonNull BillingResult billingResult, @NonNull List<Purchase> purchasesResult) {if(billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK || purchasesResult == null){return;}for (Purchase purchase : purchasesResult) {if(purchase == null || purchase.getPurchaseState() != Purchase.PurchaseState.PURCHASED){continue;}billingListener.onQueryPurchases(purchase.getAccountIdentifiers().getObfuscatedAccountId());onConsumeAsync(billingListener, purchase);
//                    这里处理已经支付过的订单,通知服务器去验证}}};QueryPurchasesParams params =QueryPurchasesParams.newBuilder().setProductType(productType).build();GoogleBillingManager.getInstance().getBillingClient().queryPurchasesAsync(params, mPurchasesResponseListener);}
}

GoogleBillingListener.java

package com.xxx.xxx;import android.util.Log;import com.android.billingclient.api.AccountIdentifiers;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.unity3d.player.UnityPlayer;import java.util.List;public class GoogleBillingListener implements PurchasesUpdatedListener {public final String objectName;public final String paySuccessMethodName;public final String detailsSusMethodName;public final String payFailMethodName;public final String payEndMethodName;public final String queryPurchasesMethodName;public final String detailsFailMethodName;public ProductDetails.OneTimePurchaseOfferDetails productDetails;public GoogleBillingListener(String objectName, String successMethodName, String processingMethodName,String failMethodName, String payEndMethodName, String queryPurchasesMethodName, String detailsFailMethodName) {this.objectName = objectName;this.paySuccessMethodName = successMethodName;this.detailsSusMethodName = processingMethodName;this.payFailMethodName = failMethodName;this.payEndMethodName = payEndMethodName;this.queryPurchasesMethodName = queryPurchasesMethodName;this.detailsFailMethodName = detailsFailMethodName;}/*** 购买监听** @param result* @param purchases*/@Overridepublic void onPurchasesUpdated(BillingResult result, List<Purchase> purchases) {Log.e("TAG", result.toString());if (null == purchases || purchases.size() == 0) {Log.e("TAG", "not purchases");UnityPlayer.UnitySendMessage(this.objectName, this.payEndMethodName, "not purchases;BillingResult:" + result.toString());return;}for (Purchase purchase : purchases) {AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();String resultStr = accountIdentifiers.getObfuscatedAccountId() + "," + purchase.getPurchaseToken() + "," + purchase.getPurchaseState();UnityPlayer.UnitySendMessage(this.objectName, this.payEndMethodName, resultStr);if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED){GoogleBillHelper.onConsumeAsync(this, purchase);}}}/*** 查询商品详情成功** @param list*/public void onProductDetailsSus(BillingResult result, List<ProductDetails> list, String orderId) {if (result.getResponseCode() != BillingClient.BillingResponseCode.OK){String msg = "Get Details Fails, code:" + result.getResponseCode() + ",msg:" + result.getDebugMessage();UnityPlayer.UnitySendMessage(this.objectName, this.detailsFailMethodName, msg);return;}if (null == list || list.size() <= 0) {Log.e("TAG", "没有查询到相关产品~~~~");UnityPlayer.UnitySendMessage(this.objectName, this.detailsFailMethodName, "Not Search Product, Please check ProductID!");return;}if (orderId != null && orderId.length() > 0){GoogleBillHelper.onOpenGooglePlay(this, UnityPlayer.currentActivity, list.get(0), orderId);productDetails = list.get(0).getOneTimePurchaseOfferDetails();}String infoList = "";for (ProductDetails details: list) {ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails = details.getOneTimePurchaseOfferDetails();//注意:如果手机语言是法语,获取的商品价格是以 , 作为分隔符String info = details.getProductId() + "|-|" + oneTimePurchaseOfferDetails.getFormattedPrice() + "|-|" +oneTimePurchaseOfferDetails.getPriceCurrencyCode() + "|-|" + oneTimePurchaseOfferDetails.getPriceAmountMicros();if (infoList.isEmpty()){infoList = info;}else{infoList = infoList + ";" + info;}}UnityPlayer.UnitySendMessage(this.objectName, this.detailsSusMethodName, infoList);}/*** 商品消费成功** @param code* @param purchaseToken*/public void onConsumeSus(int code, String result, String purchaseToken, Purchase purchase) {AccountIdentifiers accountIdentifiers = purchase.getAccountIdentifiers();String itemId = purchase.getProducts().get(0);String msg = code + "," + result + "," + purchaseToken + "," + accountIdentifiers.getObfuscatedAccountId() + "," + itemId;if (productDetails != null){msg = msg + "," + productDetails.getPriceCurrencyCode() + "," + productDetails.getPriceAmountMicros();}if (code == BillingClient.BillingResponseCode.OK) {UnityPlayer.UnitySendMessage(this.objectName, this.paySuccessMethodName, msg);}else{UnityPlayer.UnitySendMessage(this.objectName, this.payFailMethodName, msg);}}public void onQueryPurchases(String txnid){UnityPlayer.UnitySendMessage(this.objectName, this.queryPurchasesMethodName, txnid);}
}

C#部分

IAPMangaer.cs
namespace根据自己项目决定要不要写

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;namespace xxx.Sdk
{public enum BillingResponseCode{SERVICE_TIMEOUT = -3,FEATURE_NOT_SUPPORTED = -2,SERVICE_DISCONNECTED = -1,OK = 0,USER_CANCELED = 1,SERVICE_UNAVAILABLE = 2,BILLING_UNAVAILABLE = 3,ITEM_UNAVAILABLE = 4,DEVELOPER_ERROR = 5,ERROR = 6,ITEM_ALREADY_OWNED = 7,ITEM_NOT_OWNED = 8,}public class IAPManager{private bool initialize;
#if UNITY_ANDROIDprivate AndroidJavaClass billingManager;private AndroidJavaClass billingHelper;
#endifpublic event Action<bool, string> OnPayEndResult;public event Action<bool, string> OnPayResult;public event Action<bool, string> OnDetailsSus;public event Action<bool, string> OnQueryPurchasesResult;public void Initialize(){if (initialize){return;}#if UNITY_ANDROIDif (billingManager == null){billingManager = new AndroidJavaClass("com.dorocat.bombman.GoogleBillingManager");}if (billingHelper == null){billingHelper = new AndroidJavaClass("com.dorocat.bombman.GoogleBillHelper");}if (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{billingManager.CallStatic("createClient", SdkMgr.currentActivity);}));
#endifinitialize = true;}public void StartConnection(){
#if UNITY_ANDROIDif (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{billingManager.CallStatic("startConn");}));
#endif}public void endConnection(){
#if UNITY_ANDROIDif (billingManager != null){if (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{billingManager.CallStatic("endConn");}));}
#endif}public void pay(string itemId, string productType, string orderId){
#if UNITY_ANDROIDif (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",SdkMgr.GameObjectName,"OnPaySuccess","OnProductDetailsSus","OnPayFail","OnPayEnd","OnQueryPurchases","OnProductDetailsSusFail");billingHelper.CallStatic("onQuerySkuDetailsAsync", listener, productType, itemId, orderId);}));
#endif}public void getProductsDetail(string itemId, string productType){
#if UNITY_ANDROIDif (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",SdkMgr.GameObjectName,"OnPaySuccess","OnProductDetailsSus","OnPayFail","OnPayEnd","OnQueryPurchases","OnProductDetailsSusFail");billingHelper.CallStatic("onQuerySkuDetailsAsync", listener, productType, itemId, "");}));
#endif}public void queryPurchases(string productType){
#if UNITY_ANDROIDif (SdkMgr.currentActivity == null) return;SdkMgr.currentActivity.Call("runOnUiThread", new AndroidJavaRunnable(() =>{var listener = new AndroidJavaObject("com.dorocat.bombman.GoogleBillingListener",SdkMgr.GameObjectName,"OnPaySuccess","OnProductDetailsSus","OnPayFail","OnPayEnd","OnQueryPurchases","OnProductDetailsSusFail");billingHelper.CallStatic("queryPurchases", productType, listener);}));
#endif}public void onPaySuccess(string msg){OnPayResult?.Invoke(true, msg);}public void onPayFail(string msg){OnPayResult?.Invoke(false, msg);}public void onProductDetailsSus(string msg){OnDetailsSus?.Invoke(true, msg);}public void onPayEnd(string msg){OnPayEndResult?.Invoke(true, msg);}public void onQueryPurchases(string msg){OnQueryPurchasesResult?.Invoke(true, msg);}public void onDeatilSusFail(string msg){OnDetailsSus?.Invoke(false, msg);}public bool getConnectionState(){
#if UNITY_ANDROIDreturn billingManager.GetStatic<bool>("isConnection");
#elsereturn false;
#endif}}
}

自行定义一个SdkManager.cs,在这里面初始化,包括在java层定义的回调函数名也要在这里实现

public static IAPManager billingManager = null;
public static IAPManager CreateBillingClient()
{billingManager = new IAPManager();billingManager.Initialize();return billingManager;
}public void OnPaySuccess(string result)
{if (billingManager != null){billingManager.onPaySuccess(result);}else{current?.auth.OnPaySuccess(result);}
}public void OnPayFail(string message)
{if (billingManager != null){billingManager.onPayFail(message);}else{current?.auth.OnPayFail(message);}
}public void OnPayEnd(string result)
{if (billingManager != null){billingManager.onPayEnd(result);}
}public void OnProductDetailsSus(string result)
{if (billingManager != null){billingManager.onProductDetailsSus(result);}
}public void OnProductDetailsSusFail(string result)
{if (billingManager != null){billingManager.onDeatilSusFail(result);}
}public void OnQueryPurchases(string result)
{if (billingManager != null){billingManager.onQueryPurchases(result);}
}

Lua部分

初始化支付SDK

---@type xxx.Sdk.IAPManager
App.billingSdk = CS.BombMan.Sdk.SdkMgr.CreateBillingClient()

调用支付

local billingProductType =
{INAPP = "inapp",SUBS = "subs",
}---sdk支付---
function ShopMgr:pay(itemId, payCallBack, addInfo)if LuaClass.Application.isMobilePlatform thenlocal callbackcallback = function(result,str)App.billingSdk:OnPayResult("-", callback)print("onPayResult",result,str)if payCallBack thenpayCallBack(result,str)endendif not App.billingSdk:getConnectionState() then--如果没有连上Google支付服务器,开始连接App.billingSdk:StartConnection()returnendlocal payEndpayEnd = function(result, msg)App.billingSdk:OnPayEndResult("-", payEnd)print("payEnd", msg)self.starPay = falselocal infoList = string.split(msg, ",")self:requestPayEnd(infoList[1], infoList[2], tonumber(infoList[3]), self.priceStrGoogle and self.priceStrGoogle[itemId][2] or nil, self.priceStrGoogle and tonumber(self.priceStrGoogle[itemId][3]) or nil, infoList[4], itemId, payCallBack)endlocal detailFaildetailFail = function(result, msg)print("detailFail", result, msg)if not result thenApp.billingSdk:OnPayEndResult("-", payEnd)endself.starPay = falseApp.billingSdk:OnDetailsSus("-", detailFail)endself:requestPayStart(itemId, addInfo, function ()App.billingSdk:OnPayEndResult("+", payEnd)App.billingSdk:OnDetailsSus("+", detailFail)App.billingSdk:pay(itemId, billingProductType.INAPP, StringUtil.obfuscate(App.playerMgr.data.id, "pay"))self.starPay = falseend)end
end

检查补单

function ShopMgr:queryPurchases()if not self.isQuery thenlocal addCallBack = function(result, str)print("onQueryPurchases", str)local infoList = string.split(str, ",")local price = self.priceStrGoogle and infoList[5] and self.priceStrGoogle[infoList[5]]self:requestPayEnd(infoList[1], infoList[3], tonumber(infoList[4]),price and price[2] or nil, price and tonumber(price[3]) or nil, infoList[2], infoList[5])endApp.billingSdk:OnQueryPurchasesResult("+", addCallBack)endApp.billingSdk:queryPurchases(billingProductType.INAPP)self.isQuery = true
end

获取谷歌商店内价格

function ShopMgr:getProductsByGoogle()if LuaClass.Application.platform == LuaClass.RuntimePlatform.IPhonePlayer ornot isValid(App.billingSdk) thenreturnendif self.priceStrGoogle == nil thenself.priceStrGoogle = {}local templates = LuaClass.DirectpurchaseDatatable:getAll()local idstr = ""for i = 1,#templates doidstr = idstr..templates[i].ID..","endif idstr thenlocal callbackcallback = function(result,str)print("getProductsByGoogle:",result,str)App.billingSdk:OnDetailsSus("-", callback)if result thenlocal strSP = string.split(str,";")for i = 1, #strSP dolocal productInfo = string.split(strSP[i], "|-|")self.priceStrGoogle[productInfo[1]] = {--格式化后的价格 如:HK$8.00[1] = productInfo[2],--货币代码,如HKD[2] = productInfo[3],--微单位价格,1,000,000 微单位等于 1 货币单位[3] = productInfo[4],}endprint("productInfo", self.priceStrGoogle)self:queryPurchases()endendApp.billingSdk:OnDetailsSus("+", callback)App.billingSdk:pay(idstr, billingProductType.INAPP, "")endend
end

3. 导出工程打包测试

注意要导apk,并且要带有调试标签(直连手机Build即可),包名和版本号要和Google Play后台上传的包一致,确保测试机只登陆了一个谷歌测试账号
在这里插入图片描述

参考

https://blog.51cto.com/kenkao/5989952
https://www.cnblogs.com/fnlingnzb-learner/p/16385685.html

踩坑注意

1.手机语言是法语的话价格会用逗号代替小数点,注意自己使用的分隔符,例如 $1234,56
2.关闭订单这一步操作最好由后端处理,以防客户端因为网络等原因关闭订单后无法通知后端发货
3.在拉起支付时如果需要设置ObfuscatedAccountId的话,请确保每次传输的值都是一样的,否则会出现用户支付遭拒的情况
在这里插入图片描述

相关文章:

Unity客户端接入原生Google支付

Unity客户端接入原生Google支付 1. Google后台配置2. 开始接入Java部分C#部分Lua部分 3. 导出工程打包测试参考踩坑注意 1. Google后台配置 找到内部测试&#xff08;这个测试轨道过审最快&#xff09;&#xff0c;打包上传&#xff0c;这个包不需要接入支付&#xff0c;如果已…...

Spring Cloud之五大组件

Spring Cloud 是一系列框架的有序集合&#xff0c;为开发者提供了快速构建分布式系统的工具。这些组件可以帮助开发者做服务发现&#xff0c;配置管理&#xff0c;负载均衡&#xff0c;断路器&#xff0c;智能路由&#xff0c;微代理&#xff0c;控制总线等。以下是 Spring Cl…...

在 CentOS 7 上安装 Docker 并安装和部署 .NET Core 3.1

1. 安装 Docker 步骤 1.1&#xff1a;更新包索引并安装依赖包 先安装yum的扩展&#xff0c;yum-utils提供了一些额外的工具&#xff0c;这些工具可以执行比基本yum命令更复杂的任务 sudo yum install -y yum-utils sudo yum update -y #更新系统上已安装的所有软件包到最新…...

redis的学习(一):下载安装启动连接

简介 redis的下载&#xff0c;安装&#xff0c;启动&#xff0c;连接使用 nosql nosql&#xff0c;即非关系型数据库&#xff0c;和传统的关系型数据库的对比&#xff1a; sqlnosql数据结构结构化非结构化数据关联关联的非关联的查询方式sql查询非sql查询事务特性acidbase存…...

前端设计模式面试题汇总

面试题 1. 简述对网站重构的理解&#xff1f; 参考回答&#xff1a; 网站重构&#xff1a;在不改变外部行为的前提下&#xff0c;简化结构、添加可读性&#xff0c;而在网站前端保持一致的行为。也就是说是在不改变UI的情况下&#xff0c;对网站进行优化&#xff0c; 在扩展的…...

linux(CentOS、Ubuntu)安装python3.12.2环境

1.下载官网Python安装包 wget https://www.python.org/ftp/python/3.12.2/Python-3.12.2.tar.xz 1.1解压 tar -xf Python-3.12.2.tar.xz 解压完后切换到Python-3.12.2文件夹(这里根据自己解压的文件夹路径) cd /usr/packages/Python-3.12.2/ 1.2升级软件包管理器 CentOS系…...

CSS 中border-radius 属性

border-radius 属性在 CSS 中用于创建圆角边框。它可以接受一到四个值&#xff0c;这些值可以是长度值&#xff08;如像素 px、em 等&#xff09;或百分比&#xff08;%&#xff09;。当提供四个值时&#xff0c;它们分别对应于边框的左上角、右上角、右下角和左下角的圆角半径…...

【大数据专题】数据仓库

1. 简述数据仓库架构 &#xff1f; 数据仓库的核心功能从源系统抽取数据&#xff0c;通过清洗、转换、标准化&#xff0c;将数据加载到BI平台&#xff0c;进而满足业 务用户的数据分析和决策支持。 数据仓库架构包含三个部分&#xff1a;数据架构、应用程序架构、底层设施 1&…...

go关于string与[]byte再学深一点

目标&#xff1a;充分理解string与[]bytes零拷贝转换的实现 先回顾下string与[]byte的基本知识 1. string与[]byte的数据结构 reflect包中关于字符串的数据结构 // StringHeader is the runtime representation of a string.type StringHeader struct {Data uintptrLen int} …...

Qt 实战(7)元对象系统 | 7.4、属性系统:深度解析与应用

文章目录 一、属性系统&#xff1a;深度解析与应用1、定义属性2、属性系统的作用3、属性系统工作原理&#xff08;1&#xff09;Q_PROPERTY宏&#xff08;2&#xff09;moc 的作用&#xff08;3&#xff09;属性在元对象中的注册 4、获取与设置属性4.1、QObject::property()与Q…...

Docker核心技术:容器技术要解决哪些问题

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;容器技术要解决哪些问题&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题&#xff08;本文&#xff09;Docker的基本使用Docker是如何实…...

sklearn中的增量学习:特征提取的艺术

sklearn中的增量学习&#xff1a;特征提取的艺术 在机器学习领域&#xff0c;特征提取是构建有效模型的关键步骤。然而&#xff0c;并非所有数据集都适合一次性加载到内存中进行处理&#xff0c;尤其是在处理大规模数据集时。Scikit-learn&#xff08;sklearn&#xff09;提供…...

PostgreSQL 中如何处理数据的唯一性约束?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 PostgreSQL 中如何处理数据的唯一性约束&#xff1f;一、什么是唯一性约束二、为什么要设置唯一性约束…...

VAE论文阅读

在网上看到的VAE解释&#xff0c;发现有两种版本&#xff1a; 按照原来论文中的公式纯数学推导&#xff0c;一般都是了解生成问题的人写的&#xff0c;对小白很不友好。按照实操版本的&#xff0c;非常简单易懂&#xff0c;比如苏神的。但是却忽略了论文中的公式推导&#xff…...

【数据分享】2013-2022年我国省市县三级的逐月SO2数据(excel\shp格式\免费获取)

空气质量数据是在我们日常研究中经常使用的数据&#xff01;之前我们给大家分享了2000——2022年的省市县三级的逐月PM2.5数据和2013-2022年的省市县三级的逐月CO数据&#xff08;均可查看之前的文章获悉详情&#xff09;&#xff01; 本次我们分享的是我国2013——2022年的省…...

【Jmeter】记录一次Jmeter实战测试

Jmeter实战 1、需求2、实现2.1、新建线程组2.2、导入参数2.3、新建HTTP请求2.4、添加监听器2.5、结果 1、需求 查询某个接口在高并发场景下的响应时间(loadtime)&#xff0c;需求需要响应在50ms以内&#xff0c;接下来用Jmeter测试一下 Jmeter安装见文章《Jemeter安装教程&am…...

volatile,最轻量的同步机制

目录 一、volatile 二、如何使用&#xff1f; 三、volatile关键字能代替synchronized关键字吗&#xff1f; 四、总结&#xff1a; 还是老样子&#xff0c;先来看一段代码&#xff1a; 我们先由我们自己的常规思路分析一下代码&#xff1a;子线程中&#xff0c;一直循环&…...

在Linux、Windows和macOS上释放IP地址并重新获取新IP地址的方法

文章目录 LinuxWindowsmacOS 在Linux、Windows和macOS上释放IP地址并重新获取新IP地址的方法各有不同。以下是针对每种操作系统的详细步骤&#xff1a; Linux 使用DHCP客户端&#xff1a;大多数Linux发行版都使用DHCP&#xff08;动态主机配置协议&#xff09;来自动获取IP地址…...

Mamba-yolo|结合Mamba注意力机制的视觉检测

一、本文介绍 PDF地址&#xff1a;https://arxiv.org/pdf/2405.16605v1 代码地址&#xff1a;GitHub - LeapLabTHU/MLLA: Official repository of MLLA Demystify Mamba in Vision: A Linear AttentionPerspective一文中引入Baseline Mamba&#xff0c;指明Mamba在处理各种高…...

语音识别标记语言(SSML):自动标识中文多音字

好的&#xff0c;以下是完整的实现代码&#xff0c;包括导入库、分词、获取拼音和生成 SSML 标记的全过程&#xff1a; import thulac from pypinyin import pinyin, Style# 初始化 THULAC thu1 thulac.thulac(seg_onlyTrue)# 测试文本 text "银行行长正在走行。"…...

浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)

✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义&#xff08;Task Definition&…...

谷歌浏览器插件

项目中有时候会用到插件 sync-cookie-extension1.0.0&#xff1a;开发环境同步测试 cookie 至 localhost&#xff0c;便于本地请求服务携带 cookie 参考地址&#xff1a;https://juejin.cn/post/7139354571712757767 里面有源码下载下来&#xff0c;加在到扩展即可使用FeHelp…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

Qt/C++开发监控GB28181系统/取流协议/同时支持udp/tcp被动/tcp主动

一、前言说明 在2011版本的gb28181协议中&#xff0c;拉取视频流只要求udp方式&#xff0c;从2016开始要求新增支持tcp被动和tcp主动两种方式&#xff0c;udp理论上会丢包的&#xff0c;所以实际使用过程可能会出现画面花屏的情况&#xff0c;而tcp肯定不丢包&#xff0c;起码…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)

0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述&#xff0c;后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作&#xff0c;其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

【算法训练营Day07】字符串part1

文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接&#xff1a;344. 反转字符串 双指针法&#xff0c;两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

如何为服务器生成TLS证书

TLS&#xff08;Transport Layer Security&#xff09;证书是确保网络通信安全的重要手段&#xff0c;它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书&#xff0c;可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...