From 7e8a2ebb5244a9f6897e839176e24785dfc0b9af Mon Sep 17 00:00:00 2001 From: win Date: Sat, 21 Feb 2026 21:33:19 +0800 Subject: [PATCH] feat: Add user spending dashboard, update database schema, and refine various API endpoints and service logic. --- .DS_Store | Bin .claude/plan/admin-update-user-mobile.md | 0 .gitignore | 0 .vercelignore | 0 BUG_FIX_REPORT.md | 0 CLAUDE.md | 0 Dockerfile | 0 Makefile | 0 README.md | 0 bindboxgame.json | 0 build/.DS_Store | Bin build/resources/.DS_Store | Bin build/resources/admin/assets/403-BdWuHcJA.svg | 0 build/resources/admin/assets/404-BzxNMzaO.svg | 0 build/resources/admin/assets/500-C-Ru4KUd.svg | 0 .../assets/EffectEditDialog-BmAJKxJl.css | 0 .../assets/EffectManagerDialog-DnvqZPdh.css | 0 .../admin/assets/LoginLeftView-BN4zi5Xi.css | 0 .../assets/LoginLeftView-BN4zi5Xi.css.gz | Bin .../admin/assets/TitleEditDialog-i9x-drPp.css | 0 .../assets/UserAssignmentDialog-BTSnDJ3n.css | 0 .../_plugin-vue_export-helper-BCo6x5W8.js | 0 .../admin/assets/about-project-DgJMbhc5.js | 0 .../resources/admin/assets/avatar-pR7-E1hl.js | 0 .../admin/assets/avatar10-Dom60BwY.js | 0 .../admin/assets/avatar6-6Evj8BB9.js | 0 .../admin/assets/avatar6-6Evj8BB9.js.gz | Bin build/resources/admin/assets/bg-DrCBEYh-.webp | Bin .../resources/admin/assets/card-D34vavgk.css | 0 .../admin/assets/category-search-BqILMF9x.css | 0 build/resources/admin/assets/col-yED17g82.css | 0 .../admin/assets/col-yED17g82.css.gz | Bin .../assets/date-picker-panel-Dxdk0yRA.css | 0 .../assets/date-picker-panel-Dxdk0yRA.css.gz | Bin .../admin/assets/dialog-2KKj2Euo.css | 0 .../admin/assets/el-alert-G57rL0jl.css | 0 .../admin/assets/el-avatar-BmRr_O8d.css | 0 .../admin/assets/el-button-CDqfIFiK.css | 0 .../admin/assets/el-button-CDqfIFiK.css.gz | Bin .../admin/assets/el-card-fwQOLwdi.css | 0 .../admin/assets/el-checkbox-DIj50LEB.css | 0 .../admin/assets/el-col-DD1Vn-Yu.css | 0 .../admin/assets/el-col-DD1Vn-Yu.css.gz | Bin .../assets/el-date-picker-panel-BhfPqR_w.css | 0 .../el-date-picker-panel-BhfPqR_w.css.gz | Bin .../assets/el-descriptions-item-o9ObloqJ.css | 0 .../admin/assets/el-dialog-DyK7vRzj.css | 0 .../admin/assets/el-divider-BUtF_RGI.css | 0 .../admin/assets/el-drawer-BhCnIJJ3.css | 0 .../assets/el-dropdown-item-11ZCvSOX.css | 0 .../admin/assets/el-form-item-BWkJzdQ_.css | 0 .../admin/assets/el-input-number-D6iOyBgb.css | 0 .../admin/assets/el-input-tPmZxDKr.css | 0 .../admin/assets/el-input-tPmZxDKr.css.gz | Bin .../admin/assets/el-option-BHqzF8z9.css | 0 .../admin/assets/el-overlay-Db7iXMEX.css | 0 .../admin/assets/el-pagination-BNQcHhjS.css | 0 .../admin/assets/el-popover-Cktl5fHm.css | 0 .../admin/assets/el-popper-D1i0e6ba.css | 0 .../admin/assets/el-progress-Dw9yTa91.css | 0 .../admin/assets/el-radio-BuDgLcOG.css | 0 .../admin/assets/el-radio-button-CSkroacn.css | 0 .../admin/assets/el-radio-group-BzMpJalG.css | 0 .../admin/assets/el-row-C6BJsxyy.css | 0 .../admin/assets/el-scrollbar-BWxh-h6K.css | 0 .../admin/assets/el-select-DdmnTlAY.css | 0 .../admin/assets/el-switch-B5lTGWdM.css | 0 .../admin/assets/el-table-column-CKoPG0Y8.css | 0 .../assets/el-table-column-CKoPG0Y8.css.gz | Bin .../admin/assets/el-tag-DljBBxJR.css | 0 .../admin/assets/el-tooltip-l0sNRNKZ.js | 0 .../admin/assets/el-upload-q8uObtwj.css | 0 .../admin/assets/el-upload-q8uObtwj.css.gz | Bin .../admin/assets/favicon-C1KazUkF.ico | Bin .../admin/assets/form-item-B4F-CS9A.css | 0 .../assets/grant-reward-dialog-D7-C3J8j.css | 0 .../resources/admin/assets/index-0rmuCejF.css | 0 .../resources/admin/assets/index-86w9PCiC.css | 0 .../resources/admin/assets/index-B7q-DPlS.css | 0 .../resources/admin/assets/index-BF_swEeW.css | 0 .../resources/admin/assets/index-BG9yZ82v.css | 0 .../resources/admin/assets/index-CDDDnorJ.css | 0 .../resources/admin/assets/index-CEpEmnur.css | 0 .../admin/assets/index-CEpEmnur.css.gz | Bin .../resources/admin/assets/index-CIZk353b.css | 0 .../resources/admin/assets/index-CTUKoMMr.css | 0 .../resources/admin/assets/index-CXjivOvk.css | 0 .../resources/admin/assets/index-CsQLNvm4.css | 0 .../resources/admin/assets/index-DVtb5Tyi.css | 0 .../resources/admin/assets/index-DhfpFd1U.css | 0 .../admin/assets/input-number-BXCadU-U.css | 0 .../admin/assets/lock_screen_1-CH_l421c.webp | Bin .../admin/assets/login_icon-C4TVlUS8.svg | 0 .../admin/assets/new-user-3ZQ25ksW.css | 0 .../admin/assets/order-funnel-CxvO8S8s.css | 0 .../admin/assets/points-economy-DgJMbhc5.js | 0 .../admin/assets/popper-kmEP6Jl6.css | 0 .../admin/assets/product-search-Cun_ywfi.css | 0 .../admin/assets/radio-group-DfFloULT.css | 0 build/resources/admin/assets/refs-Cw5r5QN8.js | 0 .../role-permission-dialog-BYEPGzFo.css | 0 .../admin/assets/scrollbar-C8iP3G9A.css | 0 build/resources/admin/assets/sd-C0PQtrty.png | Bin .../admin/assets/select-C2cjPkEh.css | 0 .../admin/assets/slider-CppPl5od.css | 0 .../resources/admin/assets/space-3oFudasq.css | 0 build/resources/admin/assets/tag-CtW1DIiB.css | 0 .../resources/admin/assets/token-DWNpOE8r.js | 0 build/resources/admin/assets/yd-BrGqJ6Cs.png | Bin build/resources/admin/index.html | 0 cmd/check_order/main.go | 0 cmd/debug_check_coupon_22/main.go | 0 cmd/debug_coupon_9209/main.go | 76 ---- cmd/debug_task_270/main.go | 0 cmd/debug_task_time/main.go | 97 +++++ cmd/fix_openid/main.go | 0 cmd/gormgen/README.md | 0 cmd/gormgen/main.go | 0 cmd/mfmt/README.md | 0 cmd/mfmt/main.go | 0 configs/.DS_Store | Bin configs/cert/apiclient_cert.pem | 0 configs/cert/apiclient_key.pem | 0 configs/cert/pub_key.pem | 0 configs/configs.go | 0 configs/constants.go | 0 configs/dev_configs.toml | 0 configs/fat_configs.toml | 0 configs/pro_configs.toml | 0 configs/uat_configs.toml | 0 deploy/.env | 0 deploy/project/docker-compose.yaml | 0 deploy/project/swagger.yaml | 0 docs/.DS_Store | Bin docs/docs.go | 0 docs/swagger.json | 0 docs/swagger.yaml | 0 .../ACCEPTANCE_优化抖音定时任务.md | 0 .../ALIGNMENT_优化抖音定时任务.md | 0 .../CONSENSUS_优化抖音定时任务.md | 0 .../DESIGN_优化抖音定时任务.md | 0 .../FINAL_优化抖音定时任务.md | 0 .../优化抖音定时任务/TASK_优化抖音定时任务.md | 0 .../优化抖音定时任务/TODO_优化抖音定时任务.md | 0 go.mod | 5 +- go.sum | 2 - internal/.DS_Store | Bin internal/alert/alert.go | 0 internal/api/activity/activities_app.go | 0 internal/api/activity/app.go | 0 internal/api/activity/draw_logs_app.go | 2 +- internal/api/activity/issue_choices_app.go | 0 internal/api/activity/issues_app.go | 0 internal/api/activity/lottery_app.go | 12 +- internal/api/activity/lottery_app_test.go | 0 internal/api/activity/lottery_helper.go | 77 +++- .../api/activity/lottery_result_order_app.go | 0 internal/api/activity/matching_game_app.go | 2 +- .../api/activity/matching_game_app_test.go | 0 internal/api/activity/matching_game_helper.go | 0 internal/api/activity/rewards_app.go | 0 internal/api/admin/activities_admin.go | 0 internal/api/admin/activity_categories.go | 0 .../api/admin/activity_commitment_admin.go | 0 internal/api/admin/admin.go | 0 internal/api/admin/auth_refresh.go | 0 internal/api/admin/banner.go | 0 internal/api/admin/blacklist_admin.go | 0 internal/api/admin/channels.go | 0 internal/api/admin/dashboard_activity.go | 26 +- internal/api/admin/dashboard_admin.go | 4 +- internal/api/admin/dashboard_admin_test.go | 0 internal/api/admin/dashboard_spending.go | 0 internal/api/admin/dashboard_user_spending.go | 250 ++++++++++++ internal/api/admin/douyin_orders_admin.go | 0 internal/api/admin/douyin_product_rewards.go | 0 .../api/admin/game_pass_packages_admin.go | 0 internal/api/admin/game_passes_admin.go | 0 internal/api/admin/ichiban_slots_admin.go | 0 internal/api/admin/issues_admin.go | 0 internal/api/admin/item_cards_admin.go | 0 internal/api/admin/livestream_admin.go | 0 internal/api/admin/livestream_stats.go | 0 internal/api/admin/login.go | 0 internal/api/admin/lottery_admin.go | 0 internal/api/admin/matching_audit_admin.go | 0 internal/api/admin/matching_cards_admin.go | 0 internal/api/admin/miniapp_qrcode.go | 0 internal/api/admin/miniapp_shipping_admin.go | 0 internal/api/admin/order_snapshot_admin.go | 0 internal/api/admin/pay_orders_admin.go | 94 +++++ internal/api/admin/pay_orders_export.go | 0 internal/api/admin/pay_reconcile_admin.go | 0 internal/api/admin/pay_refund_admin.go | 31 +- internal/api/admin/product_batch.go | 0 internal/api/admin/product_category_create.go | 0 internal/api/admin/product_create.go | 0 internal/api/admin/rewards_admin.go | 0 internal/api/admin/scheduled_config_admin.go | 0 internal/api/admin/shipping_orders_admin.go | 0 internal/api/admin/shipping_stats_admin.go | 0 internal/api/admin/system_configs_admin.go | 0 internal/api/admin/system_coupons.go | 0 internal/api/admin/system_menu.go | 0 internal/api/admin/system_recycle.go | 0 internal/api/admin/system_role.go | 0 internal/api/admin/system_user.go | 0 internal/api/admin/titles_admin.go | 0 internal/api/admin/titles_seed.go | 322 +++++++-------- internal/api/admin/user_token_admin.go | 0 internal/api/admin/users_admin.go | 115 ++++++ internal/api/admin/users_admin_optimized.go | 0 internal/api/admin/users_admin_test.go | 0 internal/api/admin/users_batch_admin.go | 0 internal/api/admin/users_profile.go | 0 internal/api/admin/users_profit_loss.go | 0 internal/api/admin/users_reward_admin.go | 0 internal/api/app/banner.go | 0 internal/api/app/categories.go | 0 internal/api/app/coupon_transfer.go | 0 internal/api/app/notice.go | 0 internal/api/app/product.go | 0 internal/api/app/product_category.go | 0 internal/api/app/store.go | 0 internal/api/app/store_test.go | 0 internal/api/common/common.go | 0 internal/api/common/config.go | 0 internal/api/common/openid_app.go | 0 internal/api/common/upload_wangeditor.go | 0 internal/api/game/handler.go | 0 internal/api/game/handler_test.go | 0 internal/api/internal/minesweeper/handler.go | 8 +- internal/api/pay/pay.go | 0 internal/api/pay/wechat_notify.go | 0 internal/api/public/livestream_public.go | 0 internal/api/task_center/README.md | 0 internal/api/task_center/admin.go | 147 ++++++- internal/api/task_center/app.go | 0 internal/api/task_center/tasks_app.go | 3 +- internal/api/user/address_share_create_app.go | 0 internal/api/user/address_share_revoke_app.go | 0 .../api/user/address_share_submit_public.go | 0 internal/api/user/addresses_add_app.go | 0 internal/api/user/addresses_default_app.go | 0 internal/api/user/addresses_delete_app.go | 0 internal/api/user/addresses_list_app.go | 0 internal/api/user/addresses_update_app.go | 0 internal/api/user/app.go | 0 internal/api/user/bind_douyin_order_app.go | 0 internal/api/user/bind_inviter_app.go | 0 internal/api/user/cancel_shipping_app.go | 0 internal/api/user/coupons_app.go | 45 +- internal/api/user/coupons_stats_app.go | 0 internal/api/user/coupons_usage_app.go | 0 internal/api/user/coupons_usage_app_test.go | 0 internal/api/user/game_passes_app.go | 0 internal/api/user/inventory_app.go | 0 internal/api/user/invites_app.go | 0 internal/api/user/item_cards_app.go | 0 internal/api/user/login_app.go | 0 internal/api/user/login_douyin_app.go | 0 internal/api/user/orders_app.go | 0 internal/api/user/orders_test_app.go | 0 internal/api/user/pay_wechat_app.go | 0 internal/api/user/phone_bind.go | 0 internal/api/user/phone_bind_douyin_app.go | 0 internal/api/user/points_app.go | 0 internal/api/user/points_redeem_coupon_app.go | 0 .../api/user/points_redeem_item_card_app.go | 0 .../api/user/points_redeem_product_app.go | 0 internal/api/user/profile_app.go | 0 internal/api/user/redeem_inventory_app.go | 0 internal/api/user/request_shipping_app.go | 0 .../api/user/request_shipping_batch_app.go | 0 internal/api/user/shipping_groups_app.go | 0 internal/api/user/sms_login_app.go | 0 internal/api/user/stats_app.go | 0 internal/api/wechat/mini_template.go | 0 internal/code/README.md | 0 internal/code/code.go | 0 internal/code/zh-cn.go | 0 internal/dblogger/request_logger.go | 0 internal/metrics/metrics.go | 0 internal/metrics/prometheus.go | 0 internal/pkg/async/task_queue.go | 0 internal/pkg/color/string_darwin.go | 0 internal/pkg/color/string_linux.go | 0 internal/pkg/color/string_windows.go | 0 internal/pkg/core/context.go | 0 internal/pkg/core/core.go | 0 internal/pkg/core/error.go | 0 internal/pkg/cors/cors.go | 0 internal/pkg/cryptoaes/cryptoaes.go | 0 internal/pkg/cryptoaes/cryptoaes_test.go | 0 internal/pkg/cryptorsa/cryptorsa.go | 0 internal/pkg/cryptorsa/cryptorsa_test.go | 0 internal/pkg/debug/logger.go | 0 internal/pkg/douyin/access_token.go | 0 internal/pkg/douyin/code2session.go | 0 internal/pkg/douyin/phonenumber.go | 0 internal/pkg/env/env.go | 0 internal/pkg/errors/err.go | 0 internal/pkg/errors/err_test.go | 0 internal/pkg/httpclient/client.go | 0 internal/pkg/idgen/idgen.go | 0 internal/pkg/idgen/idgen_test.go | 0 internal/pkg/jsonutil/jsonutil.go | 0 internal/pkg/jwtoken/jwtoken.go | 0 internal/pkg/jwtoken/jwtoken_test.go | 0 internal/pkg/logger/logger.go | 0 internal/pkg/logger/logger_test.go | 0 internal/pkg/miniprogram/access_token.go | 0 internal/pkg/miniprogram/access_token_test.go | 0 internal/pkg/miniprogram/subscribe.go | 0 internal/pkg/miniprogram/subscribe_test.go | 0 internal/pkg/notify/lottery_notify.go | 0 internal/pkg/otel/middleware.go | 0 internal/pkg/otel/otel.go | 0 internal/pkg/pay/client.go | 0 internal/pkg/pay/wechat.go | 0 internal/pkg/points/convert.go | 0 internal/pkg/points/convert_test.go | 0 internal/pkg/redis/redis.go | 0 internal/pkg/shutdown/shutdown.go | 0 internal/pkg/sms/aliyun.go | 0 internal/pkg/startup/info.go | 0 internal/pkg/timeutil/timeutil.go | 0 internal/pkg/timeutil/timeutil_test.go | 0 internal/pkg/trace/debug.go | 0 internal/pkg/trace/http.go | 0 internal/pkg/trace/mongo.go | 0 internal/pkg/trace/redis.go | 0 internal/pkg/trace/sql.go | 0 internal/pkg/trace/trace.go | 0 internal/pkg/util/remark/remark.go | 0 internal/pkg/utils/truncate_test.go | 0 internal/pkg/utils/utils.go | 0 internal/pkg/utils/utils_test.go | 0 internal/pkg/validation/validation.go | 0 internal/pkg/wechat/code2session.go | 0 internal/pkg/wechat/decrypt.go | 0 internal/pkg/wechat/phone_number.go | 0 internal/pkg/wechat/qrcode.go | 0 internal/pkg/wechat/shipping.go | 0 internal/pkg/wechat/shipping_test.go | 0 internal/pkg/wechat/shortlink.go | 0 internal/pkg/wechat/url_scheme.go | 0 internal/proposal/alert.go | 0 internal/proposal/metrics.go | 0 internal/proposal/request_logger.go | 0 internal/proposal/session.go | 0 internal/repository/.DS_Store | Bin internal/repository/mysql/bindbox.db | 0 .../repository/mysql/dao/activities.gen.go | 0 .../mysql/dao/activity_categories.gen.go | 0 .../mysql/dao/activity_draw_effects.gen.go | 0 .../mysql/dao/activity_draw_logs.gen.go | 0 .../mysql/dao/activity_draw_receipts.gen.go | 0 .../mysql/dao/activity_issues.gen.go | 0 .../mysql/dao/activity_reward_settings.gen.go | 0 internal/repository/mysql/dao/admin.gen.go | 0 .../mysql/dao/audit_rollback_logs.gen.go | 0 internal/repository/mysql/dao/banner.gen.go | 0 internal/repository/mysql/dao/channels.gen.go | 0 .../mysql/dao/douyin_blacklist.gen.go | 0 .../repository/mysql/dao/douyin_orders.gen.go | 0 .../mysql/dao/douyin_product_rewards.gen.go | 0 .../mysql/dao/game_pass_packages.gen.go | 0 .../mysql/dao/game_ticket_logs.gen.go | 0 internal/repository/mysql/dao/gen.go | 0 .../mysql/dao/issue_position_claims.gen.go | 0 .../mysql/dao/livestream_activities.gen.go | 0 .../mysql/dao/livestream_draw_logs.gen.go | 0 .../mysql/dao/livestream_prizes.gen.go | 0 .../repository/mysql/dao/log_operation.gen.go | 0 .../repository/mysql/dao/log_request.gen.go | 0 .../mysql/dao/lottery_refund_logs.gen.go | 0 .../mysql/dao/matching_card_types.gen.go | 0 .../repository/mysql/dao/menu_actions.gen.go | 0 internal/repository/mysql/dao/menus.gen.go | 0 .../dao/mini_program_access_token.gen.go | 0 .../mysql/dao/ops_shipping_stats.gen.go | 0 .../repository/mysql/dao/order_coupons.gen.go | 0 .../repository/mysql/dao/order_items.gen.go | 0 .../mysql/dao/order_snapshots.gen.go | 0 internal/repository/mysql/dao/orders.gen.go | 4 + .../mysql/dao/payment_bill_diff.gen.go | 0 .../repository/mysql/dao/payment_bills.gen.go | 0 .../mysql/dao/payment_notify_events.gen.go | 0 .../mysql/dao/payment_preorders.gen.go | 0 .../mysql/dao/payment_refunds.gen.go | 0 .../mysql/dao/payment_transactions.gen.go | 0 .../mysql/dao/product_categories.gen.go | 0 internal/repository/mysql/dao/products.gen.go | 0 .../repository/mysql/dao/role_actions.gen.go | 0 .../repository/mysql/dao/role_menus.gen.go | 0 .../repository/mysql/dao/role_users.gen.go | 0 internal/repository/mysql/dao/roles.gen.go | 0 .../mysql/dao/shipping_records.gen.go | 0 .../mysql/dao/system_configs.gen.go | 0 .../mysql/dao/system_coupons.gen.go | 0 .../mysql/dao/system_item_cards.gen.go | 0 .../mysql/dao/system_title_effects.gen.go | 0 .../repository/mysql/dao/system_titles.gen.go | 0 .../mysql/dao/task_center_event_logs.gen.go | 0 .../mysql/dao/task_center_task_rewards.gen.go | 0 .../mysql/dao/task_center_task_tiers.gen.go | 0 .../mysql/dao/task_center_tasks.gen.go | 0 .../dao/task_center_user_progress.gen.go | 0 .../mysql/dao/user_addresses.gen.go | 0 .../mysql/dao/user_coupon_ledger.gen.go | 0 .../repository/mysql/dao/user_coupons.gen.go | 0 .../mysql/dao/user_game_passes.gen.go | 0 .../mysql/dao/user_game_tickets.gen.go | 0 .../mysql/dao/user_inventory.gen.go | 0 .../mysql/dao/user_inventory_transfers.gen.go | 0 .../repository/mysql/dao/user_invites.gen.go | 0 .../mysql/dao/user_item_cards.gen.go | 0 .../repository/mysql/dao/user_points.gen.go | 0 .../mysql/dao/user_points_ledger.gen.go | 0 .../mysql/dao/user_title_effect_claims.gen.go | 0 .../repository/mysql/dao/user_titles.gen.go | 0 internal/repository/mysql/dao/users.gen.go | 0 .../repository/mysql/model/activities.gen.go | 0 .../mysql/model/activity_categories.gen.go | 0 .../mysql/model/activity_draw_effects.gen.go | 0 .../mysql/model/activity_draw_logs.gen.go | 0 .../mysql/model/activity_draw_receipts.gen.go | 0 .../mysql/model/activity_issues.gen.go | 0 .../model/activity_reward_settings.gen.go | 0 internal/repository/mysql/model/admin.gen.go | 0 .../mysql/model/audit_rollback_logs.gen.go | 0 internal/repository/mysql/model/banner.gen.go | 0 .../repository/mysql/model/channels.gen.go | 0 .../mysql/model/douyin_blacklist.gen.go | 0 .../mysql/model/douyin_orders.gen.go | 0 .../mysql/model/douyin_product_rewards.gen.go | 0 .../mysql/model/game_pass_packages.gen.go | 0 .../mysql/model/game_ticket_logs.gen.go | 0 .../mysql/model/issue_position_claims.gen.go | 0 .../mysql/model/livestream_activities.gen.go | 0 .../mysql/model/livestream_draw_logs.gen.go | 0 .../mysql/model/livestream_prizes.gen.go | 0 .../mysql/model/log_operation.gen.go | 0 .../repository/mysql/model/log_request.gen.go | 0 .../mysql/model/lottery_refund_logs.gen.go | 0 .../mysql/model/matching_card_types.gen.go | 0 .../mysql/model/menu_actions.gen.go | 0 internal/repository/mysql/model/menus.gen.go | 0 .../model/mini_program_access_token.gen.go | 0 .../mysql/model/ops_shipping_stats.gen.go | 0 .../mysql/model/order_coupons.gen.go | 0 .../repository/mysql/model/order_items.gen.go | 0 .../mysql/model/order_snapshots.gen.go | 0 internal/repository/mysql/model/orders.gen.go | 1 + .../mysql/model/payment_bill_diff.gen.go | 0 .../mysql/model/payment_bills.gen.go | 0 .../mysql/model/payment_notify_events.gen.go | 0 .../mysql/model/payment_preorders.gen.go | 0 .../mysql/model/payment_refunds.gen.go | 0 .../mysql/model/payment_transactions.gen.go | 0 .../mysql/model/product_categories.gen.go | 0 .../repository/mysql/model/products.gen.go | 0 .../mysql/model/role_actions.gen.go | 0 .../repository/mysql/model/role_menus.gen.go | 0 .../repository/mysql/model/role_users.gen.go | 0 internal/repository/mysql/model/roles.gen.go | 0 .../mysql/model/shipping_records.gen.go | 0 .../mysql/model/system_configs.gen.go | 0 .../mysql/model/system_coupons.gen.go | 0 .../mysql/model/system_item_cards.gen.go | 0 .../mysql/model/system_title_effects.gen.go | 0 .../mysql/model/system_titles.gen.go | 0 .../mysql/model/task_center_event_logs.gen.go | 0 .../model/task_center_task_rewards.gen.go | 0 .../mysql/model/task_center_task_tiers.gen.go | 0 .../mysql/model/task_center_tasks.gen.go | 0 .../model/task_center_user_progress.gen.go | 0 .../mysql/model/user_addresses.gen.go | 0 .../mysql/model/user_coupon_ledger.gen.go | 0 .../mysql/model/user_coupons.gen.go | 0 .../mysql/model/user_game_passes.gen.go | 0 .../mysql/model/user_game_tickets.gen.go | 0 .../mysql/model/user_inventory.gen.go | 0 .../model/user_inventory_transfers.gen.go | 0 .../mysql/model/user_invites.gen.go | 0 .../mysql/model/user_item_cards.gen.go | 0 .../repository/mysql/model/user_points.gen.go | 0 .../mysql/model/user_points_ledger.gen.go | 0 .../repository/mysql/model/user_status.go | 0 .../model/user_title_effect_claims.gen.go | 0 .../repository/mysql/model/user_titles.gen.go | 0 internal/repository/mysql/model/users.gen.go | 0 internal/repository/mysql/mysql.go | 0 internal/repository/mysql/plugin.go | 0 .../repository/mysql/task_center/README.md | 0 .../repository/mysql/task_center/models.go | 0 internal/repository/mysql/test_helper.go | 0 internal/repository/mysql/testrepo_sqlite.go | 0 internal/router/.DS_Store | Bin internal/router/interceptor/admin_auth.go | 0 internal/router/interceptor/admin_rbac.go | 0 .../router/interceptor/admin_rbac_test.go | 0 internal/router/interceptor/app_auth.go | 0 internal/router/interceptor/blacklist.go | 0 internal/router/interceptor/interceptor.go | 0 internal/router/router.go | 4 + internal/service/activity/activities_list.go | 0 internal/service/activity/activity.go | 0 .../activity/activity_commitment_service.go | 0 internal/service/activity/activity_copy.go | 0 internal/service/activity/activity_create.go | 0 internal/service/activity/activity_delete.go | 0 internal/service/activity/activity_get.go | 0 internal/service/activity/activity_modify.go | 0 .../activity/activity_order_service.go | 0 internal/service/activity/category_lookup.go | 0 internal/service/activity/concurrency_test.go | 0 internal/service/activity/draw_config_save.go | 0 internal/service/activity/draw_logs_list.go | 0 .../service/activity/ichiban_slots_service.go | 0 internal/service/activity/issue_create.go | 0 internal/service/activity/issue_delete.go | 0 internal/service/activity/issue_modify.go | 0 internal/service/activity/issues_list.go | 0 internal/service/activity/lottery_process.go | 0 internal/service/activity/matching_game.go | 0 internal/service/activity/rewards_create.go | 0 internal/service/activity/rewards_list.go | 0 internal/service/activity/rewards_modify.go | 0 internal/service/activity/sanitize.go | 0 internal/service/activity/sanitize_test.go | 0 internal/service/activity/scheduler.go | 96 +++-- internal/service/activity/strategy/default.go | 0 internal/service/activity/strategy/ichiban.go | 0 .../service/activity/strategy/ichiban_test.go | 0 .../service/activity/strategy/strategy.go | 0 internal/service/admin/admin.go | 0 internal/service/admin/admin_create.go | 0 internal/service/admin/admin_delete.go | 0 internal/service/admin/admin_list.go | 0 internal/service/admin/admin_modify.go | 0 internal/service/admin/login.go | 0 internal/service/banner/banner.go | 0 internal/service/channel/channel.go | 0 internal/service/common/common.go | 0 internal/service/douyin/order_sync.go | 2 +- internal/service/douyin/reward_dispatcher.go | 10 +- internal/service/douyin/scheduler.go | 0 internal/service/game/ticket_service.go | 0 internal/service/game/token.go | 0 internal/service/game/token_test.go | 0 internal/service/livestream/DRAW_README.md | 0 .../service/livestream/discrete_random.go | 0 .../livestream/discrete_random_test.go | 0 .../livestream/draw_integration_test.go | 0 internal/service/livestream/livestream.go | 0 internal/service/order/discount.go | 0 internal/service/order/discount_test.go | 0 internal/service/product/product.go | 0 internal/service/product/product_test.go | 0 internal/service/recycle/recycle_service.go | 0 internal/service/snapshot/rollback_service.go | 0 internal/service/snapshot/snapshot_service.go | 0 internal/service/sysconfig/dynamic_config.go | 0 internal/service/sysconfig/global.go | 0 internal/service/sysconfig/sysconfig.go | 0 internal/service/task_center/cache.go | 0 internal/service/task_center/constants.go | 10 +- .../service/task_center/invite_logic_test.go | 0 .../task_center/list_tasks_filter_test.go | 0 internal/service/task_center/service.go | 386 +++++++++++++++--- internal/service/task_center/service_test.go | 156 +++++++ .../service/task_center/task_center_test.go | 6 +- internal/service/task_center/worker.go | 0 internal/service/title/assign.go | 0 internal/service/title/effect_validate.go | 0 .../service/title/effect_validate_test.go | 0 internal/service/title/effects_resolver.go | 2 +- internal/service/user/address_share.go | 0 internal/service/user/addresses.go | 0 internal/service/user/batch_user.go | 0 internal/service/user/bind_inviter.go | 95 +++++ internal/service/user/cancel_shipping.go | 67 ++- internal/service/user/coupon_add.go | 0 internal/service/user/coupon_transfer.go | 0 internal/service/user/coupons_list.go | 12 +- internal/service/user/error_test.go | 0 internal/service/user/expiration_task.go | 0 internal/service/user/game_pass.go | 0 internal/service/user/inventory_list.go | 0 internal/service/user/invite_code.go | 0 internal/service/user/invites_list.go | 0 internal/service/user/item_card_add.go | 0 internal/service/user/item_card_redeem.go | 0 internal/service/user/item_card_uses_list.go | 0 internal/service/user/item_cards_list.go | 0 internal/service/user/login_douyin.go | 0 internal/service/user/login_weixin.go | 0 internal/service/user/order_coupons.go | 72 ++-- internal/service/user/order_timeout.go | 52 +-- internal/service/user/orders_action.go | 64 +-- .../service/user/orders_auto_cancel_worker.go | 119 ++++++ internal/service/user/orders_list.go | 0 internal/service/user/points_add.go | 0 internal/service/user/points_balance.go | 0 internal/service/user/points_consume.go | 0 .../service/user/points_consume_generic.go | 0 internal/service/user/points_convert.go | 0 internal/service/user/points_ledger_list.go | 0 internal/service/user/profile.go | 0 .../user/request_shipping_batch_test.go | 0 internal/service/user/reward_grant.go | 2 + internal/service/user/reward_grant_batch.go | 0 internal/service/user/shipping_groups.go | 0 internal/service/user/sms_login.go | 0 internal/service/user/stats.go | 0 internal/service/user/user.go | 4 + main.go | 1 + migrations/006_game_tickets.sql | 0 .../20251218_add_draw_logs_unique_index.sql | 0 .../20251222_add_order_coupon_and_card.sql | 0 ...223_add_user_invites_effective_columns.sql | 0 .../20251223_fix_invite_count_comment.sql | 0 migrations/20251226_add_draw_index_unique.sql | 0 migrations/20251226_add_order_snapshots.sql | 0 migrations/20251229_douyin_orders.sql | 0 .../20260105_douyin_product_rewards.sql | 0 migrations/20260110_livestream_tables.sql | 0 migrations/20260117_livestream_commitment.sql | 0 migrations/20260118_douyin_blacklist.sql | 0 ...20260121_add_order_coupon_unique_index.sql | 0 migrations/20260121_add_user_remark.sql | 0 migrations/20260121_reconcile_coupon_data.sql | 2 +- migrations/20260121_repair_coupon_data.sql | 4 +- .../20260129_add_douyin_orders_fields.sql | 0 .../20260129_add_prize_reward_config.sql | 0 migrations/20260129_backfill_product_ids.sql | 0 .../20260130_add_activity_order_rewards.sql | 0 migrations/20260130_full_livestream_sync.sql | 0 .../20260130_rollback_prize_reward_config.sql | 0 .../20260131_product_rewards_multi_rules.sql | 0 .../20260203_add_product_id_to_draw_logs.sql | 0 migrations/20260206_add_task_tier_quota.sql | 0 .../20260217_add_coupon_show_in_miniapp.sql | 0 ...d_payer_openid_to_payment_transactions.sql | 0 .../20260218_add_product_show_in_miniapp.sql | 0 .../20260219_add_ext_order_id_to_orders.sql | 2 + migrations/matching_game_test_data.sql | 0 migrations/task_level_quota.sql | 0 scripts/swagger.bat | 0 tools/.DS_Store | Bin tools/livestream_lottery_analyzer/main.go | 0 tools/lottery_data_analyzer/main.go | 0 tools/lottery_probability_checker/main.go | 0 tools/lottery_verifier/main.go | 0 tools/lottery_verifier/verify.go | 0 tools/lottery_verifier_web/index.html | 0 tools/query_order/main.go | 0 tools/quick_check/main.go | 0 tools/test_matchmaker/go.mod | 0 tools/test_matchmaker/go.sum | 0 tools/test_matchmaker/main.go | 0 tools/verify_seed/main.go | 0 tools/wechat_debug/main.go | 0 665 files changed, 1918 insertions(+), 573 deletions(-) mode change 100644 => 100755 .DS_Store mode change 100644 => 100755 .claude/plan/admin-update-user-mobile.md mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .vercelignore mode change 100644 => 100755 BUG_FIX_REPORT.md mode change 100644 => 100755 CLAUDE.md mode change 100644 => 100755 Dockerfile mode change 100644 => 100755 Makefile mode change 100644 => 100755 README.md mode change 100644 => 100755 bindboxgame.json mode change 100644 => 100755 build/.DS_Store mode change 100644 => 100755 build/resources/.DS_Store mode change 100644 => 100755 build/resources/admin/assets/403-BdWuHcJA.svg mode change 100644 => 100755 build/resources/admin/assets/404-BzxNMzaO.svg mode change 100644 => 100755 build/resources/admin/assets/500-C-Ru4KUd.svg mode change 100644 => 100755 build/resources/admin/assets/EffectEditDialog-BmAJKxJl.css mode change 100644 => 100755 build/resources/admin/assets/EffectManagerDialog-DnvqZPdh.css mode change 100644 => 100755 build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css mode change 100644 => 100755 build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css.gz mode change 100644 => 100755 build/resources/admin/assets/TitleEditDialog-i9x-drPp.css mode change 100644 => 100755 build/resources/admin/assets/UserAssignmentDialog-BTSnDJ3n.css mode change 100644 => 100755 build/resources/admin/assets/_plugin-vue_export-helper-BCo6x5W8.js mode change 100644 => 100755 build/resources/admin/assets/about-project-DgJMbhc5.js mode change 100644 => 100755 build/resources/admin/assets/avatar-pR7-E1hl.js mode change 100644 => 100755 build/resources/admin/assets/avatar10-Dom60BwY.js mode change 100644 => 100755 build/resources/admin/assets/avatar6-6Evj8BB9.js mode change 100644 => 100755 build/resources/admin/assets/avatar6-6Evj8BB9.js.gz mode change 100644 => 100755 build/resources/admin/assets/bg-DrCBEYh-.webp mode change 100644 => 100755 build/resources/admin/assets/card-D34vavgk.css mode change 100644 => 100755 build/resources/admin/assets/category-search-BqILMF9x.css mode change 100644 => 100755 build/resources/admin/assets/col-yED17g82.css mode change 100644 => 100755 build/resources/admin/assets/col-yED17g82.css.gz mode change 100644 => 100755 build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css mode change 100644 => 100755 build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css.gz mode change 100644 => 100755 build/resources/admin/assets/dialog-2KKj2Euo.css mode change 100644 => 100755 build/resources/admin/assets/el-alert-G57rL0jl.css mode change 100644 => 100755 build/resources/admin/assets/el-avatar-BmRr_O8d.css mode change 100644 => 100755 build/resources/admin/assets/el-button-CDqfIFiK.css mode change 100644 => 100755 build/resources/admin/assets/el-button-CDqfIFiK.css.gz mode change 100644 => 100755 build/resources/admin/assets/el-card-fwQOLwdi.css mode change 100644 => 100755 build/resources/admin/assets/el-checkbox-DIj50LEB.css mode change 100644 => 100755 build/resources/admin/assets/el-col-DD1Vn-Yu.css mode change 100644 => 100755 build/resources/admin/assets/el-col-DD1Vn-Yu.css.gz mode change 100644 => 100755 build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css mode change 100644 => 100755 build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css.gz mode change 100644 => 100755 build/resources/admin/assets/el-descriptions-item-o9ObloqJ.css mode change 100644 => 100755 build/resources/admin/assets/el-dialog-DyK7vRzj.css mode change 100644 => 100755 build/resources/admin/assets/el-divider-BUtF_RGI.css mode change 100644 => 100755 build/resources/admin/assets/el-drawer-BhCnIJJ3.css mode change 100644 => 100755 build/resources/admin/assets/el-dropdown-item-11ZCvSOX.css mode change 100644 => 100755 build/resources/admin/assets/el-form-item-BWkJzdQ_.css mode change 100644 => 100755 build/resources/admin/assets/el-input-number-D6iOyBgb.css mode change 100644 => 100755 build/resources/admin/assets/el-input-tPmZxDKr.css mode change 100644 => 100755 build/resources/admin/assets/el-input-tPmZxDKr.css.gz mode change 100644 => 100755 build/resources/admin/assets/el-option-BHqzF8z9.css mode change 100644 => 100755 build/resources/admin/assets/el-overlay-Db7iXMEX.css mode change 100644 => 100755 build/resources/admin/assets/el-pagination-BNQcHhjS.css mode change 100644 => 100755 build/resources/admin/assets/el-popover-Cktl5fHm.css mode change 100644 => 100755 build/resources/admin/assets/el-popper-D1i0e6ba.css mode change 100644 => 100755 build/resources/admin/assets/el-progress-Dw9yTa91.css mode change 100644 => 100755 build/resources/admin/assets/el-radio-BuDgLcOG.css mode change 100644 => 100755 build/resources/admin/assets/el-radio-button-CSkroacn.css mode change 100644 => 100755 build/resources/admin/assets/el-radio-group-BzMpJalG.css mode change 100644 => 100755 build/resources/admin/assets/el-row-C6BJsxyy.css mode change 100644 => 100755 build/resources/admin/assets/el-scrollbar-BWxh-h6K.css mode change 100644 => 100755 build/resources/admin/assets/el-select-DdmnTlAY.css mode change 100644 => 100755 build/resources/admin/assets/el-switch-B5lTGWdM.css mode change 100644 => 100755 build/resources/admin/assets/el-table-column-CKoPG0Y8.css mode change 100644 => 100755 build/resources/admin/assets/el-table-column-CKoPG0Y8.css.gz mode change 100644 => 100755 build/resources/admin/assets/el-tag-DljBBxJR.css mode change 100644 => 100755 build/resources/admin/assets/el-tooltip-l0sNRNKZ.js mode change 100644 => 100755 build/resources/admin/assets/el-upload-q8uObtwj.css mode change 100644 => 100755 build/resources/admin/assets/el-upload-q8uObtwj.css.gz mode change 100644 => 100755 build/resources/admin/assets/favicon-C1KazUkF.ico mode change 100644 => 100755 build/resources/admin/assets/form-item-B4F-CS9A.css mode change 100644 => 100755 build/resources/admin/assets/grant-reward-dialog-D7-C3J8j.css mode change 100644 => 100755 build/resources/admin/assets/index-0rmuCejF.css mode change 100644 => 100755 build/resources/admin/assets/index-86w9PCiC.css mode change 100644 => 100755 build/resources/admin/assets/index-B7q-DPlS.css mode change 100644 => 100755 build/resources/admin/assets/index-BF_swEeW.css mode change 100644 => 100755 build/resources/admin/assets/index-BG9yZ82v.css mode change 100644 => 100755 build/resources/admin/assets/index-CDDDnorJ.css mode change 100644 => 100755 build/resources/admin/assets/index-CEpEmnur.css mode change 100644 => 100755 build/resources/admin/assets/index-CEpEmnur.css.gz mode change 100644 => 100755 build/resources/admin/assets/index-CIZk353b.css mode change 100644 => 100755 build/resources/admin/assets/index-CTUKoMMr.css mode change 100644 => 100755 build/resources/admin/assets/index-CXjivOvk.css mode change 100644 => 100755 build/resources/admin/assets/index-CsQLNvm4.css mode change 100644 => 100755 build/resources/admin/assets/index-DVtb5Tyi.css mode change 100644 => 100755 build/resources/admin/assets/index-DhfpFd1U.css mode change 100644 => 100755 build/resources/admin/assets/input-number-BXCadU-U.css mode change 100644 => 100755 build/resources/admin/assets/lock_screen_1-CH_l421c.webp mode change 100644 => 100755 build/resources/admin/assets/login_icon-C4TVlUS8.svg mode change 100644 => 100755 build/resources/admin/assets/new-user-3ZQ25ksW.css mode change 100644 => 100755 build/resources/admin/assets/order-funnel-CxvO8S8s.css mode change 100644 => 100755 build/resources/admin/assets/points-economy-DgJMbhc5.js mode change 100644 => 100755 build/resources/admin/assets/popper-kmEP6Jl6.css mode change 100644 => 100755 build/resources/admin/assets/product-search-Cun_ywfi.css mode change 100644 => 100755 build/resources/admin/assets/radio-group-DfFloULT.css mode change 100644 => 100755 build/resources/admin/assets/refs-Cw5r5QN8.js mode change 100644 => 100755 build/resources/admin/assets/role-permission-dialog-BYEPGzFo.css mode change 100644 => 100755 build/resources/admin/assets/scrollbar-C8iP3G9A.css mode change 100644 => 100755 build/resources/admin/assets/sd-C0PQtrty.png mode change 100644 => 100755 build/resources/admin/assets/select-C2cjPkEh.css mode change 100644 => 100755 build/resources/admin/assets/slider-CppPl5od.css mode change 100644 => 100755 build/resources/admin/assets/space-3oFudasq.css mode change 100644 => 100755 build/resources/admin/assets/tag-CtW1DIiB.css mode change 100644 => 100755 build/resources/admin/assets/token-DWNpOE8r.js mode change 100644 => 100755 build/resources/admin/assets/yd-BrGqJ6Cs.png mode change 100644 => 100755 build/resources/admin/index.html mode change 100644 => 100755 cmd/check_order/main.go mode change 100644 => 100755 cmd/debug_check_coupon_22/main.go delete mode 100644 cmd/debug_coupon_9209/main.go mode change 100644 => 100755 cmd/debug_task_270/main.go create mode 100755 cmd/debug_task_time/main.go mode change 100644 => 100755 cmd/fix_openid/main.go mode change 100644 => 100755 cmd/gormgen/README.md mode change 100644 => 100755 cmd/gormgen/main.go mode change 100644 => 100755 cmd/mfmt/README.md mode change 100644 => 100755 cmd/mfmt/main.go mode change 100644 => 100755 configs/.DS_Store mode change 100644 => 100755 configs/cert/apiclient_cert.pem mode change 100644 => 100755 configs/cert/apiclient_key.pem mode change 100644 => 100755 configs/cert/pub_key.pem mode change 100644 => 100755 configs/configs.go mode change 100644 => 100755 configs/constants.go mode change 100644 => 100755 configs/dev_configs.toml mode change 100644 => 100755 configs/fat_configs.toml mode change 100644 => 100755 configs/pro_configs.toml mode change 100644 => 100755 configs/uat_configs.toml mode change 100644 => 100755 deploy/.env mode change 100644 => 100755 deploy/project/docker-compose.yaml mode change 100644 => 100755 deploy/project/swagger.yaml mode change 100644 => 100755 docs/.DS_Store mode change 100644 => 100755 docs/docs.go mode change 100644 => 100755 docs/swagger.json mode change 100644 => 100755 docs/swagger.yaml mode change 100644 => 100755 docs/优化抖音定时任务/ACCEPTANCE_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/ALIGNMENT_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/CONSENSUS_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/DESIGN_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/FINAL_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/TASK_优化抖音定时任务.md mode change 100644 => 100755 docs/优化抖音定时任务/TODO_优化抖音定时任务.md mode change 100644 => 100755 go.mod mode change 100644 => 100755 go.sum mode change 100644 => 100755 internal/.DS_Store mode change 100644 => 100755 internal/alert/alert.go mode change 100644 => 100755 internal/api/activity/activities_app.go mode change 100644 => 100755 internal/api/activity/app.go mode change 100644 => 100755 internal/api/activity/draw_logs_app.go mode change 100644 => 100755 internal/api/activity/issue_choices_app.go mode change 100644 => 100755 internal/api/activity/issues_app.go mode change 100644 => 100755 internal/api/activity/lottery_app.go mode change 100644 => 100755 internal/api/activity/lottery_app_test.go mode change 100644 => 100755 internal/api/activity/lottery_helper.go mode change 100644 => 100755 internal/api/activity/lottery_result_order_app.go mode change 100644 => 100755 internal/api/activity/matching_game_app.go mode change 100644 => 100755 internal/api/activity/matching_game_app_test.go mode change 100644 => 100755 internal/api/activity/matching_game_helper.go mode change 100644 => 100755 internal/api/activity/rewards_app.go mode change 100644 => 100755 internal/api/admin/activities_admin.go mode change 100644 => 100755 internal/api/admin/activity_categories.go mode change 100644 => 100755 internal/api/admin/activity_commitment_admin.go mode change 100644 => 100755 internal/api/admin/admin.go mode change 100644 => 100755 internal/api/admin/auth_refresh.go mode change 100644 => 100755 internal/api/admin/banner.go mode change 100644 => 100755 internal/api/admin/blacklist_admin.go mode change 100644 => 100755 internal/api/admin/channels.go mode change 100644 => 100755 internal/api/admin/dashboard_activity.go mode change 100644 => 100755 internal/api/admin/dashboard_admin.go mode change 100644 => 100755 internal/api/admin/dashboard_admin_test.go mode change 100644 => 100755 internal/api/admin/dashboard_spending.go create mode 100755 internal/api/admin/dashboard_user_spending.go mode change 100644 => 100755 internal/api/admin/douyin_orders_admin.go mode change 100644 => 100755 internal/api/admin/douyin_product_rewards.go mode change 100644 => 100755 internal/api/admin/game_pass_packages_admin.go mode change 100644 => 100755 internal/api/admin/game_passes_admin.go mode change 100644 => 100755 internal/api/admin/ichiban_slots_admin.go mode change 100644 => 100755 internal/api/admin/issues_admin.go mode change 100644 => 100755 internal/api/admin/item_cards_admin.go mode change 100644 => 100755 internal/api/admin/livestream_admin.go mode change 100644 => 100755 internal/api/admin/livestream_stats.go mode change 100644 => 100755 internal/api/admin/login.go mode change 100644 => 100755 internal/api/admin/lottery_admin.go mode change 100644 => 100755 internal/api/admin/matching_audit_admin.go mode change 100644 => 100755 internal/api/admin/matching_cards_admin.go mode change 100644 => 100755 internal/api/admin/miniapp_qrcode.go mode change 100644 => 100755 internal/api/admin/miniapp_shipping_admin.go mode change 100644 => 100755 internal/api/admin/order_snapshot_admin.go mode change 100644 => 100755 internal/api/admin/pay_orders_admin.go mode change 100644 => 100755 internal/api/admin/pay_orders_export.go mode change 100644 => 100755 internal/api/admin/pay_reconcile_admin.go mode change 100644 => 100755 internal/api/admin/pay_refund_admin.go mode change 100644 => 100755 internal/api/admin/product_batch.go mode change 100644 => 100755 internal/api/admin/product_category_create.go mode change 100644 => 100755 internal/api/admin/product_create.go mode change 100644 => 100755 internal/api/admin/rewards_admin.go mode change 100644 => 100755 internal/api/admin/scheduled_config_admin.go mode change 100644 => 100755 internal/api/admin/shipping_orders_admin.go mode change 100644 => 100755 internal/api/admin/shipping_stats_admin.go mode change 100644 => 100755 internal/api/admin/system_configs_admin.go mode change 100644 => 100755 internal/api/admin/system_coupons.go mode change 100644 => 100755 internal/api/admin/system_menu.go mode change 100644 => 100755 internal/api/admin/system_recycle.go mode change 100644 => 100755 internal/api/admin/system_role.go mode change 100644 => 100755 internal/api/admin/system_user.go mode change 100644 => 100755 internal/api/admin/titles_admin.go mode change 100644 => 100755 internal/api/admin/titles_seed.go mode change 100644 => 100755 internal/api/admin/user_token_admin.go mode change 100644 => 100755 internal/api/admin/users_admin.go mode change 100644 => 100755 internal/api/admin/users_admin_optimized.go mode change 100644 => 100755 internal/api/admin/users_admin_test.go mode change 100644 => 100755 internal/api/admin/users_batch_admin.go mode change 100644 => 100755 internal/api/admin/users_profile.go mode change 100644 => 100755 internal/api/admin/users_profit_loss.go mode change 100644 => 100755 internal/api/admin/users_reward_admin.go mode change 100644 => 100755 internal/api/app/banner.go mode change 100644 => 100755 internal/api/app/categories.go mode change 100644 => 100755 internal/api/app/coupon_transfer.go mode change 100644 => 100755 internal/api/app/notice.go mode change 100644 => 100755 internal/api/app/product.go mode change 100644 => 100755 internal/api/app/product_category.go mode change 100644 => 100755 internal/api/app/store.go mode change 100644 => 100755 internal/api/app/store_test.go mode change 100644 => 100755 internal/api/common/common.go mode change 100644 => 100755 internal/api/common/config.go mode change 100644 => 100755 internal/api/common/openid_app.go mode change 100644 => 100755 internal/api/common/upload_wangeditor.go mode change 100644 => 100755 internal/api/game/handler.go mode change 100644 => 100755 internal/api/game/handler_test.go mode change 100644 => 100755 internal/api/internal/minesweeper/handler.go mode change 100644 => 100755 internal/api/pay/pay.go mode change 100644 => 100755 internal/api/pay/wechat_notify.go mode change 100644 => 100755 internal/api/public/livestream_public.go mode change 100644 => 100755 internal/api/task_center/README.md mode change 100644 => 100755 internal/api/task_center/admin.go mode change 100644 => 100755 internal/api/task_center/app.go mode change 100644 => 100755 internal/api/task_center/tasks_app.go mode change 100644 => 100755 internal/api/user/address_share_create_app.go mode change 100644 => 100755 internal/api/user/address_share_revoke_app.go mode change 100644 => 100755 internal/api/user/address_share_submit_public.go mode change 100644 => 100755 internal/api/user/addresses_add_app.go mode change 100644 => 100755 internal/api/user/addresses_default_app.go mode change 100644 => 100755 internal/api/user/addresses_delete_app.go mode change 100644 => 100755 internal/api/user/addresses_list_app.go mode change 100644 => 100755 internal/api/user/addresses_update_app.go mode change 100644 => 100755 internal/api/user/app.go mode change 100644 => 100755 internal/api/user/bind_douyin_order_app.go mode change 100644 => 100755 internal/api/user/bind_inviter_app.go mode change 100644 => 100755 internal/api/user/cancel_shipping_app.go mode change 100644 => 100755 internal/api/user/coupons_app.go mode change 100644 => 100755 internal/api/user/coupons_stats_app.go mode change 100644 => 100755 internal/api/user/coupons_usage_app.go mode change 100644 => 100755 internal/api/user/coupons_usage_app_test.go mode change 100644 => 100755 internal/api/user/game_passes_app.go mode change 100644 => 100755 internal/api/user/inventory_app.go mode change 100644 => 100755 internal/api/user/invites_app.go mode change 100644 => 100755 internal/api/user/item_cards_app.go mode change 100644 => 100755 internal/api/user/login_app.go mode change 100644 => 100755 internal/api/user/login_douyin_app.go mode change 100644 => 100755 internal/api/user/orders_app.go mode change 100644 => 100755 internal/api/user/orders_test_app.go mode change 100644 => 100755 internal/api/user/pay_wechat_app.go mode change 100644 => 100755 internal/api/user/phone_bind.go mode change 100644 => 100755 internal/api/user/phone_bind_douyin_app.go mode change 100644 => 100755 internal/api/user/points_app.go mode change 100644 => 100755 internal/api/user/points_redeem_coupon_app.go mode change 100644 => 100755 internal/api/user/points_redeem_item_card_app.go mode change 100644 => 100755 internal/api/user/points_redeem_product_app.go mode change 100644 => 100755 internal/api/user/profile_app.go mode change 100644 => 100755 internal/api/user/redeem_inventory_app.go mode change 100644 => 100755 internal/api/user/request_shipping_app.go mode change 100644 => 100755 internal/api/user/request_shipping_batch_app.go mode change 100644 => 100755 internal/api/user/shipping_groups_app.go mode change 100644 => 100755 internal/api/user/sms_login_app.go mode change 100644 => 100755 internal/api/user/stats_app.go mode change 100644 => 100755 internal/api/wechat/mini_template.go mode change 100644 => 100755 internal/code/README.md mode change 100644 => 100755 internal/code/code.go mode change 100644 => 100755 internal/code/zh-cn.go mode change 100644 => 100755 internal/dblogger/request_logger.go mode change 100644 => 100755 internal/metrics/metrics.go mode change 100644 => 100755 internal/metrics/prometheus.go mode change 100644 => 100755 internal/pkg/async/task_queue.go mode change 100644 => 100755 internal/pkg/color/string_darwin.go mode change 100644 => 100755 internal/pkg/color/string_linux.go mode change 100644 => 100755 internal/pkg/color/string_windows.go mode change 100644 => 100755 internal/pkg/core/context.go mode change 100644 => 100755 internal/pkg/core/core.go mode change 100644 => 100755 internal/pkg/core/error.go mode change 100644 => 100755 internal/pkg/cors/cors.go mode change 100644 => 100755 internal/pkg/cryptoaes/cryptoaes.go mode change 100644 => 100755 internal/pkg/cryptoaes/cryptoaes_test.go mode change 100644 => 100755 internal/pkg/cryptorsa/cryptorsa.go mode change 100644 => 100755 internal/pkg/cryptorsa/cryptorsa_test.go mode change 100644 => 100755 internal/pkg/debug/logger.go mode change 100644 => 100755 internal/pkg/douyin/access_token.go mode change 100644 => 100755 internal/pkg/douyin/code2session.go mode change 100644 => 100755 internal/pkg/douyin/phonenumber.go mode change 100644 => 100755 internal/pkg/env/env.go mode change 100644 => 100755 internal/pkg/errors/err.go mode change 100644 => 100755 internal/pkg/errors/err_test.go mode change 100644 => 100755 internal/pkg/httpclient/client.go mode change 100644 => 100755 internal/pkg/idgen/idgen.go mode change 100644 => 100755 internal/pkg/idgen/idgen_test.go mode change 100644 => 100755 internal/pkg/jsonutil/jsonutil.go mode change 100644 => 100755 internal/pkg/jwtoken/jwtoken.go mode change 100644 => 100755 internal/pkg/jwtoken/jwtoken_test.go mode change 100644 => 100755 internal/pkg/logger/logger.go mode change 100644 => 100755 internal/pkg/logger/logger_test.go mode change 100644 => 100755 internal/pkg/miniprogram/access_token.go mode change 100644 => 100755 internal/pkg/miniprogram/access_token_test.go mode change 100644 => 100755 internal/pkg/miniprogram/subscribe.go mode change 100644 => 100755 internal/pkg/miniprogram/subscribe_test.go mode change 100644 => 100755 internal/pkg/notify/lottery_notify.go mode change 100644 => 100755 internal/pkg/otel/middleware.go mode change 100644 => 100755 internal/pkg/otel/otel.go mode change 100644 => 100755 internal/pkg/pay/client.go mode change 100644 => 100755 internal/pkg/pay/wechat.go mode change 100644 => 100755 internal/pkg/points/convert.go mode change 100644 => 100755 internal/pkg/points/convert_test.go mode change 100644 => 100755 internal/pkg/redis/redis.go mode change 100644 => 100755 internal/pkg/shutdown/shutdown.go mode change 100644 => 100755 internal/pkg/sms/aliyun.go mode change 100644 => 100755 internal/pkg/startup/info.go mode change 100644 => 100755 internal/pkg/timeutil/timeutil.go mode change 100644 => 100755 internal/pkg/timeutil/timeutil_test.go mode change 100644 => 100755 internal/pkg/trace/debug.go mode change 100644 => 100755 internal/pkg/trace/http.go mode change 100644 => 100755 internal/pkg/trace/mongo.go mode change 100644 => 100755 internal/pkg/trace/redis.go mode change 100644 => 100755 internal/pkg/trace/sql.go mode change 100644 => 100755 internal/pkg/trace/trace.go mode change 100644 => 100755 internal/pkg/util/remark/remark.go mode change 100644 => 100755 internal/pkg/utils/truncate_test.go mode change 100644 => 100755 internal/pkg/utils/utils.go mode change 100644 => 100755 internal/pkg/utils/utils_test.go mode change 100644 => 100755 internal/pkg/validation/validation.go mode change 100644 => 100755 internal/pkg/wechat/code2session.go mode change 100644 => 100755 internal/pkg/wechat/decrypt.go mode change 100644 => 100755 internal/pkg/wechat/phone_number.go mode change 100644 => 100755 internal/pkg/wechat/qrcode.go mode change 100644 => 100755 internal/pkg/wechat/shipping.go mode change 100644 => 100755 internal/pkg/wechat/shipping_test.go mode change 100644 => 100755 internal/pkg/wechat/shortlink.go mode change 100644 => 100755 internal/pkg/wechat/url_scheme.go mode change 100644 => 100755 internal/proposal/alert.go mode change 100644 => 100755 internal/proposal/metrics.go mode change 100644 => 100755 internal/proposal/request_logger.go mode change 100644 => 100755 internal/proposal/session.go mode change 100644 => 100755 internal/repository/.DS_Store mode change 100644 => 100755 internal/repository/mysql/bindbox.db mode change 100644 => 100755 internal/repository/mysql/dao/activities.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_categories.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_draw_effects.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_draw_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_draw_receipts.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_issues.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/activity_reward_settings.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/admin.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/audit_rollback_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/banner.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/channels.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/douyin_blacklist.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/douyin_orders.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/douyin_product_rewards.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/game_pass_packages.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/game_ticket_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/gen.go mode change 100644 => 100755 internal/repository/mysql/dao/issue_position_claims.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/livestream_activities.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/livestream_draw_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/livestream_prizes.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/log_operation.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/log_request.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/lottery_refund_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/matching_card_types.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/menu_actions.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/menus.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/mini_program_access_token.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/ops_shipping_stats.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/order_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/order_items.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/order_snapshots.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/orders.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_bill_diff.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_bills.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_notify_events.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_preorders.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_refunds.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/payment_transactions.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/product_categories.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/products.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/role_actions.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/role_menus.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/role_users.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/roles.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/shipping_records.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/system_configs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/system_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/system_item_cards.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/system_title_effects.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/system_titles.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/task_center_event_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/task_center_task_rewards.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/task_center_task_tiers.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/task_center_tasks.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/task_center_user_progress.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_addresses.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_coupon_ledger.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_game_passes.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_game_tickets.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_inventory.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_inventory_transfers.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_invites.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_item_cards.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_points.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_points_ledger.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_title_effect_claims.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/user_titles.gen.go mode change 100644 => 100755 internal/repository/mysql/dao/users.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activities.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_categories.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_draw_effects.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_draw_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_draw_receipts.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_issues.gen.go mode change 100644 => 100755 internal/repository/mysql/model/activity_reward_settings.gen.go mode change 100644 => 100755 internal/repository/mysql/model/admin.gen.go mode change 100644 => 100755 internal/repository/mysql/model/audit_rollback_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/banner.gen.go mode change 100644 => 100755 internal/repository/mysql/model/channels.gen.go mode change 100644 => 100755 internal/repository/mysql/model/douyin_blacklist.gen.go mode change 100644 => 100755 internal/repository/mysql/model/douyin_orders.gen.go mode change 100644 => 100755 internal/repository/mysql/model/douyin_product_rewards.gen.go mode change 100644 => 100755 internal/repository/mysql/model/game_pass_packages.gen.go mode change 100644 => 100755 internal/repository/mysql/model/game_ticket_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/issue_position_claims.gen.go mode change 100644 => 100755 internal/repository/mysql/model/livestream_activities.gen.go mode change 100644 => 100755 internal/repository/mysql/model/livestream_draw_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/livestream_prizes.gen.go mode change 100644 => 100755 internal/repository/mysql/model/log_operation.gen.go mode change 100644 => 100755 internal/repository/mysql/model/log_request.gen.go mode change 100644 => 100755 internal/repository/mysql/model/lottery_refund_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/matching_card_types.gen.go mode change 100644 => 100755 internal/repository/mysql/model/menu_actions.gen.go mode change 100644 => 100755 internal/repository/mysql/model/menus.gen.go mode change 100644 => 100755 internal/repository/mysql/model/mini_program_access_token.gen.go mode change 100644 => 100755 internal/repository/mysql/model/ops_shipping_stats.gen.go mode change 100644 => 100755 internal/repository/mysql/model/order_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/model/order_items.gen.go mode change 100644 => 100755 internal/repository/mysql/model/order_snapshots.gen.go mode change 100644 => 100755 internal/repository/mysql/model/orders.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_bill_diff.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_bills.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_notify_events.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_preorders.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_refunds.gen.go mode change 100644 => 100755 internal/repository/mysql/model/payment_transactions.gen.go mode change 100644 => 100755 internal/repository/mysql/model/product_categories.gen.go mode change 100644 => 100755 internal/repository/mysql/model/products.gen.go mode change 100644 => 100755 internal/repository/mysql/model/role_actions.gen.go mode change 100644 => 100755 internal/repository/mysql/model/role_menus.gen.go mode change 100644 => 100755 internal/repository/mysql/model/role_users.gen.go mode change 100644 => 100755 internal/repository/mysql/model/roles.gen.go mode change 100644 => 100755 internal/repository/mysql/model/shipping_records.gen.go mode change 100644 => 100755 internal/repository/mysql/model/system_configs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/system_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/model/system_item_cards.gen.go mode change 100644 => 100755 internal/repository/mysql/model/system_title_effects.gen.go mode change 100644 => 100755 internal/repository/mysql/model/system_titles.gen.go mode change 100644 => 100755 internal/repository/mysql/model/task_center_event_logs.gen.go mode change 100644 => 100755 internal/repository/mysql/model/task_center_task_rewards.gen.go mode change 100644 => 100755 internal/repository/mysql/model/task_center_task_tiers.gen.go mode change 100644 => 100755 internal/repository/mysql/model/task_center_tasks.gen.go mode change 100644 => 100755 internal/repository/mysql/model/task_center_user_progress.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_addresses.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_coupon_ledger.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_coupons.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_game_passes.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_game_tickets.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_inventory.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_inventory_transfers.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_invites.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_item_cards.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_points.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_points_ledger.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_status.go mode change 100644 => 100755 internal/repository/mysql/model/user_title_effect_claims.gen.go mode change 100644 => 100755 internal/repository/mysql/model/user_titles.gen.go mode change 100644 => 100755 internal/repository/mysql/model/users.gen.go mode change 100644 => 100755 internal/repository/mysql/mysql.go mode change 100644 => 100755 internal/repository/mysql/plugin.go mode change 100644 => 100755 internal/repository/mysql/task_center/README.md mode change 100644 => 100755 internal/repository/mysql/task_center/models.go mode change 100644 => 100755 internal/repository/mysql/test_helper.go mode change 100644 => 100755 internal/repository/mysql/testrepo_sqlite.go mode change 100644 => 100755 internal/router/.DS_Store mode change 100644 => 100755 internal/router/interceptor/admin_auth.go mode change 100644 => 100755 internal/router/interceptor/admin_rbac.go mode change 100644 => 100755 internal/router/interceptor/admin_rbac_test.go mode change 100644 => 100755 internal/router/interceptor/app_auth.go mode change 100644 => 100755 internal/router/interceptor/blacklist.go mode change 100644 => 100755 internal/router/interceptor/interceptor.go mode change 100644 => 100755 internal/router/router.go mode change 100644 => 100755 internal/service/activity/activities_list.go mode change 100644 => 100755 internal/service/activity/activity.go mode change 100644 => 100755 internal/service/activity/activity_commitment_service.go mode change 100644 => 100755 internal/service/activity/activity_copy.go mode change 100644 => 100755 internal/service/activity/activity_create.go mode change 100644 => 100755 internal/service/activity/activity_delete.go mode change 100644 => 100755 internal/service/activity/activity_get.go mode change 100644 => 100755 internal/service/activity/activity_modify.go mode change 100644 => 100755 internal/service/activity/activity_order_service.go mode change 100644 => 100755 internal/service/activity/category_lookup.go mode change 100644 => 100755 internal/service/activity/concurrency_test.go mode change 100644 => 100755 internal/service/activity/draw_config_save.go mode change 100644 => 100755 internal/service/activity/draw_logs_list.go mode change 100644 => 100755 internal/service/activity/ichiban_slots_service.go mode change 100644 => 100755 internal/service/activity/issue_create.go mode change 100644 => 100755 internal/service/activity/issue_delete.go mode change 100644 => 100755 internal/service/activity/issue_modify.go mode change 100644 => 100755 internal/service/activity/issues_list.go mode change 100644 => 100755 internal/service/activity/lottery_process.go mode change 100644 => 100755 internal/service/activity/matching_game.go mode change 100644 => 100755 internal/service/activity/rewards_create.go mode change 100644 => 100755 internal/service/activity/rewards_list.go mode change 100644 => 100755 internal/service/activity/rewards_modify.go mode change 100644 => 100755 internal/service/activity/sanitize.go mode change 100644 => 100755 internal/service/activity/sanitize_test.go mode change 100644 => 100755 internal/service/activity/scheduler.go mode change 100644 => 100755 internal/service/activity/strategy/default.go mode change 100644 => 100755 internal/service/activity/strategy/ichiban.go mode change 100644 => 100755 internal/service/activity/strategy/ichiban_test.go mode change 100644 => 100755 internal/service/activity/strategy/strategy.go mode change 100644 => 100755 internal/service/admin/admin.go mode change 100644 => 100755 internal/service/admin/admin_create.go mode change 100644 => 100755 internal/service/admin/admin_delete.go mode change 100644 => 100755 internal/service/admin/admin_list.go mode change 100644 => 100755 internal/service/admin/admin_modify.go mode change 100644 => 100755 internal/service/admin/login.go mode change 100644 => 100755 internal/service/banner/banner.go mode change 100644 => 100755 internal/service/channel/channel.go mode change 100644 => 100755 internal/service/common/common.go mode change 100644 => 100755 internal/service/douyin/order_sync.go mode change 100644 => 100755 internal/service/douyin/reward_dispatcher.go mode change 100644 => 100755 internal/service/douyin/scheduler.go mode change 100644 => 100755 internal/service/game/ticket_service.go mode change 100644 => 100755 internal/service/game/token.go mode change 100644 => 100755 internal/service/game/token_test.go mode change 100644 => 100755 internal/service/livestream/DRAW_README.md mode change 100644 => 100755 internal/service/livestream/discrete_random.go mode change 100644 => 100755 internal/service/livestream/discrete_random_test.go mode change 100644 => 100755 internal/service/livestream/draw_integration_test.go mode change 100644 => 100755 internal/service/livestream/livestream.go mode change 100644 => 100755 internal/service/order/discount.go mode change 100644 => 100755 internal/service/order/discount_test.go mode change 100644 => 100755 internal/service/product/product.go mode change 100644 => 100755 internal/service/product/product_test.go mode change 100644 => 100755 internal/service/recycle/recycle_service.go mode change 100644 => 100755 internal/service/snapshot/rollback_service.go mode change 100644 => 100755 internal/service/snapshot/snapshot_service.go mode change 100644 => 100755 internal/service/sysconfig/dynamic_config.go mode change 100644 => 100755 internal/service/sysconfig/global.go mode change 100644 => 100755 internal/service/sysconfig/sysconfig.go mode change 100644 => 100755 internal/service/task_center/cache.go mode change 100644 => 100755 internal/service/task_center/constants.go mode change 100644 => 100755 internal/service/task_center/invite_logic_test.go mode change 100644 => 100755 internal/service/task_center/list_tasks_filter_test.go mode change 100644 => 100755 internal/service/task_center/service.go create mode 100644 internal/service/task_center/service_test.go mode change 100644 => 100755 internal/service/task_center/task_center_test.go mode change 100644 => 100755 internal/service/task_center/worker.go mode change 100644 => 100755 internal/service/title/assign.go mode change 100644 => 100755 internal/service/title/effect_validate.go mode change 100644 => 100755 internal/service/title/effect_validate_test.go mode change 100644 => 100755 internal/service/title/effects_resolver.go mode change 100644 => 100755 internal/service/user/address_share.go mode change 100644 => 100755 internal/service/user/addresses.go mode change 100644 => 100755 internal/service/user/batch_user.go mode change 100644 => 100755 internal/service/user/bind_inviter.go mode change 100644 => 100755 internal/service/user/cancel_shipping.go mode change 100644 => 100755 internal/service/user/coupon_add.go mode change 100644 => 100755 internal/service/user/coupon_transfer.go mode change 100644 => 100755 internal/service/user/coupons_list.go mode change 100644 => 100755 internal/service/user/error_test.go mode change 100644 => 100755 internal/service/user/expiration_task.go mode change 100644 => 100755 internal/service/user/game_pass.go mode change 100644 => 100755 internal/service/user/inventory_list.go mode change 100644 => 100755 internal/service/user/invite_code.go mode change 100644 => 100755 internal/service/user/invites_list.go mode change 100644 => 100755 internal/service/user/item_card_add.go mode change 100644 => 100755 internal/service/user/item_card_redeem.go mode change 100644 => 100755 internal/service/user/item_card_uses_list.go mode change 100644 => 100755 internal/service/user/item_cards_list.go mode change 100644 => 100755 internal/service/user/login_douyin.go mode change 100644 => 100755 internal/service/user/login_weixin.go mode change 100644 => 100755 internal/service/user/order_coupons.go mode change 100644 => 100755 internal/service/user/order_timeout.go mode change 100644 => 100755 internal/service/user/orders_action.go create mode 100644 internal/service/user/orders_auto_cancel_worker.go mode change 100644 => 100755 internal/service/user/orders_list.go mode change 100644 => 100755 internal/service/user/points_add.go mode change 100644 => 100755 internal/service/user/points_balance.go mode change 100644 => 100755 internal/service/user/points_consume.go mode change 100644 => 100755 internal/service/user/points_consume_generic.go mode change 100644 => 100755 internal/service/user/points_convert.go mode change 100644 => 100755 internal/service/user/points_ledger_list.go mode change 100644 => 100755 internal/service/user/profile.go mode change 100644 => 100755 internal/service/user/request_shipping_batch_test.go mode change 100644 => 100755 internal/service/user/reward_grant.go mode change 100644 => 100755 internal/service/user/reward_grant_batch.go mode change 100644 => 100755 internal/service/user/shipping_groups.go mode change 100644 => 100755 internal/service/user/sms_login.go mode change 100644 => 100755 internal/service/user/stats.go mode change 100644 => 100755 internal/service/user/user.go mode change 100644 => 100755 main.go mode change 100644 => 100755 migrations/006_game_tickets.sql mode change 100644 => 100755 migrations/20251218_add_draw_logs_unique_index.sql mode change 100644 => 100755 migrations/20251222_add_order_coupon_and_card.sql mode change 100644 => 100755 migrations/20251223_add_user_invites_effective_columns.sql mode change 100644 => 100755 migrations/20251223_fix_invite_count_comment.sql mode change 100644 => 100755 migrations/20251226_add_draw_index_unique.sql mode change 100644 => 100755 migrations/20251226_add_order_snapshots.sql mode change 100644 => 100755 migrations/20251229_douyin_orders.sql mode change 100644 => 100755 migrations/20260105_douyin_product_rewards.sql mode change 100644 => 100755 migrations/20260110_livestream_tables.sql mode change 100644 => 100755 migrations/20260117_livestream_commitment.sql mode change 100644 => 100755 migrations/20260118_douyin_blacklist.sql mode change 100644 => 100755 migrations/20260121_add_order_coupon_unique_index.sql mode change 100644 => 100755 migrations/20260121_add_user_remark.sql mode change 100644 => 100755 migrations/20260121_reconcile_coupon_data.sql mode change 100644 => 100755 migrations/20260121_repair_coupon_data.sql mode change 100644 => 100755 migrations/20260129_add_douyin_orders_fields.sql mode change 100644 => 100755 migrations/20260129_add_prize_reward_config.sql mode change 100644 => 100755 migrations/20260129_backfill_product_ids.sql mode change 100644 => 100755 migrations/20260130_add_activity_order_rewards.sql mode change 100644 => 100755 migrations/20260130_full_livestream_sync.sql mode change 100644 => 100755 migrations/20260130_rollback_prize_reward_config.sql mode change 100644 => 100755 migrations/20260131_product_rewards_multi_rules.sql mode change 100644 => 100755 migrations/20260203_add_product_id_to_draw_logs.sql mode change 100644 => 100755 migrations/20260206_add_task_tier_quota.sql mode change 100644 => 100755 migrations/20260217_add_coupon_show_in_miniapp.sql mode change 100644 => 100755 migrations/20260218_add_payer_openid_to_payment_transactions.sql mode change 100644 => 100755 migrations/20260218_add_product_show_in_miniapp.sql create mode 100755 migrations/20260219_add_ext_order_id_to_orders.sql mode change 100644 => 100755 migrations/matching_game_test_data.sql mode change 100644 => 100755 migrations/task_level_quota.sql mode change 100644 => 100755 scripts/swagger.bat mode change 100644 => 100755 tools/.DS_Store mode change 100644 => 100755 tools/livestream_lottery_analyzer/main.go mode change 100644 => 100755 tools/lottery_data_analyzer/main.go mode change 100644 => 100755 tools/lottery_probability_checker/main.go mode change 100644 => 100755 tools/lottery_verifier/main.go mode change 100644 => 100755 tools/lottery_verifier/verify.go mode change 100644 => 100755 tools/lottery_verifier_web/index.html mode change 100644 => 100755 tools/query_order/main.go mode change 100644 => 100755 tools/quick_check/main.go mode change 100644 => 100755 tools/test_matchmaker/go.mod mode change 100644 => 100755 tools/test_matchmaker/go.sum mode change 100644 => 100755 tools/test_matchmaker/main.go mode change 100644 => 100755 tools/verify_seed/main.go mode change 100644 => 100755 tools/wechat_debug/main.go diff --git a/.DS_Store b/.DS_Store old mode 100644 new mode 100755 diff --git a/.claude/plan/admin-update-user-mobile.md b/.claude/plan/admin-update-user-mobile.md old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/.vercelignore b/.vercelignore old mode 100644 new mode 100755 diff --git a/BUG_FIX_REPORT.md b/BUG_FIX_REPORT.md old mode 100644 new mode 100755 diff --git a/CLAUDE.md b/CLAUDE.md old mode 100644 new mode 100755 diff --git a/Dockerfile b/Dockerfile old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 diff --git a/README.md b/README.md old mode 100644 new mode 100755 diff --git a/bindboxgame.json b/bindboxgame.json old mode 100644 new mode 100755 diff --git a/build/.DS_Store b/build/.DS_Store old mode 100644 new mode 100755 diff --git a/build/resources/.DS_Store b/build/resources/.DS_Store old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/403-BdWuHcJA.svg b/build/resources/admin/assets/403-BdWuHcJA.svg old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/404-BzxNMzaO.svg b/build/resources/admin/assets/404-BzxNMzaO.svg old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/500-C-Ru4KUd.svg b/build/resources/admin/assets/500-C-Ru4KUd.svg old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/EffectEditDialog-BmAJKxJl.css b/build/resources/admin/assets/EffectEditDialog-BmAJKxJl.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/EffectManagerDialog-DnvqZPdh.css b/build/resources/admin/assets/EffectManagerDialog-DnvqZPdh.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css b/build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css.gz b/build/resources/admin/assets/LoginLeftView-BN4zi5Xi.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/TitleEditDialog-i9x-drPp.css b/build/resources/admin/assets/TitleEditDialog-i9x-drPp.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/UserAssignmentDialog-BTSnDJ3n.css b/build/resources/admin/assets/UserAssignmentDialog-BTSnDJ3n.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/_plugin-vue_export-helper-BCo6x5W8.js b/build/resources/admin/assets/_plugin-vue_export-helper-BCo6x5W8.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/about-project-DgJMbhc5.js b/build/resources/admin/assets/about-project-DgJMbhc5.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/avatar-pR7-E1hl.js b/build/resources/admin/assets/avatar-pR7-E1hl.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/avatar10-Dom60BwY.js b/build/resources/admin/assets/avatar10-Dom60BwY.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/avatar6-6Evj8BB9.js b/build/resources/admin/assets/avatar6-6Evj8BB9.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/avatar6-6Evj8BB9.js.gz b/build/resources/admin/assets/avatar6-6Evj8BB9.js.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/bg-DrCBEYh-.webp b/build/resources/admin/assets/bg-DrCBEYh-.webp old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/card-D34vavgk.css b/build/resources/admin/assets/card-D34vavgk.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/category-search-BqILMF9x.css b/build/resources/admin/assets/category-search-BqILMF9x.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/col-yED17g82.css b/build/resources/admin/assets/col-yED17g82.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/col-yED17g82.css.gz b/build/resources/admin/assets/col-yED17g82.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css b/build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css.gz b/build/resources/admin/assets/date-picker-panel-Dxdk0yRA.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/dialog-2KKj2Euo.css b/build/resources/admin/assets/dialog-2KKj2Euo.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-alert-G57rL0jl.css b/build/resources/admin/assets/el-alert-G57rL0jl.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-avatar-BmRr_O8d.css b/build/resources/admin/assets/el-avatar-BmRr_O8d.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-button-CDqfIFiK.css b/build/resources/admin/assets/el-button-CDqfIFiK.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-button-CDqfIFiK.css.gz b/build/resources/admin/assets/el-button-CDqfIFiK.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-card-fwQOLwdi.css b/build/resources/admin/assets/el-card-fwQOLwdi.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-checkbox-DIj50LEB.css b/build/resources/admin/assets/el-checkbox-DIj50LEB.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-col-DD1Vn-Yu.css b/build/resources/admin/assets/el-col-DD1Vn-Yu.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-col-DD1Vn-Yu.css.gz b/build/resources/admin/assets/el-col-DD1Vn-Yu.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css b/build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css.gz b/build/resources/admin/assets/el-date-picker-panel-BhfPqR_w.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-descriptions-item-o9ObloqJ.css b/build/resources/admin/assets/el-descriptions-item-o9ObloqJ.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-dialog-DyK7vRzj.css b/build/resources/admin/assets/el-dialog-DyK7vRzj.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-divider-BUtF_RGI.css b/build/resources/admin/assets/el-divider-BUtF_RGI.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-drawer-BhCnIJJ3.css b/build/resources/admin/assets/el-drawer-BhCnIJJ3.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-dropdown-item-11ZCvSOX.css b/build/resources/admin/assets/el-dropdown-item-11ZCvSOX.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-form-item-BWkJzdQ_.css b/build/resources/admin/assets/el-form-item-BWkJzdQ_.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-input-number-D6iOyBgb.css b/build/resources/admin/assets/el-input-number-D6iOyBgb.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-input-tPmZxDKr.css b/build/resources/admin/assets/el-input-tPmZxDKr.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-input-tPmZxDKr.css.gz b/build/resources/admin/assets/el-input-tPmZxDKr.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-option-BHqzF8z9.css b/build/resources/admin/assets/el-option-BHqzF8z9.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-overlay-Db7iXMEX.css b/build/resources/admin/assets/el-overlay-Db7iXMEX.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-pagination-BNQcHhjS.css b/build/resources/admin/assets/el-pagination-BNQcHhjS.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-popover-Cktl5fHm.css b/build/resources/admin/assets/el-popover-Cktl5fHm.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-popper-D1i0e6ba.css b/build/resources/admin/assets/el-popper-D1i0e6ba.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-progress-Dw9yTa91.css b/build/resources/admin/assets/el-progress-Dw9yTa91.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-radio-BuDgLcOG.css b/build/resources/admin/assets/el-radio-BuDgLcOG.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-radio-button-CSkroacn.css b/build/resources/admin/assets/el-radio-button-CSkroacn.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-radio-group-BzMpJalG.css b/build/resources/admin/assets/el-radio-group-BzMpJalG.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-row-C6BJsxyy.css b/build/resources/admin/assets/el-row-C6BJsxyy.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-scrollbar-BWxh-h6K.css b/build/resources/admin/assets/el-scrollbar-BWxh-h6K.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-select-DdmnTlAY.css b/build/resources/admin/assets/el-select-DdmnTlAY.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-switch-B5lTGWdM.css b/build/resources/admin/assets/el-switch-B5lTGWdM.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-table-column-CKoPG0Y8.css b/build/resources/admin/assets/el-table-column-CKoPG0Y8.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-table-column-CKoPG0Y8.css.gz b/build/resources/admin/assets/el-table-column-CKoPG0Y8.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-tag-DljBBxJR.css b/build/resources/admin/assets/el-tag-DljBBxJR.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-tooltip-l0sNRNKZ.js b/build/resources/admin/assets/el-tooltip-l0sNRNKZ.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-upload-q8uObtwj.css b/build/resources/admin/assets/el-upload-q8uObtwj.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/el-upload-q8uObtwj.css.gz b/build/resources/admin/assets/el-upload-q8uObtwj.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/favicon-C1KazUkF.ico b/build/resources/admin/assets/favicon-C1KazUkF.ico old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/form-item-B4F-CS9A.css b/build/resources/admin/assets/form-item-B4F-CS9A.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/grant-reward-dialog-D7-C3J8j.css b/build/resources/admin/assets/grant-reward-dialog-D7-C3J8j.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-0rmuCejF.css b/build/resources/admin/assets/index-0rmuCejF.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-86w9PCiC.css b/build/resources/admin/assets/index-86w9PCiC.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-B7q-DPlS.css b/build/resources/admin/assets/index-B7q-DPlS.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-BF_swEeW.css b/build/resources/admin/assets/index-BF_swEeW.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-BG9yZ82v.css b/build/resources/admin/assets/index-BG9yZ82v.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CDDDnorJ.css b/build/resources/admin/assets/index-CDDDnorJ.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CEpEmnur.css b/build/resources/admin/assets/index-CEpEmnur.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CEpEmnur.css.gz b/build/resources/admin/assets/index-CEpEmnur.css.gz old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CIZk353b.css b/build/resources/admin/assets/index-CIZk353b.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CTUKoMMr.css b/build/resources/admin/assets/index-CTUKoMMr.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CXjivOvk.css b/build/resources/admin/assets/index-CXjivOvk.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-CsQLNvm4.css b/build/resources/admin/assets/index-CsQLNvm4.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-DVtb5Tyi.css b/build/resources/admin/assets/index-DVtb5Tyi.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/index-DhfpFd1U.css b/build/resources/admin/assets/index-DhfpFd1U.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/input-number-BXCadU-U.css b/build/resources/admin/assets/input-number-BXCadU-U.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/lock_screen_1-CH_l421c.webp b/build/resources/admin/assets/lock_screen_1-CH_l421c.webp old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/login_icon-C4TVlUS8.svg b/build/resources/admin/assets/login_icon-C4TVlUS8.svg old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/new-user-3ZQ25ksW.css b/build/resources/admin/assets/new-user-3ZQ25ksW.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/order-funnel-CxvO8S8s.css b/build/resources/admin/assets/order-funnel-CxvO8S8s.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/points-economy-DgJMbhc5.js b/build/resources/admin/assets/points-economy-DgJMbhc5.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/popper-kmEP6Jl6.css b/build/resources/admin/assets/popper-kmEP6Jl6.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/product-search-Cun_ywfi.css b/build/resources/admin/assets/product-search-Cun_ywfi.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/radio-group-DfFloULT.css b/build/resources/admin/assets/radio-group-DfFloULT.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/refs-Cw5r5QN8.js b/build/resources/admin/assets/refs-Cw5r5QN8.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/role-permission-dialog-BYEPGzFo.css b/build/resources/admin/assets/role-permission-dialog-BYEPGzFo.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/scrollbar-C8iP3G9A.css b/build/resources/admin/assets/scrollbar-C8iP3G9A.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/sd-C0PQtrty.png b/build/resources/admin/assets/sd-C0PQtrty.png old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/select-C2cjPkEh.css b/build/resources/admin/assets/select-C2cjPkEh.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/slider-CppPl5od.css b/build/resources/admin/assets/slider-CppPl5od.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/space-3oFudasq.css b/build/resources/admin/assets/space-3oFudasq.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/tag-CtW1DIiB.css b/build/resources/admin/assets/tag-CtW1DIiB.css old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/token-DWNpOE8r.js b/build/resources/admin/assets/token-DWNpOE8r.js old mode 100644 new mode 100755 diff --git a/build/resources/admin/assets/yd-BrGqJ6Cs.png b/build/resources/admin/assets/yd-BrGqJ6Cs.png old mode 100644 new mode 100755 diff --git a/build/resources/admin/index.html b/build/resources/admin/index.html old mode 100644 new mode 100755 diff --git a/cmd/check_order/main.go b/cmd/check_order/main.go old mode 100644 new mode 100755 diff --git a/cmd/debug_check_coupon_22/main.go b/cmd/debug_check_coupon_22/main.go old mode 100644 new mode 100755 diff --git a/cmd/debug_coupon_9209/main.go b/cmd/debug_coupon_9209/main.go deleted file mode 100644 index 414e8c3..0000000 --- a/cmd/debug_coupon_9209/main.go +++ /dev/null @@ -1,76 +0,0 @@ -package main - -import ( - "bindbox-game/configs" - "bindbox-game/internal/repository/mysql" - "context" - "flag" - "fmt" -) - -func main() { - flag.Parse() - configs.Init() - - ctx := context.Background() - db, err := mysql.New() - if err != nil { - panic(err) - } - rawDB := db.GetDbR() - - // 1. status IN (2,4) 但 balance_amount > 0 的券(有余额却被标记为已使用/占用中) - fmt.Println("=== 异常券:status=2或4 但余额>0(应改为 status=1) ===") - var abnormal []struct { - ID int64 `gorm:"column:id"` - UserID int64 `gorm:"column:user_id"` - CouponID int64 `gorm:"column:coupon_id"` - Status int32 `gorm:"column:status"` - BalanceAmount int64 `gorm:"column:balance_amount"` - ValidEnd string `gorm:"column:valid_end"` - UsedAt string `gorm:"column:used_at"` - UsedOrderID int64 `gorm:"column:used_order_id"` - } - rawDB.WithContext(ctx).Raw(` - SELECT uc.id, uc.user_id, uc.coupon_id, uc.status, uc.balance_amount, uc.valid_end, uc.used_at, uc.used_order_id - FROM user_coupons uc - WHERE uc.status IN (2, 4) - AND uc.balance_amount > 0 - ORDER BY uc.user_id, uc.id - `).Scan(&abnormal) - - fmt.Printf("共 %d 条\n\n", len(abnormal)) - for _, c := range abnormal { - statusText := map[int32]string{2: "已使用", 4: "占用中"}[c.Status] - // 查券名 - var name string - rawDB.WithContext(ctx).Raw("SELECT name FROM system_coupons WHERE id = ?", c.CouponID).Scan(&name) - - fmt.Printf("券#%d | 用户#%d | %s(模板#%d) | status=%d(%s) | 余额=%d分(%.2f元) | 有效期至=%s\n", - c.ID, c.UserID, name, c.CouponID, c.Status, statusText, - c.BalanceAmount, float64(c.BalanceAmount)/100, c.ValidEnd) - } - - // 2. status=1 但 balance_amount=0 的券(没余额却还标记为未使用) - fmt.Println("\n=== 异常券:status=1 但余额=0(应改为 status=2) ===") - var zeroBalance []struct { - ID int64 `gorm:"column:id"` - UserID int64 `gorm:"column:user_id"` - CouponID int64 `gorm:"column:coupon_id"` - BalanceAmount int64 `gorm:"column:balance_amount"` - } - rawDB.WithContext(ctx).Raw(` - SELECT id, user_id, coupon_id, balance_amount - FROM user_coupons - WHERE status = 1 AND balance_amount = 0 - ORDER BY user_id, id - `).Scan(&zeroBalance) - - fmt.Printf("共 %d 条\n\n", len(zeroBalance)) - for _, c := range zeroBalance { - var name string - rawDB.WithContext(ctx).Raw("SELECT name FROM system_coupons WHERE id = ?", c.CouponID).Scan(&name) - fmt.Printf("券#%d | 用户#%d | %s(模板#%d) | status=1(未使用) | 余额=0\n", - c.ID, c.UserID, name, c.CouponID) - } -} diff --git a/cmd/debug_task_270/main.go b/cmd/debug_task_270/main.go old mode 100644 new mode 100755 diff --git a/cmd/debug_task_time/main.go b/cmd/debug_task_time/main.go new file mode 100755 index 0000000..f0b8ea7 --- /dev/null +++ b/cmd/debug_task_time/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "context" + "fmt" + "time" + + "bindbox-game/configs" + "bindbox-game/internal/pkg/logger" + "bindbox-game/internal/repository/mysql" + tcmodel "bindbox-game/internal/repository/mysql/task_center" + taskcenter "bindbox-game/internal/service/task_center" +) + +func main() { + // 1. 初始化 + configs.Init() + dbRepo, err := mysql.New() + if err != nil { + panic(err) + } + l, _ := logger.NewCustomLogger(logger.WithOutputInConsole()) + + // 这里简化 service 初始化,只传必要的 db + svc := taskcenter.New(l, dbRepo, nil, nil, nil) + ctx := context.Background() + + fmt.Println("=== 验证 1: 时区解析一致性 ===") + // 模拟管理后台输入 (本地时间) + startTimeStr := "2026-02-20 12:00:00" + loc, _ := time.LoadLocation("Local") + expectedT, _ := time.ParseInLocation("2006-01-02 15:04:05", startTimeStr, loc) + + // 直接通过 GORM 创建任务(模拟 Admin API 后的 Service 调用) + st := &expectedT + taskID, err := svc.CreateTask(ctx, taskcenter.CreateTaskInput{ + Name: "时区测试任务", + Status: 1, + Visibility: 1, + StartTime: st, + }) + if err != nil { + panic(err) + } + fmt.Printf("任务创建成功, ID: %d\n", taskID) + + // 从数据库读回 + var task tcmodel.Task + dbRepo.GetDbR().First(&task, taskID) + + fmt.Printf("输入本地时间: %s\n", startTimeStr) + fmt.Printf("数据库存储时间(Unix): %d\n", task.StartTime.Unix()) + fmt.Printf("预期时间戳(Unix): %d\n", expectedT.Unix()) + + if task.StartTime.Unix() == expectedT.Unix() { + fmt.Println("✅ [SUCCESS] 时区解析一致性验证通过") + } else { + fmt.Printf("❌ [FAILED] 时区解析不一致! 偏差: %d 秒\n", task.StartTime.Unix()-expectedT.Unix()) + } + + fmt.Println("\n=== 验证 2: 有效期过滤 (ListTasks) ===") + // 创建一个已经结束的任务 + pastTime := time.Now().Add(-24 * time.Hour) + svc.CreateTask(ctx, taskcenter.CreateTaskInput{ + Name: "过期任务", + Status: 1, + Visibility: 1, + EndTime: &pastTime, + }) + + // App 端查询 (OnlyActive=true) + list, total, _ := svc.ListTasks(ctx, taskcenter.ListTasksInput{Page: 1, PageSize: 10, OnlyActive: true}) + fmt.Printf("App 端展示任务数: %d / 总数: %d\n", len(list), total) + + foundPast := false + for _, item := range list { + if item.Name == "过期任务" { + foundPast = true + } + } + if !foundPast { + fmt.Println("✅ [SUCCESS] App 端成功过滤已过期任务") + } else { + fmt.Println("❌ [FAILED] App 端未能过滤已过期任务") + } + + fmt.Println("\n=== 验证 3: 领取拦截 (ClaimTier) ===") + err = svc.ClaimTier(ctx, 1, taskID, 1) // 尝试领取那个还没开始的任务 + if err != nil && err.Error() == "任务尚未开始" { + fmt.Printf("✅ [SUCCESS] 成功拦截未开始任务的领取行为: %v\n", err) + } else { + fmt.Printf("❌ [FAILED] 未能按预期拦截领取: %v\n", err) + } + + // 清理数据 (可选) + // dbRepo.GetDbW().Delete(&tcmodel.Task{}, taskID) +} diff --git a/cmd/fix_openid/main.go b/cmd/fix_openid/main.go old mode 100644 new mode 100755 diff --git a/cmd/gormgen/README.md b/cmd/gormgen/README.md old mode 100644 new mode 100755 diff --git a/cmd/gormgen/main.go b/cmd/gormgen/main.go old mode 100644 new mode 100755 diff --git a/cmd/mfmt/README.md b/cmd/mfmt/README.md old mode 100644 new mode 100755 diff --git a/cmd/mfmt/main.go b/cmd/mfmt/main.go old mode 100644 new mode 100755 diff --git a/configs/.DS_Store b/configs/.DS_Store old mode 100644 new mode 100755 diff --git a/configs/cert/apiclient_cert.pem b/configs/cert/apiclient_cert.pem old mode 100644 new mode 100755 diff --git a/configs/cert/apiclient_key.pem b/configs/cert/apiclient_key.pem old mode 100644 new mode 100755 diff --git a/configs/cert/pub_key.pem b/configs/cert/pub_key.pem old mode 100644 new mode 100755 diff --git a/configs/configs.go b/configs/configs.go old mode 100644 new mode 100755 diff --git a/configs/constants.go b/configs/constants.go old mode 100644 new mode 100755 diff --git a/configs/dev_configs.toml b/configs/dev_configs.toml old mode 100644 new mode 100755 diff --git a/configs/fat_configs.toml b/configs/fat_configs.toml old mode 100644 new mode 100755 diff --git a/configs/pro_configs.toml b/configs/pro_configs.toml old mode 100644 new mode 100755 diff --git a/configs/uat_configs.toml b/configs/uat_configs.toml old mode 100644 new mode 100755 diff --git a/deploy/.env b/deploy/.env old mode 100644 new mode 100755 diff --git a/deploy/project/docker-compose.yaml b/deploy/project/docker-compose.yaml old mode 100644 new mode 100755 diff --git a/deploy/project/swagger.yaml b/deploy/project/swagger.yaml old mode 100644 new mode 100755 diff --git a/docs/.DS_Store b/docs/.DS_Store old mode 100644 new mode 100755 diff --git a/docs/docs.go b/docs/docs.go old mode 100644 new mode 100755 diff --git a/docs/swagger.json b/docs/swagger.json old mode 100644 new mode 100755 diff --git a/docs/swagger.yaml b/docs/swagger.yaml old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/ACCEPTANCE_优化抖音定时任务.md b/docs/优化抖音定时任务/ACCEPTANCE_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/ALIGNMENT_优化抖音定时任务.md b/docs/优化抖音定时任务/ALIGNMENT_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/CONSENSUS_优化抖音定时任务.md b/docs/优化抖音定时任务/CONSENSUS_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/DESIGN_优化抖音定时任务.md b/docs/优化抖音定时任务/DESIGN_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/FINAL_优化抖音定时任务.md b/docs/优化抖音定时任务/FINAL_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/TASK_优化抖音定时任务.md b/docs/优化抖音定时任务/TASK_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/docs/优化抖音定时任务/TODO_优化抖音定时任务.md b/docs/优化抖音定时任务/TODO_优化抖音定时任务.md old mode 100644 new mode 100755 diff --git a/go.mod b/go.mod old mode 100644 new mode 100755 index 597e765..d826e7f --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 github.com/alibabacloud-go/dysmsapi-20170525/v4 v4.1.3 github.com/alibabacloud-go/tea v1.3.14 + github.com/alicebob/miniredis/v2 v2.36.1 github.com/bwmarrin/snowflake v0.3.0 github.com/bytedance/sonic v1.13.2 github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be @@ -44,6 +45,7 @@ require ( go.uber.org/multierr v1.10.0 go.uber.org/zap v1.26.0 golang.org/x/crypto v0.44.0 + golang.org/x/sync v0.18.0 golang.org/x/tools v0.38.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gorm.io/datatypes v1.1.1-0.20230130040222-c43177d3cf8c @@ -63,7 +65,6 @@ require ( github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect github.com/alibabacloud-go/openapi-util v0.1.1 // indirect github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect - github.com/alicebob/miniredis/v2 v2.36.1 // indirect github.com/aliyun/credentials-go v1.4.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect @@ -84,7 +85,6 @@ require ( github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/swag v0.19.15 // indirect github.com/goccy/go-json v0.10.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-querystring v1.0.0 // indirect github.com/google/uuid v1.6.0 // indirect @@ -129,7 +129,6 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.47.0 // indirect - golang.org/x/sync v0.18.0 // indirect golang.org/x/sys v0.39.0 // indirect golang.org/x/text v0.31.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect diff --git a/go.sum b/go.sum old mode 100644 new mode 100755 index 8c6927b..01999e0 --- a/go.sum +++ b/go.sum @@ -206,8 +206,6 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= diff --git a/internal/.DS_Store b/internal/.DS_Store old mode 100644 new mode 100755 diff --git a/internal/alert/alert.go b/internal/alert/alert.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/activities_app.go b/internal/api/activity/activities_app.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/app.go b/internal/api/activity/app.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/draw_logs_app.go b/internal/api/activity/draw_logs_app.go old mode 100644 new mode 100755 index c4e8723..a3ab152 --- a/internal/api/activity/draw_logs_app.go +++ b/internal/api/activity/draw_logs_app.go @@ -236,7 +236,7 @@ func (h *handler) ListDrawLogsByLevel() core.HandlerFunc { // 1. 获取所有中奖记录 // 我们假设这里不需要分页,或者分页逻辑比较复杂(每个等级分页?)。 - // 根据需求描述“按奖品等级进行归类”,通常 implied 展示所有或者前N个。 + // 根据需求描述"按奖品等级进行归类”,通常 implied 展示所有或者前N个。 // 这里暂且获取所有(或者一个较大的限制),然后在内存中分组。 // 如果数据量巨大,需要由 Service 层提供 Group By 查询。 // 考虑到单期中奖人数通常有限(除非是大规模活动),先尝试获取列表后分组。 diff --git a/internal/api/activity/issue_choices_app.go b/internal/api/activity/issue_choices_app.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/issues_app.go b/internal/api/activity/issues_app.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/lottery_app.go b/internal/api/activity/lottery_app.go old mode 100644 new mode 100755 index 923bf69..5fad912 --- a/internal/api/activity/lottery_app.go +++ b/internal/api/activity/lottery_app.go @@ -136,7 +136,13 @@ func (h *handler) JoinLottery() core.HandlerFunc { } if activity.AllowCoupons && req.CouponID != nil && *req.CouponID > 0 { - applied = h.applyCouponWithCap(ctx, userID, order, req.ActivityID, *req.CouponID) + var applyErr error + applied, applyErr = h.applyCouponWithCap(ctx, userID, order, req.ActivityID, *req.CouponID) + if applyErr != nil { + h.logger.Warn(fmt.Sprintf("JoinLottery Coupon Inapplicable: UserID=%d CouponID=%d Err=%v", userID, *req.CouponID, applyErr)) + ctx.AbortWithError(core.Error(http.StatusBadRequest, 170009, applyErr.Error())) + return + } if applied > 0 { order.CouponID = *req.CouponID } @@ -405,11 +411,11 @@ func (h *handler) JoinLottery() core.HandlerFunc { res := tx.Orders.UnderlyingDB().Exec(` UPDATE user_coupons SET balance_amount = balance_amount - ?, - status = CASE WHEN balance_amount - ? <= 0 THEN 2 ELSE 1 END, + status = 4, used_order_id = ?, used_at = ? WHERE id = ? AND user_id = ? AND balance_amount >= ? AND status IN (1, 4) - `, applied, applied, order.ID, now, order.CouponID, userID, applied) + `, applied, order.ID, now, order.CouponID, userID, applied) if res.Error != nil { return fmt.Errorf("优惠券预扣失败: %w", res.Error) diff --git a/internal/api/activity/lottery_app_test.go b/internal/api/activity/lottery_app_test.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/lottery_helper.go b/internal/api/activity/lottery_helper.go old mode 100644 new mode 100755 index 7f2734b..517812b --- a/internal/api/activity/lottery_helper.go +++ b/internal/api/activity/lottery_helper.go @@ -33,8 +33,8 @@ type couponJoinResult struct { // - activityID:活动ID用于范围校验 // - userCouponID:用户持券ID // -// 返回:本次实际应用的抵扣金额(分);若不适用或受封顶为0则返回0 -func (h *handler) applyCouponWithCap(ctx core.Context, userID int64, order *model.Orders, activityID int64, userCouponID int64) int64 { +// 返回:本次实际应用的抵扣金额(分);如果不可用则返回错误 +func (h *handler) applyCouponWithCap(ctx core.Context, userID int64, order *model.Orders, activityID int64, userCouponID int64) (int64, error) { // 使用单次 JOIN 查询替代 3 次分离查询,减少数据库往返 var result couponJoinResult err := h.repo.GetDbR().Raw(` @@ -55,46 +55,81 @@ func (h *handler) applyCouponWithCap(ctx core.Context, userID int64, order *mode LIMIT 1 `, userCouponID, userID).Scan(&result).Error - if err != nil || result.UserCouponID == 0 { - return 0 + if err != nil { + return 0, fmt.Errorf("查询优惠券失败: %v", err) + } + if result.UserCouponID == 0 { + // 诊断原因:为什么没查到? + var statusCheck struct { + Status int32 + } + if err := h.repo.GetDbR().Raw("SELECT status FROM user_coupons WHERE id = ? AND user_id = ?", userCouponID, userID).Scan(&statusCheck).Error; err == nil { + if statusCheck.Status == 2 { + return 0, fmt.Errorf("该优惠券已用完或已核销") + } + if statusCheck.Status == 3 { + return 0, fmt.Errorf("该优惠券已过期") + } + } + // 查 system_coupons + var scStatus int32 + if err := h.repo.GetDbR().Raw("SELECT sc.status FROM user_coupons uc JOIN system_coupons sc ON uc.coupon_id = sc.id WHERE uc.id = ?", userCouponID).Scan(&scStatus).Error; err == nil { + if scStatus != 1 { + return 0, fmt.Errorf("该类优惠券已在系统下架") + } + } + return 0, fmt.Errorf("优惠券不存在或状态异常") } now := time.Now() if result.ValidStart.After(now) { - return 0 + return 0, fmt.Errorf("优惠券尚未到生效时间 (开始时间: %s)", result.ValidStart.Format("2006-01-02 15:04:05")) } if !result.ValidEnd.IsZero() && result.ValidEnd.Before(now) { - return 0 + return 0, fmt.Errorf("优惠券已过期 (过期时间: %s)", result.ValidEnd.Format("2006-01-02 15:04:05")) } scopeOK := (result.ScopeType == 1) || (result.ScopeType == 2 && result.ActivityID == activityID) if !scopeOK { - return 0 + limitActivityName := "" + if result.ActivityID > 0 { + var act model.Activities + if err := h.repo.GetDbR().Where("id = ?", result.ActivityID).First(&act).Error; err == nil { + limitActivityName = act.Name + } + } + if limitActivityName != "" { + return 0, fmt.Errorf("该优惠券仅限活动【%s】使用", limitActivityName) + } + return 0, fmt.Errorf("优惠券不适用于当前活动") } if order.TotalAmount < result.MinSpend { - return 0 + return 0, fmt.Errorf("未达到优惠券最小使用门槛 (订单金额: %d, 起用金额: %d)", order.TotalAmount, result.MinSpend) } - cap := order.TotalAmount / 2 - remainingCap := cap - order.DiscountAmount + + // 计算封顶 + capLimit := order.TotalAmount / 2 // 最高抵扣 50% + remainingCap := capLimit - order.DiscountAmount if remainingCap <= 0 { - return 0 + return 0, fmt.Errorf("该订单已达到优惠金额封顶限制 (最高优惠订单额的 50%%)") } + applied := int64(0) switch result.DiscountType { - case 1: // 金额券 + case 1: // 金额券 (支持部分使用) bal := result.BalanceAmount if bal > 0 { if bal > remainingCap { - applied = remainingCap + applied = remainingCap // 余额够 50%,则按 50% 抵扣 } else { - applied = bal + applied = bal // 余额不足 50%,则全额用掉余额 } } - case 2: // 满减券 + case 2: // 满减券 (一次性) applied = result.DiscountValue if applied > remainingCap { applied = remainingCap } - case 3: // 折扣券 + case 3: // 折扣券 (一次性) rate := result.DiscountValue if rate < 0 { rate = 0 @@ -102,24 +137,26 @@ func (h *handler) applyCouponWithCap(ctx core.Context, userID int64, order *mode if rate > 1000 { rate = 1000 } - newAmt := order.ActualAmount * rate / 1000 - d := order.ActualAmount - newAmt + // 计算折扣掉的金额 + d := order.ActualAmount - (order.ActualAmount * rate / 1000) if d > remainingCap { applied = remainingCap } else { applied = d } } + if applied > order.ActualAmount { applied = order.ActualAmount } + if applied <= 0 { - return 0 + return 0, fmt.Errorf("当前订单状态无法再应用更多优惠 (封顶或金额不足)") } order.DiscountAmount += applied order.ActualAmount -= applied order.Remark = order.Remark + fmt.Sprintf("|c:%d:%d", userCouponID, applied) - return applied + return applied, nil } // preDeductCouponInTx 在事务中预扣优惠券余额 diff --git a/internal/api/activity/lottery_result_order_app.go b/internal/api/activity/lottery_result_order_app.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/matching_game_app.go b/internal/api/activity/matching_game_app.go old mode 100644 new mode 100755 index 4d29f94..65884eb --- a/internal/api/activity/matching_game_app.go +++ b/internal/api/activity/matching_game_app.go @@ -163,7 +163,7 @@ func (h *handler) PreOrderMatchingGame() core.HandlerFunc { return } - // 直接创建“已支付”订单 + // 直接创建"已支付”订单 orderNo := now.Format("20060102150405") + fmt.Sprintf("%04d", now.UnixNano()%10000) newOrder := &model.Orders{ UserID: userID, diff --git a/internal/api/activity/matching_game_app_test.go b/internal/api/activity/matching_game_app_test.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/matching_game_helper.go b/internal/api/activity/matching_game_helper.go old mode 100644 new mode 100755 diff --git a/internal/api/activity/rewards_app.go b/internal/api/activity/rewards_app.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/activities_admin.go b/internal/api/admin/activities_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/activity_categories.go b/internal/api/admin/activity_categories.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/activity_commitment_admin.go b/internal/api/admin/activity_commitment_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/admin.go b/internal/api/admin/admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/auth_refresh.go b/internal/api/admin/auth_refresh.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/banner.go b/internal/api/admin/banner.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/blacklist_admin.go b/internal/api/admin/blacklist_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/channels.go b/internal/api/admin/channels.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/dashboard_activity.go b/internal/api/admin/dashboard_activity.go old mode 100644 new mode 100755 index 88333d3..832210f --- a/internal/api/admin/dashboard_activity.go +++ b/internal/api/admin/dashboard_activity.go @@ -315,8 +315,9 @@ func (h *handler) DashboardActivityProfitLoss() core.HandlerFunc { } type activityLogsRequest struct { - Page int `form:"page"` - PageSize int `form:"page_size"` + Page int `form:"page"` + PageSize int `form:"page_size"` + UserID int64 `form:"user_id"` } type activityLogItem struct { @@ -384,10 +385,13 @@ func (h *handler) DashboardActivityLogs() core.HandlerFunc { db := h.repo.GetDbR().WithContext(ctx.RequestContext()) var total int64 - db.Table(model.TableNameActivityDrawLogs). + countQuery := db.Table(model.TableNameActivityDrawLogs). Joins("JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id"). - Where("activity_issues.activity_id = ?", activityID). - Count(&total) + Where("activity_issues.activity_id = ?", activityID) + if req.UserID > 0 { + countQuery = countQuery.Where("activity_draw_logs.user_id = ?", req.UserID) + } + countQuery.Count(&total) var logs []struct { ID int64 @@ -416,7 +420,7 @@ func (h *handler) DashboardActivityLogs() core.HandlerFunc { CreatedAt time.Time } - err := db.Table(model.TableNameActivityDrawLogs). + logsQuery := db.Table(model.TableNameActivityDrawLogs). Select(` activity_draw_logs.id, activity_draw_logs.user_id, @@ -453,7 +457,11 @@ func (h *handler) DashboardActivityLogs() core.HandlerFunc { Joins("LEFT JOIN user_item_cards ON user_item_cards.id = orders.item_card_id"). Joins("LEFT JOIN system_item_cards ON system_item_cards.id = user_item_cards.card_id"). Joins("LEFT JOIN (SELECT order_id, COUNT(*) as draw_count FROM activity_draw_logs GROUP BY order_id) as order_draw_counts ON order_draw_counts.order_id = activity_draw_logs.order_id"). - Where("activity_issues.activity_id = ?", activityID). + Where("activity_issues.activity_id = ?", activityID) + if req.UserID > 0 { + logsQuery = logsQuery.Where("activity_draw_logs.user_id = ?", req.UserID) + } + err := logsQuery. Order("activity_draw_logs.id DESC"). Offset((req.Page - 1) * req.PageSize). Limit(req.PageSize). @@ -623,10 +631,10 @@ type ensureActivityProfitLossMenuResponse struct { MenuID int64 `json:"menu_id"` } -// EnsureActivityProfitLossMenu 确保运营分析下存在“活动盈亏”菜单 +// EnsureActivityProfitLossMenu 确保运营分析下存在"活动盈亏”菜单 func (h *handler) EnsureActivityProfitLossMenu() core.HandlerFunc { return func(ctx core.Context) { - // 1. 查找是否存在“控制台”或者“运营中心”类的父菜单 + // 1. 查找是否存在"控制台”或者"运营中心”类的父菜单 // 很多系统会将概览放在 Dashboard 下。根据 titles_seed.go,运营是 Operations。 parent, _ := h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Name.Eq("Operations")).First() var parentID int64 diff --git a/internal/api/admin/dashboard_admin.go b/internal/api/admin/dashboard_admin.go old mode 100644 new mode 100755 index 4b73a2b..ac8958c --- a/internal/api/admin/dashboard_admin.go +++ b/internal/api/admin/dashboard_admin.go @@ -709,8 +709,8 @@ func parseRange(rangeType, startS, endS string) (time.Time, time.Time) { if st, err := time.Parse("2006-01-02", startS); err == nil { if et, err := time.Parse("2006-01-02", endS); err == nil { et = et.Add(24 * time.Hour).Add(-time.Second) - if et.Sub(st) > 31*24*time.Hour { - et = st.Add(30 * 24 * time.Hour).Add(-time.Second) + if et.Sub(st) > 365*24*time.Hour { + et = st.Add(365 * 24 * time.Hour).Add(-time.Second) } return st, et } diff --git a/internal/api/admin/dashboard_admin_test.go b/internal/api/admin/dashboard_admin_test.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/dashboard_spending.go b/internal/api/admin/dashboard_spending.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/dashboard_user_spending.go b/internal/api/admin/dashboard_user_spending.go new file mode 100755 index 0000000..97a0cbf --- /dev/null +++ b/internal/api/admin/dashboard_user_spending.go @@ -0,0 +1,250 @@ +package admin + +import ( + "fmt" + "net/http" + "strconv" + "time" + + "bindbox-game/internal/code" + "bindbox-game/internal/pkg/core" + "bindbox-game/internal/pkg/validation" + "bindbox-game/internal/repository/mysql/model" +) + +type userSpendingRequest struct { + RangeType string `form:"rangeType"` + StartDate string `form:"start"` + EndDate string `form:"end"` +} + +// userActivitySpending 用户在具体活动实例上的消费统计 +type userActivitySpending struct { + ActivityID int64 `json:"activity_id"` + ActivityName string `json:"activity_name"` + CategoryID int64 `json:"category_id"` + CategoryName string `json:"category_name"` // 一番赏/盲盒/对对碰/直播间 + Spending int64 `json:"spending"` // 消费金额(分) + PrizeValue int64 `json:"prize_value"` // 产出价值(分) + Profit int64 `json:"profit"` // 收益(分) + OrderCount int64 `json:"order_count"` // 订单数 +} + +type userSpendingResponse struct { + UserID int64 `json:"user_id"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + TotalSpend int64 `json:"total_spend"` + TotalPrize int64 `json:"total_prize"` + TotalProfit int64 `json:"total_profit"` + TotalOrders int64 `json:"total_orders"` + Activities []userActivitySpending `json:"activities"` +} + +var categoryNames = map[int64]string{ + 1: "一番赏", + 2: "盲盒/无限", + 3: "对对碰", +} + +func (h *handler) GetUserSpendingDashboard() core.HandlerFunc { + return func(ctx core.Context) { + userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递用户ID")) + return + } + req := new(userSpendingRequest) + if err := ctx.ShouldBindForm(req); err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) + return + } + + var start, end time.Time + hasRange := req.RangeType != "" && req.RangeType != "all" + if hasRange { + start, end = parseRange(req.RangeType, req.StartDate, req.EndDate) + } + + db := h.repo.GetDbR().WithContext(ctx.RequestContext()) + rsp := &userSpendingResponse{UserID: userID} + + // 获取用户基本信息 + user, _ := h.readDB.Users.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Users.ID.Eq(userID)).First() + if user != nil { + rsp.Nickname = user.Nickname + rsp.Avatar = user.Avatar + } + + // 1. 按活动实例统计消费 + type activityStat struct { + ActivityID int64 + ActivityName string + CategoryID int64 + Spending int64 + OrderCount int64 + } + var actStats []activityStat + + query := db.Table(model.TableNameOrders). + Joins("LEFT JOIN activity_draw_logs ON activity_draw_logs.order_id = orders.id"). + Joins("LEFT JOIN activity_issues ON activity_issues.id = activity_draw_logs.issue_id"). + Joins("LEFT JOIN activities ON activities.id = activity_issues.activity_id"). + Where("orders.user_id = ?", userID). + Where("orders.status = ?", 2) + + if hasRange { + query = query.Where("orders.created_at >= ?", start).Where("orders.created_at <= ?", end) + } + + if err := query.Select(` + COALESCE(activities.id, 0) as activity_id, + COALESCE(activities.name, '其他') as activity_name, + COALESCE(activities.activity_category_id, 0) as category_id, + SUM(orders.total_amount) as spending, + COUNT(DISTINCT orders.id) as order_count + `). + Group("COALESCE(activities.id, 0)"). + Order("spending DESC"). + Scan(&actStats).Error; err != nil { + h.logger.Error(fmt.Sprintf("UserSpending SQL error: %v", err)) + ctx.AbortWithError(core.Error(http.StatusBadRequest, 21030, err.Error())) + return + } + + // 2. 按活动实例统计产出价值 + type prizeStat struct { + ActivityID int64 + PrizeValue int64 + } + var prizeStats []prizeStat + + prizeQuery := db.Table(model.TableNameUserInventory). + Joins("JOIN products ON products.id = user_inventory.product_id"). + Where("user_inventory.user_id = ?", userID). + Where("user_inventory.status IN ?", []int{1, 3}). + Where("user_inventory.remark NOT LIKE ?", "%void%") + + if hasRange { + prizeQuery = prizeQuery.Where("user_inventory.created_at >= ?", start).Where("user_inventory.created_at <= ?", end) + } + + prizeQuery.Select(` + COALESCE(user_inventory.activity_id, 0) as activity_id, + SUM(products.price) as prize_value + `). + Group("COALESCE(user_inventory.activity_id, 0)"). + Scan(&prizeStats) + + prizeMap := make(map[int64]int64) + for _, p := range prizeStats { + prizeMap[p.ActivityID] = p.PrizeValue + } + + // 3. 直播间消费统计 + type livestreamStat struct { + ActivityID int64 + ActivityName string + Spending int64 + OrderCount int64 + } + var lsStats []livestreamStat + + lsQuery := db.Table("douyin_orders"). + Joins("LEFT JOIN livestream_activities ON livestream_activities.id = douyin_orders.livestream_activity_id"). + Select(` + COALESCE(douyin_orders.livestream_activity_id, 0) as activity_id, + COALESCE(livestream_activities.name, '直播间') as activity_name, + SUM(actual_pay_amount) as spending, + COUNT(*) as order_count + `). + Where("CAST(local_user_id AS SIGNED) = ?", userID). + Where("local_user_id != '' AND local_user_id != '0'") + + if hasRange { + lsQuery = lsQuery.Where("douyin_orders.created_at >= ?", start).Where("douyin_orders.created_at <= ?", end) + } + + lsQuery.Group("COALESCE(douyin_orders.livestream_activity_id, 0)").Scan(&lsStats) + + // 直播间产出 + type lsPrizeStat struct { + ActivityID int64 + PrizeValue int64 + } + var lsPrizeStats []lsPrizeStat + lsPrizeQuery := db.Table("livestream_draw_logs"). + Joins("JOIN products ON products.id = livestream_draw_logs.product_id"). + Select(` + livestream_draw_logs.livestream_activity_id as activity_id, + SUM(products.price) as prize_value + `). + Where("livestream_draw_logs.local_user_id = ?", userID). + Where("livestream_draw_logs.is_refunded = 0"). + Where("livestream_draw_logs.product_id > 0") + + if hasRange { + lsPrizeQuery = lsPrizeQuery.Where("livestream_draw_logs.created_at >= ?", start).Where("livestream_draw_logs.created_at <= ?", end) + } + + lsPrizeQuery.Group("livestream_draw_logs.livestream_activity_id").Scan(&lsPrizeStats) + + lsPrizeMap := make(map[int64]int64) + for _, p := range lsPrizeStats { + lsPrizeMap[p.ActivityID] = p.PrizeValue + } + + // 4. 组装结果 + activities := make([]userActivitySpending, 0) + var totalSpend, totalPrize, totalOrders int64 + + for _, s := range actStats { + prize := prizeMap[s.ActivityID] + catName := categoryNames[s.CategoryID] + if catName == "" { + catName = "其他" + } + item := userActivitySpending{ + ActivityID: s.ActivityID, + ActivityName: s.ActivityName, + CategoryID: s.CategoryID, + CategoryName: catName, + Spending: s.Spending, + PrizeValue: prize, + Profit: s.Spending - prize, + OrderCount: s.OrderCount, + } + activities = append(activities, item) + totalSpend += s.Spending + totalPrize += prize + totalOrders += s.OrderCount + } + + // 追加直播间活动 + for _, ls := range lsStats { + prize := lsPrizeMap[ls.ActivityID] + item := userActivitySpending{ + ActivityID: ls.ActivityID + 100000, // 避免和普通活动 ID 冲突 + ActivityName: ls.ActivityName, + CategoryID: 4, + CategoryName: "直播间", + Spending: ls.Spending, + PrizeValue: prize, + Profit: ls.Spending - prize, + OrderCount: ls.OrderCount, + } + activities = append(activities, item) + totalSpend += ls.Spending + totalPrize += prize + totalOrders += ls.OrderCount + } + + rsp.TotalSpend = totalSpend + rsp.TotalPrize = totalPrize + rsp.TotalProfit = totalSpend - totalPrize + rsp.TotalOrders = totalOrders + rsp.Activities = activities + + ctx.Payload(rsp) + } +} diff --git a/internal/api/admin/douyin_orders_admin.go b/internal/api/admin/douyin_orders_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/douyin_product_rewards.go b/internal/api/admin/douyin_product_rewards.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/game_pass_packages_admin.go b/internal/api/admin/game_pass_packages_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/game_passes_admin.go b/internal/api/admin/game_passes_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/ichiban_slots_admin.go b/internal/api/admin/ichiban_slots_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/issues_admin.go b/internal/api/admin/issues_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/item_cards_admin.go b/internal/api/admin/item_cards_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/livestream_admin.go b/internal/api/admin/livestream_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/livestream_stats.go b/internal/api/admin/livestream_stats.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/login.go b/internal/api/admin/login.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/lottery_admin.go b/internal/api/admin/lottery_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/matching_audit_admin.go b/internal/api/admin/matching_audit_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/matching_cards_admin.go b/internal/api/admin/matching_cards_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/miniapp_qrcode.go b/internal/api/admin/miniapp_qrcode.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/miniapp_shipping_admin.go b/internal/api/admin/miniapp_shipping_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/order_snapshot_admin.go b/internal/api/admin/order_snapshot_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/pay_orders_admin.go b/internal/api/admin/pay_orders_admin.go old mode 100644 new mode 100755 index 3cd5b04..ec5ca3b --- a/internal/api/admin/pay_orders_admin.go +++ b/internal/api/admin/pay_orders_admin.go @@ -3,6 +3,7 @@ package admin import ( "fmt" "net/http" + "strings" "time" "go.uber.org/zap" @@ -209,6 +210,66 @@ func (h *handler) ListPayOrders() core.HandlerFunc { } } + // 批量查询活动及其分类信息 + var activityIDs []int64 + orderActivityMap := make(map[int64]int64) // orderID -> activityID + for _, o := range rows { + remark := o.Remark + p := 0 + var aid int64 + for i := 0; i <= len(remark); i++ { + if i == len(remark) || remark[i] == '|' { + seg := remark[p:i] + if len(seg) > len("lottery:activity:") && seg[:len("lottery:activity:")] == "lottery:activity:" { + var n int64 + for j := len("lottery:activity:"); j < len(seg); j++ { + c := seg[j] + if c < '0' || c > '9' { + break + } + n = n*10 + int64(c-'0') + } + aid = n + } + if aid == 0 && len(seg) > len("activity:") && seg[:len("activity:")] == "activity:" { + var n int64 + for j := len("activity:"); j < len(seg); j++ { + c := seg[j] + if c < '0' || c > '9' { + break + } + n = n*10 + int64(c-'0') + } + aid = n + } + p = i + 1 + } + } + if aid > 0 { + activityIDs = append(activityIDs, aid) + orderActivityMap[o.ID] = aid + } + } + + activityMap := make(map[int64]*model.Activities) + categoryMap := make(map[int64]*model.ActivityCategories) + if len(activityIDs) > 0 { + acts, _ := h.readDB.Activities.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.Activities.ID.In(activityIDs...)).Find() + var categoryIDs []int64 + for _, act := range acts { + activityMap[act.ID] = act + if act.ActivityCategoryID > 0 { + categoryIDs = append(categoryIDs, act.ActivityCategoryID) + } + } + if len(categoryIDs) > 0 { + cats, _ := h.readDB.ActivityCategories.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.ActivityCategories.ID.In(categoryIDs...)).Find() + for _, cat := range cats { + categoryMap[cat.ID] = cat + } + } + } + out := make([]map[string]any, 0, len(rows)) for _, o := range rows { ledgers, err := h.readDB.UserPointsLedger.WithContext(ctx.RequestContext()).ReadDB().Where(h.readDB.UserPointsLedger.RefTable.Eq("orders"), h.readDB.UserPointsLedger.RefID.Eq(o.OrderNo)).Find() @@ -245,11 +306,44 @@ func (h *handler) ListPayOrders() core.HandlerFunc { } } + // 3. 解析业务模式与活动名称 + bizMode := "未知" + activityName := "-" + + if o.SourceType == 1 { + bizMode = "商城直购" + } else if o.SourceType == 2 { + bizMode = "抽奖活动" // 默认 + aid := orderActivityMap[o.ID] + if aid > 0 { + if act, ok := activityMap[aid]; ok { + activityName = act.Name + if cat, ok2 := categoryMap[act.ActivityCategoryID]; ok2 { + bizMode = cat.Name + } + } + } + // 兜底逻辑:扫雷等特殊标识解析 + if bizMode == "抽奖活动" || bizMode == "未知" { + remark := o.Remark + if strings.Contains(remark, "game:mode:minesweeper") { + bizMode = "扫雷" + } else if strings.Contains(remark, "game:mode:matching") { + bizMode = "对对碰" + } + } + } else if o.SourceType == 3 { + bizMode = "系统发放" + } + item := map[string]any{ "id": o.ID, "order_no": o.OrderNo, "user_id": o.UserID, "source_type": o.SourceType, + "biz_mode": bizMode, + "activity_name": activityName, + "ext_order_id": o.ExtOrderID, "actual_amount": o.ActualAmount, "status": o.Status, "paid_at": func() string { diff --git a/internal/api/admin/pay_orders_export.go b/internal/api/admin/pay_orders_export.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/pay_reconcile_admin.go b/internal/api/admin/pay_reconcile_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/pay_refund_admin.go b/internal/api/admin/pay_refund_admin.go old mode 100644 new mode 100755 index 1488a49..04d722f --- a/internal/api/admin/pay_refund_admin.go +++ b/internal/api/admin/pay_refund_admin.go @@ -139,19 +139,28 @@ func (h *handler) CreateRefund() core.HandlerFunc { _ = h.repo.GetDbR().Raw("SELECT oc.user_coupon_id, oc.applied_amount, sc.discount_type, sc.discount_value, uc.balance_amount FROM order_coupons oc JOIN user_coupons uc ON uc.id=oc.user_coupon_id JOIN system_coupons sc ON sc.id=uc.coupon_id WHERE oc.order_id=?", order.ID).Scan(&rows).Error for _, r := range rows { if r.UserCouponID > 0 && r.AppliedAmount > 0 { + // 统一退款回扣逻辑:余额加回去,并根据余额决定状态 newBal := r.BalanceAmount + r.AppliedAmount - if r.DiscountType == 1 { // 直金额券:判断回退后是否全满 - if newBal >= r.DiscountValue { - _ = h.repo.GetDbW().Exec("UPDATE user_coupons SET balance_amount=?, status=1, used_order_id=0, used_at=NULL WHERE id=?", r.DiscountValue, r.UserCouponID).Error - newBal = r.DiscountValue - } else { - // 若金额未满,维持 status=2 (已使用/使用中) - _ = h.repo.GetDbW().Exec("UPDATE user_coupons SET balance_amount=?, status=2 WHERE id=?", newBal, r.UserCouponID).Error - } - } else { // 满减/折扣券:一律恢复为未使用 - _ = h.repo.GetDbW().Exec("UPDATE user_coupons SET status=1, used_order_id=0, used_at=NULL WHERE id=?", r.UserCouponID).Error - newBal = 0 + // 容错:回退金额不能超过券面总额 + if newBal > r.DiscountValue { + newBal = r.DiscountValue } + + finalStatus := int32(1) + if newBal <= 0 { + finalStatus = 2 + } + + // 执行更新 + _ = h.repo.GetDbW().Exec(` + UPDATE user_coupons + SET balance_amount = ?, + status = ?, + used_order_id = CASE WHEN ? = 2 THEN used_order_id ELSE 0 END, + used_at = CASE WHEN ? = 2 THEN used_at ELSE NULL END + WHERE id = ? + `, newBal, finalStatus, finalStatus, finalStatus, r.UserCouponID).Error + // 记录流水 ledger := &model.UserCouponLedger{ UserID: order.UserID, diff --git a/internal/api/admin/product_batch.go b/internal/api/admin/product_batch.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/product_category_create.go b/internal/api/admin/product_category_create.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/product_create.go b/internal/api/admin/product_create.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/rewards_admin.go b/internal/api/admin/rewards_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/scheduled_config_admin.go b/internal/api/admin/scheduled_config_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/shipping_orders_admin.go b/internal/api/admin/shipping_orders_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/shipping_stats_admin.go b/internal/api/admin/shipping_stats_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_configs_admin.go b/internal/api/admin/system_configs_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_coupons.go b/internal/api/admin/system_coupons.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_menu.go b/internal/api/admin/system_menu.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_recycle.go b/internal/api/admin/system_recycle.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_role.go b/internal/api/admin/system_role.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/system_user.go b/internal/api/admin/system_user.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/titles_admin.go b/internal/api/admin/titles_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/titles_seed.go b/internal/api/admin/titles_seed.go old mode 100644 new mode 100755 index 891281b..4cd622b --- a/internal/api/admin/titles_seed.go +++ b/internal/api/admin/titles_seed.go @@ -1,178 +1,178 @@ package admin import ( - "encoding/json" - "net/http" + "encoding/json" + "net/http" - "bindbox-game/internal/code" - "bindbox-game/internal/pkg/core" - "bindbox-game/internal/repository/mysql/model" + "bindbox-game/internal/code" + "bindbox-game/internal/pkg/core" + "bindbox-game/internal/repository/mysql/model" ) type seedDefaultTitlesResponse struct { - Created int `json:"created"` - Exists int `json:"exists"` - IDs []int64 `json:"ids"` + Created int `json:"created"` + Exists int `json:"exists"` + IDs []int64 `json:"ids"` } // SeedDefaultTitles 内置6个称号与基础效果 func (h *handler) SeedDefaultTitles() core.HandlerFunc { - return func(ctx core.Context) { - // 定义默认称号与效果 - type def struct { - Name string - Description string - EffectType int32 - Params map[string]interface{} - Stack int32 - CapX1000 int32 - } - defs := []def{ - {Name: "优惠券使者", Description: "可领取优惠券", EffectType: 1, Params: map[string]interface{}{ "template_id": 0, "frequency": map[string]interface{}{"period": "day", "times": 1}}, Stack: 1, CapX1000: 0}, - {Name: "折扣官", Description: "抽奖购票折扣", EffectType: 2, Params: map[string]interface{}{ "discount_type": "percentage", "value_x1000": 100, "max_discount_x1000": 300}, Stack: 0, CapX1000: 300}, - {Name: "签到达人", Description: "签到双倍积分", EffectType: 3, Params: map[string]interface{}{ "multiplier_x1000": 2000, "daily_cap_points": 0}, Stack: 1, CapX1000: 3000}, - {Name: "卡牌使者", Description: "可领取道具卡", EffectType: 4, Params: map[string]interface{}{ "template_id": 0, "frequency": map[string]interface{}{"period": "week", "times": 2}}, Stack: 1, CapX1000: 0}, - {Name: "幸运加成者", Description: "抽奖概率加成", EffectType: 5, Params: map[string]interface{}{ "target_prize_ids": []int64{}, "boost_x1000": 100, "cap_x1000": 300}, Stack: 1, CapX1000: 300}, - {Name: "双倍之王", Description: "奖品双倍概率", EffectType: 6, Params: map[string]interface{}{ "target_prize_ids": []int64{}, "chance_x1000": 200, "period_cap_times": 1}, Stack: 1, CapX1000: 500}, - } - created := 0 - exists := 0 - var ids []int64 - for _, d := range defs { - // 是否存在同名称号 - row, _ := h.readDB.SystemTitles.WithContext(ctx.RequestContext()).Where(h.readDB.SystemTitles.Name.Eq(d.Name)).First() - if row != nil { - exists++ - ids = append(ids, row.ID) - continue - } - // 创建称号(使用模型类型) - newRow := &model.SystemTitles{ - Name: d.Name, - Description: d.Description, - Status: 1, - ObtainRulesJSON: "{}", - ScopesJSON: "{}", - } - if err := h.writeDB.SystemTitles.WithContext(ctx.RequestContext()).Create(newRow); err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号失败")) - return - } - // 重新读取ID - trow, _ := h.readDB.SystemTitles.WithContext(ctx.RequestContext()).Where(h.readDB.SystemTitles.Name.Eq(d.Name)).First() - if trow == nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号失败(读取)")) - return - } - ids = append(ids, trow.ID) - created++ - // 创建效果 - paramsBytes, _ := json.Marshal(d.Params) - eff := &model.SystemTitleEffects{ - TitleID: trow.ID, - EffectType: d.EffectType, - ParamsJSON: string(paramsBytes), - StackingStrategy: d.Stack, - CapValueX1000: d.CapX1000, - ScopesJSON: "{}", - Sort: 1, - Status: 1, - } - if err := h.writeDB.SystemTitleEffects.WithContext(ctx.RequestContext()).Create(eff); err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号效果失败")) - return - } - } - ctx.Payload(&seedDefaultTitlesResponse{Created: created, Exists: exists, IDs: ids}) - } + return func(ctx core.Context) { + // 定义默认称号与效果 + type def struct { + Name string + Description string + EffectType int32 + Params map[string]interface{} + Stack int32 + CapX1000 int32 + } + defs := []def{ + {Name: "优惠券使者", Description: "可领取优惠券", EffectType: 1, Params: map[string]interface{}{"template_id": 0, "frequency": map[string]interface{}{"period": "day", "times": 1}}, Stack: 1, CapX1000: 0}, + {Name: "折扣官", Description: "抽奖购票折扣", EffectType: 2, Params: map[string]interface{}{"discount_type": "percentage", "value_x1000": 100, "max_discount_x1000": 300}, Stack: 0, CapX1000: 300}, + {Name: "签到达人", Description: "签到双倍积分", EffectType: 3, Params: map[string]interface{}{"multiplier_x1000": 2000, "daily_cap_points": 0}, Stack: 1, CapX1000: 3000}, + {Name: "卡牌使者", Description: "可领取道具卡", EffectType: 4, Params: map[string]interface{}{"template_id": 0, "frequency": map[string]interface{}{"period": "week", "times": 2}}, Stack: 1, CapX1000: 0}, + {Name: "幸运加成者", Description: "抽奖概率加成", EffectType: 5, Params: map[string]interface{}{"target_prize_ids": []int64{}, "boost_x1000": 100, "cap_x1000": 300}, Stack: 1, CapX1000: 300}, + {Name: "双倍之王", Description: "奖品双倍概率", EffectType: 6, Params: map[string]interface{}{"target_prize_ids": []int64{}, "chance_x1000": 200, "period_cap_times": 1}, Stack: 1, CapX1000: 500}, + } + created := 0 + exists := 0 + var ids []int64 + for _, d := range defs { + // 是否存在同名称号 + row, _ := h.readDB.SystemTitles.WithContext(ctx.RequestContext()).Where(h.readDB.SystemTitles.Name.Eq(d.Name)).First() + if row != nil { + exists++ + ids = append(ids, row.ID) + continue + } + // 创建称号(使用模型类型) + newRow := &model.SystemTitles{ + Name: d.Name, + Description: d.Description, + Status: 1, + ObtainRulesJSON: "{}", + ScopesJSON: "{}", + } + if err := h.writeDB.SystemTitles.WithContext(ctx.RequestContext()).Create(newRow); err != nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号失败")) + return + } + // 重新读取ID + trow, _ := h.readDB.SystemTitles.WithContext(ctx.RequestContext()).Where(h.readDB.SystemTitles.Name.Eq(d.Name)).First() + if trow == nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号失败(读取)")) + return + } + ids = append(ids, trow.ID) + created++ + // 创建效果 + paramsBytes, _ := json.Marshal(d.Params) + eff := &model.SystemTitleEffects{ + TitleID: trow.ID, + EffectType: d.EffectType, + ParamsJSON: string(paramsBytes), + StackingStrategy: d.Stack, + CapValueX1000: d.CapX1000, + ScopesJSON: "{}", + Sort: 1, + Status: 1, + } + if err := h.writeDB.SystemTitleEffects.WithContext(ctx.RequestContext()).Create(eff); err != nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号效果失败")) + return + } + } + ctx.Payload(&seedDefaultTitlesResponse{Created: created, Exists: exists, IDs: ids}) + } } type ensureTitlesMenuResponse struct { - Ensured bool `json:"ensured"` - Parent int64 `json:"parent_id"` - MenuID int64 `json:"menu_id"` + Ensured bool `json:"ensured"` + Parent int64 `json:"parent_id"` + MenuID int64 `json:"menu_id"` } -// EnsureTitlesMenu 确保运营菜单下存在“称号管理”子菜单 +// EnsureTitlesMenu 确保运营菜单下存在"称号管理”子菜单 func (h *handler) EnsureTitlesMenu() core.HandlerFunc { - return func(ctx core.Context) { - // 查找运营菜单父节点 - parent, _ := h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Name.Eq("Operations")).First() - var parentID int64 - if parent == nil { - // 创建运营父菜单 - pm := &model.Menus{ - ParentID: 0, - Path: "/operations", - Name: "Operations", - Component: "/index/index", - Icon: "ri:tools-line", - Sort: 10, - Status: true, - KeepAlive: true, - IsHide: false, - IsHideTab: false, - CreatedUser: "system", - UpdatedUser: "system", - } - if err := h.writeDB.Menus.WithContext(ctx.RequestContext()).Create(pm); err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建运营菜单失败")) - return - } - // 读取 - parent, _ = h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Name.Eq("Operations")).First() - if parent == nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建运营菜单失败(读取)")) - return - } - } - parentID = parent.ID - // 查找称号菜单 - titlesMenu, _ := h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Path.Eq("titles")).Where(h.readDB.Menus.ParentID.Eq(parentID)).First() - if titlesMenu == nil { - tm := &struct { - ParentID int64 - Path string - Name string - Component string - Icon string - Sort int32 - Status bool - KeepAlive bool - IsHide bool - IsHideTab bool - }{ - ParentID: parentID, - Path: "titles", - Name: "Titles", - Component: "/operations/titles", - Icon: "ri:medal-line", - Sort: 50, - Status: true, - KeepAlive: true, - IsHide: false, - IsHideTab: false, - } - mm := &model.Menus{ - ParentID: tm.ParentID, - Path: tm.Path, - Name: tm.Name, - Component: tm.Component, - Icon: tm.Icon, - Sort: tm.Sort, - Status: tm.Status, - KeepAlive: tm.KeepAlive, - IsHide: tm.IsHide, - IsHideTab: tm.IsHideTab, - CreatedUser: "system", - UpdatedUser: "system", - } - if err := h.writeDB.Menus.WithContext(ctx.RequestContext()).Create(mm); err != nil { - ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号菜单失败")) - return - } - titlesMenu, _ = h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Path.Eq("titles")).Where(h.readDB.Menus.ParentID.Eq(parentID)).First() - } - ctx.Payload(&ensureTitlesMenuResponse{Ensured: true, Parent: parentID, MenuID: titlesMenu.ID}) - } -} \ No newline at end of file + return func(ctx core.Context) { + // 查找运营菜单父节点 + parent, _ := h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Name.Eq("Operations")).First() + var parentID int64 + if parent == nil { + // 创建运营父菜单 + pm := &model.Menus{ + ParentID: 0, + Path: "/operations", + Name: "Operations", + Component: "/index/index", + Icon: "ri:tools-line", + Sort: 10, + Status: true, + KeepAlive: true, + IsHide: false, + IsHideTab: false, + CreatedUser: "system", + UpdatedUser: "system", + } + if err := h.writeDB.Menus.WithContext(ctx.RequestContext()).Create(pm); err != nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建运营菜单失败")) + return + } + // 读取 + parent, _ = h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Name.Eq("Operations")).First() + if parent == nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建运营菜单失败(读取)")) + return + } + } + parentID = parent.ID + // 查找称号菜单 + titlesMenu, _ := h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Path.Eq("titles")).Where(h.readDB.Menus.ParentID.Eq(parentID)).First() + if titlesMenu == nil { + tm := &struct { + ParentID int64 + Path string + Name string + Component string + Icon string + Sort int32 + Status bool + KeepAlive bool + IsHide bool + IsHideTab bool + }{ + ParentID: parentID, + Path: "titles", + Name: "Titles", + Component: "/operations/titles", + Icon: "ri:medal-line", + Sort: 50, + Status: true, + KeepAlive: true, + IsHide: false, + IsHideTab: false, + } + mm := &model.Menus{ + ParentID: tm.ParentID, + Path: tm.Path, + Name: tm.Name, + Component: tm.Component, + Icon: tm.Icon, + Sort: tm.Sort, + Status: tm.Status, + KeepAlive: tm.KeepAlive, + IsHide: tm.IsHide, + IsHideTab: tm.IsHideTab, + CreatedUser: "system", + UpdatedUser: "system", + } + if err := h.writeDB.Menus.WithContext(ctx.RequestContext()).Create(mm); err != nil { + ctx.AbortWithError(core.Error(http.StatusInternalServerError, code.ServerError, "创建称号菜单失败")) + return + } + titlesMenu, _ = h.readDB.Menus.WithContext(ctx.RequestContext()).Where(h.readDB.Menus.Path.Eq("titles")).Where(h.readDB.Menus.ParentID.Eq(parentID)).First() + } + ctx.Payload(&ensureTitlesMenuResponse{Ensured: true, Parent: parentID, MenuID: titlesMenu.ID}) + } +} diff --git a/internal/api/admin/user_token_admin.go b/internal/api/admin/user_token_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_admin.go b/internal/api/admin/users_admin.go old mode 100644 new mode 100755 index 8840636..c09e867 --- a/internal/api/admin/users_admin.go +++ b/internal/api/admin/users_admin.go @@ -2055,3 +2055,118 @@ func (h *handler) DeleteUser() core.HandlerFunc { }) } } + +// adminBindInviterRequest 管理端绑定/修改邀请人请求 +type adminBindInviterRequest struct { + InviterUserID int64 `json:"inviter_user_id"` // 0 = 解绑 +} + +// adminSearchUserRequest 搜索用户请求(用于邀请人选择) +type adminSearchUserRequest struct { + Keyword string `form:"keyword"` // ID 或手机号 +} + +// AdminBindInviter 管理端修改用户邀请人 +// @Summary 管理端修改用户邀请人 +// @Description 运营可强制绑定/修改/解绑用户的邀请人,inviter_user_id=0 时为解绑 +// @Tags 管理端.用户 +// @Accept json +// @Produce json +// @Param user_id path integer true "被操作用户ID" +// @Param body body adminBindInviterRequest true "新邀请人ID" +// @Success 200 {object} user.AdminBindInviterOutput +// @Failure 400 {object} code.Failure +// @Router /api/admin/users/{user_id}/inviter [put] +// @Security LoginVerifyToken +func (h *handler) AdminBindInviter() core.HandlerFunc { + return func(ctx core.Context) { + userID, err := strconv.ParseInt(ctx.Param("user_id"), 10, 64) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "用户ID无效")) + return + } + + req := new(adminBindInviterRequest) + if err := ctx.ShouldBindJSON(req); err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) + return + } + + // 从会话获取操作人ID + operatorID := int64(ctx.SessionUserInfo().Id) + + result, err := h.userSvc.AdminBindInviter(ctx.RequestContext(), user.AdminBindInviterInput{ + TargetUserID: userID, + InviterUserID: req.InviterUserID, + OperatorID: operatorID, + }) + if err != nil { + msg := err.Error() + switch msg { + case "target_user_not_found": + msg = "目标用户不存在" + case "inviter_user_not_found": + msg = "邀请人用户不存在" + case "cannot_invite_self": + msg = "不能将自己设为邀请人" + } + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, msg)) + return + } + + ctx.Payload(result) + } +} + +// AdminSearchUsers 管理端搜索用户(供邀请人选择框使用) +// @Summary 搜索用户 +// @Description 按 ID 或手机号模糊搜索,用于邀请人选择 +// @Tags 管理端.用户 +// @Accept json +// @Produce json +// @Param keyword query string true "用户ID或手机号" +// @Success 200 {object} map[string]any +// @Router /api/admin/users/search [get] +// @Security LoginVerifyToken +func (h *handler) AdminSearchUsers() core.HandlerFunc { + return func(ctx core.Context) { + req := new(adminSearchUserRequest) + if err := ctx.ShouldBindForm(req); err != nil || req.Keyword == "" { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "keyword 不能为空")) + return + } + + type userItem struct { + ID int64 `json:"id"` + Nickname string `json:"nickname"` + Mobile string `json:"mobile"` + Avatar string `json:"avatar"` + } + + q := h.readDB.Users.WithContext(ctx.RequestContext()) + + // 尝试按 ID 精确匹配 + if id, err := strconv.ParseInt(req.Keyword, 10, 64); err == nil { + rows, _ := q.Where(h.readDB.Users.ID.Eq(id)).Limit(10).Find() + items := make([]userItem, 0, len(rows)) + for _, r := range rows { + items = append(items, userItem{ID: r.ID, Nickname: r.Nickname, Mobile: r.Mobile, Avatar: r.Avatar}) + } + ctx.Payload(map[string]any{"list": items}) + return + } + + // 按手机号或昵称模糊匹配 + rows, _ := q.Where( + h.readDB.Users.Mobile.Like("%"+req.Keyword+"%"), + ).Or( + h.readDB.Users.Nickname.Like("%"+req.Keyword+"%"), + ).Limit(10).Find() + + items := make([]userItem, 0, len(rows)) + for _, r := range rows { + items = append(items, userItem{ID: r.ID, Nickname: r.Nickname, Mobile: r.Mobile, Avatar: r.Avatar}) + } + ctx.Payload(map[string]any{"list": items}) + } +} diff --git a/internal/api/admin/users_admin_optimized.go b/internal/api/admin/users_admin_optimized.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_admin_test.go b/internal/api/admin/users_admin_test.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_batch_admin.go b/internal/api/admin/users_batch_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_profile.go b/internal/api/admin/users_profile.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_profit_loss.go b/internal/api/admin/users_profit_loss.go old mode 100644 new mode 100755 diff --git a/internal/api/admin/users_reward_admin.go b/internal/api/admin/users_reward_admin.go old mode 100644 new mode 100755 diff --git a/internal/api/app/banner.go b/internal/api/app/banner.go old mode 100644 new mode 100755 diff --git a/internal/api/app/categories.go b/internal/api/app/categories.go old mode 100644 new mode 100755 diff --git a/internal/api/app/coupon_transfer.go b/internal/api/app/coupon_transfer.go old mode 100644 new mode 100755 diff --git a/internal/api/app/notice.go b/internal/api/app/notice.go old mode 100644 new mode 100755 diff --git a/internal/api/app/product.go b/internal/api/app/product.go old mode 100644 new mode 100755 diff --git a/internal/api/app/product_category.go b/internal/api/app/product_category.go old mode 100644 new mode 100755 diff --git a/internal/api/app/store.go b/internal/api/app/store.go old mode 100644 new mode 100755 diff --git a/internal/api/app/store_test.go b/internal/api/app/store_test.go old mode 100644 new mode 100755 diff --git a/internal/api/common/common.go b/internal/api/common/common.go old mode 100644 new mode 100755 diff --git a/internal/api/common/config.go b/internal/api/common/config.go old mode 100644 new mode 100755 diff --git a/internal/api/common/openid_app.go b/internal/api/common/openid_app.go old mode 100644 new mode 100755 diff --git a/internal/api/common/upload_wangeditor.go b/internal/api/common/upload_wangeditor.go old mode 100644 new mode 100755 diff --git a/internal/api/game/handler.go b/internal/api/game/handler.go old mode 100644 new mode 100755 diff --git a/internal/api/game/handler_test.go b/internal/api/game/handler_test.go old mode 100644 new mode 100755 diff --git a/internal/api/internal/minesweeper/handler.go b/internal/api/internal/minesweeper/handler.go old mode 100644 new mode 100755 index 3747bda..9505aee --- a/internal/api/internal/minesweeper/handler.go +++ b/internal/api/internal/minesweeper/handler.go @@ -76,12 +76,12 @@ func (h *Handler) SettleGame() core.HandlerFunc { } // TODO: 实际结算逻辑 - // 1. 验证 ticket 状态为“进行中” + // 1. 验证 ticket 状态为"进行中” // 2. 如果 Win=true,发放奖励 - // 3. 标记 ticket 为“已完成” + // 3. 标记 ticket 为"已完成” - h.logger.Info("Game Settled", - zap.String("user_id", req.UserID), + h.logger.Info("Game Settled", + zap.String("user_id", req.UserID), zap.Bool("win", req.Win), zap.Int("score", req.Score), ) diff --git a/internal/api/pay/pay.go b/internal/api/pay/pay.go old mode 100644 new mode 100755 diff --git a/internal/api/pay/wechat_notify.go b/internal/api/pay/wechat_notify.go old mode 100644 new mode 100755 diff --git a/internal/api/public/livestream_public.go b/internal/api/public/livestream_public.go old mode 100644 new mode 100755 diff --git a/internal/api/task_center/README.md b/internal/api/task_center/README.md old mode 100644 new mode 100755 diff --git a/internal/api/task_center/admin.go b/internal/api/task_center/admin.go old mode 100644 new mode 100755 index 9f32e74..4990ac5 --- a/internal/api/task_center/admin.go +++ b/internal/api/task_center/admin.go @@ -20,7 +20,7 @@ import ( // @Router /admin/task_center/tasks [get] func (h *handler) ListTasksForAdmin() core.HandlerFunc { return func(ctx core.Context) { - items, total, err := h.task.ListTasks(ctx.RequestContext(), struct{ Page, PageSize int }{Page: 1, PageSize: 50}) + items, total, err := h.task.ListTasks(ctx.RequestContext(), tasksvc.ListTasksInput{Page: 1, PageSize: 50, OnlyActive: false}) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return @@ -71,14 +71,20 @@ func (h *handler) CreateTaskForAdmin() core.HandlerFunc { } var st, et *time.Time if req.StartTime != "" { - if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil { - st = &t + t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "开始时间格式错误: "+err.Error())) + return } + st = &t } if req.EndTime != "" { - if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil { - et = &t + t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "结束时间格式错误: "+err.Error())) + return } + et = &t } id, err := h.task.CreateTask(ctx.RequestContext(), tasksvc.CreateTaskInput{Name: req.Name, Description: req.Description, Status: req.Status, Visibility: req.Visibility, Quota: req.Quota, StartTime: st, EndTime: et}) if err != nil { @@ -122,14 +128,20 @@ func (h *handler) ModifyTaskForAdmin() core.HandlerFunc { } var st, et *time.Time if req.StartTime != "" { - if t, err := time.Parse("2006-01-02 15:04:05", req.StartTime); err == nil { - st = &t + t, err := time.ParseInLocation("2006-01-02 15:04:05", req.StartTime, time.Local) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "开始时间格式错误: "+err.Error())) + return } + st = &t } if req.EndTime != "" { - if t, err := time.Parse("2006-01-02 15:04:05", req.EndTime); err == nil { - et = &t + t, err := time.ParseInLocation("2006-01-02 15:04:05", req.EndTime, time.Local) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "结束时间格式错误: "+err.Error())) + return } + et = &t } if err := h.task.ModifyTask(ctx.RequestContext(), id, tasksvc.ModifyTaskInput{Name: req.Name, Description: req.Description, Status: req.Status, Visibility: req.Visibility, Quota: req.Quota, StartTime: st, EndTime: et}); err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) @@ -353,3 +365,120 @@ func (h *handler) SimulateInviteSuccess() core.HandlerFunc { ctx.Payload(map[string]any{"ok": true}) } } + +// rewardStatItem 奖励发放统计项 +type rewardStatItem struct { + RewardType string `json:"reward_type"` + Count int64 `json:"count"` // 发放次数 + Quantity int64 `json:"quantity"` // 发放总数量 +} + +// rewardStatsResponse 奖励发放统计响应 +type rewardStatsResponse struct { + TaskID int64 `json:"task_id"` + TotalClaim int64 `json:"total_claim"` // 总领取人次 + Stats []rewardStatItem `json:"stats"` + Logs []rewardLogItem `json:"logs"` // 详细发放记录 +} + +// rewardLogItem 奖励发放记录 +type rewardLogItem struct { + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Nickname string `json:"nickname"` + TierID int64 `json:"tier_id"` + RewardType string `json:"reward_type"` + Quantity int64 `json:"quantity"` + CreatedAt string `json:"created_at"` +} + +// GetTaskRewardStats 获取任务奖励发放统计 +// @Summary 获取任务奖励发放统计(Admin) +// @Description 获取指定任务已发放奖励的按类型统计及详细记录 +// @Tags TaskCenter(Admin) +// @Accept json +// @Produce json +// @Param id path int true "任务ID" +// @Success 200 {object} rewardStatsResponse "奖励发放统计" +// @Router /admin/task_center/tasks/{id}/reward-stats [get] +func (h *handler) GetTaskRewardStats() core.HandlerFunc { + return func(ctx core.Context) { + taskID, err := strconv.ParseInt(ctx.Param("id"), 10, 64) + if err != nil { + ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, "未传递任务ID")) + return + } + + db := h.repo.GetDbR() + rsp := &rewardStatsResponse{TaskID: taskID} + + // 1. 统计总领取人次 (去重 user_id) + var totalClaim int64 + db.Raw("SELECT COUNT(DISTINCT user_id) FROM task_center_event_logs WHERE task_id = ?", taskID).Scan(&totalClaim) + rsp.TotalClaim = totalClaim + + // 2. 按奖励类型统计 + type statRow struct { + RewardType string + Cnt int64 + Qty int64 + } + var statRows []statRow + db.Raw(` + SELECT tr.reward_type, COUNT(el.id) as cnt, COALESCE(SUM(tr.quantity), 0) as qty + FROM task_center_event_logs el + LEFT JOIN task_center_task_rewards tr ON tr.task_id = el.task_id AND tr.tier_id = el.tier_id + WHERE el.task_id = ? + GROUP BY tr.reward_type + `, taskID).Scan(&statRows) + + stats := make([]rewardStatItem, 0, len(statRows)) + for _, r := range statRows { + rt := r.RewardType + if rt == "" { + rt = "unknown" + } + stats = append(stats, rewardStatItem{RewardType: rt, Count: r.Cnt, Quantity: r.Qty}) + } + rsp.Stats = stats + + // 3. 最近的发放记录 (最多100条) + type logRow struct { + ID int64 + UserID int64 + Nickname string + TierID int64 + RewardType string + Quantity int64 + CreatedAt time.Time + } + var logRows []logRow + db.Raw(` + SELECT el.id, el.user_id, COALESCE(u.nickname, '') as nickname, + el.tier_id, COALESCE(tr.reward_type, '') as reward_type, + COALESCE(tr.quantity, 0) as quantity, el.created_at + FROM task_center_event_logs el + LEFT JOIN users u ON u.id = el.user_id + LEFT JOIN task_center_task_rewards tr ON tr.task_id = el.task_id AND tr.tier_id = el.tier_id + WHERE el.task_id = ? + ORDER BY el.id DESC + LIMIT 100 + `, taskID).Scan(&logRows) + + logs := make([]rewardLogItem, len(logRows)) + for i, r := range logRows { + logs[i] = rewardLogItem{ + ID: r.ID, + UserID: r.UserID, + Nickname: r.Nickname, + TierID: r.TierID, + RewardType: r.RewardType, + Quantity: r.Quantity, + CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), + } + } + rsp.Logs = logs + + ctx.Payload(rsp) + } +} diff --git a/internal/api/task_center/app.go b/internal/api/task_center/app.go old mode 100644 new mode 100755 diff --git a/internal/api/task_center/tasks_app.go b/internal/api/task_center/tasks_app.go old mode 100644 new mode 100755 index ba87e2a..699a441 --- a/internal/api/task_center/tasks_app.go +++ b/internal/api/task_center/tasks_app.go @@ -4,6 +4,7 @@ import ( "bindbox-game/internal/code" "bindbox-game/internal/pkg/core" "bindbox-game/internal/pkg/validation" + tasksvc "bindbox-game/internal/service/task_center" "encoding/json" "net/http" "strconv" @@ -71,7 +72,7 @@ func (h *handler) ListTasksForApp() core.HandlerFunc { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ParamBindError, validation.Error(err))) return } - items, total, err := h.task.ListTasks(ctx.RequestContext(), struct{ Page, PageSize int }{Page: req.Page, PageSize: req.PageSize}) + items, total, err := h.task.ListTasks(ctx.RequestContext(), tasksvc.ListTasksInput{Page: req.Page, PageSize: req.PageSize, OnlyActive: true}) if err != nil { ctx.AbortWithError(core.Error(http.StatusBadRequest, code.ServerError, err.Error())) return diff --git a/internal/api/user/address_share_create_app.go b/internal/api/user/address_share_create_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/address_share_revoke_app.go b/internal/api/user/address_share_revoke_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/address_share_submit_public.go b/internal/api/user/address_share_submit_public.go old mode 100644 new mode 100755 diff --git a/internal/api/user/addresses_add_app.go b/internal/api/user/addresses_add_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/addresses_default_app.go b/internal/api/user/addresses_default_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/addresses_delete_app.go b/internal/api/user/addresses_delete_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/addresses_list_app.go b/internal/api/user/addresses_list_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/addresses_update_app.go b/internal/api/user/addresses_update_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/app.go b/internal/api/user/app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/bind_douyin_order_app.go b/internal/api/user/bind_douyin_order_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/bind_inviter_app.go b/internal/api/user/bind_inviter_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/cancel_shipping_app.go b/internal/api/user/cancel_shipping_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/coupons_app.go b/internal/api/user/coupons_app.go old mode 100644 new mode 100755 index f9d4c1a..29f97bd --- a/internal/api/user/coupons_app.go +++ b/internal/api/user/coupons_app.go @@ -165,27 +165,36 @@ func buildCouponRules(c *model.SystemCoupons) string { func calcCouponSubStatus(uc *model.UserCoupons, sc *model.SystemCoupons) (subStatus string, statusDesc string) { amount := sc.DiscountValue - switch uc.Status { - case 1: // 数据库 status=1 - if uc.BalanceAmount <= 0 { - // 余额=0 → 用完了 - return "depleted", "已用完" - } - if sc.DiscountType == 1 && uc.BalanceAmount < amount { - // 金额券且余额 < 面值 → 使用中 + // 1. 优先处理过期状态 + if uc.Status == 3 { + return "expired", "已过期" + } + + // 2. 根据余额和 DiscountType 判断 + if uc.BalanceAmount > 0 { + // 只要有余额,且在有效状态(1, 2, 4)内,就是可用的 + if uc.Status == 4 { return "in_use", "使用中" } - return "unused", "可用" - case 2: // 数据库 status=2 → 已使用(满减/折扣券核销) - if sc.DiscountType == 1 { - return "depleted", "已用完" + + // 如果是金额券且余额小于面值,则是使用中 + if sc.DiscountType == 1 && uc.BalanceAmount < amount { + return "in_use", "使用中" } - return "used", "已使用" - case 3: // 数据库 status=3 → 已过期 - return "expired", "已过期" - case 4: // 数据库 status=4 → 占用中(预扣),视为使用中 - return "in_use", "使用中" - default: + + // 默认可用 return "unused", "可用" } + + // 3. 余额为 0 的情况 + if sc.DiscountType == 1 { + return "depleted", "已用完" + } + + // 满减/折扣券,状态为 2 时标记为已使用 + if uc.Status == 2 { + return "used", "已使用" + } + + return "depleted", "已用完" } diff --git a/internal/api/user/coupons_stats_app.go b/internal/api/user/coupons_stats_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/coupons_usage_app.go b/internal/api/user/coupons_usage_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/coupons_usage_app_test.go b/internal/api/user/coupons_usage_app_test.go old mode 100644 new mode 100755 diff --git a/internal/api/user/game_passes_app.go b/internal/api/user/game_passes_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/inventory_app.go b/internal/api/user/inventory_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/invites_app.go b/internal/api/user/invites_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/item_cards_app.go b/internal/api/user/item_cards_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/login_app.go b/internal/api/user/login_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/login_douyin_app.go b/internal/api/user/login_douyin_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/orders_app.go b/internal/api/user/orders_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/orders_test_app.go b/internal/api/user/orders_test_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/pay_wechat_app.go b/internal/api/user/pay_wechat_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/phone_bind.go b/internal/api/user/phone_bind.go old mode 100644 new mode 100755 diff --git a/internal/api/user/phone_bind_douyin_app.go b/internal/api/user/phone_bind_douyin_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/points_app.go b/internal/api/user/points_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/points_redeem_coupon_app.go b/internal/api/user/points_redeem_coupon_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/points_redeem_item_card_app.go b/internal/api/user/points_redeem_item_card_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/points_redeem_product_app.go b/internal/api/user/points_redeem_product_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/profile_app.go b/internal/api/user/profile_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/redeem_inventory_app.go b/internal/api/user/redeem_inventory_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/request_shipping_app.go b/internal/api/user/request_shipping_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/request_shipping_batch_app.go b/internal/api/user/request_shipping_batch_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/shipping_groups_app.go b/internal/api/user/shipping_groups_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/sms_login_app.go b/internal/api/user/sms_login_app.go old mode 100644 new mode 100755 diff --git a/internal/api/user/stats_app.go b/internal/api/user/stats_app.go old mode 100644 new mode 100755 diff --git a/internal/api/wechat/mini_template.go b/internal/api/wechat/mini_template.go old mode 100644 new mode 100755 diff --git a/internal/code/README.md b/internal/code/README.md old mode 100644 new mode 100755 diff --git a/internal/code/code.go b/internal/code/code.go old mode 100644 new mode 100755 diff --git a/internal/code/zh-cn.go b/internal/code/zh-cn.go old mode 100644 new mode 100755 diff --git a/internal/dblogger/request_logger.go b/internal/dblogger/request_logger.go old mode 100644 new mode 100755 diff --git a/internal/metrics/metrics.go b/internal/metrics/metrics.go old mode 100644 new mode 100755 diff --git a/internal/metrics/prometheus.go b/internal/metrics/prometheus.go old mode 100644 new mode 100755 diff --git a/internal/pkg/async/task_queue.go b/internal/pkg/async/task_queue.go old mode 100644 new mode 100755 diff --git a/internal/pkg/color/string_darwin.go b/internal/pkg/color/string_darwin.go old mode 100644 new mode 100755 diff --git a/internal/pkg/color/string_linux.go b/internal/pkg/color/string_linux.go old mode 100644 new mode 100755 diff --git a/internal/pkg/color/string_windows.go b/internal/pkg/color/string_windows.go old mode 100644 new mode 100755 diff --git a/internal/pkg/core/context.go b/internal/pkg/core/context.go old mode 100644 new mode 100755 diff --git a/internal/pkg/core/core.go b/internal/pkg/core/core.go old mode 100644 new mode 100755 diff --git a/internal/pkg/core/error.go b/internal/pkg/core/error.go old mode 100644 new mode 100755 diff --git a/internal/pkg/cors/cors.go b/internal/pkg/cors/cors.go old mode 100644 new mode 100755 diff --git a/internal/pkg/cryptoaes/cryptoaes.go b/internal/pkg/cryptoaes/cryptoaes.go old mode 100644 new mode 100755 diff --git a/internal/pkg/cryptoaes/cryptoaes_test.go b/internal/pkg/cryptoaes/cryptoaes_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/cryptorsa/cryptorsa.go b/internal/pkg/cryptorsa/cryptorsa.go old mode 100644 new mode 100755 diff --git a/internal/pkg/cryptorsa/cryptorsa_test.go b/internal/pkg/cryptorsa/cryptorsa_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/debug/logger.go b/internal/pkg/debug/logger.go old mode 100644 new mode 100755 diff --git a/internal/pkg/douyin/access_token.go b/internal/pkg/douyin/access_token.go old mode 100644 new mode 100755 diff --git a/internal/pkg/douyin/code2session.go b/internal/pkg/douyin/code2session.go old mode 100644 new mode 100755 diff --git a/internal/pkg/douyin/phonenumber.go b/internal/pkg/douyin/phonenumber.go old mode 100644 new mode 100755 diff --git a/internal/pkg/env/env.go b/internal/pkg/env/env.go old mode 100644 new mode 100755 diff --git a/internal/pkg/errors/err.go b/internal/pkg/errors/err.go old mode 100644 new mode 100755 diff --git a/internal/pkg/errors/err_test.go b/internal/pkg/errors/err_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/httpclient/client.go b/internal/pkg/httpclient/client.go old mode 100644 new mode 100755 diff --git a/internal/pkg/idgen/idgen.go b/internal/pkg/idgen/idgen.go old mode 100644 new mode 100755 diff --git a/internal/pkg/idgen/idgen_test.go b/internal/pkg/idgen/idgen_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/jsonutil/jsonutil.go b/internal/pkg/jsonutil/jsonutil.go old mode 100644 new mode 100755 diff --git a/internal/pkg/jwtoken/jwtoken.go b/internal/pkg/jwtoken/jwtoken.go old mode 100644 new mode 100755 diff --git a/internal/pkg/jwtoken/jwtoken_test.go b/internal/pkg/jwtoken/jwtoken_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/logger/logger.go b/internal/pkg/logger/logger.go old mode 100644 new mode 100755 diff --git a/internal/pkg/logger/logger_test.go b/internal/pkg/logger/logger_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/miniprogram/access_token.go b/internal/pkg/miniprogram/access_token.go old mode 100644 new mode 100755 diff --git a/internal/pkg/miniprogram/access_token_test.go b/internal/pkg/miniprogram/access_token_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/miniprogram/subscribe.go b/internal/pkg/miniprogram/subscribe.go old mode 100644 new mode 100755 diff --git a/internal/pkg/miniprogram/subscribe_test.go b/internal/pkg/miniprogram/subscribe_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/notify/lottery_notify.go b/internal/pkg/notify/lottery_notify.go old mode 100644 new mode 100755 diff --git a/internal/pkg/otel/middleware.go b/internal/pkg/otel/middleware.go old mode 100644 new mode 100755 diff --git a/internal/pkg/otel/otel.go b/internal/pkg/otel/otel.go old mode 100644 new mode 100755 diff --git a/internal/pkg/pay/client.go b/internal/pkg/pay/client.go old mode 100644 new mode 100755 diff --git a/internal/pkg/pay/wechat.go b/internal/pkg/pay/wechat.go old mode 100644 new mode 100755 diff --git a/internal/pkg/points/convert.go b/internal/pkg/points/convert.go old mode 100644 new mode 100755 diff --git a/internal/pkg/points/convert_test.go b/internal/pkg/points/convert_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/redis/redis.go b/internal/pkg/redis/redis.go old mode 100644 new mode 100755 diff --git a/internal/pkg/shutdown/shutdown.go b/internal/pkg/shutdown/shutdown.go old mode 100644 new mode 100755 diff --git a/internal/pkg/sms/aliyun.go b/internal/pkg/sms/aliyun.go old mode 100644 new mode 100755 diff --git a/internal/pkg/startup/info.go b/internal/pkg/startup/info.go old mode 100644 new mode 100755 diff --git a/internal/pkg/timeutil/timeutil.go b/internal/pkg/timeutil/timeutil.go old mode 100644 new mode 100755 diff --git a/internal/pkg/timeutil/timeutil_test.go b/internal/pkg/timeutil/timeutil_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/debug.go b/internal/pkg/trace/debug.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/http.go b/internal/pkg/trace/http.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/mongo.go b/internal/pkg/trace/mongo.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/redis.go b/internal/pkg/trace/redis.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/sql.go b/internal/pkg/trace/sql.go old mode 100644 new mode 100755 diff --git a/internal/pkg/trace/trace.go b/internal/pkg/trace/trace.go old mode 100644 new mode 100755 diff --git a/internal/pkg/util/remark/remark.go b/internal/pkg/util/remark/remark.go old mode 100644 new mode 100755 diff --git a/internal/pkg/utils/truncate_test.go b/internal/pkg/utils/truncate_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/utils/utils.go b/internal/pkg/utils/utils.go old mode 100644 new mode 100755 diff --git a/internal/pkg/utils/utils_test.go b/internal/pkg/utils/utils_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/validation/validation.go b/internal/pkg/validation/validation.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/code2session.go b/internal/pkg/wechat/code2session.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/decrypt.go b/internal/pkg/wechat/decrypt.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/phone_number.go b/internal/pkg/wechat/phone_number.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/qrcode.go b/internal/pkg/wechat/qrcode.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/shipping.go b/internal/pkg/wechat/shipping.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/shipping_test.go b/internal/pkg/wechat/shipping_test.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/shortlink.go b/internal/pkg/wechat/shortlink.go old mode 100644 new mode 100755 diff --git a/internal/pkg/wechat/url_scheme.go b/internal/pkg/wechat/url_scheme.go old mode 100644 new mode 100755 diff --git a/internal/proposal/alert.go b/internal/proposal/alert.go old mode 100644 new mode 100755 diff --git a/internal/proposal/metrics.go b/internal/proposal/metrics.go old mode 100644 new mode 100755 diff --git a/internal/proposal/request_logger.go b/internal/proposal/request_logger.go old mode 100644 new mode 100755 diff --git a/internal/proposal/session.go b/internal/proposal/session.go old mode 100644 new mode 100755 diff --git a/internal/repository/.DS_Store b/internal/repository/.DS_Store old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/bindbox.db b/internal/repository/mysql/bindbox.db old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activities.gen.go b/internal/repository/mysql/dao/activities.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_categories.gen.go b/internal/repository/mysql/dao/activity_categories.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_draw_effects.gen.go b/internal/repository/mysql/dao/activity_draw_effects.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_draw_logs.gen.go b/internal/repository/mysql/dao/activity_draw_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_draw_receipts.gen.go b/internal/repository/mysql/dao/activity_draw_receipts.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_issues.gen.go b/internal/repository/mysql/dao/activity_issues.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/activity_reward_settings.gen.go b/internal/repository/mysql/dao/activity_reward_settings.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/admin.gen.go b/internal/repository/mysql/dao/admin.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/audit_rollback_logs.gen.go b/internal/repository/mysql/dao/audit_rollback_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/banner.gen.go b/internal/repository/mysql/dao/banner.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/channels.gen.go b/internal/repository/mysql/dao/channels.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/douyin_blacklist.gen.go b/internal/repository/mysql/dao/douyin_blacklist.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/douyin_orders.gen.go b/internal/repository/mysql/dao/douyin_orders.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/douyin_product_rewards.gen.go b/internal/repository/mysql/dao/douyin_product_rewards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/game_pass_packages.gen.go b/internal/repository/mysql/dao/game_pass_packages.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/game_ticket_logs.gen.go b/internal/repository/mysql/dao/game_ticket_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/gen.go b/internal/repository/mysql/dao/gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/issue_position_claims.gen.go b/internal/repository/mysql/dao/issue_position_claims.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/livestream_activities.gen.go b/internal/repository/mysql/dao/livestream_activities.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/livestream_draw_logs.gen.go b/internal/repository/mysql/dao/livestream_draw_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/livestream_prizes.gen.go b/internal/repository/mysql/dao/livestream_prizes.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/log_operation.gen.go b/internal/repository/mysql/dao/log_operation.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/log_request.gen.go b/internal/repository/mysql/dao/log_request.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/lottery_refund_logs.gen.go b/internal/repository/mysql/dao/lottery_refund_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/matching_card_types.gen.go b/internal/repository/mysql/dao/matching_card_types.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/menu_actions.gen.go b/internal/repository/mysql/dao/menu_actions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/menus.gen.go b/internal/repository/mysql/dao/menus.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/mini_program_access_token.gen.go b/internal/repository/mysql/dao/mini_program_access_token.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/ops_shipping_stats.gen.go b/internal/repository/mysql/dao/ops_shipping_stats.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/order_coupons.gen.go b/internal/repository/mysql/dao/order_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/order_items.gen.go b/internal/repository/mysql/dao/order_items.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/order_snapshots.gen.go b/internal/repository/mysql/dao/order_snapshots.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/orders.gen.go b/internal/repository/mysql/dao/orders.gen.go old mode 100644 new mode 100755 index 4bf9ec1..50bd576 --- a/internal/repository/mysql/dao/orders.gen.go +++ b/internal/repository/mysql/dao/orders.gen.go @@ -47,6 +47,7 @@ func newOrders(db *gorm.DB, opts ...gen.DOOption) orders { _orders.CouponID = field.NewInt64(tableName, "coupon_id") _orders.ItemCardID = field.NewInt64(tableName, "item_card_id") _orders.Remark = field.NewString(tableName, "remark") + _orders.ExtOrderID = field.NewString(tableName, "ext_order_id") _orders.fillFieldMap() @@ -78,6 +79,7 @@ type orders struct { CouponID field.Int64 // 使用的优惠券ID ItemCardID field.Int64 // 使用的道具卡ID Remark field.String // 备注 + ExtOrderID field.String // 外部订单号(如抖店单号) fieldMap map[string]field.Expr } @@ -114,6 +116,7 @@ func (o *orders) updateTableName(table string) *orders { o.CouponID = field.NewInt64(table, "coupon_id") o.ItemCardID = field.NewInt64(table, "item_card_id") o.Remark = field.NewString(table, "remark") + o.ExtOrderID = field.NewString(table, "ext_order_id") o.fillFieldMap() @@ -151,6 +154,7 @@ func (o *orders) fillFieldMap() { o.fieldMap["coupon_id"] = o.CouponID o.fieldMap["item_card_id"] = o.ItemCardID o.fieldMap["remark"] = o.Remark + o.fieldMap["ext_order_id"] = o.ExtOrderID } func (o orders) clone(db *gorm.DB) orders { diff --git a/internal/repository/mysql/dao/payment_bill_diff.gen.go b/internal/repository/mysql/dao/payment_bill_diff.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/payment_bills.gen.go b/internal/repository/mysql/dao/payment_bills.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/payment_notify_events.gen.go b/internal/repository/mysql/dao/payment_notify_events.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/payment_preorders.gen.go b/internal/repository/mysql/dao/payment_preorders.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/payment_refunds.gen.go b/internal/repository/mysql/dao/payment_refunds.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/payment_transactions.gen.go b/internal/repository/mysql/dao/payment_transactions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/product_categories.gen.go b/internal/repository/mysql/dao/product_categories.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/products.gen.go b/internal/repository/mysql/dao/products.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/role_actions.gen.go b/internal/repository/mysql/dao/role_actions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/role_menus.gen.go b/internal/repository/mysql/dao/role_menus.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/role_users.gen.go b/internal/repository/mysql/dao/role_users.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/roles.gen.go b/internal/repository/mysql/dao/roles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/shipping_records.gen.go b/internal/repository/mysql/dao/shipping_records.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/system_configs.gen.go b/internal/repository/mysql/dao/system_configs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/system_coupons.gen.go b/internal/repository/mysql/dao/system_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/system_item_cards.gen.go b/internal/repository/mysql/dao/system_item_cards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/system_title_effects.gen.go b/internal/repository/mysql/dao/system_title_effects.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/system_titles.gen.go b/internal/repository/mysql/dao/system_titles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/task_center_event_logs.gen.go b/internal/repository/mysql/dao/task_center_event_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/task_center_task_rewards.gen.go b/internal/repository/mysql/dao/task_center_task_rewards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/task_center_task_tiers.gen.go b/internal/repository/mysql/dao/task_center_task_tiers.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/task_center_tasks.gen.go b/internal/repository/mysql/dao/task_center_tasks.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/task_center_user_progress.gen.go b/internal/repository/mysql/dao/task_center_user_progress.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_addresses.gen.go b/internal/repository/mysql/dao/user_addresses.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_coupon_ledger.gen.go b/internal/repository/mysql/dao/user_coupon_ledger.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_coupons.gen.go b/internal/repository/mysql/dao/user_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_game_passes.gen.go b/internal/repository/mysql/dao/user_game_passes.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_game_tickets.gen.go b/internal/repository/mysql/dao/user_game_tickets.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_inventory.gen.go b/internal/repository/mysql/dao/user_inventory.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_inventory_transfers.gen.go b/internal/repository/mysql/dao/user_inventory_transfers.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_invites.gen.go b/internal/repository/mysql/dao/user_invites.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_item_cards.gen.go b/internal/repository/mysql/dao/user_item_cards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_points.gen.go b/internal/repository/mysql/dao/user_points.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_points_ledger.gen.go b/internal/repository/mysql/dao/user_points_ledger.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_title_effect_claims.gen.go b/internal/repository/mysql/dao/user_title_effect_claims.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/user_titles.gen.go b/internal/repository/mysql/dao/user_titles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/dao/users.gen.go b/internal/repository/mysql/dao/users.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activities.gen.go b/internal/repository/mysql/model/activities.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_categories.gen.go b/internal/repository/mysql/model/activity_categories.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_draw_effects.gen.go b/internal/repository/mysql/model/activity_draw_effects.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_draw_logs.gen.go b/internal/repository/mysql/model/activity_draw_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_draw_receipts.gen.go b/internal/repository/mysql/model/activity_draw_receipts.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_issues.gen.go b/internal/repository/mysql/model/activity_issues.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/activity_reward_settings.gen.go b/internal/repository/mysql/model/activity_reward_settings.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/admin.gen.go b/internal/repository/mysql/model/admin.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/audit_rollback_logs.gen.go b/internal/repository/mysql/model/audit_rollback_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/banner.gen.go b/internal/repository/mysql/model/banner.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/channels.gen.go b/internal/repository/mysql/model/channels.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/douyin_blacklist.gen.go b/internal/repository/mysql/model/douyin_blacklist.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/douyin_orders.gen.go b/internal/repository/mysql/model/douyin_orders.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/douyin_product_rewards.gen.go b/internal/repository/mysql/model/douyin_product_rewards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/game_pass_packages.gen.go b/internal/repository/mysql/model/game_pass_packages.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/game_ticket_logs.gen.go b/internal/repository/mysql/model/game_ticket_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/issue_position_claims.gen.go b/internal/repository/mysql/model/issue_position_claims.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/livestream_activities.gen.go b/internal/repository/mysql/model/livestream_activities.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/livestream_draw_logs.gen.go b/internal/repository/mysql/model/livestream_draw_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/livestream_prizes.gen.go b/internal/repository/mysql/model/livestream_prizes.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/log_operation.gen.go b/internal/repository/mysql/model/log_operation.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/log_request.gen.go b/internal/repository/mysql/model/log_request.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/lottery_refund_logs.gen.go b/internal/repository/mysql/model/lottery_refund_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/matching_card_types.gen.go b/internal/repository/mysql/model/matching_card_types.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/menu_actions.gen.go b/internal/repository/mysql/model/menu_actions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/menus.gen.go b/internal/repository/mysql/model/menus.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/mini_program_access_token.gen.go b/internal/repository/mysql/model/mini_program_access_token.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/ops_shipping_stats.gen.go b/internal/repository/mysql/model/ops_shipping_stats.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/order_coupons.gen.go b/internal/repository/mysql/model/order_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/order_items.gen.go b/internal/repository/mysql/model/order_items.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/order_snapshots.gen.go b/internal/repository/mysql/model/order_snapshots.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/orders.gen.go b/internal/repository/mysql/model/orders.gen.go old mode 100644 new mode 100755 index d0a2e71..ff89a7d --- a/internal/repository/mysql/model/orders.gen.go +++ b/internal/repository/mysql/model/orders.gen.go @@ -32,6 +32,7 @@ type Orders struct { CouponID int64 `gorm:"column:coupon_id;comment:使用的优惠券ID" json:"coupon_id"` // 使用的优惠券ID ItemCardID int64 `gorm:"column:item_card_id;comment:使用的道具卡ID" json:"item_card_id"` // 使用的道具卡ID Remark string `gorm:"column:remark;comment:备注" json:"remark"` // 备注 + ExtOrderID string `gorm:"column:ext_order_id;not null;default:'';comment:外部订单号" json:"ext_order_id"` // 外部订单号 } // TableName Orders's table name diff --git a/internal/repository/mysql/model/payment_bill_diff.gen.go b/internal/repository/mysql/model/payment_bill_diff.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/payment_bills.gen.go b/internal/repository/mysql/model/payment_bills.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/payment_notify_events.gen.go b/internal/repository/mysql/model/payment_notify_events.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/payment_preorders.gen.go b/internal/repository/mysql/model/payment_preorders.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/payment_refunds.gen.go b/internal/repository/mysql/model/payment_refunds.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/payment_transactions.gen.go b/internal/repository/mysql/model/payment_transactions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/product_categories.gen.go b/internal/repository/mysql/model/product_categories.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/products.gen.go b/internal/repository/mysql/model/products.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/role_actions.gen.go b/internal/repository/mysql/model/role_actions.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/role_menus.gen.go b/internal/repository/mysql/model/role_menus.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/role_users.gen.go b/internal/repository/mysql/model/role_users.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/roles.gen.go b/internal/repository/mysql/model/roles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/shipping_records.gen.go b/internal/repository/mysql/model/shipping_records.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/system_configs.gen.go b/internal/repository/mysql/model/system_configs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/system_coupons.gen.go b/internal/repository/mysql/model/system_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/system_item_cards.gen.go b/internal/repository/mysql/model/system_item_cards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/system_title_effects.gen.go b/internal/repository/mysql/model/system_title_effects.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/system_titles.gen.go b/internal/repository/mysql/model/system_titles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/task_center_event_logs.gen.go b/internal/repository/mysql/model/task_center_event_logs.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/task_center_task_rewards.gen.go b/internal/repository/mysql/model/task_center_task_rewards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/task_center_task_tiers.gen.go b/internal/repository/mysql/model/task_center_task_tiers.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/task_center_tasks.gen.go b/internal/repository/mysql/model/task_center_tasks.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/task_center_user_progress.gen.go b/internal/repository/mysql/model/task_center_user_progress.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_addresses.gen.go b/internal/repository/mysql/model/user_addresses.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_coupon_ledger.gen.go b/internal/repository/mysql/model/user_coupon_ledger.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_coupons.gen.go b/internal/repository/mysql/model/user_coupons.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_game_passes.gen.go b/internal/repository/mysql/model/user_game_passes.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_game_tickets.gen.go b/internal/repository/mysql/model/user_game_tickets.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_inventory.gen.go b/internal/repository/mysql/model/user_inventory.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_inventory_transfers.gen.go b/internal/repository/mysql/model/user_inventory_transfers.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_invites.gen.go b/internal/repository/mysql/model/user_invites.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_item_cards.gen.go b/internal/repository/mysql/model/user_item_cards.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_points.gen.go b/internal/repository/mysql/model/user_points.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_points_ledger.gen.go b/internal/repository/mysql/model/user_points_ledger.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_status.go b/internal/repository/mysql/model/user_status.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_title_effect_claims.gen.go b/internal/repository/mysql/model/user_title_effect_claims.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/user_titles.gen.go b/internal/repository/mysql/model/user_titles.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/model/users.gen.go b/internal/repository/mysql/model/users.gen.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/mysql.go b/internal/repository/mysql/mysql.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/plugin.go b/internal/repository/mysql/plugin.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/task_center/README.md b/internal/repository/mysql/task_center/README.md old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/task_center/models.go b/internal/repository/mysql/task_center/models.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/test_helper.go b/internal/repository/mysql/test_helper.go old mode 100644 new mode 100755 diff --git a/internal/repository/mysql/testrepo_sqlite.go b/internal/repository/mysql/testrepo_sqlite.go old mode 100644 new mode 100755 diff --git a/internal/router/.DS_Store b/internal/router/.DS_Store old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/admin_auth.go b/internal/router/interceptor/admin_auth.go old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/admin_rbac.go b/internal/router/interceptor/admin_rbac.go old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/admin_rbac_test.go b/internal/router/interceptor/admin_rbac_test.go old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/app_auth.go b/internal/router/interceptor/app_auth.go old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/blacklist.go b/internal/router/interceptor/blacklist.go old mode 100644 new mode 100755 diff --git a/internal/router/interceptor/interceptor.go b/internal/router/interceptor/interceptor.go old mode 100644 new mode 100755 diff --git a/internal/router/router.go b/internal/router/router.go old mode 100644 new mode 100755 index 8ed5b41..c319f9b --- a/internal/router/router.go +++ b/internal/router/router.go @@ -140,6 +140,7 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er adminAuthApiRouter.GET("/task-center/tasks/:id/rewards", taskCenterHandler.ListTaskRewardsForAdmin()) adminAuthApiRouter.POST("/task-center/events/order-paid", taskCenterHandler.SimulateOrderPaid()) adminAuthApiRouter.POST("/task-center/events/invite-success", taskCenterHandler.SimulateInviteSuccess()) + adminAuthApiRouter.GET("/task-center/tasks/:id/reward-stats", taskCenterHandler.GetTaskRewardStats()) // 工作台 adminAuthApiRouter.GET("/dashboard/cards", adminHandler.DashboardCards()) @@ -277,11 +278,14 @@ func NewHTTPMux(logger logger.CustomLogger, db mysql.Repo) (core.Mux, func(), er adminAuthApiRouter.GET("/users/:user_id/titles", intc.RequireAdminAction("user:view"), adminHandler.ListUserTitles()) adminAuthApiRouter.GET("/users/:user_id/stats/profit_loss", intc.RequireAdminAction("user:view"), adminHandler.GetUserProfitLossTrend()) adminAuthApiRouter.GET("/users/:user_id/stats/profit_loss_details", intc.RequireAdminAction("user:view"), adminHandler.GetUserProfitLossDetails()) + adminAuthApiRouter.GET("/users/:user_id/spending", intc.RequireAdminAction("user:view"), adminHandler.GetUserSpendingDashboard()) adminAuthApiRouter.GET("/users/:user_id/profile", intc.RequireAdminAction("user:view"), adminHandler.GetUserProfile()) adminAuthApiRouter.PUT("/users/:user_id/douyin_user_id", intc.RequireAdminAction("user:modify"), adminHandler.UpdateUserDouyinID()) adminAuthApiRouter.PUT("/users/:user_id/remark", intc.RequireAdminAction("user:modify"), adminHandler.UpdateUserRemark()) adminAuthApiRouter.PUT("/users/:user_id/status", intc.RequireAdminAction("user:modify"), adminHandler.UpdateUserStatus()) adminAuthApiRouter.PUT("/users/:user_id/mobile", intc.RequireAdminAction("user:modify"), adminHandler.UpdateUserMobile()) + adminAuthApiRouter.PUT("/users/:user_id/inviter", intc.RequireAdminAction("user:modify"), adminHandler.AdminBindInviter()) + adminAuthApiRouter.GET("/users/search", intc.RequireAdminAction("user:view"), adminHandler.AdminSearchUsers()) adminAuthApiRouter.DELETE("/users/:user_id", intc.RequireAdminAction("user:delete"), adminHandler.DeleteUser()) adminAuthApiRouter.GET("/users/:user_id/audit", intc.RequireAdminAction("user:view"), adminHandler.ListUserAuditLogs()) diff --git a/internal/service/activity/activities_list.go b/internal/service/activity/activities_list.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity.go b/internal/service/activity/activity.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_commitment_service.go b/internal/service/activity/activity_commitment_service.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_copy.go b/internal/service/activity/activity_copy.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_create.go b/internal/service/activity/activity_create.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_delete.go b/internal/service/activity/activity_delete.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_get.go b/internal/service/activity/activity_get.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_modify.go b/internal/service/activity/activity_modify.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/activity_order_service.go b/internal/service/activity/activity_order_service.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/category_lookup.go b/internal/service/activity/category_lookup.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/concurrency_test.go b/internal/service/activity/concurrency_test.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/draw_config_save.go b/internal/service/activity/draw_config_save.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/draw_logs_list.go b/internal/service/activity/draw_logs_list.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/ichiban_slots_service.go b/internal/service/activity/ichiban_slots_service.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/issue_create.go b/internal/service/activity/issue_create.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/issue_delete.go b/internal/service/activity/issue_delete.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/issue_modify.go b/internal/service/activity/issue_modify.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/issues_list.go b/internal/service/activity/issues_list.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/lottery_process.go b/internal/service/activity/lottery_process.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/matching_game.go b/internal/service/activity/matching_game.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/rewards_create.go b/internal/service/activity/rewards_create.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/rewards_list.go b/internal/service/activity/rewards_list.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/rewards_modify.go b/internal/service/activity/rewards_modify.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/sanitize.go b/internal/service/activity/sanitize.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/sanitize_test.go b/internal/service/activity/sanitize_test.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/scheduler.go b/internal/service/activity/scheduler.go old mode 100644 new mode 100755 index cc347b8..bd1a076 --- a/internal/service/activity/scheduler.go +++ b/internal/service/activity/scheduler.go @@ -32,7 +32,6 @@ type scheduledConfig struct { func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo, rdb *redis.Client) { r := dao.Use(repo.GetDbR()) - w := dao.Use(repo.GetDbW()) us := usersvc.New(l, repo) activitySvc := New(l, repo, us, rdb) @@ -174,7 +173,7 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo, rdb *redis continue } - refundOrder(ctx, l, o, "ichiban_not_sold_out", wc, r, w, us, a.RefundCouponID) + refundOrder(ctx, l, o, "ichiban_not_sold_out", wc, repo, us, a.RefundCouponID) } } else { // 【Fix】已售罄:处理所有未开奖的订单(包括旧时间窗口的订单) @@ -253,7 +252,7 @@ func StartScheduledSettlement(l logger.CustomLogger, repo mysql.Repo, rdb *redis if shouldRefund { for _, o := range orders { - refundOrder(ctx, l, o, "scheduled_not_enough", wc, r, w, us, a.RefundCouponID) + refundOrder(ctx, l, o, "scheduled_not_enough", wc, repo, us, a.RefundCouponID) } } else { for _, o := range orders { @@ -359,10 +358,11 @@ func cleanupExpiredIchibanOrders(ctx context.Context, l logger.CustomLogger, rep ID int64 Remark string UserID int64 + CouponID int64 PlayType string } err := repo.GetDbR().Raw(` - SELECT o.id, o.remark, o.user_id, a.play_type + SELECT o.id, o.remark, o.user_id, o.coupon_id, a.play_type FROM orders o INNER JOIN activities a ON o.remark LIKE CONCAT('lottery:activity:', a.id, '|%') WHERE o.status = 1 @@ -404,6 +404,33 @@ func cleanupExpiredIchibanOrders(ctx context.Context, l logger.CustomLogger, rep l.Error("清理过期一番赏订单: 更新订单状态失败", zap.Int64("order_id", o.ID), zap.Error(err)) } else { l.Info("清理过期一番赏订单: 订单已关闭", zap.Int64("order_id", o.ID)) + + // ⭐ 核心修复:一番赏超时取消也必须退还优惠券 + if o.CouponID > 0 { + var oc struct { + AppliedAmount int64 + } + if err := repo.GetDbR().Raw("SELECT applied_amount FROM order_coupons WHERE order_id = ? AND user_coupon_id = ?", o.ID, o.CouponID).Scan(&oc).Error; err == nil && oc.AppliedAmount > 0 { + _ = repo.GetDbW().Exec(` + UPDATE user_coupons + SET balance_amount = balance_amount + ?, + status = 1, + used_order_id = 0, + used_at = NULL + WHERE id = ? AND status = 4 + `, oc.AppliedAmount, o.CouponID) + + // 记录流水 + _ = repo.GetDbW().Create(&model.UserCouponLedger{ + UserID: o.UserID, + UserCouponID: o.CouponID, + ChangeAmount: oc.AppliedAmount, + OrderID: o.ID, + Action: "timeout_refund", + CreatedAt: time.Now(), + }) + } + } } } } @@ -424,7 +451,18 @@ func parseTime(s string) time.Time { // uploadVirtualShippingForScheduledDraw 定时开奖后上传虚拟发货 // 收集中奖产品名称并调用微信虚拟发货API // playType: 活动玩法类型,只有 ichiban 时才发送开奖结果通知 -func refundOrder(ctx context.Context, l logger.CustomLogger, o *model.Orders, reason string, wc *paypkg.WechatPayClient, r *dao.Query, w *dao.Query, us usersvc.Service, refundCouponID int64) { +func refundOrder(ctx context.Context, l logger.CustomLogger, o *model.Orders, reason string, wc *paypkg.WechatPayClient, repo mysql.Repo, us usersvc.Service, refundCouponID int64) { + r := dao.Use(repo.GetDbR()) + w := dao.Use(repo.GetDbW()) + + // ⭐ 幂等校验:检查是否已经退过款,防止重复退款/多退优惠券 + var exists int64 + _ = repo.GetDbR().Raw("SELECT COUNT(1) FROM lottery_refund_logs WHERE order_id = ?", o.ID).Scan(&exists) + if exists > 0 { + l.Info("Refund: Order already refunded, skipping", zap.Int64("order_id", o.ID)) + return + } + // 1. Refund Points if o.PointsAmount > 0 { refundPts := o.PointsAmount / 100 @@ -458,39 +496,45 @@ func refundOrder(ctx context.Context, l logger.CustomLogger, o *model.Orders, re // 3. Refund Used Coupons ocs, _ := r.OrderCoupons.WithContext(ctx).Where(r.OrderCoupons.OrderID.Eq(o.ID)).Find() for _, oc := range ocs { - // Restore user coupon status to 1 (Unused) and clear usage info - _, err := w.UserCoupons.WithContext(ctx).Where(w.UserCoupons.ID.Eq(oc.UserCouponID)).Updates(map[string]interface{}{ - "status": 1, - "used_order_id": 0, - "used_at": nil, + // ⭐ 核心修复:退款时必须退还余额 balance_amount,而不是只复位状态 + // 这里使用 Exec 确保原子性 + _ = repo.GetDbW().Exec(` + UPDATE user_coupons + SET balance_amount = balance_amount + ?, + status = 1, + used_order_id = 0, + used_at = NULL + WHERE id = ? + `, oc.AppliedAmount, oc.UserCouponID) + + l.Info("Refund: Restored coupon balance", zap.Int64("ucID", oc.UserCouponID), zap.String("order_no", o.OrderNo)) + // 记录流水 + _ = w.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ + UserID: o.UserID, + UserCouponID: oc.UserCouponID, + ChangeAmount: oc.AppliedAmount, + BalanceAfter: 0, // 占位,实际逻辑中 BalanceAfter 应由触发器或查询获得,此处由于是退款恢复,记录变动额即可 + OrderID: o.ID, + Action: "refund_restore", + CreatedAt: time.Now(), }) - if err != nil { - l.Error("Refund: Failed to restore coupon", zap.Int64("ucID", oc.UserCouponID), zap.String("order_no", o.OrderNo), zap.Error(err)) - } else { - l.Info("Refund: Restored coupon", zap.Int64("ucID", oc.UserCouponID), zap.String("order_no", o.OrderNo)) - } } // 3.5. 一番赏退款:删除 issue_position_claims 记录,恢复格位 iss := remark.Parse(o.Remark).IssueID if iss > 0 { - result, err := w.IssuePositionClaims.WithContext(ctx).Where( + _, _ = w.IssuePositionClaims.WithContext(ctx).Where( w.IssuePositionClaims.OrderID.Eq(o.ID), ).Delete() - if err != nil { - l.Error("Refund: Failed to delete position claims", zap.Int64("order_id", o.ID), zap.Error(err)) - } else { - l.Info("Refund: Restored slot positions", zap.Int64("order_id", o.ID), zap.Int64("rows", result.RowsAffected)) - } } // 3.6. 退还道具卡(支持两种记录方式) // 方式1:从 activity_draw_effects 表查询(无限赏等游戏类型) var itemCardIDs []int64 - _ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT user_item_card_id FROM activity_draw_effects WHERE draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?)", o.ID).Scan(&itemCardIDs).Error + _ = repo.GetDbR().Raw("SELECT user_item_card_id FROM activity_draw_effects WHERE draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?)", o.ID).Scan(&itemCardIDs).Error // 方式2:从 user_item_cards 表的 used_draw_log_id 直接查询(对对碰等游戏类型) var itemCardIDsFromItemCards []int64 - _ = r.Orders.WithContext(ctx).UnderlyingDB().Raw("SELECT id FROM user_item_cards WHERE used_draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?) AND status=2", o.ID).Scan(&itemCardIDsFromItemCards).Error + _ = repo.GetDbR().Raw("SELECT id FROM user_item_cards WHERE used_draw_log_id IN (SELECT id FROM activity_draw_logs WHERE order_id=?) AND status=2", o.ID).Scan(&itemCardIDsFromItemCards).Error // 合并去重 idSet := make(map[int64]struct{}) for _, icID := range itemCardIDs { @@ -505,16 +549,14 @@ func refundOrder(ctx context.Context, l logger.CustomLogger, o *model.Orders, re } // 执行退还 for icID := range idSet { - _ = w.Orders.WithContext(ctx).UnderlyingDB().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error - l.Info("Refund: Restored item card", zap.Int64("icID", icID), zap.String("order_no", o.OrderNo)) + _ = repo.GetDbW().Exec("UPDATE user_item_cards SET status=1, used_at=NULL, used_draw_log_id=0, used_activity_id=0, used_issue_id=0, updated_at=NOW(3) WHERE id=?", icID).Error } // 4. Update Order Status _, _ = w.Orders.WithContext(ctx).Where(w.Orders.ID.Eq(o.ID)).Updates(map[string]any{w.Orders.Status.ColumnName().String(): 4}) // 5. Log Refund - iss = remark.Parse(o.Remark).IssueID - _ = w.Orders.WithContext(ctx).UnderlyingDB().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, o.ID, o.UserID, o.ActualAmount, "", 0, reason, "done").Error + _ = repo.GetDbW().Exec("INSERT INTO lottery_refund_logs(issue_id, order_id, user_id, amount, coupon_type, coupon_amount, reason, status) VALUES(?,?,?,?,?,?,?,?)", iss, o.ID, o.UserID, o.ActualAmount, "", 0, reason, "done").Error // 6. Compensation if refundCouponID > 0 { diff --git a/internal/service/activity/strategy/default.go b/internal/service/activity/strategy/default.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/strategy/ichiban.go b/internal/service/activity/strategy/ichiban.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/strategy/ichiban_test.go b/internal/service/activity/strategy/ichiban_test.go old mode 100644 new mode 100755 diff --git a/internal/service/activity/strategy/strategy.go b/internal/service/activity/strategy/strategy.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/admin.go b/internal/service/admin/admin.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/admin_create.go b/internal/service/admin/admin_create.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/admin_delete.go b/internal/service/admin/admin_delete.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/admin_list.go b/internal/service/admin/admin_list.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/admin_modify.go b/internal/service/admin/admin_modify.go old mode 100644 new mode 100755 diff --git a/internal/service/admin/login.go b/internal/service/admin/login.go old mode 100644 new mode 100755 diff --git a/internal/service/banner/banner.go b/internal/service/banner/banner.go old mode 100644 new mode 100755 diff --git a/internal/service/channel/channel.go b/internal/service/channel/channel.go old mode 100644 new mode 100755 diff --git a/internal/service/common/common.go b/internal/service/common/common.go old mode 100644 new mode 100755 diff --git a/internal/service/douyin/order_sync.go b/internal/service/douyin/order_sync.go old mode 100644 new mode 100755 index 3ec5c07..20ec326 --- a/internal/service/douyin/order_sync.go +++ b/internal/service/douyin/order_sync.go @@ -533,7 +533,7 @@ func (s *service) SyncOrder(ctx context.Context, item *DouyinOrderItem, suggestU fmt.Printf("[DEBUG] 准备发放奖励: User: %d, ProductID: %s, Type: %s, Quantity: %d, RuleID: %d\n", localUserID, order.DouyinProductID, reward.RewardType, reward.Quantity, reward.ID) - err := s.rewardDispatcher.GrantReward(ctx, localUserID, reward, int(order.ProductCount), "douyin_order", order.ID) + err := s.rewardDispatcher.GrantReward(ctx, localUserID, reward, int(order.ProductCount), "douyin_order", order.ID, order.ShopOrderID) if err != nil { fmt.Printf("[DEBUG] 订单 %s 发放奖励失败 (规则ID: %d): %v\n", item.ShopOrderID, reward.ID, err) allSuccess = false diff --git a/internal/service/douyin/reward_dispatcher.go b/internal/service/douyin/reward_dispatcher.go old mode 100644 new mode 100755 index cbea003..fcb8155 --- a/internal/service/douyin/reward_dispatcher.go +++ b/internal/service/douyin/reward_dispatcher.go @@ -54,7 +54,8 @@ func NewRewardDispatcher(ticketSvc game.TicketService, userSvc user.Service, tit // productCount: 商品数量(会乘以 reward.Quantity) // source: 来源标识 // sourceID: 来源ID(如订单ID) -func (d *RewardDispatcher) GrantReward(ctx context.Context, userID int64, reward model.DouyinProductRewards, productCount int, source string, sourceID int64) error { +// extOrderID: 外部订单号(如抖店单号) +func (d *RewardDispatcher) GrantReward(ctx context.Context, userID int64, reward model.DouyinProductRewards, productCount int, source string, sourceID int64, extOrderID string) error { if reward.Status != 1 { return nil // 规则未启用,跳过 } @@ -107,9 +108,10 @@ func (d *RewardDispatcher) GrantReward(ctx context.Context, userID int64, reward return fmt.Errorf("product_id not configured") } _, err := d.userSvc.GrantReward(ctx, userID, user.GrantRewardRequest{ - ProductID: payload.ProductID, - Quantity: totalQuantity, - Remark: remark, + ProductID: payload.ProductID, + Quantity: totalQuantity, + Remark: remark, + ExtOrderID: extOrderID, }) return err diff --git a/internal/service/douyin/scheduler.go b/internal/service/douyin/scheduler.go old mode 100644 new mode 100755 diff --git a/internal/service/game/ticket_service.go b/internal/service/game/ticket_service.go old mode 100644 new mode 100755 diff --git a/internal/service/game/token.go b/internal/service/game/token.go old mode 100644 new mode 100755 diff --git a/internal/service/game/token_test.go b/internal/service/game/token_test.go old mode 100644 new mode 100755 diff --git a/internal/service/livestream/DRAW_README.md b/internal/service/livestream/DRAW_README.md old mode 100644 new mode 100755 diff --git a/internal/service/livestream/discrete_random.go b/internal/service/livestream/discrete_random.go old mode 100644 new mode 100755 diff --git a/internal/service/livestream/discrete_random_test.go b/internal/service/livestream/discrete_random_test.go old mode 100644 new mode 100755 diff --git a/internal/service/livestream/draw_integration_test.go b/internal/service/livestream/draw_integration_test.go old mode 100644 new mode 100755 diff --git a/internal/service/livestream/livestream.go b/internal/service/livestream/livestream.go old mode 100644 new mode 100755 diff --git a/internal/service/order/discount.go b/internal/service/order/discount.go old mode 100644 new mode 100755 diff --git a/internal/service/order/discount_test.go b/internal/service/order/discount_test.go old mode 100644 new mode 100755 diff --git a/internal/service/product/product.go b/internal/service/product/product.go old mode 100644 new mode 100755 diff --git a/internal/service/product/product_test.go b/internal/service/product/product_test.go old mode 100644 new mode 100755 diff --git a/internal/service/recycle/recycle_service.go b/internal/service/recycle/recycle_service.go old mode 100644 new mode 100755 diff --git a/internal/service/snapshot/rollback_service.go b/internal/service/snapshot/rollback_service.go old mode 100644 new mode 100755 diff --git a/internal/service/snapshot/snapshot_service.go b/internal/service/snapshot/snapshot_service.go old mode 100644 new mode 100755 diff --git a/internal/service/sysconfig/dynamic_config.go b/internal/service/sysconfig/dynamic_config.go old mode 100644 new mode 100755 diff --git a/internal/service/sysconfig/global.go b/internal/service/sysconfig/global.go old mode 100644 new mode 100755 diff --git a/internal/service/sysconfig/sysconfig.go b/internal/service/sysconfig/sysconfig.go old mode 100644 new mode 100755 diff --git a/internal/service/task_center/cache.go b/internal/service/task_center/cache.go old mode 100644 new mode 100755 diff --git a/internal/service/task_center/constants.go b/internal/service/task_center/constants.go old mode 100644 new mode 100755 index c086451..1c233e7 --- a/internal/service/task_center/constants.go +++ b/internal/service/task_center/constants.go @@ -2,10 +2,12 @@ package taskcenter const ( // Task Windows - WindowDaily = "daily" - WindowWeekly = "weekly" - WindowMonthly = "monthly" - WindowLifetime = "lifetime" + WindowDaily = "daily" + WindowWeekly = "weekly" + WindowMonthly = "monthly" + WindowLifetime = "lifetime" + WindowActivityPeriod = "activity_period" // 任务有效期内(使用 task.StartTime ~ task.EndTime) + WindowSinceRegistration = "since_registration" // 注册以来(不限制) // Task Metrics MetricFirstOrder = "first_order" diff --git a/internal/service/task_center/invite_logic_test.go b/internal/service/task_center/invite_logic_test.go old mode 100644 new mode 100755 diff --git a/internal/service/task_center/list_tasks_filter_test.go b/internal/service/task_center/list_tasks_filter_test.go old mode 100644 new mode 100755 diff --git a/internal/service/task_center/service.go b/internal/service/task_center/service.go old mode 100644 new mode 100755 index 0db9d8d..37cac5c --- a/internal/service/task_center/service.go +++ b/internal/service/task_center/service.go @@ -12,6 +12,7 @@ import ( "encoding/json" "errors" "fmt" + "strings" "time" gamesvc "bindbox-game/internal/service/game" @@ -70,8 +71,9 @@ func New(l logger.CustomLogger, db mysql.Repo, rdb *redis.Client, userSvc usersv } type ListTasksInput struct { - Page int - PageSize int + Page int + PageSize int + OnlyActive bool // 是否只返回有效期内的任务 } type TaskItem struct { @@ -88,15 +90,25 @@ type TaskItem struct { Rewards []TaskRewardItem } +// TierProgress 记录单个档位在其配置窗口内的独立统计进度 +type TierProgress struct { + TierID int64 `json:"tier_id"` + OrderCount int64 `json:"order_count"` + OrderAmount int64 `json:"order_amount"` + InviteCount int64 `json:"invite_count"` + FirstOrder bool `json:"first_order"` +} + type UserProgress struct { - TaskID int64 `json:"task_id"` - UserID int64 `json:"user_id"` - OrderCount int64 `json:"order_count"` - OrderAmount int64 `json:"order_amount"` - InviteCount int64 `json:"invite_count"` - FirstOrder bool `json:"first_order"` - ClaimedTiers []int64 `json:"claimed_tiers"` - SubProgress []ActivityProgress `json:"sub_progress"` // 各活动独立进度 + TaskID int64 `json:"task_id"` + UserID int64 `json:"user_id"` + OrderCount int64 `json:"order_count"` + OrderAmount int64 `json:"order_amount"` + InviteCount int64 `json:"invite_count"` + FirstOrder bool `json:"first_order"` + ClaimedTiers []int64 `json:"claimed_tiers"` + SubProgress []ActivityProgress `json:"sub_progress"` // 各活动独立进度(向后兼容) + TierProgressMap map[int64]TierProgress `json:"tier_progress_map"` // 每个 Tier 的窗口化独立进度 } type ActivityProgress struct { @@ -171,9 +183,16 @@ func (s *service) ListTasks(ctx context.Context, in ListTasksInput) (items []Tas db := s.repo.GetDbR() var rows []tcmodel.Task q := db.Model(&tcmodel.Task{}) - - // 只返回已启用且可见的任务(过滤掉未上架的任务) - q = q.Where("status = ? AND visibility = ?", 1, 1) + // 过滤条件 + if in.OnlyActive { + now := time.Now() + q = q.Where("status = ? AND visibility = ?", 1, 1) + q = q.Where("(start_time IS NULL OR start_time <= ?) AND (end_time IS NULL OR end_time >= ?)", now, now) + } else { + // 管理后台默认也至少基于启用状态看?或者去掉限制以便管理全量 + // 维持原有逻辑: + q = q.Where("status = ? AND visibility = ?", 1, 1) + } if in.PageSize <= 0 { in.PageSize = 20 @@ -312,36 +331,162 @@ func (s *service) ListTasks(ctx context.Context, in ListTasksInput) (items []Tas return out, total, nil } +// computeTimeWindow 根据 window 配置计算时间范围 +// 返回 (windowStart, windowEnd),nil 表示该端不限制 +func computeTimeWindow(window string, taskStart, taskEnd *time.Time) (start *time.Time, end *time.Time) { + now := time.Now() + switch window { + case WindowDaily: + s := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + return &s, &now + case WindowWeekly: + weekday := int(now.Weekday()) + if weekday == 0 { + weekday = 7 + } + s := now.AddDate(0, 0, -(weekday - 1)) + s = time.Date(s.Year(), s.Month(), s.Day(), 0, 0, 0, 0, s.Location()) + return &s, &now + case WindowMonthly: + s := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) + return &s, &now + case WindowActivityPeriod: + // 使用任务级别的 StartTime / EndTime,nil 端不加限制 + return taskStart, taskEnd + default: + // lifetime / since_registration / 未知值 → 不限制 + return nil, nil + } +} + func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int64) (*UserProgress, error) { db := s.repo.GetDbR() - // 3.0 获取任务的 ActivityID 限制 - var tiers []tcmodel.TaskTier - // 只需要查 ActivityID > 0 的记录即可判断 - // 修改:不再 Limit(1),而是获取所有关联的 ActivityID - db.Select("activity_id").Where("task_id=? AND activity_id > 0", taskID).Find(&tiers) + // 加载任务信息(获取 StartTime/EndTime 用于 activity_period window) + var task tcmodel.Task + if err := db.First(&task, taskID).Error; err != nil { + return nil, err + } + // 3.0 获取任务下所有 Tier(含 Window、ActivityID、Metric 字段,用于时效分组查询) + var tiers []tcmodel.TaskTier + db.Where("task_id = ?", taskID).Find(&tiers) + + // 提取所有 activityID(用于向后兼容的全局统计和 SubProgress) targetActivityIDs := make([]int64, 0) - seen := make(map[int64]struct{}) + seenActivity := make(map[int64]struct{}) for _, t := range tiers { if t.ActivityID > 0 { - if _, ok := seen[t.ActivityID]; !ok { - seen[t.ActivityID] = struct{}{} + if _, ok := seenActivity[t.ActivityID]; !ok { + seenActivity[t.ActivityID] = struct{}{} targetActivityIDs = append(targetActivityIDs, t.ActivityID) } } } - // 1. 实时统计订单数据 - // BUG修复:排除商城订单(source_type=1),只统计抽奖相关订单 - // 通过 activity_draw_logs 和 activity_issues 表关联订单到活动 + // ── Bug1 修复:按 (window, activityID) 分组,每组带时效过滤查一次,填充 TierProgressMap ── + type windowGroupKey struct { + Window string + ActivityID int64 + } + groupMap := make(map[windowGroupKey][]tcmodel.TaskTier) + for _, t := range tiers { + key := windowGroupKey{Window: t.Window, ActivityID: t.ActivityID} + groupMap[key] = append(groupMap[key], t) + } + + tierProgressMap := make(map[int64]TierProgress) + + for wk, groupTiers := range groupMap { + wStart, wEnd := computeTimeWindow(wk.Window, task.StartTime, task.EndTime) + + // 构建动态时间条件片段 + var timeCond string + var timeArgs []interface{} + if wStart != nil { + timeCond += " AND orders.created_at >= ?" + timeArgs = append(timeArgs, *wStart) + } + if wEnd != nil { + timeCond += " AND orders.created_at <= ?" + timeArgs = append(timeArgs, *wEnd) + } + + var gOrderCount, gOrderAmount, gInviteCount int64 + + if wk.ActivityID > 0 { + // 有活动限制:通过 activity_draw_logs → activity_issues 关联,加时效过滤 + baseArgs := append([]interface{}{userID, wk.ActivityID}, timeArgs...) + db.Raw(` + SELECT COUNT(id) + FROM orders + WHERE user_id = ? AND status = 2 AND source_type != 1 + AND id IN ( + SELECT DISTINCT dl.order_id + FROM activity_draw_logs dl + INNER JOIN activity_issues ai ON ai.id = dl.issue_id + WHERE ai.activity_id = ? + )`+timeCond, baseArgs...).Scan(&gOrderCount) + + db.Raw(` + SELECT COALESCE(SUM(total_amount), 0) + FROM orders + WHERE user_id = ? AND status = 2 AND source_type != 1 + AND id IN ( + SELECT DISTINCT dl.order_id + FROM activity_draw_logs dl + INNER JOIN activity_issues ai ON ai.id = dl.issue_id + WHERE ai.activity_id = ? + )`+timeCond, baseArgs...).Scan(&gOrderAmount) + + // 邀请计数:将 orders.created_at 改为 o.created_at(别名) + inviteTimeCond := strings.ReplaceAll(timeCond, "orders.created_at", "o.created_at") + inviteArgs := append([]interface{}{userID, wk.ActivityID}, timeArgs...) + db.Raw(` + SELECT COUNT(DISTINCT ui.invitee_id) + FROM user_invites ui + INNER JOIN orders o ON o.user_id = ui.invitee_id AND o.status = 2 AND o.source_type != 1 + WHERE ui.inviter_id = ? + AND o.id IN ( + SELECT DISTINCT dl.order_id + FROM activity_draw_logs dl + INNER JOIN activity_issues ai ON ai.id = dl.issue_id + WHERE ai.activity_id = ? + )`+inviteTimeCond, inviteArgs...).Scan(&gInviteCount) + } else { + // 无活动限制:统计所有已开奖的非商城订单,追加时效过滤 + globalCond := "user_id = ? AND status = 2 AND source_type != 1 AND EXISTS (SELECT 1 FROM activity_draw_logs WHERE activity_draw_logs.order_id = orders.id)" + timeCond + globalArgs := append([]interface{}{userID}, timeArgs...) + db.Model(&model.Orders{}).Where(globalCond, globalArgs...).Count(&gOrderCount) + db.Model(&model.Orders{}).Select("COALESCE(SUM(total_amount), 0)").Where(globalCond, globalArgs...).Scan(&gOrderAmount) + + inviteWhere := "inviter_id = ?" + if wStart != nil { + inviteWhere += " AND created_at >= ?" + } + if wEnd != nil { + inviteWhere += " AND created_at <= ?" + } + db.Model(&model.UserInvites{}).Where(inviteWhere, globalArgs...).Count(&gInviteCount) + } + + for _, t := range groupTiers { + tierProgressMap[t.ID] = TierProgress{ + TierID: t.ID, + OrderCount: gOrderCount, + OrderAmount: gOrderAmount, + InviteCount: gInviteCount, + FirstOrder: gOrderCount > 0, + } + } + } + + // ── 向后兼容:全局统计(不限时间窗口,用于顶层字段 OrderCount/InviteCount 和 SubProgress)── var orderCount int64 var orderAmount int64 var subProgressList []ActivityProgress if len(targetActivityIDs) > 0 { - // 有活动ID限制时,通过 activity_draw_logs → activity_issues 关联过滤 - // 统计订单数量(使用 WHERE IN 子查询防止 JOIN 导致的重复计数问题) db.Raw(` SELECT COUNT(id) FROM orders @@ -354,8 +499,6 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 ) `, userID, targetActivityIDs).Scan(&orderCount) - // 统计订单金额 - // BUG修复:已解决 JOIN activity_draw_logs 导致金额翻倍的问题 db.Raw(` SELECT COALESCE(SUM(total_amount), 0) FROM orders @@ -368,8 +511,6 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 ) `, userID, targetActivityIDs).Scan(&orderAmount) } else { - // 无活动ID限制时,统计所有非商城订单 - // 增加 EXISTS 检查,确保订单已开奖(有开奖日志) query := db.Model(&model.Orders{}).Where("user_id = ? AND status = 2 AND source_type != 1", userID) query.Where("EXISTS (SELECT 1 FROM activity_draw_logs WHERE activity_draw_logs.order_id = orders.id)") query.Count(&orderCount) @@ -379,15 +520,14 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 queryAmount.Select("COALESCE(SUM(total_amount), 0)").Scan(&orderAmount) } - // 2. 实时统计邀请数据 + // 2. 实时统计邀请数据(全局,向后兼容) var inviteCount int64 if len(targetActivityIDs) > 0 { - // 根据配置计算:如果任务限定了活动,则只统计在该活动中有有效抽奖的人数(有效转化) db.Raw(` - SELECT COUNT(DISTINCT ui.invitee_id) - FROM user_invites ui + SELECT COUNT(DISTINCT ui.invitee_id) + FROM user_invites ui INNER JOIN orders o ON o.user_id = ui.invitee_id AND o.status = 2 AND o.source_type != 1 - WHERE ui.inviter_id = ? + WHERE ui.inviter_id = ? AND o.id IN ( SELECT DISTINCT dl.order_id FROM activity_draw_logs dl @@ -396,16 +536,14 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 ) `, userID, targetActivityIDs).Scan(&inviteCount) - // 3. 统计各活动独立进度 (SubProgress) - // 使用 GROUP BY activity_id 分别统计 - // 注意:需先去重(DISTINCT order_id)再求和,防止因 JOIN draw_logs 导致金额翻倍 + // SubProgress:各活动独立进度(向后兼容,不限时间窗口) var subStats []struct { ActivityID int64 OrderCount int64 OrderAmount int64 } db.Raw(` - SELECT + SELECT sub.activity_id, COUNT(sub.id) as order_count, COALESCE(SUM(sub.total_amount), 0) as order_amount @@ -420,17 +558,15 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 GROUP BY sub.activity_id `, userID, targetActivityIDs).Scan(&subStats) - // 映射到 UserProgress 结构 (Wait to assign to return struct) subProgressList = make([]ActivityProgress, 0, len(subStats)) - for _, s := range subStats { + for _, sp := range subStats { subProgressList = append(subProgressList, ActivityProgress{ - ActivityID: s.ActivityID, - OrderCount: s.OrderCount, - OrderAmount: s.OrderAmount, + ActivityID: sp.ActivityID, + OrderCount: sp.OrderCount, + OrderAmount: sp.OrderAmount, }) } } else { - // 全量统计(注册即计入):为了与前端“邀请记录”页面的总数对齐(针对全局任务) db.Model(&model.UserInvites{}).Where("inviter_id = ?", userID).Count(&inviteCount) } @@ -458,14 +594,15 @@ func (s *service) GetUserProgress(ctx context.Context, userID int64, taskID int6 } return &UserProgress{ - TaskID: taskID, - UserID: userID, - OrderCount: orderCount, - OrderAmount: orderAmount, - InviteCount: inviteCount, - FirstOrder: hasFirstOrder, - ClaimedTiers: allClaimed, - SubProgress: subProgressList, + TaskID: taskID, + UserID: userID, + OrderCount: orderCount, + OrderAmount: orderAmount, + InviteCount: inviteCount, + FirstOrder: hasFirstOrder, + ClaimedTiers: allClaimed, + SubProgress: subProgressList, + TierProgressMap: tierProgressMap, // Bug1 修复:每个 Tier 的窗口化独立进度 }, nil } @@ -482,20 +619,32 @@ func (s *service) ClaimTier(ctx context.Context, userID int64, taskID int64, tie return err } + // BUG2 FIX: 多任务共享订单池问题 —— 获取 Redis 分布式锁,防止并发重复领取 + if tier.ActivityID > 0 && s.redis != nil { + claimLockKey := fmt.Sprintf("tc:claim_lock:%d:%d", userID, tier.ActivityID) + locked, lockErr := s.redis.SetNX(ctx, claimLockKey, "1", 10*time.Second).Result() + if lockErr != nil { + s.logger.Error("ClaimTier: Redis lock error", zap.Error(lockErr), zap.Int64("user_id", userID), zap.Int64("activity_id", tier.ActivityID)) + return lockErr + } + if !locked { + return errors.New("操作频繁,请稍后再试") + } + defer s.redis.Del(ctx, claimLockKey) + } + // 校验是否达标 - // 校验是否达标 + // Bug1 修复:优先使用 TierProgressMap(窗口化进度),回退到全局进度 hit := false - // FIX: 如果 Tier 关联了特定活动,必须检查该活动的独立进度 - currentOrderCount := progress.OrderCount - currentOrderAmount := progress.OrderAmount - currentInviteCount := progress.InviteCount - - if tier.ActivityID > 0 { - // 默认未找到进度视为 0 - currentOrderCount = 0 - currentOrderAmount = 0 - // 在 SubProgress 中查找对应活动的进度 + var currentOrderCount, currentOrderAmount, currentInviteCount int64 + if tp, ok := progress.TierProgressMap[tierID]; ok { + // 使用该 tier 所配置 window 内的独立统计值 + currentOrderCount = tp.OrderCount + currentOrderAmount = tp.OrderAmount + currentInviteCount = tp.InviteCount + } else if tier.ActivityID > 0 { + // 回退:从 SubProgress 中找对应活动的进度 for _, sub := range progress.SubProgress { if sub.ActivityID == tier.ActivityID { currentOrderCount = sub.OrderCount @@ -503,6 +652,10 @@ func (s *service) ClaimTier(ctx context.Context, userID int64, taskID int64, tie break } } + } else { + currentOrderCount = progress.OrderCount + currentOrderAmount = progress.OrderAmount + currentInviteCount = progress.InviteCount } switch tier.Metric { @@ -528,17 +681,118 @@ func (s *service) ClaimTier(ctx context.Context, userID int64, taskID int64, tie } } + // BUG2 FIX: 跨任务累加校验 —— 防止多任务共享同一 activityID 订单池,用户用同一批订单重复领多个任务奖励 + // 规则:同一 activityID + 同一 metric 下,不同 taskID 间各取已领最大 threshold 后求和, + // 要求 currentValue >= consumedThreshold(已消耗)+ tier.Threshold(本次需消耗) + if tier.ActivityID > 0 && (tier.Metric == MetricOrderCount || tier.Metric == MetricOrderAmount || tier.Metric == MetricInviteCount) { + // 1. 查出同 activityID + 同 metric 下,属于其他 taskID 的所有 tier + var siblingTiers []tcmodel.TaskTier + if dbErr := s.repo.GetDbR(). + Where("activity_id = ? AND metric = ? AND task_id != ?", tier.ActivityID, tier.Metric, taskID). + Find(&siblingTiers).Error; dbErr != nil { + return dbErr + } + + // 2. 收集所有不同的 sibling taskID + siblingTaskIDs := make([]int64, 0, len(siblingTiers)) + siblingTaskSet := make(map[int64]struct{}) + for _, st := range siblingTiers { + if _, exists := siblingTaskSet[st.TaskID]; !exists { + siblingTaskSet[st.TaskID] = struct{}{} + siblingTaskIDs = append(siblingTaskIDs, st.TaskID) + } + } + + // 3. 计算已被其他 taskID 消耗的 threshold 总和 + // 同一 taskID 内按阶梯处理(取最大已领 threshold),不同 taskID 间求和 + var consumedThreshold int64 + if len(siblingTaskIDs) > 0 { + // 查用户在这些 sibling task 下的进度记录(含 claimed_tiers JSON) + var siblingProgresses []tcmodel.UserTaskProgress + if dbErr := s.repo.GetDbR(). + Where("user_id = ? AND task_id IN ? AND activity_id = 0", userID, siblingTaskIDs). + Find(&siblingProgresses).Error; dbErr != nil { + return dbErr + } + + // 按 taskID 整理已领取的 tierID 列表 + taskClaimedTierIDs := make(map[int64][]int64) // taskID -> []claimedTierID + for _, sp := range siblingProgresses { + var claimedIDs []int64 + if len(sp.ClaimedTiers) > 0 { + _ = json.Unmarshal([]byte(sp.ClaimedTiers), &claimedIDs) + } + if len(claimedIDs) > 0 { + taskClaimedTierIDs[sp.TaskID] = claimedIDs + } + } + + // 对每个 sibling taskID,查已领取 tier 中属于同 activityID+metric 的最大 threshold + for _, sibTaskID := range siblingTaskIDs { + claimedIDs, ok := taskClaimedTierIDs[sibTaskID] + if !ok || len(claimedIDs) == 0 { + continue + } + var claimedThresholds []int64 + if dbErr := s.repo.GetDbR().Model(&tcmodel.TaskTier{}). + Where("id IN ? AND task_id = ? AND activity_id = ? AND metric = ?", + claimedIDs, sibTaskID, tier.ActivityID, tier.Metric). + Pluck("threshold", &claimedThresholds).Error; dbErr != nil { + return dbErr + } + // 同一 taskID 下阶梯式:只计最大已领 threshold + var maxThreshold int64 + for _, th := range claimedThresholds { + if th > maxThreshold { + maxThreshold = th + } + } + consumedThreshold += maxThreshold + } + } + + // 4. 校验当前进度是否足以同时覆盖已消耗量和本次所需量 + var currentValue int64 + switch tier.Metric { + case MetricOrderCount: + currentValue = currentOrderCount + case MetricOrderAmount: + currentValue = currentOrderAmount + case MetricInviteCount: + currentValue = currentInviteCount + } + if currentValue < consumedThreshold+tier.Threshold { + s.logger.Warn("ClaimTier: cross-task threshold validation failed", + zap.Int64("user_id", userID), + zap.Int64("task_id", taskID), + zap.Int64("tier_id", tierID), + zap.Int64("current_value", currentValue), + zap.Int64("consumed_threshold", consumedThreshold), + zap.Int64("tier_threshold", tier.Threshold), + ) + return errors.New("订单量不足,已被其他任务消耗,无法领取") + } + } + if !hit { return errors.New("任务条件未达成,无法领取") } - // 2. 任务级限额校验:如果任务设置了限额(quota > 0),需要原子性地增加 claimed_count // 获取任务信息 var task tcmodel.Task if err := s.repo.GetDbR().First(&task, taskID).Error; err != nil { return err } + // 1.5 校验任务有效期 + now := time.Now() + if task.StartTime != nil && now.Before(*task.StartTime) { + return errors.New("任务尚未开始") + } + if task.EndTime != nil && now.After(*task.EndTime) { + return errors.New("任务已经结束") + } + if task.Quota > 0 { result := s.repo.GetDbW().Model(&tcmodel.Task{}). Where("id = ? AND claimed_count < quota", taskID). @@ -1042,7 +1296,7 @@ func (s *service) grantTierRewards(ctx context.Context, taskID int64, tierID int } // 容错处理:如果直接根据 tier_id 找不到奖励,可能是 ID 变更导致的。 - // 这里通过任务配置尝试“模糊匹配”——如果该任务下只有一个该档位级别的奖励 + // 这里通过任务配置尝试"模糊匹配"——如果该任务下只有一个该档位级别的奖励 if len(rewards) == 0 { var tier tcmodel.TaskTier if err := s.repo.GetDbR().First(&tier, tierID).Error; err == nil { diff --git a/internal/service/task_center/service_test.go b/internal/service/task_center/service_test.go new file mode 100644 index 0000000..1ca94c4 --- /dev/null +++ b/internal/service/task_center/service_test.go @@ -0,0 +1,156 @@ +package taskcenter + +import ( + "context" + "testing" + "time" + + "bindbox-game/internal/repository/mysql" + tcmodel "bindbox-game/internal/repository/mysql/task_center" + + "gorm.io/gorm" +) + +func ensureExtraTablesForServiceTest(t *testing.T, db *gorm.DB) { + if !db.Migrator().HasTable("orders") { + if err := db.Exec(`CREATE TABLE orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + status INTEGER NOT NULL DEFAULT 1, + source_type INTEGER NOT NULL DEFAULT 0, + total_amount INTEGER NOT NULL DEFAULT 0, + actual_amount INTEGER NOT NULL DEFAULT 0, + remark TEXT, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME + );`).Error; err != nil { + t.Fatalf("创建 orders 表失败: %v", err) + } + } + if !db.Migrator().HasTable("activity_draw_logs") { + if err := db.Exec(`CREATE TABLE activity_draw_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + issue_id INTEGER NOT NULL + );`).Error; err != nil { + t.Fatalf("创建 activity_draw_logs 表失败: %v", err) + } + } + if !db.Migrator().HasTable("user_invites") { + if err := db.Exec(`CREATE TABLE user_invites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + inviter_id INTEGER NOT NULL, + invitee_id INTEGER NOT NULL, + accumulated_amount INTEGER NOT NULL DEFAULT 0, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, + deleted_at DATETIME + );`).Error; err != nil { + t.Fatalf("创建 user_invites 表失败: %v", err) + } + } +} + +func TestGetUserProgress_TimeWindow_Integration(t *testing.T) { + repo, err := mysql.NewSQLiteRepoForTest() + if err != nil { + t.Fatalf("创建 repo 失败: %v", err) + } + db := repo.GetDbW() + + initTestTables(t, db) + ensureExtraTablesForServiceTest(t, db) + + svc := New(nil, repo, nil, nil, nil) + + now := time.Now() + taskStart := now.Add(-200 * 24 * time.Hour) + taskEnd := now.Add(200 * 24 * time.Hour) + + // 创建一个具有任务有效期的任务 + task := &tcmodel.Task{ + Name: "时效性测试任务", + Description: "测试各档位时效隔离", + Status: 1, + Visibility: 1, + StartTime: &taskStart, + EndTime: &taskEnd, + } + if err := db.Create(task).Error; err != nil { + t.Fatalf("创建任务失败: %v", err) + } + + windows := []string{WindowDaily, WindowWeekly, WindowMonthly, WindowActivityPeriod, WindowLifetime} + + tierIDMap := make(map[string]int64) + for _, w := range windows { + tier := &tcmodel.TaskTier{ + TaskID: task.ID, + Metric: MetricOrderCount, + Operator: OperatorGTE, + Threshold: 1, + Window: w, + ActivityID: 0, + } + if err := db.Create(tier).Error; err != nil { + t.Fatalf("创建档位失败: %v", err) + } + tierIDMap[w] = tier.ID + } + + userID := int64(888) + + // 插入三笔订单与邀请,处于不同时间段 + o1Time := now.Format(time.DateTime) + db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (101, ?, 2, 0, 100, ?)", userID, o1Time) + db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (101, 1)") + db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 901, ?)", userID, o1Time) + + o2Time := now.AddDate(0, -2, 0).Format(time.DateTime) + db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (102, ?, 2, 0, 100, ?)", userID, o2Time) + db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (102, 1)") + db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 902, ?)", userID, o2Time) + + o3Time := now.AddDate(-1, 0, 0).Format(time.DateTime) + db.Exec("INSERT INTO orders (id, user_id, status, source_type, total_amount, created_at) VALUES (103, ?, 2, 0, 100, ?)", userID, o3Time) + db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (103, 1)") + db.Exec("INSERT INTO user_invites (inviter_id, invitee_id, created_at) VALUES (?, 903, ?)", userID, o3Time) + + // 调用统计 + progress, err := svc.GetUserProgress(context.Background(), userID, task.ID) + if err != nil { + t.Fatalf("获取进度失败: %v", err) + } + + // 验证各 Tier 的统计数据符合预期 + for w, tid := range tierIDMap { + tp, ok := progress.TierProgressMap[tid] + if !ok { + t.Errorf("缺少 %s 的进度", w) + continue + } + + var expectedCount int64 + switch w { + case WindowDaily, WindowWeekly, WindowMonthly: + expectedCount = 1 + case WindowActivityPeriod: + expectedCount = 2 // O1, O2 + case WindowLifetime: + expectedCount = 3 // O1, O2, O3 + } + + if tp.OrderCount != expectedCount { + t.Errorf("[%s] OrderCount 不符: Expected %d, Got %d", w, expectedCount, tp.OrderCount) + } else { + t.Logf("[%s] OrderCount 验证成功: %d", w, tp.OrderCount) + } + + if tp.InviteCount != expectedCount { + t.Errorf("[%s] InviteCount 不符: Expected %d, Got %d", w, expectedCount, tp.InviteCount) + } else { + t.Logf("[%s] InviteCount 验证成功: %d", w, tp.InviteCount) + } + } +} diff --git a/internal/service/task_center/task_center_test.go b/internal/service/task_center/task_center_test.go old mode 100644 new mode 100755 index f74e840..d9e5c91 --- a/internal/service/task_center/task_center_test.go +++ b/internal/service/task_center/task_center_test.go @@ -89,6 +89,8 @@ func initTestTables(t *testing.T, db *gorm.DB) { start_time DATETIME, end_time DATETIME, visibility INTEGER NOT NULL DEFAULT 1, + quota INTEGER NOT NULL DEFAULT 0, + claimed_count INTEGER NOT NULL DEFAULT 0, conditions_schema TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP @@ -107,6 +109,8 @@ func initTestTables(t *testing.T, db *gorm.DB) { priority INTEGER NOT NULL DEFAULT 0, activity_id INTEGER NOT NULL DEFAULT 0, extra_params TEXT, + quota INTEGER NOT NULL DEFAULT 0, + claimed_count INTEGER NOT NULL DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP );`).Error; err != nil { @@ -744,7 +748,7 @@ func TestGetUserProgress_ActivityFilter_Integration(t *testing.T) { db.Exec("INSERT INTO user_invites (inviter_id, invitee_id) VALUES (?, 1001)", userID) db.Exec("INSERT INTO user_invites (inviter_id, invitee_id) VALUES (?, 1002)", userID) - // 4. 让其中一个被邀请人(1001)在活动 100 中产生有效订单(使其成为“有效邀请”) + // 4. 让其中一个被邀请人(1001)在活动 100 中产生有效订单(使其成为"有效邀请”) db.Exec("INSERT INTO orders (id, user_id, status, total_amount, source_type, remark) VALUES (10, 1001, 2, 50, 0, ?)", "activity:100|count:1") db.Exec("INSERT INTO activity_draw_logs (order_id, issue_id) VALUES (10, 10)") diff --git a/internal/service/task_center/worker.go b/internal/service/task_center/worker.go old mode 100644 new mode 100755 diff --git a/internal/service/title/assign.go b/internal/service/title/assign.go old mode 100644 new mode 100755 diff --git a/internal/service/title/effect_validate.go b/internal/service/title/effect_validate.go old mode 100644 new mode 100755 diff --git a/internal/service/title/effect_validate_test.go b/internal/service/title/effect_validate_test.go old mode 100644 new mode 100755 diff --git a/internal/service/title/effects_resolver.go b/internal/service/title/effects_resolver.go old mode 100644 new mode 100755 index 8618ac9..0b1f902 --- a/internal/service/title/effects_resolver.go +++ b/internal/service/title/effects_resolver.go @@ -130,7 +130,7 @@ func (s *service) ResolveActiveEffects(ctx context.Context, userID int64, scope } var sc scopesPayload if err := json.Unmarshal([]byte(ef.ScopesJSON), &sc); err != nil { - // 解析失败时按“全局生效”处理 + // 解析失败时按"全局生效”处理 result = append(result, ef) continue } diff --git a/internal/service/user/address_share.go b/internal/service/user/address_share.go old mode 100644 new mode 100755 diff --git a/internal/service/user/addresses.go b/internal/service/user/addresses.go old mode 100644 new mode 100755 diff --git a/internal/service/user/batch_user.go b/internal/service/user/batch_user.go old mode 100644 new mode 100755 diff --git a/internal/service/user/bind_inviter.go b/internal/service/user/bind_inviter.go old mode 100644 new mode 100755 index 75c2df6..c239775 --- a/internal/service/user/bind_inviter.go +++ b/internal/service/user/bind_inviter.go @@ -3,6 +3,7 @@ package user import ( "context" "errors" + "fmt" "bindbox-game/internal/repository/mysql/dao" "bindbox-game/internal/repository/mysql/model" @@ -89,3 +90,97 @@ func (s *service) BindInviter(ctx context.Context, userID int64, in BindInviterI return result, nil } + +// AdminBindInviterInput 管理端强制绑定/修改/解绑邀请人输入 +type AdminBindInviterInput struct { + TargetUserID int64 // 被操作用户ID + InviterUserID int64 // 新邀请人ID(0 表示解绑) + OperatorID int64 // 操作管理员ID +} + +// AdminBindInviterOutput 管理端操作结果 +type AdminBindInviterOutput struct { + OldInviterID int64 `json:"old_inviter_id"` + OldInviterNickname string `json:"old_inviter_nickname"` + NewInviterID int64 `json:"new_inviter_id"` + NewInviterNickname string `json:"new_inviter_nickname"` +} + +// AdminBindInviter 管理端强制设置/修改/解绑用户的邀请人 +func (s *service) AdminBindInviter(ctx context.Context, in AdminBindInviterInput) (*AdminBindInviterOutput, error) { + var result *AdminBindInviterOutput + + err := s.writeDB.Transaction(func(tx *dao.Query) error { + // 1. 加锁获取目标用户 + targetUser, err := tx.Users.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}). + Where(tx.Users.ID.Eq(in.TargetUserID)).First() + if err != nil { + return fmt.Errorf("target_user_not_found") + } + + // 2. 不允许自己绑自己 + if in.InviterUserID > 0 && in.InviterUserID == in.TargetUserID { + return ErrCannotInviteSelf + } + + // 3. 查询旧邀请人昵称 + oldInviterNickname := "" + if targetUser.InviterID > 0 { + if oldInviter, e := tx.Users.WithContext(ctx). + Where(tx.Users.ID.Eq(targetUser.InviterID)).First(); e == nil { + oldInviterNickname = oldInviter.Nickname + } + } + + // 4. 查询新邀请人(解绑时跳过) + newInviterNickname := "" + if in.InviterUserID > 0 { + newInviter, e := tx.Users.WithContext(ctx). + Where(tx.Users.ID.Eq(in.InviterUserID)).First() + if e != nil { + return fmt.Errorf("inviter_user_not_found") + } + newInviterNickname = newInviter.Nickname + } + + // 5. 更新 users.inviter_id + if _, err := tx.Users.WithContext(ctx). + Where(tx.Users.ID.Eq(in.TargetUserID)). + Update(tx.Users.InviterID, in.InviterUserID); err != nil { + return err + } + + // 6. 同步 user_invites:先删旧记录,再插新记录 + if targetUser.InviterID > 0 { + if _, err := tx.UserInvites.WithContext(ctx). + Where(tx.UserInvites.InviteeID.Eq(in.TargetUserID)). + Where(tx.UserInvites.InviterID.Eq(targetUser.InviterID)). + Delete(); err != nil { + return err + } + } + if in.InviterUserID > 0 { + newInvite := &model.UserInvites{ + InviterID: in.InviterUserID, + InviteeID: in.TargetUserID, + InviteCode: fmt.Sprintf("admin_op_by_%d", in.OperatorID), + } + if err := tx.UserInvites.WithContext(ctx).Create(newInvite); err != nil { + return err + } + } + + result = &AdminBindInviterOutput{ + OldInviterID: targetUser.InviterID, + OldInviterNickname: oldInviterNickname, + NewInviterID: in.InviterUserID, + NewInviterNickname: newInviterNickname, + } + return nil + }) + + if err != nil { + return nil, err + } + return result, nil +} diff --git a/internal/service/user/cancel_shipping.go b/internal/service/user/cancel_shipping.go old mode 100644 new mode 100755 index a9e7325..477f9ee --- a/internal/service/user/cancel_shipping.go +++ b/internal/service/user/cancel_shipping.go @@ -1,14 +1,18 @@ package user import ( - "bindbox-game/internal/repository/mysql/dao" "context" "fmt" + + "bindbox-game/internal/repository/mysql/dao" ) // CancelShipping 取消发货申请 // 支持按单个资产ID取消,或按批次号批量取消 // 返回成功取消的记录数 +// +// 转赠场景说明:A 赠送 B 后,shipping_records.user_id 和 user_inventory.user_id +// 均已更新为 B,因此 B 可以正常取消;A 无法取消(shipping_record 属于 B,查不到)。 func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID int64, batchNo string) (int64, error) { var cancelledCount int64 @@ -16,13 +20,15 @@ func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID var records []*struct { ID int64 InventoryID int64 + // 记录 shipping_record 中存储的实际 user_id,用于后续恢复 inventory + RecordUserID int64 } // 根据参数查询待取消的发货记录 if batchNo != "" { - // 按批次号查询 + // 按批次号查询:仅允许取消属于自己的发货记录 rows, err := tx.ShippingRecords.WithContext(ctx). - Select(tx.ShippingRecords.ID, tx.ShippingRecords.InventoryID). + Select(tx.ShippingRecords.ID, tx.ShippingRecords.InventoryID, tx.ShippingRecords.UserID). Where(tx.ShippingRecords.BatchNo.Eq(batchNo)). Where(tx.ShippingRecords.UserID.Eq(userID)). Where(tx.ShippingRecords.Status.Eq(1)). // 待发货状态 @@ -32,24 +38,30 @@ func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID } for _, r := range rows { records = append(records, &struct { - ID int64 - InventoryID int64 - }{ID: r.ID, InventoryID: r.InventoryID}) + ID int64 + InventoryID int64 + RecordUserID int64 + }{ID: r.ID, InventoryID: r.InventoryID, RecordUserID: r.UserID}) } } else if inventoryID > 0 { - // 按单个资产ID查询 + // 按单个资产ID查询:先不过滤 user_id,取到记录后比对归属 + // 避免两次 DB 查询,同时能精确区分"不存在"和"无权限"两种情况 sr, err := tx.ShippingRecords.WithContext(ctx). Where(tx.ShippingRecords.InventoryID.Eq(inventoryID)). - Where(tx.ShippingRecords.UserID.Eq(userID)). Where(tx.ShippingRecords.Status.Eq(1)). First() if err != nil { return fmt.Errorf("shipping record not found or already processed") } + if sr.UserID != userID { + // 转赠场景下 shipping_record.user_id = B(受赠方),A 无权取消 + return fmt.Errorf("no_permission: shipping record belongs to another user") + } records = append(records, &struct { - ID int64 - InventoryID int64 - }{ID: sr.ID, InventoryID: sr.InventoryID}) + ID int64 + InventoryID int64 + RecordUserID int64 + }{ID: sr.ID, InventoryID: sr.InventoryID, RecordUserID: sr.UserID}) } if len(records) == 0 { @@ -58,22 +70,37 @@ func (s *service) CancelShipping(ctx context.Context, userID int64, inventoryID // 批量处理每条记录 for _, rec := range records { - // 更新发货记录状态为已取消 (status=5) - if _, err := tx.ShippingRecords.WithContext(ctx). + // 1. 更新发货记录状态为已取消 (status=5) + res, err := tx.ShippingRecords.WithContext(ctx). Where(tx.ShippingRecords.ID.Eq(rec.ID)). - Update(tx.ShippingRecords.Status, 5); err != nil { - return err + Where(tx.ShippingRecords.Status.Eq(1)). // 防止并发重复取消 + Update(tx.ShippingRecords.Status, 5) + if err != nil { + return fmt.Errorf("update shipping record status failed: %w", err) + } + if res.RowsAffected == 0 { + // 并发场景:记录已被其他请求取消,跳过 + continue } - // 恢复库存状态为可用 (status=1) 并清空 shipping_no + // 2. 恢复库存状态为可用 (status=1) 并清空 shipping_no + // 关键:WHERE user_id 使用 rec.RecordUserID(即 shipping_record 中记录的归属人) + // 而不是函数参数 userID,保证转赠场景下 inventory.user_id 与条件匹配。 + // 在转赠场景中 rec.RecordUserID == B == inventory.user_id,两者一致。 remark := fmt.Sprintf("|shipping_cancelled_by_user:%d", userID) - if err := tx.UserInventory.WithContext(ctx).UnderlyingDB().Exec( + dbResult := tx.UserInventory.WithContext(ctx).UnderlyingDB().Exec( "UPDATE user_inventory SET status=1, shipping_no='', remark=CONCAT(IFNULL(remark,''), ?) WHERE id=? AND user_id=?", remark, rec.InventoryID, - userID, - ).Error; err != nil { - return err + rec.RecordUserID, // 使用 shipping_record 中记录的 user_id,而非调用方 userID + ) + if dbResult.Error != nil { + return fmt.Errorf("restore inventory status failed: %w", dbResult.Error) + } + if dbResult.RowsAffected == 0 { + // inventory 未能恢复,强制回滚事务,防止数据不一致(物品"丢失") + return fmt.Errorf("restore inventory failed: inventory id=%d user_id=%d not matched or status unexpected", + rec.InventoryID, rec.RecordUserID) } cancelledCount++ diff --git a/internal/service/user/coupon_add.go b/internal/service/user/coupon_add.go old mode 100644 new mode 100755 diff --git a/internal/service/user/coupon_transfer.go b/internal/service/user/coupon_transfer.go old mode 100644 new mode 100755 diff --git a/internal/service/user/coupons_list.go b/internal/service/user/coupons_list.go old mode 100644 new mode 100755 index af4a811..1a4befc --- a/internal/service/user/coupons_list.go +++ b/internal/service/user/coupons_list.go @@ -2,6 +2,7 @@ package user import ( "context" + "time" "bindbox-game/internal/repository/mysql/model" ) @@ -41,6 +42,7 @@ func (s *service) ListCouponsByStatus(ctx context.Context, userID int64, status func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32, page, pageSize int) (items []*model.UserCoupons, total int64, err error) { u := s.readDB.UserCoupons c := s.readDB.SystemCoupons + now := time.Now() tableName := u.TableName() sysTableName := c.TableName() @@ -51,12 +53,12 @@ func (s *service) ListAppCoupons(ctx context.Context, userID int64, status int32 Where("`"+tableName+"`.user_id = ?", userID) switch status { - case 1: // 有效:status=1 且余额>0(包含未使用和使用中) - db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0) - case 2: // 已失效:余额用完(status=1且balance=0) 或 已使用(status=2) 或 已过期(status=3) - db = db.Where("("+tableName+".status = ? AND "+tableName+".balance_amount = ?) OR "+tableName+".status IN (?,?)", 1, 0, 2, 3) + case 1: // 有效:余额 > 0 且 未过期 + db = db.Where(tableName+".balance_amount > ? AND "+tableName+".valid_end > ? AND "+tableName+".status IN (?, ?, ?)", 0, now, 1, 2, 4) + case 2: // 已失效:余额用完 OR 已标记过期 OR 已过截止时间 + db = db.Where("("+tableName+".balance_amount = ?) OR "+tableName+".status = ? OR "+tableName+".valid_end <= ?", 0, 3, now) default: - db = db.Where(tableName+".status = ? AND "+tableName+".balance_amount > ?", 1, 0) + db = db.Where(tableName+".balance_amount > ? AND "+tableName+".valid_end > ? AND "+tableName+".status IN (?, ?, ?)", 0, now, 1, 2, 4) } if err = db.Count(&total).Error; err != nil { diff --git a/internal/service/user/error_test.go b/internal/service/user/error_test.go old mode 100644 new mode 100755 diff --git a/internal/service/user/expiration_task.go b/internal/service/user/expiration_task.go old mode 100644 new mode 100755 diff --git a/internal/service/user/game_pass.go b/internal/service/user/game_pass.go old mode 100644 new mode 100755 diff --git a/internal/service/user/inventory_list.go b/internal/service/user/inventory_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/invite_code.go b/internal/service/user/invite_code.go old mode 100644 new mode 100755 diff --git a/internal/service/user/invites_list.go b/internal/service/user/invites_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/item_card_add.go b/internal/service/user/item_card_add.go old mode 100644 new mode 100755 diff --git a/internal/service/user/item_card_redeem.go b/internal/service/user/item_card_redeem.go old mode 100644 new mode 100755 diff --git a/internal/service/user/item_card_uses_list.go b/internal/service/user/item_card_uses_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/item_cards_list.go b/internal/service/user/item_cards_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/login_douyin.go b/internal/service/user/login_douyin.go old mode 100644 new mode 100755 diff --git a/internal/service/user/login_weixin.go b/internal/service/user/login_weixin.go old mode 100644 new mode 100755 diff --git a/internal/service/user/order_coupons.go b/internal/service/user/order_coupons.go old mode 100644 new mode 100755 index 9a6d4d1..ed820eb --- a/internal/service/user/order_coupons.go +++ b/internal/service/user/order_coupons.go @@ -68,53 +68,33 @@ func (s *service) DeductCouponsForPaidOrder(ctx context.Context, tx *dao.Query, } // 2. 确认预扣:将 status=4 (预扣中) 更新为最终状态 - if r.DiscountType == 1 { // 金额券 - // 容错:如果券因历史 Bug 仍为 status=4,根据余额修正为正确状态 - if r.Status == 4 { - finalStatus := int32(1) // 还有余额 → 未使用 - if r.BalanceAmount <= 0 { - finalStatus = 2 // 余额已扣完 → 已使用 - } - db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec( - "UPDATE user_coupons SET status = ? WHERE id = ? AND status = 4", - finalStatus, r.UserCouponID) - } - - // 记录确认流水 - ledger := &model.UserCouponLedger{ - UserID: userID, - UserCouponID: r.UserCouponID, - ChangeAmount: 0, // 确认操作不改变余额 - BalanceAfter: r.BalanceAmount, - OrderID: orderID, - Action: "confirm", - CreatedAt: time.Now(), - } - _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) - - } else { // 满减/折扣券:一次性核销 - res := db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET status = 2 - WHERE id = ? AND status = 4 - `, r.UserCouponID) - - if res.Error != nil { - return fmt.Errorf("failed to confirm coupon: %w", res.Error) - } - - // 记录确认流水 - ledger := &model.UserCouponLedger{ - UserID: userID, - UserCouponID: r.UserCouponID, - ChangeAmount: 0, - BalanceAfter: 0, - OrderID: orderID, - Action: "confirm", - CreatedAt: time.Now(), - } - _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) + // 统一逻辑:只要还有余额,状态回转到 1 (未使用/有余额);否则设为 2 (已用完) + finalStatus := int32(1) + if r.BalanceAmount <= 0 { + finalStatus = 2 } + + res := db.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` + UPDATE user_coupons + SET status = ? + WHERE id = ? AND status = 4 + `, finalStatus, r.UserCouponID) + + if res.Error != nil { + return fmt.Errorf("failed to confirm coupon: %w", res.Error) + } + + // 记录确认流水 + ledger := &model.UserCouponLedger{ + UserID: userID, + UserCouponID: r.UserCouponID, + ChangeAmount: 0, + BalanceAfter: r.BalanceAmount, + OrderID: orderID, + Action: "confirm", + CreatedAt: time.Now(), + } + _ = db.UserCouponLedger.WithContext(ctx).Create(ledger) } return nil } diff --git a/internal/service/user/order_timeout.go b/internal/service/user/order_timeout.go old mode 100644 new mode 100755 index 00ef993..f41b49d --- a/internal/service/user/order_timeout.go +++ b/internal/service/user/order_timeout.go @@ -79,39 +79,27 @@ func (s *service) cancelExpiredOrder(ctx context.Context, orderID int64, userID `, orderID, couponID).Scan(&cr) if cr.AppliedAmount > 0 { - if cr.DiscountType == 1 { - // 金额券:恢复余额 - // 此时 coupon 可能 status=1 (Active) 或 2 (Used/Exhausted) - // 不需要 status=4 限制,因为下单时已直接扣减 - res := s.writeDB.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET balance_amount = balance_amount + ?, - status = 1, - used_order_id = NULL, - used_at = NULL - WHERE id = ? - `, cr.AppliedAmount, couponID) + // 统一回退逻辑:无论券种,统统将预扣金额加回余额,并重置状态为 1 (未使用/有余额) + res := s.writeDB.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` + UPDATE user_coupons + SET balance_amount = balance_amount + ?, + status = 1, + used_order_id = NULL, + used_at = NULL + WHERE id = ? AND status = 4 + `, cr.AppliedAmount, couponID) - if res.RowsAffected > 0 { - // 记录流水 - s.writeDB.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ - UserID: userID, - UserCouponID: couponID, - ChangeAmount: cr.AppliedAmount, - OrderID: orderID, - Action: "timeout_refund", - CreatedAt: time.Now(), - }) - } - } else { - // 满减/折扣券:恢复状态 - s.writeDB.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET status = 1, - used_order_id = NULL, - used_at = NULL - WHERE id = ? AND status = 4 - `, couponID) + if res.RowsAffected > 0 { + // 记录流水 + s.writeDB.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ + UserID: userID, + UserCouponID: couponID, + ChangeAmount: cr.AppliedAmount, + BalanceAfter: 0, // 异步流水无法实时算最新,标记 0 或查询后填入,这里暂保持 Action + OrderID: orderID, + Action: "timeout_refund", + CreatedAt: time.Now(), + }) } } } diff --git a/internal/service/user/orders_action.go b/internal/service/user/orders_action.go old mode 100644 new mode 100755 index ebba616..9f61d0e --- a/internal/service/user/orders_action.go +++ b/internal/service/user/orders_action.go @@ -59,52 +59,32 @@ func (s *service) CancelOrder(ctx context.Context, userID int64, orderID int64, // 4. 退还优惠券(恢复预扣的余额和状态) if order.CouponID > 0 { - // 查询订单使用的优惠券及扣减金额 - type couponRow struct { + var oc struct { AppliedAmount int64 - DiscountType int32 } - var cr couponRow - _ = tx.OrderCoupons.WithContext(ctx).UnderlyingDB().Raw(` - SELECT oc.applied_amount AS applied_amount, sc.discount_type AS discount_type - FROM order_coupons oc - JOIN user_coupons uc ON uc.id = oc.user_coupon_id - JOIN system_coupons sc ON sc.id = uc.coupon_id - WHERE oc.order_id = ? AND oc.user_coupon_id = ? - `, order.ID, order.CouponID).Scan(&cr) + // 获取该订单实际扣减的优惠券金额 + _ = tx.OrderCoupons.WithContext(ctx).Where(tx.OrderCoupons.OrderID.Eq(order.ID), tx.OrderCoupons.UserCouponID.Eq(order.CouponID)).Scan(&oc) - if cr.AppliedAmount > 0 { - if cr.DiscountType == 1 { // 金额券:恢复余额 - res := tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET balance_amount = balance_amount + ?, - status = 1, - used_order_id = NULL, - used_at = NULL - WHERE id = ? - `, cr.AppliedAmount, order.CouponID) + // 执行原子回退:增加余额 + 重置状态 + 清除占用订单 + res := tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` + UPDATE user_coupons + SET balance_amount = balance_amount + ?, + status = 1, + used_order_id = 0, + used_at = NULL + WHERE id = ? AND used_order_id = ? + `, oc.AppliedAmount, order.CouponID, order.ID) - if res.RowsAffected > 0 { - // 记录退还流水 - _ = tx.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ - UserID: userID, - UserCouponID: order.CouponID, - ChangeAmount: cr.AppliedAmount, - BalanceAfter: 0, // 简化处理,后续可查询精确值 - OrderID: order.ID, - Action: "refund", - CreatedAt: time.Now(), - }) - } - } else { // 满减/折扣券:恢复状态 - tx.UserCoupons.WithContext(ctx).UnderlyingDB().Exec(` - UPDATE user_coupons - SET status = 1, - used_order_id = NULL, - used_at = NULL - WHERE id = ? AND status = 4 - `, order.CouponID) - } + if res.RowsAffected > 0 { + // 记录退还流水 + _ = tx.UserCouponLedger.WithContext(ctx).Create(&model.UserCouponLedger{ + UserID: userID, + UserCouponID: order.CouponID, + ChangeAmount: oc.AppliedAmount, + OrderID: order.ID, + Action: "cancel_refund", + CreatedAt: time.Now(), + }) } } diff --git a/internal/service/user/orders_auto_cancel_worker.go b/internal/service/user/orders_auto_cancel_worker.go new file mode 100644 index 0000000..b42375c --- /dev/null +++ b/internal/service/user/orders_auto_cancel_worker.go @@ -0,0 +1,119 @@ +package user + +import ( + "context" + "time" + + "bindbox-game/internal/pkg/logger" + "bindbox-game/internal/repository/mysql" + "bindbox-game/internal/repository/mysql/dao" + + "go.uber.org/zap" +) + +const ( + autoCancelInterval = 30 * time.Second // 每 30 秒扫描一次 + autoCancelThreshold = 15 * time.Minute // 超过 15 分钟未支付则取消 + autoCancelReason = "auto_cancel_timeout" + autoCancelBatchSize = 100 // 每次最多处理 100 条,避免一次性处理过多 +) + +// StartAutoCancelWorker 启动订单自动取消后台任务(包级别函数,与 StartExpirationCheck 风格一致) +func StartAutoCancelWorker(l logger.CustomLogger, repo mysql.Repo) { + svc := &service{ + logger: l, + readDB: dao.Use(repo.GetDbR()), + writeDB: dao.Use(repo.GetDbW()), + repo: repo, + } + go svc.runAutoCancelLoop(context.Background()) +} + +// StartAutoCancelWorker 也作为 service 的方法提供,以满足 Service interface +func (s *service) StartAutoCancelWorker(ctx context.Context) { + go s.runAutoCancelLoop(ctx) +} + +func (s *service) runAutoCancelLoop(ctx context.Context) { + ticker := time.NewTicker(autoCancelInterval) + defer ticker.Stop() + + s.logger.Info("[AutoCancel] 订单自动取消 worker 已启动", + zap.Duration("interval", autoCancelInterval), + zap.Duration("threshold", autoCancelThreshold), + ) + + // 启动后立即执行一次,不等第一个 tick + s.scanAndCancelExpiredOrders(ctx) + + for { + select { + case <-ctx.Done(): + s.logger.Info("[AutoCancel] 订单自动取消 worker 已停止") + return + case <-ticker.C: + s.scanAndCancelExpiredOrders(ctx) + } + } +} + +func (s *service) scanAndCancelExpiredOrders(ctx context.Context) { + deadline := time.Now().Add(-autoCancelThreshold) + + // 查询 status=1(待支付)且 created_at < now-15min 的订单 + // 使用主库(writeDB)避免主从延迟导致漏扫刚写入的超时订单 + // ORDER BY created_at ASC 保证最老订单优先处理,避免新订单持续涌入时老订单被跳过 + orders, err := s.writeDB.Orders.WithContext(ctx). + Where( + s.writeDB.Orders.Status.Eq(1), + s.writeDB.Orders.CreatedAt.Lt(deadline), + ). + Order(s.writeDB.Orders.CreatedAt). + Limit(autoCancelBatchSize). + Find() + + if err != nil { + s.logger.Error("[AutoCancel] 查询超时订单失败", zap.Error(err)) + return + } + + if len(orders) == 0 { + return + } + + s.logger.Info("[AutoCancel] 发现超时待支付订单,开始逐条取消", + zap.Int("count", len(orders)), + zap.Time("deadline", deadline), + ) + + successCount := 0 + failCount := 0 + + for _, order := range orders { + _, err := s.CancelOrder(ctx, order.UserID, order.ID, autoCancelReason) + if err != nil { + // 单条失败不影响其他订单,记录错误继续 + s.logger.Error("[AutoCancel] 取消订单失败", + zap.Int64("order_id", order.ID), + zap.Int64("user_id", order.UserID), + zap.String("order_no", order.OrderNo), + zap.Error(err), + ) + failCount++ + continue + } + + s.logger.Info("[AutoCancel] 订单已自动取消", + zap.Int64("order_id", order.ID), + zap.Int64("user_id", order.UserID), + zap.String("order_no", order.OrderNo), + zap.Time("created_at", order.CreatedAt), + ) + successCount++ + } + + s.logger.Info("[AutoCancel] 本轮取消完成", + zap.Int("success", successCount), + zap.Int("failed", failCount), + ) +} diff --git a/internal/service/user/orders_list.go b/internal/service/user/orders_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_add.go b/internal/service/user/points_add.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_balance.go b/internal/service/user/points_balance.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_consume.go b/internal/service/user/points_consume.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_consume_generic.go b/internal/service/user/points_consume_generic.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_convert.go b/internal/service/user/points_convert.go old mode 100644 new mode 100755 diff --git a/internal/service/user/points_ledger_list.go b/internal/service/user/points_ledger_list.go old mode 100644 new mode 100755 diff --git a/internal/service/user/profile.go b/internal/service/user/profile.go old mode 100644 new mode 100755 diff --git a/internal/service/user/request_shipping_batch_test.go b/internal/service/user/request_shipping_batch_test.go old mode 100644 new mode 100755 diff --git a/internal/service/user/reward_grant.go b/internal/service/user/reward_grant.go old mode 100644 new mode 100755 index 9187dd1..4840ee9 --- a/internal/service/user/reward_grant.go +++ b/internal/service/user/reward_grant.go @@ -22,6 +22,7 @@ type GrantRewardRequest struct { Remark string `json:"remark,omitempty"` // 备注 PointsAmount int64 `json:"points_amount,omitempty"` // 消耗积分 SourceType *int32 `json:"source_type,omitempty"` // 订单来源(可选,默认3) + ExtOrderID string `json:"ext_order_id,omitempty"` // 外部订单号(可选) } // GrantRewardResponse 奖励发放响应 @@ -102,6 +103,7 @@ func (s *service) GrantReward(ctx context.Context, userID int64, req GrantReward PaidAt: now, // 设置支付时间为当前时间 CancelledAt: minValidTime, // 设置取消时间为最小有效时间,避免MySQL错误 Remark: req.Remark, + ExtOrderID: req.ExtOrderID, CreatedAt: now, UpdatedAt: now, } diff --git a/internal/service/user/reward_grant_batch.go b/internal/service/user/reward_grant_batch.go old mode 100644 new mode 100755 diff --git a/internal/service/user/shipping_groups.go b/internal/service/user/shipping_groups.go old mode 100644 new mode 100755 diff --git a/internal/service/user/sms_login.go b/internal/service/user/sms_login.go old mode 100644 new mode 100755 diff --git a/internal/service/user/stats.go b/internal/service/user/stats.go old mode 100644 new mode 100755 diff --git a/internal/service/user/user.go b/internal/service/user/user.go old mode 100644 new mode 100755 index 326731f..91641cc --- a/internal/service/user/user.go +++ b/internal/service/user/user.go @@ -82,8 +82,12 @@ type Service interface { GrantGamePass(ctx context.Context, userID int64, packageID int64, count int32, orderNo string) error // 邀请人绑定 BindInviter(ctx context.Context, userID int64, in BindInviterInput) (*BindInviterOutput, error) + // 管理端强制绑定/修改/解绑邀请人 + AdminBindInviter(ctx context.Context, in AdminBindInviterInput) (*AdminBindInviterOutput, error) // 优惠券转赠 TransferCoupon(ctx context.Context, fromUserID, toUserID, userCouponID int64) error + // 订单自动取消 worker(挂在 Service 上,供 main.go 按需调用) + StartAutoCancelWorker(ctx context.Context) } type service struct { diff --git a/main.go b/main.go old mode 100644 new mode 100755 index 731d275..a924377 --- a/main.go +++ b/main.go @@ -100,6 +100,7 @@ func main() { activitysvc.StartScheduledSettlement(customLogger, dbRepo, redis.GetClient()) usersvc.StartExpirationCheck(customLogger, dbRepo) + usersvc.StartAutoCancelWorker(customLogger, dbRepo) // 启动抖店订单同步定时任务 syscfgSvc := syscfgsvc.New(customLogger, dbRepo) diff --git a/migrations/006_game_tickets.sql b/migrations/006_game_tickets.sql old mode 100644 new mode 100755 diff --git a/migrations/20251218_add_draw_logs_unique_index.sql b/migrations/20251218_add_draw_logs_unique_index.sql old mode 100644 new mode 100755 diff --git a/migrations/20251222_add_order_coupon_and_card.sql b/migrations/20251222_add_order_coupon_and_card.sql old mode 100644 new mode 100755 diff --git a/migrations/20251223_add_user_invites_effective_columns.sql b/migrations/20251223_add_user_invites_effective_columns.sql old mode 100644 new mode 100755 diff --git a/migrations/20251223_fix_invite_count_comment.sql b/migrations/20251223_fix_invite_count_comment.sql old mode 100644 new mode 100755 diff --git a/migrations/20251226_add_draw_index_unique.sql b/migrations/20251226_add_draw_index_unique.sql old mode 100644 new mode 100755 diff --git a/migrations/20251226_add_order_snapshots.sql b/migrations/20251226_add_order_snapshots.sql old mode 100644 new mode 100755 diff --git a/migrations/20251229_douyin_orders.sql b/migrations/20251229_douyin_orders.sql old mode 100644 new mode 100755 diff --git a/migrations/20260105_douyin_product_rewards.sql b/migrations/20260105_douyin_product_rewards.sql old mode 100644 new mode 100755 diff --git a/migrations/20260110_livestream_tables.sql b/migrations/20260110_livestream_tables.sql old mode 100644 new mode 100755 diff --git a/migrations/20260117_livestream_commitment.sql b/migrations/20260117_livestream_commitment.sql old mode 100644 new mode 100755 diff --git a/migrations/20260118_douyin_blacklist.sql b/migrations/20260118_douyin_blacklist.sql old mode 100644 new mode 100755 diff --git a/migrations/20260121_add_order_coupon_unique_index.sql b/migrations/20260121_add_order_coupon_unique_index.sql old mode 100644 new mode 100755 diff --git a/migrations/20260121_add_user_remark.sql b/migrations/20260121_add_user_remark.sql old mode 100644 new mode 100755 diff --git a/migrations/20260121_reconcile_coupon_data.sql b/migrations/20260121_reconcile_coupon_data.sql old mode 100644 new mode 100755 index 9c02d9d..785d913 --- a/migrations/20260121_reconcile_coupon_data.sql +++ b/migrations/20260121_reconcile_coupon_data.sql @@ -13,7 +13,7 @@ WHERE sc.discount_type = 1 -- 仅限余额券 AND uc.balance_amount != (sc.discount_value - GREATEST(IFNULL(oc_agg.used_sum, 0), IFNULL(l_agg.used_sum, 0))); -- 2. 逻辑 A: 如果余额已经扣完 (balance_amount = 0),状态必须为 2 (已使用) --- 优先级最高,无论是否到期,只要用完了就是“已使用” +-- 优先级最高,无论是否到期,只要用完了就是"已使用” UPDATE user_coupons uc SET uc.status = 2 WHERE uc.balance_amount = 0 diff --git a/migrations/20260121_repair_coupon_data.sql b/migrations/20260121_repair_coupon_data.sql old mode 100644 new mode 100755 index 128ac56..9cc1fec --- a/migrations/20260121_repair_coupon_data.sql +++ b/migrations/20260121_repair_coupon_data.sql @@ -1,4 +1,4 @@ --- 1. 修复【金额券】:余额已为 0,且尚未到期,但状态被错误标记为“已过期”(3) 的券,统一修复为“已使用”(2) +-- 1. 修复【金额券】:余额已为 0,且尚未到期,但状态被错误标记为"已过期”(3) 的券,统一修复为"已使用”(2) UPDATE user_coupons SET status = 2, used_at = IFNULL(used_at, updated_at) @@ -6,7 +6,7 @@ WHERE balance_amount = 0 AND status = 3 AND valid_end > NOW(); --- 2. 修复【核销记录一致性】:已经在 order_coupons 表中有抵扣记录,但状态仍为“已过期”(3) 的券 +-- 2. 修复【核销记录一致性】:已经在 order_coupons 表中有抵扣记录,但状态仍为"已过期”(3) 的券 UPDATE user_coupons uc JOIN order_coupons oc ON uc.id = oc.user_coupon_id SET uc.status = 2, diff --git a/migrations/20260129_add_douyin_orders_fields.sql b/migrations/20260129_add_douyin_orders_fields.sql old mode 100644 new mode 100755 diff --git a/migrations/20260129_add_prize_reward_config.sql b/migrations/20260129_add_prize_reward_config.sql old mode 100644 new mode 100755 diff --git a/migrations/20260129_backfill_product_ids.sql b/migrations/20260129_backfill_product_ids.sql old mode 100644 new mode 100755 diff --git a/migrations/20260130_add_activity_order_rewards.sql b/migrations/20260130_add_activity_order_rewards.sql old mode 100644 new mode 100755 diff --git a/migrations/20260130_full_livestream_sync.sql b/migrations/20260130_full_livestream_sync.sql old mode 100644 new mode 100755 diff --git a/migrations/20260130_rollback_prize_reward_config.sql b/migrations/20260130_rollback_prize_reward_config.sql old mode 100644 new mode 100755 diff --git a/migrations/20260131_product_rewards_multi_rules.sql b/migrations/20260131_product_rewards_multi_rules.sql old mode 100644 new mode 100755 diff --git a/migrations/20260203_add_product_id_to_draw_logs.sql b/migrations/20260203_add_product_id_to_draw_logs.sql old mode 100644 new mode 100755 diff --git a/migrations/20260206_add_task_tier_quota.sql b/migrations/20260206_add_task_tier_quota.sql old mode 100644 new mode 100755 diff --git a/migrations/20260217_add_coupon_show_in_miniapp.sql b/migrations/20260217_add_coupon_show_in_miniapp.sql old mode 100644 new mode 100755 diff --git a/migrations/20260218_add_payer_openid_to_payment_transactions.sql b/migrations/20260218_add_payer_openid_to_payment_transactions.sql old mode 100644 new mode 100755 diff --git a/migrations/20260218_add_product_show_in_miniapp.sql b/migrations/20260218_add_product_show_in_miniapp.sql old mode 100644 new mode 100755 diff --git a/migrations/20260219_add_ext_order_id_to_orders.sql b/migrations/20260219_add_ext_order_id_to_orders.sql new file mode 100755 index 0000000..5b82968 --- /dev/null +++ b/migrations/20260219_add_ext_order_id_to_orders.sql @@ -0,0 +1,2 @@ +ALTER TABLE `orders` ADD COLUMN `ext_order_id` varchar(64) DEFAULT '' COMMENT '外部订单号(如抖店单号)' AFTER `remark`; +ALTER TABLE `orders` ADD INDEX `idx_ext_order_id` (`ext_order_id`); diff --git a/migrations/matching_game_test_data.sql b/migrations/matching_game_test_data.sql old mode 100644 new mode 100755 diff --git a/migrations/task_level_quota.sql b/migrations/task_level_quota.sql old mode 100644 new mode 100755 diff --git a/scripts/swagger.bat b/scripts/swagger.bat old mode 100644 new mode 100755 diff --git a/tools/.DS_Store b/tools/.DS_Store old mode 100644 new mode 100755 diff --git a/tools/livestream_lottery_analyzer/main.go b/tools/livestream_lottery_analyzer/main.go old mode 100644 new mode 100755 diff --git a/tools/lottery_data_analyzer/main.go b/tools/lottery_data_analyzer/main.go old mode 100644 new mode 100755 diff --git a/tools/lottery_probability_checker/main.go b/tools/lottery_probability_checker/main.go old mode 100644 new mode 100755 diff --git a/tools/lottery_verifier/main.go b/tools/lottery_verifier/main.go old mode 100644 new mode 100755 diff --git a/tools/lottery_verifier/verify.go b/tools/lottery_verifier/verify.go old mode 100644 new mode 100755 diff --git a/tools/lottery_verifier_web/index.html b/tools/lottery_verifier_web/index.html old mode 100644 new mode 100755 diff --git a/tools/query_order/main.go b/tools/query_order/main.go old mode 100644 new mode 100755 diff --git a/tools/quick_check/main.go b/tools/quick_check/main.go old mode 100644 new mode 100755 diff --git a/tools/test_matchmaker/go.mod b/tools/test_matchmaker/go.mod old mode 100644 new mode 100755 diff --git a/tools/test_matchmaker/go.sum b/tools/test_matchmaker/go.sum old mode 100644 new mode 100755 diff --git a/tools/test_matchmaker/main.go b/tools/test_matchmaker/main.go old mode 100644 new mode 100755 diff --git a/tools/verify_seed/main.go b/tools/verify_seed/main.go old mode 100644 new mode 100755 diff --git a/tools/wechat_debug/main.go b/tools/wechat_debug/main.go old mode 100644 new mode 100755