当前位置: 首页 > 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 "银行行长正在走行。"…...

微信小程序之bind和catch

这两个呢&#xff0c;都是绑定事件用的&#xff0c;具体使用有些小区别。 官方文档&#xff1a; 事件冒泡处理不同 bind&#xff1a;绑定的事件会向上冒泡&#xff0c;即触发当前组件的事件后&#xff0c;还会继续触发父组件的相同事件。例如&#xff0c;有一个子视图绑定了b…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

椭圆曲线密码学(ECC)

一、ECC算法概述 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography&#xff09;是基于椭圆曲线数学理论的公钥密码系统&#xff0c;由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA&#xff0c;ECC在相同安全强度下密钥更短&#xff08;256位ECC ≈ 3072位RSA…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

微信小程序 - 手机震动

一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注&#xff1a;文档 https://developers.weixin.qq…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

vue3+vite项目中使用.env文件环境变量方法

vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量&#xff0c;这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中&#xff0c;新增了一个本地验证码接口 /code&#xff0c;使用函数式路由&#xff08;RouterFunction&#xff09;和 Hutool 的 Circle…...

基于 TAPD 进行项目管理

起因 自己写了个小工具&#xff0c;仓库用的Github。之前在用markdown进行需求管理&#xff0c;现在随着功能的增加&#xff0c;感觉有点难以管理了&#xff0c;所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD&#xff0c;需要提供一个企业名新建一个项目&#…...