From 5ccacde1b74a748dcf63944083c4d16052212739 Mon Sep 17 00:00:00 2001 From: linquan <349589071@qq.com> Date: Thu, 8 Jan 2026 15:28:56 +0800 Subject: [PATCH] =?UTF-8?q?harmonyos,=20=E5=B9=BF=E5=91=8A=EF=BC=8C?= =?UTF-8?q?=E9=87=8D=E7=BD=AE=E5=85=85=E5=80=BC=E5=8F=8C=E5=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/game/basicinfo.go | 2 +- api/v1/game/order.go | 125 +++++++ internal/controller/game_order.go | 15 + internal/controller/game_server.go | 5 + internal/harmonyos/iap_server.go | 74 ++++ internal/harmonyos/jws_checker.go | 271 ++++++++++++++ internal/harmonyos/jwt_generator.go | 126 +++++++ internal/harmonyos/notification/appserver.go | 79 ++++ .../notification/notificationConstant.go | 46 +++ .../notification/notificationMetaData.go | 31 ++ .../notification/notificationPayload.go | 27 ++ internal/harmonyos/order_lookup.go | 107 ++++++ internal/harmonyos/order_service.go | 87 +++++ internal/harmonyos/subscription_service.go | 84 +++++ internal/service/harmonyOrder.go | 123 +++++++ internal/service/hmsAccessToken.go | 11 +- internal/serviceGame/advertisement.go | 4 +- .../serviceGame/internal/advertisement.go | 153 ++------ internal/serviceGame/order.go | 44 ++- manifest/config/IAPKey.p8 | 6 + manifest/config/RootCaG2Ecdsa.cer | 14 + test/build.bat | 5 + test/recharge.go | 126 +++++++ test/test.go | 343 ++++++++++++------ 24 files changed, 1662 insertions(+), 246 deletions(-) create mode 100644 internal/harmonyos/iap_server.go create mode 100644 internal/harmonyos/jws_checker.go create mode 100644 internal/harmonyos/jwt_generator.go create mode 100644 internal/harmonyos/notification/appserver.go create mode 100644 internal/harmonyos/notification/notificationConstant.go create mode 100644 internal/harmonyos/notification/notificationMetaData.go create mode 100644 internal/harmonyos/notification/notificationPayload.go create mode 100644 internal/harmonyos/order_lookup.go create mode 100644 internal/harmonyos/order_service.go create mode 100644 internal/harmonyos/subscription_service.go create mode 100644 internal/service/harmonyOrder.go create mode 100644 manifest/config/IAPKey.p8 create mode 100644 manifest/config/RootCaG2Ecdsa.cer create mode 100644 test/build.bat create mode 100644 test/recharge.go diff --git a/api/v1/game/basicinfo.go b/api/v1/game/basicinfo.go index 56bfdc0..7ddb19c 100644 --- a/api/v1/game/basicinfo.go +++ b/api/v1/game/basicinfo.go @@ -254,7 +254,7 @@ type GameChangeNameRes struct { type HarmonyChangeEventReq struct { g.Meta `path:"/harmonyChangeEvent" tags:"game" method:"post" summary:"鸿蒙账号改变事件"` - Account string `p:"account"` + Account string `p:"user_id"` Server int `p:"server"` Name string `p:"name"` } diff --git a/api/v1/game/order.go b/api/v1/game/order.go index f826694..06e4b64 100644 --- a/api/v1/game/order.go +++ b/api/v1/game/order.go @@ -252,6 +252,7 @@ type AddRechargeSignRes struct { type ResetRechargeSignReq struct { g.Meta `path:"/order/resetSign" tags:"订单" method:"get" summary:"重置充值情况"` Password string `p:"password"` + Server int32 `p:"serverId"` } type ResetRechargeSignRes struct { @@ -304,6 +305,7 @@ type HuaWeiOrderResponse struct { ContinuationToken string `json:"continuationToken"` OrderInfoList []HuaWeiOrderInfo `json:"orderInfoList"` } + type HuaWeiOrderInfo struct { OrderNo string `json:"orderNo"` RequestId string `json:"requestId"` @@ -331,3 +333,126 @@ type GetHuaWeiOrderListRes struct { g.Meta `mime:"application/json"` Order HuaWeiOrderResponse `json:"order"` } + +// 获取订单号 +type GetHarmonyOrderReq struct { + g.Meta `path:"/order/getHarmonyOrder" tags:"订单" method:"get" summary:"获取订单号"` + Order string `p:"purchaseOrderId"` +} + +type GetHarmonyOrderRes struct { + g.Meta `mime:"application/json"` + OrderStatus int32 `json:"orderStatus"` + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + PurchaseOrderInfo PurchaseOrderPayload `json:"purchaseOrderInfo"` +} + +type HarmonyLookup struct { + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + OrderStatus int32 `json:"orderStatus"` + JwsPurchaseOrder string `json:"jwsPurchaseOrder"` +} + +type PurchaseOrder struct { + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + JwsPurchaseOrder string `json:"jwsPurchaseOrder"` +} + +type DeveloperPayload struct { + UnitId int64 `json:"unitId"` + Server int `json:"server"` + Channel string `json:"channel"` + TradeNo string `json:"tradeNo"` +} + +type PurchaseOrderPayload struct { + Environment string `json:"environment"` + PurchaseOrderId string `json:"purchaseOrderId"` + PurchaseToken string `json:"purchaseToken"` + ApplicationId string `json:"applicationId"` + ProductId string `json:"productId"` + ProductType string `json:"productType"` + Quantity int64 `json:"quantity"` + PurchaseTime int64 `json:"purchaseTime"` + FinishStatus string `json:"finishStatus"` + NeedFinish bool `json:"needFinish"` + Price int64 `json:"price"` + Currency string `json:"currency"` + DeveloperPayload string `json:"developerPayload"` + PurchaseOrderRevocationReasonCode string `json:"purchaseOrderRevocationReasonCode"` + RevocationTime int64 `json:"revocationTime"` + OfferTypeCode string `json:"offerTypeCode"` + OfferId string `json:"offerId"` + CountryCode string `json:"countryCode"` + SignedTime int64 `json:"signedTime"` +} + +// 获取订单号 +type GetHarmonyOrdersReq struct { + g.Meta `path:"/order/getHarmonyOrders" tags:"订单" method:"get" summary:"获取订单号"` + StartTime int64 `p:"startTime"` + EndTime int64 `p:"endTime"` + ContinuationToken string `p:"continuationToken"` +} + +type GetHarmonyOrdersRes struct { + g.Meta `mime:"application/json"` + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + OrderInfoList []OrderInfo `json:"orderInfoList"` + ContinuationToken string `json:"continuationToken"` +} + +type HarmonyOrders struct { + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + OrderInfoList []OrderInfo `json:"orderInfoList"` + ContinuationToken string `json:"continuationToken"` +} + +type OrderInfo struct { + RequestId string `json:"requestId"` + Country string `json:"country"` + MerchantId string `json:"merchantId"` + ApplicationId string `json:"applicationId"` + OrderTime int64 `json:"orderTime"` + TradeTime int64 `json:"tradeTime"` + ProductId string `json:"productId"` + ProductName string `json:"productName"` + PayMoney string `json:"payMoney"` + CouponAmt string `json:"couponAmt"` + Currency string `json:"currency"` + TradeState int32 `json:"tradeState"` + TradeType string `json:"tradeType"` + RefundTime int64 `json:"refundTime"` + RevocationTime string `json:"refundMoney"` +} + +// 获取订单号 +type GetHarmonyUserOrdersReq struct { + g.Meta `path:"/order/getHarmonyUserOrders" tags:"订单" method:"get" summary:"获取订单号"` + PurchaseOrderId string `p:"purchaseOrderId"` + ProductType string `p:"productType"` + ContinuationToken string `p:"continuationToken"` + StartTime int64 `p:"startTime"` + EndTime int64 `p:"endTime"` + ProductIdList []string `p:"productIdList"` +} + +type GetHarmonyUserOrdersRes struct { + g.Meta `mime:"application/json"` + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + OrderInfoList []PurchaseOrderPayload `json:"orderInfoList"` + ContinuationToken string `json:"continuationToken"` +} + +type HarmonyUserOrders struct { + ResponseCode string `json:"responseCode"` + ResponseMessage string `json:"responseMessage"` + ContinuationToken string `json:"continuationToken"` + JwsPurchaseOrderList []string `json:"jwsPurchaseOrderList"` +} diff --git a/internal/controller/game_order.go b/internal/controller/game_order.go index 3e2803c..38764d4 100644 --- a/internal/controller/game_order.go +++ b/internal/controller/game_order.go @@ -100,3 +100,18 @@ func (c *orderController) GetHuaWeiOrderList(ctx context.Context, req *game.GetH res, err = serviceGame.GameOrder().GetHuaWeiOrderList(ctx, req) return } + +func (c *orderController) GetHarmonyOrder(ctx context.Context, req *game.GetHarmonyOrderReq) (res *game.GetHarmonyOrderRes, err error) { + res, err = serviceGame.GameOrder().GetHarmonyOrder(ctx, req) + return +} + +func (c *orderController) GetHarmonyOrders(ctx context.Context, req *game.GetHarmonyOrdersReq) (res *game.GetHarmonyOrdersRes, err error) { + res, err = serviceGame.GameOrder().GetHarmonyOrders(ctx, req) + return +} + +func (c *orderController) GetHarmonyUserOrders(ctx context.Context, req *game.GetHarmonyUserOrdersReq) (res *game.GetHarmonyUserOrdersRes, err error) { + res, err = serviceGame.GameOrder().GetHarmonyUserOrders(ctx, req) + return +} diff --git a/internal/controller/game_server.go b/internal/controller/game_server.go index defacb5..e738197 100644 --- a/internal/controller/game_server.go +++ b/internal/controller/game_server.go @@ -157,3 +157,8 @@ func (c *serverController) HarmonyChangeEvent(ctx context.Context, req *game.Har res, err = serviceGame.GameRole().HarmonyChangeEvent(ctx, req) return } + +func (c *serverController) Csj(ctx context.Context, req *game.HarmonyChangeEventReq) (res *game.HarmonyChangeEventRes, err error) { + res, err = serviceGame.GameRole().HarmonyChangeEvent(ctx, req) + return +} diff --git a/internal/harmonyos/iap_server.go b/internal/harmonyos/iap_server.go new file mode 100644 index 0000000..b9d9177 --- /dev/null +++ b/internal/harmonyos/iap_server.go @@ -0,0 +1,74 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" +) + +type IapServer struct { +} + +const ( + // URL_ROOT IAP Server Root Url + URL_ROOT = "https://iap.cloud.huawei.com" + + TIME_OUT = time.Second * 5 // TODO: Need to replace it with the actual business logic. + + WHITE_SPACE = " " +) + +func (iap *IapServer) httpPost(url string, bodyMap map[string]interface{}) (string, error) { + bodyData, err := json.Marshal(bodyMap) + if err != nil { + return "", err + } + + req, err := http.NewRequest("POST", url, bytes.NewReader(bodyData)) + if err != nil { + return "", err + } + + jwtGenerator := &JWTGenerator{} + jwt, err := jwtGenerator.GenJWT(bodyMap) + if err != nil { + return "", err + } + req, err = iap.BuildAuthorization(req, jwt) + + var RequestHttpClient = http.Client{Timeout: TIME_OUT} + response, err := RequestHttpClient.Do(req) + if err != nil { + return "", err + } + + bodyBytes, err := ioutil.ReadAll(response.Body) + return string(bodyBytes), err +} + +func (iap *IapServer) BuildAuthorization(req *http.Request, jwt string) (*http.Request, error) { + var authHeader = fmt.Sprintf("Bearer%s%s", WHITE_SPACE, jwt) + req.Header.Set("Content-Type", "application/json; charset=UTF-8") + req.Header.Set("Authorization", authHeader) + return req, nil +} diff --git a/internal/harmonyos/jws_checker.go b/internal/harmonyos/jws_checker.go new file mode 100644 index 0000000..20efa77 --- /dev/null +++ b/internal/harmonyos/jws_checker.go @@ -0,0 +1,271 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "github.com/cristalhq/jwt/v3" + "io/ioutil" + "net/http" + "strings" +) + +const ( + CA_CERT_FILE_PATH = "manifest/config/RootCaG2Ecdsa.cer" // TODO: Need to replace it with the actual value. + + LEAF_CERT_OID = "1.3.6.1.4.1.2011.2.415.1.1" + + // HEADER_PARAM_ALG Algorithm type. The value is always ES256. + HEADER_PARAM_ALG = "ES256" + + X5C_CHAIN_LENGTH = 3 +) + +type JWSChecker struct { +} + +type Header struct { + // Alg Algorithm type. The value is always ES256. + Alg string `json:"alg"` + // X5c Indicates the X.509 certificate chain. The sequence is leaf certificate, intermediate certificate, and root certificate. + X5c []string `json:"x5c"` +} + +func (jwsChecker *JWSChecker) CheckAndDecodeJWS(jwsStr string) (string, error) { + jwsStrArr := strings.Split(jwsStr, ".") + if len(jwsStrArr) != X5C_CHAIN_LENGTH { + // TODO: Need to replace it with the actual business logic. + return "", errors.New("invalid jwsStr length") + } + + certChain, err := jwsChecker.extractCertChain(jwsStrArr) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + rootCert, err := jwsChecker.loadRootCert() + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + if err = jwsChecker.verifyCertChain(certChain, rootCert); err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + if err = jwsChecker.verifyOID(certChain[0]); err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + if err = jwsChecker.verifyCRLFromCerts(certChain[:2]); err != nil { + // TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy. + return "", err + } + + if err = jwsChecker.verifySignature(jwsStr, certChain[0], string(jwt.ES256)); err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + payLoadBytes, err := base64.RawStdEncoding.DecodeString(jwsStrArr[1]) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + return string(payLoadBytes), err +} + +func (jwsChecker *JWSChecker) loadRootCert() (*x509.Certificate, error) { + rootCABytes, err := ioutil.ReadFile(CA_CERT_FILE_PATH) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + + rootCerBlock, _ := pem.Decode(rootCABytes) + if rootCerBlock == nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + + rootCer, err := x509.ParseCertificate(rootCerBlock.Bytes) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + + return rootCer, err +} + +func (jwsChecker *JWSChecker) extractCertChain(jwsStrArr []string) ([]*x509.Certificate, error) { + headerByte, err := base64.RawStdEncoding.DecodeString(jwsStrArr[0]) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + + var header Header + err = json.Unmarshal(headerByte, &header) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + + certChain := make([]*x509.Certificate, len(header.X5c)) + + for i := range header.X5c { + certByte, err := base64.StdEncoding.DecodeString(header.X5c[i]) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + cert, err := x509.ParseCertificate(certByte) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return nil, err + } + certChain[i] = cert + } + + return certChain, err +} + +func (jwsChecker *JWSChecker) verifyCertChain(certChain []*x509.Certificate, rootCert *x509.Certificate) error { + roots := x509.NewCertPool() + roots.AddCert(rootCert) + + intermedia := x509.NewCertPool() + intermedia.AddCert(certChain[1]) + + opts := x509.VerifyOptions{ + Roots: roots, + Intermediates: intermedia, + } + _, err := rootCert.Verify(opts) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return err + } + + _, err = certChain[0].Verify(opts) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return err + } + + return err +} + +func (jwsChecker *JWSChecker) verifyCRLFromCerts(certChain []*x509.Certificate) error { + for _, cert := range certChain { + err := checkCrl(cert) + if err != nil { + // TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy. + return err + } + } + return nil +} + +func checkCrl(cert *x509.Certificate) error { + if len(cert.CRLDistributionPoints) == 0 { + return errors.New("No CRL distribution points found in certificate") + } + + resp, err := http.Get(cert.CRLDistributionPoints[0]) + if err != nil { + // TODO: Determine whether to ignore CRL certificate download exception based on application Security Policy. + return err + } + defer resp.Body.Close() + + crlData, err := ioutil.ReadAll(resp.Body) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return err + } + + crl, err := x509.ParseCRL(crlData) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return err + } + + if crl.TBSCertList.RevokedCertificates == nil { + return nil + } + + for _, revokedCert := range crl.TBSCertList.RevokedCertificates { + if revokedCert.SerialNumber.Cmp(cert.SerialNumber) == 0 { + // TODO: Need to replace it with the actual business logic. + return fmt.Errorf("certificate %s has been revoked", cert.Subject.CommonName) + } + } + + return nil +} + +func (jwsChecker *JWSChecker) verifyOID(leafCert *x509.Certificate) error { + var isExist = false + for i := range leafCert.Extensions { + if LEAF_CERT_OID == leafCert.Extensions[i].Id.String() && leafCert.Extensions[i].Critical == false { + isExist = true + } + } + + if !isExist { + // TODO: Need to replace it with the actual business logic. + return errors.New("oid verification failed") + } + + return nil +} + +func (jwsChecker *JWSChecker) verifySignature(jwsStr string, leafCert *x509.Certificate, algParam string) error { + if algParam != jwt.ES256.String() { + // TODO: Need to replace it with the actual business logic. + return errors.New("invalid algorithm") + } + switch pubicKey := leafCert.PublicKey.(type) { + case *ecdsa.PublicKey: + verifier, err := jwt.NewVerifierES(jwt.ES256, pubicKey) + if err != nil { + return err + } + + token := []byte(jwsStr) + _, err = jwt.ParseAndVerify(token, verifier) + if err != nil { + return err + } + default: + // TODO: Need to replace it with the actual business logic. + return errors.New("public key not supported") + } + return nil +} diff --git a/internal/harmonyos/jwt_generator.go b/internal/harmonyos/jwt_generator.go new file mode 100644 index 0000000..d08ccf2 --- /dev/null +++ b/internal/harmonyos/jwt_generator.go @@ -0,0 +1,126 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "crypto/ecdsa" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/json" + "encoding/pem" + "errors" + "github.com/cristalhq/jwt/v3" + "io/ioutil" + "time" +) + +type JWTGenerator struct { +} + +type IAPJWTClaims struct { + // Iss Key issuer ID. + Iss string `json:"iss"` + // Aud Expected receiver of the JWT. The value is fixed at iap-v1. + Aud string `json:"aud"` + // Iat Time when the JWT is issued. The value is a UTC timestamp, in seconds. + Iat int64 `json:"iat"` + // Exp Time when the JWT expires. The value is a UTC timestamp, in seconds. exp-iat indicates the validity period of the JWT, which cannot exceed one hour. + Exp int64 `json:"exp"` + // Aid App ID. + Aid string `json:"aid"` + // Digest Hash value of the request body (JSON character string), which is used to verify the integrity of the body. The algorithm is SHA-256. + Digest string `json:"digest"` +} + +const ( + // JWT_PRI_KEY_PATH Private key file path. + JWT_PRI_KEY_PATH = "manifest/config/IAPKey.p8" // TODO: Need to replace it with the actual value. + + // ACTIVE_TIME JWT validity period, which is a UTC timestamp in seconds. The validity period cannot exceed 1 hour. + ACTIVE_TIME_SECOND = 3600 // TODO: Need to replace it with the actual value. + + KID = "f35b90cf-48be-4bb9-bb66-f7e814d5521c" // TODO: Need to replace it with the actual value. + + ISSUER_ID = "002124f1-f9a9-468e-92ea-26561ea9c605" // TODO: Need to replace it with the actual business logic. + + APP_ID = "6917589392953468039" // TODO: Need to replace it with the actual business logic. +) + +func (jwtGenerator *JWTGenerator) GenJWT(bodyMap map[string]interface{}) (string, error) { + privateKeyPEM, err := ioutil.ReadFile(JWT_PRI_KEY_PATH) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + block, _ := pem.Decode(privateKeyPEM) + if block == nil { + // TODO: Need to replace it with the actual business logic. + return "", errors.New("the key content is empty") + } + + privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + ecdsaPrivateKey, ok := privateKey.(*ecdsa.PrivateKey) + if !ok { + // TODO: Need to replace it with the actual business logic. + return "", errors.New("failed to convert private key to *ecdsa.PrivateKey") + } + + signer, err := jwt.NewSignerES(jwt.ES256, ecdsaPrivateKey) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + payloadJson, err := json.Marshal(bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + return "", err + } + + hash := sha256.New() + hash.Write(payloadJson) + sha256Sum := hash.Sum(nil) + sha256Hex := hex.EncodeToString(sha256Sum) + + builder := jwt.NewBuilder(signer, jwt.WithKeyID(KID)) // TODO: Need to replace it with the actual business logic. + + signTime := time.Now().UTC().Unix() + claims := IAPJWTClaims{ + Iss: ISSUER_ID, // TODO: Need to replace it with the actual business logic. + Aud: "iap-v1", + Iat: signTime, + Exp: signTime + ACTIVE_TIME_SECOND, + Aid: APP_ID, // TODO: Need to replace it with the actual business logic. + Digest: sha256Hex, + } + + iapJwt, err := builder.Build(claims) + if err != nil { + // TODO: Need replace it with your business logic. + return "", err + } + + return iapJwt.String(), nil +} diff --git a/internal/harmonyos/notification/appserver.go b/internal/harmonyos/notification/appserver.go new file mode 100644 index 0000000..d011367 --- /dev/null +++ b/internal/harmonyos/notification/appserver.go @@ -0,0 +1,79 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package notification + +import ( + "encoding/json" +) + +type AppServer struct{} + +// DealNotificationV3 Deal notification information V3. +// Params: +// +// notificationPayloadStr: Notification content, which is the payload decoded by jwsNotification. +// +// Return: +// +// nil +// +// Exception: +// +// error +func (a *AppServer) DealNotificationV3(notificationPayloadStr string) error { + var notificationPayload = NotificationPayload{} + if err := json.Unmarshal([]byte(notificationPayloadStr), ¬ificationPayload); err != nil { + return err + } + // TODO: Need to replace it with the actual business logic. + notificationSubtype := notificationPayload.NotificationSubtype + switch notificationSubtype { + case INITIAL_BUY: + break + case DID_RENEW: + break + case RESTORE: + break + case AUTO_RENEW_ENABLED: + break + case AUTO_RENEW_DISABLED: + break + case DOWNGRADE: + break + case UPGRADE: + break + case REFUND_TRANSACTION: + break + case BILLING_RETRY: + break + case PRICE_INCREASE: + break + case BILLING_RECOVERY: + break + case PRODUCT_NOT_FOR_SALE: + break + case APPLICATION_DELETE_SUBSCRIPTION_HOSTING: + break + case RENEWAL_EXTENDED: + break + default: + break + } + + return nil +} diff --git a/internal/harmonyos/notification/notificationConstant.go b/internal/harmonyos/notification/notificationConstant.go new file mode 100644 index 0000000..a81b14b --- /dev/null +++ b/internal/harmonyos/notification/notificationConstant.go @@ -0,0 +1,46 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package notification + +type NotificationType = string + +type SubNotificationType = string + +const ( + DID_NEW_TRANSACTION NotificationType = "DID_NEW_TRANSACTION" + DID_CHANGE_RENEWAL_STATUS NotificationType = "DID_CHANGE_RENEWAL_STATUS" + REVOKE NotificationType = "REVOKE" + RENEWAL_TIME_MODIFIED NotificationType = "RENEWAL_TIME_MODIFIED" + EXPIRE NotificationType = "EXPIRE" + SYNC NotificationType = "SYNC" + + INITIAL_BUY SubNotificationType = "INITIAL_BUY" + DID_RENEW SubNotificationType = "DID_RENEW" + RESTORE SubNotificationType = "RESTORE" + AUTO_RENEW_ENABLED SubNotificationType = "AUTO_RENEW_ENABLED" + AUTO_RENEW_DISABLED SubNotificationType = "AUTO_RENEW_DISABLED" + DOWNGRADE SubNotificationType = "DOWNGRADE" + UPGRADE SubNotificationType = "UPGRADE" + REFUND_TRANSACTION SubNotificationType = "REFUND_TRANSACTION" + BILLING_RETRY SubNotificationType = "BILLING_RETRY" + PRICE_INCREASE SubNotificationType = "PRICE_INCREASE" + BILLING_RECOVERY SubNotificationType = "BILLING_RECOVERY" + PRODUCT_NOT_FOR_SALE SubNotificationType = "PRODUCT_NOT_FOR_SALE" + APPLICATION_DELETE_SUBSCRIPTION_HOSTING SubNotificationType = "APPLICATION_DELETE_SUBSCRIPTION_HOSTING" + RENEWAL_EXTENDED SubNotificationType = "RENEWAL_EXTENDED" +) diff --git a/internal/harmonyos/notification/notificationMetaData.go b/internal/harmonyos/notification/notificationMetaData.go new file mode 100644 index 0000000..e40caef --- /dev/null +++ b/internal/harmonyos/notification/notificationMetaData.go @@ -0,0 +1,31 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package notification + +type NotificationMetaData struct { + Environment string `json:"environment"` + ApplicationId string `json:"applicationId"` + PackageName string `json:"packageName"` + Type int `json:"type"` + CurrentProductId string `json:"currentProductId"` + SubGroupId string `json:"subGroupId"` + SubGroupGenerationId string `json:"subGroupGenerationId"` + SubscriptionId string `json:"subscriptionId"` + PurchaseToken string `json:"purchaseToken"` + PurchaseOrderId string `json:"purchaseOrderId"` +} diff --git a/internal/harmonyos/notification/notificationPayload.go b/internal/harmonyos/notification/notificationPayload.go new file mode 100644 index 0000000..76c204a --- /dev/null +++ b/internal/harmonyos/notification/notificationPayload.go @@ -0,0 +1,27 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package notification + +type NotificationPayload struct { + NotificationType NotificationType `json:"notificationType"` + NotificationSubtype SubNotificationType `json:"notificationSubtype"` + NotificationRequestId string `json:"notificationRequestId"` + NotificationMetaData NotificationMetaData `json:"notificationMetaData"` + NotificationVersion string `json:"notificationVersion"` + SignedTime int64 `json:"signedTime"` +} diff --git a/internal/harmonyos/order_lookup.go b/internal/harmonyos/order_lookup.go new file mode 100644 index 0000000..1d0da20 --- /dev/null +++ b/internal/harmonyos/order_lookup.go @@ -0,0 +1,107 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "log" +) + +const ( + URL_ORDER_LOOKUP = "/harmony/v1/application/order/lookup" + URL_ORDER_LIST_QUERY = "/order/harmony/v1/application/trade/orders/query" + URL_USER_ORDER_LIST_QUERY = "/harmony/v1/application/user/orders/query" +) + +type OrderLookup struct { + IapServer +} + +// OrderQuery This API is used to query the latest status of a consumable or non-consumable order. +// Params: +// +// orderNo: Order ID of a purchase. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (orderService *OrderLookup) OrderQuery(bodyMap map[string]interface{}) (resp string, err error) { + + resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_LOOKUP, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + log.Print("Error:", err) + } else { + // TODO: Need to replace it with the actual business logic. + log.Print("order query response is: ", resp) + } + return resp, err +} + +// UserOrdersQuery This API is used to query the latest status of a consumable or non-consumable order. +// Params: +// +// purchaseOrderId: Order ID of a purchase. +// productType: product Type. +// continuationToken: continuationToken. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (orderService *OrderLookup) UserOrdersQuery(bodyMap map[string]interface{}) (resp string, err error) { + resp, err = orderService.httpPost(URL_ROOT+URL_USER_ORDER_LIST_QUERY, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + log.Print("Error:", err) + } else { + // TODO: Need to replace it with the actual business logic. + log.Print("order status query response is: ", resp) + } + return resp, err +} + +// OrdersQuery This API is used to query the latest status of a consumable or non-consumable order. +// Params: +// +// orderNo: Order ID of a purchase. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (orderService *OrderLookup) OrdersQuery(bodyMap map[string]interface{}) (resp string, err error) { + resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_LIST_QUERY, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + log.Print("Error:", err) + } else { + // TODO: Need to replace it with the actual business logic. + log.Print("order status query response is: ", resp) + } + return resp, err +} diff --git a/internal/harmonyos/order_service.go b/internal/harmonyos/order_service.go new file mode 100644 index 0000000..0c15f3d --- /dev/null +++ b/internal/harmonyos/order_service.go @@ -0,0 +1,87 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "log" +) + +const ( + URL_ORDER_STATUS_QUERY = "/order/harmony/v1/application/order/status/query" + + URL_ORDER_SHIPPED_CONFIRM = "/order/harmony/v1/application/purchase/shipped/confirm" +) + +type OrderService struct { + IapServer +} + +// OrderStatusQuery This API is used to query the latest status of a consumable or non-consumable order. +// Params: +// +// purchaseOrderId: Order ID of a purchase. +// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (orderService *OrderService) OrderStatusQuery(purchaseOrderId string, purchaseToken string) (resp string, err error) { + bodyMap := make(map[string]interface{}) + bodyMap["purchaseOrderId"] = purchaseOrderId + bodyMap["purchaseToken"] = purchaseToken + + resp, err = orderService.httpPost(URL_ROOT+URL_ORDER_STATUS_QUERY, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + log.Print("Error:", err) + } else { + // TODO: Need to replace it with the actual business logic. + log.Print("order status query response is: ", resp) + } + return resp, err +} + +// OrderShippedConfirm This API is used to acknowledge that a purchased consumable or non-consumable has been delivered. +// Params: +// +// purchaseOrderId: Order ID of a purchase. +// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (orderService *OrderService) OrderShippedConfirm(purchaseOrderId string, purchaseToken string) { + bodyMap := make(map[string]interface{}) + bodyMap["purchaseOrderId"] = purchaseOrderId + bodyMap["purchaseToken"] = purchaseToken + resp, err := orderService.httpPost(URL_ROOT+URL_ORDER_SHIPPED_CONFIRM, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + log.Print("Error:", err) + } + // TODO: Need to replace it with the actual business logic. + log.Print("order shipped confirm response is: ", resp) +} diff --git a/internal/harmonyos/subscription_service.go b/internal/harmonyos/subscription_service.go new file mode 100644 index 0000000..95c2cba --- /dev/null +++ b/internal/harmonyos/subscription_service.go @@ -0,0 +1,84 @@ +/* + * Copyright 2024. Huawei Technologies Co., Ltd. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package harmonyos + +import ( + "fmt" +) + +const ( + URL_SUB_STATUS_QUERY = "/subscription/harmony/v1/application/subscription/status/query" + + URL_SUB_SHIPPED_CONFIRM = "/subscription/harmony/v1/application/purchase/shipped/confirm" +) + +type SubscriptionService struct { + IapServer +} + +// SubStatusQuery This API is used to query the latest status of an auto-renewable subscription. +// Params: +// +// purchaseOrderId: Order ID of a purchase. +// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (subService *SubscriptionService) SubStatusQuery(purchaseOrderId string, purchaseToken string) { + bodyMap := make(map[string]interface{}) + bodyMap["purchaseOrderId"] = purchaseOrderId + bodyMap["purchaseToken"] = purchaseToken + resp, err := subService.httpPost(URL_ROOT+URL_SUB_STATUS_QUERY, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + fmt.Println("Error:", err) + } + // TODO: Need to replace it with the actual business logic. + fmt.Println("sub status query response is:", resp) +} + +// SubShippedConfirm This API is used to acknowledge that a purchased subscription has been delivered to the user. +// Params: +// +// purchaseOrderId: Order ID of a purchase. +// purchaseToken: Purchase token of a product, which you can obtain from the information returned after a purchase or an order query. +// +// Return: +// +// nil +// +// Exception: +// +// nil +func (subService *SubscriptionService) SubShippedConfirm(purchaseOrderId string, purchaseToken string) { + // pack the request body + bodyMap := make(map[string]interface{}) + bodyMap["purchaseOrderId"] = purchaseOrderId + bodyMap["purchaseToken"] = purchaseToken + resp, err := subService.httpPost(URL_ROOT+URL_SUB_SHIPPED_CONFIRM, bodyMap) + if err != nil { + // TODO: Need to replace it with the actual business logic. + } + // TODO: Need to replace it with the actual business logic. + fmt.Println("sub shipped confirm response is:", resp) +} diff --git a/internal/service/harmonyOrder.go b/internal/service/harmonyOrder.go new file mode 100644 index 0000000..e973d1d --- /dev/null +++ b/internal/service/harmonyOrder.go @@ -0,0 +1,123 @@ +package service + +import ( + "encoding/json" + "errors" + "log" + "strconv" + "tyj_admin/api/v1/game" + "tyj_admin/internal/harmonyos" +) + +func HarmonyOrderLookup(req *game.GetHarmonyOrderReq) (res *game.GetHarmonyOrderRes, err error) { + res = new(game.GetHarmonyOrderRes) + bodyMap := make(map[string]interface{}) + bodyMap["orderNo"] = req.Order + orderService := &harmonyos.OrderLookup{} + orderInfoStr, err := orderService.OrderQuery(bodyMap) + if err != nil { + log.Printf("HarmonyOrderLookup======>.OrderStatusQuery : %v - err:%s", req.Order, err.Error()) + return + } + orderInfo := &game.HarmonyLookup{} + if err = json.Unmarshal([]byte(orderInfoStr), &orderInfo); err != nil { + log.Printf("HarmonyOrderLookup======>.Unmarshal : %v", orderInfoStr) + return + } + res.OrderStatus = orderInfo.OrderStatus + if orderInfo.OrderStatus != 1 { + err = errors.New("交易号无效") + return + } + + jwsChecker := &harmonyos.JWSChecker{} + purchaseOrderStr, err := jwsChecker.CheckAndDecodeJWS(orderInfo.JwsPurchaseOrder) + if err != nil { + log.Printf("HarmonyOrderLookup======>.jwsChecker.CheckAndDecodeJWS : err: %s, %s", err.Error(), purchaseOrderStr) + return + } + purchaseOrder := game.PurchaseOrderPayload{} + if err = json.Unmarshal([]byte(purchaseOrderStr), &purchaseOrder); err != nil { + log.Printf("HarmonyOrderLookup======>.CheckAndDecodeJWS Unmarshal: %v, Err: %s", purchaseOrderStr, err) + return + } + res.PurchaseOrderInfo = purchaseOrder + return +} + +func HarmonyOrders(req *game.GetHarmonyOrdersReq) (res *game.GetHarmonyOrdersRes, err error) { + res = new(game.GetHarmonyOrdersRes) + bodyMap := map[string]interface{}{ + "startTime": req.StartTime, + "endTime": req.EndTime, + } + if len(req.ContinuationToken) > 0 { + bodyMap["continuationToken"] = req.ContinuationToken + } + orderService := &harmonyos.OrderLookup{} + orderInfoStr, err := orderService.OrdersQuery(bodyMap) + if err != nil { + log.Printf("HarmonyOrders======>.StartTime: %d, EndTime : %d - err:%s", req.StartTime, req.EndTime, err.Error()) + return + } + + if err = json.Unmarshal([]byte(orderInfoStr), &res); err != nil { + log.Printf("HarmonyOrders======>.Unmarshal : %v", orderInfoStr) + return + } + + return +} + +func HarmonyUserOrders(req *game.GetHarmonyUserOrdersReq) (res *game.GetHarmonyUserOrdersRes, err error) { + res = new(game.GetHarmonyUserOrdersRes) + bodyMap := make(map[string]interface{}) + bodyMap["purchaseOrderId"] = req.PurchaseOrderId + if req.ProductType != "" { + bodyMap["productType"], err = strconv.Atoi(req.ProductType) + } + if req.StartTime != 0 { + bodyMap["startTime"] = req.StartTime + } + if req.EndTime != 0 { + bodyMap["endTime"] = req.EndTime + } + if len(req.ProductIdList) > 0 { + bodyMap["productIdList"] = req.ProductIdList + } + if len(req.ContinuationToken) > 0 { + bodyMap["continuationToken"] = req.ContinuationToken + } + orderService := &harmonyos.OrderLookup{} + orderInfoStr, err := orderService.UserOrdersQuery(bodyMap) + if err != nil { + log.Printf("HarmonyUserOrders======>.purchaseOrderId: %s, - err:%s", req.PurchaseOrderId, err.Error()) + return + } + + orderInfo := game.HarmonyUserOrders{} + if err = json.Unmarshal([]byte(orderInfoStr), &orderInfo); err != nil { + log.Printf("HarmonyUserOrders======>.Unmarshal : %v", orderInfoStr) + return + } + res.ResponseCode = orderInfo.ResponseCode + res.ResponseMessage = orderInfo.ResponseMessage + res.ContinuationToken = orderInfo.ContinuationToken + for _, v := range orderInfo.JwsPurchaseOrderList { + jwsChecker := &harmonyos.JWSChecker{} + purchaseOrderStr, err1 := jwsChecker.CheckAndDecodeJWS(v) + if err1 != nil { + err = err1 + log.Printf("HarmonyUserOrders======>.jwsChecker.CheckAndDecodeJWS : err: %s, %s", err.Error(), purchaseOrderStr) + continue + } + purchaseOrder := game.PurchaseOrderPayload{} + if err = json.Unmarshal([]byte(purchaseOrderStr), &purchaseOrder); err != nil { + log.Printf("HarmonyUserOrders======>.CheckAndDecodeJWS Unmarshal: %v, Err: %s", purchaseOrderStr, err) + continue + } + res.OrderInfoList = append(res.OrderInfoList, purchaseOrder) + } + + return +} diff --git a/internal/service/hmsAccessToken.go b/internal/service/hmsAccessToken.go index 42c6a85..cc77a52 100644 --- a/internal/service/hmsAccessToken.go +++ b/internal/service/hmsAccessToken.go @@ -11,11 +11,12 @@ import ( ) type DemoConfig struct { - ClientSecret string - ClientId string - TokenUrl string - RootUrlOrder string - AccessToken string + ClientSecret string + ClientId string + TokenUrl string + RootUrlOrder string + AccessToken string + UrlOrderLookup string } func GetDefaultConfig() *DemoConfig { diff --git a/internal/serviceGame/advertisement.go b/internal/serviceGame/advertisement.go index 9730d95..8c9d23a 100644 --- a/internal/serviceGame/advertisement.go +++ b/internal/serviceGame/advertisement.go @@ -52,13 +52,13 @@ func (g *advertisementImpl) DeepAdvertise(ctx context.Context, req *game.DeepADR func (g *advertisementImpl) AttributionHugeAmount(ctx context.Context, req *game.ATHAReq) (res *game.ATHARes, err error) { res = new(game.ATHARes) - res, err = internal.AttributionHugeAmount(ctx, req) + _, err = internal.AttributionHugeAmount(ctx, req) return } func (g *advertisementImpl) ConversionHugeAmount(ctx context.Context, req *game.CSHAReq) (res *game.CSHARes, err error) { res = new(game.CSHARes) - res, err = internal.ConversionHugeAmount(ctx, req) + res, err = internal.ConversionHugeAmount(ctx, req, entity.AdvertisementOceanegine1{}) return } diff --git a/internal/serviceGame/internal/advertisement.go b/internal/serviceGame/internal/advertisement.go index 549f4f2..29d39bb 100644 --- a/internal/serviceGame/internal/advertisement.go +++ b/internal/serviceGame/internal/advertisement.go @@ -104,69 +104,25 @@ func sendMsgHugeAmount(ctx context.Context, url string, data map[string]interfac if err != nil { return "", err } - fmt.Println("Deposit - json: ", tmp) + log.Println("Deposit - json: ", url, tmp) return tmp, err } -func AttributionHugeAmount(ctx context.Context, req *game.ATHAReq) (res *game.ATHARes, err error) { +func AttributionHugeAmount(ctx context.Context, req *game.ATHAReq) (res entity.AdvertisementOceanegine1, err error) { log.Printf("AttributionHugeAmount: %s", gjson.MustEncodeString(req)) data := map[string]interface{}{ "platform": req.Platform, // ios或android "package_name": req.PackageName, "customer_active_time": fmt.Sprint(time.Now().UnixMilli()), // 毫秒时间戳,客户激活归因时间点 } - var adDatas []entity.AdvertisementOceanegine1 - model := dao.AdvertisementOceanegine1.Ctx(ctx) if req.Platform == "android" { - if req.AndroidId != "" && req.AndroidId != "__ANDROIDID__" && req.AndroidId != "00000000-0000-0000-0000-000000000000" { - _ = model.Where("adv_android_id=?", req.AndroidId).Where("os=?", 0).Scan(&adDatas) - } - if len(adDatas) == 0 { - log.Printf("AttributionHugeAmount: adData is nil %s", gjson.MustEncodeString(req)) - err = errors.New("account is nil") - return - } data["android_id"] = req.AndroidId } else if req.Platform == "ios" { - if req.Idfa != "" && req.Idfa != "00000000-0000-0000-0000-000000000000" { - _ = model.Where("idfa=?", req.Idfa).Where("os=?", 1).Scan(&adDatas) - } - if len(adDatas) == 0 { - if req.Caid != "" { - var list []map[string]string - _ = json.Unmarshal([]byte(req.Caid), &list) - for _, v := range list { - _ = model.Where("caid=?", v["caid"]).Where("os=?", 1).Scan(&adDatas) - if len(adDatas) > 0 { - break - } - } - } - if len(adDatas) == 0 { - log.Printf("AttributionHugeAmount: adData is nil %s", gjson.MustEncodeString(req)) - err = errors.New("account is nil") - return - } - } data["idfv"] = req.Idfv } else { err = errors.New("platform is error " + req.Platform) return } - if len(adDatas) == 0 { - err = errors.New("无广告!") - return - } - var adData entity.AdvertisementOceanegine1 - for _, v := range adDatas { - if adData.Id == 0 || v.LastTouchTime > adData.LastTouchTime || (v.LastTouchTime == adData.LastTouchTime && v.CDate.Unix() > adData.CDate.Unix()) { - adData = v - } - } - if adData.CallbackParam == "" { - err = errors.New("无广告!") - return - } url := "https://analytics.oceanengine.com/sdk/app/attribution" tmp, err := sendMsgHugeAmount(ctx, url, data) @@ -177,61 +133,22 @@ func AttributionHugeAmount(ctx context.Context, req *game.ATHAReq) (res *game.AT if err != nil { return } - if resJson != nil && resJson.Get("code").Int() == 0 { - callbackUrl := resJson.Get("callback_url").String() callbackParam := resJson.Get("callback_param").String() if callbackParam == "" { err = errors.New("callbackParam nil") return } - androidId := resJson.Get("adv_android_id").String() - //if androidId == "" { - // androidId = req.AndroidId - //} - idfa := resJson.Get("idfa").String() - //if idfa == "" { - // idfa = req.Idfa - //} - advIdfv := resJson.Get("adv_idfv").String() - //if advIdfv == "" { - // advIdfv = req.Idfv - //} - lastTouchTime := resJson.Get("last_touch_time").Int64() * 1000 - if adData.LastTouchTime > lastTouchTime { - err = errors.New("lastTouchTime repeat") - return - } - advertiseData := do.AdvertisementOceanegine1{AdvAndroidId: androidId, Idfa: idfa, AdvIdfv: advIdfv, - CallbackUrl: callbackUrl, CallbackParam: callbackParam, LastTouchTime: lastTouchTime, UnitId: req.UnitId} - if req.Platform == "android" { - advertiseData.Os = 0 - if androidId == "00000000-0000-0000-0000-000000000000" || androidId == "" { - err = errors.New("androidId default") - return - } - } else if req.Platform == "ios" { - advertiseData.Os = 1 - if idfa == "00000000-0000-0000-0000-000000000000" || idfa == "" { - err = errors.New("idfa default") - return - } - if advIdfv == "" { - err = errors.New("adv_idfv default") - return - } - } - - _, err = dao.AdvertisementOceanegine1.Ctx(ctx).WherePri(adData.Id).Update(advertiseData) + res.CallbackParam = callbackParam + res.LastTouchTime = resJson.Get("last_touch_time").Int64() * 1000 + return } else { err = errors.New("获取失败") return } - - return } -func ConversionHugeAmount(ctx context.Context, req *game.CSHAReq) (res *game.CSHARes, err error) { +func ConversionHugeAmount(ctx context.Context, req *game.CSHAReq, adOc entity.AdvertisementOceanegine1) (res *game.CSHARes, err error) { //log.Printf("ConversionHugeAmount: %s", gjson.MustEncodeString(req)) var adDatas []entity.AdvertisementOceanegine1 device := map[string]interface{}{ @@ -266,18 +183,21 @@ func ConversionHugeAmount(ctx context.Context, req *game.CSHAReq) (res *game.CSH err = errors.New("platform is error " + req.Platform) return } - - if len(adDatas) == 0 { - log.Printf("ConversionHugeAmount: adData is nil %s", gjson.MustEncodeString(req)) - err = errors.New("account is nil") - return - } var adData entity.AdvertisementOceanegine1 - for _, v := range adDatas { - if adData.Id == 0 || v.LastTouchTime > adData.LastTouchTime || (v.LastTouchTime == adData.LastTouchTime && v.CDate.Unix() > adData.CDate.Unix()) { - adData = v + if len(adDatas) > 0 { + for _, v := range adDatas { + if adData.Id == 0 || v.LastTouchTime > adData.LastTouchTime || (v.LastTouchTime == adData.LastTouchTime && v.CDate.Unix() > adData.CDate.Unix()) { + adData = v + } + } + if adData.Id > 0 { + adOc.Id = adData.Id } } + + if adOc.LastTouchTime != 0 && adData.LastTouchTime < adOc.LastTouchTime { + adData = adOc + } if adData.CallbackParam == "" { err = errors.New("callbackParam is nil") return @@ -325,7 +245,7 @@ func ConversionHugeAmount(ctx context.Context, req *game.CSHAReq) (res *game.CSH resJson, err := gjson.DecodeToJson(tmp) if resJson != nil && resJson.Get("code").Int() != 0 { err = errors.New(resJson.Get("message").String()) - } else if resJson.Get("code").Int() == 0 { + } else if resJson.Get("code").Int() == 0 && adData.Id > 0 { saveData := do.AdvertisementOceanegine1{} if req.EventType == consts.EventType_Active { saveData.Active = 1 @@ -365,21 +285,7 @@ func HugeAmount(ctx context.Context, req *game.AdvertiseHAReq) { if req.Caid != "" { log.Printf("HugeAmount: req: %s", gjson.MustEncodeString(req)) } - req2 := game.CSHAReq{ - Platform: req.Platform, - Id: req.Id, - Idfa: req.Idfa, - EventType: req.EventType, - Properties: req.Properties, - Caid: req.Caid, - //UnitId: req.UnitId, - } - req2.Id = req.Id - _, err := ConversionHugeAmount(ctx, &req2) - if err != nil { - log.Printf("AdvertiseHugeAmount: ConversionHugeAmount err- %s, req: %s", err.Error(), gjson.MustEncodeString(req2)) - return - } + req1 := game.ATHAReq{ Platform: req.Platform, PackageName: req.PackageName, @@ -391,13 +297,26 @@ func HugeAmount(ctx context.Context, req *game.AdvertiseHAReq) { } else if req.Platform == "ios" { req1.Idfv = req.Id } else { - log.Printf("AdvertiseHugeAmount: plarform err- %s, req.Platform: %s", err.Error(), req.Platform) + log.Printf("AdvertiseHugeAmount: plarform , req.Platform: %s", req.Platform) return } - _, err = AttributionHugeAmount(ctx, &req1) + adOc, err := AttributionHugeAmount(ctx, &req1) if err != nil { log.Printf("AdvertiseHugeAmount: AttributionHugeAmount err- %s, req: %s", err.Error(), gjson.MustEncodeString(req1)) - return + } + + req2 := game.CSHAReq{ + Platform: req.Platform, + Id: req.Id, + Idfa: req.Idfa, + EventType: req.EventType, + Properties: req.Properties, + Caid: req.Caid, + } + req2.Id = req.Id + _, err = ConversionHugeAmount(ctx, &req2, adOc) + if err != nil { + log.Printf("AdvertiseHugeAmount: ConversionHugeAmount err- %s, req: %s", err.Error(), gjson.MustEncodeString(req2)) } return } diff --git a/internal/serviceGame/order.go b/internal/serviceGame/order.go index 427c66a..f13abdc 100644 --- a/internal/serviceGame/order.go +++ b/internal/serviceGame/order.go @@ -44,6 +44,9 @@ type IGameOrder interface { ResetRechargeSign(ctx context.Context, req *game.ResetRechargeSignReq) (res *game.ResetRechargeSignRes, err error) GetTransactionId(ctx context.Context, req *game.GetTransactionIdReq) (res *game.GetTransactionIdRes, err error) GetHuaWeiOrderList(ctx context.Context, req *game.GetHuaWeiOrderListReq) (res *game.GetHuaWeiOrderListRes, err error) + GetHarmonyOrder(ctx context.Context, req *game.GetHarmonyOrderReq) (res *game.GetHarmonyOrderRes, err error) + GetHarmonyOrders(ctx context.Context, req *game.GetHarmonyOrdersReq) (res *game.GetHarmonyOrdersRes, err error) + GetHarmonyUserOrders(ctx context.Context, req *game.GetHarmonyUserOrdersReq) (res *game.GetHarmonyUserOrdersRes, err error) } type gameOrderImpl struct { @@ -598,17 +601,21 @@ func (o gameOrderImpl) AddRechargeSign(ctx context.Context, req *game.AddRecharg } func (o gameOrderImpl) ResetRechargeSign(ctx context.Context, req *game.ResetRechargeSignReq) (res *game.ResetRechargeSignRes, err error) { - user := service.Context().GetLoginUser(ctx) - fulluser, err1 := service.User().GetUserByUsername(ctx, user.UserName) - if err1 != nil { - return nil, err1 - } - password := libUtils.EncryptPassword(req.Password, fulluser.UserSalt) - if fulluser.UserPassword != password { - log.Printf("ResetRechargeSign 密码错误! fulluser: %v, pwd: %s", gjson.MustEncodeString(fulluser), password) - return nil, errors.New("密码错误!") - } - result, err := dao.GameRechargeLevel.Ctx(ctx).Where("1=1").Delete() + //user := service.Context().GetLoginUser(ctx) + //fulluser, err1 := service.User().GetUserByUsername(ctx, user.UserName) + //if err1 != nil { + // return nil, err1 + //} + //password := libUtils.EncryptPassword(req.Password, fulluser.UserSalt) + //if fulluser.UserPassword != password { + // log.Printf("ResetRechargeSign 密码错误! fulluser: %v, pwd: %s", gjson.MustEncodeString(fulluser), password) + // return nil, errors.New("密码错误!") + //} + model := dao.GameRechargeLevel.Ctx(ctx).Where("1=1") + if req.Server >= 0 { + model = model.Where("server=?", req.Server) + } + result, err := model.Delete() if err != nil { log.Printf("ResetRechargeSign: %v", err) return nil, err @@ -647,3 +654,18 @@ func (o gameOrderImpl) GetHuaWeiOrderList(ctx context.Context, req *game.GetHuaW res.Order = *order return } + +func (o gameOrderImpl) GetHarmonyOrder(ctx context.Context, req *game.GetHarmonyOrderReq) (res *game.GetHarmonyOrderRes, err error) { + res, err = service.HarmonyOrderLookup(req) + return +} + +func (o gameOrderImpl) GetHarmonyOrders(ctx context.Context, req *game.GetHarmonyOrdersReq) (res *game.GetHarmonyOrdersRes, err error) { + res, err = service.HarmonyOrders(req) + return +} + +func (o gameOrderImpl) GetHarmonyUserOrders(ctx context.Context, req *game.GetHarmonyUserOrdersReq) (res *game.GetHarmonyUserOrdersRes, err error) { + res, err = service.HarmonyUserOrders(req) + return +} diff --git a/manifest/config/IAPKey.p8 b/manifest/config/IAPKey.p8 new file mode 100644 index 0000000..2b9bac1 --- /dev/null +++ b/manifest/config/IAPKey.p8 @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg+oOEZ3evRCWbbgUj +BDfyrt+OinOGwgW7jcWQ27lcAWWgCgYIKoZIzj0DAQehRANCAAQy3I+h5+egmhf9 +NEOdXwACHa7Brz5atHtCAaTG4OTekPW9kbAGVYR+qAmWGmI7kDQYbO+5jG30YRdJ +Ed4992DO +-----END PRIVATE KEY----- diff --git a/manifest/config/RootCaG2Ecdsa.cer b/manifest/config/RootCaG2Ecdsa.cer new file mode 100644 index 0000000..e7fc6ba --- /dev/null +++ b/manifest/config/RootCaG2Ecdsa.cer @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICGjCCAaGgAwIBAgIIShhpn519jNAwCgYIKoZIzj0EAwMwUzELMAkGA1UEBhMC +Q04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UECwwKSHVhd2VpIENCRzEeMBwGA1UE +AwwVSHVhd2VpIENCRyBSb290IENBIEcyMB4XDTIwMDMxNjAzMDQzOVoXDTQ5MDMx +NjAzMDQzOVowUzELMAkGA1UEBhMCQ04xDzANBgNVBAoMBkh1YXdlaTETMBEGA1UE +CwwKSHVhd2VpIENCRzEeMBwGA1UEAwwVSHVhd2VpIENCRyBSb290IENBIEcyMHYw +EAYHKoZIzj0CAQYFK4EEACIDYgAEWidkGnDSOw3/HE2y2GHl+fpWBIa5S+IlnNrs +GUvwC1I2QWvtqCHWmwFlFK95zKXiM8s9yV3VVXh7ivN8ZJO3SC5N1TCrvB2lpHMB +wcz4DA0kgHCMm/wDec6kOHx1xvCRo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUo45a9Vq8cYwqaiVyfkiS4pLcIAAwCgYIKoZI +zj0EAwMDZwAwZAIwMypeB7P0IbY7c6gpWcClhRznOJFj8uavrNu2PIoz9KIqr3jn +BlBHJs0myI7ntYpEAjBbm8eDMZY5zq5iMZUC6H7UzYSix4Uy1YlsLVV738PtKP9h +FTjgDHctXJlC5L7+ZDY= +-----END CERTIFICATE----- diff --git a/test/build.bat b/test/build.bat new file mode 100644 index 0000000..caec57c --- /dev/null +++ b/test/build.bat @@ -0,0 +1,5 @@ +$env:GOOS="windows" +go build -o ./test/copyAccount.exe ./test/test.go +go build -o ./test/rechargeAccount.exe ./test/recharge.go + +go build -o ./test/rechargeAccount100.exe ./test/recharge.go \ No newline at end of file diff --git a/test/recharge.go b/test/recharge.go new file mode 100644 index 0000000..546fd45 --- /dev/null +++ b/test/recharge.go @@ -0,0 +1,126 @@ +package main + +import ( + "crypto/md5" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" +) + +const TIME_FORMAT = "2006-01-02 15:04:05" + +func main() { + var unitId string + + fmt.Print("unitId账号数据输入 : ") + _, err := fmt.Scan(&unitId) // 读取输入并赋值给变量 + if err != nil { + fmt.Println("输入错误:", err) + return + } + var config string + + fmt.Print("输入充值配置Id(101-106) : ") + _, err = fmt.Scan(&config) // 读取输入并赋值给变量 + if err != nil { + fmt.Println("输入错误:", err) + return + } + configs := map[string]string{ + "101": "6", + "102": "30", + "103": "68", + "104": "198", + "105": "328", + "106": "648", + } + if configs[config] == "" { + fmt.Println("输入错误, 请输入(101-106)的id:") + _, err = fmt.Scan(&config) // 读取输入并赋值给变量 + if err != nil { + fmt.Println("输入错误:", err) + return + } + return + } + + tradeNo := fmt.Sprintf("GM%v%v", config, time.Now().UnixMilli()) + body := map[string]string{"unitId": unitId, "server": "1", "channel": "000000000000", "rechargeId": config, + "tradeStatus": "SUCCESS", "externalTradeNo": tradeNo, "totalAmount": configs[config], "tradeNo": tradeNo, + "buyerId": "", "createTime": time.Now().Format(TIME_FORMAT), "payedTime": time.Now().Format(TIME_FORMAT)} + sendToServer(body) + + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + fmt.Println("操作完成... (输入 'ctrl-c' 退出)") + for { + select { + case <-ticker.C: + // 这里可以添加条件检查(如读取文件、网络请求等) + default: + // 非阻塞检查用户输入(需配合 goroutine) + } + } +} + +func addServerMd5(body map[string]string) string { + data := body["unitId"] + body["server"] + body["channel"] + body["rechargeId"] + + body["tradeStatus"] + body["externalTradeNo"] + body["totalAmount"] + body["tradeNo"] + body["buyerId"] + body["createTime"] + + body["payedTime"] + "b93ac9c70a071c537542f6fe95cf4d67" + sign := fmt.Sprintf("%x", md5.Sum([]byte(data))) + log.Printf("data: %s, md5: %s", data, sign) + return fmt.Sprintf("%x", md5.Sum([]byte(data))) +} + +type TradeRes struct { + T string `json:"_t"` + Error int `json:"Error"` + Message string `json:"Message"` +} + +func sendToServer(body map[string]string) string { + //url := "http://192.168.2.100:30300/Recharge" + url := "http://127.0.0.1:30300/Recharge" + + log.Printf("notify========== url: %s", url) + body["sign"] = addServerMd5(body) + buf, err := json.Marshal(body) + if err != nil { + fmt.Println(err) + return err.Error() + } + reqBody := strings.NewReader(string(buf)) + log.Printf("notify========== url: %s, reqBody: %v", url, reqBody) + reqt, _ := http.NewRequest("GET", url, reqBody) + + response, err := http.DefaultClient.Do(reqt) + if err != nil { + log.Printf("notify========== server callback Error %s", err.Error()) + return err.Error() + } + // 这里要格式化再输出,因为 ReadAll 返回的是字节切片 + log.Printf("notify========== server callback StatusCode %d", response.StatusCode) + defer response.Body.Close() + bytes, err := io.ReadAll(response.Body) + if err != nil { + return err.Error() + } + // 这里要格式化再输出,因为 ReadAll 返回的是字节切片 + log.Printf("notify========== server callback Body %s", bytes) + if response.StatusCode == 200 { + var tradeRes TradeRes + err = json.Unmarshal(bytes, &tradeRes) + if tradeRes.Error == 400000 { + return "SUCCESS" + } else { + return tradeRes.Message + } + } + + return fmt.Sprint(response.StatusCode) +} diff --git a/test/test.go b/test/test.go index 76c54b0..f4827d8 100644 --- a/test/test.go +++ b/test/test.go @@ -11,17 +11,18 @@ import ( "log" "os" "os/exec" + "strconv" "strings" "time" ) -const redisAdress = "119.29.144.246:4114" +const redisAdress = "test.taoyuanjilogin.com:4114" const redisPass = "peach" -const linkAccount = "mongodb://root:peach123@192.168.2.100:27017/PeachValley" -const link = "mongodb://127.0.0.1:27017/PeachValley" +const linkAccount = "mongodb://peach:peach@test.taoyuanjilogin.com:4113/PeachValley4" +const link = "mongodb://127.0.0.1:21017/PeachValley" -const notAccount = false +const hasAccount = true func main() { @@ -47,7 +48,7 @@ func main() { }) if model == "1" { - fmt.Printf("%s准备复制账号信息: \n", model) + fmt.Printf("%s准备下载账号信息: \n", model) copyUnit(ctx, rdb) } else { fmt.Printf("%s准备替换账号信息: \n", model) @@ -56,7 +57,7 @@ func main() { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() - fmt.Println("程序仍在运行...(输入 'ctrl-c' 退出)") + fmt.Println("操作完成... (输入 'ctrl-c' 退出)") for { select { case <-ticker.C: @@ -69,103 +70,226 @@ func main() { func copyUnit(ctx context.Context, rdb *redis.Client) { var destUid string - fmt.Print("输入需要复制的uid: ") + fmt.Print("输入需要下载的UniqueId: ") _, err := fmt.Scan(&destUid) // 读取输入并赋值给变量 if err != nil { fmt.Println("输入错误:", err) return } - fmt.Printf("正在复制账号: %s\n", destUid) + int64Uid, _ := strconv.ParseInt(destUid, 10, 64) + fmt.Printf("准备下载账号: %s\n", destUid) rdb.Do(ctx, "select", 0) destId, err := rdb.HGet(ctx, "public_id_for_uid", fmt.Sprint(destUid)).Result() if err != nil { - fmt.Println("public_id_for_uid: err", err) - return + fmt.Println("redis打开失败 无法搜索 _id, err", err) + fmt.Println("尝试通过数据库查查找账号_id。") + destFileName := "DestUnit" + destUid + ".json" + if hasAccount { + mongoexportCommand(ctx, destFileName, "Unit", int64Uid, linkAccount, 1) + } else { + mongoexportCommand(ctx, destFileName, "Unit", int64Uid, link, 1) + } + time.Sleep(5 * time.Second) + destId, _ = writeDestFile(destFileName) + if destId == "" { + fmt.Println("未找到需要下载的账号_id") + fmt.Print("请手动输入需要下载的账号_id: ") + _, err = fmt.Scan(&destId) // 读取输入并赋值给变量 + if err != nil { + fmt.Println("输入错误:", err) + return + } + } } if destId == "" { - fmt.Println("public_id_for_uid: value is nil") + fmt.Println("未找到需要下载的账号_id") return } - filename := "Unit.json" - storeFilename := "StoreComponent.json" - userDir := "./" + filename - storeDir := "./" + storeFilename - name := "mongoexport" - args := []string{} - args1 := []string{} - if notAccount { - args = append(args, fmt.Sprintf("--uri=\"%s\"", link)) - args1 = append(args1, fmt.Sprintf("--uri=\"%s\"", link)) + int64Id, _ := strconv.ParseInt(destId, 10, 64) + filename := "Unit" + destUid + ".json" + storeFilename := "StoreComponent" + destUid + ".json" + deadFilename := "DeadVillagerManagerComponent" + destUid + ".json" + decorationFilename := "DecorationManagerComponent" + destUid + ".json" + log.Printf("download Unit destid: %d, destUniqueId: %d", int64Id, int64Uid) + g.Try(ctx, func(ctx context.Context) { + if hasAccount { + mongoexportCommand(ctx, filename, "Unit", int64Uid, linkAccount, 1) + mongoexportCommand(ctx, storeFilename, "StoreComponent", int64Id, linkAccount, 2) + mongoexportCommand(ctx, deadFilename, "DeadVillagerManagerComponent", int64Id, linkAccount, 2) + mongoexportCommand(ctx, decorationFilename, "DecorationManagerComponent", int64Id, linkAccount, 2) + } else { + mongoexportCommand(ctx, filename, "Unit", int64Uid, link, 1) + mongoexportCommand(ctx, storeFilename, "StoreComponent", int64Id, link, 2) + mongoexportCommand(ctx, deadFilename, "DeadVillagerManagerComponent", int64Id, link, 2) + mongoexportCommand(ctx, decorationFilename, "DecorationManagerComponent", int64Id, link, 2) + } + }) + +} + +func writeDestFile(fileName string) (destId string, err error) { + destUserDir := "./" + fileName + writeByte, err1 := os.ReadFile(destUserDir) + if err1 != nil { + return "", err1 + } + + var v1 map[string]interface{} + if strings.HasPrefix(string(writeByte), "[") { + user := []interface{}{} + json.Unmarshal(writeByte, &user) + v1 = user[0].(map[string]interface{}) } else { - args = append(args, fmt.Sprintf("--uri=\"%s\"", linkAccount)) - args = append(args, "--authenticationDatabase=admin") - args1 = append(args1, fmt.Sprintf("--uri=\"%s\"", linkAccount)) - args1 = append(args1, "--authenticationDatabase=admin") + json.Unmarshal(writeByte, &v1) } - args = append(args, "--collection=Unit") - args = append(args, "--jsonArray") - args = append(args, fmt.Sprintf("--out=\"%s\"", userDir)) - args = append(args, fmt.Sprintf("--query=\"{\\\"UniqueId\\\":%s}\"", destUid)) - args = append(args, "--jsonFormat=canonical") - args = append(args, "--type=json") - args1 = append(args1, "--collection=StoreComponent") - args1 = append(args1, "--jsonArray") - args1 = append(args1, fmt.Sprintf("--out=\"%s\"", storeDir)) - args1 = append(args1, fmt.Sprintf("--query=\"{\\\"_id\\\":%s}\"", destId)) - args1 = append(args1, "--jsonFormat=canonical") - args1 = append(args1, "--type=json") - - cmdCommand(ctx, name, args...) - cmdCommand(ctx, name, args1...) + if _, ok := v1["_id"]; ok { + uid := v1["_id"].(map[string]interface{}) + destId = fmt.Sprint(uid["$numberLong"]) + } + err = os.Remove(destUserDir) + if err != nil { + log.Printf("%s os.Remove failed with %s\n", destUserDir, err) + return + } + return +} + +func mongoexportCommand(ctx context.Context, filename string, collection string, uid int64, url string, qType int) { + g.Try(ctx, func(ctx context.Context) { + args := []string{} + dir := "./" + filename + args = append(args, fmt.Sprintf("--uri=\"%s\"", url)) + args = append(args, fmt.Sprintf("--authenticationDatabase=admin")) + args = append(args, fmt.Sprintf("--collection=%s", collection)) + args = append(args, "--jsonArray") + args = append(args, fmt.Sprintf("--out=\"%s\"", dir)) + if qType == 1 { + args = append(args, fmt.Sprintf("--query=\"{\\\"UniqueId\\\":%d}\"", uid)) + } else { + args = append(args, fmt.Sprintf("--query=\"{\\\"_id\\\":%d}\"", uid)) + } + + args = append(args, "--jsonFormat=canonical") + args = append(args, "--type=json") + err := cmdCommand(ctx, "mongoexport", args...) + if err != nil { + log.Printf(" %s cmd.Run() args failed with %s\n", dir, err) + return + } + }) } func updateUnit(ctx context.Context, rdb *redis.Client) { - var filename string - var storeFilename string var destUid string - fmt.Print("输入需要修改的uid: ") + var srcUid string + fmt.Print("输入需要修改的UniqueId: ") _, err := fmt.Scan(&destUid) // 读取输入并赋值给变量 if err != nil { fmt.Println("输入错误:", err) return } - fmt.Print("输入需要复制的unit文件名(需加后缀): ") - _, err = fmt.Scan(&filename) // 读取输入并赋值给变量 + fmt.Print("输入需要导入的UniqueId: ") + _, err = fmt.Scan(&srcUid) // 读取输入并赋值给变量 if err != nil { fmt.Println("输入错误:", err) - return } - fmt.Printf("正在复制账号: %s\n", filename) - fmt.Print("输入需要复制的store文件名(需加后缀): ") - _, err = fmt.Scan(&storeFilename) // 读取输入并赋值给变量 + var filename = "Unit" + srcUid + ".json" + var storeFilename = "StoreComponent" + srcUid + ".json" + var deadFilename = "DeadVillagerManagerComponent" + srcUid + ".json" + var decorationFilename = "DecorationManagerComponent" + srcUid + ".json" + fmt.Printf("导入账号Unit信息: %s\n", filename) + fmt.Printf("导入账号store信息: %s\n", storeFilename) + fmt.Printf("导入账号deadVillager信息: %s\n", deadFilename) + fmt.Printf("导入账号decoration信息: %s\n", decorationFilename) + fmt.Printf("请按照以上命名修改需要导入的文件放到当前文件夹下 \n") + time.Sleep(2 * time.Second) + fmt.Printf("输入任意字符,按回车键继续... :") + pause1 := "" + _, err = fmt.Scan(&pause1) if err != nil { fmt.Println("输入错误:", err) return } - fmt.Printf("正在复制账号: %s\n", storeFilename) - fmt.Printf("现在修改账号: %s\n", destUid) + fmt.Printf("准备修改账号: %s\n", destUid) + + int64Uid, _ := strconv.ParseInt(destUid, 10, 64) destId, err := rdb.HGet(ctx, "public_id_for_uid", fmt.Sprint(destUid)).Result() if err != nil { - fmt.Println("public_id_for_uid: err", err) - return + fmt.Println("redis无法打开 无法搜索 _id, err:", err) + fmt.Println("尝试通过数据库查查找账号_id。") + destFileName := "DestUnit" + destUid + ".json" + if hasAccount { + mongoexportCommand(ctx, destFileName, "Unit", int64Uid, linkAccount, 1) + } else { + mongoexportCommand(ctx, destFileName, "Unit", int64Uid, link, 1) + } + time.Sleep(5 * time.Second) + destId, _ = writeDestFile(destFileName) + if destId == "" { + fmt.Print("请手动输入需要修改的账号_id: ") + _, err = fmt.Scan(&destId) // 读取输入并赋值给变量 + if err != nil { + fmt.Println("输入错误:", err) + return + } + } } if destId == "" { - fmt.Println("public_id_for_uid: value is nil") + fmt.Println("未找到需要修改的账号_id") return } - userDir := "./Unit" + fmt.Sprint(destId) + ".json" - srcUserDir := "./" + filename + int64Id, _ := strconv.ParseInt(destId, 10, 64) + //intUid, _ := strconv.Atoi(destUid) + userDir := "./Unit" + destId + fmt.Sprint(time.Now().Unix()) + ".json" + srcId, err1 := writeFile1(int64Id, userDir, filename, int64Uid) + if err1 != nil { + err = err1 + return + } + log.Printf("copyUnit srcid: %s, destid: %d, DestName: %d", srcId, int64Id, int64Uid) + userStoreDir := "./StoreComponent" + destUid + fmt.Sprint(time.Now().Unix()) + ".json" + err = writeFile(int64Id, srcId, userStoreDir, storeFilename) + if err != nil { + return + } + userDeadDir := "./DeadVillagerManagerComponent" + destUid + fmt.Sprint(time.Now().Unix()) + ".json" + err = writeFile(int64Id, srcId, userDeadDir, deadFilename) + if err != nil { + return + } + userDecorationDir := "./DecorationManagerComponent" + destUid + fmt.Sprint(time.Now().Unix()) + ".json" + err = writeFile(int64Id, srcId, userDecorationDir, decorationFilename) + if err != nil { + return + } + g.Try(ctx, func(ctx context.Context) { + if hasAccount { + mongoimportCommand(ctx, "Unit", userDir, linkAccount) + mongoimportCommand(ctx, "StoreComponent", userStoreDir, linkAccount) + mongoimportCommand(ctx, "DeadVillagerManagerComponent", userDeadDir, linkAccount) + mongoimportCommand(ctx, "DecorationManagerComponent", userDecorationDir, linkAccount) + } else { + mongoimportCommand(ctx, "Unit", userDir, link) + mongoimportCommand(ctx, "StoreComponent", userStoreDir, link) + mongoimportCommand(ctx, "DeadVillagerManagerComponent", userDeadDir, link) + mongoimportCommand(ctx, "DecorationManagerComponent", userDecorationDir, link) + } + }) + + fmt.Println("CopyUnit GetRoleDelta", destId) +} +func writeFile1(destId int64, userDir string, fileName string, destName int64) (srcId string, err error) { + srcUserDir := "./" + fileName writeByte, err1 := os.ReadFile(srcUserDir) if err1 != nil { - return + return "", err1 } srcUid := "" - srcId := "" var v1 map[string]interface{} if strings.HasPrefix(string(writeByte), "[") { user := []interface{}{} @@ -180,83 +304,81 @@ func updateUnit(ctx context.Context, rdb *redis.Client) { srcUid = fmt.Sprint(UniqueId["$numberInt"]) } if _, ok := v1["_id"]; ok { - UniqueId := v1["_id"].(map[string]interface{}) - srcId = fmt.Sprint(UniqueId["$numberLong"]) + uid := v1["_id"].(map[string]interface{}) + srcId = fmt.Sprint(uid["$numberLong"]) } - userStoreDir := "./StoreComponent" + fmt.Sprint(destUid) + ".json" - srcStoreDir := "./" + storeFilename - writeStoreByte, err1 := os.ReadFile(srcStoreDir) - if err1 != nil { - return - } - - var v2 map[string]interface{} - if strings.HasPrefix(string(writeStoreByte), "[") { - user := []interface{}{} - json.Unmarshal(writeStoreByte, &user) - v2 = user[0].(map[string]interface{}) - } else { - json.Unmarshal(writeStoreByte, &v2) - } - - //log.Printf("copyUnit string(writeByte): %s ", string(writeByte)) - log.Printf("copyUnit srcid: %s, uid: %s, destid: %s, uid: %s", srcId, srcUid, destId, destUid) - //log.Printf("copyUnit string(writeByte): %s ", gjson.MustEncodeString(user)) + log.Printf("copyUnit srcid: %s, uid: %s, destId: %d, destName: %d", srcId, srcUid, destId, destName) if srcId == "" || srcUid == "" { return } writeUnitString := strings.ReplaceAll(gjson.MustEncodeString(v1), srcId, fmt.Sprint(destId)) - writeStoreString := strings.ReplaceAll(gjson.MustEncodeString(v2), srcId, fmt.Sprint(destId)) - //log.Printf("copyUnit writeString: %s,", writeString) - writeUnitString = ReplaceLastOccurrence(writeUnitString, srcUid, fmt.Sprint(destUid)) - //log.Printf("copyUnit writeString: %s,", writeString) + writeUnitString = ReplaceLastOccurrence(writeUnitString, srcUid, fmt.Sprint(destName)) if err = os.WriteFile(userDir, []byte(writeUnitString), 0666); err != nil { log.Println(err) return } - if err = os.WriteFile(userStoreDir, []byte(writeStoreString), 0666); err != nil { - log.Println(err) - return + + return +} + +func writeFile(destId int64, srcId string, userDir string, fileName string) (err error) { + srcDir := "./" + fileName + writeByte, err1 := os.ReadFile(srcDir) + if err1 != nil { + return err1 } - name := "mongoimport" - args := []string{} - args1 := []string{} - if notAccount { - args = append(args, fmt.Sprintf("--uri=\"%s\"", link)) - args1 = append(args1, fmt.Sprintf("--uri=\"%s\"", link)) + var v map[string]interface{} + if strings.HasPrefix(string(writeByte), "[") { + user := []interface{}{} + json.Unmarshal(writeByte, &user) + if len(user) != 0 { + v = user[0].(map[string]interface{}) + } } else { - args = append(args, fmt.Sprintf("--uri=\"%s\"", linkAccount)) - args = append(args, "--authenticationDatabase=admin") - args1 = append(args1, fmt.Sprintf("--uri=\"%s\"", linkAccount)) - args1 = append(args1, "--authenticationDatabase=admin") + json.Unmarshal(writeByte, &v) + } + if len(v) != 0 { + writeString := strings.ReplaceAll(gjson.MustEncodeString(v), srcId, fmt.Sprint(destId)) + if err = os.WriteFile(userDir, []byte(writeString), 0666); err != nil { + log.Println(err) + return + } } - args = append(args, "--collection=Unit") - args = append(args, fmt.Sprintf("--file=\"%s\"", userDir)) + return +} + +func mongoimportCommand(ctx context.Context, collection, dir, url string) { + args := []string{} + args = append(args, fmt.Sprintf("--uri=\"%s\"", url)) + args = append(args, fmt.Sprintf("--authenticationDatabase=admin")) + args = append(args, fmt.Sprintf("--collection=%s", collection)) + args = append(args, fmt.Sprintf("--file=\"%s\"", dir)) args = append(args, "--type=json") args = append(args, "--mode=upsert") - args1 = append(args1, "--collection=StoreComponent") - args1 = append(args1, fmt.Sprintf("--file=\"%s\"", userStoreDir)) - args1 = append(args1, "--type=json") - args1 = append(args1, "--mode=upsert") - cmdCommand(ctx, name, args...) - cmdCommand(ctx, name, args1...) - cmdCommand(ctx, "del", userDir) - cmdCommand(ctx, "del", userStoreDir) + err := cmdCommand(ctx, "mongoimport", args...) + if err != nil { + log.Printf("%s cmd.Run() failed with %s\n", dir, err) + return + } - fmt.Println("CopyUnit GetRoleDelta", destId) + err = os.Remove(dir) + if err != nil { + log.Printf("%s os.Remove failed with %s\n", dir, err) + return + } } -func cmdCommand(ctx context.Context, name string, args ...string) { - g.Try(ctx, func(ctx context.Context) { +func cmdCommand(ctx context.Context, name string, args ...string) (err error) { + err = g.Try(ctx, func(ctx context.Context) { cmd := exec.Command(name, args...) var stdout, stderr bytes.Buffer cmd.Stdout = &stdout // 标准输出 cmd.Stderr = &stderr // 标准错误 - err := cmd.Run() + err = cmd.Run() if err != nil { log.Printf("cmd.Run() failed with %s\n", err) return @@ -264,6 +386,7 @@ func cmdCommand(ctx context.Context, name string, args ...string) { outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes()) fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr) }) + return } // ReplaceLastOccurrence 替换最后一个匹配的子字符串