From 848827482f54456ca45726b6f7c095135b17eee1 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 25 Apr 2026 08:30:30 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=93=E6=89=8B?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=93=8D=E5=BA=94=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/logic/player/getPlayerLogic.go | 84 +++++++++++++++++-- .../rpc/internal/logic/getPlayersByIdLogic.go | 1 + deploy/dev/test_all_apis.py | 20 ++++- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/app/player/api/internal/logic/player/getPlayerLogic.go b/app/player/api/internal/logic/player/getPlayerLogic.go index 1db5992..2a6d460 100644 --- a/app/player/api/internal/logic/player/getPlayerLogic.go +++ b/app/player/api/internal/logic/player/getPlayerLogic.go @@ -5,13 +5,16 @@ package player import ( "context" + "encoding/json" "errors" - "juwan-backend/app/player/rpc/playerservice" + "strconv" + "time" "juwan-backend/app/player/api/internal/svc" "juwan-backend/app/player/api/internal/types" + "juwan-backend/app/player/rpc/playerservice" + "juwan-backend/app/users/rpc/usercenter" - "github.com/jinzhu/copier" "github.com/zeromicro/go-zero/core/logx" ) @@ -31,18 +34,83 @@ func NewGetPlayerLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetPlay } func (l *GetPlayerLogic) GetPlayer(req *types.GetPlayerReq) (resp *types.PlayerProfile, err error) { - player, err := l.svcCtx.PlayerRpc.GetPlayersById(l.ctx, &playerservice.GetPlayersByIdReq{ + playerResp, err := l.svcCtx.PlayerRpc.GetPlayersById(l.ctx, &playerservice.GetPlayersByIdReq{ Id: req.Id, }) if err != nil { logx.Errorf("GetPlayerLogic.GetPlayers err: %v", err) return nil, errors.New("failed to get player details") } - resp = &types.PlayerProfile{} - err = copier.Copy(resp, &player) - if err != nil { - logx.Errorf("copier.Copy err: %v", err) - return nil, errors.New("copier.Copy err") + player := playerResp.GetPlayers() + if player == nil { + return nil, errors.New("player not found") } + + resp = &types.PlayerProfile{ + Id: player.Id, + Rating: player.Rating, + TotalOrders: player.TotalOrders, + Status: player.Status, + Gender: player.Gender, + Services: []types.PlayerService{}, + Tags: append([]string{}, player.Tags...), + } + + games := make([]string, 0, len(player.Games)) + for _, gameID := range player.Games { + games = append(games, strconv.FormatInt(gameID, 10)) + } + resp.Games = games + + if player.ShopId != 0 { + resp.ShopId = strconv.FormatInt(player.ShopId, 10) + } + + usersResp, err := l.svcCtx.UserRpc.GetUsersByIds(l.ctx, &usercenter.GetUsersByIdsReq{Ids: []int64{player.UserId}}) + if err != nil { + return nil, err + } + if len(usersResp.Users) > 0 { + u := usersResp.Users[0] + verificationStatus := map[string]string{} + if u.VerificationStatus != "" { + _ = json.Unmarshal([]byte(u.VerificationStatus), &verificationStatus) + } + resp.User = types.UserProfile{ + Id: strconv.FormatInt(u.Id, 10), + Username: u.Username, + Nickname: u.Nickname, + Avatar: u.Avatar, + Role: u.CurrentRole, + VerifiedRoles: append([]string{}, u.VerifiedRoles...), + VerificationStatus: verificationStatus, + Phone: u.Phone, + Bio: u.Bio, + CreatedAt: time.Unix(u.CreatedAt, 0).Format(time.DateTime), + } + } + + svcResp, svcErr := l.svcCtx.PlayerRpc.SearchPlayerServices(l.ctx, &playerservice.SearchPlayerServicesReq{ + PlayerId: player.Id, + Limit: 100, + }) + if svcErr != nil { + logx.Errorf("GetPlayer SearchPlayerServices player=%d err: %v", player.Id, svcErr) + } else { + for _, s := range svcResp.PlayerServices { + resp.Services = append(resp.Services, types.PlayerService{ + Id: s.Id, + PlayerId: s.PlayerId, + GameId: s.GameId, + Title: s.Title, + Description: s.Description, + Price: s.Price, + Unit: s.Unit, + RankRange: s.RankRange, + Availability: s.Availability, + }) + } + } + return } diff --git a/app/player/rpc/internal/logic/getPlayersByIdLogic.go b/app/player/rpc/internal/logic/getPlayersByIdLogic.go index 959bd20..6896eee 100644 --- a/app/player/rpc/internal/logic/getPlayersByIdLogic.go +++ b/app/player/rpc/internal/logic/getPlayersByIdLogic.go @@ -36,6 +36,7 @@ func (l *GetPlayersByIdLogic) GetPlayersById(in *pb.GetPlayersByIdReq) (*pb.GetP Id: player.ID, UserId: player.UserID, Status: player.Status, + Gender: player.Gender, Rating: player.Rating.InexactFloat64(), TotalOrders: int64(player.TotalOrders), CompletedOrders: int64(player.CompletedOrders), diff --git a/deploy/dev/test_all_apis.py b/deploy/dev/test_all_apis.py index 40537b3..5891f6c 100644 --- a/deploy/dev/test_all_apis.py +++ b/deploy/dev/test_all_apis.py @@ -791,7 +791,12 @@ def phase6_player(s: Session, game_id): report(f"GET /players/{player_id}", code, body) report_check( f"GET /players/{player_id} returns initialized online player", - code == 200 and same_id(body.get("id"), player_id) and body.get("status") == "online", + code == 200 + and same_id(body.get("id"), player_id) + and body.get("status") == "online" + and body.get("gender") is True + and as_int((body.get("user") or {}).get("id")) > 0 + and isinstance(body.get("services"), list), body, ) @@ -860,6 +865,19 @@ def phase6_player(s: Session, game_id): body, ) + code, body, _ = s.get(f"{GATEWAY}/api/v1/players/{player_id}") + report(f"GET /players/{player_id} (after service update)", code, body) + service = find_item_by_id(body.get("services") or [], service_id) + report_check( + f"GET /players/{player_id} includes updated service", + code == 200 + and body.get("gender") is True + and bool(service) + and service.get("title") == "Updated Service" + and as_decimal(service.get("price")) == Decimal("60"), + body, + ) + if player_id: code, body, _ = s.get(f"{GATEWAY}/api/v1/players/{player_id}/services") report(f"GET /players/{player_id}/services", code, body) From e290e7908a74a42af1b89abe96488f57497bc408 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 25 Apr 2026 08:31:00 +0800 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=E5=A2=9E=E5=BC=BA=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=88=9B=E5=BB=BA=E9=87=91=E9=A2=9D=E6=96=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/dev/test_all_apis.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/dev/test_all_apis.py b/deploy/dev/test_all_apis.py index 5891f6c..cdc28b5 100644 --- a/deploy/dev/test_all_apis.py +++ b/deploy/dev/test_all_apis.py @@ -1231,7 +1231,7 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s and same_id(order_obj.get("playerId"), player_id) and same_id(order_obj.get("shopId"), shop_id) and order_obj.get("status") == "pending_payment" - and as_decimal(order_obj.get("totalPrice")) == Decimal("50") + and as_decimal(order_obj.get("totalPrice")) == Decimal("60") and order_obj.get("note") == "test order", body, ) @@ -1266,7 +1266,9 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s "created order detail matches participants", same_id(body.get("consumerId"), consumer_user_id) and same_id(body.get("playerId"), player_id) - and same_id(body.get("shopId"), shop_id), + and same_id(body.get("shopId"), shop_id) + and as_decimal(body.get("totalPrice")) == Decimal("60") + and body.get("note") == "test order", body, ) From 7f2d3f7d0e20973087b114bf4f43aec9c97fdbb2 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 25 Apr 2026 08:31:29 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E7=A4=BE=E5=8C=BA?= =?UTF-8?q?=E5=85=AC=E5=BC=80=E6=8E=A5=E5=8F=A3=E5=8F=AF=E9=80=89=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/dev/envoy.yaml | 10 ++++++++++ deploy/k8s/envoy/envoy.yaml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/deploy/dev/envoy.yaml b/deploy/dev/envoy.yaml index 5a21fc3..a18a89c 100644 --- a/deploy/dev/envoy.yaml +++ b/deploy/dev/envoy.yaml @@ -651,6 +651,11 @@ static_resources: headers: - name: ":method" exact_match: GET + requires: + requires_any: + requirements: + - provider_name: juwan_user_jwt + - allow_missing: {} - match: safe_regex: google_re2: {} @@ -663,6 +668,11 @@ static_resources: headers: - name: ":method" exact_match: GET + requires: + requires_any: + requirements: + - provider_name: juwan_user_jwt + - allow_missing: {} - match: prefix: /api/v1/reviews headers: diff --git a/deploy/k8s/envoy/envoy.yaml b/deploy/k8s/envoy/envoy.yaml index 27e6bb1..b330acc 100644 --- a/deploy/k8s/envoy/envoy.yaml +++ b/deploy/k8s/envoy/envoy.yaml @@ -449,6 +449,11 @@ data: headers: - name: ":method" exact_match: "GET" + requires: + requires_any: + requirements: + - provider_name: juwan_user_jwt + - allow_missing: {} - match: prefix: "/api/v1/reviews" headers: @@ -487,6 +492,11 @@ data: headers: - name: ":method" exact_match: "GET" + requires: + requires_any: + requirements: + - provider_name: juwan_user_jwt + - allow_missing: {} - match: safe_regex: google_re2: {} From 813b9de07eda7fd79f58d7046bbed274dd485932 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 25 Apr 2026 13:23:32 +0800 Subject: [PATCH 4/5] =?UTF-8?q?test:=20=E8=A1=A5=E5=85=85=20dev=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=B4=9F=E5=90=91=E6=B5=81=E7=A8=8B=E6=96=AD?= =?UTF-8?q?=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/dev/test_all_apis.py | 185 ++++++++++++++++++++++++++++++++++++ 1 file changed, 185 insertions(+) diff --git a/deploy/dev/test_all_apis.py b/deploy/dev/test_all_apis.py index cdc28b5..24d0ecc 100644 --- a/deploy/dev/test_all_apis.py +++ b/deploy/dev/test_all_apis.py @@ -50,6 +50,28 @@ def same_id(left, right): return str(left) == str(right) +def body_text(body): + if not isinstance(body, dict): + return str(body) + if "_raw" in body: + return str(body["_raw"]) + if "message" in body: + return str(body["message"]) + if "_error" in body: + return str(body["_error"]) + return json.dumps(body, ensure_ascii=False) + + +def has_error_text(body, *parts): + text = body_text(body) + return all(part in text for part in parts) + + +def report_rejected(name, status_code, body, expect_status=(400, 403, 500)): + report(name, status_code, body, expect_status=expect_status) + report_check(f"{name} returns error body", bool(body_text(body)), body) + + def pick_items(body): if isinstance(body.get("items"), list): return body["items"] @@ -572,6 +594,20 @@ def phase3_admin_and_verification( body, ) + code, body, _ = s_reject.post( + f"{GATEWAY}/api/v1/users/me/switch-role", + json_body={"role": "owner"}, + headers=csrf_reject, + ) + report_rejected("POST /users/me/switch-role (rejected owner)", code, body) + code, body, _ = s_reject.get(f"{GATEWAY}/api/v1/users/me") + report("GET /users/me (after rejected owner switch)", code, body) + report_check( + "rejected owner switch does not change current role", + body.get("role") == "consumer", + body, + ) + code, body, _ = s_user.get(f"{GATEWAY}/api/v1/users/me") report("GET /users/me (after approval)", code, body) verified_roles = body.get("verifiedRoles") or [] @@ -1094,6 +1130,40 @@ def phase7_shop( body, ) + code, body, _ = s_invited_player.post( + f"{GATEWAY}/api/v1/shops/{shop_id}/invitations", + json_body={"playerId": invited_player_id}, + headers=s_invited_player.csrf_headers(), + ) + report_rejected( + f"POST /shops/{shop_id}/invitations (non-owner)", + code, + body, + ) + + if invitation_id: + code, body, _ = s_owner.post( + f"{GATEWAY}/api/v1/shops/invitations/{invitation_id}/accept", + json_body={}, + headers=csrf, + ) + report_rejected( + f"POST /shops/invitations/{invitation_id}/accept (wrong player)", + code, + body, + ) + code, body, _ = s_owner.get( + f"{GATEWAY}/api/v1/shops/{shop_id}/invitations", + ) + report(f"GET /shops/{shop_id}/invitations (after wrong accept)", code, body) + pending_invitation = find_item_by_id(pick_items(body), invitation_id) + report_check( + "wrong invitation accept keeps status pending", + bool(pending_invitation) + and pending_invitation.get("status") == "pending", + body, + ) + s_invited_player.post( f"{GATEWAY}/api/v1/users/me/switch-role", json_body={"role": "player"}, @@ -1272,6 +1342,22 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s body, ) + code, body, _ = s_actor.post( + f"{GATEWAY}/api/v1/orders/{order_id}/accept", + json_body={}, + headers=s_actor.csrf_headers(), + ) + report_rejected(f"POST /orders/{order_id}/accept (before pay)", code, body) + check_order_status(s_consumer, order_id, "pending_payment", "after early accept") + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close", + json_body={}, + headers=csrf, + ) + report_rejected(f"POST /orders/{order_id}/confirm-close (before close request)", code, body) + check_order_status(s_consumer, order_id, "pending_payment", "after early confirm-close") + code, body, _ = s_consumer.post( f"{GATEWAY}/api/v1/orders/{order_id}/pay", json_body={}, @@ -1280,6 +1366,22 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s report(f"POST /orders/{order_id}/pay", code, body) check_order_status(s_consumer, order_id, "pending_accept", "after pay") + code, body, _ = s_actor.post( + f"{GATEWAY}/api/v1/orders/{order_id}/request-close", + json_body={}, + headers=s_actor.csrf_headers(), + ) + report_rejected(f"POST /orders/{order_id}/request-close (before accept)", code, body) + check_order_status(s_consumer, order_id, "pending_accept", "after early request-close") + + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/pay", + json_body={}, + headers=csrf, + ) + report_rejected(f"POST /orders/{order_id}/pay (second time)", code, body) + check_order_status(s_consumer, order_id, "pending_accept", "after duplicate pay") + code, body, _ = s_actor.post( f"{GATEWAY}/api/v1/orders/{order_id}/accept", json_body={}, @@ -1289,6 +1391,14 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s body = check_order_status(s_consumer, order_id, "in_progress", "after accept") report_check("accepted order has acceptedAt", bool(body.get("acceptedAt")), body) + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order_id}/confirm-close", + json_body={}, + headers=csrf, + ) + report_rejected(f"POST /orders/{order_id}/confirm-close (before request-close)", code, body) + check_order_status(s_consumer, order_id, "in_progress", "after early confirm-close") + code, body, _ = s_actor.post( f"{GATEWAY}/api/v1/orders/{order_id}/request-close", json_body={}, @@ -1322,6 +1432,15 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s body, ) + if order2_id: + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order2_id}/review", + json_body={"rating": 5, "content": "too early"}, + headers=csrf, + ) + report_rejected(f"POST /orders/{order2_id}/review (before completion)", code, body) + check_order_status(s_consumer, order2_id, "pending_payment", "after early review") + if order_id: code, body, _ = s_consumer.post( f"{GATEWAY}/api/v1/orders/{order_id}/reorder", @@ -1356,6 +1475,14 @@ def phase8_order(s_consumer: Session, s_actor: Session, player_id, service_id, s report(f"POST /orders/{order2_id}/cancel", code, body) check_order_status(s_consumer, order2_id, "cancelled", "second order after cancel") + code, body, _ = s_consumer.post( + f"{GATEWAY}/api/v1/orders/{order2_id}/pay", + json_body={}, + headers=csrf, + ) + report_rejected(f"POST /orders/{order2_id}/pay (after cancel)", code, body) + check_order_status(s_consumer, order2_id, "cancelled", "after cancelled pay") + code, body, _ = s_consumer.post( f"{GATEWAY}/api/v1/orders/paid", json_body={ @@ -1693,6 +1820,26 @@ def phase8e_search_and_favorites(s: Session, user_id, player_id, shop_id): skip("Favorites mutation flow", "Missing user or player id") return + code, body, _ = s.post( + f"{GATEWAY}/api/v1/favorites", + json_body={"targetType": "player", "targetId": "not-a-snowflake"}, + headers=s.csrf_headers(), + ) + report_rejected("POST /favorites (invalid targetId)", code, body) + code, body, _ = s.get(f"{GATEWAY}/api/v1/favorites") + report("GET /favorites (after invalid targetId)", code, body) + report_check( + "invalid favorite target is absent", + not bool( + find_item( + pick_items(body), + lambda item: item.get("targetType") == "player" + and same_id(item.get("targetId"), "not-a-snowflake"), + ) + ), + body, + ) + code, body, _ = s.post( f"{GATEWAY}/api/v1/favorites", json_body={"targetType": "player", "targetId": str(player_id)}, @@ -1797,6 +1944,35 @@ def phase9_wallet(s: Session): body, ) + code, body, _ = s.post( + f"{GATEWAY}/api/v1/wallet/withdraw", + json_body={"amount": "0", "method": "alipay"}, + headers=csrf, + ) + report_rejected("POST /wallet/withdraw (zero amount)", code, body) + code, body, _ = s.get(f"{GATEWAY}/api/v1/wallet/balance") + report("GET /wallet/balance (after zero withdraw)", code, body) + report_check( + "zero withdraw keeps balance unchanged", + as_decimal(body.get("balance")) == initial_balance, + body, + ) + + excessive_amount = initial_balance + Decimal("999999") + code, body, _ = s.post( + f"{GATEWAY}/api/v1/wallet/withdraw", + json_body={"amount": str(excessive_amount), "method": "alipay"}, + headers=csrf, + ) + report_rejected("POST /wallet/withdraw (insufficient balance)", code, body) + code, body, _ = s.get(f"{GATEWAY}/api/v1/wallet/balance") + report("GET /wallet/balance (after insufficient withdraw)", code, body) + report_check( + "insufficient withdraw keeps balance unchanged", + as_decimal(body.get("balance")) == initial_balance, + body, + ) + code, body, _ = s.post( f"{GATEWAY}/api/v1/wallet/topup", json_body={"amount": "100.00", "method": "alipay"}, @@ -2013,6 +2189,15 @@ def phase10_community(s: Session, user_id): def phase11_objectstory(s: Session): print("\n=== Phase 11: Objectstory (File) ===") + code, body, _ = s.post_multipart( + f"{GATEWAY}/api/v1/upload", + fields={"type": "post"}, + files={"file": (f"empty-{rand_str(4)}.txt", b"", "text/plain")}, + headers=s.csrf_headers(), + ) + report_rejected("POST /upload (empty file)", code, body) + report_check("empty upload does not return url", not body.get("url"), body) + code, body, _ = s.post_multipart( f"{GATEWAY}/api/v1/upload", fields={"type": "post"}, From c8b2a986a0507c5a70d391eaf355071608c4db72 Mon Sep 17 00:00:00 2001 From: zetaloop Date: Sat, 25 Apr 2026 19:19:31 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20docker=20=E6=94=AF=E6=8C=81=20ipv6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/dev/envoy.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deploy/dev/envoy.yaml b/deploy/dev/envoy.yaml index a18a89c..fa840bf 100644 --- a/deploy/dev/envoy.yaml +++ b/deploy/dev/envoy.yaml @@ -3,8 +3,9 @@ static_resources: - name: ingress_http address: socket_address: - address: 0.0.0.0 + address: "::" port_value: 8080 + ipv4_compat: true filter_chains: - filters: - name: envoy.filters.network.http_connection_manager